@celilo/cli 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 +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- package/src/variables/types.ts +59 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module build command
|
|
3
|
+
*
|
|
4
|
+
* Executes module build scripts (Ansible playbooks) for imported modules
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDb } from '../../db/client';
|
|
8
|
+
import { buildModuleFromSource } from '../../services/module-build';
|
|
9
|
+
import { getArg, validateRequiredArgs } from '../parser';
|
|
10
|
+
import type { CommandResult } from '../types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handle module build command
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* celilo module build <module-id>
|
|
17
|
+
*
|
|
18
|
+
* @param args - Command arguments
|
|
19
|
+
* @param flags - Command flags (unused)
|
|
20
|
+
* @returns Command result
|
|
21
|
+
*/
|
|
22
|
+
export async function handleModuleBuild(
|
|
23
|
+
args: string[],
|
|
24
|
+
_flags: Record<string, string | boolean>,
|
|
25
|
+
): Promise<CommandResult> {
|
|
26
|
+
// Validate arguments
|
|
27
|
+
const error = validateRequiredArgs(args, 1);
|
|
28
|
+
if (error) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
error: `${error}\n\nUsage:\n celilo module build <module-id>`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const moduleId = getArg(args, 0);
|
|
36
|
+
if (!moduleId) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: 'Module ID is required',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const db = getDb();
|
|
44
|
+
const result = await buildModuleFromSource(moduleId, db);
|
|
45
|
+
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: result.error || 'Build failed',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
message: `ā Build successful\nArtifacts: ${result.artifacts?.join(', ') || 'none'}`,
|
|
56
|
+
data: {
|
|
57
|
+
artifacts: result.artifacts,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module config command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { eq } from 'drizzle-orm';
|
|
6
|
+
import { getDb } from '../../db/client';
|
|
7
|
+
import { modules } from '../../db/schema';
|
|
8
|
+
import {
|
|
9
|
+
formatConfigValue,
|
|
10
|
+
getAllModuleConfigValues,
|
|
11
|
+
getModuleConfigValue,
|
|
12
|
+
setModuleConfigValue,
|
|
13
|
+
} from '../../services/module-config';
|
|
14
|
+
import { getArg, validateRequiredArgs } from '../parser';
|
|
15
|
+
import type { CommandResult } from '../types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handle module config set command
|
|
19
|
+
*
|
|
20
|
+
* Usage: celilo module config set <module-id> <key> <value>
|
|
21
|
+
*
|
|
22
|
+
* @param args - Command arguments
|
|
23
|
+
* @returns Command result
|
|
24
|
+
*/
|
|
25
|
+
export async function handleModuleConfigSet(args: string[]): Promise<CommandResult> {
|
|
26
|
+
// Validate arguments
|
|
27
|
+
const error = validateRequiredArgs(args, 3);
|
|
28
|
+
if (error) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
error: `${error}\n\nUsage: celilo module config set <module-id> <key> <value>`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const moduleId = getArg(args, 0);
|
|
36
|
+
const key = getArg(args, 1);
|
|
37
|
+
const value = getArg(args, 2);
|
|
38
|
+
|
|
39
|
+
if (!moduleId || !key || !value) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'Module ID, key, and value are required',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const db = getDb();
|
|
47
|
+
|
|
48
|
+
// Check if module exists
|
|
49
|
+
const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
|
|
50
|
+
if (!module) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: `Module not found: ${moduleId}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate key against manifest
|
|
58
|
+
const manifest = module.manifestData as Record<string, unknown>;
|
|
59
|
+
const variables = manifest.variables as
|
|
60
|
+
| { owns?: Array<{ name: string; required?: boolean; default?: string }> }
|
|
61
|
+
| undefined;
|
|
62
|
+
const declaredVars = variables?.owns || [];
|
|
63
|
+
|
|
64
|
+
// Check if key is declared in manifest
|
|
65
|
+
const declaredVar = declaredVars.find((v) => v.name === key);
|
|
66
|
+
if (!declaredVar) {
|
|
67
|
+
const validKeys = declaredVars.map((v) => v.name).join(', ');
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Invalid config key '${key}' for module ${moduleId}.\n\nValid keys: ${validKeys || '(none declared)'}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Set config value using service (handles primitive and complex types)
|
|
75
|
+
try {
|
|
76
|
+
await setModuleConfigValue(moduleId, key, value);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
message: `Set config for ${moduleId}: ${key}`,
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: `Failed to set config: ${error instanceof Error ? error.message : String(error)}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handle module config get command
|
|
92
|
+
*
|
|
93
|
+
* Usage: celilo module config get <module-id> [key]
|
|
94
|
+
*
|
|
95
|
+
* @param args - Command arguments
|
|
96
|
+
* @returns Command result
|
|
97
|
+
*/
|
|
98
|
+
export async function handleModuleConfigGet(args: string[]): Promise<CommandResult> {
|
|
99
|
+
// Validate arguments
|
|
100
|
+
const error = validateRequiredArgs(args, 1);
|
|
101
|
+
if (error) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: `${error}\n\nUsage: celilo module config get <module-id> [key]`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const moduleId = getArg(args, 0);
|
|
109
|
+
const key = getArg(args, 1);
|
|
110
|
+
|
|
111
|
+
if (!moduleId) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: 'Module ID is required',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const db = getDb();
|
|
119
|
+
|
|
120
|
+
// Check if module exists
|
|
121
|
+
const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
|
|
122
|
+
if (!module) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: `Module not found: ${moduleId}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (key) {
|
|
130
|
+
// Get specific config value
|
|
131
|
+
const configValue = getModuleConfigValue(moduleId, key);
|
|
132
|
+
|
|
133
|
+
if (!configValue) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: `Config key not found: ${key}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const formatted = formatConfigValue(configValue);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: true,
|
|
144
|
+
message: `${key} = ${formatted}`,
|
|
145
|
+
data: { key, value: configValue.value },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Get all config for module
|
|
150
|
+
const configs = getAllModuleConfigValues(moduleId);
|
|
151
|
+
|
|
152
|
+
if (configs.length === 0) {
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
message: `No configuration set for ${moduleId}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const lines = [`Configuration for ${moduleId}:`, ''];
|
|
160
|
+
for (const config of configs) {
|
|
161
|
+
const formatted = formatConfigValue(config);
|
|
162
|
+
lines.push(`${config.key} = ${formatted}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
message: lines.join('\n'),
|
|
168
|
+
data: configs.map((c) => ({ key: c.key, value: c.value })),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module deploy command
|
|
3
|
+
*
|
|
4
|
+
* Deploys module to infrastructure (Terraform + Ansible)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDb } from '../../db/client';
|
|
8
|
+
import { formatPreflightResult, runPreflight } from '../../services/deploy-preflight';
|
|
9
|
+
import { deployModule } from '../../services/module-deploy';
|
|
10
|
+
import { getArg, hasFlag, validateRequiredArgs } from '../parser';
|
|
11
|
+
import type { CommandResult } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle module deploy command
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* celilo module deploy <module-id> [--no-interactive] [--debug] [--preflight]
|
|
18
|
+
*
|
|
19
|
+
* @param args - Command arguments
|
|
20
|
+
* @param flags - Command flags
|
|
21
|
+
* @returns Command result
|
|
22
|
+
*/
|
|
23
|
+
export async function handleModuleDeploy(
|
|
24
|
+
args: string[],
|
|
25
|
+
flags: Record<string, string | boolean>,
|
|
26
|
+
): Promise<CommandResult> {
|
|
27
|
+
// Validate arguments
|
|
28
|
+
const error = validateRequiredArgs(args, 1);
|
|
29
|
+
if (error) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: `${error}\n\nUsage:\n celilo module deploy <module-id> [--no-interactive] [--preflight]`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const moduleId = getArg(args, 0);
|
|
37
|
+
if (!moduleId) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: 'Module ID is required',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const db = getDb();
|
|
45
|
+
|
|
46
|
+
// --preflight: fast validation only, no actual deployment
|
|
47
|
+
if (hasFlag(flags, 'preflight')) {
|
|
48
|
+
const preflight = await runPreflight(moduleId, db);
|
|
49
|
+
const message = formatPreflightResult(preflight);
|
|
50
|
+
return preflight.success ? { success: true, message } : { success: false, error: message };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const noInteractive = hasFlag(flags, 'no-interactive');
|
|
54
|
+
const debug = hasFlag(flags, 'debug');
|
|
55
|
+
|
|
56
|
+
const result = await deployModule(moduleId, db, { noInteractive, debug });
|
|
57
|
+
|
|
58
|
+
if (!result.success) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: result.error || 'Deployment failed',
|
|
62
|
+
details: result.phases,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
message: `ā Module '${moduleId}' deployed successfully`,
|
|
69
|
+
data: result.phases,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module generate command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { eq } from 'drizzle-orm';
|
|
8
|
+
import {
|
|
9
|
+
promptForMissingCapabilitySecrets,
|
|
10
|
+
validateCapabilitySecrets,
|
|
11
|
+
} from '../../capabilities/secret-validation';
|
|
12
|
+
import { getDb } from '../../db/client';
|
|
13
|
+
import { moduleConfigs, modules } from '../../db/schema';
|
|
14
|
+
import type { ModuleManifest } from '../../manifest/schema';
|
|
15
|
+
import { interviewForMissingSecrets, validateModuleSecrets } from '../../services/config-interview';
|
|
16
|
+
import { getModuleBuildStatus, verifyArtifactsExist } from '../../services/module-build';
|
|
17
|
+
import { generateTemplates } from '../../templates/generator';
|
|
18
|
+
import { promptForMissingConfig } from '../interactive-config';
|
|
19
|
+
import { getArg, getFlag, validateRequiredArgs } from '../parser';
|
|
20
|
+
import type { CommandResult } from '../types';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle module generate command
|
|
24
|
+
*
|
|
25
|
+
* Usage: celilo module generate <module-id> [--output <path>]
|
|
26
|
+
*
|
|
27
|
+
* @param args - Command arguments
|
|
28
|
+
* @param flags - Command flags
|
|
29
|
+
* @returns Command result
|
|
30
|
+
*/
|
|
31
|
+
export async function handleModuleGenerate(
|
|
32
|
+
args: string[],
|
|
33
|
+
flags: Record<string, string | boolean>,
|
|
34
|
+
): Promise<CommandResult> {
|
|
35
|
+
// Validate arguments
|
|
36
|
+
const error = validateRequiredArgs(args, 1);
|
|
37
|
+
if (error) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: `${error}\n\nUsage: celilo module generate <module-id> [--output <path>]`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const moduleId = getArg(args, 0);
|
|
45
|
+
if (!moduleId) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: 'Module ID is required',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const db = getDb();
|
|
53
|
+
|
|
54
|
+
// Check if module exists
|
|
55
|
+
const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
|
|
56
|
+
if (!module) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: `Module not found: ${moduleId}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get module path and output path
|
|
64
|
+
const modulePath = module.sourcePath;
|
|
65
|
+
|
|
66
|
+
// Check if module files exist (may have been deleted from /tmp)
|
|
67
|
+
if (!existsSync(modulePath)) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Module files not found at: ${modulePath}\n\nThe module files may have been deleted (e.g., /tmp cleanup).\n\nTo fix this:\n 1. Remove the module: celilo module remove ${moduleId}\n 2. Re-import it: celilo module import <original-module-path>\n 3. Reconfigure if needed: celilo module config set ${moduleId} <key> <value>\n 4. Regenerate: celilo module generate ${moduleId}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if module requires build step
|
|
75
|
+
const manifest = module.manifestData as ModuleManifest;
|
|
76
|
+
if (manifest.build) {
|
|
77
|
+
const buildStatus = await getModuleBuildStatus(moduleId, db);
|
|
78
|
+
|
|
79
|
+
if (!buildStatus || buildStatus.status !== 'success') {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error:
|
|
83
|
+
`Module ${moduleId} requires build step before generation.\n\n` +
|
|
84
|
+
`Run: celilo module build ${moduleId}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Verify artifacts still exist
|
|
89
|
+
if (buildStatus.artifacts.length > 0) {
|
|
90
|
+
const artifactsValid = verifyArtifactsExist(buildStatus.artifacts);
|
|
91
|
+
|
|
92
|
+
if (!artifactsValid) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: `Build artifacts missing or invalid.\n\nRebuild: celilo module build ${moduleId}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const outputPathFlag = getFlag(flags, 'output', '');
|
|
102
|
+
const outputPath = outputPathFlag || `${modulePath}/generated`;
|
|
103
|
+
const resolvedOutputPath = resolve(outputPath);
|
|
104
|
+
|
|
105
|
+
// Check for missing capability secrets
|
|
106
|
+
const secretValidation = await validateCapabilitySecrets(moduleId, db.$client);
|
|
107
|
+
|
|
108
|
+
if (!secretValidation.success) {
|
|
109
|
+
// If missing secrets and in interactive mode, prompt user
|
|
110
|
+
if (process.stdin.isTTY && secretValidation.missingSecrets) {
|
|
111
|
+
const promptResult = await promptForMissingCapabilitySecrets(
|
|
112
|
+
moduleId,
|
|
113
|
+
secretValidation.missingSecrets,
|
|
114
|
+
db.$client,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (!promptResult.success) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: promptResult.error || 'Failed to collect capability secrets',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Non-interactive: error with helpful message
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: secretValidation.error || 'Missing capability secrets',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for missing module secrets
|
|
133
|
+
const moduleSecretsMissing = await validateModuleSecrets(moduleId, db);
|
|
134
|
+
|
|
135
|
+
if (moduleSecretsMissing.length > 0) {
|
|
136
|
+
// Always try to interview for missing secrets
|
|
137
|
+
// Auto-generated secrets work in non-interactive mode
|
|
138
|
+
const result = await interviewForMissingSecrets(moduleId, moduleSecretsMissing, db);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: result.error || 'Failed to collect module secrets',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Try to generate templates
|
|
148
|
+
let result = await generateTemplates({
|
|
149
|
+
moduleId,
|
|
150
|
+
modulePath,
|
|
151
|
+
outputPath: resolvedOutputPath,
|
|
152
|
+
db,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// If missing config, prompt for it and retry
|
|
156
|
+
if (!result.success && result.error?.includes('Missing required configuration')) {
|
|
157
|
+
// Get missing required variables
|
|
158
|
+
const requiredVars = manifest.variables?.owns?.filter((v) => v.required) || [];
|
|
159
|
+
|
|
160
|
+
if (requiredVars.length > 0) {
|
|
161
|
+
// Get current module configuration
|
|
162
|
+
const configs = db
|
|
163
|
+
.select()
|
|
164
|
+
.from(moduleConfigs)
|
|
165
|
+
.where(eq(moduleConfigs.moduleId, moduleId))
|
|
166
|
+
.all();
|
|
167
|
+
|
|
168
|
+
const configMap = new Map(configs.map((c) => [c.key, c.value || c.valueJson]));
|
|
169
|
+
|
|
170
|
+
// Find missing variables
|
|
171
|
+
const missingVars = requiredVars.filter((v) => !configMap.has(v.name));
|
|
172
|
+
|
|
173
|
+
if (missingVars.length > 0) {
|
|
174
|
+
// Only prompt if in interactive mode
|
|
175
|
+
if (!process.stdin.isTTY) {
|
|
176
|
+
// Non-interactive: return error
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: result.error || 'Missing required configuration',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Prompt for missing config
|
|
184
|
+
const collected = await promptForMissingConfig(moduleId, missingVars, db);
|
|
185
|
+
|
|
186
|
+
if (!collected) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: 'Failed to collect required configuration',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Retry generation with new config
|
|
194
|
+
result = await generateTemplates({
|
|
195
|
+
moduleId,
|
|
196
|
+
modulePath,
|
|
197
|
+
outputPath: resolvedOutputPath,
|
|
198
|
+
db,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: result.error,
|
|
208
|
+
details: result.details,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Build infrastructure info message
|
|
213
|
+
let infrastructureMsg = '';
|
|
214
|
+
if (result.infrastructure) {
|
|
215
|
+
const infra = result.infrastructure;
|
|
216
|
+
|
|
217
|
+
if (infra.type === 'machine') {
|
|
218
|
+
infrastructureMsg = `\nš¦ Infrastructure: Existing machine "${infra.machineName || infra.machineId}" (zone: ${infra.zone})`;
|
|
219
|
+
} else if (infra.type === 'container_service') {
|
|
220
|
+
infrastructureMsg = `\nš¦ Infrastructure: Container service "${infra.serviceName || infra.serviceId}" (zone: ${infra.zone})`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const filesList = result.files.map((f) => ` - ${f.path}`).join('\n');
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
message: `Successfully generated ${result.files.length} files:\n${filesList}\n\nOutput: ${result.outputPath}${infrastructureMsg}`,
|
|
229
|
+
data: {
|
|
230
|
+
fileCount: result.files.length,
|
|
231
|
+
outputPath: result.outputPath,
|
|
232
|
+
files: result.files,
|
|
233
|
+
infrastructure: result.infrastructure,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module health check command
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* celilo module health [module-id] [--json] [--debug]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDb } from '../../db/client';
|
|
9
|
+
import type { HealthCheckResult } from '../../services/health-runner';
|
|
10
|
+
import { runAllHealthChecks, runModuleHealthCheck } from '../../services/health-runner';
|
|
11
|
+
import { getArg, hasFlag } from '../parser';
|
|
12
|
+
import type { CommandResult } from '../types';
|
|
13
|
+
|
|
14
|
+
const STATUS_ICONS: Record<string, string> = {
|
|
15
|
+
healthy: 'ā',
|
|
16
|
+
degraded: 'ā ',
|
|
17
|
+
unhealthy: 'ā',
|
|
18
|
+
'no-checks': 'ā',
|
|
19
|
+
error: 'ā',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CHECK_ICONS: Record<string, string> = {
|
|
23
|
+
pass: 'ā',
|
|
24
|
+
warn: 'ā ',
|
|
25
|
+
fail: 'ā',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function formatResult(result: HealthCheckResult): string {
|
|
29
|
+
const icon = STATUS_ICONS[result.status] || '?';
|
|
30
|
+
const lines: string[] = [];
|
|
31
|
+
|
|
32
|
+
if (result.status === 'no-checks') {
|
|
33
|
+
lines.push(` ${result.moduleId} ${icon} no health check defined`);
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (result.status === 'error') {
|
|
38
|
+
lines.push(` ${result.moduleId} ${icon} error: ${result.error}`);
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const stateNote =
|
|
43
|
+
result.status === 'healthy' || result.status === 'degraded' ? ' ā VERIFIED' : '';
|
|
44
|
+
lines.push(` ${result.moduleId} ${icon} ${result.status}${stateNote}`);
|
|
45
|
+
|
|
46
|
+
for (const check of result.checks) {
|
|
47
|
+
const checkIcon = CHECK_ICONS[check.status] || '?';
|
|
48
|
+
lines.push(` ${checkIcon} ${check.name.padEnd(20)} ${check.message}`);
|
|
49
|
+
if (check.details) {
|
|
50
|
+
lines.push(` ${check.details}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle module health command
|
|
59
|
+
*/
|
|
60
|
+
export async function handleModuleHealth(
|
|
61
|
+
args: string[],
|
|
62
|
+
flags: Record<string, string | boolean>,
|
|
63
|
+
): Promise<CommandResult> {
|
|
64
|
+
const db = getDb();
|
|
65
|
+
const debug = hasFlag(flags, 'debug');
|
|
66
|
+
const jsonOutput = hasFlag(flags, 'json');
|
|
67
|
+
const moduleId = getArg(args, 0);
|
|
68
|
+
|
|
69
|
+
let results: HealthCheckResult[];
|
|
70
|
+
|
|
71
|
+
if (moduleId) {
|
|
72
|
+
const result = await runModuleHealthCheck(moduleId, db, { debug });
|
|
73
|
+
results = [result];
|
|
74
|
+
} else {
|
|
75
|
+
results = await runAllHealthChecks(db, { debug });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (jsonOutput) {
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
message: JSON.stringify({ modules: results }, null, 2),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (results.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
message: 'No deployed modules to check',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const output = results.map(formatResult).join('\n\n');
|
|
93
|
+
const allHealthy = results.every(
|
|
94
|
+
(r) => r.status === 'healthy' || r.status === 'degraded' || r.status === 'no-checks',
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!allHealthy) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: output,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
message: output,
|
|
107
|
+
};
|
|
108
|
+
}
|