fix: refresh generated paths before watch restarts (#110422)

This commit is contained in:
Shakker
2026-07-18 06:42:27 +01:00
committed by Shakker
parent 643476f24d
commit 309a97bb3d
3 changed files with 66 additions and 6 deletions
+4
View File
@@ -31,6 +31,10 @@ export function runWatchMain(params?: {
};
}>;
watchPaths?: string[];
pathClassifier?: {
refreshGeneratedPluginAssetPaths(): void;
isRestartRelevantRunNodePath(repoPath: unknown): boolean;
};
process?: NodeJS.Process;
cwd?: string;
args?: string[];
+25 -6
View File
@@ -8,7 +8,7 @@ import path from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";
import { sleep } from "./lib/sleep.mjs";
import { isRestartRelevantRunNodePath, runNodeWatchedPaths } from "./run-node-watch-paths.mjs";
import { createRunNodePathClassifier, runNodeWatchedPaths } from "./run-node-watch-paths.mjs";
const WATCH_NODE_RUNNER = "scripts/run-node.mjs";
const WATCH_RESTART_SIGNAL = "SIGTERM";
@@ -60,7 +60,7 @@ const isDirectoryLikeWatchedPath = (repoPath, watchPaths) => {
});
};
const isIgnoredWatchPath = (filePath, cwd, watchPaths, stats) => {
const isIgnoredWatchPath = (filePath, cwd, watchPaths, pathClassifier, stats) => {
const repoPath = resolveRepoPath(filePath, cwd);
if (hasIgnoredPathSegment(repoPath)) {
return true;
@@ -70,7 +70,7 @@ const isIgnoredWatchPath = (filePath, cwd, watchPaths, stats) => {
return false;
}
}
return !isRestartRelevantRunNodePath(repoPath);
return !pathClassifier.isRestartRelevantRunNodePath(repoPath);
};
const shouldRestartAfterChildExit = (exitCode, exitSignal) =>
@@ -253,6 +253,10 @@ const releaseWatchLock = (lockHandle) => {
* sleep?: (ms: number) => Promise<void>;
* signalProcess?: (pid: number, signal: string | number) => void;
* lockDisabled?: boolean;
* pathClassifier?: {
* refreshGeneratedPluginAssetPaths: () => void;
* isRestartRelevantRunNodePath: (repoPath: unknown) => boolean;
* };
* createWatcher?: (
* watchPaths: string[],
* options: { ignoreInitial: boolean; ignored: (watchPath: string) => boolean },
@@ -264,10 +268,11 @@ const releaseWatchLock = (lockHandle) => {
* Runs the watch loop and restarts the child process on relevant changes.
*/
export async function runWatchMain(params = {}) {
const cwd = params.cwd ?? process.cwd();
const deps = {
spawn: params.spawn ?? spawn,
process: params.process ?? process,
cwd: params.cwd ?? process.cwd(),
cwd,
args: params.args ?? process.argv.slice(2),
env: params.env ? { ...params.env } : { ...process.env },
fs: params.fs ?? fs,
@@ -275,6 +280,7 @@ export async function runWatchMain(params = {}) {
sleep: params.sleep ?? sleep,
signalProcess: params.signalProcess ?? ((pid, signal) => process.kill(pid, signal)),
lockDisabled: params.lockDisabled === true,
pathClassifier: params.pathClassifier ?? createRunNodePathClassifier({ rootDir: cwd }),
createWatcher: params.createWatcher,
loadChokidar: params.loadChokidar ?? loadChokidar,
watchPaths: params.watchPaths ?? runNodeWatchedPaths,
@@ -379,6 +385,16 @@ export async function runWatchMain(params = {}) {
};
const startRunner = () => {
try {
deps.pathClassifier.refreshGeneratedPluginAssetPaths();
} catch (error) {
logWatcher(
`Failed to refresh generated asset paths: ${error?.message ?? "unknown error"}`,
deps,
);
settle(1);
return;
}
watchProcess = deps.spawn(deps.process.execPath, buildRunnerArgs(deps.args), {
cwd: deps.cwd,
detached: useChildProcessGroup,
@@ -556,7 +572,10 @@ export async function runWatchMain(params = {}) {
};
const requestRestart = (changedPath) => {
if (shuttingDown || isIgnoredWatchPath(changedPath, deps.cwd, deps.watchPaths)) {
if (
shuttingDown ||
isIgnoredWatchPath(changedPath, deps.cwd, deps.watchPaths, deps.pathClassifier)
) {
return;
}
if (!watchProcess) {
@@ -604,7 +623,7 @@ export async function runWatchMain(params = {}) {
watcher = createWatcher(deps.watchPaths, {
ignoreInitial: true,
ignored: (watchPath, stats) =>
isIgnoredWatchPath(watchPath, deps.cwd, deps.watchPaths, stats),
isIgnoredWatchPath(watchPath, deps.cwd, deps.watchPaths, deps.pathClassifier, stats),
});
watcher.on("add", requestRestart);
watcher.on("change", requestRestart);
+37
View File
@@ -278,6 +278,43 @@ describe("watch-node script", () => {
expect(watcher.close).toHaveBeenCalledTimes(1);
});
it("refreshes generated asset paths before each runner start", async () => {
const childA = createAutoExitChild();
const childB = createKillableChild();
const spawn = vi.fn().mockReturnValueOnce(childA).mockReturnValueOnce(childB);
const watcher = Object.assign(new EventEmitter(), {
close: vi.fn(async () => {}),
});
const fakeProcess = createFakeProcess();
const pathClassifier = {
refreshGeneratedPluginAssetPaths: vi.fn(),
isRestartRelevantRunNodePath: vi.fn(() => true),
};
const runPromise = runWatch({
args: ["gateway", "--force"],
createWatcher: () => watcher,
fs: { existsSync: () => true },
lockDisabled: true,
pathClassifier,
process: fakeProcess,
spawn,
});
expect(pathClassifier.refreshGeneratedPluginAssetPaths).toHaveBeenCalledTimes(1);
watcher.emit("change", "extensions/browser/package.json");
await new Promise((resolve) => {
setImmediate(resolve);
});
expect(spawn).toHaveBeenCalledTimes(2);
expect(pathClassifier.refreshGeneratedPluginAssetPaths).toHaveBeenCalledTimes(2);
fakeProcess.emit("SIGINT");
const exitCode = await runPromise;
expect(exitCode).toBe(130);
});
it("terminates child on SIGINT and returns shell interrupt code", async () => {
const { child, spawn, watcher, createWatcher, fakeProcess } = createWatchHarness();