Skip to content

Commit 048b1e9

Browse files
committed
Add stopFirst flag to dependency config for service restart ordering
Introduces a new per-dependency boolean option `stopFirst` (default: false). When true on a dependency of a service, the running service (adoptee) is stopped before that dependency executes. This allows dependencies to freely write files the service may have open, avoiding file-lock and port conflicts on both Unix and Windows. Usage: "dependencies": [{"script": "build", "cascade": true, "stopFirst": true}] https://claude.ai/code/session_01Be7upyt3ZRNdHuhyi1W7eF
1 parent b9b87e3 commit 048b1e9

4 files changed

Lines changed: 39 additions & 2 deletions

File tree

schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
"cascade": {
4242
"markdownDescription": "When `true` (the default), whenever this dependency runs, this script (the dependent) will be marked stale and need to re-run too, regardless of whether the dependency produced new or relevant output. When `false` Wireit won't assume that the dependent is stale just because the dependency ran. This can reduce unnecessary re-building (or restarting in the case of services) when `files` captures all of the relevant output of the dependency.\n\nFor more info, see https://github.com/google/wireit#re-run-on-change",
4343
"type": "boolean"
44+
},
45+
"stopFirst": {
46+
"markdownDescription": "When `true`, if the dependent is a running service, it will be stopped before this dependency executes. This is useful when the dependency needs to write files that the service has open (e.g. rebuilding output the service serves). Defaults to `false`.",
47+
"type": "boolean"
4448
}
4549
}
4650
}

src/analyzer.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ export class Analyzer {
696696
// property plus optional extra annotations.
697697
let specifierResult;
698698
let cascade = true; // Default;
699+
let stopFirst = false; // Default;
699700
if (maybeUnresolved.type === 'string') {
700701
specifierResult = failUnlessNonBlankString(
701702
maybeUnresolved,
@@ -762,6 +763,36 @@ export class Analyzer {
762763
continue;
763764
}
764765
}
766+
const stopFirstResult = findNodeAtLocation(maybeUnresolved, [
767+
'stopFirst',
768+
]);
769+
if (stopFirstResult !== undefined) {
770+
if (
771+
stopFirstResult.value === true ||
772+
stopFirstResult.value === false
773+
) {
774+
stopFirst = stopFirstResult.value;
775+
} else {
776+
encounteredError = true;
777+
placeholder.failures.push({
778+
type: 'failure',
779+
reason: 'invalid-config-syntax',
780+
script: {packageDir: pathlib.dirname(packageJson.jsonFile.path)},
781+
diagnostic: {
782+
severity: 'error',
783+
message: `The "stopFirst" property must be either true or false.`,
784+
location: {
785+
file: packageJson.jsonFile,
786+
range: {
787+
offset: stopFirstResult.offset,
788+
length: stopFirstResult.length,
789+
},
790+
},
791+
},
792+
});
793+
continue;
794+
}
795+
}
765796
} else {
766797
encounteredError = true;
767798
placeholder.failures.push({
@@ -836,6 +867,7 @@ export class Analyzer {
836867
specifier: unresolved,
837868
config: placeHolderInfo.placeholder,
838869
cascade,
870+
stopFirst,
839871
});
840872
this.#ongoingWorkPromises.push(
841873
(async () => {

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface Dependency<
5555
config: Config;
5656
specifier: JsonAstNode<string>;
5757
cascade: boolean;
58+
stopFirst: boolean;
5859
}
5960

6061
export type ScriptConfig =

src/execution/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,11 @@ export class ServiceScriptExecution extends BaseExecutionWithCommand<ServiceScri
361361
});
362362

363363
const adoptee = this.#state.adoptee;
364-
// If any dependency has cascade:true, stop the adoptee before running
364+
// If any dependency has stopFirst:true, stop the adoptee before running
365365
// deps so they can freely write to files the service may have open.
366366
const shouldStopAdopteeEarly =
367367
adoptee !== undefined &&
368-
this._config.dependencies.some((dep) => dep.cascade);
368+
this._config.dependencies.some((dep) => dep.stopFirst);
369369

370370
this.#state = {
371371
id: 'executingDeps',

0 commit comments

Comments
 (0)