paratix 0.0.1 → 0.1.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 CHANGED
@@ -45,7 +45,7 @@ export default server({
45
45
  ssh: {
46
46
  user: "root",
47
47
  ports: [22],
48
- privateKey: "~/.ssh/id_ed25519",
48
+ privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
49
49
  },
50
50
  run: [hostname.set("web-01"), pkg.update("2025-03-01"), pkg.installed("nginx", "curl", "git")],
51
51
  })
@@ -63,6 +63,43 @@ Preview changes without applying them:
63
63
  npx paratix apply server.ts --dry-run
64
64
  ```
65
65
 
66
+ For most modules, `--dry-run` reports whether Paratix would change remote state without mutating it.
67
+ For SSH hardening modules, Paratix now goes one step further:
68
+
69
+ - `sshd.config` validates the prospective config with `sshd -t`
70
+ - `sshd.port` validates the prospective config with `sshd -t`
71
+
72
+ Runtime effects are still intentionally not executed during `--dry-run`. In particular, reloads,
73
+ restarts, port switches, firewall reachability, and reconnect behavior are reported as limited
74
+ verification in the run output instead of being performed.
75
+
76
+ ## SSH host key migration
77
+
78
+ Paratix now defaults to strict host-key checking (`ssh.strictHostKeyChecking: "yes"`).
79
+ Existing playbooks that relied on implicit TOFU must now opt in explicitly:
80
+
81
+ ```typescript
82
+ ssh: {
83
+ user: "root",
84
+ ports: [22],
85
+ privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
86
+ strictHostKeyChecking: "accept-new", // explicit TOFU opt-in
87
+ }
88
+ ```
89
+
90
+ For a safer bootstrap of brand-new hosts, pin the expected host key instead of using TOFU:
91
+
92
+ ```typescript
93
+ ssh: {
94
+ user: "root",
95
+ ports: [22],
96
+ privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
97
+ expectedHostFingerprint: "SHA256:your-known-fingerprint",
98
+ }
99
+ ```
100
+
101
+ You can also pin the full OpenSSH public key with `expectedHostPublicKey`.
102
+
66
103
  ## Core concepts
67
104
 
68
105
  ### Playbook
@@ -75,7 +112,12 @@ import { server } from "paratix"
75
112
  export default server({
76
113
  name: "web-01",
77
114
  host: "10.0.0.1",
78
- ssh: { user: "root", ports: [22], privateKey: "~/.ssh/id_ed25519" },
115
+ ssh: {
116
+ user: "root",
117
+ ports: [22],
118
+ privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
119
+ expectedHostFingerprint: "SHA256:your-known-fingerprint",
120
+ },
79
121
  env: {
80
122
  DOMAIN: "example.com",
81
123
  APP_PORT: 3000,
@@ -160,6 +202,12 @@ export default server({
160
202
 
161
203
  Modules can return `meta` in their result, which merges into the environment for subsequent modules.
162
204
 
205
+ When environment values come from multiple sources, Paratix merges them in this order, with later values winning:
206
+
207
+ 1. `--env-file <path>`
208
+ 2. `server({ env })`
209
+ 3. `--env <key=value>`
210
+
163
211
  ### Templates
164
212
 
165
213
  `file.template()` deploys a file with `{{KEY}}` placeholders resolved from the environment.
@@ -186,7 +234,7 @@ Use `\{{` to produce a literal `{{` in the output. Unknown keys throw an error a
186
234
  paratix apply <file>
187
235
 
188
236
  Options:
189
- --dry-run Check only, don't apply
237
+ --dry-run Check only, don't apply. Some modules validate prospective config but cannot verify runtime restarts.
190
238
  --env <key=value> Set environment variable (repeatable)
191
239
  --env-file <path> Load .env file
192
240
  --reconnect-timeout <s> SSH reconnect timeout in seconds (default: 300)
@@ -341,6 +389,36 @@ Import these from `"paratix"`:
341
389
 
342
390
  This package includes an `llm-guide.md` file that provides detailed information for writing Paratix modules and playbooks. It covers the complete API reference, code patterns, and common mistakes to avoid. When using an LLM to generate Paratix code, point it at this file for best results.
343
391
 
392
+ ## Integration tests
393
+
394
+ Paratix ships a separate integration test entry point for real SSH/SFTP checks:
395
+
396
+ ```bash
397
+ pnpm --filter paratix test:integration
398
+ ```
399
+
400
+ For the full workspace review path including integration coverage, use:
401
+
402
+ ```bash
403
+ pnpm agent:check:integration
404
+ ```
405
+
406
+ These tests are intentionally not part of the default unit test run. They require:
407
+
408
+ - on macOS: `colima` plus a working Docker CLI connected to the active Colima runtime
409
+ - on Linux/CI: a reachable Docker runtime
410
+
411
+ The integration suite starts a temporary SSH test container, runs the tests against it and removes the container again afterwards. If `colima` is missing, the suite aborts with a clear error message instead of hanging or silently skipping coverage.
412
+
413
+ The suite now verifies real remote end state for core modules such as
414
+ `file.directory`, `file.copy`, `file.template`, `command.shell`, `download.url`,
415
+ and `download.large`, including ownership, mode, content, large-download flags,
416
+ and idempotent `check()` behavior against a live server.
417
+
418
+ An example GitHub Actions workflow is included as a disabled template in
419
+ `.github/workflows/integration-check.yml.disabled`. Rename it to `.yml` when
420
+ you want the integration path to run on GitHub.
421
+
344
422
  ## License
345
423
 
346
424
  MIT
@@ -0,0 +1,439 @@
1
+ import {
2
+ CommandError,
3
+ assertValidModuleMetaEntries,
4
+ mergeEnvironmentFromMeta
5
+ } from "./chunk-G3BMCQKU.js";
6
+
7
+ // src/output.ts
8
+ import pc from "picocolors";
9
+
10
+ // src/outputFormatting.ts
11
+ import { stripVTControlCharacters } from "util";
12
+ var PACKAGE_MODULE_SUFFIX_LENGTH = 2;
13
+ var PACKAGE_COLUMN_GAP_WIDTH = 2;
14
+ var DEFAULT_TERMINAL_COLUMNS = 100;
15
+ var MIN_PACKAGE_COLUMNS_WIDTH = 24;
16
+ var MIN_PACKAGE_COLUMN_WIDTH = 18;
17
+ var MIN_ANIMATED_LINE_COLUMNS = 8;
18
+ function splitPackageModuleName(name) {
19
+ for (const prefix of ["package.installed: ", "package.absent: "]) {
20
+ if (!name.startsWith(prefix)) continue;
21
+ const packages = name.slice(prefix.length).split(",").map((entry) => entry.trim()).filter(Boolean);
22
+ if (packages.length === 0) return null;
23
+ return {
24
+ packages,
25
+ summaryName: prefix.slice(0, -PACKAGE_MODULE_SUFFIX_LENGTH)
26
+ };
27
+ }
28
+ return null;
29
+ }
30
+ function getPackageColumns(parameters) {
31
+ if (parameters.packages.length <= 1) return parameters.packages;
32
+ const availableWidth = Math.max(
33
+ (parameters.terminalColumns ?? DEFAULT_TERMINAL_COLUMNS) - parameters.continuationIndentWidth,
34
+ MIN_PACKAGE_COLUMNS_WIDTH
35
+ );
36
+ const widestPackage = Math.max(...parameters.packages.map((entry) => entry.length));
37
+ const columnWidth = Math.max(widestPackage + PACKAGE_COLUMN_GAP_WIDTH, MIN_PACKAGE_COLUMN_WIDTH);
38
+ const columnCount = Math.max(Math.floor(availableWidth / columnWidth), 1);
39
+ const rowCount = Math.ceil(parameters.packages.length / columnCount);
40
+ return Array.from(
41
+ { length: rowCount },
42
+ (_row, rowIndex) => Array.from({ length: columnCount }, (_column, columnIndex) => {
43
+ const packageIndex = rowIndex + columnIndex * rowCount;
44
+ const packageName = parameters.packages.at(packageIndex);
45
+ if (packageName == null) return "";
46
+ const isLastVisibleColumn = columnIndex === columnCount - 1 || packageIndex + rowCount >= parameters.packages.length;
47
+ return isLastVisibleColumn ? packageName : packageName.padEnd(columnWidth);
48
+ }).filter(Boolean).join("")
49
+ );
50
+ }
51
+ function getPackageSummaryDetail(packageCount, detail) {
52
+ const packageLabel = packageCount === 1 ? "1 package" : `${packageCount} packages`;
53
+ return detail == null ? packageLabel : `${packageLabel} \xB7 ${detail}`;
54
+ }
55
+ function fitAnimatedModuleLine(line, columns) {
56
+ if (columns === void 0 || columns < MIN_ANIMATED_LINE_COLUMNS) return line;
57
+ const plainLine = stripVTControlCharacters(line);
58
+ if (plainLine.length < columns) return line;
59
+ return `${plainLine.slice(0, columns - 1)}\u2026`;
60
+ }
61
+ function formatDisplayModule(parameters) {
62
+ const packageModule = splitPackageModuleName(parameters.name);
63
+ if (packageModule == null) {
64
+ return {
65
+ detail: parameters.detail,
66
+ detailLines: [],
67
+ name: parameters.name
68
+ };
69
+ }
70
+ return {
71
+ detail: getPackageSummaryDetail(packageModule.packages.length, parameters.detail),
72
+ detailLines: parameters.status === "waiting" ? [] : getPackageColumns({
73
+ continuationIndentWidth: parameters.continuationIndentWidth,
74
+ packages: packageModule.packages,
75
+ terminalColumns: parameters.terminalColumns
76
+ }),
77
+ name: packageModule.summaryName
78
+ };
79
+ }
80
+
81
+ // src/output.ts
82
+ var MODULE_NAME_WIDTH = 56;
83
+ var MIN_MODULE_NAME_WIDTH = 12;
84
+ var OUTPUT_INDENT_UNIT = " ";
85
+ var SPINNER_FRAME_INTERVAL_MS = 80;
86
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
87
+ var STATUS_ICONS = {
88
+ changed: pc.yellow("\u21BA"),
89
+ failed: pc.red("\u2717"),
90
+ ok: pc.green("\u2713"),
91
+ skipped: pc.dim("\u2298"),
92
+ waiting: pc.cyan("\u23F8")
93
+ };
94
+ var CLI_HEADER_LINES = [
95
+ " _ _ ",
96
+ " | | (_) ",
97
+ " _ __ __ _ _ __ __ _| |_ ___ __",
98
+ " | '_ \\ / _` | '__/ _` | __| \\ \\/ /",
99
+ " | |_) | (_| | | | (_| | |_| |> < ",
100
+ " | .__/ \\__,_|_| \\__,_|\\__|_/_/\\_\\",
101
+ " | | ",
102
+ " |_| "
103
+ ];
104
+ var activeSpinner = null;
105
+ var recipeOutputDepth = -1;
106
+ function renderCliHeader(version) {
107
+ const versionText = pc.dim(`v${version}`);
108
+ return `${pc.cyan(CLI_HEADER_LINES.join("\n"))}${versionText}
109
+ `;
110
+ }
111
+ function printCliHeader(version) {
112
+ console.log(renderCliHeader(version));
113
+ }
114
+ function supportsAnimatedModuleOutput() {
115
+ return process.stdout.isTTY && typeof process.stdout.clearLine === "function" && typeof process.stdout.cursorTo === "function";
116
+ }
117
+ function getModuleIcon(status, waitingFrame) {
118
+ return status === "waiting" ? pc.cyan(waitingFrame ?? "|") : STATUS_ICONS[status];
119
+ }
120
+ function getModuleStatusText(status) {
121
+ switch (status) {
122
+ case "changed": {
123
+ return pc.yellow(status);
124
+ }
125
+ case "failed": {
126
+ return pc.red(status);
127
+ }
128
+ case "ok": {
129
+ return pc.green(status);
130
+ }
131
+ case "skipped": {
132
+ return pc.dim(status);
133
+ }
134
+ case "waiting": {
135
+ return pc.cyan("running");
136
+ }
137
+ }
138
+ }
139
+ function getCurrentOutputDepth() {
140
+ return Math.max(recipeOutputDepth, 0);
141
+ }
142
+ function getModuleIndent() {
143
+ return OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1);
144
+ }
145
+ function getRecipeHeaderIndent() {
146
+ return recipeOutputDepth < 0 ? "" : OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1);
147
+ }
148
+ function getErrorIndent() {
149
+ return `${getModuleIndent()}\u2502 `;
150
+ }
151
+ function getContinuationIndent() {
152
+ return `${getModuleIndent()} `;
153
+ }
154
+ async function withRecipeOutputScope(scopedOperation) {
155
+ recipeOutputDepth += 1;
156
+ try {
157
+ return await scopedOperation();
158
+ } finally {
159
+ recipeOutputDepth -= 1;
160
+ }
161
+ }
162
+ function renderModuleLine(parameters) {
163
+ const { detail, name, status, waitingFrame } = parameters;
164
+ const indent = getModuleIndent();
165
+ const icon = getModuleIcon(status, waitingFrame);
166
+ const statusText = getModuleStatusText(status);
167
+ const detailSuffix = detail == null ? "" : ` ${pc.dim(detail)}`;
168
+ const alignedNameWidth = Math.max(MODULE_NAME_WIDTH - indent.length, MIN_MODULE_NAME_WIDTH);
169
+ return `${indent}${icon} ${name.padEnd(alignedNameWidth)} ${statusText}${detailSuffix}`;
170
+ }
171
+ function writeAnimatedModuleLine(line) {
172
+ process.stdout.clearLine(0);
173
+ process.stdout.cursorTo(0);
174
+ process.stdout.write(fitAnimatedModuleLine(line, process.stdout.columns));
175
+ }
176
+ function stopAnimatedModuleLine(clearCurrentLine = false) {
177
+ if (activeSpinner == null) return;
178
+ clearInterval(activeSpinner.interval);
179
+ activeSpinner = null;
180
+ if (clearCurrentLine && supportsAnimatedModuleOutput()) {
181
+ process.stdout.clearLine(0);
182
+ process.stdout.cursorTo(0);
183
+ }
184
+ }
185
+ function startModuleSpinner(name, detail) {
186
+ if (!supportsAnimatedModuleOutput()) return;
187
+ stopAnimatedModuleLine();
188
+ const displayModule = formatDisplayModule({
189
+ continuationIndentWidth: getContinuationIndent().length,
190
+ detail,
191
+ name,
192
+ status: "waiting",
193
+ terminalColumns: process.stdout.columns
194
+ });
195
+ const spinner = {
196
+ detail: displayModule.detail,
197
+ frameIndex: 0,
198
+ interval: setInterval(() => {
199
+ spinner.frameIndex = (spinner.frameIndex + 1) % SPINNER_FRAMES.length;
200
+ writeAnimatedModuleLine(
201
+ renderModuleLine({
202
+ detail: spinner.detail,
203
+ name: displayModule.name,
204
+ status: "waiting",
205
+ waitingFrame: SPINNER_FRAMES[spinner.frameIndex]
206
+ })
207
+ );
208
+ }, SPINNER_FRAME_INTERVAL_MS)
209
+ };
210
+ activeSpinner = spinner;
211
+ writeAnimatedModuleLine(
212
+ renderModuleLine({
213
+ detail: displayModule.detail,
214
+ name: displayModule.name,
215
+ status: "waiting",
216
+ waitingFrame: SPINNER_FRAMES[0]
217
+ })
218
+ );
219
+ }
220
+ function printRecipeHeader(name) {
221
+ stopAnimatedModuleLine(true);
222
+ const header = pc.bold(pc.blue(`[${name}]`));
223
+ console.log(`${getRecipeHeaderIndent()}${header}`);
224
+ }
225
+ function printRunContext(parameters) {
226
+ const mode = parameters.dryRun ? pc.yellow("dry-run") : pc.green("apply");
227
+ const ports = parameters.ports.join(", ");
228
+ console.log(
229
+ pc.dim(`Run ${parameters.name} \xB7 host ${parameters.host} \xB7 ports ${ports} \xB7 mode ${mode}`)
230
+ );
231
+ }
232
+ function printModuleResult(name, status, detail) {
233
+ const displayModule = formatDisplayModule({
234
+ continuationIndentWidth: getContinuationIndent().length,
235
+ detail,
236
+ name,
237
+ status,
238
+ terminalColumns: process.stdout.columns
239
+ });
240
+ const line = renderModuleLine({
241
+ detail: displayModule.detail,
242
+ name: displayModule.name,
243
+ status
244
+ });
245
+ if (supportsAnimatedModuleOutput() && activeSpinner != null) {
246
+ stopAnimatedModuleLine();
247
+ writeAnimatedModuleLine(line);
248
+ process.stdout.write("\n");
249
+ for (const detailLine of displayModule.detailLines) {
250
+ process.stdout.write(`${getContinuationIndent()}${pc.dim(detailLine)}
251
+ `);
252
+ }
253
+ return;
254
+ }
255
+ console.log(line);
256
+ for (const detailLine of displayModule.detailLines) {
257
+ console.log(`${getContinuationIndent()}${pc.dim(detailLine)}`);
258
+ }
259
+ }
260
+ function printCommandError(stdout, stderr) {
261
+ const lines = [];
262
+ if (stderr.trim()) {
263
+ lines.push(...stderr.trim().split("\n"));
264
+ }
265
+ if (stdout.trim()) {
266
+ lines.push(...stdout.trim().split("\n"));
267
+ }
268
+ if (lines.length > 0) {
269
+ console.error(pc.red(`${getErrorIndent()}Error output:`));
270
+ for (const line of lines) {
271
+ console.error(pc.red(`${getErrorIndent()}${line}`));
272
+ }
273
+ }
274
+ }
275
+ function printVerboseCommandError(stdout, stderr) {
276
+ if (stderr.trim()) {
277
+ console.error(pc.red(`${getErrorIndent()}Full stderr:`));
278
+ for (const line of stderr.trim().split("\n")) {
279
+ console.error(pc.red(`${getErrorIndent()}${line}`));
280
+ }
281
+ }
282
+ if (stdout.trim()) {
283
+ console.error(pc.red(`${getErrorIndent()}Full stdout:`));
284
+ for (const line of stdout.trim().split("\n")) {
285
+ console.error(pc.red(`${getErrorIndent()}${line}`));
286
+ }
287
+ }
288
+ }
289
+ function printVerboseErrorBlock(label, content) {
290
+ if (!content.trim()) {
291
+ return;
292
+ }
293
+ console.error(pc.red(`${getErrorIndent()}${label}`));
294
+ for (const line of content.trim().split("\n")) {
295
+ console.error(pc.red(`${getErrorIndent()}${line}`));
296
+ }
297
+ }
298
+ function getErrorCause(error) {
299
+ return error.cause;
300
+ }
301
+ function printVerboseErrorCause(cause, depth) {
302
+ const label = `Cause ${depth}:`;
303
+ if (cause instanceof Error) {
304
+ const stack = cause.stack?.trim() ?? "";
305
+ const stackOrMessage = stack.length > 0 ? stack : String(cause);
306
+ printVerboseErrorBlock(label, stackOrMessage);
307
+ const nestedCause = getErrorCause(cause);
308
+ if (nestedCause !== void 0) {
309
+ printVerboseErrorCause(nestedCause, depth + 1);
310
+ }
311
+ return;
312
+ }
313
+ printVerboseErrorBlock(label, String(cause));
314
+ }
315
+ function printVerboseGenericError(error) {
316
+ const stack = error.stack?.trim() ?? "";
317
+ const stackOrMessage = stack.length > 0 ? stack : String(error);
318
+ printVerboseErrorBlock("Full stack:", stackOrMessage);
319
+ const cause = getErrorCause(error);
320
+ if (cause !== void 0) {
321
+ printVerboseErrorCause(cause, 1);
322
+ }
323
+ }
324
+ function printCommandFailure(error, verbose) {
325
+ if (verbose && error instanceof CommandError) {
326
+ const summaryLine = error.message.split("\n")[0];
327
+ printCommandError("", summaryLine);
328
+ printVerboseCommandError(error.fullStdout, error.fullStderr);
329
+ return;
330
+ }
331
+ printCommandError("", String(error));
332
+ if (verbose && error instanceof Error) {
333
+ printVerboseGenericError(error);
334
+ }
335
+ }
336
+ function printSummary(stats) {
337
+ const parts = [
338
+ pc.yellow(`${stats.changed} changed`),
339
+ pc.green(`${stats.ok} ok`),
340
+ pc.dim(`${stats.skipped} skipped`),
341
+ stats.failed > 0 ? pc.red(`${stats.failed} failed`) : `${stats.failed} failed`,
342
+ pc.cyan(`${stats.signals} signals triggered`)
343
+ ];
344
+ console.log(`
345
+ ${parts.join(pc.dim(" \xB7 "))}`);
346
+ }
347
+
348
+ // src/signalOrchestration.ts
349
+ function handleSignalResult(parameters) {
350
+ const { hooks, result, signalName, verbose } = parameters;
351
+ printModuleResult(`signal: ${signalName}`, result.status);
352
+ if (result.status === "failed" && result.error != null) {
353
+ printCommandFailure(result.error, verbose);
354
+ }
355
+ hooks?.onSignalFinished?.(result.status);
356
+ return result.status === "failed" ? "failed" : "changed";
357
+ }
358
+ function handleSignalFailure(parameters) {
359
+ const { error, hooks, signalName, verbose } = parameters;
360
+ printModuleResult(`signal: ${signalName}`, "failed");
361
+ printCommandFailure(error, verbose);
362
+ hooks?.onSignalFinished?.("failed");
363
+ return "failed";
364
+ }
365
+ async function applySignalMeta(parameters) {
366
+ assertValidModuleMetaEntries(parameters.result.meta);
367
+ const nextEnvironment = await mergeEnvironmentFromMeta(
368
+ parameters.currentEnvironment,
369
+ parameters.result.meta
370
+ );
371
+ await parameters.onSignalStep?.({
372
+ env: nextEnvironment,
373
+ meta: parameters.result.meta,
374
+ status: parameters.result.status
375
+ });
376
+ return nextEnvironment;
377
+ }
378
+ async function runOneSignal(parameters) {
379
+ const connection = parameters.signal.local === true ? null : parameters.ssh;
380
+ startModuleSpinner(`signal: ${parameters.signal.name}`);
381
+ const result = await parameters.signal.apply(connection, parameters.currentEnvironment);
382
+ const nextEnvironment = await applySignalMeta({
383
+ currentEnvironment: parameters.currentEnvironment,
384
+ onSignalStep: parameters.onSignalStep,
385
+ result
386
+ });
387
+ return {
388
+ nextEnvironment,
389
+ status: handleSignalResult({
390
+ hooks: parameters.hooks,
391
+ result,
392
+ signalName: parameters.signal.name,
393
+ verbose: parameters.verbose
394
+ })
395
+ };
396
+ }
397
+ async function runSignalModules(parameters) {
398
+ const getShutdownSignal = parameters.shutdownSignal ?? (() => null);
399
+ const verbose = parameters.verbose ?? false;
400
+ let currentEnvironment = parameters.environment;
401
+ let status = "changed";
402
+ for (const signal of parameters.signals) {
403
+ if (getShutdownSignal() != null) break;
404
+ parameters.hooks?.onSignalStarted?.();
405
+ try {
406
+ const signalStep = await runOneSignal({
407
+ currentEnvironment,
408
+ hooks: parameters.hooks,
409
+ onSignalStep: parameters.onSignalStep,
410
+ signal,
411
+ ssh: parameters.ssh,
412
+ verbose
413
+ });
414
+ currentEnvironment = signalStep.nextEnvironment;
415
+ if (signalStep.status === "failed") status = "failed";
416
+ } catch (error) {
417
+ status = handleSignalFailure({
418
+ error,
419
+ hooks: parameters.hooks,
420
+ signalName: signal.name,
421
+ verbose
422
+ });
423
+ }
424
+ }
425
+ return status;
426
+ }
427
+
428
+ export {
429
+ printCliHeader,
430
+ withRecipeOutputScope,
431
+ startModuleSpinner,
432
+ printRecipeHeader,
433
+ printRunContext,
434
+ printModuleResult,
435
+ printCommandFailure,
436
+ printSummary,
437
+ runSignalModules
438
+ };
439
+ //# sourceMappingURL=chunk-DUIGEB2J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/output.ts","../src/outputFormatting.ts","../src/signalOrchestration.ts"],"sourcesContent":["import pc from \"picocolors\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\nimport { fitAnimatedModuleLine, formatDisplayModule } from \"./outputFormatting.js\"\nimport { CommandError } from \"./sshHelpers.js\"\n\nconst MODULE_NAME_WIDTH = 56\nconst MIN_MODULE_NAME_WIDTH = 12\nconst OUTPUT_INDENT_UNIT = \" \"\nconst SPINNER_FRAME_INTERVAL_MS = 80\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst STATUS_ICONS: Record<DisplayStatus, string> = {\n changed: pc.yellow(\"\\u21ba\"),\n failed: pc.red(\"\\u2717\"),\n ok: pc.green(\"\\u2713\"),\n skipped: pc.dim(\"\\u2298\"),\n waiting: pc.cyan(\"\\u23f8\"),\n}\n\nconst CLI_HEADER_LINES = [\n \" _ _ \",\n \" | | (_) \",\n \" _ __ __ _ _ __ __ _| |_ ___ __\",\n \" | '_ \\\\ / _` | '__/ _` | __| \\\\ \\\\/ /\",\n \" | |_) | (_| | | | (_| | |_| |> < \",\n \" | .__/ \\\\__,_|_| \\\\__,_|\\\\__|_/_/\\\\_\\\\\",\n \" | | \",\n \" |_| \",\n]\n\ntype ActiveSpinner = {\n detail?: string\n frameIndex: number\n interval: NodeJS.Timeout\n}\n\nlet activeSpinner: ActiveSpinner | null = null\nlet recipeOutputDepth = -1\n\nexport function renderCliHeader(version: string): string {\n const versionText = pc.dim(`v${version}`)\n return `${pc.cyan(CLI_HEADER_LINES.join(\"\\n\"))}${versionText}\\n`\n}\n\nexport function printCliHeader(version: string): void {\n console.log(renderCliHeader(version))\n}\n\nfunction supportsAnimatedModuleOutput(): boolean {\n return (\n process.stdout.isTTY &&\n typeof process.stdout.clearLine === \"function\" &&\n typeof process.stdout.cursorTo === \"function\"\n )\n}\n\nfunction getModuleIcon(status: DisplayStatus, waitingFrame?: string): string {\n return status === \"waiting\" ? pc.cyan(waitingFrame ?? \"|\") : STATUS_ICONS[status]\n}\n\nfunction getModuleStatusText(status: DisplayStatus): string {\n switch (status) {\n case \"changed\": {\n return pc.yellow(status)\n }\n case \"failed\": {\n return pc.red(status)\n }\n case \"ok\": {\n return pc.green(status)\n }\n case \"skipped\": {\n return pc.dim(status)\n }\n case \"waiting\": {\n return pc.cyan(\"running\")\n }\n }\n}\n\nfunction getCurrentOutputDepth(): number {\n return Math.max(recipeOutputDepth, 0)\n}\n\nfunction getModuleIndent(): string {\n return OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1)\n}\n\nfunction getRecipeHeaderIndent(): string {\n return recipeOutputDepth < 0 ? \"\" : OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1)\n}\n\nfunction getErrorIndent(): string {\n return `${getModuleIndent()}│ `\n}\n\nfunction getContinuationIndent(): string {\n return `${getModuleIndent()} `\n}\n\nexport async function withRecipeOutputScope<T>(\n scopedOperation: () => Promise<T> | T\n): Promise<T> {\n recipeOutputDepth += 1\n try {\n return await scopedOperation()\n } finally {\n recipeOutputDepth -= 1\n }\n}\n\nfunction renderModuleLine(parameters: {\n detail?: string\n name: string\n status: DisplayStatus\n waitingFrame?: string\n}): string {\n const { detail, name, status, waitingFrame } = parameters\n const indent = getModuleIndent()\n const icon = getModuleIcon(status, waitingFrame)\n const statusText = getModuleStatusText(status)\n const detailSuffix = detail == null ? \"\" : ` ${pc.dim(detail)}`\n const alignedNameWidth = Math.max(MODULE_NAME_WIDTH - indent.length, MIN_MODULE_NAME_WIDTH)\n return `${indent}${icon} ${name.padEnd(alignedNameWidth)} ${statusText}${detailSuffix}`\n}\n\nfunction writeAnimatedModuleLine(line: string): void {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n process.stdout.write(fitAnimatedModuleLine(line, process.stdout.columns))\n}\n\nfunction stopAnimatedModuleLine(clearCurrentLine = false): void {\n if (activeSpinner == null) return\n\n clearInterval(activeSpinner.interval)\n activeSpinner = null\n\n if (clearCurrentLine && supportsAnimatedModuleOutput()) {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n }\n}\n\nexport function startModuleSpinner(name: string, detail?: string): void {\n if (!supportsAnimatedModuleOutput()) return\n\n stopAnimatedModuleLine()\n const displayModule = formatDisplayModule({\n continuationIndentWidth: getContinuationIndent().length,\n detail,\n name,\n status: \"waiting\",\n terminalColumns: process.stdout.columns,\n })\n\n const spinner: ActiveSpinner = {\n detail: displayModule.detail,\n frameIndex: 0,\n interval: setInterval(() => {\n spinner.frameIndex = (spinner.frameIndex + 1) % SPINNER_FRAMES.length\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: spinner.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[spinner.frameIndex],\n })\n )\n }, SPINNER_FRAME_INTERVAL_MS),\n }\n\n activeSpinner = spinner\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: displayModule.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[0],\n })\n )\n}\n\nexport function resetLiveOutputForTests(): void {\n stopAnimatedModuleLine()\n recipeOutputDepth = -1\n}\n\n/**\n * Print a bold, colored header line marking the start of a recipe run.\n * @param name - The recipe or server name to display.\n */\nexport function printRecipeHeader(name: string): void {\n stopAnimatedModuleLine(true)\n const header = pc.bold(pc.blue(`[${name}]`))\n console.log(`${getRecipeHeaderIndent()}${header}`)\n}\n\nexport function printRunContext(parameters: {\n dryRun: boolean\n host: string\n name: string\n ports: number[]\n}): void {\n const mode = parameters.dryRun ? pc.yellow(\"dry-run\") : pc.green(\"apply\")\n const ports = parameters.ports.join(\", \")\n console.log(\n pc.dim(`Run ${parameters.name} · host ${parameters.host} · ports ${ports} · mode ${mode}`)\n )\n}\n\n/**\n * Print a single module result row with a status icon, name, and colored status label.\n *\n * @param name - The module name shown in the left column.\n * @param status - One of the known status strings (`ok`, `changed`, `skipped`, `failed`).\n * @param detail - Optional short detail appended in dim text after the status.\n */\nexport function printModuleResult(name: string, status: DisplayStatus, detail?: string): void {\n const displayModule = formatDisplayModule({\n continuationIndentWidth: getContinuationIndent().length,\n detail,\n name,\n status,\n terminalColumns: process.stdout.columns,\n })\n const line = renderModuleLine({\n detail: displayModule.detail,\n name: displayModule.name,\n status,\n })\n if (supportsAnimatedModuleOutput() && activeSpinner != null) {\n stopAnimatedModuleLine()\n writeAnimatedModuleLine(line)\n process.stdout.write(\"\\n\")\n for (const detailLine of displayModule.detailLines) {\n process.stdout.write(`${getContinuationIndent()}${pc.dim(detailLine)}\\n`)\n }\n return\n }\n\n console.log(line)\n for (const detailLine of displayModule.detailLines) {\n console.log(`${getContinuationIndent()}${pc.dim(detailLine)}`)\n }\n}\n\n/**\n * Print captured stderr and stdout from a failed command in a red bordered block.\n * Outputs nothing if both streams are empty.\n *\n * @param stdout - Captured standard output of the failed command.\n * @param stderr - Captured standard error of the failed command.\n */\nexport function printCommandError(stdout: string, stderr: string): void {\n const lines: string[] = []\n if (stderr.trim()) {\n lines.push(...stderr.trim().split(\"\\n\"))\n }\n if (stdout.trim()) {\n lines.push(...stdout.trim().split(\"\\n\"))\n }\n if (lines.length > 0) {\n console.error(pc.red(`${getErrorIndent()}Error output:`))\n for (const line of lines) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\n/**\n * Print the full (untruncated) stdout and stderr of a failed command.\n * Used in verbose mode to show the complete output that was truncated in the error message.\n *\n * @param stdout - Full standard output of the failed command.\n * @param stderr - Full standard error of the failed command.\n */\nexport function printVerboseCommandError(stdout: string, stderr: string): void {\n if (stderr.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stderr:`))\n for (const line of stderr.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n if (stdout.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stdout:`))\n for (const line of stdout.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\nfunction printVerboseErrorBlock(label: string, content: string): void {\n if (!content.trim()) {\n return\n }\n\n console.error(pc.red(`${getErrorIndent()}${label}`))\n for (const line of content.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n}\n\nfunction getErrorCause(error: Error): unknown {\n return (error as { cause?: unknown } & Error).cause\n}\n\nfunction printVerboseErrorCause(cause: unknown, depth: number): void {\n const label = `Cause ${depth}:`\n if (cause instanceof Error) {\n const stack = cause.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(cause)\n printVerboseErrorBlock(label, stackOrMessage)\n const nestedCause = getErrorCause(cause)\n if (nestedCause !== undefined) {\n printVerboseErrorCause(nestedCause, depth + 1)\n }\n return\n }\n\n printVerboseErrorBlock(label, String(cause))\n}\n\nfunction printVerboseGenericError(error: Error): void {\n const stack = error.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(error)\n printVerboseErrorBlock(\"Full stack:\", stackOrMessage)\n const cause = getErrorCause(error)\n if (cause !== undefined) {\n printVerboseErrorCause(cause, 1)\n }\n}\n\n/**\n * Print the error message of a failed command and, when verbose mode is active\n * and the error is a {@link CommandError}, the full untruncated output.\n *\n * @param error - The caught error value.\n * @param verbose - Whether to show full stdout/stderr.\n */\nexport function printCommandFailure(error: unknown, verbose: boolean): void {\n if (verbose && error instanceof CommandError) {\n // Print only the exit-code line, skip the truncated output and hint\n const summaryLine = error.message.split(\"\\n\")[0]\n printCommandError(\"\", summaryLine)\n printVerboseCommandError(error.fullStdout, error.fullStderr)\n return\n }\n\n printCommandError(\"\", String(error))\n if (verbose && error instanceof Error) {\n printVerboseGenericError(error)\n }\n}\n\n/**\n * Print a run summary line with counts for each status category.\n *\n * @param stats - Aggregated counts from the completed run.\n * @param stats.changed - Number of modules that changed state.\n * @param stats.ok - Number of modules already in desired state.\n * @param stats.skipped - Number of modules skipped.\n * @param stats.failed - Number of modules that failed.\n * @param stats.signals - Number of signals triggered.\n */\nexport function printSummary(stats: {\n changed: number\n failed: number\n ok: number\n signals: number\n skipped: number\n}): void {\n const parts = [\n pc.yellow(`${stats.changed} changed`),\n pc.green(`${stats.ok} ok`),\n pc.dim(`${stats.skipped} skipped`),\n stats.failed > 0 ? pc.red(`${stats.failed} failed`) : `${stats.failed} failed`,\n pc.cyan(`${stats.signals} signals triggered`),\n ]\n console.log(`\\n${parts.join(pc.dim(\" \\u00b7 \"))}`)\n}\n","import { stripVTControlCharacters } from \"node:util\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst PACKAGE_MODULE_SUFFIX_LENGTH = 2\nconst PACKAGE_COLUMN_GAP_WIDTH = 2\nconst DEFAULT_TERMINAL_COLUMNS = 100\nconst MIN_PACKAGE_COLUMNS_WIDTH = 24\nconst MIN_PACKAGE_COLUMN_WIDTH = 18\nconst MIN_ANIMATED_LINE_COLUMNS = 8\n\nexport type DisplayModule = {\n detail?: string\n detailLines: string[]\n name: string\n}\n\nfunction splitPackageModuleName(name: string): { packages: string[]; summaryName: string } | null {\n for (const prefix of [\"package.installed: \", \"package.absent: \"]) {\n if (!name.startsWith(prefix)) continue\n\n const packages = name\n .slice(prefix.length)\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n if (packages.length === 0) return null\n\n return {\n packages,\n summaryName: prefix.slice(0, -PACKAGE_MODULE_SUFFIX_LENGTH),\n }\n }\n\n return null\n}\n\nfunction getPackageColumns(parameters: {\n continuationIndentWidth: number\n packages: string[]\n terminalColumns?: number\n}): string[] {\n if (parameters.packages.length <= 1) return parameters.packages\n\n const availableWidth = Math.max(\n (parameters.terminalColumns ?? DEFAULT_TERMINAL_COLUMNS) - parameters.continuationIndentWidth,\n MIN_PACKAGE_COLUMNS_WIDTH\n )\n const widestPackage = Math.max(...parameters.packages.map((entry) => entry.length))\n const columnWidth = Math.max(widestPackage + PACKAGE_COLUMN_GAP_WIDTH, MIN_PACKAGE_COLUMN_WIDTH)\n const columnCount = Math.max(Math.floor(availableWidth / columnWidth), 1)\n const rowCount = Math.ceil(parameters.packages.length / columnCount)\n\n return Array.from({ length: rowCount }, (_row, rowIndex) =>\n Array.from({ length: columnCount }, (_column, columnIndex) => {\n const packageIndex = rowIndex + columnIndex * rowCount\n const packageName = parameters.packages.at(packageIndex)\n if (packageName == null) return \"\"\n const isLastVisibleColumn =\n columnIndex === columnCount - 1 || packageIndex + rowCount >= parameters.packages.length\n return isLastVisibleColumn ? packageName : packageName.padEnd(columnWidth)\n })\n .filter(Boolean)\n .join(\"\")\n )\n}\n\nfunction getPackageSummaryDetail(packageCount: number, detail?: string): string {\n const packageLabel = packageCount === 1 ? \"1 package\" : `${packageCount} packages`\n return detail == null ? packageLabel : `${packageLabel} · ${detail}`\n}\n\nexport function fitAnimatedModuleLine(line: string, columns?: number): string {\n if (columns === undefined || columns < MIN_ANIMATED_LINE_COLUMNS) return line\n\n const plainLine = stripVTControlCharacters(line)\n if (plainLine.length < columns) return line\n\n return `${plainLine.slice(0, columns - 1)}\\u2026`\n}\n\nexport function formatDisplayModule(parameters: {\n continuationIndentWidth: number\n detail?: string\n name: string\n status: DisplayStatus\n terminalColumns?: number\n}): DisplayModule {\n const packageModule = splitPackageModuleName(parameters.name)\n if (packageModule == null) {\n return {\n detail: parameters.detail,\n detailLines: [],\n name: parameters.name,\n }\n }\n\n return {\n detail: getPackageSummaryDetail(packageModule.packages.length, parameters.detail),\n detailLines:\n parameters.status === \"waiting\"\n ? []\n : getPackageColumns({\n continuationIndentWidth: parameters.continuationIndentWidth,\n packages: packageModule.packages,\n terminalColumns: parameters.terminalColumns,\n }),\n name: packageModule.summaryName,\n }\n}\n","import type {\n Environment,\n Module,\n ModuleStatus,\n OrchestrationStep,\n SshConnection,\n} from \"./types.js\"\n\nimport { assertValidModuleMetaEntries, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { printCommandFailure, printModuleResult, startModuleSpinner } from \"./output.js\"\n\nexport type SignalHooks = {\n onSignalFinished?: (status: ModuleStatus) => void\n onSignalStarted?: () => void\n}\n\nexport type SignalRunStatus = \"changed\" | \"failed\"\n\ntype SignalRunParameters = {\n environment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}\n\nfunction handleSignalResult(parameters: {\n hooks?: SignalHooks\n result: Awaited<ReturnType<Module[\"apply\"]>>\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { hooks, result, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, result.status)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n hooks?.onSignalFinished?.(result.status)\n return result.status === \"failed\" ? \"failed\" : \"changed\"\n}\n\nfunction handleSignalFailure(parameters: {\n error: unknown\n hooks?: SignalHooks\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { error, hooks, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, \"failed\")\n printCommandFailure(error, verbose)\n hooks?.onSignalFinished?.(\"failed\")\n return \"failed\"\n}\n\nasync function applySignalMeta(parameters: {\n currentEnvironment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n result: Awaited<ReturnType<Module[\"apply\"]>>\n}): Promise<Environment> {\n assertValidModuleMetaEntries(parameters.result.meta)\n const nextEnvironment = await mergeEnvironmentFromMeta(\n parameters.currentEnvironment,\n parameters.result.meta\n )\n await parameters.onSignalStep?.({\n env: nextEnvironment,\n meta: parameters.result.meta,\n status: parameters.result.status,\n })\n return nextEnvironment\n}\n\nasync function runOneSignal(parameters: {\n currentEnvironment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n signal: Module\n ssh: null | SshConnection\n verbose: boolean\n}): Promise<{ nextEnvironment: Environment; status: SignalRunStatus }> {\n const connection = parameters.signal.local === true ? null : parameters.ssh\n startModuleSpinner(`signal: ${parameters.signal.name}`)\n const result = await parameters.signal.apply(connection, parameters.currentEnvironment)\n const nextEnvironment = await applySignalMeta({\n currentEnvironment: parameters.currentEnvironment,\n onSignalStep: parameters.onSignalStep,\n result,\n })\n return {\n nextEnvironment,\n status: handleSignalResult({\n hooks: parameters.hooks,\n result,\n signalName: parameters.signal.name,\n verbose: parameters.verbose,\n }),\n }\n}\n\nexport async function runSignalModules(parameters: SignalRunParameters): Promise<SignalRunStatus> {\n const getShutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let currentEnvironment = parameters.environment\n let status: SignalRunStatus = \"changed\"\n\n for (const signal of parameters.signals) {\n if (getShutdownSignal() != null) break\n parameters.hooks?.onSignalStarted?.()\n try {\n // eslint-disable-next-line no-await-in-loop\n const signalStep = await runOneSignal({\n currentEnvironment,\n hooks: parameters.hooks,\n onSignalStep: parameters.onSignalStep,\n signal,\n ssh: parameters.ssh,\n verbose,\n })\n currentEnvironment = signalStep.nextEnvironment\n if (signalStep.status === \"failed\") status = \"failed\"\n } catch (error) {\n status = handleSignalFailure({\n error,\n hooks: parameters.hooks,\n signalName: signal.name,\n verbose,\n })\n }\n }\n\n return status\n}\n"],"mappings":"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","names":[]}