Files
fusion/scripts/__tests__/build-workspace.test.mjs
gsxdsm e9f14bf024 perf: speed up local pnpm build and cap stacked verifications (#2134)
## Summary

- Extend the workspace content-hash skip cache to **all** packages (not
just plugins), with `--force` / `--full` flags
- Default local CLI packaging to a **fast mode** (bin/extension +
migrations only); full desktop/plugin/DTS staging runs on CI or `pnpm
build:full`
- Enable TypeScript `incremental` builds for warm recompiles
- Add `maxConcurrentVerifications` (default **1**) so concurrent tasks
cannot stack monorepo typecheck/build and peg CPU

Warm `pnpm build` measured ~**126s → ~0.8s** when nothing changed.

## Test plan

- [x] `node --test scripts/__tests__/build-workspace.test.mjs` (12 pass)
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/verification-concurrency.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/settings-parity.test.ts`
- [x] Local: first `pnpm build` rebuilds as needed; second warm `pnpm
build` skips all packages (~0.8s)
- [x] Fast CLI packaging logs skip of desktop/plugin staging without
`FUSION_CLI_FULL_PACKAGE`
- [ ] CI: `pnpm build` still full-packages under `CI=true` (plugin
staging / release surfaces)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a Scheduling setting to limit concurrent verification tasks from
1–8, with a default of 1.
* Verification tasks now support cancellation while waiting or running.
  * Added options for forced and full workspace builds.

* **Performance**
* Local builds can skip unchanged packages and use incremental
compilation for faster rebuilds.
* Local CLI packaging is faster by default, while full packaging remains
available when needed.

* **Documentation**
* Updated the settings reference with the new verification concurrency
option.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 08:44:11 -07:00

323 lines
14 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import {
BUILD_CACHE_VERSION,
PLUGIN_BUILD_GLOBAL_INPUT_PATHS,
computePluginSourceHash,
discoverWorkspacePackages,
ensureFullPackageCliPlanned,
planWorkspaceBuild,
readPluginBuildCache,
requiredPluginOutputs,
wantsFullCliPackage,
} from "../build-workspace.mjs";
function createWorkspace() {
const root = mkdtempSync(path.join(tmpdir(), "fn-7290-build-workspace-"));
writeFileSync(path.join(root, "pnpm-workspace.yaml"), "packages:\n - 'packages/*'\n - 'plugins/*'\n");
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "workspace-root", private: true }, null, 2));
writeFileSync(path.join(root, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
writeFileSync(path.join(root, "tsconfig.json"), JSON.stringify({ extends: "./tsconfig.base.json" }, null, 2));
writeFileSync(path.join(root, "tsconfig.base.json"), JSON.stringify({ compilerOptions: { strict: true } }, null, 2));
mkdirSync(path.join(root, "plugins"), { recursive: true });
writeFileSync(path.join(root, "plugins", "tsconfig.base.json"), JSON.stringify({ extends: "../tsconfig.base.json" }, null, 2));
mkdirSync(path.join(root, "scripts", "lib"), { recursive: true });
writeFileSync(path.join(root, "scripts", "build-workspace.mjs"), "export {};\n");
writeFileSync(path.join(root, "scripts", "lib", "content-hash.mjs"), "export {};\n");
writePackage(root, "packages/core", {
name: "@fusion/core",
scripts: { build: "tsc" },
});
mkdirSync(path.join(root, "packages/core", "src"), { recursive: true });
writeFileSync(path.join(root, "packages/core", "src", "index.ts"), "export const core = 1;\n");
writePackage(root, "packages/desktop", {
name: "@fusion/desktop",
scripts: { build: "tsc" },
});
writePackage(root, "plugins/fusion-plugin-alpha", {
name: "@fusion-plugin-examples/alpha",
scripts: { build: "tsc" },
dependencies: { "@fusion/core": "workspace:*" },
exports: { ".": { types: "./src/index.ts", import: "./dist/index.js" } },
});
mkdirSync(path.join(root, "plugins/fusion-plugin-alpha", "src"), { recursive: true });
writeFileSync(path.join(root, "plugins/fusion-plugin-alpha", "src", "index.ts"), "export const alpha = 1;\n");
return root;
}
function writePackage(root, dir, manifest) {
mkdirSync(path.join(root, dir), { recursive: true });
writeFileSync(path.join(root, dir, "package.json"), JSON.stringify(manifest, null, 2));
}
function withWorkspace(fn) {
const root = createWorkspace();
try {
return fn(root);
} finally {
rmSync(root, { recursive: true, force: true });
}
}
function initGit(root) {
spawnSync("git", ["init"], { cwd: root, stdio: "ignore" });
spawnSync("git", ["add", "."], { cwd: root, stdio: "ignore" });
}
function packageByName(packages, name) {
return packages.find((pkg) => pkg.name === name);
}
function writePluginDist(root, dir = "plugins/fusion-plugin-alpha") {
mkdirSync(path.join(root, dir, "dist"), { recursive: true });
writeFileSync(path.join(root, dir, "dist", "index.js"), "export const alpha = 1;\n");
}
test("discovers workspace packages and classifies plugin directories", () => {
withWorkspace((root) => {
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const core = packageByName(packages, "@fusion/core");
assert.equal(plugin.isPlugin, true);
assert.equal(core.isPlugin, false);
assert.deepEqual(plugin.requiredOutputs, ["plugins/fusion-plugin-alpha/dist/index.js"]);
assert.deepEqual(plugin.inputPaths, [
...PLUGIN_BUILD_GLOBAL_INPUT_PATHS,
"packages/core",
"plugins/fusion-plugin-alpha",
].sort((a, b) => a.localeCompare(b)));
});
});
test("unchanged packages with outputs and cache are skipped (plugins and non-plugins)", () => {
withWorkspace((root) => {
writePluginDist(root);
// Core required outputs from src/index.ts → packages/core/dist/index.js
mkdirSync(path.join(root, "packages/core", "dist"), { recursive: true });
writeFileSync(path.join(root, "packages/core", "dist", "index.js"), "export const core = 1;\n");
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const core = packageByName(packages, "@fusion/core");
const pluginHash = computePluginSourceHash(plugin, root);
const coreHash = computePluginSourceHash(core, root);
const cache = {
version: BUILD_CACHE_VERSION,
entries: {
[plugin.name]: { sourceHash: pluginHash },
[core.name]: { sourceHash: coreHash },
},
};
const plan = planWorkspaceBuild({ rootDir: root, packages, cache });
assert.deepEqual(plan.plannedPackages.map((pkg) => pkg.name), []);
assert.deepEqual(
(plan.skippedPackages ?? plan.skippedPlugins).map((pkg) => pkg.name).sort(),
["@fusion-plugin-examples/alpha", "@fusion/core"].sort(),
);
assert.deepEqual(plan.excludedPackages.map((pkg) => pkg.name), ["@fusion/desktop"]);
});
});
test("force rebuilds packages even when cache matches", () => {
withWorkspace((root) => {
writePluginDist(root);
mkdirSync(path.join(root, "packages/core", "dist"), { recursive: true });
writeFileSync(path.join(root, "packages/core", "dist", "index.js"), "export const core = 1;\n");
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const core = packageByName(packages, "@fusion/core");
const cache = {
version: BUILD_CACHE_VERSION,
entries: {
[plugin.name]: { sourceHash: computePluginSourceHash(plugin, root) },
[core.name]: { sourceHash: computePluginSourceHash(core, root) },
},
};
const plan = planWorkspaceBuild({ rootDir: root, packages, cache, force: true });
assert.ok(plan.plannedPackages.some((pkg) => pkg.name === "@fusion/core"));
assert.ok(plan.plannedPackages.some((pkg) => pkg.name === "@fusion-plugin-examples/alpha"));
assert.equal(packageByName(plan.plannedPackages, "@fusion/core").buildReason, "force");
});
});
test("plugin packages build when required outputs are missing even with a matching cache", () => {
withWorkspace((root) => {
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const hash = computePluginSourceHash(plugin, root);
const cache = { version: BUILD_CACHE_VERSION, entries: { [plugin.name]: { sourceHash: hash } } };
const plan = planWorkspaceBuild({ rootDir: root, packages, cache });
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "missing-output");
});
});
test("plugin packages build when no successful-build cache entry exists", () => {
withWorkspace((root) => {
writePluginDist(root);
initGit(root);
const packages = discoverWorkspacePackages(root);
const plan = planWorkspaceBuild({ rootDir: root, packages, cache: { version: BUILD_CACHE_VERSION, entries: {} } });
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "no-cache");
});
});
test("plugin packages build when tracked source files change", () => {
withWorkspace((root) => {
writePluginDist(root);
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const originalHash = computePluginSourceHash(plugin, root);
writeFileSync(path.join(root, plugin.dir, "src", "index.ts"), "export const alpha = 2;\n");
const plan = planWorkspaceBuild({
rootDir: root,
packages,
cache: { version: BUILD_CACHE_VERSION, entries: { [plugin.name]: { sourceHash: originalHash } } },
});
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "changed-inputs");
});
});
test("plugin packages build when untracked plugin source files are present", () => {
withWorkspace((root) => {
writePluginDist(root);
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const originalHash = computePluginSourceHash(plugin, root);
writeFileSync(path.join(root, plugin.dir, "src", "extra.ts"), "export const extra = true;\n");
const plan = planWorkspaceBuild({
rootDir: root,
packages,
cache: { version: BUILD_CACHE_VERSION, entries: { [plugin.name]: { sourceHash: originalHash } } },
});
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "changed-inputs");
});
});
test("plugin packages build when declared workspace dependency files change", () => {
withWorkspace((root) => {
writePluginDist(root);
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const originalHash = computePluginSourceHash(plugin, root);
writeFileSync(path.join(root, "packages/core", "src", "index.ts"), "export const core = 2;\n");
const plan = planWorkspaceBuild({
rootDir: root,
packages,
cache: { version: BUILD_CACHE_VERSION, entries: { [plugin.name]: { sourceHash: originalHash } } },
});
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "changed-inputs");
});
});
test("plugin packages build when root TypeScript/build-tooling inputs change", () => {
withWorkspace((root) => {
writePluginDist(root);
initGit(root);
const packages = discoverWorkspacePackages(root);
const plugin = packageByName(packages, "@fusion-plugin-examples/alpha");
const originalHash = computePluginSourceHash(plugin, root);
writeFileSync(path.join(root, "tsconfig.base.json"), JSON.stringify({ compilerOptions: { strict: false } }, null, 2));
const plan = planWorkspaceBuild({
rootDir: root,
packages,
cache: { version: BUILD_CACHE_VERSION, entries: { [plugin.name]: { sourceHash: originalHash } } },
});
const plannedPlugin = packageByName(plan.plannedPackages, "@fusion-plugin-examples/alpha");
assert.equal(plannedPlugin.buildReason, "changed-inputs");
});
});
test("invalid cache versions are ignored so plugins rebuild once", () => {
withWorkspace((root) => {
mkdirSync(path.join(root, ".fusion", "cache"), { recursive: true });
writeFileSync(path.join(root, ".fusion", "cache", "plugin-build-cache.json"), JSON.stringify({ version: -1, entries: { stale: {} } }));
assert.deepEqual(readPluginBuildCache(root), { version: BUILD_CACHE_VERSION, entries: {} });
});
});
test("required outputs include source-export dist counterparts", () => {
withWorkspace((root) => {
mkdirSync(path.join(root, "plugins/fusion-plugin-beta", "src", "nested"), { recursive: true });
writeFileSync(path.join(root, "plugins/fusion-plugin-beta", "src", "index.ts"), "export {};\n");
writeFileSync(path.join(root, "plugins/fusion-plugin-beta", "src", "nested", "view.tsx"), "export {};\n");
const outputs = requiredPluginOutputs(root, "plugins/fusion-plugin-beta", {
exports: {
".": { types: "./src/index.d.ts", import: "./src/index.ts" },
"./view": { types: "./src/nested/view.d.ts", import: "./src/nested/view.tsx" },
},
});
assert.deepEqual(outputs, [
"plugins/fusion-plugin-beta/dist/index.js",
"plugins/fusion-plugin-beta/dist/nested/view.js",
]);
});
});
test("root package build script points at the workspace build wrapper", () => {
const rootPackage = JSON.parse(readFileSync(path.resolve("package.json"), "utf8"));
assert.equal(rootPackage.scripts.build, "node scripts/build-workspace.mjs");
});
test("full package mode force-includes CLI even when content-hash would skip it", () => {
const skipped = [
{ name: "@runfusion/fusion", isPlugin: false, buildReason: "unchanged", sourceHash: "abc" },
{ name: "@fusion/core", isPlugin: false, buildReason: "unchanged", sourceHash: "def" },
];
const { plannedPackages, skippedPackages } = ensureFullPackageCliPlanned([], skipped, { fullPackage: true });
assert.equal(plannedPackages.length, 1);
assert.equal(plannedPackages[0].name, "@runfusion/fusion");
assert.equal(plannedPackages[0].buildReason, "full-package");
assert.deepEqual(skippedPackages.map((p) => p.name), ["@fusion/core"]);
});
test("full package mode is a no-op when CLI already planned", () => {
const planned = [{ name: "@runfusion/fusion", buildReason: "changed-inputs" }];
const skipped = [{ name: "@fusion/core", buildReason: "unchanged" }];
const result = ensureFullPackageCliPlanned(planned, skipped, { fullPackage: true });
assert.equal(result.plannedPackages.length, 1);
assert.equal(result.plannedPackages[0].buildReason, "changed-inputs");
});
test("wantsFullCliPackage matches CLI packaging env rules", () => {
assert.equal(wantsFullCliPackage({}, { fullFlag: false }), false);
assert.equal(wantsFullCliPackage({}, { fullFlag: true }), true);
assert.equal(wantsFullCliPackage({ CI: "true" }, { fullFlag: false }), true);
assert.equal(wantsFullCliPackage({ FUSION_CLI_FULL_PACKAGE: "1" }, { fullFlag: false }), true);
assert.equal(wantsFullCliPackage({ FUSION_CLI_FULL_PACKAGE: "0", CI: "true" }, { fullFlag: true }), false);
assert.equal(wantsFullCliPackage({ npm_lifecycle_event: "prepack" }, { fullFlag: false }), true);
});