paratix 0.12.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/{chunk-M3ZQJNKM.js → chunk-XIRORNO3.js} +249 -24
- package/dist/cli.js +2250 -1529
- package/dist/{index-udpAybq3.d.ts → index-DwKdTyHo.d.ts} +25 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +7 -8
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +1 -2
- package/llm-guide.md +17 -17
- package/package.json +4 -2
- package/dist/chunk-M3ZQJNKM.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/modules/index.js.map +0 -1
|
@@ -157,6 +157,17 @@ type Module = {
|
|
|
157
157
|
* @returns `"ok"` if the desired state is already present, `"needs-apply"` otherwise.
|
|
158
158
|
*/
|
|
159
159
|
check: (ssh: null | SshConnection, environment: Environment) => Promise<"needs-apply" | "ok">;
|
|
160
|
+
/**
|
|
161
|
+
* Internal discriminator that distinguishes leaf modules from recipes.
|
|
162
|
+
*
|
|
163
|
+
* Leaf modules leave this `undefined` (treated as `"module"`); recipes set
|
|
164
|
+
* `"recipe"` so the runner can narrow to the recipe contract via the
|
|
165
|
+
* `isRecipe` type guard instead of an unsafe structural cast. Intentionally
|
|
166
|
+
* optional so existing custom modules and playbooks stay source-compatible —
|
|
167
|
+
* they never need to set it.
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
kind?: "module" | "recipe";
|
|
160
171
|
/**
|
|
161
172
|
* When true the module runs locally instead of over SSH.
|
|
162
173
|
* The `ssh` parameter will be `null` in check/apply.
|
|
@@ -240,8 +251,16 @@ type SshConnection = {
|
|
|
240
251
|
probeSudo: () => Promise<void>;
|
|
241
252
|
/** Read the full contents of a remote file as a string. */
|
|
242
253
|
readFile: (remotePath: string) => Promise<string>;
|
|
243
|
-
/**
|
|
244
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Reconnect the SSH session using the current host and registered port
|
|
256
|
+
* candidates. An explicit `reconnectTimeout` in the SSH config always takes
|
|
257
|
+
* precedence; when it is unset, `options.defaultTimeout` (in milliseconds)
|
|
258
|
+
* overrides the generic reconnect default for this call — used by the reboot
|
|
259
|
+
* path to grant a longer window.
|
|
260
|
+
*/
|
|
261
|
+
reconnect: (options?: {
|
|
262
|
+
defaultTimeout?: number;
|
|
263
|
+
}) => Promise<void>;
|
|
245
264
|
/** Remove a previously registered port from the reconnect candidate list. */
|
|
246
265
|
removePort: (port: number) => void;
|
|
247
266
|
/** Return the SHA-256 hex digest of a remote file, or `null` if not found. */
|
|
@@ -250,13 +269,13 @@ type SshConnection = {
|
|
|
250
269
|
test: (command: string) => Promise<boolean>;
|
|
251
270
|
/** Update the target host address (e.g. after a reboot with new IP). */
|
|
252
271
|
updateHost: (host: string) => void;
|
|
253
|
-
/** Upload a local file to the remote host via SFTP. */
|
|
272
|
+
/** Upload a local file to the remote host via SFTP. `options.mode` is optional and defaults to `"0600"`. */
|
|
254
273
|
uploadFile: (localPath: string, remotePath: string, options?: {
|
|
255
274
|
mode?: string;
|
|
256
275
|
}) => Promise<void>;
|
|
257
|
-
/** Write a string to a remote file, creating or overwriting it. */
|
|
258
|
-
writeFile: (remotePath: string, content: string, options
|
|
259
|
-
mode
|
|
276
|
+
/** Write a string to a remote file, creating or overwriting it. `options.mode` is optional and defaults to `"0600"`. */
|
|
277
|
+
writeFile: (remotePath: string, content: string, options?: {
|
|
278
|
+
mode?: string;
|
|
260
279
|
}) => Promise<void>;
|
|
261
280
|
};
|
|
262
281
|
/** SSH connection parameters for a server. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-
|
|
2
|
-
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-
|
|
1
|
+
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-DwKdTyHo.js';
|
|
2
|
+
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-DwKdTyHo.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Fail the run if a condition on the current env is not satisfied.
|
|
@@ -185,11 +185,16 @@ type SignalHooks = {
|
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
187
|
* Internal representation of a recipe module.
|
|
188
|
-
*
|
|
188
|
+
*
|
|
189
|
+
* The `kind: "recipe"` discriminator lets the runner distinguish recipes from
|
|
190
|
+
* leaf modules through the {@link isRecipe} type guard, forming a discriminated
|
|
191
|
+
* union with plain {@link Module} values (which leave `kind` unset). The child
|
|
192
|
+
* `_modules` and optional `_signals` are recipe-specific data that TypeScript
|
|
193
|
+
* narrows to once `isRecipe` confirmed the discriminator — no structural cast
|
|
194
|
+
* is required anywhere.
|
|
189
195
|
* @internal
|
|
190
196
|
*/
|
|
191
197
|
type RecipeModule = {
|
|
192
|
-
_isRecipe: true;
|
|
193
198
|
_modules: Module[];
|
|
194
199
|
_signals?: Module[];
|
|
195
200
|
apply: (ssh: null | SshConnection, environment: Environment, options?: {
|
|
@@ -199,6 +204,7 @@ type RecipeModule = {
|
|
|
199
204
|
signalHooks?: SignalHooks;
|
|
200
205
|
verbose?: boolean;
|
|
201
206
|
}) => Promise<ModuleResult>;
|
|
207
|
+
kind: "recipe";
|
|
202
208
|
} & Module;
|
|
203
209
|
/**
|
|
204
210
|
* Group a list of modules into a named, self-contained recipe.
|
|
@@ -215,8 +221,11 @@ type RecipeModule = {
|
|
|
215
221
|
* @returns A RecipeModule that groups the child modules.
|
|
216
222
|
*
|
|
217
223
|
* @example
|
|
224
|
+
* import { recipe } from "paratix"
|
|
225
|
+
* import { package as pkg, file, service } from "paratix/modules"
|
|
226
|
+
*
|
|
218
227
|
* export const nginxRecipe = recipe("nginx", [
|
|
219
|
-
*
|
|
228
|
+
* pkg.installed("nginx"),
|
|
220
229
|
* file.template("/etc/nginx/nginx.conf", "./files/nginx.conf.tmpl"),
|
|
221
230
|
* service.enabled("nginx"),
|
|
222
231
|
* ], {
|
|
@@ -239,11 +248,14 @@ declare function recipe(name: string, modules: Module[], options?: {
|
|
|
239
248
|
* @throws {Error} When any required field is missing or empty.
|
|
240
249
|
*
|
|
241
250
|
* @example
|
|
251
|
+
* import { server } from "paratix"
|
|
252
|
+
* import { package as pkg } from "paratix/modules"
|
|
253
|
+
*
|
|
242
254
|
* export default server({
|
|
243
255
|
* name: "web-01",
|
|
244
256
|
* host: "10.0.0.1",
|
|
245
257
|
* ssh: { user: "root", ports: [22], privateKey: "~/.ssh/id_ed25519" }, // "~" is expanded
|
|
246
|
-
* run: [
|
|
258
|
+
* run: [pkg.installed("nginx")],
|
|
247
259
|
* });
|
|
248
260
|
*/
|
|
249
261
|
declare function server(config: ServerDefinition): ServerDefinition;
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
user,
|
|
62
62
|
validateHostLabel,
|
|
63
63
|
validateSshConfig
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-XIRORNO3.js";
|
|
65
65
|
|
|
66
66
|
// src/conditionalModules.ts
|
|
67
67
|
function createConditionalApplyState(environment) {
|
|
@@ -1191,11 +1191,11 @@ async function runSignalModules(parameters) {
|
|
|
1191
1191
|
|
|
1192
1192
|
// src/recipe.ts
|
|
1193
1193
|
var INTERRUPTED_BEFORE_APPLY = /* @__PURE__ */ Symbol("recipe-interrupted-before-apply");
|
|
1194
|
-
function
|
|
1195
|
-
return module.
|
|
1194
|
+
function isRecipe(module) {
|
|
1195
|
+
return module.kind === "recipe";
|
|
1196
1196
|
}
|
|
1197
1197
|
function printRecipeChildResult(module, result) {
|
|
1198
|
-
if (
|
|
1198
|
+
if (isRecipe(module)) {
|
|
1199
1199
|
printRecipeModuleResult(module.name, result.status, result.detail);
|
|
1200
1200
|
return;
|
|
1201
1201
|
}
|
|
@@ -1262,7 +1262,7 @@ async function checkRecipeChild(targetModule, connection, currentEnvironment) {
|
|
|
1262
1262
|
return targetModule.check(connection, currentEnvironment);
|
|
1263
1263
|
}
|
|
1264
1264
|
async function applyRecipeChild(parameters) {
|
|
1265
|
-
if (
|
|
1265
|
+
if (isRecipe(parameters.targetModule)) {
|
|
1266
1266
|
return parameters.targetModule.apply(parameters.connection, parameters.currentEnvironment, {
|
|
1267
1267
|
onChildStep: parameters.onChildStep,
|
|
1268
1268
|
onSignalStep: parameters.onSignalStep,
|
|
@@ -1469,7 +1469,6 @@ function createRecipeDryRunApply(name, modules, needsDryRunApply) {
|
|
|
1469
1469
|
const result = await dryRunRecipeModule({
|
|
1470
1470
|
environment,
|
|
1471
1471
|
recipeModule: {
|
|
1472
|
-
_isRecipe: true,
|
|
1473
1472
|
_modules: modules,
|
|
1474
1473
|
async apply() {
|
|
1475
1474
|
await Promise.resolve();
|
|
@@ -1479,6 +1478,7 @@ function createRecipeDryRunApply(name, modules, needsDryRunApply) {
|
|
|
1479
1478
|
await Promise.resolve();
|
|
1480
1479
|
return "ok";
|
|
1481
1480
|
},
|
|
1481
|
+
kind: "recipe",
|
|
1482
1482
|
name
|
|
1483
1483
|
},
|
|
1484
1484
|
shutdownSignal: parameters?.shutdownSignal,
|
|
@@ -1498,7 +1498,6 @@ function recipe(name, modules, options) {
|
|
|
1498
1498
|
...modules.some((module) => module._dryRunBlocker === true) ? { _dryRunBlocker: true } : {},
|
|
1499
1499
|
...modules.some((module) => module._dryRunMetaProducer === true) ? { _dryRunMetaProducer: true } : {},
|
|
1500
1500
|
...applyDryRun == null ? {} : { _applyDryRun: applyDryRun },
|
|
1501
|
-
_isRecipe: true,
|
|
1502
1501
|
_modules: modules,
|
|
1503
1502
|
_signals: options?.signals,
|
|
1504
1503
|
_supportsChildStepHook: true,
|
|
@@ -1526,6 +1525,7 @@ function recipe(name, modules, options) {
|
|
|
1526
1525
|
}
|
|
1527
1526
|
return "ok";
|
|
1528
1527
|
},
|
|
1528
|
+
kind: "recipe",
|
|
1529
1529
|
name
|
|
1530
1530
|
};
|
|
1531
1531
|
}
|
|
@@ -1634,4 +1634,3 @@ export {
|
|
|
1634
1634
|
user,
|
|
1635
1635
|
when
|
|
1636
1636
|
};
|
|
1637
|
-
//# sourceMappingURL=index.js.map
|
package/dist/modules/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { V as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-
|
|
1
|
+
export { V as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-DwKdTyHo.js';
|
package/dist/modules/index.js
CHANGED
package/llm-guide.md
CHANGED
|
@@ -481,23 +481,23 @@ function myCustomModule(configPath: string, content: string): Module {
|
|
|
481
481
|
|
|
482
482
|
Methods available on the `ssh` parameter:
|
|
483
483
|
|
|
484
|
-
| Method
|
|
485
|
-
|
|
|
486
|
-
| `ssh.exec(cmd, options?)`
|
|
487
|
-
| `ssh.test(cmd)`
|
|
488
|
-
| `ssh.output(cmd)`
|
|
489
|
-
| `ssh.lines(cmd)`
|
|
490
|
-
| `ssh.exists(path)`
|
|
491
|
-
| `ssh.readFile(path)`
|
|
492
|
-
| `ssh.writeFile(path, content, { mode: "0644" })`
|
|
493
|
-
| `ssh.uploadFile(local, remote)`
|
|
494
|
-
| `ssh.downloadFile(remote, local)`
|
|
495
|
-
| `ssh.sha256(path)`
|
|
496
|
-
| `ssh.addPort(port)`
|
|
497
|
-
| `ssh.disconnect()`
|
|
498
|
-
| `ssh.getConnectionInfo()`
|
|
499
|
-
| `ssh.probeSudo()`
|
|
500
|
-
| `ssh.updateHost(host)`
|
|
484
|
+
| Method | Return type | Description |
|
|
485
|
+
| ------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
486
|
+
| `ssh.exec(cmd, options?)` | `Promise<ExecResult>` | Run command, get `{ code, stdout, stderr }`. Throws on non-zero unless `ignoreExitCode: true`. |
|
|
487
|
+
| `ssh.test(cmd)` | `Promise<boolean>` | Run command, return `true` if exit code is 0. |
|
|
488
|
+
| `ssh.output(cmd)` | `Promise<string>` | Run command, return trimmed stdout. |
|
|
489
|
+
| `ssh.lines(cmd)` | `Promise<string[]>` | Run command, return stdout split into lines. |
|
|
490
|
+
| `ssh.exists(path)` | `Promise<boolean>` | Check if remote path exists. |
|
|
491
|
+
| `ssh.readFile(path)` | `Promise<string>` | Read remote file content. |
|
|
492
|
+
| `ssh.writeFile(path, content, { mode: "0644" })` | `Promise<void>` | Write content to remote file. `mode` is optional and defaults to `"0600"`. |
|
|
493
|
+
| `ssh.uploadFile(local, remote, { mode: "0644" })` | `Promise<void>` | Upload local file via SFTP. `mode` is optional and defaults to `"0600"`. |
|
|
494
|
+
| `ssh.downloadFile(remote, local)` | `Promise<void>` | Download remote file. |
|
|
495
|
+
| `ssh.sha256(path)` | `Promise<string \| null>` | Get SHA-256 hex digest, or null if not found. |
|
|
496
|
+
| `ssh.addPort(port)` | `void` | Register an additional port opened on the remote host (advanced). |
|
|
497
|
+
| `ssh.disconnect()` | `void` | Close the SSH connection. |
|
|
498
|
+
| `ssh.getConnectionInfo()` | `{ host, port, privateKeyPath?, user }` | Return current connection parameters. |
|
|
499
|
+
| `ssh.probeSudo()` | `Promise<void>` | Probe/cache sudo access; prompts interactively if needed. |
|
|
500
|
+
| `ssh.updateHost(host)` | `void` | Update the target host address (e.g., after IP change). |
|
|
501
501
|
|
|
502
502
|
### ExecOptions
|
|
503
503
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paratix",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Idempotent VPS setup tool in TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "24.13.2",
|
|
25
25
|
"@types/ssh2": "^1.15.4",
|
|
26
|
+
"@vitest/coverage-v8": "4.1.9",
|
|
26
27
|
"tsup": "^8.4.0",
|
|
27
28
|
"typescript": "^6.0.3",
|
|
28
29
|
"vitest": "^4.1.7"
|
|
@@ -56,9 +57,10 @@
|
|
|
56
57
|
"scripts": {
|
|
57
58
|
"build": "tsup",
|
|
58
59
|
"test": "pnpm test:unit && pnpm test:dist",
|
|
60
|
+
"test:coverage": "vitest run --coverage",
|
|
59
61
|
"test:dist": "pnpm build && vitest run --config vitest.distribution.config.ts",
|
|
60
62
|
"test:integration": "pnpm build && vitest run --config vitest.integration.config.ts",
|
|
61
|
-
"test:unit": "vitest run",
|
|
63
|
+
"test:unit": "vitest run --coverage",
|
|
62
64
|
"test:watch": "vitest"
|
|
63
65
|
}
|
|
64
66
|
}
|