@intentius/chant-lexicon-fly 0.108.1 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "f64e7a6dc0024fd5c51402938af053fb2ad45a3a5c6b48451c22bed2cfe1de74",
4
+ "manifest.json": "1b38d175549db9a6c3283ec90f1201cbf02f8abe6f6f7d5c98d1f7bb45f9e1fc",
5
5
  "meta.json": "273f2a523b66f08ca91642dba2592c44765ab59ddf7329998884a0b08c6c3f24",
6
6
  "types/index.d.ts": "2f01714e1aa5f5695d68416e143f281584efb84457138c93825fea34e2f3fa0a",
7
7
  "rules/guest-sizing.ts": "bc749d218df8f3cc3d8fbe388205ae96e6b204ae0d6d77e8bf82cb936cc53ff6",
@@ -15,5 +15,5 @@
15
15
  "skills/chant-fly-ops.md": "44102f1fdf611b9262c04457258990e44026e83f9e5b19ae86b69322f3a37620",
16
16
  "skills/chant-fly-sprites.md": "3d100680422151b58d082c3a7b30a286edfc52faa4887995c9ad5553873bab95"
17
17
  },
18
- "composite": "be509a43a75dff438c476da8faeaadd1691486efc443b42dfd110618b8d9ea1c"
18
+ "composite": "448da3739a3164917a0a53dc53c145bc5e00871031607fd73a5c41844c9129d8"
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fly",
3
- "version": "0.108.1",
3
+ "version": "0.110.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Fly"
6
6
  }
@@ -12,7 +12,7 @@
12
12
  /** One declared service, as the declaration states it (core's `BoxService`, less its pointer). */
13
13
  export interface BoxServiceDeclaration {
14
14
  name: string;
15
- /** As written: `${VAR}` references are expanded by {@link expandServiceCommand}. */
15
+ /** As written: split into argv and `${VAR}`-expanded by {@link serviceCommandArgv}. */
16
16
  cmd: string;
17
17
  needs: string[];
18
18
  httpPort: number | null;
@@ -28,6 +28,16 @@ export declare function boxServices(cwd?: string): Promise<BoxServiceDeclaration
28
28
  * empty path in it. Pure.
29
29
  */
30
30
  export declare function expandServiceCommand(name: string, cmd: string, env?: NodeJS.ProcessEnv): string;
31
+ /**
32
+ * A service's command as the supervisor runs it: the executable and its
33
+ * arguments. The declared `cmd` is split on whitespace, with no quoting or
34
+ * other shell syntax (a command that needs it belongs in a script), and each
35
+ * word is then expanded by {@link expandServiceCommand}, so a variable whose
36
+ * value has a space stays one word. sprite-env takes the arguments as one
37
+ * comma-separated `--args`, so an argument with a comma in it is refused
38
+ * here rather than split there. Pure.
39
+ */
40
+ export declare function serviceCommandArgv(name: string, cmd: string, env?: NodeJS.ProcessEnv): string[];
31
41
  /**
32
42
  * The services in an order each one's `needs` come before it, and in file
33
43
  * order otherwise. The list is one the declaration reader accepted, so every
@@ -1 +1 @@
1
- {"version":3,"file":"box-services.d.ts","sourceRoot":"","sources":["../../../src/op/activities/box-services.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kGAAkG;AAClG,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,0FAA0F;AAC1F,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAiBvF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM5G;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAe9G"}
1
+ {"version":3,"file":"box-services.d.ts","sourceRoot":"","sources":["../../../src/op/activities/box-services.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kGAAkG;AAClG,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,0FAA0F;AAC1F,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAiBvF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM5G;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,EAAE,CAS5G;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAe9G"}
@@ -24,7 +24,7 @@ export type { DeclaredSpriteService, SpriteServicesObserveArgs, SpriteServicesOb
24
24
  export { spriteWriteFile, spriteReadFile, spriteListDir, spriteRemove, spriteFsUrl, defaultSpritesRawHttp, } from "./sprite-fs.js";
25
25
  export type { SpritesRawHttp, SpriteWriteFileArgs, SpriteReadFileArgs, SpriteReadFileResult, SpriteListDirArgs, SpriteDirEntry, SpriteRemoveArgs, } from "./sprite-fs.js";
26
26
  export { spriteApplyNetworkPolicy, spriteApplyServices, validateNetworkRules, networkRulesEqual, validateServices, serviceConfigEqual, parseServiceDefinitions, listedServiceDiffers, spriteEnvCreateArgs, } from "./sprite-config.js";
27
- export { boxServices, expandServiceCommand, inStartOrder } from "./box-services.js";
27
+ export { boxServices, expandServiceCommand, inStartOrder, serviceCommandArgv } from "./box-services.js";
28
28
  export type { BoxServiceDeclaration } from "./box-services.js";
29
29
  export type { NetworkRule, SpriteApplyNetworkPolicyArgs, SpriteApplyNetworkPolicyResult, ServiceSpec, SpriteApplyServicesArgs, SpriteApplyServicesResult, ServiceApplyAction, ListedService, } from "./sprite-config.js";
30
30
  export { spriteTaskCreate, spriteTaskRefresh, spriteTaskRelease, spriteTasksUrl, spriteTaskUrl, } from "./sprite-tasks.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"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"}
@@ -81,13 +81,19 @@ export interface SpriteApplyServicesArgs {
81
81
  start?: boolean;
82
82
  /** Without `id`: restart each applied service that was already converged. Default: false. */
83
83
  restart?: boolean;
84
+ /**
85
+ * Without `id` and without `only`: delete each service the supervisor has
86
+ * that the box block does not declare (an `optional` one is declared), so a
87
+ * service dropped from the block stops running. Default: true.
88
+ */
89
+ prune?: boolean;
84
90
  /** Without `id`: the `sprite-env` binary. Default: the one on PATH, else /.sprite/bin/sprite-env. */
85
91
  spriteEnv?: string;
86
92
  endpoint?: string;
87
93
  token?: string;
88
94
  }
