@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
package/src/variables/parser.ts
CHANGED
|
@@ -9,13 +9,19 @@ import type { VariableReference } from './types';
|
|
|
9
9
|
* Examples:
|
|
10
10
|
* - $self:container_ip
|
|
11
11
|
* - ${self:disk}G
|
|
12
|
+
* - $self:domains[0] ← array indexing on $self: refs
|
|
12
13
|
* - $capability:dns_registrar.primary_domain
|
|
13
14
|
* - ${system:base_url}/api
|
|
14
15
|
*
|
|
15
|
-
* Path must start with letter or underscore, not digit
|
|
16
|
+
* Path must start with letter or underscore, not digit. An optional
|
|
17
|
+
* [N] suffix indexes into an array variable (currently only
|
|
18
|
+
* meaningful on $self: refs whose target is `type: array` — e.g.
|
|
19
|
+
* namecheap's `domains` declared as an array; the manifest's
|
|
20
|
+
* capability data block can then read `$self:domains[0]` as a
|
|
21
|
+
* computed alias for the canonical default).
|
|
16
22
|
*/
|
|
17
23
|
const VARIABLE_PATTERN =
|
|
18
|
-
/\$\{(self|system|system_secret|secret|capability):([a-zA-Z_][a-zA-Z0-9_.-]*)\}|\$(self|system|system_secret|secret|capability):([a-zA-Z_][a-zA-Z0-9_.-]*)/g;
|
|
24
|
+
/\$\{(self|system|system_secret|secret|capability):([a-zA-Z_][a-zA-Z0-9_.-]*(?:\[\d+\])?)\}|\$(self|system|system_secret|secret|capability):([a-zA-Z_][a-zA-Z0-9_.-]*(?:\[\d+\])?)/g;
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
27
|
* Parse template content to extract variable references
|
|
@@ -56,8 +62,9 @@ export function parseVariables(content: string): VariableReference[] {
|
|
|
56
62
|
*/
|
|
57
63
|
export function hasVariables(content: string): boolean {
|
|
58
64
|
// Create new regex without state to avoid issues with global flag
|
|
59
|
-
// Matches both ${type:path} and $type:path
|
|
60
|
-
const pattern =
|
|
65
|
+
// Matches both ${type:path} and $type:path (with optional [N] index)
|
|
66
|
+
const pattern =
|
|
67
|
+
/\$\{?(self|system|system_secret|secret|capability):([a-zA-Z_][a-zA-Z0-9_.-]*(?:\[\d+\])?)/;
|
|
61
68
|
return pattern.test(content);
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -69,8 +76,42 @@ export function hasVariables(content: string): boolean {
|
|
|
69
76
|
*/
|
|
70
77
|
export function isValidVariableFormat(variable: string): boolean {
|
|
71
78
|
// Path must start with letter or underscore, not digit
|
|
72
|
-
// Accepts both ${type:path} and $type:path
|
|
79
|
+
// Accepts both ${type:path} and $type:path with optional [N] indexing
|
|
73
80
|
const pattern =
|
|
74
|
-
/^(?:\$\{(self|system|system_secret|secret|capability):[a-zA-Z_][a-zA-Z0-9_.-]
|
|
81
|
+
/^(?:\$\{(self|system|system_secret|secret|capability):[a-zA-Z_][a-zA-Z0-9_.-]*(?:\[\d+\])?\}|\$(self|system|system_secret|secret|capability):[a-zA-Z_][a-zA-Z0-9_.-]*(?:\[\d+\])?)$/;
|
|
75
82
|
return pattern.test(variable);
|
|
76
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Split a variable path into its base name and optional array index.
|
|
87
|
+
*
|
|
88
|
+
* Examples:
|
|
89
|
+
* parsePath("domains") → { name: "domains" }
|
|
90
|
+
* parsePath("domains[0]") → { name: "domains", index: 0 }
|
|
91
|
+
* parsePath("a.b.c[2]") → { name: "a.b.c", index: 2 }
|
|
92
|
+
*
|
|
93
|
+
* Pure helper used wherever `$self:NAME[N]` semantics need to be
|
|
94
|
+
* applied to a resolved value (currently the capability-data
|
|
95
|
+
* resolver in `context.ts` and the lazy-resolution path in
|
|
96
|
+
* `resolver.ts`).
|
|
97
|
+
*/
|
|
98
|
+
export function parsePath(path: string): { name: string; index?: number } {
|
|
99
|
+
const match = /^(.+)\[(\d+)\]$/.exec(path);
|
|
100
|
+
if (!match) return { name: path };
|
|
101
|
+
return { name: match[1], index: Number.parseInt(match[2], 10) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Apply an optional `[N]` index to a value. Used by the variable
|
|
106
|
+
* resolver after looking up a name like `domains` in a config map —
|
|
107
|
+
* if the original path had `[N]`, we drill into the array.
|
|
108
|
+
*
|
|
109
|
+
* Returns `undefined` for out-of-bounds indexes or when an index is
|
|
110
|
+
* applied to a non-array. Callers turn that into a resolver error.
|
|
111
|
+
*/
|
|
112
|
+
export function applyIndex(value: unknown, index: number | undefined): unknown {
|
|
113
|
+
if (index === undefined) return value;
|
|
114
|
+
if (!Array.isArray(value)) return undefined;
|
|
115
|
+
if (index < 0 || index >= value.length) return undefined;
|
|
116
|
+
return value[index];
|
|
117
|
+
}
|
|
@@ -29,7 +29,7 @@ const mockDb = {
|
|
|
29
29
|
const createContext = (overrides?: Partial<ResolutionContext>): ResolutionContext => ({
|
|
30
30
|
moduleId: 'test-module',
|
|
31
31
|
selfConfig: {
|
|
32
|
-
|
|
32
|
+
target_ip: '192.168.0.50',
|
|
33
33
|
hostname: 'homebridge',
|
|
34
34
|
cores: '2',
|
|
35
35
|
'resources.machine.cpu': '2',
|
|
@@ -69,8 +69,8 @@ describe('resolveVariable', () => {
|
|
|
69
69
|
test('should resolve self variable', async () => {
|
|
70
70
|
const variable: VariableReference = {
|
|
71
71
|
type: 'self',
|
|
72
|
-
path: '
|
|
73
|
-
raw: '$self:
|
|
72
|
+
path: 'target_ip',
|
|
73
|
+
raw: '$self:target_ip',
|
|
74
74
|
};
|
|
75
75
|
const context = createContext();
|
|
76
76
|
|
|
@@ -245,7 +245,7 @@ describe('resolveVariable', () => {
|
|
|
245
245
|
|
|
246
246
|
describe('resolveTemplate', () => {
|
|
247
247
|
test('should resolve template with single variable', async () => {
|
|
248
|
-
const template = 'ip: $self:
|
|
248
|
+
const template = 'ip: $self:target_ip';
|
|
249
249
|
const context = createContext();
|
|
250
250
|
|
|
251
251
|
const result = await resolveTemplate(template, context, mockDb);
|
|
@@ -259,7 +259,7 @@ describe('resolveTemplate', () => {
|
|
|
259
259
|
test('should resolve template with multiple variables', async () => {
|
|
260
260
|
const template = `
|
|
261
261
|
hostname: $self:hostname
|
|
262
|
-
ip: $self:
|
|
262
|
+
ip: $self:target_ip
|
|
263
263
|
domain: $system:network.domain
|
|
264
264
|
`;
|
|
265
265
|
const context = createContext();
|
|
@@ -276,7 +276,7 @@ domain: $system:network.domain
|
|
|
276
276
|
|
|
277
277
|
test('should resolve template with mixed variable types', async () => {
|
|
278
278
|
const template = `
|
|
279
|
-
|
|
279
|
+
target_ip: $self:target_ip
|
|
280
280
|
management_ip: $system:management.ip
|
|
281
281
|
dns_server: $capability:dns_external.nameserver
|
|
282
282
|
api_key: $secret:api_key
|
|
@@ -287,7 +287,7 @@ api_key: $secret:api_key
|
|
|
287
287
|
|
|
288
288
|
expect(result.success).toBe(true);
|
|
289
289
|
if (result.success) {
|
|
290
|
-
expect(result.content).toContain('
|
|
290
|
+
expect(result.content).toContain('target_ip: 192.168.0.50');
|
|
291
291
|
expect(result.content).toContain('management_ip: 192.168.0.10');
|
|
292
292
|
expect(result.content).toContain('dns_server: ns1.example.com');
|
|
293
293
|
expect(result.content).toContain('api_key: secret123');
|
|
@@ -324,7 +324,7 @@ resource "proxmox_lxc" "container" {
|
|
|
324
324
|
hostname = "$self:hostname"
|
|
325
325
|
cores = $self:cores
|
|
326
326
|
network {
|
|
327
|
-
ip = "$self:
|
|
327
|
+
ip = "$self:target_ip/24"
|
|
328
328
|
gateway = "$system:management.ip"
|
|
329
329
|
}
|
|
330
330
|
environment = {
|
|
@@ -362,7 +362,7 @@ resource "proxmox_lxc" "container" {
|
|
|
362
362
|
|
|
363
363
|
test('should return errors for missing variables', async () => {
|
|
364
364
|
const template = `
|
|
365
|
-
ip: $self:
|
|
365
|
+
ip: $self:target_ip
|
|
366
366
|
missing: $self:missing_var
|
|
367
367
|
another_missing: $secret:missing_secret
|
|
368
368
|
`;
|
|
@@ -380,9 +380,9 @@ another_missing: $secret:missing_secret
|
|
|
380
380
|
|
|
381
381
|
test('should handle repeated variables', async () => {
|
|
382
382
|
const template = `
|
|
383
|
-
ip1: $self:
|
|
384
|
-
ip2: $self:
|
|
385
|
-
ip3: $self:
|
|
383
|
+
ip1: $self:target_ip
|
|
384
|
+
ip2: $self:target_ip
|
|
385
|
+
ip3: $self:target_ip
|
|
386
386
|
`;
|
|
387
387
|
const context = createContext();
|
|
388
388
|
|
|
@@ -393,7 +393,7 @@ ip3: $self:container_ip
|
|
|
393
393
|
expect(result.content).toContain('ip1: 192.168.0.50');
|
|
394
394
|
expect(result.content).toContain('ip2: 192.168.0.50');
|
|
395
395
|
expect(result.content).toContain('ip3: 192.168.0.50');
|
|
396
|
-
expect(result.content).not.toContain('$self:
|
|
396
|
+
expect(result.content).not.toContain('$self:target_ip');
|
|
397
397
|
}
|
|
398
398
|
});
|
|
399
399
|
|
|
@@ -420,7 +420,7 @@ $self:hostname
|
|
|
420
420
|
const template = 'ansible_host: $self:inventory.ansible_host';
|
|
421
421
|
const context = createContext({
|
|
422
422
|
selfConfig: {
|
|
423
|
-
|
|
423
|
+
target_ip: '10.0.10.10/24',
|
|
424
424
|
'inventory.ansible_host': '10.0.10.10', // Auto-derived (CIDR stripped)
|
|
425
425
|
'inventory.ansible_user': 'root',
|
|
426
426
|
'inventory.groups': 'test',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseVariables } from './parser';
|
|
1
|
+
import { applyIndex, parsePath, parseVariables } from './parser';
|
|
2
2
|
import type {
|
|
3
3
|
ResolutionContext,
|
|
4
4
|
ResolveResult,
|
|
@@ -173,8 +173,11 @@ export async function resolveVariable(
|
|
|
173
173
|
|
|
174
174
|
// Check if value contains unresolved $self: variable (lazy resolution)
|
|
175
175
|
if (typeof value === 'string' && value.startsWith('$self:')) {
|
|
176
|
-
// Get provider module's config to resolve the variable
|
|
177
|
-
|
|
176
|
+
// Get provider module's config to resolve the variable.
|
|
177
|
+
// The path can be a plain key ("primary_domain") or include
|
|
178
|
+
// an array index ("domains[0]") — see parsePath/applyIndex
|
|
179
|
+
// and the syntax note in the multi-domain DDNS design doc.
|
|
180
|
+
const { name, index } = parsePath(value.substring(6));
|
|
178
181
|
|
|
179
182
|
// Get provider module ID
|
|
180
183
|
const providerQuery = db.$client.prepare(
|
|
@@ -193,23 +196,38 @@ export async function resolveVariable(
|
|
|
193
196
|
};
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
//
|
|
199
|
+
// Fetch both `value` and `value_json` so we can index into
|
|
200
|
+
// arrays when the path contained `[N]`.
|
|
197
201
|
const configQuery = db.$client.prepare(
|
|
198
|
-
'SELECT value FROM module_configs WHERE module_id = ? AND key = ?',
|
|
202
|
+
'SELECT value, value_json FROM module_configs WHERE module_id = ? AND key = ?',
|
|
199
203
|
);
|
|
200
|
-
const configResult = configQuery.get(providerResult.id,
|
|
201
|
-
| { value: string }
|
|
204
|
+
const configResult = configQuery.get(providerResult.id, name) as
|
|
205
|
+
| { value: string; value_json: string | null }
|
|
202
206
|
| undefined;
|
|
203
207
|
|
|
204
208
|
if (!configResult) {
|
|
205
209
|
return {
|
|
206
210
|
success: false,
|
|
207
211
|
variable: variable.raw,
|
|
208
|
-
error: `Provider module '${providerResult.id}' has not configured '${
|
|
212
|
+
error: `Provider module '${providerResult.id}' has not configured '${name}' (required by capability '${capabilityName}')`,
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
const rawValue: unknown = configResult.value_json
|
|
217
|
+
? JSON.parse(configResult.value_json)
|
|
218
|
+
: configResult.value;
|
|
219
|
+
const indexed = applyIndex(rawValue, index);
|
|
220
|
+
if (indexed === undefined) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
variable: variable.raw,
|
|
224
|
+
error:
|
|
225
|
+
index === undefined
|
|
226
|
+
? `Provider module '${providerResult.id}' has not configured '${name}'`
|
|
227
|
+
: `'$self:${name}[${index}]' is out of bounds or '${name}' is not an array on '${providerResult.id}'`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return { success: true, value: typeof indexed === 'string' ? indexed : String(indexed) };
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
// Convert value to string
|
package/src/variables/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Variable reference parsed from template
|
|
7
|
-
* Example: $self:
|
|
7
|
+
* Example: $self:target_ip -> { type: 'self', path: 'target_ip', raw: '$self:target_ip' }
|
|
8
8
|
*/
|
|
9
9
|
export interface VariableReference {
|
|
10
10
|
type: 'self' | 'system' | 'system_secret' | 'secret' | 'capability';
|