paratix 0.6.0 → 0.7.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 +1 -1
- package/dist/{chunk-ENWMSERJ.js → chunk-D4CS2GCH.js} +73 -21
- package/dist/chunk-D4CS2GCH.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/index.d.ts +2 -2
- package/dist/modules/index.js +1 -1
- package/dist/{types-BPzPHfax.d.ts → types-Cl2Muw1x.d.ts} +2 -0
- package/dist/{user-CiAMlpWO.d.ts → user-BJMqDePy.d.ts} +1 -1
- package/llm-guide.md +1 -1
- package/package.json +1 -1
- package/dist/chunk-ENWMSERJ.js.map +0 -1
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -296,7 +296,7 @@ async function applyModule(parameters) {
|
|
|
296
296
|
const connection = targetModule.local === true ? null : ssh;
|
|
297
297
|
const result = dryRun && targetModule._applyDryRun != null ? await targetModule._applyDryRun(connection, currentEnvironment) : await targetModule.apply(connection, currentEnvironment);
|
|
298
298
|
const stepResult = await handleMetaAndBuildResult(ssh, currentEnvironment, result);
|
|
299
|
-
const detail = dryRun ? result._dryRunDetail ?? "(dry-run)" :
|
|
299
|
+
const detail = dryRun ? result._dryRunDetail ?? "(dry-run)" : result.detail;
|
|
300
300
|
printModuleResult(targetModule.name, result.status, detail);
|
|
301
301
|
if (result.status === "failed" && result.error != null) {
|
|
302
302
|
printCommandFailure(result.error, verbose);
|
|
@@ -695,7 +695,7 @@ async function loadServerDefinitionFromFile(file, options) {
|
|
|
695
695
|
return definition;
|
|
696
696
|
}
|
|
697
697
|
var program = new Command();
|
|
698
|
-
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.
|
|
698
|
+
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.7.0-f05c332");
|
|
699
699
|
program.command("apply <file>").description("Apply a server definition").option(
|
|
700
700
|
"--dry-run",
|
|
701
701
|
"Only check, do not apply. Some modules validate prospective config but cannot verify runtime restarts.",
|
|
@@ -707,7 +707,7 @@ program.command("apply <file>").description("Apply a server definition").option(
|
|
|
707
707
|
DEFAULT_RECONNECT_TIMEOUT_SECONDS
|
|
708
708
|
).option("--verbose", "Show full stack traces on error", false).action(async (file, options) => {
|
|
709
709
|
try {
|
|
710
|
-
printCliHeader("0.
|
|
710
|
+
printCliHeader("0.7.0-f05c332");
|
|
711
711
|
const environmentOverrides = applyCliEnvironmentOverrides(
|
|
712
712
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>
|
|
713
713
|
options.env,
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/dryRunRecipe.ts","../src/runnerHelpers.ts","../src/runner.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport { realpathSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { inspect } from \"node:util\"\nimport pc from \"picocolors\"\n\nimport type { Environment, ServerDefinition } from \"./types.js\"\n\nimport { printCliHeader } from \"./output.js\"\nimport { runPlaybook } from \"./runner.js\"\nimport { collectSshConfigErrors } from \"./serverDefinitionValidation.js\"\n\ndeclare const PACKAGE_DISPLAY_VERSION: string\n\nconst SECONDS_TO_MS = 1000\nconst DEFAULT_RECONNECT_TIMEOUT_SECONDS = 300\nconst ENVIRONMENT_KEY_PATTERN = /^[A-Za-z_]\\w*$/v\nconst FIRST_RUN_ENV_NAME = \"PARATIX_FIRST_RUN\"\n\n/**\n * Type guard that checks whether `value` has the shape of a\n * {@link ServerDefinition} — an object with a non-empty string `name`,\n * a non-empty string `host`, a valid `ssh` config, and a non-empty `run` array.\n *\n * @param value - The value to inspect.\n * @returns `true` when `value` satisfies the structural requirements of `ServerDefinition`.\n */\nexport function isServerDefinitionLike(value: unknown): value is ServerDefinition {\n return collectDefinitionErrors(value).length === 0\n}\n\n/** Descriptor for a property validation check. */\ntype PropertyCheck = {\n /** The key to look up in the object. */\n key: string\n /** The label to use in error messages (defaults to `key`). */\n label?: string\n}\n\n/**\n * Validates that a required string property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectStringErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected string)`)\n } else if (typeof object[check.key] !== \"string\") {\n errors.push(`Invalid property '${name}' (expected string, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof check above\n } else if ((object[check.key] as string).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\n/**\n * Validates that a required array property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectArrayErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected array)`)\n } else if (!Array.isArray(object[check.key])) {\n errors.push(`Invalid property '${name}' (expected array, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by Array.isArray check above\n } else if ((object[check.key] as unknown[]).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\nfunction collectSshErrors(value: Record<string, unknown>, errors: string[]): void {\n if (!(\"ssh\" in value)) {\n errors.push(\"Missing property 'ssh' (expected object)\")\n return\n }\n errors.push(...collectSshConfigErrors(value.ssh))\n}\n\n/**\n * Collects human-readable error messages for every property of `value` that\n * does not conform to the `ServerDefinition` shape.\n *\n * @param value - The value to validate.\n * @returns An array of error strings, empty when `value` is structurally valid.\n */\nexport function collectDefinitionErrors(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== \"object\" || value === null) {\n errors.push(\"Export is not an object\")\n return errors\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof/null checks above\n const object = value as Record<string, unknown>\n collectStringErrors(object, { key: \"name\" }, errors)\n collectStringErrors(object, { key: \"host\" }, errors)\n collectSshErrors(object, errors)\n collectArrayErrors(object, { key: \"run\" }, errors)\n return errors\n}\n\n/**\n * Assertion function that ensures `value` is a valid {@link ServerDefinition}.\n *\n * When validation fails, all collected errors are printed to stderr and the\n * process exits with code `2`, so callers can treat the function as a\n * narrowing assertion without additional error handling.\n *\n * @param value - The value to validate.\n * @param file - Path of the file that exported `value`, used in the error message.\n */\nfunction validateServerDefinition(value: unknown, file: string): asserts value is ServerDefinition {\n if (isServerDefinitionLike(value)) {\n return\n }\n const errors = collectDefinitionErrors(value)\n const details = errors.map((entry) => ` - ${entry}`).join(\"\\n\")\n console.error(\n `Error: ${file} does not export a valid ServerDefinition.\\n${details}\\n Use the server() helper to create a valid definition.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n}\n\n/**\n * Returns a human-readable string for any caught value.\n * Uses `.message` for `Error` instances and falls back to the string\n * representation for primitives. For plain objects that have no meaningful\n * `toString`, the JSON representation is used instead of `[object Object]`.\n *\n * @param value - The value to convert to a string.\n * @returns A human-readable string representation of `value`.\n */\nfunction errorToString(value: unknown): string {\n if (value instanceof Error) return value.message\n if (typeof value === \"object\" && value !== null) {\n try {\n return JSON.stringify(value)\n } catch {\n return inspect(value, { breakLength: Infinity, depth: 5 })\n }\n }\n return String(value)\n}\n\n/**\n * Walks the cause chain of `error` and prints each cause to stderr.\n *\n * @param error - The root `Error` whose `.cause` chain should be printed.\n */\nfunction printCauseChain(error: Error): void {\n let cause = error.cause\n while (cause != null) {\n console.error(` Caused by: ${errorToString(cause)}`)\n cause = cause instanceof Error ? cause.cause : undefined\n }\n}\n\n/**\n * Prints a structured error message to stderr, including the cause chain and\n * optionally the full stack trace when `verbose` is `true`.\n *\n * @param error - The caught value (may be any type).\n * @param verbose - When `true`, the stack trace of `error` is printed.\n */\nexport function printExceptionError(error: unknown, verbose: boolean): void {\n console.error(`Error: ${errorToString(error)}`)\n\n if (error instanceof Error) {\n printCauseChain(error)\n\n if (verbose && error.stack != null) {\n console.error(`\\n${error.stack}`)\n }\n }\n}\n\n/**\n * Handles a failed attempt to load the `tsx` runtime.\n *\n * When the failed import is for a TypeScript file (`.ts`, `.mts`, `.cts`), a\n * clear error message is printed to stderr and the process exits with code 2.\n * For JavaScript files the failure is silently ignored because `tsx` is not\n * required there.\n *\n * @param filePath - The resolved path of the playbook file being loaded.\n */\nexport function handleTsxLoadFailure(filePath: string): void {\n if (/\\.[cm]?ts$/v.test(filePath)) {\n console.error(\n `${pc.red(\"Error:\")} tsx is required to run TypeScript playbooks but could not be loaded.\\n` +\n ` Install it with: ${pc.bold(\"npm install -g tsx\")} or add it as a devDependency.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n}\n\n/**\n * Returns whether the current CLI module is the direct process entrypoint.\n *\n * This resolves symlinks on both sides so pnpm-style executable shims and\n * symlinked `node_modules` entries still count as direct execution.\n *\n * @param moduleUrl - The current module URL, usually `import.meta.url`.\n * @param candidateEntryScript - The process entry script path, usually `process.argv[1]`.\n * @returns `true` when both paths resolve to the same file on disk.\n */\nexport function isDirectCliExecution(moduleUrl: string, candidateEntryScript?: string): boolean {\n if (candidateEntryScript == null) {\n return false\n }\n\n return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(candidateEntryScript)\n}\n\nexport function applyCliEnvironmentOverrides(\n environment: Environment,\n options: { firstRun: boolean }\n): Environment {\n if (!options.firstRun) return environment\n return { ...environment, [FIRST_RUN_ENV_NAME]: \"true\" }\n}\n\nexport function applyCliProcessEnvironment(options: { firstRun: boolean }): void {\n if (!options.firstRun) return\n process.env[FIRST_RUN_ENV_NAME] = \"true\"\n}\n\nexport async function loadServerDefinitionFromFile(\n file: string,\n options: { firstRun: boolean }\n): Promise<ServerDefinition> {\n const filePath = resolve(file)\n const fileUrl = pathToFileURL(filePath).href\n\n applyCliProcessEnvironment(options)\n\n // Register tsx for TypeScript imports\n await import(\"tsx/esm/api\")\n .then((tsx: { register: () => void }) => {\n tsx.register()\n })\n .catch(() => {\n handleTsxLoadFailure(filePath)\n })\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Dynamic import has unknown shape\n const imported = await import(fileUrl)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-type-assertion -- Accessing .default on dynamic import\n const definition = (imported.default ?? imported) as ServerDefinition\n\n validateServerDefinition(definition, filePath)\n return definition\n}\n\nconst program = new Command()\n\nprogram\n .name(\"paratix\")\n .description(\"Idempotent VPS setup tool in TypeScript\")\n .version(PACKAGE_DISPLAY_VERSION)\n\nprogram\n .command(\"apply <file>\")\n .description(\"Apply a server definition\")\n .option(\n \"--dry-run\",\n \"Only check, do not apply. Some modules validate prospective config but cannot verify runtime restarts.\",\n false\n )\n .option(\"--env <key=value...>\", \"Set env values\", collectEnvironment, {})\n .option(\"--env-file <path>\", \"Load dotenv file\")\n .option(\"--first-run\", \"Set PARATIX_FIRST_RUN=true before loading the playbook\", false)\n .option(\n \"--reconnect-timeout <seconds>\",\n \"SSH reconnect timeout\",\n parsePositiveNumber,\n DEFAULT_RECONNECT_TIMEOUT_SECONDS\n )\n .option(\"--verbose\", \"Show full stack traces on error\", false)\n .action(async (file: string, options: Record<string, unknown>) => {\n try {\n printCliHeader(PACKAGE_DISPLAY_VERSION)\n const environmentOverrides = applyCliEnvironmentOverrides(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n options.env as Environment,\n {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n }\n )\n const definition = await loadServerDefinitionFromFile(file, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n })\n\n await runPlaybook(definition, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n dryRun: options.dryRun as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n envFile: options.envFile as string | undefined,\n envOverrides: environmentOverrides,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n reconnectTimeout: (options.reconnectTimeout as number) * SECONDS_TO_MS,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n verbose: options.verbose as boolean,\n })\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n printExceptionError(error, options.verbose as boolean)\n // eslint-disable-next-line node/no-process-exit\n process.exit(process.exitCode ?? 2)\n }\n })\n\nexport function parsePositiveNumber(value: string): number {\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.error(`Invalid --reconnect-timeout value: ${value} (expected a positive number)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return parsed\n}\n\nexport function collectEnvironment(\n value: string,\n previous: Record<string, string>\n): Record<string, string> {\n const eqIndex = value.indexOf(\"=\")\n if (eqIndex === -1) {\n console.error(`Invalid --env format: ${value} (expected key=value)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n const key = value.slice(0, eqIndex)\n const value_ = value.slice(eqIndex + 1)\n if (!ENVIRONMENT_KEY_PATTERN.test(key)) {\n console.error(\n `Invalid --env name: ${key === \"\" ? \"(empty)\" : key} (expected [A-Za-z_][A-Za-z0-9_]*)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return { ...previous, [key]: value_ }\n}\n\n// Only parse when executed directly, not when imported (e.g. in tests)\nconst entryScript = process.argv[1]\nif (isDirectCliExecution(import.meta.url, entryScript)) {\n await program.parseAsync()\n}\n","import type { RecipeModule } from \"./recipe.js\"\nimport type { SshConnectionImpl } from \"./ssh.js\"\nimport type { Environment, ModuleStatus } from \"./types.js\"\n\nimport { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\n\ntype StepResult = { env: Environment; shouldBreak: boolean; status?: ModuleStatus; stopRun?: true }\n\nfunction shouldExecuteApplyDuringDryRun(module: RecipeModule[\"_modules\"][number]): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function executeDryRunBlockingModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n connection: null | SshConnectionImpl\n environment: Environment\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, connection, environment, verbose } = parameters\n startModuleSpinner(childModule.name)\n const result =\n childModule._applyDryRun == null\n ? await childModule.apply(connection, environment)\n : await childModule._applyDryRun(connection, environment)\n const nextEnvironment =\n result.meta == null ? environment : await mergeEnvironmentFromMeta(environment, result.meta)\n printModuleResult(childModule.name, result.status, result._dryRunDetail ?? \"(dry-run)\")\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return {\n env: nextEnvironment,\n shouldBreak: result.status === \"failed\" || result._stopRun === true,\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\nasync function executeDryRunChildModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n environment: Environment\n ssh: SshConnectionImpl\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, environment, ssh, verbose } = parameters\n const connection = childModule.local === true ? null : ssh\n startModuleSpinner(childModule.name)\n const checkResult = await childModule.check(connection, environment)\n if (checkResult !== \"ok\" && shouldExecuteApplyDuringDryRun(childModule)) {\n return executeDryRunBlockingModule({ childModule, connection, environment, verbose })\n }\n const status = checkResult === \"ok\" ? \"ok\" : \"changed\"\n const suffix = checkResult === \"ok\" ? undefined : \"(dry-run)\"\n printModuleResult(childModule.name, status, suffix)\n return { env: environment, shouldBreak: false, status }\n}\n\nexport async function dryRunRecipeModule(parameters: {\n environment: Environment\n options?: {\n verbose?: boolean\n }\n recipeModule: RecipeModule\n ssh: SshConnectionImpl\n}): Promise<StepResult> {\n return withRecipeOutputScope(async () => {\n const { environment, recipeModule, ssh } = parameters\n printRecipeHeader(recipeModule.name)\n let aggregatedStatus: \"changed\" | \"ok\" = \"ok\"\n let currentEnvironment = environment\n const verbose = parameters.options?.verbose ?? false\n\n for (const childModule of recipeModule._modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await executeDryRunChildModule({\n childModule,\n environment: currentEnvironment,\n ssh,\n verbose,\n })\n if (result.shouldBreak) return result\n currentEnvironment = result.env\n if (result.status === \"changed\") aggregatedStatus = \"changed\"\n }\n\n return { env: currentEnvironment, shouldBreak: false, status: aggregatedStatus }\n })\n}\n","const SIGNAL_EXIT_BASE = 128\nconst SIGTERM_NUMBER = 15\nconst SIGINT_NUMBER = 2\n\n/**\n * Returns the conventional exit code for a termination signal.\n * Follows the POSIX convention of 128 + signal number.\n *\n * @param signal - The received signal (`SIGTERM` or `SIGINT`).\n * @returns The exit code to use when the process is terminated by `signal`.\n */\nexport function signalExitCode(signal: NodeJS.Signals): number {\n return SIGNAL_EXIT_BASE + (signal === \"SIGTERM\" ? SIGTERM_NUMBER : SIGINT_NUMBER)\n}\n\n/**\n * Sets `process.exitCode` based on the run outcome.\n * A received shutdown signal takes precedence over module failures.\n *\n * @param shutdownSignal - The signal that interrupted the run, or `null` if the\n * run completed normally.\n * @param stats - Accumulated run statistics used to detect module failures.\n * @param stats.failed - Number of modules that failed.\n */\nexport function resolveExitCode(\n shutdownSignal: NodeJS.Signals | null,\n stats: { failed: number }\n): void {\n if (shutdownSignal != null) {\n process.exitCode = signalExitCode(shutdownSignal)\n } else if (stats.failed > 0) {\n process.exitCode = 1\n }\n}\n","/* eslint-disable max-lines -- central runner orchestration stays intentionally co-located */\nimport type { RecipeModule } from \"./recipe.js\"\nimport type {\n Environment,\n Module,\n ModuleResult,\n ModuleStatus,\n OrchestrationStep,\n ServerDefinition,\n} from \"./types.js\"\n\nimport { dryRunRecipeModule } from \"./dryRunRecipe.js\"\nimport { loadDotEnvironment, mergeEnvironment } from \"./environment.js\"\nimport {\n assertValidModuleMetaEntries,\n isSshdPortMetaEntry,\n isSystemHostMetaEntry,\n isSystemRebootMetaEntry,\n mergeEnvironmentFromMeta,\n} from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRunContext,\n printSummary,\n startModuleSpinner,\n} from \"./output.js\"\nimport { resolveExitCode, signalExitCode } from \"./runnerHelpers.js\"\nimport { runSignalModules, type SignalRunStatus } from \"./signalOrchestration.js\"\nimport { SshConnectionImpl } from \"./ssh.js\"\n\n/** Holds the shutdown listener, SSH setter, and getter for the first received signal. */\ntype ShutdownState = {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n promptAbortSignal: AbortSignal\n setSsh: (connection: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}\n\n/**\n * Registers shutdown handlers.\n * @returns The listener state and first-signal getter.\n */\nfunction setupShutdownHandlers(): ShutdownState {\n let receivedSignal: NodeJS.Signals | null = null\n let ssh: null | SshConnectionImpl = null\n const promptAbortController = new AbortController()\n\n const handleShutdownSignal = (signal: NodeJS.Signals): void => {\n if (receivedSignal != null) {\n // eslint-disable-next-line node/no-process-exit\n process.exit(signalExitCode(signal))\n }\n receivedSignal = signal\n promptAbortController.abort(new Error(`Terminal prompt interrupted by ${signal}`))\n console.error(`\\nReceived ${signal}, shutting down…`)\n ssh?.disconnect()\n }\n\n process.on(\"SIGINT\", handleShutdownSignal)\n process.on(\"SIGTERM\", handleShutdownSignal)\n\n return {\n handleShutdownSignal,\n promptAbortSignal: promptAbortController.signal,\n setSsh: (connection: SshConnectionImpl) => {\n ssh = connection\n },\n shutdownSignal: () => receivedSignal,\n }\n}\n\nexport type RunOptions = {\n /** When `true`, modules report what would change without applying anything. Defaults to `false`. */\n dryRun?: boolean\n /** Path to a `.env` file whose variables are merged into the run environment. */\n envFile?: string\n /** Additional environment variables that override values from `envFile` and the server definition. */\n envOverrides?: Environment\n /** Custom reconnect timeout in milliseconds passed to SSH, overriding the config default. */\n reconnectTimeout?: number\n /** When `true`, failed commands print full stdout/stderr in addition to the summary error. */\n verbose?: boolean\n}\n\nclass RunStats {\n public changed = 0\n public failed = 0\n public ok = 0\n public signals = 0\n public skipped = 0\n\n public incrementSignals(): void {\n this.signals++\n }\n\n public update(status: ModuleStatus): void {\n switch (status) {\n case \"changed\": {\n this.changed++\n break\n }\n case \"failed\": {\n this.failed++\n break\n }\n case \"ok\": {\n this.ok++\n break\n }\n case \"skipped\": {\n this.skipped++\n break\n }\n }\n }\n}\n\ntype StepResult = {\n env: Environment\n flushSignals?: true\n shouldBreak: boolean\n status?: ModuleStatus\n stopRun?: true\n}\n\nfunction interruptedStepResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: true }\n}\n\nfunction shouldBreakAfterResult(result: Pick<ModuleResult, \"_stopRun\" | \"status\">): boolean {\n return result.status === \"failed\" || result._stopRun === true\n}\n\nfunction interruptedBeforeApply(\n environment: Environment,\n shutdownSignal: () => NodeJS.Signals | null\n): StepResult | undefined {\n if (shutdownSignal() == null) return undefined\n return interruptedStepResult(environment)\n}\n\nasync function applyCheckedModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const interrupted = interruptedBeforeApply(\n parameters.currentEnvironment,\n parameters.shutdownSignal\n )\n if (interrupted != null) return interrupted\n\n return applyModule({\n currentEnvironment: parameters.currentEnvironment,\n dryRun: parameters.dryRun,\n ssh: parameters.ssh,\n targetModule: parameters.targetModule,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldExecuteApplyDuringDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nfunction handleCaughtStepError(parameters: {\n environment: Environment\n error: unknown\n moduleName: string\n shutdownSignal: () => NodeJS.Signals | null\n verbose: boolean\n}): StepResult {\n if (parameters.shutdownSignal() != null) {\n return interruptedStepResult(parameters.environment)\n }\n printModuleResult(parameters.moduleName, \"failed\")\n printCommandFailure(parameters.error, parameters.verbose)\n return { env: parameters.environment, shouldBreak: true, status: \"failed\" }\n}\n\nfunction isRecipe(target: Module): target is RecipeModule {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- RecipeModule uses _isRecipe as discriminator\n return \"_isRecipe\" in target && (target as RecipeModule)._isRecipe\n}\n\nasync function initializeEnvironment(\n options: RunOptions,\n definition: ServerDefinition\n): Promise<Environment> {\n const dotEnvironment =\n options.envFile == null ? undefined : await loadDotEnvironment(options.envFile)\n return mergeEnvironment({}, dotEnvironment, definition.env, options.envOverrides)\n}\n\nasync function handlePortChange(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n const portEntry = metaEntries?.find((entry) => isSshdPortMetaEntry(entry))\n if (portEntry == null) return\n\n const newPort = portEntry.port\n ssh.addPort(newPort)\n\n // Skip reconnect when a reboot is pending — the reboot handler will\n // reconnect on all registered ports (including the newly added one).\n if (metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false) return\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(\n `Failed to reconnect on port ${newPort} after port change: ${String(error)}. ` +\n `Verify that port ${newPort} is allowed by the server's firewall rules.`\n )\n throw error\n }\n}\n\nasync function handleReboot(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n if (!(metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false)) return\n\n const hostEntry = metaEntries?.find((entry) => isSystemHostMetaEntry(entry))\n if (hostEntry != null) {\n ssh.updateHost(hostEntry.host)\n }\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(`Failed to reconnect after reboot: ${String(error)}`)\n throw error\n }\n}\n\nasync function applyRunnerControlPlaneMeta(\n ssh: SshConnectionImpl,\n step: Pick<OrchestrationStep, \"meta\">\n): Promise<void> {\n if (step.meta == null) return\n assertValidModuleMetaEntries(step.meta)\n await handlePortChange(ssh, step.meta)\n await handleReboot(ssh, step.meta)\n}\n\nasync function handleMetaAndBuildResult(\n ssh: SshConnectionImpl,\n environment: Environment,\n result: ModuleResult\n): Promise<StepResult> {\n let currentEnvironment = environment\n\n if (result.meta != null) {\n currentEnvironment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n await applyRunnerControlPlaneMeta(ssh, { meta: result.meta })\n }\n\n return {\n env: currentEnvironment,\n flushSignals: result._flushSignals,\n shouldBreak: shouldBreakAfterResult(result),\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runDryRunRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n verbose: boolean\n): Promise<StepResult> {\n return dryRunRecipeModule({\n environment,\n options: { verbose },\n recipeModule,\n ssh,\n })\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n stats: RunStats,\n verbose: boolean,\n dryRun: boolean,\n shutdownSignal: () => NodeJS.Signals | null\n): Promise<StepResult> {\n try {\n if (dryRun) return await runDryRunRecipeModule(recipeModule, environment, ssh, verbose)\n\n // check() iterates all child modules; apply() checks them again internally via executeModules().\n startModuleSpinner(recipeModule.name)\n const checkResult = await recipeModule.check(ssh, environment)\n if (checkResult === \"ok\") {\n printModuleResult(recipeModule.name, \"ok\")\n return { env: environment, shouldBreak: false, status: \"ok\" }\n }\n\n const result = await recipeModule.apply(ssh, environment, {\n onChildStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signalHooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n verbose,\n })\n return await handleMetaAndBuildResult(ssh, environment, result)\n } catch (error) {\n return handleCaughtStepError({\n environment,\n error,\n moduleName: recipeModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\nasync function applyModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, dryRun = false, ssh, targetModule, verbose } = parameters\n const connection = targetModule.local === true ? null : ssh\n const result =\n dryRun && targetModule._applyDryRun != null\n ? await targetModule._applyDryRun(connection, currentEnvironment)\n : await targetModule.apply(connection, currentEnvironment)\n const stepResult = await handleMetaAndBuildResult(ssh, currentEnvironment, result)\n const detail = dryRun ? (result._dryRunDetail ?? \"(dry-run)\") : undefined\n printModuleResult(targetModule.name, result.status, detail)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return stepResult\n}\n\nasync function checkRegularModule(parameters: {\n env: Environment\n ssh: SshConnectionImpl\n targetModule: Module\n}): Promise<\"needs-apply\" | \"ok\"> {\n const { env, ssh, targetModule } = parameters\n const connection = targetModule.local === true ? null : ssh\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, env)\n}\n\nfunction buildDryRunChangedResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: false, status: \"changed\" }\n}\n\ntype RegularModuleArguments = {\n dryRun: boolean\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}\n\nasync function runRegularModule(parameters: RegularModuleArguments): Promise<StepResult> {\n const { dryRun, env, ssh, targetModule, verbose } = parameters\n const shutdownSignal = parameters.shutdownSignal\n\n try {\n const checkResult = await checkRegularModule({ env, ssh, targetModule })\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return { env, shouldBreak: false, status: \"ok\" }\n }\n\n if (dryRun) {\n if (shouldExecuteApplyDuringDryRun(targetModule)) {\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: true,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n }\n printModuleResult(targetModule.name, \"changed\", \"(dry-run)\")\n return buildDryRunChangedResult(env)\n }\n\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: false,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n } catch (error) {\n return handleCaughtStepError({\n environment: env,\n error,\n moduleName: targetModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\ntype LoopArguments = {\n definitionSignals?: Module[]\n dryRun: boolean\n env: Environment\n modules: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\ntype ModuleLoopState = {\n currentEnvironment: Environment\n signalsPending: boolean\n stopRun?: true\n}\n\nfunction updateLoopSignalState(input: {\n currentSignalsPending: boolean\n result: StepResult\n stats: RunStats\n}): boolean {\n if (input.result.status == null) return input.currentSignalsPending\n input.stats.update(input.result.status)\n return input.result.status === \"changed\" ? true : input.currentSignalsPending\n}\n\nfunction shouldFlushTopLevelSignals(input: {\n definitionSignals?: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: StepResult\n}): input is {\n definitionSignals: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: { flushSignals: true } & StepResult\n} {\n return (\n input.stepResult.flushSignals === true &&\n !input.dryRun &&\n input.shutdownSignal() == null &&\n input.signalsPending &&\n input.stats.failed === 0 &&\n input.definitionSignals != null\n )\n}\n\nasync function flushPendingTopLevelSignals(input: {\n currentEnvironment: Environment\n definitionSignals: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<SignalRunStatus> {\n return runSignals({\n env: input.currentEnvironment,\n shutdownSignal: input.shutdownSignal,\n signals: input.definitionSignals,\n ssh: input.ssh,\n stats: input.stats,\n verbose: input.verbose,\n })\n}\n\nfunction applyLoopResultToState(\n state: ModuleLoopState,\n result: StepResult,\n stats: RunStats\n): ModuleLoopState {\n return {\n currentEnvironment: result.env,\n signalsPending: updateLoopSignalState({\n currentSignalsPending: state.signalsPending,\n result,\n stats,\n }),\n stopRun: result.stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function flushTopLevelSignalsIfRequested(parameters: {\n definitionSignals?: Module[]\n dryRun: boolean\n loopState: ModuleLoopState\n result: StepResult\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<{ nextSignalsPending: boolean; outcome: \"break\" | \"continue\" }> {\n if (\n !shouldFlushTopLevelSignals({\n definitionSignals: parameters.definitionSignals,\n dryRun: parameters.dryRun,\n shutdownSignal: parameters.shutdownSignal,\n signalsPending: parameters.loopState.signalsPending,\n stats: parameters.stats,\n stepResult: parameters.result,\n })\n ) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n const definitionSignals = parameters.definitionSignals\n if (definitionSignals == null) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n\n const signalStatus = await flushPendingTopLevelSignals({\n currentEnvironment: parameters.loopState.currentEnvironment,\n definitionSignals,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n stats: parameters.stats,\n verbose: parameters.verbose,\n })\n return {\n nextSignalsPending: false,\n outcome: signalStatus === \"failed\" ? \"break\" : \"continue\",\n }\n}\n\nasync function createModuleStepPromise(parameters: {\n currentEnvironment: Environment\n currentModule: Module\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, currentModule, dryRun, shutdownSignal, ssh, stats, verbose } =\n parameters\n\n return isRecipe(currentModule)\n ? runRecipeModule(\n currentModule,\n currentEnvironment,\n ssh,\n stats,\n verbose,\n dryRun,\n shutdownSignal\n )\n : runRegularModule({\n dryRun,\n env: currentEnvironment,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n}\n\nasync function runModuleLoop(parameters: LoopArguments): Promise<{\n env: Environment\n signalsPending: boolean\n stopRun?: true\n}> {\n const { definitionSignals, dryRun, modules, shutdownSignal, ssh, stats, verbose } = parameters\n const loopState: ModuleLoopState = {\n currentEnvironment: parameters.env,\n signalsPending: false,\n stopRun: undefined,\n }\n\n for (const currentModule of modules) {\n // A module already running when the signal arrived completes normally\n // and its result is still counted in stats before the loop exits here.\n if (shutdownSignal() != null) break\n const stepPromise = createModuleStepPromise({\n currentEnvironment: loopState.currentEnvironment,\n currentModule,\n dryRun,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n\n // eslint-disable-next-line no-await-in-loop\n const result = await stepPromise\n\n Object.assign(loopState, applyLoopResultToState(loopState, result, stats))\n // eslint-disable-next-line no-await-in-loop\n const flushResult = await flushTopLevelSignalsIfRequested({\n definitionSignals,\n dryRun,\n loopState,\n result,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n loopState.signalsPending = flushResult.nextSignalsPending\n if (flushResult.outcome === \"break\") break\n if (result.shouldBreak) break\n }\n\n return {\n env: loopState.currentEnvironment,\n signalsPending: loopState.signalsPending,\n stopRun: loopState.stopRun,\n }\n}\n\ntype SignalArguments = {\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n signals: Module[]\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function runSignals(parameters: SignalArguments): Promise<SignalRunStatus> {\n const { env, shutdownSignal, signals, ssh, stats, verbose } = parameters\n return runSignalModules({\n environment: env,\n hooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signals,\n ssh,\n verbose,\n })\n}\n\nfunction throwIfShutdownRequested(shutdownSignal: () => NodeJS.Signals | null): void {\n const signal = shutdownSignal()\n if (signal == null) return\n throw new Error(`Bootstrap interrupted by ${signal}`)\n}\n\nasync function connectAndRegister(parameters: {\n definition: ServerDefinition\n options: RunOptions\n promptAbortSignal: AbortSignal\n setSsh: (c: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}): Promise<SshConnectionImpl> {\n const { definition, options, promptAbortSignal, setSsh, shutdownSignal } = parameters\n const sshConfig = {\n ...definition.ssh,\n ports: [...definition.ssh.ports],\n ...(options.reconnectTimeout == null ? {} : { reconnectTimeout: options.reconnectTimeout }),\n }\n const ssh = new SshConnectionImpl(definition.host, sshConfig)\n setSsh(ssh)\n throwIfShutdownRequested(shutdownSignal)\n await ssh.connect({ abortSignal: promptAbortSignal })\n throwIfShutdownRequested(shutdownSignal)\n return ssh\n}\n\ntype ExecuteRunArguments = {\n definition: ServerDefinition\n dryRun: boolean\n environment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function executeRun(parameters: ExecuteRunArguments): Promise<void> {\n const { definition, dryRun, environment, shutdownSignal, ssh, stats, verbose } = parameters\n\n printRecipeHeader(definition.name)\n const loopResult = await runModuleLoop({\n definitionSignals: definition.signals,\n dryRun,\n env: environment,\n modules: definition.run,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n const finalEnvironment = loopResult.env\n\n if (\n !dryRun &&\n shutdownSignal() == null &&\n loopResult.signalsPending &&\n stats.failed === 0 &&\n definition.signals != null\n )\n await runSignals({\n env: finalEnvironment,\n shutdownSignal,\n signals: definition.signals,\n ssh,\n stats,\n verbose,\n })\n\n printSummary(stats)\n}\n\nfunction rethrowIfNotShutdown(error: unknown, shutdownSignal: () => NodeJS.Signals | null): void {\n if (shutdownSignal() == null) throw error\n}\n\nexport async function runPlaybook(\n definition: ServerDefinition,\n options: RunOptions = {}\n): Promise<void> {\n const { dryRun = false, verbose = false } = options\n const environment = await initializeEnvironment(options, definition)\n const { handleShutdownSignal, promptAbortSignal, setSsh, shutdownSignal } =\n setupShutdownHandlers()\n const stats = new RunStats()\n let ssh: SshConnectionImpl | undefined\n\n printRunContext({\n dryRun,\n host: definition.host,\n name: definition.name,\n ports: definition.ssh.ports,\n })\n\n // No catch block: connect errors propagate to cli.ts, which prints them and exits with code 2.\n try {\n ssh = await connectAndRegister({\n definition,\n options,\n promptAbortSignal,\n setSsh,\n shutdownSignal,\n })\n await executeRun({ definition, dryRun, environment, shutdownSignal, ssh, stats, verbose })\n } catch (error) {\n rethrowIfNotShutdown(error, shutdownSignal)\n } finally {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const)\n process.removeListener(signal, handleShutdownSignal)\n ssh?.disconnect()\n }\n\n resolveExitCode(shutdownSignal(), stats)\n}\n"],"mappings":"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","names":["shouldExecuteApplyDuringDryRun"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/dryRunRecipe.ts","../src/runnerHelpers.ts","../src/runner.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport { realpathSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { inspect } from \"node:util\"\nimport pc from \"picocolors\"\n\nimport type { Environment, ServerDefinition } from \"./types.js\"\n\nimport { printCliHeader } from \"./output.js\"\nimport { runPlaybook } from \"./runner.js\"\nimport { collectSshConfigErrors } from \"./serverDefinitionValidation.js\"\n\ndeclare const PACKAGE_DISPLAY_VERSION: string\n\nconst SECONDS_TO_MS = 1000\nconst DEFAULT_RECONNECT_TIMEOUT_SECONDS = 300\nconst ENVIRONMENT_KEY_PATTERN = /^[A-Za-z_]\\w*$/v\nconst FIRST_RUN_ENV_NAME = \"PARATIX_FIRST_RUN\"\n\n/**\n * Type guard that checks whether `value` has the shape of a\n * {@link ServerDefinition} — an object with a non-empty string `name`,\n * a non-empty string `host`, a valid `ssh` config, and a non-empty `run` array.\n *\n * @param value - The value to inspect.\n * @returns `true` when `value` satisfies the structural requirements of `ServerDefinition`.\n */\nexport function isServerDefinitionLike(value: unknown): value is ServerDefinition {\n return collectDefinitionErrors(value).length === 0\n}\n\n/** Descriptor for a property validation check. */\ntype PropertyCheck = {\n /** The key to look up in the object. */\n key: string\n /** The label to use in error messages (defaults to `key`). */\n label?: string\n}\n\n/**\n * Validates that a required string property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectStringErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected string)`)\n } else if (typeof object[check.key] !== \"string\") {\n errors.push(`Invalid property '${name}' (expected string, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof check above\n } else if ((object[check.key] as string).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\n/**\n * Validates that a required array property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectArrayErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected array)`)\n } else if (!Array.isArray(object[check.key])) {\n errors.push(`Invalid property '${name}' (expected array, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by Array.isArray check above\n } else if ((object[check.key] as unknown[]).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\nfunction collectSshErrors(value: Record<string, unknown>, errors: string[]): void {\n if (!(\"ssh\" in value)) {\n errors.push(\"Missing property 'ssh' (expected object)\")\n return\n }\n errors.push(...collectSshConfigErrors(value.ssh))\n}\n\n/**\n * Collects human-readable error messages for every property of `value` that\n * does not conform to the `ServerDefinition` shape.\n *\n * @param value - The value to validate.\n * @returns An array of error strings, empty when `value` is structurally valid.\n */\nexport function collectDefinitionErrors(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== \"object\" || value === null) {\n errors.push(\"Export is not an object\")\n return errors\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof/null checks above\n const object = value as Record<string, unknown>\n collectStringErrors(object, { key: \"name\" }, errors)\n collectStringErrors(object, { key: \"host\" }, errors)\n collectSshErrors(object, errors)\n collectArrayErrors(object, { key: \"run\" }, errors)\n return errors\n}\n\n/**\n * Assertion function that ensures `value` is a valid {@link ServerDefinition}.\n *\n * When validation fails, all collected errors are printed to stderr and the\n * process exits with code `2`, so callers can treat the function as a\n * narrowing assertion without additional error handling.\n *\n * @param value - The value to validate.\n * @param file - Path of the file that exported `value`, used in the error message.\n */\nfunction validateServerDefinition(value: unknown, file: string): asserts value is ServerDefinition {\n if (isServerDefinitionLike(value)) {\n return\n }\n const errors = collectDefinitionErrors(value)\n const details = errors.map((entry) => ` - ${entry}`).join(\"\\n\")\n console.error(\n `Error: ${file} does not export a valid ServerDefinition.\\n${details}\\n Use the server() helper to create a valid definition.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n}\n\n/**\n * Returns a human-readable string for any caught value.\n * Uses `.message` for `Error` instances and falls back to the string\n * representation for primitives. For plain objects that have no meaningful\n * `toString`, the JSON representation is used instead of `[object Object]`.\n *\n * @param value - The value to convert to a string.\n * @returns A human-readable string representation of `value`.\n */\nfunction errorToString(value: unknown): string {\n if (value instanceof Error) return value.message\n if (typeof value === \"object\" && value !== null) {\n try {\n return JSON.stringify(value)\n } catch {\n return inspect(value, { breakLength: Infinity, depth: 5 })\n }\n }\n return String(value)\n}\n\n/**\n * Walks the cause chain of `error` and prints each cause to stderr.\n *\n * @param error - The root `Error` whose `.cause` chain should be printed.\n */\nfunction printCauseChain(error: Error): void {\n let cause = error.cause\n while (cause != null) {\n console.error(` Caused by: ${errorToString(cause)}`)\n cause = cause instanceof Error ? cause.cause : undefined\n }\n}\n\n/**\n * Prints a structured error message to stderr, including the cause chain and\n * optionally the full stack trace when `verbose` is `true`.\n *\n * @param error - The caught value (may be any type).\n * @param verbose - When `true`, the stack trace of `error` is printed.\n */\nexport function printExceptionError(error: unknown, verbose: boolean): void {\n console.error(`Error: ${errorToString(error)}`)\n\n if (error instanceof Error) {\n printCauseChain(error)\n\n if (verbose && error.stack != null) {\n console.error(`\\n${error.stack}`)\n }\n }\n}\n\n/**\n * Handles a failed attempt to load the `tsx` runtime.\n *\n * When the failed import is for a TypeScript file (`.ts`, `.mts`, `.cts`), a\n * clear error message is printed to stderr and the process exits with code 2.\n * For JavaScript files the failure is silently ignored because `tsx` is not\n * required there.\n *\n * @param filePath - The resolved path of the playbook file being loaded.\n */\nexport function handleTsxLoadFailure(filePath: string): void {\n if (/\\.[cm]?ts$/v.test(filePath)) {\n console.error(\n `${pc.red(\"Error:\")} tsx is required to run TypeScript playbooks but could not be loaded.\\n` +\n ` Install it with: ${pc.bold(\"npm install -g tsx\")} or add it as a devDependency.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n}\n\n/**\n * Returns whether the current CLI module is the direct process entrypoint.\n *\n * This resolves symlinks on both sides so pnpm-style executable shims and\n * symlinked `node_modules` entries still count as direct execution.\n *\n * @param moduleUrl - The current module URL, usually `import.meta.url`.\n * @param candidateEntryScript - The process entry script path, usually `process.argv[1]`.\n * @returns `true` when both paths resolve to the same file on disk.\n */\nexport function isDirectCliExecution(moduleUrl: string, candidateEntryScript?: string): boolean {\n if (candidateEntryScript == null) {\n return false\n }\n\n return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(candidateEntryScript)\n}\n\nexport function applyCliEnvironmentOverrides(\n environment: Environment,\n options: { firstRun: boolean }\n): Environment {\n if (!options.firstRun) return environment\n return { ...environment, [FIRST_RUN_ENV_NAME]: \"true\" }\n}\n\nexport function applyCliProcessEnvironment(options: { firstRun: boolean }): void {\n if (!options.firstRun) return\n process.env[FIRST_RUN_ENV_NAME] = \"true\"\n}\n\nexport async function loadServerDefinitionFromFile(\n file: string,\n options: { firstRun: boolean }\n): Promise<ServerDefinition> {\n const filePath = resolve(file)\n const fileUrl = pathToFileURL(filePath).href\n\n applyCliProcessEnvironment(options)\n\n // Register tsx for TypeScript imports\n await import(\"tsx/esm/api\")\n .then((tsx: { register: () => void }) => {\n tsx.register()\n })\n .catch(() => {\n handleTsxLoadFailure(filePath)\n })\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Dynamic import has unknown shape\n const imported = await import(fileUrl)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-type-assertion -- Accessing .default on dynamic import\n const definition = (imported.default ?? imported) as ServerDefinition\n\n validateServerDefinition(definition, filePath)\n return definition\n}\n\nconst program = new Command()\n\nprogram\n .name(\"paratix\")\n .description(\"Idempotent VPS setup tool in TypeScript\")\n .version(PACKAGE_DISPLAY_VERSION)\n\nprogram\n .command(\"apply <file>\")\n .description(\"Apply a server definition\")\n .option(\n \"--dry-run\",\n \"Only check, do not apply. Some modules validate prospective config but cannot verify runtime restarts.\",\n false\n )\n .option(\"--env <key=value...>\", \"Set env values\", collectEnvironment, {})\n .option(\"--env-file <path>\", \"Load dotenv file\")\n .option(\"--first-run\", \"Set PARATIX_FIRST_RUN=true before loading the playbook\", false)\n .option(\n \"--reconnect-timeout <seconds>\",\n \"SSH reconnect timeout\",\n parsePositiveNumber,\n DEFAULT_RECONNECT_TIMEOUT_SECONDS\n )\n .option(\"--verbose\", \"Show full stack traces on error\", false)\n .action(async (file: string, options: Record<string, unknown>) => {\n try {\n printCliHeader(PACKAGE_DISPLAY_VERSION)\n const environmentOverrides = applyCliEnvironmentOverrides(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n options.env as Environment,\n {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n }\n )\n const definition = await loadServerDefinitionFromFile(file, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n })\n\n await runPlaybook(definition, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n dryRun: options.dryRun as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n envFile: options.envFile as string | undefined,\n envOverrides: environmentOverrides,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n reconnectTimeout: (options.reconnectTimeout as number) * SECONDS_TO_MS,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n verbose: options.verbose as boolean,\n })\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n printExceptionError(error, options.verbose as boolean)\n // eslint-disable-next-line node/no-process-exit\n process.exit(process.exitCode ?? 2)\n }\n })\n\nexport function parsePositiveNumber(value: string): number {\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.error(`Invalid --reconnect-timeout value: ${value} (expected a positive number)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return parsed\n}\n\nexport function collectEnvironment(\n value: string,\n previous: Record<string, string>\n): Record<string, string> {\n const eqIndex = value.indexOf(\"=\")\n if (eqIndex === -1) {\n console.error(`Invalid --env format: ${value} (expected key=value)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n const key = value.slice(0, eqIndex)\n const value_ = value.slice(eqIndex + 1)\n if (!ENVIRONMENT_KEY_PATTERN.test(key)) {\n console.error(\n `Invalid --env name: ${key === \"\" ? \"(empty)\" : key} (expected [A-Za-z_][A-Za-z0-9_]*)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return { ...previous, [key]: value_ }\n}\n\n// Only parse when executed directly, not when imported (e.g. in tests)\nconst entryScript = process.argv[1]\nif (isDirectCliExecution(import.meta.url, entryScript)) {\n await program.parseAsync()\n}\n","import type { RecipeModule } from \"./recipe.js\"\nimport type { SshConnectionImpl } from \"./ssh.js\"\nimport type { Environment, ModuleStatus } from \"./types.js\"\n\nimport { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\n\ntype StepResult = { env: Environment; shouldBreak: boolean; status?: ModuleStatus; stopRun?: true }\n\nfunction shouldExecuteApplyDuringDryRun(module: RecipeModule[\"_modules\"][number]): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function executeDryRunBlockingModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n connection: null | SshConnectionImpl\n environment: Environment\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, connection, environment, verbose } = parameters\n startModuleSpinner(childModule.name)\n const result =\n childModule._applyDryRun == null\n ? await childModule.apply(connection, environment)\n : await childModule._applyDryRun(connection, environment)\n const nextEnvironment =\n result.meta == null ? environment : await mergeEnvironmentFromMeta(environment, result.meta)\n printModuleResult(childModule.name, result.status, result._dryRunDetail ?? \"(dry-run)\")\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return {\n env: nextEnvironment,\n shouldBreak: result.status === \"failed\" || result._stopRun === true,\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\nasync function executeDryRunChildModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n environment: Environment\n ssh: SshConnectionImpl\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, environment, ssh, verbose } = parameters\n const connection = childModule.local === true ? null : ssh\n startModuleSpinner(childModule.name)\n const checkResult = await childModule.check(connection, environment)\n if (checkResult !== \"ok\" && shouldExecuteApplyDuringDryRun(childModule)) {\n return executeDryRunBlockingModule({ childModule, connection, environment, verbose })\n }\n const status = checkResult === \"ok\" ? \"ok\" : \"changed\"\n const suffix = checkResult === \"ok\" ? undefined : \"(dry-run)\"\n printModuleResult(childModule.name, status, suffix)\n return { env: environment, shouldBreak: false, status }\n}\n\nexport async function dryRunRecipeModule(parameters: {\n environment: Environment\n options?: {\n verbose?: boolean\n }\n recipeModule: RecipeModule\n ssh: SshConnectionImpl\n}): Promise<StepResult> {\n return withRecipeOutputScope(async () => {\n const { environment, recipeModule, ssh } = parameters\n printRecipeHeader(recipeModule.name)\n let aggregatedStatus: \"changed\" | \"ok\" = \"ok\"\n let currentEnvironment = environment\n const verbose = parameters.options?.verbose ?? false\n\n for (const childModule of recipeModule._modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await executeDryRunChildModule({\n childModule,\n environment: currentEnvironment,\n ssh,\n verbose,\n })\n if (result.shouldBreak) return result\n currentEnvironment = result.env\n if (result.status === \"changed\") aggregatedStatus = \"changed\"\n }\n\n return { env: currentEnvironment, shouldBreak: false, status: aggregatedStatus }\n })\n}\n","const SIGNAL_EXIT_BASE = 128\nconst SIGTERM_NUMBER = 15\nconst SIGINT_NUMBER = 2\n\n/**\n * Returns the conventional exit code for a termination signal.\n * Follows the POSIX convention of 128 + signal number.\n *\n * @param signal - The received signal (`SIGTERM` or `SIGINT`).\n * @returns The exit code to use when the process is terminated by `signal`.\n */\nexport function signalExitCode(signal: NodeJS.Signals): number {\n return SIGNAL_EXIT_BASE + (signal === \"SIGTERM\" ? SIGTERM_NUMBER : SIGINT_NUMBER)\n}\n\n/**\n * Sets `process.exitCode` based on the run outcome.\n * A received shutdown signal takes precedence over module failures.\n *\n * @param shutdownSignal - The signal that interrupted the run, or `null` if the\n * run completed normally.\n * @param stats - Accumulated run statistics used to detect module failures.\n * @param stats.failed - Number of modules that failed.\n */\nexport function resolveExitCode(\n shutdownSignal: NodeJS.Signals | null,\n stats: { failed: number }\n): void {\n if (shutdownSignal != null) {\n process.exitCode = signalExitCode(shutdownSignal)\n } else if (stats.failed > 0) {\n process.exitCode = 1\n }\n}\n","/* eslint-disable max-lines -- central runner orchestration stays intentionally co-located */\nimport type { RecipeModule } from \"./recipe.js\"\nimport type {\n Environment,\n Module,\n ModuleResult,\n ModuleStatus,\n OrchestrationStep,\n ServerDefinition,\n} from \"./types.js\"\n\nimport { dryRunRecipeModule } from \"./dryRunRecipe.js\"\nimport { loadDotEnvironment, mergeEnvironment } from \"./environment.js\"\nimport {\n assertValidModuleMetaEntries,\n isSshdPortMetaEntry,\n isSystemHostMetaEntry,\n isSystemRebootMetaEntry,\n mergeEnvironmentFromMeta,\n} from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRunContext,\n printSummary,\n startModuleSpinner,\n} from \"./output.js\"\nimport { resolveExitCode, signalExitCode } from \"./runnerHelpers.js\"\nimport { runSignalModules, type SignalRunStatus } from \"./signalOrchestration.js\"\nimport { SshConnectionImpl } from \"./ssh.js\"\n\n/** Holds the shutdown listener, SSH setter, and getter for the first received signal. */\ntype ShutdownState = {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n promptAbortSignal: AbortSignal\n setSsh: (connection: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}\n\n/**\n * Registers shutdown handlers.\n * @returns The listener state and first-signal getter.\n */\nfunction setupShutdownHandlers(): ShutdownState {\n let receivedSignal: NodeJS.Signals | null = null\n let ssh: null | SshConnectionImpl = null\n const promptAbortController = new AbortController()\n\n const handleShutdownSignal = (signal: NodeJS.Signals): void => {\n if (receivedSignal != null) {\n // eslint-disable-next-line node/no-process-exit\n process.exit(signalExitCode(signal))\n }\n receivedSignal = signal\n promptAbortController.abort(new Error(`Terminal prompt interrupted by ${signal}`))\n console.error(`\\nReceived ${signal}, shutting down…`)\n ssh?.disconnect()\n }\n\n process.on(\"SIGINT\", handleShutdownSignal)\n process.on(\"SIGTERM\", handleShutdownSignal)\n\n return {\n handleShutdownSignal,\n promptAbortSignal: promptAbortController.signal,\n setSsh: (connection: SshConnectionImpl) => {\n ssh = connection\n },\n shutdownSignal: () => receivedSignal,\n }\n}\n\nexport type RunOptions = {\n /** When `true`, modules report what would change without applying anything. Defaults to `false`. */\n dryRun?: boolean\n /** Path to a `.env` file whose variables are merged into the run environment. */\n envFile?: string\n /** Additional environment variables that override values from `envFile` and the server definition. */\n envOverrides?: Environment\n /** Custom reconnect timeout in milliseconds passed to SSH, overriding the config default. */\n reconnectTimeout?: number\n /** When `true`, failed commands print full stdout/stderr in addition to the summary error. */\n verbose?: boolean\n}\n\nclass RunStats {\n public changed = 0\n public failed = 0\n public ok = 0\n public signals = 0\n public skipped = 0\n\n public incrementSignals(): void {\n this.signals++\n }\n\n public update(status: ModuleStatus): void {\n switch (status) {\n case \"changed\": {\n this.changed++\n break\n }\n case \"failed\": {\n this.failed++\n break\n }\n case \"ok\": {\n this.ok++\n break\n }\n case \"skipped\": {\n this.skipped++\n break\n }\n }\n }\n}\n\ntype StepResult = {\n env: Environment\n flushSignals?: true\n shouldBreak: boolean\n status?: ModuleStatus\n stopRun?: true\n}\n\nfunction interruptedStepResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: true }\n}\n\nfunction shouldBreakAfterResult(result: Pick<ModuleResult, \"_stopRun\" | \"status\">): boolean {\n return result.status === \"failed\" || result._stopRun === true\n}\n\nfunction interruptedBeforeApply(\n environment: Environment,\n shutdownSignal: () => NodeJS.Signals | null\n): StepResult | undefined {\n if (shutdownSignal() == null) return undefined\n return interruptedStepResult(environment)\n}\n\nasync function applyCheckedModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const interrupted = interruptedBeforeApply(\n parameters.currentEnvironment,\n parameters.shutdownSignal\n )\n if (interrupted != null) return interrupted\n\n return applyModule({\n currentEnvironment: parameters.currentEnvironment,\n dryRun: parameters.dryRun,\n ssh: parameters.ssh,\n targetModule: parameters.targetModule,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldExecuteApplyDuringDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nfunction handleCaughtStepError(parameters: {\n environment: Environment\n error: unknown\n moduleName: string\n shutdownSignal: () => NodeJS.Signals | null\n verbose: boolean\n}): StepResult {\n if (parameters.shutdownSignal() != null) {\n return interruptedStepResult(parameters.environment)\n }\n printModuleResult(parameters.moduleName, \"failed\")\n printCommandFailure(parameters.error, parameters.verbose)\n return { env: parameters.environment, shouldBreak: true, status: \"failed\" }\n}\n\nfunction isRecipe(target: Module): target is RecipeModule {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- RecipeModule uses _isRecipe as discriminator\n return \"_isRecipe\" in target && (target as RecipeModule)._isRecipe\n}\n\nasync function initializeEnvironment(\n options: RunOptions,\n definition: ServerDefinition\n): Promise<Environment> {\n const dotEnvironment =\n options.envFile == null ? undefined : await loadDotEnvironment(options.envFile)\n return mergeEnvironment({}, dotEnvironment, definition.env, options.envOverrides)\n}\n\nasync function handlePortChange(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n const portEntry = metaEntries?.find((entry) => isSshdPortMetaEntry(entry))\n if (portEntry == null) return\n\n const newPort = portEntry.port\n ssh.addPort(newPort)\n\n // Skip reconnect when a reboot is pending — the reboot handler will\n // reconnect on all registered ports (including the newly added one).\n if (metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false) return\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(\n `Failed to reconnect on port ${newPort} after port change: ${String(error)}. ` +\n `Verify that port ${newPort} is allowed by the server's firewall rules.`\n )\n throw error\n }\n}\n\nasync function handleReboot(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n if (!(metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false)) return\n\n const hostEntry = metaEntries?.find((entry) => isSystemHostMetaEntry(entry))\n if (hostEntry != null) {\n ssh.updateHost(hostEntry.host)\n }\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(`Failed to reconnect after reboot: ${String(error)}`)\n throw error\n }\n}\n\nasync function applyRunnerControlPlaneMeta(\n ssh: SshConnectionImpl,\n step: Pick<OrchestrationStep, \"meta\">\n): Promise<void> {\n if (step.meta == null) return\n assertValidModuleMetaEntries(step.meta)\n await handlePortChange(ssh, step.meta)\n await handleReboot(ssh, step.meta)\n}\n\nasync function handleMetaAndBuildResult(\n ssh: SshConnectionImpl,\n environment: Environment,\n result: ModuleResult\n): Promise<StepResult> {\n let currentEnvironment = environment\n\n if (result.meta != null) {\n currentEnvironment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n await applyRunnerControlPlaneMeta(ssh, { meta: result.meta })\n }\n\n return {\n env: currentEnvironment,\n flushSignals: result._flushSignals,\n shouldBreak: shouldBreakAfterResult(result),\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runDryRunRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n verbose: boolean\n): Promise<StepResult> {\n return dryRunRecipeModule({\n environment,\n options: { verbose },\n recipeModule,\n ssh,\n })\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n stats: RunStats,\n verbose: boolean,\n dryRun: boolean,\n shutdownSignal: () => NodeJS.Signals | null\n): Promise<StepResult> {\n try {\n if (dryRun) return await runDryRunRecipeModule(recipeModule, environment, ssh, verbose)\n\n // check() iterates all child modules; apply() checks them again internally via executeModules().\n startModuleSpinner(recipeModule.name)\n const checkResult = await recipeModule.check(ssh, environment)\n if (checkResult === \"ok\") {\n printModuleResult(recipeModule.name, \"ok\")\n return { env: environment, shouldBreak: false, status: \"ok\" }\n }\n\n const result = await recipeModule.apply(ssh, environment, {\n onChildStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signalHooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n verbose,\n })\n return await handleMetaAndBuildResult(ssh, environment, result)\n } catch (error) {\n return handleCaughtStepError({\n environment,\n error,\n moduleName: recipeModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\nasync function applyModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, dryRun = false, ssh, targetModule, verbose } = parameters\n const connection = targetModule.local === true ? null : ssh\n const result =\n dryRun && targetModule._applyDryRun != null\n ? await targetModule._applyDryRun(connection, currentEnvironment)\n : await targetModule.apply(connection, currentEnvironment)\n const stepResult = await handleMetaAndBuildResult(ssh, currentEnvironment, result)\n const detail = dryRun ? (result._dryRunDetail ?? \"(dry-run)\") : result.detail\n printModuleResult(targetModule.name, result.status, detail)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return stepResult\n}\n\nasync function checkRegularModule(parameters: {\n env: Environment\n ssh: SshConnectionImpl\n targetModule: Module\n}): Promise<\"needs-apply\" | \"ok\"> {\n const { env, ssh, targetModule } = parameters\n const connection = targetModule.local === true ? null : ssh\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, env)\n}\n\nfunction buildDryRunChangedResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: false, status: \"changed\" }\n}\n\ntype RegularModuleArguments = {\n dryRun: boolean\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}\n\nasync function runRegularModule(parameters: RegularModuleArguments): Promise<StepResult> {\n const { dryRun, env, ssh, targetModule, verbose } = parameters\n const shutdownSignal = parameters.shutdownSignal\n\n try {\n const checkResult = await checkRegularModule({ env, ssh, targetModule })\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return { env, shouldBreak: false, status: \"ok\" }\n }\n\n if (dryRun) {\n if (shouldExecuteApplyDuringDryRun(targetModule)) {\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: true,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n }\n printModuleResult(targetModule.name, \"changed\", \"(dry-run)\")\n return buildDryRunChangedResult(env)\n }\n\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: false,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n } catch (error) {\n return handleCaughtStepError({\n environment: env,\n error,\n moduleName: targetModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\ntype LoopArguments = {\n definitionSignals?: Module[]\n dryRun: boolean\n env: Environment\n modules: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\ntype ModuleLoopState = {\n currentEnvironment: Environment\n signalsPending: boolean\n stopRun?: true\n}\n\nfunction updateLoopSignalState(input: {\n currentSignalsPending: boolean\n result: StepResult\n stats: RunStats\n}): boolean {\n if (input.result.status == null) return input.currentSignalsPending\n input.stats.update(input.result.status)\n return input.result.status === \"changed\" ? true : input.currentSignalsPending\n}\n\nfunction shouldFlushTopLevelSignals(input: {\n definitionSignals?: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: StepResult\n}): input is {\n definitionSignals: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: { flushSignals: true } & StepResult\n} {\n return (\n input.stepResult.flushSignals === true &&\n !input.dryRun &&\n input.shutdownSignal() == null &&\n input.signalsPending &&\n input.stats.failed === 0 &&\n input.definitionSignals != null\n )\n}\n\nasync function flushPendingTopLevelSignals(input: {\n currentEnvironment: Environment\n definitionSignals: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<SignalRunStatus> {\n return runSignals({\n env: input.currentEnvironment,\n shutdownSignal: input.shutdownSignal,\n signals: input.definitionSignals,\n ssh: input.ssh,\n stats: input.stats,\n verbose: input.verbose,\n })\n}\n\nfunction applyLoopResultToState(\n state: ModuleLoopState,\n result: StepResult,\n stats: RunStats\n): ModuleLoopState {\n return {\n currentEnvironment: result.env,\n signalsPending: updateLoopSignalState({\n currentSignalsPending: state.signalsPending,\n result,\n stats,\n }),\n stopRun: result.stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function flushTopLevelSignalsIfRequested(parameters: {\n definitionSignals?: Module[]\n dryRun: boolean\n loopState: ModuleLoopState\n result: StepResult\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<{ nextSignalsPending: boolean; outcome: \"break\" | \"continue\" }> {\n if (\n !shouldFlushTopLevelSignals({\n definitionSignals: parameters.definitionSignals,\n dryRun: parameters.dryRun,\n shutdownSignal: parameters.shutdownSignal,\n signalsPending: parameters.loopState.signalsPending,\n stats: parameters.stats,\n stepResult: parameters.result,\n })\n ) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n const definitionSignals = parameters.definitionSignals\n if (definitionSignals == null) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n\n const signalStatus = await flushPendingTopLevelSignals({\n currentEnvironment: parameters.loopState.currentEnvironment,\n definitionSignals,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n stats: parameters.stats,\n verbose: parameters.verbose,\n })\n return {\n nextSignalsPending: false,\n outcome: signalStatus === \"failed\" ? \"break\" : \"continue\",\n }\n}\n\nasync function createModuleStepPromise(parameters: {\n currentEnvironment: Environment\n currentModule: Module\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, currentModule, dryRun, shutdownSignal, ssh, stats, verbose } =\n parameters\n\n return isRecipe(currentModule)\n ? runRecipeModule(\n currentModule,\n currentEnvironment,\n ssh,\n stats,\n verbose,\n dryRun,\n shutdownSignal\n )\n : runRegularModule({\n dryRun,\n env: currentEnvironment,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n}\n\nasync function runModuleLoop(parameters: LoopArguments): Promise<{\n env: Environment\n signalsPending: boolean\n stopRun?: true\n}> {\n const { definitionSignals, dryRun, modules, shutdownSignal, ssh, stats, verbose } = parameters\n const loopState: ModuleLoopState = {\n currentEnvironment: parameters.env,\n signalsPending: false,\n stopRun: undefined,\n }\n\n for (const currentModule of modules) {\n // A module already running when the signal arrived completes normally\n // and its result is still counted in stats before the loop exits here.\n if (shutdownSignal() != null) break\n const stepPromise = createModuleStepPromise({\n currentEnvironment: loopState.currentEnvironment,\n currentModule,\n dryRun,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n\n // eslint-disable-next-line no-await-in-loop\n const result = await stepPromise\n\n Object.assign(loopState, applyLoopResultToState(loopState, result, stats))\n // eslint-disable-next-line no-await-in-loop\n const flushResult = await flushTopLevelSignalsIfRequested({\n definitionSignals,\n dryRun,\n loopState,\n result,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n loopState.signalsPending = flushResult.nextSignalsPending\n if (flushResult.outcome === \"break\") break\n if (result.shouldBreak) break\n }\n\n return {\n env: loopState.currentEnvironment,\n signalsPending: loopState.signalsPending,\n stopRun: loopState.stopRun,\n }\n}\n\ntype SignalArguments = {\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n signals: Module[]\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function runSignals(parameters: SignalArguments): Promise<SignalRunStatus> {\n const { env, shutdownSignal, signals, ssh, stats, verbose } = parameters\n return runSignalModules({\n environment: env,\n hooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signals,\n ssh,\n verbose,\n })\n}\n\nfunction throwIfShutdownRequested(shutdownSignal: () => NodeJS.Signals | null): void {\n const signal = shutdownSignal()\n if (signal == null) return\n throw new Error(`Bootstrap interrupted by ${signal}`)\n}\n\nasync function connectAndRegister(parameters: {\n definition: ServerDefinition\n options: RunOptions\n promptAbortSignal: AbortSignal\n setSsh: (c: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}): Promise<SshConnectionImpl> {\n const { definition, options, promptAbortSignal, setSsh, shutdownSignal } = parameters\n const sshConfig = {\n ...definition.ssh,\n ports: [...definition.ssh.ports],\n ...(options.reconnectTimeout == null ? {} : { reconnectTimeout: options.reconnectTimeout }),\n }\n const ssh = new SshConnectionImpl(definition.host, sshConfig)\n setSsh(ssh)\n throwIfShutdownRequested(shutdownSignal)\n await ssh.connect({ abortSignal: promptAbortSignal })\n throwIfShutdownRequested(shutdownSignal)\n return ssh\n}\n\ntype ExecuteRunArguments = {\n definition: ServerDefinition\n dryRun: boolean\n environment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function executeRun(parameters: ExecuteRunArguments): Promise<void> {\n const { definition, dryRun, environment, shutdownSignal, ssh, stats, verbose } = parameters\n\n printRecipeHeader(definition.name)\n const loopResult = await runModuleLoop({\n definitionSignals: definition.signals,\n dryRun,\n env: environment,\n modules: definition.run,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n const finalEnvironment = loopResult.env\n\n if (\n !dryRun &&\n shutdownSignal() == null &&\n loopResult.signalsPending &&\n stats.failed === 0 &&\n definition.signals != null\n )\n await runSignals({\n env: finalEnvironment,\n shutdownSignal,\n signals: definition.signals,\n ssh,\n stats,\n verbose,\n })\n\n printSummary(stats)\n}\n\nfunction rethrowIfNotShutdown(error: unknown, shutdownSignal: () => NodeJS.Signals | null): void {\n if (shutdownSignal() == null) throw error\n}\n\nexport async function runPlaybook(\n definition: ServerDefinition,\n options: RunOptions = {}\n): Promise<void> {\n const { dryRun = false, verbose = false } = options\n const environment = await initializeEnvironment(options, definition)\n const { handleShutdownSignal, promptAbortSignal, setSsh, shutdownSignal } =\n setupShutdownHandlers()\n const stats = new RunStats()\n let ssh: SshConnectionImpl | undefined\n\n printRunContext({\n dryRun,\n host: definition.host,\n name: definition.name,\n ports: definition.ssh.ports,\n })\n\n // No catch block: connect errors propagate to cli.ts, which prints them and exits with code 2.\n try {\n ssh = await connectAndRegister({\n definition,\n options,\n promptAbortSignal,\n setSsh,\n shutdownSignal,\n })\n await executeRun({ definition, dryRun, environment, shutdownSignal, ssh, stats, verbose })\n } catch (error) {\n rethrowIfNotShutdown(error, shutdownSignal)\n } finally {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const)\n process.removeListener(signal, handleShutdownSignal)\n ssh?.disconnect()\n }\n\n resolveExitCode(shutdownSignal(), stats)\n}\n"],"mappings":"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","names":["shouldExecuteApplyDuringDryRun"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, d as SshdPortMetaEntry, e as SystemHostMetaEntry, f as SystemRebootMetaEntry, g as ModuleResult, h as ExecResult, i as ModuleStatus, j as SshConnection, O as OrchestrationStep, S as ServerDefinition } from './types-
|
|
2
|
-
export { k as EnvironmentValue, l as ExecOptions, N as NEEDS_APPLY, m as SshConfig } from './types-
|
|
3
|
-
export { a as apt, b as archive, c as command, d as compose, e as cron, f as download, g as file, h as git, i as group, j as hostname, m as mount, n as net, o as op, p as package, q as quadlet, r as releaseUpgrade, k as rsync, s as script, l as service, t as ssh, u as sshd, v as sysctl, w as system, x as systemd, y as ufw, z as user } from './user-
|
|
1
|
+
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, d as SshdPortMetaEntry, e as SystemHostMetaEntry, f as SystemRebootMetaEntry, g as ModuleResult, h as ExecResult, i as ModuleStatus, j as SshConnection, O as OrchestrationStep, S as ServerDefinition } from './types-Cl2Muw1x.js';
|
|
2
|
+
export { k as EnvironmentValue, l as ExecOptions, N as NEEDS_APPLY, m as SshConfig } from './types-Cl2Muw1x.js';
|
|
3
|
+
export { a as apt, b as archive, c as command, d as compose, e as cron, f as download, g as file, h as git, i as group, j as hostname, m as mount, n as net, o as op, p as package, q as quadlet, r as releaseUpgrade, k as rsync, s as script, l as service, t as ssh, u as sshd, v as sysctl, w as system, x as systemd, y as ufw, z as user } from './user-BJMqDePy.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Fail the run if a condition on the current env is not satisfied.
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
systemd,
|
|
40
40
|
ufw,
|
|
41
41
|
user
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-D4CS2GCH.js";
|
|
43
43
|
import {
|
|
44
44
|
CommandError,
|
|
45
45
|
assertValidModuleMetaEntries,
|
|
@@ -384,10 +384,10 @@ function isRecipeModuleLike(module) {
|
|
|
384
384
|
}
|
|
385
385
|
function printRecipeChildResult(module, result) {
|
|
386
386
|
if (isRecipeModuleLike(module)) {
|
|
387
|
-
printRecipeModuleResult(module.name, result.status);
|
|
387
|
+
printRecipeModuleResult(module.name, result.status, result.detail);
|
|
388
388
|
return;
|
|
389
389
|
}
|
|
390
|
-
printModuleResult(module.name, result.status);
|
|
390
|
+
printModuleResult(module.name, result.status, result.detail);
|
|
391
391
|
}
|
|
392
392
|
function applyRecipeStepToState(state, step, preserveControlPlaneMeta) {
|
|
393
393
|
let stepMeta;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/conditionalModules.ts","../src/builtins.ts","../src/recipe.ts","../src/server.ts"],"sourcesContent":["import { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { detectPackageManager, isPackageInstalled } from \"./modules/package.js\"\nimport { shellQuote } from \"./ssh.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"./types.js\"\n\ntype ConditionalApplyState = {\n environment: Environment\n flushSignals?: true\n meta: ModuleMetaEntry[]\n status: \"changed\" | \"ok\" | \"skipped\"\n stopRun?: true\n}\n\nfunction createConditionalApplyState(environment: Environment): ConditionalApplyState {\n return { environment: { ...environment }, meta: [], status: \"ok\" }\n}\n\nfunction markConditionalApplyChanged(state: ConditionalApplyState): ConditionalApplyState {\n return { ...state, status: \"changed\" }\n}\n\nasync function executeConditionalApply(parameters: {\n dryRun: boolean\n environment: Environment\n module: Module\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun, environment, module, ssh } = parameters\n if (dryRun && module._applyDryRun != null) {\n return module._applyDryRun(ssh, environment)\n }\n return module.apply(ssh, environment)\n}\n\nfunction shouldExecuteConditionalApply(module: Module, dryRun: boolean): boolean {\n if (!dryRun) return true\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function mergeConditionalApplyState(\n state: ConditionalApplyState,\n result: ModuleResult\n): Promise<ConditionalApplyState> {\n const environment = await mergeEnvironmentFromMeta(state.environment, result.meta)\n return {\n environment,\n flushSignals: result._flushSignals === true ? true : state.flushSignals,\n meta: result.meta == null ? state.meta : [...state.meta, ...result.meta],\n status: result.status === \"changed\" ? \"changed\" : state.status,\n stopRun: result._stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function applyConditionalModules(parameters: {\n dryRun?: boolean\n environment: Environment\n modules: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n const { dryRun = false, modules, ssh } = parameters\n let state = createConditionalApplyState(parameters.environment)\n\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const checkResult = await currentModule.check(ssh, state.environment)\n if (checkResult === \"ok\") continue\n\n if (!shouldExecuteConditionalApply(currentModule, dryRun)) {\n state = markConditionalApplyChanged(state)\n continue\n }\n\n // eslint-disable-next-line no-await-in-loop -- conditional modules must preserve ordered env propagation\n const result = await executeConditionalApply({\n dryRun,\n environment: state.environment,\n module: currentModule,\n ssh,\n })\n if (result.status === \"failed\") return result\n // eslint-disable-next-line no-await-in-loop -- downstream env must see each module's meta in order\n state = await mergeConditionalApplyState(state, result)\n if (state.stopRun === true) break\n }\n\n return {\n _flushSignals: state.flushSignals,\n _stopRun: state.stopRun,\n meta: state.meta.length === 0 ? undefined : state.meta,\n status: state.status,\n }\n}\n\nfunction shouldExecuteConditionalDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nfunction whenNeedsDryRunApply(modules: Module[]): boolean {\n return modules.some((module) => shouldExecuteConditionalDryRun(module))\n}\n\nasync function checkConditionalModules(\n modules: Module[],\n ssh: null | SshConnection,\n environment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n const currentEnvironment = { ...environment }\n for (const currentModule of modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await currentModule.check(ssh, currentEnvironment)\n if (result === NEEDS_APPLY) {\n return NEEDS_APPLY\n }\n }\n return \"ok\"\n}\n\ntype Condition = (ssh: null | SshConnection, environment: Environment) => boolean | Promise<boolean>\n\nfunction createWhenDryRunApply(\n condition: Condition,\n modules: Module[],\n needsDryRunApply: boolean\n): ((ssh: null | SshConnection, environment: Environment) => Promise<ModuleResult>) | undefined {\n if (!needsDryRunApply) return undefined\n return async (ssh: null | SshConnection, environment: Environment) => {\n if (!(await condition(ssh, environment))) {\n return { status: \"skipped\" as const }\n }\n return applyConditionalModules({ dryRun: true, environment, modules, ssh })\n }\n}\n\nexport function createConditionalModule(parameters: {\n condition: Condition\n modules: Module[]\n name: string\n}): Module {\n const needsDryRunApply = whenNeedsDryRunApply(parameters.modules)\n const applyDryRun = createWhenDryRunApply(\n parameters.condition,\n parameters.modules,\n needsDryRunApply\n )\n\n return {\n ...(parameters.modules.some((module) => module._dryRunBlocker === true)\n ? { _dryRunBlocker: true as const }\n : {}),\n ...(parameters.modules.some((module) => module._dryRunMetaProducer === true)\n ? { _dryRunMetaProducer: true as const }\n : {}),\n ...(applyDryRun == null ? {} : { _applyDryRun: applyDryRun }),\n async apply(ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!(await parameters.condition(ssh, environment))) {\n return { status: \"skipped\" }\n }\n return applyConditionalModules({ environment, modules: parameters.modules, ssh })\n },\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n if (!(await parameters.condition(ssh, environment))) {\n return \"ok\"\n }\n return checkConditionalModules(parameters.modules, ssh, environment)\n },\n name: parameters.name,\n }\n}\n\nfunction filesystemTypeName(testFlag: \"-d\" | \"-f\" | \"-L\" | \"-S\"): string {\n switch (testFlag) {\n case \"-d\": {\n return \"path\"\n }\n case \"-f\": {\n return \"file\"\n }\n case \"-L\": {\n return \"symlink\"\n }\n case \"-S\": {\n return \"socket\"\n }\n }\n}\n\nexport function createFilesystemGuard(parameters: {\n invert: boolean\n modules: Module[]\n path: string\n testFlag: \"-d\" | \"-f\" | \"-L\" | \"-S\"\n}): Module {\n const typeName = filesystemTypeName(parameters.testFlag)\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const exists = await ssh.test(`test ${parameters.testFlag} ${shellQuote(parameters.path)}`)\n return parameters.invert ? !exists : exists\n },\n modules: parameters.modules,\n name: `when.${typeName}${parameters.invert ? \"Missing\" : \"Exists\"}: ${parameters.path}`,\n })\n}\n\nexport function createCommandGuard(\n commandName: string,\n invert: boolean,\n modules: Module[]\n): Module {\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const exists = await ssh.test(`command -v ${shellQuote(commandName)} >/dev/null 2>&1`)\n return invert ? !exists : exists\n },\n modules,\n name: `when.command${invert ? \"Missing\" : \"Exists\"}: ${commandName}`,\n })\n}\n\nexport function createPackageGuard(\n packageName: string,\n invert: boolean,\n modules: Module[]\n): Module {\n return createConditionalModule({\n condition: async (ssh) => {\n if (ssh == null) return false\n const pm = await detectPackageManager(ssh)\n if (pm == null) return false\n const installed = await isPackageInstalled(ssh, pm, packageName)\n return invert ? !installed : installed\n },\n modules,\n name: `when.package${invert ? \"Absent\" : \"Installed\"}: ${packageName}`,\n })\n}\n","import {\n createCommandGuard,\n createConditionalModule,\n createFilesystemGuard,\n createPackageGuard,\n} from \"./conditionalModules.js\"\nimport { failed } from \"./moduleFailure.js\"\nimport {\n type Environment,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Fail the run if a condition on the current env is not satisfied.\n *\n * The check phase evaluates the condition; if it returns `false`, apply\n * marks the module as failed, which aborts the parent recipe.\n *\n * @param condition - A predicate receiving the current env at run time.\n * @param message - Human-readable description shown in the run output.\n * @returns A Module that asserts the condition.\n *\n * @example\n * assert(env => !!env[\"APP_SECRET\"], \"APP_SECRET must be set\")\n */\nexport function assert(condition: (environment: Environment) => boolean, message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (condition(environment)) {\n return { status: \"ok\" }\n }\n return failed(`[assert] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return condition(environment) ? \"ok\" : NEEDS_APPLY\n },\n name: `assert: ${message}`,\n }\n}\n\n/**\n * Print a static debug message to the console during the apply phase.\n * Always runs (never skipped by the check phase).\n *\n * @param message - The message to print, prefixed with `[debug]`.\n * @returns A Module that prints a debug message.\n */\nexport function debug(message: string): Module {\n return {\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n console.log(` [debug] ${message}`)\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `debug: ${message}`,\n }\n}\n\n/**\n * Unconditionally abort the run with a failed status and an error message.\n * Useful as a sentinel at the end of a conditional branch.\n *\n * @param message - The message to print, prefixed with `[fail]`.\n * @returns A Module that fails the run.\n */\nexport function fail(message: string): Module {\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return failed(`[fail] ${message}`)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `fail: ${message}`,\n }\n}\n\n/**\n * Pause execution and wait for the operator to press Enter.\n * Useful for interactive confirmation during a run.\n *\n * @param message - Prompt shown to the operator. Defaults to `\"Press enter to continue...\"`.\n * @returns A Module that pauses execution.\n */\nexport function pause(message?: string): Module {\n return {\n async apply(): Promise<ModuleResult> {\n const promptText = message ?? \"Press enter to continue...\"\n process.stdout.write(` [pause] ${promptText} `)\n\n await new Promise<void>((resolve) => {\n process.stdin.once(\"data\", () => {\n process.stdin.pause()\n resolve()\n })\n })\n\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: message == null ? \"pause\" : `pause: ${message}`,\n }\n}\n\nfunction isFirstRunEnabled(environment: Environment): boolean {\n return environment.PARATIX_FIRST_RUN === \"true\" || environment.FIRST_RUN === true\n}\n\n/**\n * Built-ins related to the explicit first-run bootstrap stage.\n */\nexport const firstRun = {\n /**\n * Stop the current run successfully when Paratix was invoked with `--first-run`.\n * Useful as an explicit stage boundary in scaffolded playbooks.\n *\n * @param message - Optional note shown in the module name.\n * @returns A local module that stops the run only during first-run execution.\n */\n stop(message?: string): Module {\n const moduleName = message == null ? \"firstRun.stop\" : `firstRun.stop: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(_ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!isFirstRunEnabled(environment)) {\n return { status: \"ok\" }\n }\n\n return {\n _dryRunDetail: \"(first-run stop)\",\n _stopRun: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(\n _ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n return isFirstRunEnabled(environment) ? NEEDS_APPLY : \"ok\"\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\n/**\n * Built-ins for explicit signal checkpoints.\n */\nexport const signals = {\n /**\n * Flush all currently pending signals for the active scope.\n * Signals remain scope-local:\n * - in a recipe, this flushes that recipe's signals\n * - at top level, this flushes `server(...).signals`\n *\n * @param message - Optional note shown in the module name.\n * @returns A local control module that requests an immediate signal flush.\n */\n flush(message?: string): Module {\n const moduleName = message == null ? \"signals.flush\" : `signals.flush: ${message}`\n\n return {\n _dryRunBlocker: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async apply(): Promise<ModuleResult> {\n return {\n _dryRunDetail: \"(dry-run, pending signals not executed)\",\n _flushSignals: true,\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n local: true,\n name: moduleName,\n }\n },\n}\n\n/**\n * Run one or more modules only when a runtime condition is met.\n * When the condition is `false`, the whole group is skipped without running\n * any child checks or applies.\n *\n * @param condition - A predicate evaluated against the current env at run time.\n * @param modules - One or more modules to run when the condition is `true`.\n * @returns A Module that conditionally runs the inner modules.\n *\n * @example\n * when(env => env[\"DEPLOY_ENV\"] === \"production\", service.enabled(\"fail2ban\"))\n */\nfunction baseWhen(condition: (environment: Environment) => boolean, ...modules: Module[]): Module {\n return createConditionalModule({\n condition: (_ssh, environment) => condition(environment),\n modules,\n name: `when: conditional (${modules.length} modules)`,\n })\n}\n\ntype WhenFunction = {\n commandExists: (commandName: string, ...modules: Module[]) => Module\n commandMissing: (commandName: string, ...modules: Module[]) => Module\n fileExists: (path: string, ...modules: Module[]) => Module\n fileMissing: (path: string, ...modules: Module[]) => Module\n packageAbsent: (packageName: string, ...modules: Module[]) => Module\n packageInstalled: (packageName: string, ...modules: Module[]) => Module\n pathExists: (path: string, ...modules: Module[]) => Module\n pathMissing: (path: string, ...modules: Module[]) => Module\n socketExists: (path: string, ...modules: Module[]) => Module\n socketMissing: (path: string, ...modules: Module[]) => Module\n symlinkExists: (path: string, ...modules: Module[]) => Module\n symlinkMissing: (path: string, ...modules: Module[]) => Module\n} & typeof baseWhen\n\nexport const when: WhenFunction = Object.assign(baseWhen, {\n commandExists: (commandName: string, ...modules: Module[]) =>\n createCommandGuard(commandName, false, modules),\n commandMissing: (commandName: string, ...modules: Module[]) =>\n createCommandGuard(commandName, true, modules),\n fileExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-f\" }),\n fileMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-f\" }),\n packageAbsent: (packageName: string, ...modules: Module[]) =>\n createPackageGuard(packageName, true, modules),\n packageInstalled: (packageName: string, ...modules: Module[]) =>\n createPackageGuard(packageName, false, modules),\n pathExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-d\" }),\n pathMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-d\" }),\n socketExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-S\" }),\n socketMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-S\" }),\n symlinkExists: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: false, modules, path, testFlag: \"-L\" }),\n symlinkMissing: (path: string, ...modules: Module[]) =>\n createFilesystemGuard({ invert: true, modules, path, testFlag: \"-L\" }),\n})\n","/* eslint-disable max-lines -- recipe orchestration intentionally stays co-located */\nimport { isEnvironmentMetaEntry, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRecipeModuleResult,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\nimport { runSignalModules, type SignalHooks } from \"./signalOrchestration.js\"\nimport { CommandError } from \"./sshHelpers.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n type ModuleStatus,\n NEEDS_APPLY,\n type OrchestrationStep,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Internal representation of a recipe module.\n * The `_isRecipe` flag lets the runner distinguish recipes from leaf modules.\n * @internal\n */\nexport type RecipeModule = {\n _isRecipe: true\n _modules: Module[]\n _signals?: Module[]\n apply: (\n ssh: null | SshConnection,\n environment: Environment,\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ) => Promise<ModuleResult>\n} & Module\n\ntype RecipeState = {\n env: Environment\n meta?: ModuleMetaEntry[]\n signalsPending: boolean\n status: Exclude<ModuleStatus, \"skipped\">\n stopRun?: true\n}\n\ntype ExecuteModulesParameters = {\n environment: Environment\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n verbose?: boolean\n}\n\ntype RecipeLoopStepResult =\n | { kind: \"break\"; state: RecipeState }\n | { kind: \"continue\"; state: RecipeState }\n\nconst INTERRUPTED_BEFORE_APPLY = Symbol(\"recipe-interrupted-before-apply\")\n\nfunction isRecipeModuleLike(module: Module): boolean {\n return (module as { _isRecipe?: boolean } & Module)._isRecipe === true\n}\n\nfunction printRecipeChildResult(module: Module, result: ModuleResult): void {\n if (isRecipeModuleLike(module)) {\n printRecipeModuleResult(module.name, result.status)\n return\n }\n\n printModuleResult(module.name, result.status)\n}\n\nfunction applyRecipeStepToState(\n state: RecipeState,\n step: OrchestrationStep,\n preserveControlPlaneMeta: boolean\n): RecipeState {\n let stepMeta: ModuleMetaEntry[] | undefined\n if (step.meta == null) {\n stepMeta = undefined\n } else if (preserveControlPlaneMeta) {\n stepMeta = step.meta\n } else {\n stepMeta = step.meta.filter(isEnvironmentMetaEntry)\n }\n const nextMeta = stepMeta == null ? (state.meta ?? []) : [...(state.meta ?? []), ...stepMeta]\n let nextStatus = state.status\n if (step.status === \"failed\") nextStatus = \"failed\"\n else if (step.status === \"changed\") nextStatus = \"changed\"\n\n return {\n env: step.env,\n meta: nextMeta,\n signalsPending: step.status === \"changed\" ? true : state.signalsPending,\n status: nextStatus,\n stopRun: step._stopRun === true ? true : state.stopRun,\n }\n}\n\n/**\n * Recipes run their own check→apply loop, separate from the runner's\n * `runModuleLoop` in runner.ts. This is intentional: recipes execute as a\n * single nested module inside the runner, so SSH-lifecycle concerns\n * (port-change reconnects, reboot handling), shutdown-signal guards,\n * dry-run mode and stats tracking are the runner's responsibility and\n * must not be duplicated here.\n *\n * @param parameters - Parameters for executing one child module.\n * @param parameters.targetModule - The module to check and conditionally apply.\n * @param parameters.ssh - Active SSH connection, or `null` for local modules.\n * @param parameters.currentEnvironment - Environment values available to the module.\n * @param parameters.shutdownSignal - Optional shutdown getter used to suppress new apply steps.\n * @param parameters.verbose - Whether verbose command diagnostics should be printed.\n * @returns The updated environment and status, or `null` if the module was already ok.\n */\nasync function executeOneModule(parameters: {\n currentEnvironment: Environment\n shutdownSignal?: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose?: boolean\n}): Promise<null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY> {\n const { currentEnvironment, ssh, targetModule } = parameters\n const verbose = parameters.verbose ?? false\n const connection = targetModule.local === true ? null : ssh\n const checkResult = await checkRecipeChild(targetModule, connection, currentEnvironment)\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return null\n }\n\n if ((parameters.shutdownSignal?.() ?? null) != null) {\n return INTERRUPTED_BEFORE_APPLY\n }\n\n const result = await targetModule.apply(connection, currentEnvironment)\n printRecipeChildResult(targetModule, result)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n\n const environment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n return {\n _flushSignals: result._flushSignals,\n _stopRun: result._stopRun,\n env: environment,\n meta: result.meta,\n status: result.status,\n }\n}\n\nasync function checkRecipeChild(\n targetModule: Module,\n connection: null | SshConnection,\n currentEnvironment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, currentEnvironment)\n}\n\nasync function applyExecutedRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n state: RecipeState\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n}): Promise<null | RecipeState> {\n if (parameters.step == null) return parameters.state\n if (parameters.step === INTERRUPTED_BEFORE_APPLY) return null\n\n if (parameters.onChildStep != null) {\n await parameters.onChildStep(parameters.step)\n }\n\n return applyRecipeStepToState(\n parameters.state,\n parameters.step,\n parameters.preserveControlPlaneMeta\n )\n}\n\nfunction failedRecipeState(environment: Environment): RecipeState {\n return {\n env: environment,\n meta: undefined,\n signalsPending: false,\n status: \"failed\",\n }\n}\n\nfunction annotateRecipeChildError(moduleName: string, error: unknown): Error {\n const prefix = `[${moduleName}] `\n if (error instanceof CommandError) {\n return new CommandError(`${prefix}${error.message}`, error.fullStdout, error.fullStderr)\n }\n if (error instanceof Error) {\n return new Error(`${prefix}${error.message}`)\n }\n return new Error(`${prefix}${String(error)}`)\n}\n\ntype RecipeChildExecution =\n | { kind: \"failed\"; state: RecipeState }\n | {\n kind: \"step\"\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n }\n\nasync function executeRecipeChildStep(parameters: {\n currentEnvironment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose: boolean\n}): Promise<RecipeChildExecution> {\n try {\n return { kind: \"step\", step: await executeOneModule(parameters) }\n } catch (error) {\n if (parameters.shutdownSignal() != null) {\n return { kind: \"step\", step: INTERRUPTED_BEFORE_APPLY }\n }\n printModuleResult(parameters.targetModule.name, \"failed\")\n printCommandFailure(error, parameters.verbose)\n return { kind: \"failed\", state: failedRecipeState(parameters.currentEnvironment) }\n }\n}\n\nasync function processRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n ssh: null | SshConnection\n state: RecipeState\n step: RecipeChildExecution\n verbose: boolean\n}): Promise<RecipeLoopStepResult> {\n if (parameters.step.kind === \"failed\") {\n return { kind: \"break\", state: parameters.step.state }\n }\n\n const nextState = await applyExecutedRecipeStep({\n onChildStep: parameters.onChildStep,\n preserveControlPlaneMeta: parameters.preserveControlPlaneMeta,\n state: parameters.state,\n step: parameters.step.step,\n })\n if (nextState == null) {\n return { kind: \"break\", state: parameters.state }\n }\n\n let state = nextState\n if (shouldFlushRecipeSignals(parameters.step.step, state, parameters.signals)) {\n const signalStatus = await flushPendingRecipeSignals({\n environment: state.env,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n state = applyRecipeSignalStatus(state, signalStatus)\n }\n\n if (state.status === \"failed\" || state.stopRun === true) {\n return { kind: \"break\", state }\n }\n\n return { kind: \"continue\", state }\n}\n\nasync function executeModules(\n modules: Module[],\n ssh: null | SshConnection,\n parameters: ExecuteModulesParameters\n): Promise<RecipeState> {\n const onChildStep = parameters.onChildStep\n const preserveControlPlaneMeta = onChildStep == null\n const shutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let state: RecipeState = {\n env: { ...parameters.environment },\n meta: undefined,\n signalsPending: false,\n status: \"ok\",\n }\n\n for (const currentModule of modules) {\n if (shutdownSignal() != null) break\n // eslint-disable-next-line no-await-in-loop\n const step = await executeRecipeChildStep({\n currentEnvironment: state.env,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n // eslint-disable-next-line no-await-in-loop\n const processedStep = await processRecipeStep({\n onChildStep,\n onSignalStep: parameters.onSignalStep,\n preserveControlPlaneMeta,\n shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh,\n state,\n step,\n verbose,\n })\n state = processedStep.state\n if (processedStep.kind === \"break\") return state\n }\n\n return state\n}\n\nasync function triggerSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return runSignalModules({\n environment: parameters.environment,\n hooks: parameters.signalHooks,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldFlushRecipeSignals(\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY,\n state: RecipeState,\n signals?: Module[]\n): signals is Module[] {\n return (\n step != null &&\n step !== INTERRUPTED_BEFORE_APPLY &&\n step._flushSignals === true &&\n state.signalsPending &&\n signals != null\n )\n}\n\nfunction applyRecipeSignalStatus(\n state: RecipeState,\n signalStatus: \"changed\" | \"failed\"\n): RecipeState {\n return {\n ...state,\n signalsPending: false,\n status: signalStatus === \"failed\" ? \"failed\" : state.status,\n }\n}\n\nasync function flushPendingRecipeSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return triggerSignals(parameters)\n}\n\nasync function applyRecipe(parameters: {\n environment: Environment\n modules: Module[]\n name: string\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n signals?: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n return withRecipeOutputScope(async () => {\n const shutdownSignal = parameters.options?.shutdownSignal\n const verbose = parameters.options?.verbose ?? false\n printRecipeHeader(parameters.name)\n const state = await executeModules(parameters.modules, parameters.ssh, {\n environment: parameters.environment,\n onChildStep: parameters.options?.onChildStep,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n verbose,\n })\n\n if (shouldRunRecipeSignalsAtEnd(state, parameters.signals)) {\n state.status = await triggerSignals({\n environment: state.env,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose,\n })\n }\n\n return {\n _stopRun: state.stopRun,\n meta: state.meta,\n status: state.status,\n }\n })\n}\n\nfunction shouldRunRecipeSignalsAtEnd(state: RecipeState, signals?: Module[]): signals is Module[] {\n return state.signalsPending && state.status === \"changed\" && signals != null\n}\n\n/**\n * Group a list of modules into a named, self-contained recipe.\n *\n * The recipe runs each child module in order, short-circuits on the first\n * failure, and propagates `meta` env values from one module to all subsequent\n * ones. If any child reports `\"changed\"`, the optional `signals` are triggered\n * at the end of the run.\n *\n * @param name - Display name shown in the run output header.\n * @param modules - Ordered list of modules to execute.\n * @param options - Optional recipe configuration.\n * @param options.signals - Modules to fire when at least one child changed state.\n * @returns A RecipeModule that groups the child modules.\n *\n * @example\n * export const nginxRecipe = recipe(\"nginx\", [\n * apt.installed(\"nginx\"),\n * file.template(\"/etc/nginx/nginx.conf\", \"./files/nginx.conf.tmpl\"),\n * service.enabled(\"nginx\"),\n * ], {\n * signals: [service.reload(\"nginx\")],\n * });\n */\nexport function recipe(\n name: string,\n modules: Module[],\n options?: { signals?: Module[] }\n): RecipeModule {\n return {\n _isRecipe: true,\n _modules: modules,\n _signals: options?.signals,\n async apply(\n ssh: null | SshConnection,\n environment: Environment,\n parameters?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ): Promise<ModuleResult> {\n return applyRecipe({\n environment,\n modules,\n name,\n options: parameters,\n signals: options?.signals,\n ssh,\n })\n },\n\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n // Each child receives the original environment — no meta propagation,\n // because check() never calls apply() and therefore produces no meta.\n for (const childModule of modules) {\n const connection = childModule.local === true ? null : ssh\n let result: \"needs-apply\" | \"ok\"\n try {\n // eslint-disable-next-line no-await-in-loop\n result = await childModule.check(connection, environment)\n } catch (error) {\n throw annotateRecipeChildError(childModule.name, error)\n }\n if (result === NEEDS_APPLY) return NEEDS_APPLY\n }\n return \"ok\"\n },\n\n name,\n }\n}\n","import type { ServerDefinition } from \"./types.js\"\n\nimport { validateSshConfig } from \"./serverDefinitionValidation.js\"\n\n/**\n * Define a server and validate its configuration at construction time.\n *\n * This is a thin identity function whose only purpose is to provide\n * type-safe validation with descriptive error messages before the runner\n * ever attempts an SSH connection.\n *\n * @param config - The server definition to validate and return.\n * @returns The validated server definition.\n * @throws {Error} When any required field is missing or empty.\n *\n * @example\n * export default server({\n * name: \"web-01\",\n * host: \"10.0.0.1\",\n * ssh: { user: \"root\", ports: [22], privateKey: \"~/.ssh/id_ed25519\" }, // \"~\" is expanded\n * run: [apt.installed(\"nginx\")],\n * });\n */\nexport function server(config: ServerDefinition): ServerDefinition {\n if (config.host.length === 0) {\n throw new Error(\"ServerDefinition: host is required\")\n }\n if (config.name.length === 0) {\n throw new Error(\"ServerDefinition: name is required\")\n }\n validateSshConfig(config.ssh)\n if (config.run.length === 0) {\n throw new Error(\"ServerDefinition: run must contain at least one module\")\n }\n\n return config\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoBA,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,8BAA8B,QAAgB,QAA0B;AAC/E,MAAI,CAAC,OAAQ,QAAO;AACpB,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,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,+BAA+B,QAAyB;AAC/D,SACE,OAAO,gBAAgB,QACvB,OAAO,mBAAmB,QAC1B,OAAO,wBAAwB;AAEnC;AAEA,SAAS,qBAAqB,SAA4B;AACxD,SAAO,QAAQ,KAAK,CAAC,WAAW,+BAA+B,MAAM,CAAC;AACxE;AAEA,eAAe,wBACb,SACAA,MACA,aAC+B;AAC/B,QAAM,qBAAqB,EAAE,GAAG,YAAY;AAC5C,aAAW,iBAAiB,SAAS;AAEnC,UAAM,SAAS,MAAM,cAAc,MAAMA,MAAK,kBAAkB;AAChE,QAAI,WAAW,aAAa;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAIA,SAAS,sBACP,WACA,SACA,kBAC8F;AAC9F,MAAI,CAAC,iBAAkB,QAAO;AAC9B,SAAO,OAAOA,MAA2B,gBAA6B;AACpE,QAAI,CAAE,MAAM,UAAUA,MAAK,WAAW,GAAI;AACxC,aAAO,EAAE,QAAQ,UAAmB;AAAA,IACtC;AACA,WAAO,wBAAwB,EAAE,QAAQ,MAAM,aAAa,SAAS,KAAAA,KAAI,CAAC;AAAA,EAC5E;AACF;AAEO,SAAS,wBAAwB,YAI7B;AACT,QAAM,mBAAmB,qBAAqB,WAAW,OAAO;AAChE,QAAM,cAAc;AAAA,IAClB,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAI,WAAW,QAAQ,KAAK,CAAC,WAAW,OAAO,mBAAmB,IAAI,IAClE,EAAE,gBAAgB,KAAc,IAChC,CAAC;AAAA,IACL,GAAI,WAAW,QAAQ,KAAK,CAAC,WAAW,OAAO,wBAAwB,IAAI,IACvE,EAAE,qBAAqB,KAAc,IACrC,CAAC;AAAA,IACL,GAAI,eAAe,OAAO,CAAC,IAAI,EAAE,cAAc,YAAY;AAAA,IAC3D,MAAM,MAAMA,MAA2B,aAAiD;AACtF,UAAI,CAAE,MAAM,WAAW,UAAUA,MAAK,WAAW,GAAI;AACnD,eAAO,EAAE,QAAQ,UAAU;AAAA,MAC7B;AACA,aAAO,wBAAwB,EAAE,aAAa,SAAS,WAAW,SAAS,KAAAA,KAAI,CAAC;AAAA,IAClF;AAAA,IACA,MAAM,MACJA,MACA,aAC+B;AAC/B,UAAI,CAAE,MAAM,WAAW,UAAUA,MAAK,WAAW,GAAI;AACnD,eAAO;AAAA,MACT;AACA,aAAO,wBAAwB,WAAW,SAASA,MAAK,WAAW;AAAA,IACrE;AAAA,IACA,MAAM,WAAW;AAAA,EACnB;AACF;AAEA,SAAS,mBAAmB,UAA6C;AACvE,UAAQ,UAAU;AAAA,IAChB,KAAK,MAAM;AACT,aAAO;AAAA,IACT;AAAA,IACA,KAAK,MAAM;AACT,aAAO;AAAA,IACT;AAAA,IACA,KAAK,MAAM;AACT,aAAO;AAAA,IACT;AAAA,IACA,KAAK,MAAM;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,sBAAsB,YAK3B;AACT,QAAM,WAAW,mBAAmB,WAAW,QAAQ;AACvD,SAAO,wBAAwB;AAAA,IAC7B,WAAW,OAAOA,SAAQ;AACxB,UAAIA,QAAO,KAAM,QAAO;AACxB,YAAM,SAAS,MAAMA,KAAI,KAAK,QAAQ,WAAW,QAAQ,IAAI,WAAW,WAAW,IAAI,CAAC,EAAE;AAC1F,aAAO,WAAW,SAAS,CAAC,SAAS;AAAA,IACvC;AAAA,IACA,SAAS,WAAW;AAAA,IACpB,MAAM,QAAQ,QAAQ,GAAG,WAAW,SAAS,YAAY,QAAQ,KAAK,WAAW,IAAI;AAAA,EACvF,CAAC;AACH;AAEO,SAAS,mBACd,aACA,QACA,SACQ;AACR,SAAO,wBAAwB;AAAA,IAC7B,WAAW,OAAOA,SAAQ;AACxB,UAAIA,QAAO,KAAM,QAAO;AACxB,YAAM,SAAS,MAAMA,KAAI,KAAK,cAAc,WAAW,WAAW,CAAC,kBAAkB;AACrF,aAAO,SAAS,CAAC,SAAS;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,MAAM,eAAe,SAAS,YAAY,QAAQ,KAAK,WAAW;AAAA,EACpE,CAAC;AACH;AAEO,SAAS,mBACd,aACA,QACA,SACQ;AACR,SAAO,wBAAwB;AAAA,IAC7B,WAAW,OAAOA,SAAQ;AACxB,UAAIA,QAAO,KAAM,QAAO;AACxB,YAAM,KAAK,MAAM,qBAAqBA,IAAG;AACzC,UAAI,MAAM,KAAM,QAAO;AACvB,YAAM,YAAY,MAAM,mBAAmBA,MAAK,IAAI,WAAW;AAC/D,aAAO,SAAS,CAAC,YAAY;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,MAAM,eAAe,SAAS,WAAW,WAAW,KAAK,WAAW;AAAA,EACtE,CAAC;AACH;;;AClOO,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;AAcA,SAAS,SAAS,cAAqD,SAA2B;AAChG,SAAO,wBAAwB;AAAA,IAC7B,WAAW,CAAC,MAAM,gBAAgB,UAAU,WAAW;AAAA,IACvD;AAAA,IACA,MAAM,sBAAsB,QAAQ,MAAM;AAAA,EAC5C,CAAC;AACH;AAiBO,IAAM,OAAqB,OAAO,OAAO,UAAU;AAAA,EACxD,eAAe,CAAC,gBAAwB,YACtC,mBAAmB,aAAa,OAAO,OAAO;AAAA,EAChD,gBAAgB,CAAC,gBAAwB,YACvC,mBAAmB,aAAa,MAAM,OAAO;AAAA,EAC/C,YAAY,CAAC,SAAiB,YAC5B,sBAAsB,EAAE,QAAQ,OAAO,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACxE,aAAa,CAAC,SAAiB,YAC7B,sBAAsB,EAAE,QAAQ,MAAM,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACvE,eAAe,CAAC,gBAAwB,YACtC,mBAAmB,aAAa,MAAM,OAAO;AAAA,EAC/C,kBAAkB,CAAC,gBAAwB,YACzC,mBAAmB,aAAa,OAAO,OAAO;AAAA,EAChD,YAAY,CAAC,SAAiB,YAC5B,sBAAsB,EAAE,QAAQ,OAAO,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACxE,aAAa,CAAC,SAAiB,YAC7B,sBAAsB,EAAE,QAAQ,MAAM,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACvE,cAAc,CAAC,SAAiB,YAC9B,sBAAsB,EAAE,QAAQ,OAAO,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACxE,eAAe,CAAC,SAAiB,YAC/B,sBAAsB,EAAE,QAAQ,MAAM,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACvE,eAAe,CAAC,SAAiB,YAC/B,sBAAsB,EAAE,QAAQ,OAAO,SAAS,MAAM,UAAU,KAAK,CAAC;AAAA,EACxE,gBAAgB,CAAC,SAAiB,YAChC,sBAAsB,EAAE,QAAQ,MAAM,SAAS,MAAM,UAAU,KAAK,CAAC;AACzE,CAAC;;;ACrMD,IAAM,2BAA2B,uBAAO,iCAAiC;AAEzE,SAAS,mBAAmB,QAAyB;AACnD,SAAQ,OAA4C,cAAc;AACpE;AAEA,SAAS,uBAAuB,QAAgB,QAA4B;AAC1E,MAAI,mBAAmB,MAAM,GAAG;AAC9B,4BAAwB,OAAO,MAAM,OAAO,MAAM;AAClD;AAAA,EACF;AAEA,oBAAkB,OAAO,MAAM,OAAO,MAAM;AAC9C;AAEA,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,yBAAuB,cAAc,MAAM;AAC3C,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;;;AC3eO,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 printRecipeModuleResult,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\nimport { runSignalModules, type SignalHooks } from \"./signalOrchestration.js\"\nimport { CommandError } from \"./sshHelpers.js\"\nimport {\n type Environment,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n type ModuleStatus,\n NEEDS_APPLY,\n type OrchestrationStep,\n type SshConnection,\n} from \"./types.js\"\n\n/**\n * Internal representation of a recipe module.\n * The `_isRecipe` flag lets the runner distinguish recipes from leaf modules.\n * @internal\n */\nexport type RecipeModule = {\n _isRecipe: true\n _modules: Module[]\n _signals?: Module[]\n apply: (\n ssh: null | SshConnection,\n environment: Environment,\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ) => Promise<ModuleResult>\n} & Module\n\ntype RecipeState = {\n env: Environment\n meta?: ModuleMetaEntry[]\n signalsPending: boolean\n status: Exclude<ModuleStatus, \"skipped\">\n stopRun?: true\n}\n\ntype ExecuteModulesParameters = {\n environment: Environment\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n verbose?: boolean\n}\n\ntype RecipeLoopStepResult =\n | { kind: \"break\"; state: RecipeState }\n | { kind: \"continue\"; state: RecipeState }\n\nconst INTERRUPTED_BEFORE_APPLY = Symbol(\"recipe-interrupted-before-apply\")\n\nfunction isRecipeModuleLike(module: Module): boolean {\n return (module as { _isRecipe?: boolean } & Module)._isRecipe === true\n}\n\nfunction printRecipeChildResult(module: Module, result: ModuleResult): void {\n if (isRecipeModuleLike(module)) {\n printRecipeModuleResult(module.name, result.status, result.detail)\n return\n }\n\n printModuleResult(module.name, result.status, result.detail)\n}\n\nfunction applyRecipeStepToState(\n state: RecipeState,\n step: OrchestrationStep,\n preserveControlPlaneMeta: boolean\n): RecipeState {\n let stepMeta: ModuleMetaEntry[] | undefined\n if (step.meta == null) {\n stepMeta = undefined\n } else if (preserveControlPlaneMeta) {\n stepMeta = step.meta\n } else {\n stepMeta = step.meta.filter(isEnvironmentMetaEntry)\n }\n const nextMeta = stepMeta == null ? (state.meta ?? []) : [...(state.meta ?? []), ...stepMeta]\n let nextStatus = state.status\n if (step.status === \"failed\") nextStatus = \"failed\"\n else if (step.status === \"changed\") nextStatus = \"changed\"\n\n return {\n env: step.env,\n meta: nextMeta,\n signalsPending: step.status === \"changed\" ? true : state.signalsPending,\n status: nextStatus,\n stopRun: step._stopRun === true ? true : state.stopRun,\n }\n}\n\n/**\n * Recipes run their own check→apply loop, separate from the runner's\n * `runModuleLoop` in runner.ts. This is intentional: recipes execute as a\n * single nested module inside the runner, so SSH-lifecycle concerns\n * (port-change reconnects, reboot handling), shutdown-signal guards,\n * dry-run mode and stats tracking are the runner's responsibility and\n * must not be duplicated here.\n *\n * @param parameters - Parameters for executing one child module.\n * @param parameters.targetModule - The module to check and conditionally apply.\n * @param parameters.ssh - Active SSH connection, or `null` for local modules.\n * @param parameters.currentEnvironment - Environment values available to the module.\n * @param parameters.shutdownSignal - Optional shutdown getter used to suppress new apply steps.\n * @param parameters.verbose - Whether verbose command diagnostics should be printed.\n * @returns The updated environment and status, or `null` if the module was already ok.\n */\nasync function executeOneModule(parameters: {\n currentEnvironment: Environment\n shutdownSignal?: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose?: boolean\n}): Promise<null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY> {\n const { currentEnvironment, ssh, targetModule } = parameters\n const verbose = parameters.verbose ?? false\n const connection = targetModule.local === true ? null : ssh\n const checkResult = await checkRecipeChild(targetModule, connection, currentEnvironment)\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return null\n }\n\n if ((parameters.shutdownSignal?.() ?? null) != null) {\n return INTERRUPTED_BEFORE_APPLY\n }\n\n const result = await targetModule.apply(connection, currentEnvironment)\n printRecipeChildResult(targetModule, result)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n\n const environment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n return {\n _flushSignals: result._flushSignals,\n _stopRun: result._stopRun,\n env: environment,\n meta: result.meta,\n status: result.status,\n }\n}\n\nasync function checkRecipeChild(\n targetModule: Module,\n connection: null | SshConnection,\n currentEnvironment: Environment\n): Promise<\"needs-apply\" | \"ok\"> {\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, currentEnvironment)\n}\n\nasync function applyExecutedRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n state: RecipeState\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n}): Promise<null | RecipeState> {\n if (parameters.step == null) return parameters.state\n if (parameters.step === INTERRUPTED_BEFORE_APPLY) return null\n\n if (parameters.onChildStep != null) {\n await parameters.onChildStep(parameters.step)\n }\n\n return applyRecipeStepToState(\n parameters.state,\n parameters.step,\n parameters.preserveControlPlaneMeta\n )\n}\n\nfunction failedRecipeState(environment: Environment): RecipeState {\n return {\n env: environment,\n meta: undefined,\n signalsPending: false,\n status: \"failed\",\n }\n}\n\nfunction annotateRecipeChildError(moduleName: string, error: unknown): Error {\n const prefix = `[${moduleName}] `\n if (error instanceof CommandError) {\n return new CommandError(`${prefix}${error.message}`, error.fullStdout, error.fullStderr)\n }\n if (error instanceof Error) {\n return new Error(`${prefix}${error.message}`)\n }\n return new Error(`${prefix}${String(error)}`)\n}\n\ntype RecipeChildExecution =\n | { kind: \"failed\"; state: RecipeState }\n | {\n kind: \"step\"\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY\n }\n\nasync function executeRecipeChildStep(parameters: {\n currentEnvironment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n targetModule: Module\n verbose: boolean\n}): Promise<RecipeChildExecution> {\n try {\n return { kind: \"step\", step: await executeOneModule(parameters) }\n } catch (error) {\n if (parameters.shutdownSignal() != null) {\n return { kind: \"step\", step: INTERRUPTED_BEFORE_APPLY }\n }\n printModuleResult(parameters.targetModule.name, \"failed\")\n printCommandFailure(error, parameters.verbose)\n return { kind: \"failed\", state: failedRecipeState(parameters.currentEnvironment) }\n }\n}\n\nasync function processRecipeStep(parameters: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n preserveControlPlaneMeta: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals?: Module[]\n ssh: null | SshConnection\n state: RecipeState\n step: RecipeChildExecution\n verbose: boolean\n}): Promise<RecipeLoopStepResult> {\n if (parameters.step.kind === \"failed\") {\n return { kind: \"break\", state: parameters.step.state }\n }\n\n const nextState = await applyExecutedRecipeStep({\n onChildStep: parameters.onChildStep,\n preserveControlPlaneMeta: parameters.preserveControlPlaneMeta,\n state: parameters.state,\n step: parameters.step.step,\n })\n if (nextState == null) {\n return { kind: \"break\", state: parameters.state }\n }\n\n let state = nextState\n if (shouldFlushRecipeSignals(parameters.step.step, state, parameters.signals)) {\n const signalStatus = await flushPendingRecipeSignals({\n environment: state.env,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n state = applyRecipeSignalStatus(state, signalStatus)\n }\n\n if (state.status === \"failed\" || state.stopRun === true) {\n return { kind: \"break\", state }\n }\n\n return { kind: \"continue\", state }\n}\n\nasync function executeModules(\n modules: Module[],\n ssh: null | SshConnection,\n parameters: ExecuteModulesParameters\n): Promise<RecipeState> {\n const onChildStep = parameters.onChildStep\n const preserveControlPlaneMeta = onChildStep == null\n const shutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let state: RecipeState = {\n env: { ...parameters.environment },\n meta: undefined,\n signalsPending: false,\n status: \"ok\",\n }\n\n for (const currentModule of modules) {\n if (shutdownSignal() != null) break\n // eslint-disable-next-line no-await-in-loop\n const step = await executeRecipeChildStep({\n currentEnvironment: state.env,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n // eslint-disable-next-line no-await-in-loop\n const processedStep = await processRecipeStep({\n onChildStep,\n onSignalStep: parameters.onSignalStep,\n preserveControlPlaneMeta,\n shutdownSignal,\n signalHooks: parameters.signalHooks,\n signals: parameters.signals,\n ssh,\n state,\n step,\n verbose,\n })\n state = processedStep.state\n if (processedStep.kind === \"break\") return state\n }\n\n return state\n}\n\nasync function triggerSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return runSignalModules({\n environment: parameters.environment,\n hooks: parameters.signalHooks,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldFlushRecipeSignals(\n step: null | OrchestrationStep | typeof INTERRUPTED_BEFORE_APPLY,\n state: RecipeState,\n signals?: Module[]\n): signals is Module[] {\n return (\n step != null &&\n step !== INTERRUPTED_BEFORE_APPLY &&\n step._flushSignals === true &&\n state.signalsPending &&\n signals != null\n )\n}\n\nfunction applyRecipeSignalStatus(\n state: RecipeState,\n signalStatus: \"changed\" | \"failed\"\n): RecipeState {\n return {\n ...state,\n signalsPending: false,\n status: signalStatus === \"failed\" ? \"failed\" : state.status,\n }\n}\n\nasync function flushPendingRecipeSignals(parameters: {\n environment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}): Promise<\"changed\" | \"failed\"> {\n return triggerSignals(parameters)\n}\n\nasync function applyRecipe(parameters: {\n environment: Environment\n modules: Module[]\n name: string\n options?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n signals?: Module[]\n ssh: null | SshConnection\n}): Promise<ModuleResult> {\n return withRecipeOutputScope(async () => {\n const shutdownSignal = parameters.options?.shutdownSignal\n const verbose = parameters.options?.verbose ?? false\n printRecipeHeader(parameters.name)\n const state = await executeModules(parameters.modules, parameters.ssh, {\n environment: parameters.environment,\n onChildStep: parameters.options?.onChildStep,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n verbose,\n })\n\n if (shouldRunRecipeSignalsAtEnd(state, parameters.signals)) {\n state.status = await triggerSignals({\n environment: state.env,\n onSignalStep: parameters.options?.onSignalStep,\n shutdownSignal,\n signalHooks: parameters.options?.signalHooks,\n signals: parameters.signals,\n ssh: parameters.ssh,\n verbose,\n })\n }\n\n return {\n _stopRun: state.stopRun,\n meta: state.meta,\n status: state.status,\n }\n })\n}\n\nfunction shouldRunRecipeSignalsAtEnd(state: RecipeState, signals?: Module[]): signals is Module[] {\n return state.signalsPending && state.status === \"changed\" && signals != null\n}\n\n/**\n * Group a list of modules into a named, self-contained recipe.\n *\n * The recipe runs each child module in order, short-circuits on the first\n * failure, and propagates `meta` env values from one module to all subsequent\n * ones. If any child reports `\"changed\"`, the optional `signals` are triggered\n * at the end of the run.\n *\n * @param name - Display name shown in the run output header.\n * @param modules - Ordered list of modules to execute.\n * @param options - Optional recipe configuration.\n * @param options.signals - Modules to fire when at least one child changed state.\n * @returns A RecipeModule that groups the child modules.\n *\n * @example\n * export const nginxRecipe = recipe(\"nginx\", [\n * apt.installed(\"nginx\"),\n * file.template(\"/etc/nginx/nginx.conf\", \"./files/nginx.conf.tmpl\"),\n * service.enabled(\"nginx\"),\n * ], {\n * signals: [service.reload(\"nginx\")],\n * });\n */\nexport function recipe(\n name: string,\n modules: Module[],\n options?: { signals?: Module[] }\n): RecipeModule {\n return {\n _isRecipe: true,\n _modules: modules,\n _signals: options?.signals,\n async apply(\n ssh: null | SshConnection,\n environment: Environment,\n parameters?: {\n onChildStep?: (step: OrchestrationStep) => Promise<void>\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signalHooks?: SignalHooks\n verbose?: boolean\n }\n ): Promise<ModuleResult> {\n return applyRecipe({\n environment,\n modules,\n name,\n options: parameters,\n signals: options?.signals,\n ssh,\n })\n },\n\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n // Each child receives the original environment — no meta propagation,\n // because check() never calls apply() and therefore produces no meta.\n for (const childModule of modules) {\n const connection = childModule.local === true ? null : ssh\n let result: \"needs-apply\" | \"ok\"\n try {\n // eslint-disable-next-line no-await-in-loop\n result = await childModule.check(connection, environment)\n } catch (error) {\n throw annotateRecipeChildError(childModule.name, error)\n }\n if (result === NEEDS_APPLY) return NEEDS_APPLY\n }\n return \"ok\"\n },\n\n name,\n }\n}\n","import type { ServerDefinition } from \"./types.js\"\n\nimport { validateSshConfig } from \"./serverDefinitionValidation.js\"\n\n/**\n * Define a server and validate its configuration at construction time.\n *\n * This is a thin identity function whose only purpose is to provide\n * type-safe validation with descriptive error messages before the runner\n * ever attempts an SSH connection.\n *\n * @param config - The server definition to validate and return.\n * @returns The validated server definition.\n * @throws {Error} When any required field is missing or empty.\n *\n * @example\n * export default server({\n * name: \"web-01\",\n * host: \"10.0.0.1\",\n * ssh: { user: \"root\", ports: [22], privateKey: \"~/.ssh/id_ed25519\" }, // \"~\" is expanded\n * run: [apt.installed(\"nginx\")],\n * });\n */\nexport function server(config: ServerDefinition): ServerDefinition {\n if (config.host.length === 0) {\n throw new Error(\"ServerDefinition: host is required\")\n }\n if (config.name.length === 0) {\n throw new Error(\"ServerDefinition: name is required\")\n }\n validateSshConfig(config.ssh)\n if (config.run.length === 0) {\n throw new Error(\"ServerDefinition: run must contain at least one module\")\n }\n\n return config\n}\n"],"mappings":"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","names":["ssh","ssh","signals"]}
|