diff --git a/scripts/build-plugins.cjs b/scripts/build-plugins.cjs
index 318c53294..1a585d11a 100644
--- a/scripts/build-plugins.cjs
+++ b/scripts/build-plugins.cjs
@@ -8,11 +8,12 @@
*
* For development, `--local
` (or TERMIX_LOCAL_PLUGINS) points at a
* folder of plugin repos like ../Termix-Plugins. A plugin there whose sources
- * changed since its last build is rebuilt first, then any built one (has
+ * changed since its last build is rebuilt first with this checkout's SDK, so
+ * SDK edits apply without publishing, then any built one (has
* dist/) is copied in place of its pin, or added when it has no pin.
*/
-const { execSync } = require("node:child_process");
+const { execFileSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const {
@@ -21,6 +22,7 @@ const {
extractArtifact,
findLocalPluginBuilds,
rebuildLocalPlugins,
+ localSdkBuildCommand,
copyLocalBuild,
buildBundledIndex,
} = require("./lib/bundled-plugins.cjs");
@@ -34,9 +36,10 @@ async function main() {
const localArg =
flag !== -1 ? process.argv[flag + 1] : process.env.TERMIX_LOCAL_PLUGINS;
const localDir = localArg ? path.resolve(root, localArg) : null;
+ const [node, buildArgs] = localSdkBuildCommand();
rebuildLocalPlugins(localDir, null, (repo, id) => {
console.log(`building ${id}`);
- execSync("npm run build", { cwd: repo, stdio: "inherit" });
+ execFileSync(node, buildArgs, { cwd: repo, stdio: "inherit" });
});
const local = findLocalPluginBuilds(localDir);
diff --git a/scripts/bundled-plugins.test.ts b/scripts/bundled-plugins.test.ts
index 7ee32339a..2f77790c5 100644
--- a/scripts/bundled-plugins.test.ts
+++ b/scripts/bundled-plugins.test.ts
@@ -13,6 +13,7 @@ import {
buildBundledIndex,
findLocalPluginBuilds,
isLocalBuildStale,
+ localSdkBuildCommand,
rebuildLocalPlugins,
copyLocalBuild,
} from "./lib/bundled-plugins.cjs";
@@ -354,6 +355,40 @@ describe("rebuilding local plugins", () => {
expect(isLocalBuildStale(root)).toBe(true);
});
+ it("is stale when the SDK CLI changed after the build", () => {
+ const root = repo(tempDir(), "Plugin-A", "a");
+ const cli = path.join(tempDir(), "cli");
+ fs.mkdirSync(cli);
+ fs.writeFileSync(path.join(cli, "index.mjs"), "x");
+ 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);
+ touch(path.join(cli, "index.mjs"), 60);
+ expect(isLocalBuildStale(root, cli)).toBe(false);
+
+ touch(path.join(root, "dist", "frontend.js"), 30);
+ touch(path.join(cli, "index.mjs"), 0);
+ expect(isLocalBuildStale(root, cli)).toBe(true);
+ expect(isLocalBuildStale(root)).toBe(false);
+ });
+
+ it("builds with this checkout's SDK CLI, not the plugin's own", () => {
+ const [command, args] = localSdkBuildCommand();
+ expect(command).toBe(process.execPath);
+ expect(args).toEqual([
+ path.resolve(
+ __dirname,
+ "..",
+ "packages",
+ "plugin-sdk",
+ "cli",
+ "index.mjs",
+ ),
+ "build",
+ ]);
+ });
+
it("builds only stale repos for bundled ids that have node_modules", () => {
const dir = tempDir();
repo(dir, "Plugin-A", "a");
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index 6d8ba5a8d..0b97b69d6 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -45,7 +45,10 @@ import {
} from "./lib/dev-docker.mjs";
const require = createRequire(import.meta.url);
-const { copyLocalBuild } = require("./lib/bundled-plugins.cjs");
+const {
+ copyLocalBuild,
+ localSdkBuildCommand,
+} = require("./lib/bundled-plugins.cjs");
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const BACKEND_ENTRY = path.join(
@@ -83,7 +86,9 @@ function run(command, args, cwd = root) {
const child = spawn(command, args, {
cwd,
stdio: ["ignore", "inherit", "inherit"],
- shell: process.platform === "win32",
+ // npm is a .cmd on Windows and needs the shell, but the shell splits
+ // arguments on spaces, so a path like "Personal Projects" gets cut.
+ shell: process.platform === "win32" && !path.isAbsolute(command),
});
child.on("error", reject);
child.on("exit", (code) =>
@@ -541,7 +546,7 @@ function watchCoreForDocker() {
async function rebuildPlugin({ id, repo }) {
log(`building ${id}`);
try {
- await run("npm", ["run", "build"], repo);
+ await run(...localSdkBuildCommand(), repo);
} catch (error) {
warn(`${id} failed to build: ${error.message}`);
return;
diff --git a/scripts/lib/bundled-plugins.cjs b/scripts/lib/bundled-plugins.cjs
index 940e1e02e..ddbbc034d 100644
--- a/scripts/lib/bundled-plugins.cjs
+++ b/scripts/lib/bundled-plugins.cjs
@@ -299,14 +299,37 @@ function newestMtime(target) {
}
/** True when a plugin repo has never been built or its sources changed since. */
-function isLocalBuildStale(repo) {
+/** The SDK CLI in this checkout, which dev builds local plugins with. */
+const LOCAL_SDK_CLI = path.join(
+ __dirname,
+ "..",
+ "..",
+ "packages",
+ "plugin-sdk",
+ "cli",
+);
+
+/**
+ * True when the build is missing, a source is newer than it, or the SDK CLI
+ * that builds it changed since (sdkCli, when given).
+ */
+function isLocalBuildStale(repo, sdkCli) {
const built = newestMtime(path.join(repo, "dist"));
if (!built) return true;
+ if (sdkCli && newestMtime(sdkCli) > built) return true;
return BUILD_INPUTS.some(
(entry) => newestMtime(path.join(repo, entry)) > built,
);
}
+/**
+ * The command that builds a plugin repo with this checkout's SDK instead of
+ * the published one in its node_modules, so SDK edits apply right away.
+ */
+function localSdkBuildCommand() {
+ return [process.execPath, [path.join(LOCAL_SDK_CLI, "index.mjs"), "build"]];
+}
+
/**
* Builds each plugin repo whose build is missing or out of date. ids limits
* it to those plugins; null builds every one.
@@ -325,7 +348,8 @@ function rebuildLocalPlugins(dir, ids, run) {
} catch {
continue;
}
- if ((ids && !ids.has(id)) || !isLocalBuildStale(repo)) continue;
+ if ((ids && !ids.has(id)) || !isLocalBuildStale(repo, LOCAL_SDK_CLI))
+ continue;
run(repo, id);
rebuilt.push(id);
}
@@ -346,6 +370,7 @@ function copyLocalBuild(repo, destination) {
module.exports = {
findLocalPluginBuilds,
isLocalBuildStale,
+ localSdkBuildCommand,
rebuildLocalPlugins,
copyLocalBuild,
parseBundledPlugins,