paratix 0.3.0 → 0.4.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/README.md +5 -0
- package/dist/{chunk-ULJMW23T.js → chunk-C45YPXCX.js} +35 -2
- package/dist/chunk-C45YPXCX.js.map +1 -0
- package/dist/{chunk-DUIGEB2J.js → chunk-EGP3QRLV.js} +5 -2
- package/dist/chunk-EGP3QRLV.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +16 -1
- package/dist/index.js +182 -98
- package/dist/index.js.map +1 -1
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules/index.js +1 -1
- package/llm-guide.md +39 -4
- package/package.json +1 -1
- package/dist/chunk-DUIGEB2J.js.map +0 -1
- package/dist/chunk-ULJMW23T.js.map +0 -1
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBO,SAAS,OAAO,WAAkD,SAAyB;AAChG,SAAO;AAAA,IACL,gBAAgB;AAAA;AAAA,IAEhB,MAAM,MAAM,MAA4B,aAAiD;AACvF,UAAI,UAAU,WAAW,GAAG;AAC1B,eAAO,EAAE,QAAQ,KAAK;AAAA,MACxB;AACA,aAAO,OAAO,YAAY,OAAO,EAAE;AAAA,IACrC;AAAA;AAAA,IAEA,MAAM,MACJ,MACA,aAC+B;AAC/B,aAAO,UAAU,WAAW,IAAI,OAAO;AAAA,IACzC;AAAA,IACA,MAAM,WAAW,OAAO;AAAA,EAC1B;AACF;AASO,SAAS,MAAM,SAAyB;AAC7C,SAAO;AAAA;AAAA,IAEL,MAAM,QAA+B;AACnC,cAAQ,IAAI,aAAa,OAAO,EAAE;AAClC,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA;AAAA,IAEA,MAAM,QAAuC;AAC3C,aAAO;AAAA,IACT;AAAA,IACA,MAAM,UAAU,OAAO;AAAA,EACzB;AACF;AASO,SAAS,KAAK,SAAyB;AAC5C,SAAO;AAAA,IACL,gBAAgB;AAAA;AAAA,IAEhB,MAAM,QAA+B;AACnC,aAAO,OAAO,UAAU,OAAO,EAAE;AAAA,IACnC;AAAA;AAAA,IAEA,MAAM,QAAuC;AAC3C,aAAO;AAAA,IACT;AAAA,IACA,MAAM,SAAS,OAAO;AAAA,EACxB;AACF;AASO,SAAS,MAAM,SAA0B;AAC9C,SAAO;AAAA,IACL,MAAM,QAA+B;AACnC,YAAM,aAAa,WAAW;AAC9B,cAAQ,OAAO,MAAM,aAAa,UAAU,GAAG;AAE/C,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,gBAAQ,MAAM,KAAK,QAAQ,MAAM;AAC/B,kBAAQ,MAAM,MAAM;AACpB,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAED,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA;AAAA,IAEA,MAAM,QAAuC;AAC3C,aAAO;AAAA,IACT;AAAA,IACA,MAAM,WAAW,OAAO,UAAU,UAAU,OAAO;AAAA,EACrD;AACF;AAEA,SAAS,kBAAkB,aAAmC;AAC5D,SAAO,YAAY,sBAAsB,UAAU,YAAY,cAAc;AAC/E;AAKO,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQtB,KAAK,SAA0B;AAC7B,UAAM,aAAa,WAAW,OAAO,kBAAkB,kBAAkB,OAAO;AAEhF,WAAO;AAAA,MACL,gBAAgB;AAAA;AAAA,MAEhB,MAAM,MAAM,MAA4B,aAAiD;AACvF,YAAI,CAAC,kBAAkB,WAAW,GAAG;AACnC,iBAAO,EAAE,QAAQ,KAAK;AAAA,QACxB;AAEA,eAAO;AAAA,UACL,eAAe;AAAA,UACf,UAAU;AAAA,UACV,QAAQ;AAAA,QACV;AAAA,MACF;AAAA;AAAA,MAEA,MAAM,MACJ,MACA,aAC+B;AAC/B,eAAO,kBAAkB,WAAW,IAAI,cAAc;AAAA,MACxD;AAAA,MACA,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAKO,IAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUrB,MAAM,SAA0B;AAC9B,UAAM,aAAa,WAAW,OAAO,kBAAkB,kBAAkB,OAAO;AAEhF,WAAO;AAAA,MACL,gBAAgB;AAAA;AAAA,MAEhB,MAAM,QAA+B;AACnC,eAAO;AAAA,UACL,eAAe;AAAA,UACf,eAAe;AAAA,UACf,QAAQ;AAAA,QACV;AAAA,MACF;AAAA;AAAA,MAEA,MAAM,QAAuC;AAC3C,eAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,eAAe,wBAAwB,YAKb;AACxB,QAAM,EAAE,SAAS,OAAO,SAAS,KAAAA,KAAI,IAAI;AACzC,MAAI,QAAQ,4BAA4B,WAAW,WAAW;AAE9D,aAAW,iBAAiB,SAAS;AAEnC,UAAM,cAAc,MAAM,cAAc,MAAMA,MAAK,MAAM,WAAW;AACpE,QAAI,gBAAgB,KAAM;AAE1B,QAAI,CAAC,8BAA8B,eAAe,MAAM,GAAG;AACzD,cAAQ,4BAA4B,KAAK;AACzC;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,wBAAwB;AAAA,MAC3C;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,QAAQ;AAAA,MACR,KAAAA;AAAA,IACF,CAAC;AACD,QAAI,OAAO,WAAW,SAAU,QAAO;AAEvC,YAAQ,MAAM,2BAA2B,OAAO,MAAM;AACtD,QAAI,MAAM,YAAY,KAAM;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM,KAAK,WAAW,IAAI,SAAY,MAAM;AAAA,IAClD,QAAQ,MAAM;AAAA,EAChB;AACF;AAEA,SAAS,8BAA8B,QAAgB,QAA0B;AAC/E,MAAI,CAAC,OAAQ,QAAO;AACpB,SACE,OAAO,gBAAgB,QACvB,OAAO,mBAAmB,QAC1B,OAAO,wBAAwB;AAEnC;AAUA,SAAS,4BAA4B,aAAiD;AACpF,SAAO,EAAE,aAAa,EAAE,GAAG,YAAY,GAAG,MAAM,CAAC,GAAG,QAAQ,KAAK;AACnE;AAEA,SAAS,4BAA4B,OAAqD;AACxF,SAAO,EAAE,GAAG,OAAO,QAAQ,UAAU;AACvC;AAEA,eAAe,wBAAwB,YAKb;AACxB,QAAM,EAAE,QAAQ,aAAa,QAAQ,KAAAA,KAAI,IAAI;AAC7C,MAAI,UAAU,OAAO,gBAAgB,MAAM;AACzC,WAAO,OAAO,aAAaA,MAAK,WAAW;AAAA,EAC7C;AACA,SAAO,OAAO,MAAMA,MAAK,WAAW;AACtC;AAEA,SAAS,qBAAqB,SAA4B;AACxD,SAAO,QAAQ,KAAK,CAAC,WAAW,+BAA+B,MAAM,CAAC;AACxE;AAEA,SAAS,+BAA+B,QAAyB;AAC/D,SACE,OAAO,gBAAgB,QACvB,OAAO,mBAAmB,QAC1B,OAAO,wBAAwB;AAEnC;AAEA,eAAe,2BACb,OACA,QACgC;AAChC,QAAM,cAAc,MAAM,yBAAyB,MAAM,aAAa,OAAO,IAAI;AACjF,SAAO;AAAA,IACL;AAAA,IACA,cAAc,OAAO,kBAAkB,OAAO,OAAO,MAAM;AAAA,IAC3D,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG,OAAO,IAAI;AAAA,IACvE,QAAQ,OAAO,WAAW,YAAY,YAAY,MAAM;AAAA,IACxD,SAAS,OAAO,aAAa,OAAO,OAAO,MAAM;AAAA,EACnD;AACF;AAEA,SAAS,sBACP,WACA,SACA,kBAC8F;AAC9F,MAAI,CAAC,iBAAkB,QAAO;AAC9B,SAAO,OAAOA,MAA2B,gBAA6B;AACpE,QAAI,CAAC,UAAU,WAAW,GAAG;AAC3B,aAAO,EAAE,QAAQ,UAAmB;AAAA,IACtC;AACA,WAAO,wBAAwB,EAAE,QAAQ,MAAM,aAAa,SAAS,KAAAA,KAAI,CAAC;AAAA,EAC5E;AACF;AAcO,SAAS,KACd,cACG,SACK;AACR,QAAM,mBAAmB,qBAAqB,OAAO;AACrD,QAAM,cAAc,sBAAsB,WAAW,SAAS,gBAAgB;AAC9E,SAAO;AAAA,IACL,GAAI,QAAQ,KAAK,CAAC,WAAW,OAAO,mBAAmB,IAAI,IACvD,EAAE,gBAAgB,KAAc,IAChC,CAAC;AAAA,IACL,GAAI,QAAQ,KAAK,CAAC,WAAW,OAAO,wBAAwB,IAAI,IAC5D,EAAE,qBAAqB,KAAc,IACrC,CAAC;AAAA,IACL,GAAI,eAAe,OAAO,CAAC,IAAI,EAAE,cAAc,YAAY;AAAA,IAC3D,MAAM,MAAMA,MAA2B,aAAiD;AACtF,UAAI,CAAC,UAAU,WAAW,GAAG;AAC3B,eAAO,EAAE,QAAQ,UAAU;AAAA,MAC7B;AACA,aAAO,wBAAwB,EAAE,aAAa,SAAS,KAAAA,KAAI,CAAC;AAAA,IAC9D;AAAA,IACA,MAAM,MACJA,MACA,aAC+B;AAC/B,UAAI,CAAC,UAAU,WAAW,GAAG;AAC3B,eAAO;AAAA,MACT;AAGA,YAAM,qBAAqB,EAAE,GAAG,YAAY;AAC5C,iBAAW,iBAAiB,SAAS;AAEnC,cAAM,SAAS,MAAM,cAAc,MAAMA,MAAK,kBAAkB;AAChE,YAAI,WAAW,aAAa;AAC1B,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,sBAAsB,QAAQ,MAAM;AAAA,EAC5C;AACF;;;ACjTA,IAAM,2BAA2B,uBAAO,iCAAiC;AAEzE,SAAS,uBACP,OACA,MACA,0BACa;AACb,MAAI;AACJ,MAAI,KAAK,QAAQ,MAAM;AACrB,eAAW;AAAA,EACb,WAAW,0BAA0B;AACnC,eAAW,KAAK;AAAA,EAClB,OAAO;AACL,eAAW,KAAK,KAAK,OAAO,sBAAsB;AAAA,EACpD;AACA,QAAM,WAAW,YAAY,OAAQ,MAAM,QAAQ,CAAC,IAAK,CAAC,GAAI,MAAM,QAAQ,CAAC,GAAI,GAAG,QAAQ;AAC5F,MAAI,aAAa,MAAM;AACvB,MAAI,KAAK,WAAW,SAAU,cAAa;AAAA,WAClC,KAAK,WAAW,UAAW,cAAa;AAEjD,SAAO;AAAA,IACL,KAAK,KAAK;AAAA,IACV,MAAM;AAAA,IACN,gBAAgB,KAAK,WAAW,YAAY,OAAO,MAAM;AAAA,IACzD,QAAQ;AAAA,IACR,SAAS,KAAK,aAAa,OAAO,OAAO,MAAM;AAAA,EACjD;AACF;AAkBA,eAAe,iBAAiB,YAMwC;AACtE,QAAM,EAAE,oBAAoB,KAAAC,MAAK,aAAa,IAAI;AAClD,QAAM,UAAU,WAAW,WAAW;AACtC,QAAM,aAAa,aAAa,UAAU,OAAO,OAAOA;AACxD,QAAM,cAAc,MAAM,iBAAiB,cAAc,YAAY,kBAAkB;AAEvF,MAAI,gBAAgB,MAAM;AACxB,sBAAkB,aAAa,MAAM,IAAI;AACzC,WAAO;AAAA,EACT;AAEA,OAAK,WAAW,iBAAiB,KAAK,SAAS,MAAM;AACnD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM,aAAa,MAAM,YAAY,kBAAkB;AACtE,oBAAkB,aAAa,MAAM,OAAO,MAAM;AAClD,MAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM;AACtD,wBAAoB,OAAO,OAAO,OAAO;AAAA,EAC3C;AAEA,QAAM,cAAc,MAAM,yBAAyB,oBAAoB,OAAO,IAAI;AAClF,SAAO;AAAA,IACL,eAAe,OAAO;AAAA,IACtB,UAAU,OAAO;AAAA,IACjB,KAAK;AAAA,IACL,MAAM,OAAO;AAAA,IACb,QAAQ,OAAO;AAAA,EACjB;AACF;AAEA,eAAe,iBACb,cACA,YACA,oBAC+B;AAC/B,qBAAmB,aAAa,IAAI;AACpC,SAAO,aAAa,MAAM,YAAY,kBAAkB;AAC1D;AAEA,eAAe,wBAAwB,YAKP;AAC9B,MAAI,WAAW,QAAQ,KAAM,QAAO,WAAW;AAC/C,MAAI,WAAW,SAAS,yBAA0B,QAAO;AAEzD,MAAI,WAAW,eAAe,MAAM;AAClC,UAAM,WAAW,YAAY,WAAW,IAAI;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACF;AAEA,SAAS,kBAAkB,aAAuC;AAChE,SAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EACV;AACF;AAEA,SAAS,yBAAyB,YAAoB,OAAuB;AAC3E,QAAM,SAAS,IAAI,UAAU;AAC7B,MAAI,iBAAiB,cAAc;AACjC,WAAO,IAAI,aAAa,GAAG,MAAM,GAAG,MAAM,OAAO,IAAI,MAAM,YAAY,MAAM,UAAU;AAAA,EACzF;AACA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,OAAO,EAAE;AAAA,EAC9C;AACA,SAAO,IAAI,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,CAAC,EAAE;AAC9C;AASA,eAAe,uBAAuB,YAMJ;AAChC,MAAI;AACF,WAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,iBAAiB,UAAU,EAAE;AAAA,EAClE,SAAS,OAAO;AACd,QAAI,WAAW,eAAe,KAAK,MAAM;AACvC,aAAO,EAAE,MAAM,QAAQ,MAAM,yBAAyB;AAAA,IACxD;AACA,sBAAkB,WAAW,aAAa,MAAM,QAAQ;AACxD,wBAAoB,OAAO,WAAW,OAAO;AAC7C,WAAO,EAAE,MAAM,UAAU,OAAO,kBAAkB,WAAW,kBAAkB,EAAE;AAAA,EACnF;AACF;AAEA,eAAe,kBAAkB,YAWC;AAChC,MAAI,WAAW,KAAK,SAAS,UAAU;AACrC,WAAO,EAAE,MAAM,SAAS,OAAO,WAAW,KAAK,MAAM;AAAA,EACvD;AAEA,QAAM,YAAY,MAAM,wBAAwB;AAAA,IAC9C,aAAa,WAAW;AAAA,IACxB,0BAA0B,WAAW;AAAA,IACrC,OAAO,WAAW;AAAA,IAClB,MAAM,WAAW,KAAK;AAAA,EACxB,CAAC;AACD,MAAI,aAAa,MAAM;AACrB,WAAO,EAAE,MAAM,SAAS,OAAO,WAAW,MAAM;AAAA,EAClD;AAEA,MAAI,QAAQ;AACZ,MAAI,yBAAyB,WAAW,KAAK,MAAM,OAAO,WAAW,OAAO,GAAG;AAC7E,UAAM,eAAe,MAAM,0BAA0B;AAAA,MACnD,aAAa,MAAM;AAAA,MACnB,cAAc,WAAW;AAAA,MACzB,gBAAgB,WAAW;AAAA,MAC3B,aAAa,WAAW;AAAA,MACxB,SAAS,WAAW;AAAA,MACpB,KAAK,WAAW;AAAA,MAChB,SAAS,WAAW;AAAA,IACtB,CAAC;AACD,YAAQ,wBAAwB,OAAO,YAAY;AAAA,EACrD;AAEA,MAAI,MAAM,WAAW,YAAY,MAAM,YAAY,MAAM;AACvD,WAAO,EAAE,MAAM,SAAS,MAAM;AAAA,EAChC;AAEA,SAAO,EAAE,MAAM,YAAY,MAAM;AACnC;AAEA,eAAe,eACb,SACAA,MACA,YACsB;AACtB,QAAM,cAAc,WAAW;AAC/B,QAAM,2BAA2B,eAAe;AAChD,QAAM,iBAAiB,WAAW,mBAAmB,MAAM;AAC3D,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,QAAqB;AAAA,IACvB,KAAK,EAAE,GAAG,WAAW,YAAY;AAAA,IACjC,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EACV;AAEA,aAAW,iBAAiB,SAAS;AACnC,QAAI,eAAe,KAAK,KAAM;AAE9B,UAAM,OAAO,MAAM,uBAAuB;AAAA,MACxC,oBAAoB,MAAM;AAAA,MAC1B;AAAA,MACA,KAAAA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MAAM,kBAAkB;AAAA,MAC5C;AAAA,MACA,cAAc,WAAW;AAAA,MACzB;AAAA,MACA;AAAA,MACA,aAAa,WAAW;AAAA,MACxB,SAAS,WAAW;AAAA,MACpB,KAAAA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,YAAQ,cAAc;AACtB,QAAI,cAAc,SAAS,QAAS,QAAO;AAAA,EAC7C;AAEA,SAAO;AACT;AAEA,eAAe,eAAe,YAQI;AAChC,SAAO,iBAAiB;AAAA,IACtB,aAAa,WAAW;AAAA,IACxB,OAAO,WAAW;AAAA,IAClB,cAAc,WAAW;AAAA,IACzB,gBAAgB,WAAW;AAAA,IAC3B,SAAS,WAAW;AAAA,IACpB,KAAK,WAAW;AAAA,IAChB,SAAS,WAAW;AAAA,EACtB,CAAC;AACH;AAEA,SAAS,yBACP,MACA,OACAC,UACqB;AACrB,SACE,QAAQ,QACR,SAAS,4BACT,KAAK,kBAAkB,QACvB,MAAM,kBACNA,YAAW;AAEf;AAEA,SAAS,wBACP,OACA,cACa;AACb,SAAO;AAAA,IACL,GAAG;AAAA,IACH,gBAAgB;AAAA,IAChB,QAAQ,iBAAiB,WAAW,WAAW,MAAM;AAAA,EACvD;AACF;AAEA,eAAe,0BAA0B,YAQP;AAChC,SAAO,eAAe,UAAU;AAClC;AAEA,eAAe,YAAY,YAaD;AACxB,SAAO,sBAAsB,YAAY;AACvC,UAAM,iBAAiB,WAAW,SAAS;AAC3C,UAAM,UAAU,WAAW,SAAS,WAAW;AAC/C,sBAAkB,WAAW,IAAI;AACjC,UAAM,QAAQ,MAAM,eAAe,WAAW,SAAS,WAAW,KAAK;AAAA,MACrE,aAAa,WAAW;AAAA,MACxB,aAAa,WAAW,SAAS;AAAA,MACjC,cAAc,WAAW,SAAS;AAAA,MAClC;AAAA,MACA,aAAa,WAAW,SAAS;AAAA,MACjC,SAAS,WAAW;AAAA,MACpB;AAAA,IACF,CAAC;AAED,QAAI,4BAA4B,OAAO,WAAW,OAAO,GAAG;AAC1D,YAAM,SAAS,MAAM,eAAe;AAAA,QAClC,aAAa,MAAM;AAAA,QACnB,cAAc,WAAW,SAAS;AAAA,QAClC;AAAA,QACA,aAAa,WAAW,SAAS;AAAA,QACjC,SAAS,WAAW;AAAA,QACpB,KAAK,WAAW;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,UAAU,MAAM;AAAA,MAChB,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,IAChB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,4BAA4B,OAAoBA,UAAyC;AAChG,SAAO,MAAM,kBAAkB,MAAM,WAAW,aAAaA,YAAW;AAC1E;AAyBO,SAAS,OACd,MACA,SACA,SACc;AACd,SAAO;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,UAAU,SAAS;AAAA,IACnB,MAAM,MACJD,MACA,aACA,YAOuB;AACvB,aAAO,YAAY;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,SAAS,SAAS;AAAA,QAClB,KAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,MACJA,MACA,aAC+B;AAG/B,iBAAW,eAAe,SAAS;AACjC,cAAM,aAAa,YAAY,UAAU,OAAO,OAAOA;AACvD,YAAI;AACJ,YAAI;AAEF,mBAAS,MAAM,YAAY,MAAM,YAAY,WAAW;AAAA,QAC1D,SAAS,OAAO;AACd,gBAAM,yBAAyB,YAAY,MAAM,KAAK;AAAA,QACxD;AACA,YAAI,WAAW,YAAa,QAAO;AAAA,MACrC;AACA,aAAO;AAAA,IACT;AAAA,IAEA;AAAA,EACF;AACF;;;AC7dO,SAAS,OAAO,QAA4C;AACjE,MAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,MAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,oBAAkB,OAAO,GAAG;AAC5B,MAAI,OAAO,IAAI,WAAW,GAAG;AAC3B,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AAEA,SAAO;AACT;","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 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"]}
|
package/dist/modules/index.d.ts
CHANGED
|
@@ -1274,6 +1274,12 @@ declare const systemd: {
|
|
|
1274
1274
|
* Modules for managing the UFW (Uncomplicated Firewall) on Debian/Ubuntu hosts.
|
|
1275
1275
|
*/
|
|
1276
1276
|
declare const ufw: {
|
|
1277
|
+
/**
|
|
1278
|
+
* Ensure UFW is inactive. If UFW is not installed, this is treated as already satisfied.
|
|
1279
|
+
*
|
|
1280
|
+
* @returns A Module that ensures UFW is disabled.
|
|
1281
|
+
*/
|
|
1282
|
+
disabled(): Module;
|
|
1277
1283
|
/**
|
|
1278
1284
|
* Ensure UFW is active. Enables the firewall non-interactively if not already running.
|
|
1279
1285
|
*
|
package/dist/modules/index.js
CHANGED
package/llm-guide.md
CHANGED
|
@@ -287,10 +287,11 @@ rsync SSH process and does not depend on a local `known_hosts` entry.
|
|
|
287
287
|
|
|
288
288
|
### `ufw`
|
|
289
289
|
|
|
290
|
-
| Method
|
|
291
|
-
|
|
|
292
|
-
| `ufw.
|
|
293
|
-
| `ufw.
|
|
290
|
+
| Method | Signature | Idempotent |
|
|
291
|
+
| -------------- | ---------------------------------------------------------------- | ---------- |
|
|
292
|
+
| `ufw.disabled` | `(): Module` | Yes |
|
|
293
|
+
| `ufw.enabled` | `(): Module` | Yes |
|
|
294
|
+
| `ufw.rule` | `(action: "allow" \| "deny", ports: number \| number[]): Module` | Yes |
|
|
294
295
|
|
|
295
296
|
### `user`
|
|
296
297
|
|
|
@@ -457,6 +458,40 @@ Conditionally run modules. Skipped modules report `"skipped"`, not `"failed"`.
|
|
|
457
458
|
when((env) => env["DEPLOY_ENV"] === "production", service.enabled("fail2ban"), ufw.enabled())
|
|
458
459
|
```
|
|
459
460
|
|
|
461
|
+
### `when.packageInstalled(name, ...modules)` / `when.packageAbsent(name, ...modules)`
|
|
462
|
+
|
|
463
|
+
Conditionally run modules based on package state on the remote host.
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
when.packageInstalled("ufw", ufw.disabled())
|
|
467
|
+
when.packageAbsent("docker-ce", package.installed("docker-ce"))
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### `when.commandExists(name, ...modules)` / `when.commandMissing(name, ...modules)`
|
|
471
|
+
|
|
472
|
+
Conditionally run modules based on whether a command exists on the remote host.
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
when.commandExists("docker", service.running("docker"))
|
|
476
|
+
when.commandMissing("docker", package.installed("docker-ce"))
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Filesystem guard variants
|
|
480
|
+
|
|
481
|
+
Use explicit filesystem-type guards when a module or recipe should only run for a specific entry type:
|
|
482
|
+
|
|
483
|
+
- `when.fileExists(path, ...modules)` / `when.fileMissing(path, ...modules)` for regular files
|
|
484
|
+
- `when.pathExists(path, ...modules)` / `when.pathMissing(path, ...modules)` for directories
|
|
485
|
+
- `when.symlinkExists(path, ...modules)` / `when.symlinkMissing(path, ...modules)` for symlinks
|
|
486
|
+
- `when.socketExists(path, ...modules)` / `when.socketMissing(path, ...modules)` for Unix sockets
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
when.fileExists("/etc/myapp/config.yml", service.reload("myapp"))
|
|
490
|
+
when.pathMissing("/etc/traefik", file.directory("/etc/traefik"))
|
|
491
|
+
when.symlinkExists("/etc/myapp/current", service.restart("myapp"))
|
|
492
|
+
when.socketExists("/run/docker.sock", service.running("docker"))
|
|
493
|
+
```
|
|
494
|
+
|
|
460
495
|
### `debug(message)`
|
|
461
496
|
|
|
462
497
|
Print a debug message during apply. Always runs.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/output.ts","../src/outputFormatting.ts","../src/signalOrchestration.ts"],"sourcesContent":["import pc from \"picocolors\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\nimport { fitAnimatedModuleLine, formatDisplayModule } from \"./outputFormatting.js\"\nimport { CommandError } from \"./sshHelpers.js\"\n\nconst MODULE_NAME_WIDTH = 56\nconst MIN_MODULE_NAME_WIDTH = 12\nconst OUTPUT_INDENT_UNIT = \" \"\nconst SPINNER_FRAME_INTERVAL_MS = 80\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst STATUS_ICONS: Record<DisplayStatus, string> = {\n changed: pc.yellow(\"\\u21ba\"),\n failed: pc.red(\"\\u2717\"),\n ok: pc.green(\"\\u2713\"),\n skipped: pc.dim(\"\\u2298\"),\n waiting: pc.cyan(\"\\u23f8\"),\n}\n\nconst CLI_HEADER_LINES = [\n \" _ _ \",\n \" | | (_) \",\n \" _ __ __ _ _ __ __ _| |_ ___ __\",\n \" | '_ \\\\ / _` | '__/ _` | __| \\\\ \\\\/ /\",\n \" | |_) | (_| | | | (_| | |_| |> < \",\n \" | .__/ \\\\__,_|_| \\\\__,_|\\\\__|_/_/\\\\_\\\\\",\n \" | | \",\n \" |_| \",\n]\n\ntype ActiveSpinner = {\n detail?: string\n frameIndex: number\n interval: NodeJS.Timeout\n}\n\nlet activeSpinner: ActiveSpinner | null = null\nlet recipeOutputDepth = -1\n\nexport function renderCliHeader(version: string): string {\n const versionText = pc.dim(`v${version}`)\n return `${pc.cyan(CLI_HEADER_LINES.join(\"\\n\"))}${versionText}\\n`\n}\n\nexport function printCliHeader(version: string): void {\n console.log(renderCliHeader(version))\n}\n\nfunction supportsAnimatedModuleOutput(): boolean {\n return (\n process.stdout.isTTY &&\n typeof process.stdout.clearLine === \"function\" &&\n typeof process.stdout.cursorTo === \"function\"\n )\n}\n\nfunction getModuleIcon(status: DisplayStatus, waitingFrame?: string): string {\n return status === \"waiting\" ? pc.cyan(waitingFrame ?? \"|\") : STATUS_ICONS[status]\n}\n\nfunction getModuleStatusText(status: DisplayStatus): string {\n switch (status) {\n case \"changed\": {\n return pc.yellow(status)\n }\n case \"failed\": {\n return pc.red(status)\n }\n case \"ok\": {\n return pc.green(status)\n }\n case \"skipped\": {\n return pc.dim(status)\n }\n case \"waiting\": {\n return pc.cyan(\"running\")\n }\n }\n}\n\nfunction getCurrentOutputDepth(): number {\n return Math.max(recipeOutputDepth, 0)\n}\n\nfunction getModuleIndent(): string {\n return OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1)\n}\n\nfunction getRecipeHeaderIndent(): string {\n return recipeOutputDepth < 0 ? \"\" : OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1)\n}\n\nfunction getErrorIndent(): string {\n return `${getModuleIndent()}│ `\n}\n\nfunction getContinuationIndent(): string {\n return `${getModuleIndent()} `\n}\n\nexport async function withRecipeOutputScope<T>(\n scopedOperation: () => Promise<T> | T\n): Promise<T> {\n recipeOutputDepth += 1\n try {\n return await scopedOperation()\n } finally {\n recipeOutputDepth -= 1\n }\n}\n\nfunction renderModuleLine(parameters: {\n detail?: string\n name: string\n status: DisplayStatus\n waitingFrame?: string\n}): string {\n const { detail, name, status, waitingFrame } = parameters\n const indent = getModuleIndent()\n const icon = getModuleIcon(status, waitingFrame)\n const statusText = getModuleStatusText(status)\n const detailSuffix = detail == null ? \"\" : ` ${pc.dim(detail)}`\n const alignedNameWidth = Math.max(MODULE_NAME_WIDTH - indent.length, MIN_MODULE_NAME_WIDTH)\n return `${indent}${icon} ${name.padEnd(alignedNameWidth)} ${statusText}${detailSuffix}`\n}\n\nfunction writeAnimatedModuleLine(line: string): void {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n process.stdout.write(fitAnimatedModuleLine(line, process.stdout.columns))\n}\n\nfunction stopAnimatedModuleLine(clearCurrentLine = false): void {\n if (activeSpinner == null) return\n\n clearInterval(activeSpinner.interval)\n activeSpinner = null\n\n if (clearCurrentLine && supportsAnimatedModuleOutput()) {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n }\n}\n\nexport function startModuleSpinner(name: string, detail?: string): void {\n if (!supportsAnimatedModuleOutput()) return\n\n stopAnimatedModuleLine()\n const displayModule = formatDisplayModule({\n continuationIndentWidth: getContinuationIndent().length,\n detail,\n name,\n status: \"waiting\",\n terminalColumns: process.stdout.columns,\n })\n\n const spinner: ActiveSpinner = {\n detail: displayModule.detail,\n frameIndex: 0,\n interval: setInterval(() => {\n spinner.frameIndex = (spinner.frameIndex + 1) % SPINNER_FRAMES.length\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: spinner.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[spinner.frameIndex],\n })\n )\n }, SPINNER_FRAME_INTERVAL_MS),\n }\n\n activeSpinner = spinner\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: displayModule.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[0],\n })\n )\n}\n\nexport function resetLiveOutputForTests(): void {\n stopAnimatedModuleLine()\n recipeOutputDepth = -1\n}\n\n/**\n * Print a bold, colored header line marking the start of a recipe run.\n * @param name - The recipe or server name to display.\n */\nexport function printRecipeHeader(name: string): void {\n stopAnimatedModuleLine(true)\n const header = pc.bold(pc.blue(`[${name}]`))\n console.log(`${getRecipeHeaderIndent()}${header}`)\n}\n\nexport function printRunContext(parameters: {\n dryRun: boolean\n host: string\n name: string\n ports: number[]\n}): void {\n const mode = parameters.dryRun ? pc.yellow(\"dry-run\") : pc.green(\"apply\")\n const ports = parameters.ports.join(\", \")\n console.log(\n pc.dim(`Run ${parameters.name} · host ${parameters.host} · ports ${ports} · mode ${mode}`)\n )\n}\n\n/**\n * Print a single module result row with a status icon, name, and colored status label.\n *\n * @param name - The module name shown in the left column.\n * @param status - One of the known status strings (`ok`, `changed`, `skipped`, `failed`).\n * @param detail - Optional short detail appended in dim text after the status.\n */\nexport function printModuleResult(name: string, status: DisplayStatus, detail?: string): void {\n const displayModule = formatDisplayModule({\n continuationIndentWidth: getContinuationIndent().length,\n detail,\n name,\n status,\n terminalColumns: process.stdout.columns,\n })\n const line = renderModuleLine({\n detail: displayModule.detail,\n name: displayModule.name,\n status,\n })\n if (supportsAnimatedModuleOutput() && activeSpinner != null) {\n stopAnimatedModuleLine()\n writeAnimatedModuleLine(line)\n process.stdout.write(\"\\n\")\n for (const detailLine of displayModule.detailLines) {\n process.stdout.write(`${getContinuationIndent()}${pc.dim(detailLine)}\\n`)\n }\n return\n }\n\n console.log(line)\n for (const detailLine of displayModule.detailLines) {\n console.log(`${getContinuationIndent()}${pc.dim(detailLine)}`)\n }\n}\n\n/**\n * Print captured stderr and stdout from a failed command in a red bordered block.\n * Outputs nothing if both streams are empty.\n *\n * @param stdout - Captured standard output of the failed command.\n * @param stderr - Captured standard error of the failed command.\n */\nexport function printCommandError(stdout: string, stderr: string): void {\n const lines: string[] = []\n if (stderr.trim()) {\n lines.push(...stderr.trim().split(\"\\n\"))\n }\n if (stdout.trim()) {\n lines.push(...stdout.trim().split(\"\\n\"))\n }\n if (lines.length > 0) {\n console.error(pc.red(`${getErrorIndent()}Error output:`))\n for (const line of lines) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\n/**\n * Print the full (untruncated) stdout and stderr of a failed command.\n * Used in verbose mode to show the complete output that was truncated in the error message.\n *\n * @param stdout - Full standard output of the failed command.\n * @param stderr - Full standard error of the failed command.\n */\nexport function printVerboseCommandError(stdout: string, stderr: string): void {\n if (stderr.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stderr:`))\n for (const line of stderr.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n if (stdout.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stdout:`))\n for (const line of stdout.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\nfunction printVerboseErrorBlock(label: string, content: string): void {\n if (!content.trim()) {\n return\n }\n\n console.error(pc.red(`${getErrorIndent()}${label}`))\n for (const line of content.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n}\n\nfunction getErrorCause(error: Error): unknown {\n return (error as { cause?: unknown } & Error).cause\n}\n\nfunction printVerboseErrorCause(cause: unknown, depth: number): void {\n const label = `Cause ${depth}:`\n if (cause instanceof Error) {\n const stack = cause.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(cause)\n printVerboseErrorBlock(label, stackOrMessage)\n const nestedCause = getErrorCause(cause)\n if (nestedCause !== undefined) {\n printVerboseErrorCause(nestedCause, depth + 1)\n }\n return\n }\n\n printVerboseErrorBlock(label, String(cause))\n}\n\nfunction printVerboseGenericError(error: Error): void {\n const stack = error.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(error)\n printVerboseErrorBlock(\"Full stack:\", stackOrMessage)\n const cause = getErrorCause(error)\n if (cause !== undefined) {\n printVerboseErrorCause(cause, 1)\n }\n}\n\n/**\n * Print the error message of a failed command and, when verbose mode is active\n * and the error is a {@link CommandError}, the full untruncated output.\n *\n * @param error - The caught error value.\n * @param verbose - Whether to show full stdout/stderr.\n */\nexport function printCommandFailure(error: unknown, verbose: boolean): void {\n if (verbose && error instanceof CommandError) {\n // Print only the exit-code line, skip the truncated output and hint\n const summaryLine = error.message.split(\"\\n\")[0]\n printCommandError(\"\", summaryLine)\n printVerboseCommandError(error.fullStdout, error.fullStderr)\n return\n }\n\n printCommandError(\"\", String(error))\n if (verbose && error instanceof Error) {\n printVerboseGenericError(error)\n }\n}\n\n/**\n * Print a run summary line with counts for each status category.\n *\n * @param stats - Aggregated counts from the completed run.\n * @param stats.changed - Number of modules that changed state.\n * @param stats.ok - Number of modules already in desired state.\n * @param stats.skipped - Number of modules skipped.\n * @param stats.failed - Number of modules that failed.\n * @param stats.signals - Number of signals triggered.\n */\nexport function printSummary(stats: {\n changed: number\n failed: number\n ok: number\n signals: number\n skipped: number\n}): void {\n const parts = [\n pc.yellow(`${stats.changed} changed`),\n pc.green(`${stats.ok} ok`),\n pc.dim(`${stats.skipped} skipped`),\n stats.failed > 0 ? pc.red(`${stats.failed} failed`) : `${stats.failed} failed`,\n pc.cyan(`${stats.signals} signals triggered`),\n ]\n console.log(`\\n${parts.join(pc.dim(\" \\u00b7 \"))}`)\n}\n","import { stripVTControlCharacters } from \"node:util\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst PACKAGE_MODULE_SUFFIX_LENGTH = 2\nconst PACKAGE_COLUMN_GAP_WIDTH = 2\nconst DEFAULT_TERMINAL_COLUMNS = 100\nconst MIN_PACKAGE_COLUMNS_WIDTH = 24\nconst MIN_PACKAGE_COLUMN_WIDTH = 18\nconst MIN_ANIMATED_LINE_COLUMNS = 8\n\nexport type DisplayModule = {\n detail?: string\n detailLines: string[]\n name: string\n}\n\nfunction splitPackageModuleName(name: string): { packages: string[]; summaryName: string } | null {\n for (const prefix of [\"package.installed: \", \"package.absent: \"]) {\n if (!name.startsWith(prefix)) continue\n\n const packages = name\n .slice(prefix.length)\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n if (packages.length === 0) return null\n\n return {\n packages,\n summaryName: prefix.slice(0, -PACKAGE_MODULE_SUFFIX_LENGTH),\n }\n }\n\n return null\n}\n\nfunction getPackageColumns(parameters: {\n continuationIndentWidth: number\n packages: string[]\n terminalColumns?: number\n}): string[] {\n if (parameters.packages.length <= 1) return parameters.packages\n\n const availableWidth = Math.max(\n (parameters.terminalColumns ?? DEFAULT_TERMINAL_COLUMNS) - parameters.continuationIndentWidth,\n MIN_PACKAGE_COLUMNS_WIDTH\n )\n const widestPackage = Math.max(...parameters.packages.map((entry) => entry.length))\n const columnWidth = Math.max(widestPackage + PACKAGE_COLUMN_GAP_WIDTH, MIN_PACKAGE_COLUMN_WIDTH)\n const columnCount = Math.max(Math.floor(availableWidth / columnWidth), 1)\n const rowCount = Math.ceil(parameters.packages.length / columnCount)\n\n return Array.from({ length: rowCount }, (_row, rowIndex) =>\n Array.from({ length: columnCount }, (_column, columnIndex) => {\n const packageIndex = rowIndex + columnIndex * rowCount\n const packageName = parameters.packages.at(packageIndex)\n if (packageName == null) return \"\"\n const isLastVisibleColumn =\n columnIndex === columnCount - 1 || packageIndex + rowCount >= parameters.packages.length\n return isLastVisibleColumn ? packageName : packageName.padEnd(columnWidth)\n })\n .filter(Boolean)\n .join(\"\")\n )\n}\n\nfunction getPackageSummaryDetail(packageCount: number, detail?: string): string {\n const packageLabel = packageCount === 1 ? \"1 package\" : `${packageCount} packages`\n return detail == null ? packageLabel : `${packageLabel} · ${detail}`\n}\n\nexport function fitAnimatedModuleLine(line: string, columns?: number): string {\n if (columns === undefined || columns < MIN_ANIMATED_LINE_COLUMNS) return line\n\n const plainLine = stripVTControlCharacters(line)\n if (plainLine.length < columns) return line\n\n return `${plainLine.slice(0, columns - 1)}\\u2026`\n}\n\nexport function formatDisplayModule(parameters: {\n continuationIndentWidth: number\n detail?: string\n name: string\n status: DisplayStatus\n terminalColumns?: number\n}): DisplayModule {\n const packageModule = splitPackageModuleName(parameters.name)\n if (packageModule == null) {\n return {\n detail: parameters.detail,\n detailLines: [],\n name: parameters.name,\n }\n }\n\n return {\n detail: getPackageSummaryDetail(packageModule.packages.length, parameters.detail),\n detailLines:\n parameters.status === \"waiting\"\n ? []\n : getPackageColumns({\n continuationIndentWidth: parameters.continuationIndentWidth,\n packages: packageModule.packages,\n terminalColumns: parameters.terminalColumns,\n }),\n name: packageModule.summaryName,\n }\n}\n","import type {\n Environment,\n Module,\n ModuleStatus,\n OrchestrationStep,\n SshConnection,\n} from \"./types.js\"\n\nimport { assertValidModuleMetaEntries, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { printCommandFailure, printModuleResult, startModuleSpinner } from \"./output.js\"\n\nexport type SignalHooks = {\n onSignalFinished?: (status: ModuleStatus) => void\n onSignalStarted?: () => void\n}\n\nexport type SignalRunStatus = \"changed\" | \"failed\"\n\ntype SignalRunParameters = {\n environment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}\n\nfunction handleSignalResult(parameters: {\n hooks?: SignalHooks\n result: Awaited<ReturnType<Module[\"apply\"]>>\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { hooks, result, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, result.status)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n hooks?.onSignalFinished?.(result.status)\n return result.status === \"failed\" ? \"failed\" : \"changed\"\n}\n\nfunction handleSignalFailure(parameters: {\n error: unknown\n hooks?: SignalHooks\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { error, hooks, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, \"failed\")\n printCommandFailure(error, verbose)\n hooks?.onSignalFinished?.(\"failed\")\n return \"failed\"\n}\n\nasync function applySignalMeta(parameters: {\n currentEnvironment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n result: Awaited<ReturnType<Module[\"apply\"]>>\n}): Promise<Environment> {\n assertValidModuleMetaEntries(parameters.result.meta)\n const nextEnvironment = await mergeEnvironmentFromMeta(\n parameters.currentEnvironment,\n parameters.result.meta\n )\n await parameters.onSignalStep?.({\n env: nextEnvironment,\n meta: parameters.result.meta,\n status: parameters.result.status,\n })\n return nextEnvironment\n}\n\nasync function runOneSignal(parameters: {\n currentEnvironment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n signal: Module\n ssh: null | SshConnection\n verbose: boolean\n}): Promise<{ nextEnvironment: Environment; status: SignalRunStatus }> {\n const connection = parameters.signal.local === true ? null : parameters.ssh\n startModuleSpinner(`signal: ${parameters.signal.name}`)\n const result = await parameters.signal.apply(connection, parameters.currentEnvironment)\n const nextEnvironment = await applySignalMeta({\n currentEnvironment: parameters.currentEnvironment,\n onSignalStep: parameters.onSignalStep,\n result,\n })\n return {\n nextEnvironment,\n status: handleSignalResult({\n hooks: parameters.hooks,\n result,\n signalName: parameters.signal.name,\n verbose: parameters.verbose,\n }),\n }\n}\n\nexport async function runSignalModules(parameters: SignalRunParameters): Promise<SignalRunStatus> {\n const getShutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let currentEnvironment = parameters.environment\n let status: SignalRunStatus = \"changed\"\n\n for (const signal of parameters.signals) {\n if (getShutdownSignal() != null) break\n parameters.hooks?.onSignalStarted?.()\n try {\n // eslint-disable-next-line no-await-in-loop\n const signalStep = await runOneSignal({\n currentEnvironment,\n hooks: parameters.hooks,\n onSignalStep: parameters.onSignalStep,\n signal,\n ssh: parameters.ssh,\n verbose,\n })\n currentEnvironment = signalStep.nextEnvironment\n if (signalStep.status === \"failed\") status = \"failed\"\n } catch (error) {\n status = handleSignalFailure({\n error,\n hooks: parameters.hooks,\n signalName: signal.name,\n verbose,\n })\n }\n }\n\n return status\n}\n"],"mappings":"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","names":[]}
|