@build-astron-co/nimbus 0.2.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/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Command
|
|
3
|
+
*
|
|
4
|
+
* Preview infrastructure changes without applying them
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nimbus preview terraform [directory]
|
|
8
|
+
* nimbus preview k8s [directory]
|
|
9
|
+
* nimbus preview helm [chart]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logger } from '../utils';
|
|
13
|
+
import { ui } from '../wizard/ui';
|
|
14
|
+
import { terraformClient, k8sClient, helmClient } from '../clients';
|
|
15
|
+
import { loadSafetyPolicy, evaluateSafety, type SafetyContext } from '../config/safety-policy';
|
|
16
|
+
import { displaySafetySummary } from '../wizard/approval';
|
|
17
|
+
|
|
18
|
+
export interface PreviewOptions {
|
|
19
|
+
/** Type of infrastructure to preview */
|
|
20
|
+
type: 'terraform' | 'k8s' | 'helm';
|
|
21
|
+
/** Directory or chart path */
|
|
22
|
+
directory?: string;
|
|
23
|
+
/** Output format */
|
|
24
|
+
format?: 'table' | 'json' | 'diff';
|
|
25
|
+
/** Show detailed output */
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
/** Skip safety checks */
|
|
28
|
+
skipSafety?: boolean;
|
|
29
|
+
/** Target specific resources (terraform) */
|
|
30
|
+
target?: string;
|
|
31
|
+
/** Namespace (k8s) */
|
|
32
|
+
namespace?: string;
|
|
33
|
+
/** Release name (helm) */
|
|
34
|
+
release?: string;
|
|
35
|
+
/** Values file (helm) */
|
|
36
|
+
valuesFile?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Preview command handler
|
|
41
|
+
*/
|
|
42
|
+
export async function previewCommand(options: PreviewOptions): Promise<void> {
|
|
43
|
+
logger.info('Running preview', { type: options.type });
|
|
44
|
+
|
|
45
|
+
ui.newLine();
|
|
46
|
+
ui.header(`Preview ${capitalize(options.type)} Changes`);
|
|
47
|
+
|
|
48
|
+
switch (options.type) {
|
|
49
|
+
case 'terraform':
|
|
50
|
+
await previewTerraform(options);
|
|
51
|
+
break;
|
|
52
|
+
case 'k8s':
|
|
53
|
+
await previewKubernetes(options);
|
|
54
|
+
break;
|
|
55
|
+
case 'helm':
|
|
56
|
+
await previewHelm(options);
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
ui.error(`Unknown preview type: ${options.type}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Preview Terraform changes
|
|
65
|
+
*/
|
|
66
|
+
async function previewTerraform(options: PreviewOptions): Promise<void> {
|
|
67
|
+
const directory = options.directory || '.';
|
|
68
|
+
|
|
69
|
+
ui.info(`Directory: ${directory}`);
|
|
70
|
+
ui.newLine();
|
|
71
|
+
|
|
72
|
+
// Check if terraform client is available
|
|
73
|
+
const clientAvailable = await terraformClient.isAvailable();
|
|
74
|
+
|
|
75
|
+
if (clientAvailable) {
|
|
76
|
+
await previewTerraformWithService(options);
|
|
77
|
+
} else {
|
|
78
|
+
await previewTerraformWithCLI(options);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Preview Terraform using service
|
|
84
|
+
*/
|
|
85
|
+
async function previewTerraformWithService(options: PreviewOptions): Promise<void> {
|
|
86
|
+
const directory = options.directory || '.';
|
|
87
|
+
|
|
88
|
+
ui.startSpinner({ message: 'Creating execution plan...' });
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const result = await terraformClient.plan(directory, {});
|
|
92
|
+
|
|
93
|
+
ui.stopSpinnerSuccess('Plan created');
|
|
94
|
+
ui.newLine();
|
|
95
|
+
|
|
96
|
+
if (!result.success) {
|
|
97
|
+
ui.error(`Plan failed: ${result.error}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Display plan
|
|
102
|
+
displayTerraformPlan(result, options);
|
|
103
|
+
|
|
104
|
+
// Run safety checks if not skipped
|
|
105
|
+
if (!options.skipSafety) {
|
|
106
|
+
await runSafetyChecks('plan', 'terraform', result.output, options);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
ui.stopSpinnerFail('Plan failed');
|
|
110
|
+
ui.error((error as Error).message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Preview Terraform using local CLI
|
|
116
|
+
*/
|
|
117
|
+
async function previewTerraformWithCLI(options: PreviewOptions): Promise<void> {
|
|
118
|
+
const { spawn } = await import('child_process');
|
|
119
|
+
const directory = options.directory || '.';
|
|
120
|
+
|
|
121
|
+
const args = ['plan', '-no-color'];
|
|
122
|
+
|
|
123
|
+
if (options.target) {
|
|
124
|
+
args.push('-target', options.target);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ui.info(`Running: terraform ${args.join(' ')}`);
|
|
128
|
+
ui.newLine();
|
|
129
|
+
|
|
130
|
+
return new Promise(resolve => {
|
|
131
|
+
let output = '';
|
|
132
|
+
|
|
133
|
+
const proc = spawn('terraform', args, {
|
|
134
|
+
cwd: directory,
|
|
135
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
proc.stdout?.on('data', data => {
|
|
139
|
+
const text = data.toString();
|
|
140
|
+
output += text;
|
|
141
|
+
process.stdout.write(text);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
proc.stderr?.on('data', data => {
|
|
145
|
+
process.stderr.write(data);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
proc.on('error', error => {
|
|
149
|
+
ui.error(`Failed to run terraform: ${error.message}`);
|
|
150
|
+
ui.info('Make sure terraform is installed and in your PATH');
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
proc.on('close', async code => {
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
ui.newLine();
|
|
157
|
+
ui.success('Plan preview complete');
|
|
158
|
+
|
|
159
|
+
// Run safety checks if not skipped
|
|
160
|
+
if (!options.skipSafety) {
|
|
161
|
+
await runSafetyChecks('plan', 'terraform', output, options);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
ui.newLine();
|
|
165
|
+
ui.error(`Terraform plan failed with exit code ${code}`);
|
|
166
|
+
}
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Display Terraform plan results
|
|
174
|
+
*/
|
|
175
|
+
function displayTerraformPlan(
|
|
176
|
+
result: { success: boolean; hasChanges: boolean; output: string },
|
|
177
|
+
options: PreviewOptions
|
|
178
|
+
): void {
|
|
179
|
+
if (!result.hasChanges) {
|
|
180
|
+
ui.success('No changes. Infrastructure is up to date.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (options.format === 'json') {
|
|
185
|
+
console.log(JSON.stringify({ hasChanges: result.hasChanges, output: result.output }, null, 2));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Parse changes from output
|
|
190
|
+
const addMatch = result.output.match(/(\d+) to add/);
|
|
191
|
+
const changeMatch = result.output.match(/(\d+) to change/);
|
|
192
|
+
const destroyMatch = result.output.match(/(\d+) to destroy/);
|
|
193
|
+
|
|
194
|
+
const add = parseInt(addMatch?.[1] || '0', 10);
|
|
195
|
+
const change = parseInt(changeMatch?.[1] || '0', 10);
|
|
196
|
+
const destroy = parseInt(destroyMatch?.[1] || '0', 10);
|
|
197
|
+
|
|
198
|
+
// Display summary table
|
|
199
|
+
ui.print(ui.bold('Plan Summary:'));
|
|
200
|
+
ui.newLine();
|
|
201
|
+
|
|
202
|
+
if (add > 0) {
|
|
203
|
+
ui.print(` ${ui.color(`+ ${add} to add`, 'green')}`);
|
|
204
|
+
}
|
|
205
|
+
if (change > 0) {
|
|
206
|
+
ui.print(` ${ui.color(`~ ${change} to change`, 'yellow')}`);
|
|
207
|
+
}
|
|
208
|
+
if (destroy > 0) {
|
|
209
|
+
ui.print(` ${ui.color(`- ${destroy} to destroy`, 'red')}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Show detailed output if verbose
|
|
213
|
+
if (options.verbose) {
|
|
214
|
+
ui.newLine();
|
|
215
|
+
ui.print(ui.bold('Detailed Changes:'));
|
|
216
|
+
ui.newLine();
|
|
217
|
+
ui.print(result.output);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Preview Kubernetes changes
|
|
223
|
+
*/
|
|
224
|
+
async function previewKubernetes(options: PreviewOptions): Promise<void> {
|
|
225
|
+
const directory = options.directory || '.';
|
|
226
|
+
const namespace = options.namespace || 'default';
|
|
227
|
+
|
|
228
|
+
ui.info(`Directory: ${directory}`);
|
|
229
|
+
ui.info(`Namespace: ${namespace}`);
|
|
230
|
+
ui.newLine();
|
|
231
|
+
|
|
232
|
+
// Check if k8s client is available
|
|
233
|
+
const clientAvailable = await k8sClient.isAvailable();
|
|
234
|
+
|
|
235
|
+
// K8s client doesn't have a diff method, always use CLI
|
|
236
|
+
// If client is available, we could use dry-run apply in the future
|
|
237
|
+
if (clientAvailable) {
|
|
238
|
+
ui.info('Using kubectl diff for preview...');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Use kubectl diff CLI
|
|
242
|
+
await previewKubernetesWithCLI(options);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Preview Kubernetes using kubectl
|
|
247
|
+
*/
|
|
248
|
+
async function previewKubernetesWithCLI(options: PreviewOptions): Promise<void> {
|
|
249
|
+
const { spawn } = await import('child_process');
|
|
250
|
+
const directory = options.directory || '.';
|
|
251
|
+
const namespace = options.namespace || 'default';
|
|
252
|
+
|
|
253
|
+
const args = ['diff', '-f', directory, '-n', namespace];
|
|
254
|
+
|
|
255
|
+
ui.info(`Running: kubectl ${args.join(' ')}`);
|
|
256
|
+
ui.newLine();
|
|
257
|
+
|
|
258
|
+
return new Promise(resolve => {
|
|
259
|
+
const proc = spawn('kubectl', args, {
|
|
260
|
+
stdio: 'inherit',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
proc.on('error', error => {
|
|
264
|
+
ui.error(`Failed to run kubectl: ${error.message}`);
|
|
265
|
+
ui.info('Make sure kubectl is installed and configured');
|
|
266
|
+
resolve();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
proc.on('close', code => {
|
|
270
|
+
ui.newLine();
|
|
271
|
+
if (code === 0) {
|
|
272
|
+
ui.success('No changes detected');
|
|
273
|
+
} else if (code === 1) {
|
|
274
|
+
ui.info('Changes detected (see diff above)');
|
|
275
|
+
} else {
|
|
276
|
+
ui.error(`kubectl diff failed with exit code ${code}`);
|
|
277
|
+
}
|
|
278
|
+
resolve();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Display Kubernetes diff
|
|
285
|
+
*/
|
|
286
|
+
function _displayK8sDiff(
|
|
287
|
+
result: { success: boolean; hasDiff: boolean; output?: string },
|
|
288
|
+
options: PreviewOptions
|
|
289
|
+
): void {
|
|
290
|
+
if (options.format === 'json') {
|
|
291
|
+
console.log(JSON.stringify(result, null, 2));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
ui.print(ui.bold('Kubernetes Diff:'));
|
|
296
|
+
ui.newLine();
|
|
297
|
+
|
|
298
|
+
if (result.output) {
|
|
299
|
+
// Color the diff output
|
|
300
|
+
const lines = result.output.split('\n');
|
|
301
|
+
for (const line of lines) {
|
|
302
|
+
if (line.startsWith('+')) {
|
|
303
|
+
ui.print(ui.color(line, 'green'));
|
|
304
|
+
} else if (line.startsWith('-')) {
|
|
305
|
+
ui.print(ui.color(line, 'red'));
|
|
306
|
+
} else if (line.startsWith('@')) {
|
|
307
|
+
ui.print(ui.color(line, 'cyan'));
|
|
308
|
+
} else {
|
|
309
|
+
ui.print(line);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Preview Helm changes
|
|
317
|
+
*/
|
|
318
|
+
async function previewHelm(options: PreviewOptions): Promise<void> {
|
|
319
|
+
const chart = options.directory || '.';
|
|
320
|
+
const release = options.release || 'preview';
|
|
321
|
+
const namespace = options.namespace || 'default';
|
|
322
|
+
|
|
323
|
+
ui.info(`Chart: ${chart}`);
|
|
324
|
+
ui.info(`Release: ${release}`);
|
|
325
|
+
ui.info(`Namespace: ${namespace}`);
|
|
326
|
+
ui.newLine();
|
|
327
|
+
|
|
328
|
+
// Check if helm client is available
|
|
329
|
+
const clientAvailable = await helmClient.isAvailable();
|
|
330
|
+
|
|
331
|
+
// Helm client doesn't have a diff method, always use CLI
|
|
332
|
+
// If client is available, we could use template comparison in the future
|
|
333
|
+
if (clientAvailable) {
|
|
334
|
+
ui.info('Using helm template for preview...');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Use helm template CLI
|
|
338
|
+
await previewHelmWithCLI(options);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Preview Helm using helm CLI
|
|
343
|
+
*/
|
|
344
|
+
async function previewHelmWithCLI(options: PreviewOptions): Promise<void> {
|
|
345
|
+
const { spawn } = await import('child_process');
|
|
346
|
+
const chart = options.directory || '.';
|
|
347
|
+
const release = options.release || 'preview';
|
|
348
|
+
const namespace = options.namespace || 'default';
|
|
349
|
+
|
|
350
|
+
// Use helm template to preview what would be generated
|
|
351
|
+
const args = ['template', release, chart, '-n', namespace];
|
|
352
|
+
|
|
353
|
+
if (options.valuesFile) {
|
|
354
|
+
args.push('-f', options.valuesFile);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
ui.info(`Running: helm ${args.join(' ')}`);
|
|
358
|
+
ui.newLine();
|
|
359
|
+
|
|
360
|
+
return new Promise(resolve => {
|
|
361
|
+
const proc = spawn('helm', args, {
|
|
362
|
+
stdio: 'inherit',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
proc.on('error', error => {
|
|
366
|
+
ui.error(`Failed to run helm: ${error.message}`);
|
|
367
|
+
ui.info('Make sure helm is installed and in your PATH');
|
|
368
|
+
resolve();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
proc.on('close', code => {
|
|
372
|
+
ui.newLine();
|
|
373
|
+
if (code === 0) {
|
|
374
|
+
ui.success('Template preview complete');
|
|
375
|
+
} else {
|
|
376
|
+
ui.error(`helm template failed with exit code ${code}`);
|
|
377
|
+
}
|
|
378
|
+
resolve();
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Display Helm diff
|
|
385
|
+
*/
|
|
386
|
+
function _displayHelmDiff(
|
|
387
|
+
result: { success: boolean; hasDiff?: boolean; output?: string },
|
|
388
|
+
options: PreviewOptions
|
|
389
|
+
): void {
|
|
390
|
+
if (options.format === 'json') {
|
|
391
|
+
console.log(JSON.stringify(result, null, 2));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!result.hasDiff) {
|
|
396
|
+
ui.success('No changes. Helm release is up to date.');
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
ui.print(ui.bold('Helm Diff:'));
|
|
401
|
+
ui.newLine();
|
|
402
|
+
|
|
403
|
+
if (result.output) {
|
|
404
|
+
ui.print(result.output);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Run safety checks on the preview
|
|
410
|
+
*/
|
|
411
|
+
async function runSafetyChecks(
|
|
412
|
+
operation: string,
|
|
413
|
+
type: 'terraform' | 'kubernetes' | 'helm',
|
|
414
|
+
output: string,
|
|
415
|
+
options: PreviewOptions
|
|
416
|
+
): Promise<void> {
|
|
417
|
+
const policy = loadSafetyPolicy();
|
|
418
|
+
|
|
419
|
+
const context: SafetyContext = {
|
|
420
|
+
operation,
|
|
421
|
+
type,
|
|
422
|
+
planOutput: output,
|
|
423
|
+
metadata: {
|
|
424
|
+
directory: options.directory,
|
|
425
|
+
namespace: options.namespace,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const result = evaluateSafety(context, policy);
|
|
430
|
+
|
|
431
|
+
ui.newLine();
|
|
432
|
+
displaySafetySummary({
|
|
433
|
+
operation: `${type} ${operation}`,
|
|
434
|
+
risks: result.risks,
|
|
435
|
+
passed: result.passed,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (result.requiresApproval) {
|
|
439
|
+
ui.newLine();
|
|
440
|
+
ui.warning('This operation will require approval when applied');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Capitalize first letter
|
|
446
|
+
*/
|
|
447
|
+
function capitalize(str: string): string {
|
|
448
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Export as default
|
|
452
|
+
export default previewCommand;
|