paratix 0.3.0 → 0.5.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.
package/dist/index.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  printCommandFailure,
3
3
  printModuleResult,
4
4
  printRecipeHeader,
5
+ printRecipeModuleResult,
5
6
  runSignalModules,
6
7
  startModuleSpinner,
7
8
  withRecipeOutputScope
8
- } from "./chunk-DUIGEB2J.js";
9
+ } from "./chunk-3WK4QNJK.js";
9
10
  import {
10
11
  NEEDS_APPLY,
11
12
  apt,
@@ -13,6 +14,7 @@ import {
13
14
  command,
14
15
  compose,
15
16
  cron,
17
+ detectPackageManager,
16
18
  download,
17
19
  failed,
18
20
  failedCommand,
@@ -20,10 +22,12 @@ import {
20
22
  git,
21
23
  group,
22
24
  hostname,
25
+ isPackageInstalled,
23
26
  mount,
24
27
  net,
25
28
  op,
26
29
  pkg,
30
+ quadlet,
27
31
  releaseUpgrade,
28
32
  rsync,
29
33
  script,
@@ -35,7 +39,7 @@ import {
35
39
  systemd,
36
40
  ufw,
37
41
  user
38
- } from "./chunk-ULJMW23T.js";
42
+ } from "./chunk-LI47NIKN.js";
39
43
  import {
40
44
  CommandError,
41
45
  assertValidModuleMetaEntries,
@@ -58,7 +62,166 @@ import {
58
62
  systemHostMeta,
59
63
  systemRebootMeta,
60
64
  validateSshConfig
61
- } from "./chunk-G3BMCQKU.js";
65
+ } from "./chunk-MHPFGCEY.js";
66
+
67
+ // src/conditionalModules.ts
68
+ function createConditionalApplyState(environment) {
69
+ return { environment: { ...environment }, meta: [], status: "ok" };
70
+ }
71
+ function markConditionalApplyChanged(state) {
72
+ return { ...state, status: "changed" };
73
+ }
74
+ async function executeConditionalApply(parameters) {
75
+ const { dryRun, environment, module, ssh: ssh2 } = parameters;
76
+ if (dryRun && module._applyDryRun != null) {
77
+ return module._applyDryRun(ssh2, environment);
78
+ }
79
+ return module.apply(ssh2, environment);
80
+ }
81
+ function shouldExecuteConditionalApply(module, dryRun) {
82
+ if (!dryRun) return true;
83
+ return module._applyDryRun != null || module._dryRunBlocker === true || module._dryRunMetaProducer === true;
84
+ }
85
+ async function mergeConditionalApplyState(state, result) {
86
+ const environment = await mergeEnvironmentFromMeta(state.environment, result.meta);
87
+ return {
88
+ environment,
89
+ flushSignals: result._flushSignals === true ? true : state.flushSignals,
90
+ meta: result.meta == null ? state.meta : [...state.meta, ...result.meta],
91
+ status: result.status === "changed" ? "changed" : state.status,
92
+ stopRun: result._stopRun === true ? true : state.stopRun
93
+ };
94
+ }
95
+ async function applyConditionalModules(parameters) {
96
+ const { dryRun = false, modules, ssh: ssh2 } = parameters;
97
+ let state = createConditionalApplyState(parameters.environment);
98
+ for (const currentModule of modules) {
99
+ const checkResult = await currentModule.check(ssh2, state.environment);
100
+ if (checkResult === "ok") continue;
101
+ if (!shouldExecuteConditionalApply(currentModule, dryRun)) {
102
+ state = markConditionalApplyChanged(state);
103
+ continue;
104
+ }
105
+ const result = await executeConditionalApply({
106
+ dryRun,
107
+ environment: state.environment,
108
+ module: currentModule,
109
+ ssh: ssh2
110
+ });
111
+ if (result.status === "failed") return result;
112
+ state = await mergeConditionalApplyState(state, result);
113
+ if (state.stopRun === true) break;
114
+ }
115
+ return {
116
+ _flushSignals: state.flushSignals,
117
+ _stopRun: state.stopRun,
118
+ meta: state.meta.length === 0 ? void 0 : state.meta,
119
+ status: state.status
120
+ };
121
+ }
122
+ function shouldExecuteConditionalDryRun(module) {
123
+ return module._applyDryRun != null || module._dryRunBlocker === true || module._dryRunMetaProducer === true;
124
+ }
125
+ function whenNeedsDryRunApply(modules) {
126
+ return modules.some((module) => shouldExecuteConditionalDryRun(module));
127
+ }
128
+ async function checkConditionalModules(modules, ssh2, environment) {
129
+ const currentEnvironment = { ...environment };
130
+ for (const currentModule of modules) {
131
+ const result = await currentModule.check(ssh2, currentEnvironment);
132
+ if (result === NEEDS_APPLY) {
133
+ return NEEDS_APPLY;
134
+ }
135
+ }
136
+ return "ok";
137
+ }
138
+ function createWhenDryRunApply(condition, modules, needsDryRunApply) {
139
+ if (!needsDryRunApply) return void 0;
140
+ return async (ssh2, environment) => {
141
+ if (!await condition(ssh2, environment)) {
142
+ return { status: "skipped" };
143
+ }
144
+ return applyConditionalModules({ dryRun: true, environment, modules, ssh: ssh2 });
145
+ };
146
+ }
147
+ function createConditionalModule(parameters) {
148
+ const needsDryRunApply = whenNeedsDryRunApply(parameters.modules);
149
+ const applyDryRun = createWhenDryRunApply(
150
+ parameters.condition,
151
+ parameters.modules,
152
+ needsDryRunApply
153
+ );
154
+ return {
155
+ ...parameters.modules.some((module) => module._dryRunBlocker === true) ? { _dryRunBlocker: true } : {},
156
+ ...parameters.modules.some((module) => module._dryRunMetaProducer === true) ? { _dryRunMetaProducer: true } : {},
157
+ ...applyDryRun == null ? {} : { _applyDryRun: applyDryRun },
158
+ async apply(ssh2, environment) {
159
+ if (!await parameters.condition(ssh2, environment)) {
160
+ return { status: "skipped" };
161
+ }
162
+ return applyConditionalModules({ environment, modules: parameters.modules, ssh: ssh2 });
163
+ },
164
+ async check(ssh2, environment) {
165
+ if (!await parameters.condition(ssh2, environment)) {
166
+ return "ok";
167
+ }
168
+ return checkConditionalModules(parameters.modules, ssh2, environment);
169
+ },
170
+ name: parameters.name
171
+ };
172
+ }
173
+ function filesystemTypeName(testFlag) {
174
+ switch (testFlag) {
175
+ case "-d": {
176
+ return "path";
177
+ }
178
+ case "-f": {
179
+ return "file";
180
+ }
181
+ case "-L": {
182
+ return "symlink";
183
+ }
184
+ case "-S": {
185
+ return "socket";
186
+ }
187
+ }
188
+ }
189
+ function createFilesystemGuard(parameters) {
190
+ const typeName = filesystemTypeName(parameters.testFlag);
191
+ return createConditionalModule({
192
+ condition: async (ssh2) => {
193
+ if (ssh2 == null) return false;
194
+ const exists = await ssh2.test(`test ${parameters.testFlag} ${shellQuote(parameters.path)}`);
195
+ return parameters.invert ? !exists : exists;
196
+ },
197
+ modules: parameters.modules,
198
+ name: `when.${typeName}${parameters.invert ? "Missing" : "Exists"}: ${parameters.path}`
199
+ });
200
+ }
201
+ function createCommandGuard(commandName, invert, modules) {
202
+ return createConditionalModule({
203
+ condition: async (ssh2) => {
204
+ if (ssh2 == null) return false;
205
+ const exists = await ssh2.test(`command -v ${shellQuote(commandName)} >/dev/null 2>&1`);
206
+ return invert ? !exists : exists;
207
+ },
208
+ modules,
209
+ name: `when.command${invert ? "Missing" : "Exists"}: ${commandName}`
210
+ });
211
+ }
212
+ function createPackageGuard(packageName, invert, modules) {
213
+ return createConditionalModule({
214
+ condition: async (ssh2) => {
215
+ if (ssh2 == null) return false;
216
+ const pm = await detectPackageManager(ssh2);
217
+ if (pm == null) return false;
218
+ const installed = await isPackageInstalled(ssh2, pm, packageName);
219
+ return invert ? !installed : installed;
220
+ },
221
+ modules,
222
+ name: `when.package${invert ? "Absent" : "Installed"}: ${packageName}`
223
+ });
224
+ }
62
225
 
63
226
  // src/builtins.ts
64
227
  function assert(condition, message) {
@@ -192,107 +355,40 @@ var signals = {
192
355
  };
193
356
  }
194
357
  };
195
- async function applyConditionalModules(parameters) {
196
- const { dryRun = false, modules, ssh: ssh2 } = parameters;
197
- let state = createConditionalApplyState(parameters.environment);
198
- for (const currentModule of modules) {
199
- const checkResult = await currentModule.check(ssh2, state.environment);
200
- if (checkResult === "ok") continue;
201
- if (!shouldExecuteConditionalApply(currentModule, dryRun)) {
202
- state = markConditionalApplyChanged(state);
203
- continue;
204
- }
205
- const result = await executeConditionalApply({
206
- dryRun,
207
- environment: state.environment,
208
- module: currentModule,
209
- ssh: ssh2
210
- });
211
- if (result.status === "failed") return result;
212
- state = await mergeConditionalApplyState(state, result);
213
- if (state.stopRun === true) break;
214
- }
215
- return {
216
- _flushSignals: state.flushSignals,
217
- _stopRun: state.stopRun,
218
- meta: state.meta.length === 0 ? void 0 : state.meta,
219
- status: state.status
220
- };
221
- }
222
- function shouldExecuteConditionalApply(module, dryRun) {
223
- if (!dryRun) return true;
224
- return module._applyDryRun != null || module._dryRunBlocker === true || module._dryRunMetaProducer === true;
225
- }
226
- function createConditionalApplyState(environment) {
227
- return { environment: { ...environment }, meta: [], status: "ok" };
228
- }
229
- function markConditionalApplyChanged(state) {
230
- return { ...state, status: "changed" };
231
- }
232
- async function executeConditionalApply(parameters) {
233
- const { dryRun, environment, module, ssh: ssh2 } = parameters;
234
- if (dryRun && module._applyDryRun != null) {
235
- return module._applyDryRun(ssh2, environment);
236
- }
237
- return module.apply(ssh2, environment);
238
- }
239
- function whenNeedsDryRunApply(modules) {
240
- return modules.some((module) => shouldExecuteConditionalDryRun(module));
241
- }
242
- function shouldExecuteConditionalDryRun(module) {
243
- return module._applyDryRun != null || module._dryRunBlocker === true || module._dryRunMetaProducer === true;
244
- }
245
- async function mergeConditionalApplyState(state, result) {
246
- const environment = await mergeEnvironmentFromMeta(state.environment, result.meta);
247
- return {
248
- environment,
249
- flushSignals: result._flushSignals === true ? true : state.flushSignals,
250
- meta: result.meta == null ? state.meta : [...state.meta, ...result.meta],
251
- status: result.status === "changed" ? "changed" : state.status,
252
- stopRun: result._stopRun === true ? true : state.stopRun
253
- };
254
- }
255
- function createWhenDryRunApply(condition, modules, needsDryRunApply) {
256
- if (!needsDryRunApply) return void 0;
257
- return async (ssh2, environment) => {
258
- if (!condition(environment)) {
259
- return { status: "skipped" };
260
- }
261
- return applyConditionalModules({ dryRun: true, environment, modules, ssh: ssh2 });
262
- };
263
- }
264
- function when(condition, ...modules) {
265
- const needsDryRunApply = whenNeedsDryRunApply(modules);
266
- const applyDryRun = createWhenDryRunApply(condition, modules, needsDryRunApply);
267
- return {
268
- ...modules.some((module) => module._dryRunBlocker === true) ? { _dryRunBlocker: true } : {},
269
- ...modules.some((module) => module._dryRunMetaProducer === true) ? { _dryRunMetaProducer: true } : {},
270
- ...applyDryRun == null ? {} : { _applyDryRun: applyDryRun },
271
- async apply(ssh2, environment) {
272
- if (!condition(environment)) {
273
- return { status: "skipped" };
274
- }
275
- return applyConditionalModules({ environment, modules, ssh: ssh2 });
276
- },
277
- async check(ssh2, environment) {
278
- if (!condition(environment)) {
279
- return "ok";
280
- }
281
- const currentEnvironment = { ...environment };
282
- for (const currentModule of modules) {
283
- const result = await currentModule.check(ssh2, currentEnvironment);
284
- if (result === NEEDS_APPLY) {
285
- return NEEDS_APPLY;
286
- }
287
- }
288
- return "ok";
289
- },
358
+ function baseWhen(condition, ...modules) {
359
+ return createConditionalModule({
360
+ condition: (_ssh, environment) => condition(environment),
361
+ modules,
290
362
  name: `when: conditional (${modules.length} modules)`
291
- };
363
+ });
292
364
  }
365
+ var when = Object.assign(baseWhen, {
366
+ commandExists: (commandName, ...modules) => createCommandGuard(commandName, false, modules),
367
+ commandMissing: (commandName, ...modules) => createCommandGuard(commandName, true, modules),
368
+ fileExists: (path, ...modules) => createFilesystemGuard({ invert: false, modules, path, testFlag: "-f" }),
369
+ fileMissing: (path, ...modules) => createFilesystemGuard({ invert: true, modules, path, testFlag: "-f" }),
370
+ packageAbsent: (packageName, ...modules) => createPackageGuard(packageName, true, modules),
371
+ packageInstalled: (packageName, ...modules) => createPackageGuard(packageName, false, modules),
372
+ pathExists: (path, ...modules) => createFilesystemGuard({ invert: false, modules, path, testFlag: "-d" }),
373
+ pathMissing: (path, ...modules) => createFilesystemGuard({ invert: true, modules, path, testFlag: "-d" }),
374
+ socketExists: (path, ...modules) => createFilesystemGuard({ invert: false, modules, path, testFlag: "-S" }),
375
+ socketMissing: (path, ...modules) => createFilesystemGuard({ invert: true, modules, path, testFlag: "-S" }),
376
+ symlinkExists: (path, ...modules) => createFilesystemGuard({ invert: false, modules, path, testFlag: "-L" }),
377
+ symlinkMissing: (path, ...modules) => createFilesystemGuard({ invert: true, modules, path, testFlag: "-L" })
378
+ });
293
379
 
294
380
  // src/recipe.ts
295
381
  var INTERRUPTED_BEFORE_APPLY = /* @__PURE__ */ Symbol("recipe-interrupted-before-apply");
382
+ function isRecipeModuleLike(module) {
383
+ return module._isRecipe === true;
384
+ }
385
+ function printRecipeChildResult(module, result) {
386
+ if (isRecipeModuleLike(module)) {
387
+ printRecipeModuleResult(module.name, result.status);
388
+ return;
389
+ }
390
+ printModuleResult(module.name, result.status);
391
+ }
296
392
  function applyRecipeStepToState(state, step, preserveControlPlaneMeta) {
297
393
  let stepMeta;
298
394
  if (step.meta == null) {
@@ -327,7 +423,7 @@ async function executeOneModule(parameters) {
327
423
  return INTERRUPTED_BEFORE_APPLY;
328
424
  }
329
425
  const result = await targetModule.apply(connection, currentEnvironment);
330
- printModuleResult(targetModule.name, result.status);
426
+ printRecipeChildResult(targetModule, result);
331
427
  if (result.status === "failed" && result.error != null) {
332
428
  printCommandFailure(result.error, verbose);
333
429
  }
@@ -597,6 +693,7 @@ export {
597
693
  op,
598
694
  pkg as package,
599
695
  pause,
696
+ quadlet,
600
697
  recipe,
601
698
  releaseUpgrade,
602
699
  rsync,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/builtins.ts","../src/recipe.ts","../src/server.ts"],"sourcesContent":["import { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { failed } from \"./moduleFailure.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Fail the run if a condition on the current env is not satisfied.\n *\n * The check phase evaluates the condition; if it returns `false`, apply\n * marks the module as failed, which aborts the parent recipe.\n *\n * @param condition - A predicate receiving the current env at run time.\n * @param message - Human-readable description shown in the run output.\n * @returns A Module that asserts the condition.\n *\n * @example\n * assert(env => !!env[\"APP_SECRET\"], \"APP_SECRET must be set\")\n */\nexport function assert(condition: (environment: Environment) => boolean, message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (condition(environment)) {\n return { status: \"ok\" }\n }\n return failed(`[assert] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return condition(environment) ? \"ok\" : NEEDS_APPLY\n },\n name: `assert: ${message}`,\n }\n}\n\n/**\n * Print a static debug message to the console during the apply phase.\n * Always runs (never skipped by the check phase).\n *\n * @param message - The message to print, prefixed with `[debug]`.\n * @returns A Module that prints a debug message.\n */\nexport function debug(message: string): Module {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n console.log(` [debug] ${message}`)\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `debug: ${message}`,\n }\n}\n\n/**\n * Unconditionally abort the run with a failed status and an error message.\n * Useful as a sentinel at the end of a conditional branch.\n *\n * @param message - The message to print, prefixed with `[fail]`.\n * @returns A Module that fails the run.\n */\nexport function fail(message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return failed(`[fail] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `fail: ${message}`,\n }\n}\n\n/**\n * Pause execution and wait for the operator to press Enter.\n * Useful for interactive confirmation during a run.\n *\n * @param message - Prompt shown to the operator. Defaults to `\"Press enter to continue...\"`.\n * @returns A Module that pauses execution.\n */\nexport function pause(message?: string): Module {\n return {\n async apply(): Promise<ModuleResult> {\n const promptText = message ?? \"Press enter to continue...\"\n process.stdout.write(` [pause] ${promptText} `)\n\n await new Promise<void>((resolve) => {\n process.stdin.once(\"data\", () => {\n process.stdin.pause()\n resolve()\n })\n })\n\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: message == null ? \"pause\" : `pause: ${message}`,\n }\n}\n\nfunction isFirstRunEnabled(environment: Environment): boolean {\n return environment.PARATIX_FIRST_RUN === \"true\" || environment.FIRST_RUN === true\n}\n\n/**\n * Built-ins related to the explicit first-run bootstrap stage.\n */\nexport const firstRun = {\n /**\n * Stop the current run successfully when Paratix was invoked with `--first-run`.\n * Useful as an explicit stage boundary in scaffolded playbooks.\n *\n * @param message - Optional note shown in the module name.\n * @returns A local module that stops the run only during first-run execution.\n */\n stop(message?: string): Module {\n const moduleName = message == null ? \"firstRun.stop\" : `firstRun.stop: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!isFirstRunEnabled(environment)) {\n return { status: \"ok\" }\n }\n\n return {\n _dryRunDetail: \"(first-run stop)\",\n _stopRun: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return isFirstRunEnabled(environment) ? NEEDS_APPLY : \"ok\"\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\n/**\n * Built-ins for explicit signal checkpoints.\n */\nexport const signals = {\n /**\n * Flush all currently pending signals for the active scope.\n * Signals remain scope-local:\n * - in a recipe, this flushes that recipe's signals\n * - at top level, this flushes `server(...).signals`\n *\n * @param message - Optional note shown in the module name.\n * @returns A local control module that requests an immediate signal flush.\n */\n flush(message?: string): Module {\n const moduleName = message == null ? \"signals.flush\" : `signals.flush: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return {\n _dryRunDetail: \"(dry-run, pending signals not executed)\",\n _flushSignals: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\nasync function applyConditionalModules(parameters: {\n dryRun?: boolean\n environment: Environment\n modules: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun = false, modules, ssh } = parameters\n let state = createConditionalApplyState(parameters.environment)\n\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const checkResult = await currentModule.check(ssh, state.environment)\n if (checkResult === \"ok\") continue\n\n if (!shouldExecuteConditionalApply(currentModule, dryRun)) {\n state = markConditionalApplyChanged(state)\n continue\n }\n\n // eslint-disable-next-line no-await-in-loop -- conditional modules must preserve ordered env propagation\n const result = await executeConditionalApply({\n dryRun,\n environment: state.environment,\n module: currentModule,\n ssh,\n })\n if (result.status === \"failed\") return result\n // eslint-disable-next-line no-await-in-loop -- downstream env must see each module's meta in order\n state = await mergeConditionalApplyState(state, result)\n if (state.stopRun === true) break\n }\n\n return {\n _flushSignals: state.flushSignals,\n _stopRun: state.stopRun,\n meta: state.meta.length === 0 ? undefined : state.meta,\n status: state.status,\n }\n}\n\nfunction shouldExecuteConditionalApply(module: Module, dryRun: boolean): boolean {\n if (!dryRun) return true\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\ntype ConditionalApplyState = {\n environment: Environment\n flushSignals?: true\n meta: ModuleMetaEntry[]\n status: \"changed\" | \"ok\" | \"skipped\"\n stopRun?: true\n}\n\nfunction createConditionalApplyState(environment: Environment): ConditionalApplyState {\n return { environment: { ...environment }, meta: [], status: \"ok\" }\n}\n\nfunction markConditionalApplyChanged(state: ConditionalApplyState): ConditionalApplyState {\n return { ...state, status: \"changed\" }\n}\n\nasync function executeConditionalApply(parameters: {\n dryRun: boolean\n environment: Environment\n module: Module\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun, environment, module, ssh } = parameters\n if (dryRun && module._applyDryRun != null) {\n return module._applyDryRun(ssh, environment)\n }\n return module.apply(ssh, environment)\n}\n\nfunction whenNeedsDryRunApply(modules: Module[]): boolean {\n return modules.some((module) => shouldExecuteConditionalDryRun(module))\n}\n\nfunction shouldExecuteConditionalDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function mergeConditionalApplyState(\n state: ConditionalApplyState,\n result: ModuleResult\n): Promise<ConditionalApplyState> {\n const environment = await mergeEnvironmentFromMeta(state.environment, result.meta)\n return {\n environment,\n flushSignals: result._flushSignals === true ? true : state.flushSignals,\n meta: result.meta == null ? state.meta : [...state.meta, ...result.meta],\n status: result.status === \"changed\" ? \"changed\" : state.status,\n stopRun: result._stopRun === true ? true : state.stopRun,\n }\n}\n\nfunction createWhenDryRunApply(\n condition: (environment: Environment) => boolean,\n modules: Module[],\n needsDryRunApply: boolean\n): ((ssh: null | SshConnection, environment: Environment) => Promise<ModuleResult>) | undefined {\n if (!needsDryRunApply) return undefined\n return async (ssh: null | SshConnection, environment: Environment) => {\n if (!condition(environment)) {\n return { status: \"skipped\" as const }\n }\n return applyConditionalModules({ dryRun: true, environment, modules, ssh })\n }\n}\n\n/**\n * Run one or more modules only when a runtime condition is met.\n * When the condition is `false`, the whole group is skipped without running\n * any child checks or applies.\n *\n * @param condition - A predicate evaluated against the current env at run time.\n * @param modules - One or more modules to run when the condition is `true`.\n * @returns A Module that conditionally runs the inner modules.\n *\n * @example\n * when(env => env[\"DEPLOY_ENV\"] === \"production\", service.enabled(\"fail2ban\"))\n */\nexport function when(\n condition: (environment: Environment) => boolean,\n ...modules: Module[]\n): Module {\n const needsDryRunApply = whenNeedsDryRunApply(modules)\n const applyDryRun = createWhenDryRunApply(condition, modules, needsDryRunApply)\n return {\n ...(modules.some((module) => module._dryRunBlocker === true)\n ? { _dryRunBlocker: true as const }\n : {}),\n ...(modules.some((module) => module._dryRunMetaProducer === true)\n ? { _dryRunMetaProducer: true as const }\n : {}),\n ...(applyDryRun == null ? {} : { _applyDryRun: applyDryRun }),\n async apply(ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!condition(environment)) {\n return { status: \"skipped\" }\n }\n return applyConditionalModules({ environment, modules, ssh })\n },\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n if (!condition(environment)) {\n return \"ok\"\n }\n // Defensive copy so inner modules can mutate the env without affecting\n // the caller's object (see Bug #13 regression tests).\n const currentEnvironment = { ...environment }\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await currentModule.check(ssh, currentEnvironment)\n if (result === NEEDS_APPLY) {\n return NEEDS_APPLY\n }\n }\n return \"ok\"\n },\n name: `when: conditional (${modules.length} modules)`,\n }\n}\n","/* eslint-disable max-lines -- recipe orchestration intentionally stays co-located */\nimport { isEnvironmentMetaEntry, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\nimport { runSignalModules, type SignalHooks } from \"./signalOrchestration.js\"\nimport { CommandError } from \"./sshHelpers.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n type ModuleStatus,\n NEEDS_APPLY,\n type OrchestrationStep,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Internal representation of a recipe module.\n * The `_isRecipe` flag lets the runner distinguish recipes from leaf modules.\n * @internal\n */\nexport type RecipeModule = {\n _isRecipe: true\n _modules: Module[]\n _signals?: Module[]\n apply: (\n ssh: null | SshConnection,\n environment: Environment,\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ) => Promise<ModuleResult>\n} & Module\n\ntype RecipeState = {\n env: Environment\n meta?: ModuleMetaEntry[]\n signalsPending: boolean\n status: Exclude<ModuleStatus, \"skipped\">\n stopRun?: true\n}\n\ntype ExecuteModulesParameters = {\n environment: Environment\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n verbose?: boolean\n}\n\ntype RecipeLoopStepResult =\n | { kind: \"break\"; state: RecipeState }\n | { kind: \"continue\"; state: RecipeState }\n\nconst INTERRUPTED_BEFORE_APPLY = Symbol(\"recipe-interrupted-before-apply\")\n\nfunction applyRecipeStepToState(\n state: RecipeState,\n step: OrchestrationStep,\n preserveControlPlaneMeta: boolean\n): RecipeState {\n let stepMeta: ModuleMetaEntry[] | undefined\n if (step.meta == null) {\n stepMeta = undefined\n } else if (preserveControlPlaneMeta) {\n stepMeta = step.meta\n } else {\n stepMeta = step.meta.filter(isEnvironmentMetaEntry)\n }\n const nextMeta = stepMeta == null ? (state.meta ?? []) : [...(state.meta ?? []), ...stepMeta]\n let nextStatus = state.status\n if (step.status === \"failed\") nextStatus = \"failed\"\n else if (step.status === \"changed\") nextStatus = \"changed\"\n\n return {\n env: step.env,\n meta: nextMeta,\n signalsPending: step.status === \"changed\" ? true : state.signalsPending,\n status: nextStatus,\n stopRun: step._stopRun === true ? true : state.stopRun,\n }\n}\n\n/**\n * Recipes run their own check→apply loop, separate from the runner's\n * `runModuleLoop` in runner.ts. This is intentional: recipes execute as a\n * single nested module inside the runner, so SSH-lifecycle concerns\n * (port-change reconnects, reboot handling), shutdown-signal guards,\n * dry-run mode and stats tracking are the runner's responsibility and\n * must not be duplicated here.\n *\n * @param parameters - Parameters for executing one child module.\n * @param parameters.targetModule - The module to check and conditionally apply.\n * @param parameters.ssh - Active SSH connection, or `null` for local modules.\n * @param parameters.currentEnvironment - Environment values available to the module.\n * @param parameters.shutdownSignal - Optional shutdown getter used to suppress new apply steps.\n * @param parameters.verbose - Whether verbose command diagnostics should be printed.\n * @returns The updated environment and status, or `null` if the module was already ok.\n */\nasync function executeOneModule(parameters: {\n currentEnvironment: Environment\n shutdownSignal?: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose?: boolean\n}): Promise<null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY> {\n const { currentEnvironment, ssh, targetModule } = parameters\n const verbose = parameters.verbose ?? false\n const connection = targetModule.local === true ? null : ssh\n const checkResult = await checkRecipeChild(targetModule, connection, currentEnvironment)\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return null\n }\n\n if ((parameters.shutdownSignal?.() ?? null) != null) {\n return INTERRUPTED_BEFORE_APPLY\n }\n\n const result = await targetModule.apply(connection, currentEnvironment)\n printModuleResult(targetModule.name, result.status)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n\n const environment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n return {\n _flushSignals: result._flushSignals,\n _stopRun: result._stopRun,\n env: environment,\n meta: result.meta,\n status: result.status,\n }\n}\n\nasync function checkRecipeChild(\n targetModule: Module,\n connection: null | SshConnection,\n currentEnvironment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, currentEnvironment)\n}\n\nasync function applyExecutedRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n state: RecipeState\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n}): Promise<null | RecipeState> {\n if (parameters.step == null) return parameters.state\n if (parameters.step === INTERRUPTED_BEFORE_APPLY) return null\n\n if (parameters.onChildStep != null) {\n await parameters.onChildStep(parameters.step)\n }\n\n return applyRecipeStepToState(\n parameters.state,\n parameters.step,\n parameters.preserveControlPlaneMeta\n )\n}\n\nfunction failedRecipeState(environment: Environment): RecipeState {\n return {\n env: environment,\n meta: undefined,\n signalsPending: false,\n status: \"failed\",\n }\n}\n\nfunction annotateRecipeChildError(moduleName: string, error: unknown): Error {\n const prefix = `[${moduleName}] `\n if (error instanceof CommandError) {\n return new CommandError(`${prefix}${error.message}`, error.fullStdout, error.fullStderr)\n }\n if (error instanceof Error) {\n return new Error(`${prefix}${error.message}`)\n }\n return new Error(`${prefix}${String(error)}`)\n}\n\ntype RecipeChildExecution =\n | { kind: \"failed\"; state: RecipeState }\n | {\n kind: \"step\"\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n }\n\nasync function executeRecipeChildStep(parameters: {\n currentEnvironment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose: boolean\n}): Promise<RecipeChildExecution> {\n try {\n return { kind: \"step\", step: await executeOneModule(parameters) }\n } catch (error) {\n if (parameters.shutdownSignal() != null) {\n return { kind: \"step\", step: INTERRUPTED_BEFORE_APPLY }\n }\n printModuleResult(parameters.targetModule.name, \"failed\")\n printCommandFailure(error, parameters.verbose)\n return { kind: \"failed\", state: failedRecipeState(parameters.currentEnvironment) }\n }\n}\n\nasync function processRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n ssh: null | SshConnection\n state: RecipeState\n step: RecipeChildExecution\n verbose: boolean\n}): Promise<RecipeLoopStepResult> {\n if (parameters.step.kind === \"failed\") {\n return { kind: \"break\", state: parameters.step.state }\n }\n\n const nextState = await applyExecutedRecipeStep({\n onChildStep: parameters.onChildStep,\n preserveControlPlaneMeta: parameters.preserveControlPlaneMeta,\n state: parameters.state,\n step: parameters.step.step,\n })\n if (nextState == null) {\n return { kind: \"break\", state: parameters.state }\n }\n\n let state = nextState\n if (shouldFlushRecipeSignals(parameters.step.step, state, parameters.signals)) {\n const signalStatus = await flushPendingRecipeSignals({\n environment: state.env,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n state = applyRecipeSignalStatus(state, signalStatus)\n }\n\n if (state.status === \"failed\" || state.stopRun === true) {\n return { kind: \"break\", state }\n }\n\n return { kind: \"continue\", state }\n}\n\nasync function executeModules(\n modules: Module[],\n ssh: null | SshConnection,\n parameters: ExecuteModulesParameters\n): Promise<RecipeState> {\n const onChildStep = parameters.onChildStep\n const preserveControlPlaneMeta = onChildStep == null\n const shutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let state: RecipeState = {\n env: { ...parameters.environment },\n meta: undefined,\n signalsPending: false,\n status: \"ok\",\n }\n\n for (const currentModule of modules) {\n if (shutdownSignal() != null) break\n // eslint-disable-next-line no-await-in-loop\n const step = await executeRecipeChildStep({\n currentEnvironment: state.env,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n // eslint-disable-next-line no-await-in-loop\n const processedStep = await processRecipeStep({\n onChildStep,\n onSignalStep: parameters.onSignalStep,\n preserveControlPlaneMeta,\n shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh,\n state,\n step,\n verbose,\n })\n state = processedStep.state\n if (processedStep.kind === \"break\") return state\n }\n\n return state\n}\n\nasync function triggerSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return runSignalModules({\n environment: parameters.environment,\n hooks: parameters.signalHooks,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldFlushRecipeSignals(\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY,\n state: RecipeState,\n signals?: Module[]\n): signals is Module[] {\n return (\n step != null &&\n step !== INTERRUPTED_BEFORE_APPLY &&\n step._flushSignals === true &&\n state.signalsPending &&\n signals != null\n )\n}\n\nfunction applyRecipeSignalStatus(\n state: RecipeState,\n signalStatus: \"changed\" | \"failed\"\n): RecipeState {\n return {\n ...state,\n signalsPending: false,\n status: signalStatus === \"failed\" ? \"failed\" : state.status,\n }\n}\n\nasync function flushPendingRecipeSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return triggerSignals(parameters)\n}\n\nasync function applyRecipe(parameters: {\n environment: Environment\n modules: Module[]\n name: string\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n signals?: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n return withRecipeOutputScope(async () => {\n const shutdownSignal = parameters.options?.shutdownSignal\n const verbose = parameters.options?.verbose ?? false\n printRecipeHeader(parameters.name)\n const state = await executeModules(parameters.modules, parameters.ssh, {\n environment: parameters.environment,\n onChildStep: parameters.options?.onChildStep,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n verbose,\n })\n\n if (shouldRunRecipeSignalsAtEnd(state, parameters.signals)) {\n state.status = await triggerSignals({\n environment: state.env,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose,\n })\n }\n\n return {\n _stopRun: state.stopRun,\n meta: state.meta,\n status: state.status,\n }\n })\n}\n\nfunction shouldRunRecipeSignalsAtEnd(state: RecipeState, signals?: Module[]): signals is Module[] {\n return state.signalsPending && state.status === \"changed\" && signals != null\n}\n\n/**\n * Group a list of modules into a named, self-contained recipe.\n *\n * The recipe runs each child module in order, short-circuits on the first\n * failure, and propagates `meta` env values from one module to all subsequent\n * ones. If any child reports `\"changed\"`, the optional `signals` are triggered\n * at the end of the run.\n *\n * @param name - Display name shown in the run output header.\n * @param modules - Ordered list of modules to execute.\n * @param options - Optional recipe configuration.\n * @param options.signals - Modules to fire when at least one child changed state.\n * @returns A RecipeModule that groups the child modules.\n *\n * @example\n * export const nginxRecipe = recipe(\"nginx\", [\n * apt.installed(\"nginx\"),\n * file.template(\"/etc/nginx/nginx.conf\", \"./files/nginx.conf.tmpl\"),\n * service.enabled(\"nginx\"),\n * ], {\n * signals: [service.reload(\"nginx\")],\n * });\n */\nexport function recipe(\n name: string,\n modules: Module[],\n options?: { signals?: Module[] }\n): RecipeModule {\n return {\n _isRecipe: true,\n _modules: modules,\n _signals: options?.signals,\n async apply(\n ssh: null | SshConnection,\n environment: Environment,\n parameters?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ): Promise<ModuleResult> {\n return applyRecipe({\n environment,\n modules,\n name,\n options: parameters,\n signals: options?.signals,\n ssh,\n })\n },\n\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n // Each child receives the original environment — no meta propagation,\n // because check() never calls apply() and therefore produces no meta.\n for (const childModule of modules) {\n const connection = childModule.local === true ? null : ssh\n let result: \"needs-apply\" | \"ok\"\n try {\n // eslint-disable-next-line no-await-in-loop\n result = await childModule.check(connection, environment)\n } catch (error) {\n throw annotateRecipeChildError(childModule.name, error)\n }\n if (result === NEEDS_APPLY) return NEEDS_APPLY\n }\n return \"ok\"\n },\n\n name,\n }\n}\n","import type { ServerDefinition } from \"./types.js\"\n\nimport { validateSshConfig } from \"./serverDefinitionValidation.js\"\n\n/**\n * Define a server and validate its configuration at construction time.\n *\n * This is a thin identity function whose only purpose is to provide\n * type-safe validation with descriptive error messages before the runner\n * ever attempts an SSH connection.\n *\n * @param config - The server definition to validate and return.\n * @returns The validated server definition.\n * @throws {Error} When any required field is missing or empty.\n *\n * @example\n * export default server({\n * name: \"web-01\",\n * host: \"10.0.0.1\",\n * ssh: { user: \"root\", ports: [22], privateKey: \"~/.ssh/id_ed25519\" }, // \"~\" is expanded\n * run: [apt.installed(\"nginx\")],\n * });\n */\nexport function server(config: ServerDefinition): ServerDefinition {\n if (config.host.length === 0) {\n throw new Error(\"ServerDefinition: host is required\")\n }\n if (config.name.length === 0) {\n throw new Error(\"ServerDefinition: name is required\")\n }\n validateSshConfig(config.ssh)\n if (config.run.length === 0) {\n throw new Error(\"ServerDefinition: run must contain at least one module\")\n }\n\n return config\n}\n"],"mappings":"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","names":["ssh","ssh","signals"]}
1
+ {"version":3,"sources":["../src/conditionalModules.ts","../src/builtins.ts","../src/recipe.ts","../src/server.ts"],"sourcesContent":["import { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { detectPackageManager, isPackageInstalled } from \"./modules/package.js\"\nimport { shellQuote } from \"./ssh.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"./types.js\"\n\ntype ConditionalApplyState = {\n environment: Environment\n flushSignals?: true\n meta: ModuleMetaEntry[]\n status: \"changed\" | \"ok\" | \"skipped\"\n stopRun?: true\n}\n\nfunction createConditionalApplyState(environment: Environment): ConditionalApplyState {\n return { environment: { ...environment }, meta: [], status: \"ok\" }\n}\n\nfunction markConditionalApplyChanged(state: ConditionalApplyState): ConditionalApplyState {\n return { ...state, status: \"changed\" }\n}\n\nasync function executeConditionalApply(parameters: {\n dryRun: boolean\n environment: Environment\n module: Module\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun, environment, module, ssh } = parameters\n if (dryRun && module._applyDryRun != null) {\n return module._applyDryRun(ssh, environment)\n }\n return module.apply(ssh, environment)\n}\n\nfunction shouldExecuteConditionalApply(module: Module, dryRun: boolean): boolean {\n if (!dryRun) return true\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function mergeConditionalApplyState(\n state: ConditionalApplyState,\n result: ModuleResult\n): Promise<ConditionalApplyState> {\n const environment = await mergeEnvironmentFromMeta(state.environment, result.meta)\n return {\n environment,\n flushSignals: result._flushSignals === true ? true : state.flushSignals,\n meta: result.meta == null ? state.meta : [...state.meta, ...result.meta],\n status: result.status === \"changed\" ? \"changed\" : state.status,\n stopRun: result._stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function applyConditionalModules(parameters: {\n dryRun?: boolean\n environment: Environment\n modules: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun = false, modules, ssh } = parameters\n let state = createConditionalApplyState(parameters.environment)\n\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const checkResult = await currentModule.check(ssh, state.environment)\n if (checkResult === \"ok\") continue\n\n if (!shouldExecuteConditionalApply(currentModule, dryRun)) {\n state = markConditionalApplyChanged(state)\n continue\n }\n\n // eslint-disable-next-line no-await-in-loop -- conditional modules must preserve ordered env propagation\n const result = await executeConditionalApply({\n dryRun,\n environment: state.environment,\n module: currentModule,\n ssh,\n })\n if (result.status === \"failed\") return result\n // eslint-disable-next-line no-await-in-loop -- downstream env must see each module's meta in order\n state = await mergeConditionalApplyState(state, result)\n if (state.stopRun === true) break\n }\n\n return {\n _flushSignals: state.flushSignals,\n _stopRun: state.stopRun,\n meta: state.meta.length === 0 ? undefined : state.meta,\n status: state.status,\n }\n}\n\nfunction shouldExecuteConditionalDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nfunction whenNeedsDryRunApply(modules: Module[]): boolean {\n return modules.some((module) => shouldExecuteConditionalDryRun(module))\n}\n\nasync function checkConditionalModules(\n modules: Module[],\n ssh: null | SshConnection,\n environment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n const currentEnvironment = { ...environment }\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await currentModule.check(ssh, currentEnvironment)\n if (result === NEEDS_APPLY) {\n return NEEDS_APPLY\n }\n }\n return \"ok\"\n}\n\ntype Condition = (ssh: null | SshConnection, environment: Environment) => boolean | Promise<boolean>\n\nfunction createWhenDryRunApply(\n condition: Condition,\n modules: Module[],\n needsDryRunApply: boolean\n): ((ssh: null | SshConnection, environment: Environment) => Promise<ModuleResult>) | undefined {\n if (!needsDryRunApply) return undefined\n return async (ssh: null | SshConnection, environment: Environment) => {\n if (!(await condition(ssh, environment))) {\n return { status: \"skipped\" as const }\n }\n return applyConditionalModules({ dryRun: true, environment, modules, ssh })\n }\n}\n\nexport function createConditionalModule(parameters: {\n condition: Condition\n modules: Module[]\n name: string\n}): Module {\n const needsDryRunApply = whenNeedsDryRunApply(parameters.modules)\n const applyDryRun = createWhenDryRunApply(\n parameters.condition,\n parameters.modules,\n needsDryRunApply\n )\n\n return {\n ...(parameters.modules.some((module) => module._dryRunBlocker === true)\n ? { _dryRunBlocker: true as const }\n : {}),\n ...(parameters.modules.some((module) => module._dryRunMetaProducer === true)\n ? { _dryRunMetaProducer: true as const }\n : {}),\n ...(applyDryRun == null ? {} : { _applyDryRun: applyDryRun }),\n async apply(ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!(await parameters.condition(ssh, environment))) {\n return { status: \"skipped\" }\n }\n return applyConditionalModules({ environment, modules: parameters.modules, ssh })\n },\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n if (!(await parameters.condition(ssh, environment))) {\n return \"ok\"\n }\n return checkConditionalModules(parameters.modules, ssh, environment)\n },\n name: parameters.name,\n }\n}\n\nfunction filesystemTypeName(testFlag: \"-d\" | \"-f\" | \"-L\" | \"-S\"): string {\n switch (testFlag) {\n case \"-d\": {\n return \"path\"\n }\n case \"-f\": {\n return \"file\"\n }\n case \"-L\": {\n return \"symlink\"\n }\n case \"-S\": {\n return \"socket\"\n }\n }\n}\n\nexport function createFilesystemGuard(parameters: {\n invert: boolean\n modules: Module[]\n path: string\n testFlag: \"-d\" | \"-f\" | \"-L\" | \"-S\"\n}): Module {\n const typeName = filesystemTypeName(parameters.testFlag)\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const exists = await ssh.test(`test ${parameters.testFlag} ${shellQuote(parameters.path)}`)\n return parameters.invert ? !exists : exists\n },\n modules: parameters.modules,\n name: `when.${typeName}${parameters.invert ? \"Missing\" : \"Exists\"}: ${parameters.path}`,\n })\n}\n\nexport function createCommandGuard(\n commandName: string,\n invert: boolean,\n modules: Module[]\n): Module {\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const exists = await ssh.test(`command -v ${shellQuote(commandName)} >/dev/null 2>&1`)\n return invert ? !exists : exists\n },\n modules,\n name: `when.command${invert ? \"Missing\" : \"Exists\"}: ${commandName}`,\n })\n}\n\nexport function createPackageGuard(\n packageName: string,\n invert: boolean,\n modules: Module[]\n): Module {\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const pm = await detectPackageManager(ssh)\n if (pm == null) return false\n const installed = await isPackageInstalled(ssh, pm, packageName)\n return invert ? !installed : installed\n },\n modules,\n name: `when.package${invert ? \"Absent\" : \"Installed\"}: ${packageName}`,\n })\n}\n","import {\n createCommandGuard,\n createConditionalModule,\n createFilesystemGuard,\n createPackageGuard,\n} from \"./conditionalModules.js\"\nimport { failed } from \"./moduleFailure.js\"\nimport {\n type Environment,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Fail the run if a condition on the current env is not satisfied.\n *\n * The check phase evaluates the condition; if it returns `false`, apply\n * marks the module as failed, which aborts the parent recipe.\n *\n * @param condition - A predicate receiving the current env at run time.\n * @param message - Human-readable description shown in the run output.\n * @returns A Module that asserts the condition.\n *\n * @example\n * assert(env => !!env[\"APP_SECRET\"], \"APP_SECRET must be set\")\n */\nexport function assert(condition: (environment: Environment) => boolean, message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (condition(environment)) {\n return { status: \"ok\" }\n }\n return failed(`[assert] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return condition(environment) ? \"ok\" : NEEDS_APPLY\n },\n name: `assert: ${message}`,\n }\n}\n\n/**\n * Print a static debug message to the console during the apply phase.\n * Always runs (never skipped by the check phase).\n *\n * @param message - The message to print, prefixed with `[debug]`.\n * @returns A Module that prints a debug message.\n */\nexport function debug(message: string): Module {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n console.log(` [debug] ${message}`)\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `debug: ${message}`,\n }\n}\n\n/**\n * Unconditionally abort the run with a failed status and an error message.\n * Useful as a sentinel at the end of a conditional branch.\n *\n * @param message - The message to print, prefixed with `[fail]`.\n * @returns A Module that fails the run.\n */\nexport function fail(message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return failed(`[fail] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `fail: ${message}`,\n }\n}\n\n/**\n * Pause execution and wait for the operator to press Enter.\n * Useful for interactive confirmation during a run.\n *\n * @param message - Prompt shown to the operator. Defaults to `\"Press enter to continue...\"`.\n * @returns A Module that pauses execution.\n */\nexport function pause(message?: string): Module {\n return {\n async apply(): Promise<ModuleResult> {\n const promptText = message ?? \"Press enter to continue...\"\n process.stdout.write(` [pause] ${promptText} `)\n\n await new Promise<void>((resolve) => {\n process.stdin.once(\"data\", () => {\n process.stdin.pause()\n resolve()\n })\n })\n\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: message == null ? \"pause\" : `pause: ${message}`,\n }\n}\n\nfunction isFirstRunEnabled(environment: Environment): boolean {\n return environment.PARATIX_FIRST_RUN === \"true\" || environment.FIRST_RUN === true\n}\n\n/**\n * Built-ins related to the explicit first-run bootstrap stage.\n */\nexport const firstRun = {\n /**\n * Stop the current run successfully when Paratix was invoked with `--first-run`.\n * Useful as an explicit stage boundary in scaffolded playbooks.\n *\n * @param message - Optional note shown in the module name.\n * @returns A local module that stops the run only during first-run execution.\n */\n stop(message?: string): Module {\n const moduleName = message == null ? \"firstRun.stop\" : `firstRun.stop: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!isFirstRunEnabled(environment)) {\n return { status: \"ok\" }\n }\n\n return {\n _dryRunDetail: \"(first-run stop)\",\n _stopRun: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return isFirstRunEnabled(environment) ? NEEDS_APPLY : \"ok\"\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\n/**\n * Built-ins for explicit signal checkpoints.\n */\nexport const signals = {\n /**\n * Flush all currently pending signals for the active scope.\n * Signals remain scope-local:\n * - in a recipe, this flushes that recipe's signals\n * - at top level, this flushes `server(...).signals`\n *\n * @param message - Optional note shown in the module name.\n * @returns A local control module that requests an immediate signal flush.\n */\n flush(message?: string): Module {\n const moduleName = message == null ? \"signals.flush\" : `signals.flush: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return {\n _dryRunDetail: \"(dry-run, pending signals not executed)\",\n _flushSignals: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\n/**\n * Run one or more modules only when a runtime condition is met.\n * When the condition is `false`, the whole group is skipped without running\n * any child checks or applies.\n *\n * @param condition - A predicate evaluated against the current env at run time.\n * @param modules - One or more modules to run when the condition is `true`.\n * @returns A Module that conditionally runs the inner modules.\n *\n * @example\n * when(env => env[\"DEPLOY_ENV\"] === \"production\", service.enabled(\"fail2ban\"))\n */\nfunction baseWhen(condition: (environment: Environment) => boolean, ...modules: Module[]): Module {\n return createConditionalModule({\n condition: (_ssh, environment) => condition(environment),\n modules,\n name: `when: conditional (${modules.length} modules)`,\n })\n}\n\ntype WhenFunction = {\n commandExists: (commandName: string, ...modules: Module[]) => Module\n commandMissing: (commandName: string, ...modules: Module[]) => Module\n fileExists: (path: string, ...modules: Module[]) => Module\n fileMissing: (path: string, ...modules: Module[]) => Module\n packageAbsent: (packageName: string, ...modules: Module[]) => Module\n packageInstalled: (packageName: string, ...modules: Module[]) => Module\n pathExists: (path: string, ...modules: Module[]) => Module\n pathMissing: (path: string, ...modules: Module[]) => Module\n socketExists: (path: string, ...modules: Module[]) => Module\n socketMissing: (path: string, ...modules: Module[]) => Module\n symlinkExists: (path: string, ...modules: Module[]) => Module\n symlinkMissing: (path: string, ...modules: Module[]) => Module\n} & typeof baseWhen\n\nexport const when: WhenFunction = Object.assign(baseWhen, {\n commandExists: (commandName: string, ...modules: Module[]) =>\n createCommandGuard(commandName, false, modules),\n commandMissing: (commandName: string, ...modules: Module[]) =>\n createCommandGuard(commandName, true, modules),\n fileExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-f\" }),\n fileMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-f\" }),\n packageAbsent: (packageName: string, ...modules: Module[]) =>\n createPackageGuard(packageName, true, modules),\n packageInstalled: (packageName: string, ...modules: Module[]) =>\n createPackageGuard(packageName, false, modules),\n pathExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-d\" }),\n pathMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-d\" }),\n socketExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-S\" }),\n socketMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-S\" }),\n symlinkExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-L\" }),\n symlinkMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-L\" }),\n})\n","/* eslint-disable max-lines -- recipe orchestration intentionally stays co-located */\nimport { isEnvironmentMetaEntry, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRecipeModuleResult,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\nimport { runSignalModules, type SignalHooks } from \"./signalOrchestration.js\"\nimport { CommandError } from \"./sshHelpers.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n type ModuleStatus,\n NEEDS_APPLY,\n type OrchestrationStep,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Internal representation of a recipe module.\n * The `_isRecipe` flag lets the runner distinguish recipes from leaf modules.\n * @internal\n */\nexport type RecipeModule = {\n _isRecipe: true\n _modules: Module[]\n _signals?: Module[]\n apply: (\n ssh: null | SshConnection,\n environment: Environment,\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ) => Promise<ModuleResult>\n} & Module\n\ntype RecipeState = {\n env: Environment\n meta?: ModuleMetaEntry[]\n signalsPending: boolean\n status: Exclude<ModuleStatus, \"skipped\">\n stopRun?: true\n}\n\ntype ExecuteModulesParameters = {\n environment: Environment\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n verbose?: boolean\n}\n\ntype RecipeLoopStepResult =\n | { kind: \"break\"; state: RecipeState }\n | { kind: \"continue\"; state: RecipeState }\n\nconst INTERRUPTED_BEFORE_APPLY = Symbol(\"recipe-interrupted-before-apply\")\n\nfunction isRecipeModuleLike(module: Module): boolean {\n return (module as { _isRecipe?: boolean } & Module)._isRecipe === true\n}\n\nfunction printRecipeChildResult(module: Module, result: ModuleResult): void {\n if (isRecipeModuleLike(module)) {\n printRecipeModuleResult(module.name, result.status)\n return\n }\n\n printModuleResult(module.name, result.status)\n}\n\nfunction applyRecipeStepToState(\n state: RecipeState,\n step: OrchestrationStep,\n preserveControlPlaneMeta: boolean\n): RecipeState {\n let stepMeta: ModuleMetaEntry[] | undefined\n if (step.meta == null) {\n stepMeta = undefined\n } else if (preserveControlPlaneMeta) {\n stepMeta = step.meta\n } else {\n stepMeta = step.meta.filter(isEnvironmentMetaEntry)\n }\n const nextMeta = stepMeta == null ? (state.meta ?? []) : [...(state.meta ?? []), ...stepMeta]\n let nextStatus = state.status\n if (step.status === \"failed\") nextStatus = \"failed\"\n else if (step.status === \"changed\") nextStatus = \"changed\"\n\n return {\n env: step.env,\n meta: nextMeta,\n signalsPending: step.status === \"changed\" ? true : state.signalsPending,\n status: nextStatus,\n stopRun: step._stopRun === true ? true : state.stopRun,\n }\n}\n\n/**\n * Recipes run their own check→apply loop, separate from the runner's\n * `runModuleLoop` in runner.ts. This is intentional: recipes execute as a\n * single nested module inside the runner, so SSH-lifecycle concerns\n * (port-change reconnects, reboot handling), shutdown-signal guards,\n * dry-run mode and stats tracking are the runner's responsibility and\n * must not be duplicated here.\n *\n * @param parameters - Parameters for executing one child module.\n * @param parameters.targetModule - The module to check and conditionally apply.\n * @param parameters.ssh - Active SSH connection, or `null` for local modules.\n * @param parameters.currentEnvironment - Environment values available to the module.\n * @param parameters.shutdownSignal - Optional shutdown getter used to suppress new apply steps.\n * @param parameters.verbose - Whether verbose command diagnostics should be printed.\n * @returns The updated environment and status, or `null` if the module was already ok.\n */\nasync function executeOneModule(parameters: {\n currentEnvironment: Environment\n shutdownSignal?: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose?: boolean\n}): Promise<null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY> {\n const { currentEnvironment, ssh, targetModule } = parameters\n const verbose = parameters.verbose ?? false\n const connection = targetModule.local === true ? null : ssh\n const checkResult = await checkRecipeChild(targetModule, connection, currentEnvironment)\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return null\n }\n\n if ((parameters.shutdownSignal?.() ?? null) != null) {\n return INTERRUPTED_BEFORE_APPLY\n }\n\n const result = await targetModule.apply(connection, currentEnvironment)\n printRecipeChildResult(targetModule, result)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n\n const environment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n return {\n _flushSignals: result._flushSignals,\n _stopRun: result._stopRun,\n env: environment,\n meta: result.meta,\n status: result.status,\n }\n}\n\nasync function checkRecipeChild(\n targetModule: Module,\n connection: null | SshConnection,\n currentEnvironment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, currentEnvironment)\n}\n\nasync function applyExecutedRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n state: RecipeState\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n}): Promise<null | RecipeState> {\n if (parameters.step == null) return parameters.state\n if (parameters.step === INTERRUPTED_BEFORE_APPLY) return null\n\n if (parameters.onChildStep != null) {\n await parameters.onChildStep(parameters.step)\n }\n\n return applyRecipeStepToState(\n parameters.state,\n parameters.step,\n parameters.preserveControlPlaneMeta\n )\n}\n\nfunction failedRecipeState(environment: Environment): RecipeState {\n return {\n env: environment,\n meta: undefined,\n signalsPending: false,\n status: \"failed\",\n }\n}\n\nfunction annotateRecipeChildError(moduleName: string, error: unknown): Error {\n const prefix = `[${moduleName}] `\n if (error instanceof CommandError) {\n return new CommandError(`${prefix}${error.message}`, error.fullStdout, error.fullStderr)\n }\n if (error instanceof Error) {\n return new Error(`${prefix}${error.message}`)\n }\n return new Error(`${prefix}${String(error)}`)\n}\n\ntype RecipeChildExecution =\n | { kind: \"failed\"; state: RecipeState }\n | {\n kind: \"step\"\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n }\n\nasync function executeRecipeChildStep(parameters: {\n currentEnvironment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose: boolean\n}): Promise<RecipeChildExecution> {\n try {\n return { kind: \"step\", step: await executeOneModule(parameters) }\n } catch (error) {\n if (parameters.shutdownSignal() != null) {\n return { kind: \"step\", step: INTERRUPTED_BEFORE_APPLY }\n }\n printModuleResult(parameters.targetModule.name, \"failed\")\n printCommandFailure(error, parameters.verbose)\n return { kind: \"failed\", state: failedRecipeState(parameters.currentEnvironment) }\n }\n}\n\nasync function processRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n ssh: null | SshConnection\n state: RecipeState\n step: RecipeChildExecution\n verbose: boolean\n}): Promise<RecipeLoopStepResult> {\n if (parameters.step.kind === \"failed\") {\n return { kind: \"break\", state: parameters.step.state }\n }\n\n const nextState = await applyExecutedRecipeStep({\n onChildStep: parameters.onChildStep,\n preserveControlPlaneMeta: parameters.preserveControlPlaneMeta,\n state: parameters.state,\n step: parameters.step.step,\n })\n if (nextState == null) {\n return { kind: \"break\", state: parameters.state }\n }\n\n let state = nextState\n if (shouldFlushRecipeSignals(parameters.step.step, state, parameters.signals)) {\n const signalStatus = await flushPendingRecipeSignals({\n environment: state.env,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n state = applyRecipeSignalStatus(state, signalStatus)\n }\n\n if (state.status === \"failed\" || state.stopRun === true) {\n return { kind: \"break\", state }\n }\n\n return { kind: \"continue\", state }\n}\n\nasync function executeModules(\n modules: Module[],\n ssh: null | SshConnection,\n parameters: ExecuteModulesParameters\n): Promise<RecipeState> {\n const onChildStep = parameters.onChildStep\n const preserveControlPlaneMeta = onChildStep == null\n const shutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let state: RecipeState = {\n env: { ...parameters.environment },\n meta: undefined,\n signalsPending: false,\n status: \"ok\",\n }\n\n for (const currentModule of modules) {\n if (shutdownSignal() != null) break\n // eslint-disable-next-line no-await-in-loop\n const step = await executeRecipeChildStep({\n currentEnvironment: state.env,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n // eslint-disable-next-line no-await-in-loop\n const processedStep = await processRecipeStep({\n onChildStep,\n onSignalStep: parameters.onSignalStep,\n preserveControlPlaneMeta,\n shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh,\n state,\n step,\n verbose,\n })\n state = processedStep.state\n if (processedStep.kind === \"break\") return state\n }\n\n return state\n}\n\nasync function triggerSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return runSignalModules({\n environment: parameters.environment,\n hooks: parameters.signalHooks,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldFlushRecipeSignals(\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY,\n state: RecipeState,\n signals?: Module[]\n): signals is Module[] {\n return (\n step != null &&\n step !== INTERRUPTED_BEFORE_APPLY &&\n step._flushSignals === true &&\n state.signalsPending &&\n signals != null\n )\n}\n\nfunction applyRecipeSignalStatus(\n state: RecipeState,\n signalStatus: \"changed\" | \"failed\"\n): RecipeState {\n return {\n ...state,\n signalsPending: false,\n status: signalStatus === \"failed\" ? \"failed\" : state.status,\n }\n}\n\nasync function flushPendingRecipeSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return triggerSignals(parameters)\n}\n\nasync function applyRecipe(parameters: {\n environment: Environment\n modules: Module[]\n name: string\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n signals?: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n return withRecipeOutputScope(async () => {\n const shutdownSignal = parameters.options?.shutdownSignal\n const verbose = parameters.options?.verbose ?? false\n printRecipeHeader(parameters.name)\n const state = await executeModules(parameters.modules, parameters.ssh, {\n environment: parameters.environment,\n onChildStep: parameters.options?.onChildStep,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n verbose,\n })\n\n if (shouldRunRecipeSignalsAtEnd(state, parameters.signals)) {\n state.status = await triggerSignals({\n environment: state.env,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose,\n })\n }\n\n return {\n _stopRun: state.stopRun,\n meta: state.meta,\n status: state.status,\n }\n })\n}\n\nfunction shouldRunRecipeSignalsAtEnd(state: RecipeState, signals?: Module[]): signals is Module[] {\n return state.signalsPending && state.status === \"changed\" && signals != null\n}\n\n/**\n * Group a list of modules into a named, self-contained recipe.\n *\n * The recipe runs each child module in order, short-circuits on the first\n * failure, and propagates `meta` env values from one module to all subsequent\n * ones. If any child reports `\"changed\"`, the optional `signals` are triggered\n * at the end of the run.\n *\n * @param name - Display name shown in the run output header.\n * @param modules - Ordered list of modules to execute.\n * @param options - Optional recipe configuration.\n * @param options.signals - Modules to fire when at least one child changed state.\n * @returns A RecipeModule that groups the child modules.\n *\n * @example\n * export const nginxRecipe = recipe(\"nginx\", [\n * apt.installed(\"nginx\"),\n * file.template(\"/etc/nginx/nginx.conf\", \"./files/nginx.conf.tmpl\"),\n * service.enabled(\"nginx\"),\n * ], {\n * signals: [service.reload(\"nginx\")],\n * });\n */\nexport function recipe(\n name: string,\n modules: Module[],\n options?: { signals?: Module[] }\n): RecipeModule {\n return {\n _isRecipe: true,\n _modules: modules,\n _signals: options?.signals,\n async apply(\n ssh: null | SshConnection,\n environment: Environment,\n parameters?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ): Promise<ModuleResult> {\n return applyRecipe({\n environment,\n modules,\n name,\n options: parameters,\n signals: options?.signals,\n ssh,\n })\n },\n\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n // Each child receives the original environment — no meta propagation,\n // because check() never calls apply() and therefore produces no meta.\n for (const childModule of modules) {\n const connection = childModule.local === true ? null : ssh\n let result: \"needs-apply\" | \"ok\"\n try {\n // eslint-disable-next-line no-await-in-loop\n result = await childModule.check(connection, environment)\n } catch (error) {\n throw annotateRecipeChildError(childModule.name, error)\n }\n if (result === NEEDS_APPLY) return NEEDS_APPLY\n }\n return \"ok\"\n },\n\n name,\n }\n}\n","import type { ServerDefinition } from \"./types.js\"\n\nimport { validateSshConfig } from \"./serverDefinitionValidation.js\"\n\n/**\n * Define a server and validate its configuration at construction time.\n *\n * This is a thin identity function whose only purpose is to provide\n * type-safe validation with descriptive error messages before the runner\n * ever attempts an SSH connection.\n *\n * @param config - The server definition to validate and return.\n * @returns The validated server definition.\n * @throws {Error} When any required field is missing or empty.\n *\n * @example\n * export default server({\n * name: \"web-01\",\n * host: \"10.0.0.1\",\n * ssh: { user: \"root\", ports: [22], privateKey: \"~/.ssh/id_ed25519\" }, // \"~\" is expanded\n * run: [apt.installed(\"nginx\")],\n * });\n */\nexport function server(config: ServerDefinition): ServerDefinition {\n if (config.host.length === 0) {\n throw new Error(\"ServerDefinition: host is required\")\n }\n if (config.name.length === 0) {\n throw new Error(\"ServerDefinition: name is required\")\n }\n validateSshConfig(config.ssh)\n if (config.run.length === 0) {\n throw new Error(\"ServerDefinition: run must contain at least one module\")\n }\n\n return config\n}\n"],"mappings":"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","names":["ssh","ssh","signals"]}