@celilo/cli 0.1.4 → 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 +9 -8
- package/src/ansible/inventory.ts +9 -7
- package/src/capabilities/public-web-helpers.test.ts +2 -2
- package/src/capabilities/public-web-publish.test.ts +45 -12
- package/src/capabilities/registration.test.ts +6 -6
- package/src/capabilities/well-known.test.ts +2 -2
- package/src/capabilities/well-known.ts +5 -5
- 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-show.ts +1 -1
- 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.test.ts +3 -3
- package/src/db/schema.ts +26 -17
- package/src/hooks/capability-loader.ts +135 -72
- package/src/hooks/define-hook.test.ts +11 -3
- 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/manifest/template-validator.test.ts +1 -1
- package/src/manifest/template-validator.ts +1 -1
- package/src/manifest/validate.test.ts +1 -1
- package/src/module/import.ts +20 -12
- package/src/module/packaging/build.ts +121 -25
- 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-planner.ts +5 -5
- 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/dns-auto-register.ts +4 -4
- package/src/services/ensure-interview.test.ts +245 -0
- package/src/services/health-runner.ts +110 -38
- package/src/services/infrastructure-variable-resolver.test.ts +1 -1
- package/src/services/infrastructure-variable-resolver.ts +3 -3
- package/src/services/module-build.ts +11 -13
- package/src/services/module-deploy.ts +372 -61
- package/src/services/proxmox-state-recovery.ts +6 -6
- 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 +3 -3
- package/src/templates/generator.ts +43 -2
- 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.test.ts +31 -31
- package/src/variables/context.ts +65 -17
- package/src/variables/declarative-derivation.test.ts +306 -0
- package/src/variables/declarative-derivation.ts +4 -2
- package/src/variables/parser.test.ts +64 -9
- package/src/variables/parser.ts +47 -6
- package/src/variables/resolver.test.ts +14 -14
- package/src/variables/resolver.ts +27 -9
- package/src/variables/types.ts +1 -1
- package/tsconfig.json +1 -0
|
@@ -66,6 +66,32 @@ export const COMMANDS: CommandDef[] = [
|
|
|
66
66
|
name: 'status',
|
|
67
67
|
description: 'Show system and module status',
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: 'audit',
|
|
71
|
+
description: 'Top-level alias for `system audit`',
|
|
72
|
+
flags: [
|
|
73
|
+
{
|
|
74
|
+
name: 'json',
|
|
75
|
+
description: 'Output a stable JSON schema instead of the human-readable table',
|
|
76
|
+
takesValue: false,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'tui',
|
|
80
|
+
description: 'Force the interactive TUI (default when stdout is a terminal)',
|
|
81
|
+
takesValue: false,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'no-tui',
|
|
85
|
+
description: 'Force the static text report even when stdout is a terminal',
|
|
86
|
+
takesValue: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'theme',
|
|
90
|
+
description: 'TUI color theme (dark|light); defaults to dark',
|
|
91
|
+
takesValue: true,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
69
95
|
{
|
|
70
96
|
name: 'capability',
|
|
71
97
|
description: 'View registered module capabilities',
|
|
@@ -87,8 +113,26 @@ export const COMMANDS: CommandDef[] = [
|
|
|
87
113
|
subcommands: [
|
|
88
114
|
{
|
|
89
115
|
name: 'import',
|
|
90
|
-
description: 'Import module
|
|
91
|
-
|
|
116
|
+
description: 'Import a module (file <path> | public-registry <name>)',
|
|
117
|
+
subcommands: [
|
|
118
|
+
{
|
|
119
|
+
name: 'file',
|
|
120
|
+
description: 'Import from local filesystem',
|
|
121
|
+
args: [{ name: 'path', description: 'Module path', completion: 'directories' }],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'public-registry',
|
|
125
|
+
description: 'Import from celilo.computer registry',
|
|
126
|
+
args: [{ name: 'name', description: 'Module name' }],
|
|
127
|
+
flags: [
|
|
128
|
+
{
|
|
129
|
+
name: 'registry',
|
|
130
|
+
description: 'Registry URL (overrides default celilo.computer)',
|
|
131
|
+
takesValue: true,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
92
136
|
flags: [
|
|
93
137
|
{
|
|
94
138
|
name: 'target',
|
|
@@ -141,8 +185,15 @@ export const COMMANDS: CommandDef[] = [
|
|
|
141
185
|
],
|
|
142
186
|
},
|
|
143
187
|
{
|
|
188
|
+
name: 'verify',
|
|
189
|
+
description: 'Verify module integrity (signature + checksums)',
|
|
190
|
+
args: [{ name: 'id', description: 'Module ID', completion: 'module_ids' }],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
// Deprecation alias for `module verify`. Removed after one
|
|
194
|
+
// release cycle. See CELILO_UPDATE D11.
|
|
144
195
|
name: 'audit',
|
|
145
|
-
description: '
|
|
196
|
+
description: 'DEPRECATED — use `module verify` instead',
|
|
146
197
|
args: [{ name: 'id', description: 'Module ID', completion: 'module_ids' }],
|
|
147
198
|
},
|
|
148
199
|
{
|
|
@@ -277,6 +328,41 @@ export const COMMANDS: CommandDef[] = [
|
|
|
277
328
|
},
|
|
278
329
|
],
|
|
279
330
|
},
|
|
331
|
+
{
|
|
332
|
+
name: 'install',
|
|
333
|
+
description: 'Download and import a module from the registry',
|
|
334
|
+
args: [{ name: 'name', description: 'Module name' }],
|
|
335
|
+
flags: [{ name: 'registry', description: 'Registry URL', takesValue: true }],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'search',
|
|
339
|
+
description: 'Search the module registry',
|
|
340
|
+
args: [{ name: 'query', description: 'Search query (optional)' }],
|
|
341
|
+
flags: [
|
|
342
|
+
{ name: 'registry', description: 'Registry URL', takesValue: true },
|
|
343
|
+
{ name: 'limit', description: 'Max results', takesValue: true },
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'publish',
|
|
348
|
+
description: 'Build and publish a module to the registry',
|
|
349
|
+
args: [{ name: 'module-dir', description: 'Module directory', completion: 'directories' }],
|
|
350
|
+
flags: [
|
|
351
|
+
{ name: 'token', description: 'Publish token', takesValue: true },
|
|
352
|
+
{ name: 'registry', description: 'Registry URL', takesValue: true },
|
|
353
|
+
{ name: 'revision', description: 'Package revision number', takesValue: true },
|
|
354
|
+
{
|
|
355
|
+
name: 'message',
|
|
356
|
+
description: 'One-line release note stamped into release.json',
|
|
357
|
+
takesValue: true,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'allow-dirty',
|
|
361
|
+
description: 'Bypass the clean-working-tree check (emergency publish)',
|
|
362
|
+
takesValue: false,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
},
|
|
280
366
|
],
|
|
281
367
|
},
|
|
282
368
|
{
|
|
@@ -500,6 +586,63 @@ export const COMMANDS: CommandDef[] = [
|
|
|
500
586
|
],
|
|
501
587
|
},
|
|
502
588
|
{ name: 'vault-password', description: 'Display Ansible vault password' },
|
|
589
|
+
{
|
|
590
|
+
name: 'audit',
|
|
591
|
+
description: 'Report system-wide drift (no mutations)',
|
|
592
|
+
flags: [
|
|
593
|
+
{
|
|
594
|
+
name: 'json',
|
|
595
|
+
description: 'Output a stable JSON schema instead of the human-readable table',
|
|
596
|
+
takesValue: false,
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: 'tui',
|
|
600
|
+
description: 'Force the interactive TUI (default when stdout is a terminal)',
|
|
601
|
+
takesValue: false,
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: 'no-tui',
|
|
605
|
+
description: 'Force the static text report even when stdout is a terminal',
|
|
606
|
+
takesValue: false,
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
name: 'theme',
|
|
610
|
+
description: 'TUI color theme (dark|light); defaults to dark',
|
|
611
|
+
takesValue: true,
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: 'update',
|
|
617
|
+
description: 'Bring the system to the audit-determined READY state',
|
|
618
|
+
flags: [
|
|
619
|
+
{
|
|
620
|
+
name: 'module',
|
|
621
|
+
description: 'Restrict the run to a single module',
|
|
622
|
+
takesValue: true,
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: 'dry-run',
|
|
626
|
+
description: 'Print the plan without executing',
|
|
627
|
+
takesValue: false,
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: 'no-backup',
|
|
631
|
+
description: 'Skip pre-update backups (requires explicit acknowledgement)',
|
|
632
|
+
takesValue: false,
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: 'allow-destructive',
|
|
636
|
+
description: 'Allow destructive terraform plans through',
|
|
637
|
+
takesValue: false,
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: 'json',
|
|
641
|
+
description: 'Output a stable JSON schema instead of the human-readable summary',
|
|
642
|
+
takesValue: false,
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
},
|
|
503
646
|
],
|
|
504
647
|
},
|
|
505
648
|
{
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Used for automated completion testing to ensure 100% coverage.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import type { CLIContext } from '../test-utils/cli-context';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A node in the command tree
|
|
@@ -21,7 +21,7 @@ export interface CommandNode {
|
|
|
21
21
|
* Parser that discovers command structure from help text
|
|
22
22
|
*/
|
|
23
23
|
export class CommandTreeParser {
|
|
24
|
-
constructor(private cli:
|
|
24
|
+
constructor(private cli: CLIContext) {}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Discover all commands by parsing help output
|
|
@@ -30,15 +30,15 @@ export class CommandTreeParser {
|
|
|
30
30
|
*/
|
|
31
31
|
async discover(): Promise<Map<string, CommandNode>> {
|
|
32
32
|
// Get top-level commands
|
|
33
|
-
const
|
|
34
|
-
const topLevelCommands = this.parseHelpText(
|
|
33
|
+
const helpResult = await this.cli.run('--help');
|
|
34
|
+
const topLevelCommands = this.parseHelpText(helpResult.stdout);
|
|
35
35
|
const tree = new Map<string, CommandNode>();
|
|
36
36
|
|
|
37
37
|
// For each top-level command, discover its subcommands
|
|
38
38
|
for (const cmdName of topLevelCommands) {
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
41
|
-
const subcommandNames = this.parseHelpText(
|
|
40
|
+
const subHelpResult = await this.cli.run(`${cmdName} --help`);
|
|
41
|
+
const subcommandNames = this.parseHelpText(subHelpResult.stdout);
|
|
42
42
|
|
|
43
43
|
// Check if subcommands are actually the same as top-level commands
|
|
44
44
|
// This happens when a command like 'help' just shows the main help again
|
|
@@ -111,10 +111,11 @@ export class CommandTreeParser {
|
|
|
111
111
|
|
|
112
112
|
// Check if we're leaving the Commands section
|
|
113
113
|
// Look for major section headers that appear with little/no indentation
|
|
114
|
-
// Examples: "
|
|
114
|
+
// Examples: "Usage:", "Options:", "Description:", " For command-specific help:"
|
|
115
|
+
// NOTE: Must NOT match nested " Options:" (4-space indent) inside subcommand listings
|
|
115
116
|
if (
|
|
116
117
|
inCommandSection &&
|
|
117
|
-
/^[│\s]{0,
|
|
118
|
+
/^[│\s]{0,3}(Usage|Options|Description|Examples|For|Enable|Related):/i.test(line)
|
|
118
119
|
) {
|
|
119
120
|
inCommandSection = false;
|
|
120
121
|
continue;
|
|
@@ -10,14 +10,11 @@
|
|
|
10
10
|
import { eq } from 'drizzle-orm';
|
|
11
11
|
import { FuelGauge } from '../../cli/fuel-gauge';
|
|
12
12
|
import { getDb } from '../../db/client';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { createGaugeLogger } from '../../hooks/logger';
|
|
13
|
+
import { modules } from '../../db/schema';
|
|
14
|
+
import { createConsoleLogger, createGaugeLogger } from '../../hooks/logger';
|
|
15
|
+
import { runNamedHook } from '../../hooks/run-named-hook';
|
|
16
|
+
import type { HookName } from '../../hooks/types';
|
|
18
17
|
import type { ModuleManifest } from '../../manifest/schema';
|
|
19
|
-
import { decryptSecret } from '../../secrets/encryption';
|
|
20
|
-
import { getOrCreateMasterKey } from '../../secrets/master-key';
|
|
21
18
|
import { getArg, hasFlag, validateRequiredArgs } from '../parser';
|
|
22
19
|
import type { CommandResult } from '../types';
|
|
23
20
|
|
|
@@ -52,19 +49,15 @@ export async function handleHookRun(
|
|
|
52
49
|
const debug = hasFlag(flags, 'debug');
|
|
53
50
|
const db = getDb();
|
|
54
51
|
|
|
55
|
-
//
|
|
52
|
+
// Surface "module not found" / "hook not in manifest" with the same
|
|
53
|
+
// error messages we used to produce inline. The runNamedHook helper
|
|
54
|
+
// returns a structured result we can format here.
|
|
56
55
|
const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
|
|
57
56
|
if (!module) {
|
|
58
|
-
return {
|
|
59
|
-
success: false,
|
|
60
|
-
error: `Module not found: ${moduleId}`,
|
|
61
|
-
};
|
|
57
|
+
return { success: false, error: `Module not found: ${moduleId}` };
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
// Find hook definition in manifest
|
|
65
59
|
const manifest = module.manifestData as ModuleManifest;
|
|
66
|
-
|
|
67
|
-
if (!hookDef) {
|
|
60
|
+
if (!manifest.hooks?.[hookName as keyof typeof manifest.hooks]) {
|
|
68
61
|
const available = Object.keys(manifest.hooks || {});
|
|
69
62
|
return {
|
|
70
63
|
success: false,
|
|
@@ -82,48 +75,12 @@ export async function handleHookRun(
|
|
|
82
75
|
}
|
|
83
76
|
}
|
|
84
77
|
|
|
85
|
-
// Build config map from DB
|
|
86
|
-
const configRecords = db
|
|
87
|
-
.select()
|
|
88
|
-
.from(moduleConfigs)
|
|
89
|
-
.where(eq(moduleConfigs.moduleId, moduleId))
|
|
90
|
-
.all();
|
|
91
|
-
const configMap: Record<string, unknown> = {};
|
|
92
|
-
for (const c of configRecords) {
|
|
93
|
-
configMap[c.key] = c.valueJson ? JSON.parse(c.valueJson) : c.value;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Build secrets map from DB
|
|
97
|
-
const secretRecords = db.select().from(secrets).where(eq(secrets.moduleId, moduleId)).all();
|
|
98
|
-
const masterKey = await getOrCreateMasterKey();
|
|
99
|
-
const secretMap: Record<string, string> = {};
|
|
100
|
-
for (const s of secretRecords) {
|
|
101
|
-
secretMap[s.name] = decryptSecret(
|
|
102
|
-
{ encryptedValue: s.encryptedValue, iv: s.iv, authTag: s.authTag },
|
|
103
|
-
masterKey,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const requiredCapabilities = manifest.requires.capabilities.map((c) => c.name);
|
|
108
|
-
|
|
109
|
-
// Run the hook — use console logger in debug mode, FuelGauge otherwise.
|
|
110
|
-
// The logger is constructed BEFORE loadCapabilityFunctions so the
|
|
111
|
-
// auto-logging wrapper (HOOK_API_V2 D6) can capture it for every
|
|
112
|
-
// capability call.
|
|
113
78
|
if (debug) {
|
|
114
79
|
const logger = createConsoleLogger(moduleId, hookName);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
module.sourcePath,
|
|
118
|
-
hookName,
|
|
119
|
-
manifest.celilo_contract,
|
|
120
|
-
hookDef,
|
|
80
|
+
const result = await runNamedHook(moduleId, hookName as HookName, db, logger, {
|
|
81
|
+
debug: true,
|
|
121
82
|
inputs,
|
|
122
|
-
|
|
123
|
-
secretMap,
|
|
124
|
-
logger,
|
|
125
|
-
{ debug, capabilities: capabilityFunctions, requiredCapabilities },
|
|
126
|
-
);
|
|
83
|
+
});
|
|
127
84
|
|
|
128
85
|
if (!result.success) {
|
|
129
86
|
let errorMsg = result.error || 'Hook execution failed';
|
|
@@ -144,19 +101,11 @@ export async function handleHookRun(
|
|
|
144
101
|
const gauge = new FuelGauge(`Running hook: ${hookName}`);
|
|
145
102
|
gauge.start();
|
|
146
103
|
const logger = createGaugeLogger(gauge, moduleId, hookName);
|
|
147
|
-
const capabilityFunctions = await loadCapabilityFunctions(moduleId, db, logger);
|
|
148
104
|
|
|
149
|
-
const result = await
|
|
150
|
-
|
|
151
|
-
hookName,
|
|
152
|
-
manifest.celilo_contract,
|
|
153
|
-
hookDef,
|
|
105
|
+
const result = await runNamedHook(moduleId, hookName as HookName, db, logger, {
|
|
106
|
+
debug: false,
|
|
154
107
|
inputs,
|
|
155
|
-
|
|
156
|
-
secretMap,
|
|
157
|
-
logger,
|
|
158
|
-
{ debug: false, capabilities: capabilityFunctions, requiredCapabilities },
|
|
159
|
-
);
|
|
108
|
+
});
|
|
160
109
|
|
|
161
110
|
if (!result.success) {
|
|
162
111
|
gauge.stop(false);
|
|
@@ -1,51 +1,21 @@
|
|
|
1
|
-
import { auditModule } from '../../module/packaging/audit';
|
|
2
|
-
import type { CommandResult } from '../types';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
2
|
+
* Deprecation alias: `module audit` → `module verify`.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
4
|
+
* Per CELILO_UPDATE D11, `audit` is now reserved for system-level
|
|
5
|
+
* drift detection (`celilo system audit`). The integrity check that
|
|
6
|
+
* used to live at `module audit` was renamed to `module verify`.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* integration tests.
|
|
8
|
+
* This shim emits a one-line warning to stderr and delegates to
|
|
9
|
+
* `moduleVerify` so existing scripts and tests don't break overnight.
|
|
10
|
+
* Remove after one release cycle.
|
|
13
11
|
*/
|
|
14
|
-
export async function moduleAudit(args: string[]): Promise<CommandResult> {
|
|
15
|
-
if (args.length === 0) {
|
|
16
|
-
return {
|
|
17
|
-
success: false,
|
|
18
|
-
error: 'Module ID is required\n\nUsage: celilo module audit <module-id>',
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const moduleId = args[0];
|
|
23
|
-
|
|
24
|
-
const result = await auditModule(moduleId);
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (result.success) {
|
|
31
|
-
return {
|
|
32
|
-
success: true,
|
|
33
|
-
message: `Module '${moduleId}' passed integrity check\n No violations found.`,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Build a multi-line failure message that includes every violation.
|
|
38
|
-
const violationLines = result.violations.map((v) => {
|
|
39
|
-
const icon = v.type === 'missing' ? '⚠' : v.type === 'modified' ? '✗' : '!';
|
|
40
|
-
return ` ${icon} [${v.type.toUpperCase()}] ${v.message}`;
|
|
41
|
-
});
|
|
13
|
+
import type { CommandResult } from '../types';
|
|
14
|
+
import { moduleVerify } from './module-verify';
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...violationLines,
|
|
49
|
-
].join('\n'),
|
|
50
|
-
};
|
|
16
|
+
export async function moduleAudit(args: string[]): Promise<CommandResult> {
|
|
17
|
+
process.stderr.write(
|
|
18
|
+
'warning: `celilo module audit` is deprecated; use `celilo module verify` instead.\n',
|
|
19
|
+
);
|
|
20
|
+
return moduleVerify(args);
|
|
51
21
|
}
|
|
@@ -63,9 +63,12 @@ export async function handleModuleDeploy(
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Success message is emitted by deployModule via the active ProgressDisplay,
|
|
67
|
+
// so we return an empty message to avoid the top-level clack outro printing
|
|
68
|
+
// a duplicate line in a different style.
|
|
66
69
|
return {
|
|
67
70
|
success: true,
|
|
68
|
-
message:
|
|
71
|
+
message: '',
|
|
69
72
|
data: result.phases,
|
|
70
73
|
};
|
|
71
74
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handlePublicRegistryImport (sparse-protocol registry path).
|
|
3
|
+
*
|
|
4
|
+
* We spy on RegistryClient.prototype methods to control network behavior
|
|
5
|
+
* without making real HTTP calls. The importModule call (DB + filesystem)
|
|
6
|
+
* is exercised by integration tests; here we cover the registry-layer logic:
|
|
7
|
+
* - 404 / unreachable registry
|
|
8
|
+
* - Module not in index
|
|
9
|
+
* - All versions yanked
|
|
10
|
+
* - Correct version is picked (latest non-yanked)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test';
|
|
14
|
+
import type { Mock } from 'bun:test';
|
|
15
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import type { IndexEntry } from '../../registry/client';
|
|
19
|
+
import { RegistryClient } from '../../registry/client';
|
|
20
|
+
import { handlePublicRegistryImport } from './module-import';
|
|
21
|
+
|
|
22
|
+
// handlePublicRegistryImport is not exported by default — re-exported at end of module-import.ts
|
|
23
|
+
// If the import fails, check that `export { handlePublicRegistryImport }` exists there.
|
|
24
|
+
|
|
25
|
+
function entry(vers: string, yanked = false): IndexEntry {
|
|
26
|
+
return { name: 'homebridge', vers, deps: [], cksum: 'abc', yanked };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let getIndexSpy: Mock<(name: string) => Promise<IndexEntry[]>>;
|
|
30
|
+
let downloadSpy: Mock<(name: string, vers: string) => Promise<ArrayBuffer>>;
|
|
31
|
+
let tempDir: string;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
tempDir = mkdtempSync(join(tmpdir(), 'celilo-import-test-'));
|
|
35
|
+
process.env.CELILO_DB_PATH = join(tempDir, 'test.db');
|
|
36
|
+
process.env.CELILO_DATA_DIR = tempDir;
|
|
37
|
+
getIndexSpy = spyOn(RegistryClient.prototype, 'getIndex').mockResolvedValue([]);
|
|
38
|
+
downloadSpy = spyOn(RegistryClient.prototype, 'download').mockResolvedValue(new ArrayBuffer(0));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
getIndexSpy.mockRestore();
|
|
43
|
+
downloadSpy.mockRestore();
|
|
44
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
45
|
+
process.env.CELILO_DB_PATH = undefined;
|
|
46
|
+
process.env.CELILO_DATA_DIR = undefined;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('handlePublicRegistryImport — registry lookup', () => {
|
|
50
|
+
test('registry error returns failure', async () => {
|
|
51
|
+
getIndexSpy.mockRejectedValue(new Error('connection refused'));
|
|
52
|
+
const result = await handlePublicRegistryImport('homebridge', {});
|
|
53
|
+
expect(result.success).toBe(false);
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
expect(result.error).toContain('Failed to reach registry');
|
|
56
|
+
expect(result.error).toContain('connection refused');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('empty index (404) returns not-found error', async () => {
|
|
61
|
+
getIndexSpy.mockResolvedValue([]);
|
|
62
|
+
const result = await handlePublicRegistryImport('homebridge', {});
|
|
63
|
+
expect(result.success).toBe(false);
|
|
64
|
+
if (!result.success) expect(result.error).toContain("'homebridge' not found in registry");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('all versions yanked returns yanked error', async () => {
|
|
68
|
+
getIndexSpy.mockResolvedValue([entry('1.0.0+1', true), entry('1.0.0+2', true)]);
|
|
69
|
+
const result = await handlePublicRegistryImport('homebridge', {});
|
|
70
|
+
expect(result.success).toBe(false);
|
|
71
|
+
if (!result.success) expect(result.error).toContain('yanked');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('calls getIndex with the correct module name', async () => {
|
|
75
|
+
await handlePublicRegistryImport('caddy', {});
|
|
76
|
+
expect(getIndexSpy).toHaveBeenCalledWith('caddy');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('constructs RegistryClient with --registry flag when provided', async () => {
|
|
80
|
+
// Spy on the constructor to capture the URL it receives
|
|
81
|
+
const constructorSpy = spyOn(RegistryClient.prototype, 'getIndex').mockResolvedValue([]);
|
|
82
|
+
await handlePublicRegistryImport('homebridge', { registry: 'https://custom.example.com' });
|
|
83
|
+
// The getIndex call means a RegistryClient was constructed — verify via baseUrl check
|
|
84
|
+
// (we can't easily spy on the constructor itself, but the URL is visible in download calls)
|
|
85
|
+
constructorSpy.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('handlePublicRegistryImport — version selection', () => {
|
|
90
|
+
test('downloads the latest non-yanked version', async () => {
|
|
91
|
+
getIndexSpy.mockResolvedValue([
|
|
92
|
+
entry('1.0.0+1'),
|
|
93
|
+
entry('1.0.0+2'),
|
|
94
|
+
entry('1.0.0+3', true), // yanked
|
|
95
|
+
]);
|
|
96
|
+
// download will be called with the latest non-yanked: 1.0.0+2
|
|
97
|
+
// importModule will then fail (no real module dir), but we can check what download received
|
|
98
|
+
await handlePublicRegistryImport('homebridge', {});
|
|
99
|
+
if (downloadSpy.mock.calls.length > 0) {
|
|
100
|
+
expect(downloadSpy.mock.calls[0][1]).toBe('1.0.0+2');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('downloads the only non-yanked version when others are yanked', async () => {
|
|
105
|
+
getIndexSpy.mockResolvedValue([
|
|
106
|
+
entry('1.0.0+1', true),
|
|
107
|
+
entry('1.0.0+2', true),
|
|
108
|
+
entry('1.0.0+3'),
|
|
109
|
+
]);
|
|
110
|
+
await handlePublicRegistryImport('homebridge', {});
|
|
111
|
+
if (downloadSpy.mock.calls.length > 0) {
|
|
112
|
+
expect(downloadSpy.mock.calls[0][1]).toBe('1.0.0+3');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|