fix: unify connection failure screens and rebuild stale local plugins

This commit is contained in:
LukeGus committed 2026-10-06 15:21:44 -05:00
1 parent a72aa5b57b
commit 49fc241e06
9 files changed
+262 -29

No files matched your search

+13 -2
View File
@@ -7,10 +7,12 @@
* pinned sha256 and unpacked.
*
* For development, `--local <dir>` (or TERMIX_LOCAL_PLUGINS) points at a
* folder of plugin repos like ../Termix-Plugins. Any plugin built there (has
* dist/) is copied in place of its pin, so local plugin changes show up.
* folder of plugin repos like ../Termix-Plugins. A bundled plugin there whose
* sources changed since its last build is rebuilt first, then any built one
* (has dist/) is copied in place of its pin, so local plugin changes show up.
*/
const { execSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const {
@@ -18,6 +20,7 @@ const {
fetchArtifact,
extractArtifact,
findLocalPluginBuilds,
rebuildLocalPlugins,
copyLocalBuild,
} = require("./lib/bundled-plugins.cjs");
@@ -30,6 +33,14 @@ async function main() {
const localArg =
flag !== -1 ? process.argv[flag + 1] : process.env.TERMIX_LOCAL_PLUGINS;
const localDir = localArg ? path.resolve(root, localArg) : null;
rebuildLocalPlugins(
localDir,
new Set(plugins.map((p) => p.id)),
(repo, id) => {
console.log(`building ${id}`);
execSync("npm run build", { cwd: repo, stdio: "inherit" });
},
);
const local = findLocalPluginBuilds(localDir);
fs.rmSync(destination, { recursive: true, force: true });
+51
View File
@@ -11,6 +11,8 @@ import {
extractArtifact,
pinsFromIndex,
findLocalPluginBuilds,
isLocalBuildStale,
rebuildLocalPlugins,
copyLocalBuild,
} from "./lib/bundled-plugins.cjs";
@@ -230,3 +232,52 @@ describe("local plugin builds", () => {
expect(fs.existsSync(path.join(out, "src"))).toBe(false);
});
});
describe("rebuilding local plugins", () => {
function repo(dir: string, name: string, id: string): string {
const root = path.join(dir, name);
fs.mkdirSync(path.join(root, "src"), { recursive: true });
fs.mkdirSync(path.join(root, "node_modules"));
fs.writeFileSync(path.join(root, "manifest.json"), JSON.stringify({ id }));
fs.writeFileSync(path.join(root, "src", "index.ts"), "x");
return root;
}
function touch(file: string, secondsAgo: number) {
const time = new Date(Date.now() - secondsAgo * 1000);
fs.utimesSync(file, time, time);
}
it("is stale with no dist or with a source newer than the build", () => {
const root = repo(tempDir(), "Plugin-A", "a");
expect(isLocalBuildStale(root)).toBe(true);
fs.mkdirSync(path.join(root, "dist"));
fs.writeFileSync(path.join(root, "dist", "frontend.js"), "x");
touch(path.join(root, "manifest.json"), 60);
touch(path.join(root, "src", "index.ts"), 60);
expect(isLocalBuildStale(root)).toBe(false);
touch(path.join(root, "src", "index.ts"), 0);
touch(path.join(root, "dist", "frontend.js"), 30);
expect(isLocalBuildStale(root)).toBe(true);
});
it("builds only stale repos for bundled ids that have node_modules", () => {
const dir = tempDir();
repo(dir, "Plugin-A", "a");
repo(dir, "Plugin-B", "b");
const noDeps = repo(dir, "Plugin-C", "c");
fs.rmSync(path.join(noDeps, "node_modules"), { recursive: true });
const run: string[] = [];
const rebuilt = rebuildLocalPlugins(
dir,
new Set(["a", "c"]),
(_repo: string, id: string) => run.push(id),
);
expect(rebuilt).toEqual(["a"]);
expect(run).toEqual(["a"]);
expect(rebuildLocalPlugins(null, new Set(["a"]), () => {})).toEqual([]);
});
});
+52
View File
@@ -203,6 +203,56 @@ function findLocalPluginBuilds(dir) {
return builds;
}
const BUILD_INPUTS = [
"src",
"locales",
"migrations",
"manifest.json",
"package.json",
];
function newestMtime(target) {
if (!fs.existsSync(target)) return 0;
const stat = fs.statSync(target);
if (!stat.isDirectory()) return stat.mtimeMs;
let newest = 0;
for (const name of fs.readdirSync(target)) {
newest = Math.max(newest, newestMtime(path.join(target, name)));
}
return newest;
}
/** True when a plugin repo has never been built or its sources changed since. */
function isLocalBuildStale(repo) {
const built = newestMtime(path.join(repo, "dist"));
if (!built) return true;
return BUILD_INPUTS.some(
(entry) => newestMtime(path.join(repo, entry)) > built,
);
}
/** Builds each listed plugin repo whose build is missing or out of date. */
function rebuildLocalPlugins(dir, ids, run) {
const rebuilt = [];
if (!dir || !fs.existsSync(dir)) return rebuilt;
for (const name of fs.readdirSync(dir)) {
const repo = path.join(dir, name);
const manifestPath = path.join(repo, "manifest.json");
if (!fs.existsSync(manifestPath)) continue;
if (!fs.existsSync(path.join(repo, "node_modules"))) continue;
let id;
try {
({ id } = JSON.parse(fs.readFileSync(manifestPath, "utf8")));
} catch {
continue;
}
if (!ids.has(id) || !isLocalBuildStale(repo)) continue;
run(repo, id);
rebuilt.push(id);
}
return rebuilt;
}
/** Copies a local plugin build into the staging folder. */
function copyLocalBuild(repo, destination) {
fs.mkdirSync(destination, { recursive: true });
@@ -216,6 +266,8 @@ function copyLocalBuild(repo, destination) {
module.exports = {
findLocalPluginBuilds,
isLocalBuildStale,
rebuildLocalPlugins,
copyLocalBuild,
parseBundledPlugins,
pinsFromIndex,