89
95
  /** What an apply through sprite-env did to one service (#2880). */
90
- export type ServiceApplyAction = "created" | "replaced" | "restarted" | "started" | "left";
96
+ export type ServiceApplyAction = "created" | "replaced" | "restarted" | "started" | "left" | "deleted";
91
97
  export interface SpriteApplyServicesResult {
92
98
  /** Names that were created or updated (converged services are skipped). */
93
99
  applied: string[];
@@ -96,7 +102,7 @@ export interface SpriteApplyServicesResult {
96
102
  /**
97
103
  * Without `id`: each service applied, in the order it was applied, with
98
104
  * what the apply did to it (#2880). `left` is a converged service that was
99
- * not restarted or started.
105
+ * not restarted or started, `deleted` one the block does not declare (`prune`).
100
106
  */
101
107
  services?: {
102
108
  name: string;
@@ -137,6 +143,15 @@ export interface ListedService {
137
143
  export declare function parseServiceDefinitions(text: string): ListedService[];
138
144
  /** Whether a listed service differs from the declared one in `cmd`, `needs` or `httpPort`. A field the listing does not carry is not compared. Pure. */
139
145
  export declare function listedServiceDiffers(listed: ListedService, declared: BoxServiceDeclaration, cmd: string): string | null;
140
- /** The arguments of `sprite-env services create` for a declared service. Pure. */
141
- export declare function spriteEnvCreateArgs(s: BoxServiceDeclaration, cmd: string): string[];
146
+ /**
147
+ * The arguments of `sprite-env services create` for a declared service.
148
+ * `argv` is the command from {@link serviceCommandArgv}: its first word is
149
+ * `--cmd`, the rest `--args` (comma-separated), as sprite-env and upstream
150
+ * Sprites take them; neither runs `--cmd` through a shell. A string is split
151
+ * on whitespace. With a `duration`, sprite-env streams the service's output
152
+ * for that long and fails the create if the service exits in it, so
153
+ * `--no-stream` (which returns as soon as the service is defined) is passed
154
+ * only without one. Pure.
155
+ */
156
+ export declare function spriteEnvCreateArgs(s: BoxServiceDeclaration, argv: string | readonly string[]): string[];
142
157
  //# sourceMappingURL=sprite-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sprite-config.d.ts","sourceRoot":"","sources":["../../../src/op/activities/sprite-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sprite-config.d.ts","sourceRoot":"","sources":["../../../src/op/activities/sprite-config.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-fly",
3
- "version": "0.108.1",
3
+ "version": "0.110.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -69,7 +69,7 @@
69
69
  "zod": "^4.3.6"
70
70
  },
71
71
  "peerDependencies": {
72
- "@intentius/chant": "^0.108.1",
72
+ "@intentius/chant": "^0.110.0",
73
73
  "typescript": "^5.9.3",
74
74
  "zod": "^4.3.6"
75
75
  },
@@ -13,7 +13,7 @@
13
13
  /** One declared service, as the declaration states it (core's `BoxService`, less its pointer). */
14
14
  export interface BoxServiceDeclaration {
15
15
  name: string;
16
- /** As written: `${VAR}` references are expanded by {@link expandServiceCommand}. */
16
+ /** As written: split into argv and `${VAR}`-expanded by {@link serviceCommandArgv}. */
17
17
  cmd: string;
18
18
  needs: string[];
19
19
  httpPort: number | null;
@@ -55,6 +55,26 @@ export function expandServiceCommand(name: string, cmd: string, env: NodeJS.Proc
55
55
  });
56
56
  }
57
57
 
58
+ /**
59
+ * A service's command as the supervisor runs it: the executable and its
60
+ * arguments. The declared `cmd` is split on whitespace, with no quoting or
61
+ * other shell syntax (a command that needs it belongs in a script), and each
62
+ * word is then expanded by {@link expandServiceCommand}, so a variable whose
63
+ * value has a space stays one word. sprite-env takes the arguments as one
64
+ * comma-separated `--args`, so an argument with a comma in it is refused
65
+ * here rather than split there. Pure.
66
+ */
67
+ export function serviceCommandArgv(name: string, cmd: string, env: NodeJS.ProcessEnv = process.env): string[] {
68
+ const words = cmd.trim().split(/\s+/).filter((w) => w !== "");
69
+ if (words.length === 0) throw new Error(`service ${name}'s cmd is empty`);
70
+ const argv = words.map((w) => expandServiceCommand(name, w, env));
71
+ const comma = argv.slice(1).find((a) => a.includes(","));
72
+ if (comma !== undefined) {
73
+ throw new Error(`service ${name}'s cmd has the argument ${JSON.stringify(comma)}, and sprite-env's --args is comma-separated, so it can't carry a comma; run the command from a script`);
74
+ }
75
+ return argv;
76
+ }
77
+
58
78
  /**
59
79
  * The services in an order each one's `needs` come before it, and in file
60
80
  * order otherwise. The list is one the declaration reader accepted, so every
@@ -231,7 +231,7 @@ export {
231
231
  spriteEnvCreateArgs,
232
232
  } from "./sprite-config";
233
233
  // A box's declared services (#2880), read from its box block with `box: true`.
234
- export { boxServices, expandServiceCommand, inStartOrder } from "./box-services";
234
+ export { boxServices, expandServiceCommand, inStartOrder, serviceCommandArgv } from "./box-services";
235
235
  export type { BoxServiceDeclaration } from "./box-services";
236
236
  export type {
237
237
  NetworkRule,
@@ -23,7 +23,7 @@ import { convergeTick } from "@intentius/chant/op/activities";
23
23
  import { readConvergeLedger } from "@intentius/chant/lifecycle/converge-ledger";
24
24
  import { spriteApplyServices, listedServiceDiffers, parseServiceDefinitions, spriteEnvCreateArgs } from "./sprite-config";
25
25
  import { spriteServiceRestart, spriteServicesObserve } from "./sprite-service-converge";
26
- import { expandServiceCommand } from "./box-services";
26
+ import { expandServiceCommand, serviceCommandArgv } from "./box-services";
27
27
  import { spriteServicesObserve as observeStep, spriteServiceRestart as restartStep } from "../builders";
28
28
 
29
29
  let root: string;
@@ -48,23 +48,29 @@ const fail = (m) => { console.error("sprite-env: " + m); process.exit(1); };
48
48
  if (verb === "list") {
49
49
  const out = readdirSync(dir).filter((f) => f.endsWith(".json")).sort().map((f) => {
50
50
  const s = JSON.parse(readFileSync(join(dir, f), "utf8"));
51
- return { name: s.name, cmd: s.cmd, args: [], needs: s.needs, http_port: s.httpPort, state: { name: s.name, status: s.status } };
51
+ return { name: s.name, cmd: s.cmd, args: s.args, needs: s.needs, http_port: s.httpPort, state: { name: s.name, status: s.status } };
52
52
  });
53
53
  console.log(JSON.stringify(out));
54
54
  } else if (verb === "create") {
55
55
  if (existsSync(file(name))) fail("service " + name + " already exists");
56
- const s = { name, cmd: null, needs: [], httpPort: 0, status: "running" };
56
+ const s = { name, cmd: null, args: [], needs: [], httpPort: 0, status: "running" };
57
57
  for (let i = 0; i < rest.length; i++) {
58
58
  const a = rest[i];
59
59
  if (a === "--no-stream") continue;
60
60
  const v = rest[++i];
61
61
  if (a === "--cmd") s.cmd = v;
62
+ else if (a === "--args") s.args = v.split(",");
62
63
  else if (a === "--needs") s.needs = v.split(",");
63
64
  else if (a === "--http-port") s.httpPort = Number(v);
64
65
  else if (a === "--duration") s.duration = v;
65
66
  else fail("unknown option " + a);
66
67
  }
67
68
  for (const n of s.needs) if (!existsSync(file(n))) fail("service " + name + " needs " + n + ", which does not exist");
69
+ if (s.httpPort > 0) {
70
+ for (const f of readdirSync(dir).filter((f) => f.endsWith(".json"))) {
71
+ if (JSON.parse(readFileSync(join(dir, f), "utf8")).httpPort > 0) fail("another service already has an HTTP port configured (409)");
72
+ }
73
+ }
68
74
  writeFileSync(file(name), JSON.stringify(s));
69
75
  } else if (verb === "delete") {
70
76
  if (!existsSync(file(name))) fail("no service " + name);
@@ -195,13 +201,22 @@ describe("the sprite-env listing and create line (#2880)", () => {
195
201
  expect(listedServiceDiffers({ name: "door", status: "running", cmd: "/h/d.sh", needs: [], httpPort: 8080 }, declared, "/h/d.sh")).toMatch(/^needs/);
196
202
  expect(listedServiceDiffers({ name: "door", status: "running", cmd: "/h/d.sh", needs: ["hud"], httpPort: null }, declared, "/h/d.sh")).toMatch(/^httpPort/);
197
203
  expect(listedServiceDiffers({ name: "door", status: "running" }, declared, "/h/d.sh")).toBeNull();
198
- expect(spriteEnvCreateArgs({ ...declared, duration: "2s" }, "/h/d.sh")).toEqual(["services", "create", "door", "--cmd", "/h/d.sh", "--needs", "hud", "--http-port", "8080", "--duration", "2s", "--no-stream"]);
204
+ expect(spriteEnvCreateArgs({ ...declared, duration: "2s" }, "/h/d.sh")).toEqual(["services", "create", "door", "--cmd", "/h/d.sh", "--needs", "hud", "--http-port", "8080", "--duration", "2s"]);
205
+ expect(spriteEnvCreateArgs(declared, ["node", "/h/preview.mjs", "--port", "5173"])).toEqual(["services", "create", "door", "--cmd", "node", "--args", "/h/preview.mjs,--port,5173", "--needs", "hud", "--http-port", "8080", "--no-stream"]);
199
206
  });
200
207
 
201
208
  test("${VAR} expands from the environment, and an unset one fails naming it", () => {
202
209
  expect(expandServiceCommand("app", "${HOME}/box/run.sh --port ${PORT}", { HOME: "/home/sprite", PORT: "5173" })).toBe("/home/sprite/box/run.sh --port 5173");
203
210
  expect(() => expandServiceCommand("app", "${NOPE}/run.sh", {})).toThrow("service app's cmd names ${NOPE}, which is not set in this process's environment");
204
211
  });
212
+
213
+ test("a cmd with arguments splits on whitespace into the executable and its arguments, expanding each word (sandpit#6)", () => {
214
+ expect(serviceCommandArgv("app", "node ${HOME}/box/app/steward/preview.mjs", { HOME: "/home/sprite" })).toEqual(["node", "/home/sprite/box/app/steward/preview.mjs"]);
215
+ expect(serviceCommandArgv("app", " npm --prefix app start ", {})).toEqual(["npm", "--prefix", "app", "start"]);
216
+ expect(serviceCommandArgv("app", "${BIN} --root ${ROOT}", { BIN: "/opt/x", ROOT: "/a b" })).toEqual(["/opt/x", "--root", "/a b"]);
217
+ expect(() => serviceCommandArgv("app", "node a.js --hosts a,b", {})).toThrow('service app\'s cmd has the argument "a,b"');
218
+ expect(() => serviceCommandArgv("app", " ", {})).toThrow("service app's cmd is empty");
219
+ });
205
220
  });
206
221
 
207
222
  describe("spriteApplyServices({ box: true }) through sprite-env (#2880)", () => {
@@ -212,8 +227,8 @@ describe("spriteApplyServices({ box: true }) through sprite-env (#2880)", () =>
212
227
  expect(first.services).toEqual([{ name: "app", action: "created" }, { name: "hud", action: "created" }]);
213
228
  expect(first).toMatchObject({ applied: ["app", "hud"], started: [] });
214
229
  expect(takeCalls()).toEqual([
215
- "services create app --cmd /home/sprite/box/run-app.sh --duration 3s --no-stream",
216
- "services create hud --cmd /home/sprite/box/run-daemon.sh --needs app --http-port 8080 --duration 3s --no-stream",
230
+ "services create app --cmd /home/sprite/box/run-app.sh --duration 3s",
231
+ "services create hud --cmd /home/sprite/box/run-daemon.sh --needs app --http-port 8080 --duration 3s",
217
232
  ]);
218
233
 
219
234
  const second = await spriteApplyServices({ box: true, start: true });
@@ -224,10 +239,80 @@ describe("spriteApplyServices({ box: true }) through sprite-env (#2880)", () =>
224
239
  declare([SERVICES()[0], { ...SERVICES()[1], cmd: "${BOX_HOME}/run-hud.sh" }, SERVICES()[2]]);
225
240
  const third = await spriteApplyServices({ box: true, start: true });
226
241
  expect(third.services).toEqual([{ name: "app", action: "left" }, { name: "hud", action: "replaced" }]);
227
- expect(takeCalls()).toEqual(["services delete hud", "services create hud --cmd /home/sprite/box/run-hud.sh --needs app --http-port 8080 --duration 3s --no-stream"]);
242
+ expect(takeCalls()).toEqual(["services delete hud", "services create hud --cmd /home/sprite/box/run-hud.sh --needs app --http-port 8080 --duration 3s"]);
228
243
  expect((listed().get("hud") as { cmd: string }).cmd).toBe("/home/sprite/box/run-hud.sh");
229
244
  });
230
245
 
246
+ test("a cmd with arguments goes to sprite-env as --cmd and --args, and the listing it makes reads back as converged (sandpit#6)", async () => {
247
+ declare([{ name: "app", cmd: "node ${BOX_HOME}/preview.mjs --port 5173", duration: "3s", health: health("app") }]);
248
+ rmSync(join(state, "app.json"), { force: true });
249
+ takeCalls();
250
+ // hud, from the earlier tests, is not declared here, so the apply deletes it.
251
+ expect((await spriteApplyServices({ box: true })).services).toEqual([{ name: "hud", action: "deleted" }, { name: "app", action: "created" }]);
252
+ expect(takeCalls()).toEqual(["services delete hud", "services create app --cmd node --args /home/sprite/box/preview.mjs,--port,5173 --duration 3s"]);
253
+ expect(listed().get("app")).toMatchObject({ cmd: "node", args: ["/home/sprite/box/preview.mjs", "--port", "5173"] });
254
+ expect((await spriteApplyServices({ box: true })).services).toEqual([{ name: "app", action: "left" }]);
255
+ expect(takeCalls()).toEqual([]);
256
+ rmSync(join(state, "app.json"), { force: true });
257
+ declare(SERVICES());
258
+ });
259
+
260
+ test("a moved httpPort is let go by its old holder before the new holder is defined with it", async () => {
261
+ await spriteApplyServices({ box: true });
262
+ takeCalls();
263
+ // app comes first in start order, so without the release app would be
264
+ // defined with the port while hud still holds it, and the supervisor refuses that.
265
+ const { httpPort: _moved, ...hudWithoutPort } = SERVICES()[1];
266
+ declare([{ ...SERVICES()[0], httpPort: 8080 }, hudWithoutPort, SERVICES()[2]]);
267
+ const moved = await spriteApplyServices({ box: true });
268
+ expect(moved.services).toEqual([{ name: "app", action: "replaced" }, { name: "hud", action: "replaced" }]);
269
+ expect(takeCalls()).toEqual([
270
+ "services delete hud",
271
+ "services create hud --cmd /home/sprite/box/run-daemon.sh --needs app --duration 3s",
272
+ "services delete app",
273
+ "services create app --cmd /home/sprite/box/run-app.sh --http-port 8080 --duration 3s",
274
+ ]);
275
+ expect((listed().get("app") as { http_port: number }).http_port).toBe(8080);
276
+ expect((listed().get("hud") as { http_port: number }).http_port).toBe(0);
277
+
278
+ // And back again: app lets go first this time.
279
+ declare(SERVICES());
280
+ expect((await spriteApplyServices({ box: true })).services).toEqual([{ name: "app", action: "replaced" }, { name: "hud", action: "replaced" }]);
281
+ expect(takeCalls()).toEqual([
282
+ "services delete app",
283
+ "services create app --cmd /home/sprite/box/run-app.sh --duration 3s",
284
+ "services delete hud",
285
+ "services create hud --cmd /home/sprite/box/run-daemon.sh --needs app --http-port 8080 --duration 3s",
286
+ ]);
287
+ });
288
+
289
+ test("a full apply deletes the services the block does not declare, dependants first; only and prune: false leave them", async () => {
290
+ await spriteApplyServices({ box: true });
291
+ // Two services nobody declared, one needing the other, and an optional one that is declared.
292
+ const extra = (name: string, needs: string[]) =>
293
+ writeFileSync(join(state, `${name}.json`), JSON.stringify({ name, cmd: `/x/${name}.sh`, args: [], needs, httpPort: 0, status: "running" }));
294
+ extra("old-db", []);
295
+ extra("old-api", ["old-db"]);
296
+ extra("site", []);
297
+ takeCalls();
298
+
299
+ expect((await spriteApplyServices({ box: true, only: ["app"] })).services).toEqual([{ name: "app", action: "left" }]);
300
+ expect((await spriteApplyServices({ box: true, prune: false })).services).toEqual([{ name: "app", action: "left" }, { name: "hud", action: "left" }]);
301
+ expect(takeCalls()).toEqual([]);
302
+
303
+ const pruned = await spriteApplyServices({ box: true });
304
+ expect(pruned.services).toEqual([
305
+ { name: "old-api", action: "deleted" },
306
+ { name: "old-db", action: "deleted" },
307
+ { name: "app", action: "left" },
308
+ { name: "hud", action: "left" },
309
+ ]);
310
+ expect(pruned.applied).toEqual([]);
311
+ expect(takeCalls()).toEqual(["services delete old-api", "services delete old-db"]);
312
+ expect([...listed().keys()].sort()).toEqual(["app", "hud", "site"]);
313
+ rmSync(join(state, "site.json"));
314
+ });
315
+
231
316
  test("start starts a defined service that is stopped; without start it is left", async () => {
232
317
  await spriteApplyServices({ box: true });
233
318
  setStatus("app", "stopped");
@@ -24,7 +24,7 @@
24
24
 
25
25
  import { execFile } from "node:child_process";
26
26
  import { promisify } from "node:util";
27
- import { expandServiceCommand, inStartOrder, boxServices, type BoxServiceDeclaration } from "./box-services";
27
+ import { serviceCommandArgv, inStartOrder, boxServices, type BoxServiceDeclaration } from "./box-services";
28
28
  import { findSpriteEnv } from "./sprite-service-converge";
29
29
  import { resolveSpritesEndpoint, defaultSpritesHttp, type SpritesHttp } from "./sprites";
30
30
  import { logProgress } from "./progress";
@@ -144,6 +144,12 @@ export interface SpriteApplyServicesArgs {
144
144
  start?: boolean;
145
145
  /** Without `id`: restart each applied service that was already converged. Default: false. */
146
146
  restart?: boolean;
147
+ /**
148
+ * Without `id` and without `only`: delete each service the supervisor has
149
+ * that the box block does not declare (an `optional` one is declared), so a
150
+ * service dropped from the block stops running. Default: true.
151
+ */
152
+ prune?: boolean;
147
153
  /** Without `id`: the `sprite-env` binary. Default: the one on PATH, else /.sprite/bin/sprite-env. */
148
154
  spriteEnv?: string;
149
155
  endpoint?: string;
@@ -151,7 +157,7 @@ export interface SpriteApplyServicesArgs {
151
157
  }
152
158
 
153
159
  /** What an apply through sprite-env did to one service (#2880). */
154
- export type ServiceApplyAction = "created" | "replaced" | "restarted" | "started" | "left";
160
+ export type ServiceApplyAction = "created" | "replaced" | "restarted" | "started" | "left" | "deleted";
155
161
 
156
162
  export interface SpriteApplyServicesResult {
157
163
  /** Names that were created or updated (converged services are skipped). */
@@ -161,7 +167,7 @@ export interface SpriteApplyServicesResult {
161
167
  /**
162
168
  * Without `id`: each service applied, in the order it was applied, with
163
169
  * what the apply did to it (#2880). `left` is a converged service that was
164
- * not restarted or started.
170
+ * not restarted or started, `deleted` one the block does not declare (`prune`).
165
171
  */
166
172
  services?: { name: string; action: ServiceApplyAction }[];
167
173
  }
@@ -363,18 +369,28 @@ export function listedServiceDiffers(listed: ListedService, declared: BoxService
363
369
  return null;
364
370
  }
365
371
 
366
- /** The arguments of `sprite-env services create` for a declared service. Pure. */
367
- export function spriteEnvCreateArgs(s: BoxServiceDeclaration, cmd: string): string[] {
372
+ /**
373
+ * The arguments of `sprite-env services create` for a declared service.
374
+ * `argv` is the command from {@link serviceCommandArgv}: its first word is
375
+ * `--cmd`, the rest `--args` (comma-separated), as sprite-env and upstream
376
+ * Sprites take them; neither runs `--cmd` through a shell. A string is split
377
+ * on whitespace. With a `duration`, sprite-env streams the service's output
378
+ * for that long and fails the create if the service exits in it, so
379
+ * `--no-stream` (which returns as soon as the service is defined) is passed
380
+ * only without one. Pure.
381
+ */
382
+ export function spriteEnvCreateArgs(s: BoxServiceDeclaration, argv: string | readonly string[]): string[] {
383
+ const [cmd, ...rest] = typeof argv === "string" ? argv.trim().split(/\s+/) : argv;
368
384
  return [
369
385
  "services",
370
386
  "create",
371
387
  s.name,
372
388
  "--cmd",
373
389
  cmd,
390
+ ...(rest.length > 0 ? ["--args", rest.join(",")] : []),
374
391
  ...(s.needs.length > 0 ? ["--needs", s.needs.join(",")] : []),
375
392
  ...(s.httpPort !== null ? ["--http-port", String(s.httpPort)] : []),
376
- ...(s.duration !== null ? ["--duration", s.duration] : []),
377
- "--no-stream",
393
+ ...(s.duration !== null ? ["--duration", s.duration] : ["--no-stream"]),
378
394
  ];
379
395
  }
380
396
 
@@ -400,7 +416,7 @@ async function applyThroughSpriteEnv(args: SpriteApplyServicesArgs, signal?: Abo
400
416
  const only = args.only ? new Set(args.only) : null;
401
417
  const targets = inStartOrder(declared).filter((s) => (only ? only.has(s.name) : !s.optional));
402
418
  // Expand every command before changing anything, so a missing variable fails the step with nothing half-applied.
403
- const commands = new Map(targets.map((s) => [s.name, expandServiceCommand(s.name, s.cmd)]));
419
+ const commands = new Map(targets.map((s) => [s.name, serviceCommandArgv(s.name, s.cmd)]));
404
420
 
405
421
  const bin = findSpriteEnv(args.spriteEnv);
406
422
  if (!bin) throw new Error("spriteApplyServices: no sprite-env on PATH or in /.sprite/bin, and no sprite id to reach the Sprites API with");
@@ -414,12 +430,52 @@ async function applyThroughSpriteEnv(args: SpriteApplyServicesArgs, signal?: Abo
414
430
  };
415
431
  const live = new Map(parseServiceDefinitions(await spriteEnv(["services", "list"])).map((s) => [s.name, s]));
416
432
 
433
+ // A full apply leaves the supervisor with the declared services only: one
434
+ // the block no longer declares is deleted, before anything is defined, so
435
+ // the HTTP port or a name it held is free. Dependants go before what they need.
417
436
  const services: { name: string; action: ServiceApplyAction }[] = [];
437
+ if (!only && args.prune !== false) {
438
+ const undeclared = new Set([...live.keys()].filter((n) => !names.has(n)));
439
+ while (undeclared.size > 0) {
440
+ const needed = new Set([...undeclared].flatMap((n) => live.get(n)?.needs ?? []));
441
+ const next = [...undeclared].sort().find((n) => !needed.has(n)) ?? [...undeclared].sort()[0];
442
+ await spriteEnv(["services", "delete", next]);
443
+ logProgress(`services: deleted ${next}, which the box block does not declare`);
444
+ services.push({ name: next, action: "deleted" });
445
+ undeclared.delete(next);
446
+ live.delete(next);
447
+ }
448
+ }
449
+
450
+ // The supervisor gives one service the HTTP port and refuses a second holder
451
+ // (409) while the first still has it. So a service whose listed port is not
452
+ // the declared one is replaced first, before any service is defined with a
453
+ // port: when the port moves from one service to another, the old holder has
454
+ // let go of it by the time the new one asks. One whose declared needs are not
455
+ // all defined yet can't be created early and waits for its turn below.
456
+ const released = new Set<string>();
418
457
  for (const s of targets) {
419
- const cmd = commands.get(s.name)!;
458
+ const cur = live.get(s.name);
459
+ if (!cur || cur.httpPort === undefined || cur.httpPort === null || cur.httpPort === s.httpPort) continue;
460
+ if (!s.needs.every((n) => live.has(n))) continue;
461
+ const argv = commands.get(s.name)!;
462
+ const why = listedServiceDiffers(cur, s, argv.join(" "));
463
+ await spriteEnv(["services", "delete", s.name]);
464
+ await spriteEnv(spriteEnvCreateArgs(s, argv));
465
+ logProgress(`services: replaced ${s.name} (${why}) first, to free the HTTP port`);
466
+ released.add(s.name);
467
+ }
468
+
469
+ for (const s of targets) {
470
+ if (released.has(s.name)) {
471
+ services.push({ name: s.name, action: "replaced" });
472
+ continue;
473
+ }
474
+ const argv = commands.get(s.name)!;
475
+ const cmd = argv.join(" ");
420
476
  const cur = live.get(s.name);
421
477
  if (!cur) {
422
- await spriteEnv(spriteEnvCreateArgs(s, cmd));
478
+ await spriteEnv(spriteEnvCreateArgs(s, argv));
423
479
  logProgress(`services: created ${s.name}`);
424
480
  services.push({ name: s.name, action: "created" });
425
481
  continue;
@@ -427,7 +483,7 @@ async function applyThroughSpriteEnv(args: SpriteApplyServicesArgs, signal?: Abo
427
483
  const why = listedServiceDiffers(cur, s, cmd);
428
484
  if (why) {
429
485
  await spriteEnv(["services", "delete", s.name]);
430
- await spriteEnv(spriteEnvCreateArgs(s, cmd));
486
+ await spriteEnv(spriteEnvCreateArgs(s, argv));
431
487
  logProgress(`services: replaced ${s.name} (${why})`);
432
488
  services.push({ name: s.name, action: "replaced" });
433
489
  continue;
@@ -452,6 +508,7 @@ async function applyThroughSpriteEnv(args: SpriteApplyServicesArgs, signal?: Abo
452
508
  }
453
509
  }
454
510
  const applied = services.filter((e) => e.action === "created" || e.action === "replaced").map((e) => e.name);
455
- logProgress(`services: applied ${applied.length}/${targets.length} through sprite-env, started ${started.length}`);
511
+ const deleted = services.filter((e) => e.action === "deleted").length;
512
+ logProgress(`services: applied ${applied.length}/${targets.length} through sprite-env, started ${started.length}${deleted > 0 ? `, deleted ${deleted}` : ""}`);
456
513
  return { applied, started, services };
457
514
  }