fix(FN-2462): enforce frozen-lockfile bootstrap across workflows
- Update CI, version, and test-release workflows to use pnpm install --frozen-lockfile consistently. - Align contributor and settings documentation plus worktree init examples toward deterministic frozen-lockfile bootstrap. - Clarify TaskExecutor worktree init guidance to distinguish dependency bootstrap failures from missing workspace dist export failures. - Refresh workflow, executor, restart, and SettingsModal tests (including stable Node Sync tab selection) to assert the new bootstrap contract.
This commit is contained in:
@@ -432,7 +432,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
pollIntervalMs: 15000,
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: false,
|
||||
worktreeInitCommand: "pnpm install",
|
||||
worktreeInitCommand: "pnpm install --frozen-lockfile",
|
||||
});
|
||||
|
||||
const executor = new TaskExecutor(store, "/tmp/test");
|
||||
@@ -440,7 +440,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
|
||||
// execSync is called for worktree creation + init command
|
||||
const initCall = mockedExecSync.mock.calls.find(
|
||||
(call) => call[0] === "pnpm install",
|
||||
(call) => call[0] === "pnpm install --frozen-lockfile",
|
||||
);
|
||||
expect(initCall).toBeDefined();
|
||||
expect(initCall![1]).toMatchObject({
|
||||
@@ -452,7 +452,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
expect(store.logEntry).toHaveBeenCalledWith(
|
||||
"FN-010",
|
||||
expect.stringMatching(/^\[timing\] Worktree init command completed in \d+ms$/),
|
||||
"pnpm install",
|
||||
"pnpm install --frozen-lockfile",
|
||||
expect.objectContaining({ agentId: "executor" }),
|
||||
);
|
||||
});
|
||||
@@ -464,7 +464,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
const executor = new TaskExecutor(store, "/tmp/test");
|
||||
await executor.execute(makeTask());
|
||||
|
||||
// Only worktree creation calls to execSync, no "pnpm install" etc.
|
||||
// Only worktree creation calls to execSync, no "pnpm install --frozen-lockfile" etc.
|
||||
const initCall = mockedExecSync.mock.calls.find(
|
||||
(call) => typeof call[0] === "string" && !call[0].startsWith("git"),
|
||||
);
|
||||
@@ -524,7 +524,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
pollIntervalMs: 15000,
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: false,
|
||||
worktreeInitCommand: "pnpm install",
|
||||
worktreeInitCommand: "pnpm install --frozen-lockfile",
|
||||
});
|
||||
|
||||
// Worktree already exists (resume)
|
||||
@@ -2019,15 +2019,15 @@ describe("TaskExecutor worktree pool integration", () => {
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: false,
|
||||
recycleWorktrees: true,
|
||||
worktreeInitCommand: "pnpm install",
|
||||
worktreeInitCommand: "pnpm install --frozen-lockfile",
|
||||
});
|
||||
|
||||
const executor = new TaskExecutor(store, "/tmp/test", { pool });
|
||||
await executor.execute(makeTask());
|
||||
|
||||
// "pnpm install" should NOT have been called (pooled worktree has warm cache)
|
||||
// "pnpm install --frozen-lockfile" should NOT have been called (pooled worktree has warm cache)
|
||||
const initCalls = mockedExecSync.mock.calls.filter(
|
||||
(c) => c[0] === "pnpm install",
|
||||
(c) => c[0] === "pnpm install --frozen-lockfile",
|
||||
);
|
||||
expect(initCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -1320,11 +1320,16 @@ export class TaskExecutor {
|
||||
}
|
||||
|
||||
// Run worktree init command for fresh worktrees (skip for pooled — caches are warm).
|
||||
// The init command must leave the worktree in a state where workspace
|
||||
// packages resolve at runtime (e.g. `pnpm install && pnpm build`). If
|
||||
// it doesn't, the first command the executor runs will usually fail
|
||||
// with "@fusion/core entry not found" because monorepo exports point
|
||||
// to dist/. 5-minute timeout accommodates install + build together.
|
||||
// The init command should deterministically install the full dependency
|
||||
// graph required by test/typecheck/build commands (for pnpm workspaces,
|
||||
// prefer `pnpm install --frozen-lockfile`) so transitive modules and
|
||||
// declarations like @vitest/runner, loupe, debug, @types/express, and
|
||||
// node-pty are present after bootstrap.
|
||||
//
|
||||
// NOTE: This is distinct from the separate workspace-export failure
|
||||
// class where internal packages fail to resolve because exports point
|
||||
// to missing dist/* outputs (e.g. "@fusion/core entry not found").
|
||||
// 5-minute timeout accommodates larger dependency installs.
|
||||
if (settings.worktreeInitCommand) {
|
||||
const initStartedAt = Date.now();
|
||||
try {
|
||||
|
||||
@@ -366,7 +366,7 @@ describe("In-progress task resume after restart", () => {
|
||||
const store = createMockStore();
|
||||
store.getSettings.mockResolvedValue({
|
||||
...DEFAULT_SETTINGS,
|
||||
worktreeInitCommand: "pnpm install",
|
||||
worktreeInitCommand: "pnpm install --frozen-lockfile",
|
||||
});
|
||||
const task = makeTask("FN-030", "in-progress");
|
||||
store.listTasks.mockResolvedValue([task]);
|
||||
@@ -384,7 +384,7 @@ describe("In-progress task resume after restart", () => {
|
||||
|
||||
// No init command calls
|
||||
const initCalls = mockedExecSync.mock.calls.filter(
|
||||
(call) => call[0] === "pnpm install",
|
||||
(call) => call[0] === "pnpm install --frozen-lockfile",
|
||||
);
|
||||
expect(initCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user