@celilo/cli 0.1.5 → 0.1.6
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/drizzle/0004_caddy_hostname_list.sql +25 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +9 -2
- package/src/ansible/inventory.test.ts +3 -2
- package/src/ansible/inventory.ts +5 -1
- package/src/capabilities/public-web-helpers.test.ts +2 -2
- package/src/capabilities/public-web-publish.test.ts +34 -1
- package/src/cli/cli.test.ts +2 -2
- package/src/cli/command-registry.ts +146 -3
- package/src/cli/command-tree-parser.test.ts +1 -1
- package/src/cli/command-tree-parser.ts +9 -8
- package/src/cli/commands/hook-run.ts +15 -66
- package/src/cli/commands/module-audit.ts +14 -44
- package/src/cli/commands/module-deploy.ts +4 -1
- package/src/cli/commands/module-import-registry.test.ts +115 -0
- package/src/cli/commands/module-import.ts +106 -22
- package/src/cli/commands/module-publish.test.ts +235 -0
- package/src/cli/commands/module-publish.ts +234 -0
- package/src/cli/commands/module-remove.ts +82 -2
- package/src/cli/commands/module-search.ts +57 -0
- package/src/cli/commands/module-secret-get.ts +59 -0
- package/src/cli/commands/module-terraform-unlock.ts +57 -0
- package/src/cli/commands/module-verify.test.ts +59 -0
- package/src/cli/commands/module-verify.ts +53 -0
- package/src/cli/commands/status.ts +30 -20
- package/src/cli/commands/system-audit.test.ts +138 -0
- package/src/cli/commands/system-audit.ts +571 -0
- package/src/cli/commands/system-update.ts +391 -0
- package/src/cli/completion.ts +15 -1
- package/src/cli/fuel-gauge.ts +68 -3
- package/src/cli/generate-zsh-completion.ts +13 -3
- package/src/cli/index.ts +112 -5
- package/src/cli/parser.ts +11 -0
- package/src/cli/prompts.ts +36 -5
- package/src/cli/tui/audit-state.test.ts +246 -0
- package/src/cli/tui/audit-state.ts +525 -0
- package/src/cli/tui/audit-tui.test.tsx +135 -0
- package/src/cli/tui/audit-tui.tsx +624 -0
- package/src/cli/tui/celebration.tsx +29 -0
- package/src/cli/tui/clipboard.test.ts +94 -0
- package/src/cli/tui/clipboard.ts +101 -0
- package/src/cli/tui/icons.ts +22 -0
- package/src/cli/tui/keybar.tsx +65 -0
- package/src/cli/tui/keymap.test.ts +105 -0
- package/src/cli/tui/keymap.ts +70 -0
- package/src/cli/tui/modals/analyzing.tsx +75 -0
- package/src/cli/tui/modals/celebration.tsx +44 -0
- package/src/cli/tui/modals/reaudit-prompt.tsx +35 -0
- package/src/cli/tui/modals/remediate.tsx +44 -0
- package/src/cli/tui/modals.test.ts +137 -0
- package/src/cli/tui/mouse.test.ts +78 -0
- package/src/cli/tui/mouse.ts +114 -0
- package/src/cli/tui/panes/categories.tsx +62 -0
- package/src/cli/tui/panes/command-log.tsx +87 -0
- package/src/cli/tui/panes/detail.tsx +175 -0
- package/src/cli/tui/panes/findings.tsx +97 -0
- package/src/cli/tui/panes/summary.tsx +64 -0
- package/src/cli/tui/spawn.ts +130 -0
- package/src/cli/tui/theme.ts +42 -0
- package/src/cli/tui/wrap.test.ts +43 -0
- package/src/cli/tui/wrap.ts +45 -0
- package/src/cli/types.ts +5 -0
- package/src/db/client.ts +55 -2
- package/src/db/schema.ts +26 -17
- package/src/hooks/capability-loader.ts +133 -73
- package/src/hooks/define-hook.test.ts +9 -1
- package/src/hooks/executor.ts +22 -1
- package/src/hooks/load-hook-config.test.ts +165 -0
- package/src/hooks/load-hook-config.ts +60 -0
- package/src/hooks/logger.ts +42 -12
- package/src/hooks/run-named-hook.ts +128 -0
- package/src/hooks/types.ts +19 -0
- package/src/manifest/ensure-schema.test.ts +115 -0
- package/src/manifest/schema.ts +76 -0
- package/src/module/import.ts +20 -12
- package/src/module/packaging/build.ts +85 -16
- package/src/module/packaging/release-metadata.test.ts +103 -0
- package/src/module/packaging/release-metadata.ts +145 -0
- package/src/registry/client.test.ts +228 -0
- package/src/registry/client.ts +157 -0
- package/src/services/audit/backups.test.ts +233 -0
- package/src/services/audit/backups.ts +128 -0
- package/src/services/audit/capability-abi.test.ts +153 -0
- package/src/services/audit/capability-abi.ts +204 -0
- package/src/services/audit/cli-version.test.ts +60 -0
- package/src/services/audit/cli-version.ts +87 -0
- package/src/services/audit/health.test.ts +84 -0
- package/src/services/audit/health.ts +43 -0
- package/src/services/audit/index.test.ts +99 -0
- package/src/services/audit/index.ts +118 -0
- package/src/services/audit/machines-reachable.test.ts +87 -0
- package/src/services/audit/machines-reachable.ts +87 -0
- package/src/services/audit/module-configs.test.ts +131 -0
- package/src/services/audit/module-configs.ts +80 -0
- package/src/services/audit/module-versions.test.ts +99 -0
- package/src/services/audit/module-versions.ts +154 -0
- package/src/services/audit/schema.test.ts +68 -0
- package/src/services/audit/schema.ts +115 -0
- package/src/services/audit/secrets-decryptable.test.ts +82 -0
- package/src/services/audit/secrets-decryptable.ts +97 -0
- package/src/services/audit/services-credentials.test.ts +54 -0
- package/src/services/audit/services-credentials.ts +64 -0
- package/src/services/audit/services-reachable.test.ts +60 -0
- package/src/services/audit/services-reachable.ts +64 -0
- package/src/services/audit/terraform-plan.test.ts +127 -0
- package/src/services/audit/terraform-plan.ts +153 -0
- package/src/services/audit/types.test.ts +36 -0
- package/src/services/audit/types.ts +90 -0
- package/src/services/audit/unconfigured-modules.test.ts +48 -0
- package/src/services/audit/unconfigured-modules.ts +71 -0
- package/src/services/audit/undeployed-modules.test.ts +66 -0
- package/src/services/audit/undeployed-modules.ts +72 -0
- package/src/services/build-stream.ts +122 -122
- package/src/services/config-interview.ts +407 -2
- package/src/services/deploy-ansible.ts +73 -7
- package/src/services/deploy-preflight.ts +45 -4
- package/src/services/deploy-terraform.ts +31 -24
- package/src/services/deploy-validation.ts +167 -23
- package/src/services/ensure-interview.test.ts +245 -0
- package/src/services/health-runner.ts +110 -38
- package/src/services/module-build.ts +11 -13
- package/src/services/module-deploy.ts +370 -59
- package/src/services/ssh-key-manager.test.ts +1 -1
- package/src/services/ssh-key-manager.ts +3 -2
- package/src/services/terraform-env.ts +62 -0
- package/src/services/update/dep-graph.test.ts +214 -0
- package/src/services/update/dep-graph.ts +215 -0
- package/src/services/update/orchestrator.test.ts +463 -0
- package/src/services/update/orchestrator.ts +359 -0
- package/src/services/update/progress.ts +49 -0
- package/src/services/update/self-update.test.ts +68 -0
- package/src/services/update/self-update.ts +57 -0
- package/src/services/update/types.ts +94 -0
- package/src/templates/generator.test.ts +1 -1
- package/src/templates/generator.ts +42 -1
- package/src/test-utils/completion-harness.test.ts +1 -1
- package/src/test-utils/completion-harness.ts +4 -4
- package/src/variables/capability-self-ref.test.ts +203 -0
- package/src/variables/context.ts +49 -1
- package/src/variables/declarative-derivation.test.ts +306 -0
- package/src/variables/declarative-derivation.ts +4 -2
- package/src/variables/parser.test.ts +56 -1
- package/src/variables/parser.ts +47 -6
- package/src/variables/resolver.ts +27 -9
- package/tsconfig.json +1 -0
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
import { eq } from 'drizzle-orm';
|
|
9
9
|
import { FuelGauge } from '../cli/fuel-gauge';
|
|
10
10
|
import type { DbClient } from '../db/client';
|
|
11
|
-
import {
|
|
11
|
+
import { modules } from '../db/schema';
|
|
12
12
|
import { loadCapabilityFunctions } from '../hooks/capability-loader';
|
|
13
13
|
import { invokeHook } from '../hooks/executor';
|
|
14
|
-
import {
|
|
14
|
+
import { loadHookConfigMap } from '../hooks/load-hook-config';
|
|
15
|
+
import { createCapturingLogger, createConsoleLogger, createGaugeLogger } from '../hooks/logger';
|
|
15
16
|
import type { HookResult } from '../hooks/types';
|
|
16
17
|
import type { ModuleManifest } from '../manifest/schema';
|
|
17
18
|
import { decryptSecret } from '../secrets/encryption';
|
|
18
19
|
import { getOrCreateMasterKey } from '../secrets/master-key';
|
|
19
|
-
import { getMachine } from './machine-pool';
|
|
20
20
|
|
|
21
21
|
export interface HealthCheckItem {
|
|
22
22
|
name: string;
|
|
@@ -34,11 +34,19 @@ export interface HealthCheckResult {
|
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Run health checks for a single module
|
|
37
|
+
*
|
|
38
|
+
* `onProgress` (when supplied) replaces the FuelGauge animation. Used
|
|
39
|
+
* by the audit TUI to stream progress messages into its own UI rather
|
|
40
|
+
* than letting them leak to stdout before the alt-screen takes over.
|
|
37
41
|
*/
|
|
38
42
|
export async function runModuleHealthCheck(
|
|
39
43
|
moduleId: string,
|
|
40
44
|
db: DbClient,
|
|
41
|
-
options: {
|
|
45
|
+
options: {
|
|
46
|
+
debug?: boolean;
|
|
47
|
+
noInteractive?: boolean;
|
|
48
|
+
onProgress?: (msg: string) => void;
|
|
49
|
+
} = {},
|
|
42
50
|
): Promise<HealthCheckResult> {
|
|
43
51
|
const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
|
|
44
52
|
if (!module) {
|
|
@@ -52,14 +60,13 @@ export async function runModuleHealthCheck(
|
|
|
52
60
|
return { moduleId, status: 'no-checks', checks: [] };
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
// Build config
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
configMap[c.key] = c.valueJson ? JSON.parse(c.valueJson) : c.value;
|
|
61
|
-
}
|
|
63
|
+
// Build config + secrets for hook context. The config map shape
|
|
64
|
+
// (including the machine-IP fallback for machine deploys) lives in
|
|
65
|
+
// a single helper so health_check, on_install/on_uninstall via
|
|
66
|
+
// run-named-hook, and capability-loader all see the same thing.
|
|
67
|
+
const configMap = await loadHookConfigMap(moduleId, db);
|
|
62
68
|
|
|
69
|
+
const { secrets } = await import('../db/schema');
|
|
63
70
|
const secretRecords = db.select().from(secrets).where(eq(secrets.moduleId, moduleId)).all();
|
|
64
71
|
const masterKey = await getOrCreateMasterKey();
|
|
65
72
|
const secretMap: Record<string, string> = {};
|
|
@@ -70,20 +77,6 @@ export async function runModuleHealthCheck(
|
|
|
70
77
|
);
|
|
71
78
|
}
|
|
72
79
|
|
|
73
|
-
// Inject machine IP for machine-based deployments
|
|
74
|
-
const infraRecord = db
|
|
75
|
-
.select()
|
|
76
|
-
.from(moduleInfrastructure)
|
|
77
|
-
.where(eq(moduleInfrastructure.moduleId, moduleId))
|
|
78
|
-
.get();
|
|
79
|
-
if (infraRecord?.machineId) {
|
|
80
|
-
const machine = await getMachine(infraRecord.machineId);
|
|
81
|
-
if (machine) {
|
|
82
|
-
configMap['ip.primary'] = machine.ipAddress;
|
|
83
|
-
configMap.target_ip = machine.ipAddress;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
80
|
const requiredCapabilities = manifest.requires.capabilities.map((c) => c.name);
|
|
88
81
|
|
|
89
82
|
// Run the hook. Logger is constructed BEFORE loadCapabilityFunctions
|
|
@@ -104,6 +97,25 @@ export async function runModuleHealthCheck(
|
|
|
104
97
|
logger,
|
|
105
98
|
{ debug: true, capabilities: capabilityFunctions, requiredCapabilities },
|
|
106
99
|
);
|
|
100
|
+
} else if (options.onProgress) {
|
|
101
|
+
// TUI mode: emit a progress message instead of drawing FuelGauge
|
|
102
|
+
// (which writes to stdout and would corrupt the alt-screen render).
|
|
103
|
+
// Use a capturing logger to absorb hook-level log lines that would
|
|
104
|
+
// otherwise leak to stdout via createConsoleLogger.
|
|
105
|
+
options.onProgress(`Checking ${moduleId}`);
|
|
106
|
+
const { logger } = createCapturingLogger();
|
|
107
|
+
const capabilityFunctions = await loadCapabilityFunctions(moduleId, db, logger);
|
|
108
|
+
hookResult = await invokeHook(
|
|
109
|
+
module.sourcePath,
|
|
110
|
+
'health_check',
|
|
111
|
+
manifest.celilo_contract,
|
|
112
|
+
hookDef,
|
|
113
|
+
{},
|
|
114
|
+
configMap,
|
|
115
|
+
secretMap,
|
|
116
|
+
logger,
|
|
117
|
+
{ debug: false, capabilities: capabilityFunctions, requiredCapabilities },
|
|
118
|
+
);
|
|
107
119
|
} else {
|
|
108
120
|
const gauge = new FuelGauge(`Checking ${moduleId}`, {
|
|
109
121
|
skipAnimation: options.noInteractive,
|
|
@@ -162,23 +174,83 @@ export async function runModuleHealthCheck(
|
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
/**
|
|
165
|
-
*
|
|
177
|
+
* Cap on concurrent health checks. Picked small on purpose — the
|
|
178
|
+
* waits are SSH-bound, but each invocation also spins up a hook
|
|
179
|
+
* subprocess locally, and an unbounded fan-out can swamp a low-spec
|
|
180
|
+
* machine or a slow link. Four lets us overlap most network waits
|
|
181
|
+
* without thrashing.
|
|
182
|
+
*/
|
|
183
|
+
const HEALTH_CHECK_CONCURRENCY = 4;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Run `worker` over `items`, with at most `cap` in flight at a time.
|
|
187
|
+
* Results land in input order. `worker` errors are wrapped by the
|
|
188
|
+
* caller — this helper does not catch them itself.
|
|
189
|
+
*/
|
|
190
|
+
async function mapConcurrent<T, R>(
|
|
191
|
+
items: T[],
|
|
192
|
+
cap: number,
|
|
193
|
+
worker: (item: T) => Promise<R>,
|
|
194
|
+
): Promise<R[]> {
|
|
195
|
+
const results = new Array<R>(items.length);
|
|
196
|
+
let cursor = 0;
|
|
197
|
+
async function pump(): Promise<void> {
|
|
198
|
+
while (cursor < items.length) {
|
|
199
|
+
const idx = cursor++;
|
|
200
|
+
results[idx] = await worker(items[idx]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const workers = Array.from({ length: Math.min(cap, items.length) }, pump);
|
|
204
|
+
await Promise.all(workers);
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Run health checks for all deployed modules.
|
|
210
|
+
*
|
|
211
|
+
* Parallelizes per-module checks with a small concurrency cap so SSH
|
|
212
|
+
* waits overlap. Per-module failures are converted into `error`
|
|
213
|
+
* results so one bad module doesn't prevent siblings from being
|
|
214
|
+
* audited.
|
|
166
215
|
*/
|
|
167
216
|
export async function runAllHealthChecks(
|
|
168
217
|
db: DbClient,
|
|
169
|
-
options: { debug?: boolean } = {},
|
|
218
|
+
options: { debug?: boolean; onProgress?: (msg: string) => void } = {},
|
|
170
219
|
): Promise<HealthCheckResult[]> {
|
|
171
|
-
const
|
|
172
|
-
|
|
220
|
+
const eligible = db
|
|
221
|
+
.select()
|
|
222
|
+
.from(modules)
|
|
223
|
+
.all()
|
|
224
|
+
.filter((m) => ['INSTALLED', 'VERIFIED'].includes(m.state));
|
|
173
225
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
226
|
+
// Wrap onProgress with a "done N/M" counter so the TUI surfaces
|
|
227
|
+
// overall progress instead of flickering between the latest of
|
|
228
|
+
// four concurrent in-flight checks.
|
|
229
|
+
const total = eligible.length;
|
|
230
|
+
let done = 0;
|
|
231
|
+
const wrappedOnProgress = options.onProgress
|
|
232
|
+
? (msg: string) => {
|
|
233
|
+
options.onProgress?.(`${msg} (${done}/${total} done)`);
|
|
234
|
+
}
|
|
235
|
+
: undefined;
|
|
236
|
+
const childOptions = { ...options, onProgress: wrappedOnProgress };
|
|
182
237
|
|
|
183
|
-
return
|
|
238
|
+
return mapConcurrent(eligible, HEALTH_CHECK_CONCURRENCY, async (module) => {
|
|
239
|
+
try {
|
|
240
|
+
const result = await runModuleHealthCheck(module.id, db, childOptions);
|
|
241
|
+
done++;
|
|
242
|
+
// Emit one final "N/M done" tick after each module completes,
|
|
243
|
+
// so progress moves even when no new check is starting.
|
|
244
|
+
wrappedOnProgress?.(`Checked ${module.id}`);
|
|
245
|
+
return result;
|
|
246
|
+
} catch (err) {
|
|
247
|
+
done++;
|
|
248
|
+
return {
|
|
249
|
+
moduleId: module.id,
|
|
250
|
+
status: 'error',
|
|
251
|
+
checks: [],
|
|
252
|
+
error: err instanceof Error ? err.message : String(err),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
});
|
|
184
256
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync, statSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { eq } from 'drizzle-orm';
|
|
13
|
+
import { log } from '../cli/prompts';
|
|
13
14
|
import type { DbClient } from '../db/client';
|
|
14
15
|
import { type BuildStatus, moduleBuilds, modules } from '../db/schema';
|
|
15
16
|
import type { ModuleManifest } from '../manifest/schema';
|
|
@@ -90,21 +91,18 @@ async function executeBuildCommand(
|
|
|
90
91
|
useNix: boolean,
|
|
91
92
|
): Promise<{ success: boolean; output: string; error?: string }> {
|
|
92
93
|
const { executeBuildWithProgress } = await import('./build-stream');
|
|
94
|
+
const { shellEscape } = await import('../utils/shell');
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} else {
|
|
101
|
-
command = 'bash';
|
|
102
|
-
args = ['-c', commandStr];
|
|
103
|
-
}
|
|
96
|
+
// build-stream passes through `shell: true`, so spawn re-wraps everything in
|
|
97
|
+
// `/bin/sh -c`. If we sent ['bash', '-c', commandStr] as command+args, sh
|
|
98
|
+
// would re-parse the join — `&&` outside the inner quotes splits the chain
|
|
99
|
+
// and `bun install` runs in modulePath instead of the cd'd subdir. Pass the
|
|
100
|
+
// full command as one string so sh -c gets a single, unsplit shell command.
|
|
101
|
+
const command = useNix ? `nix develop --command bash -c ${shellEscape(commandStr)}` : commandStr;
|
|
104
102
|
|
|
105
103
|
const result = await executeBuildWithProgress({
|
|
106
104
|
command,
|
|
107
|
-
args,
|
|
105
|
+
args: [],
|
|
108
106
|
cwd: modulePath,
|
|
109
107
|
});
|
|
110
108
|
|
|
@@ -308,7 +306,7 @@ export async function buildModuleFromSource(moduleId: string, db: DbClient): Pro
|
|
|
308
306
|
|
|
309
307
|
// Verify artifacts
|
|
310
308
|
if (artifacts && artifacts.length > 0) {
|
|
311
|
-
|
|
309
|
+
log.message('Verifying build artifacts...');
|
|
312
310
|
const verification = verifyBuildArtifacts(artifacts, modulePath);
|
|
313
311
|
const allExist = verification.every((v) => v.exists);
|
|
314
312
|
|
|
@@ -337,7 +335,7 @@ export async function buildModuleFromSource(moduleId: string, db: DbClient): Pro
|
|
|
337
335
|
// Display artifact info
|
|
338
336
|
for (const result of verification) {
|
|
339
337
|
const sizeStr = result.size ? ` (${formatBytes(result.size)})` : '';
|
|
340
|
-
|
|
338
|
+
log.success(`Artifact verified: ${result.path}${sizeStr}`);
|
|
341
339
|
}
|
|
342
340
|
|
|
343
341
|
// Record successful build
|