openkitt 0.1.2
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 +236 -0
- package/dist/ast/engine.d.ts +26 -0
- package/dist/ast/engine.js +130 -0
- package/dist/ast/engine.js.map +1 -0
- package/dist/ast/operations.d.ts +13 -0
- package/dist/ast/operations.js +329 -0
- package/dist/ast/operations.js.map +1 -0
- package/dist/ast/validator.d.ts +19 -0
- package/dist/ast/validator.js +281 -0
- package/dist/ast/validator.js.map +1 -0
- package/dist/audit/logger.d.ts +14 -0
- package/dist/audit/logger.js +102 -0
- package/dist/audit/logger.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +401 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/create-confirm.d.ts +13 -0
- package/dist/commands/create-confirm.js +38 -0
- package/dist/commands/create-confirm.js.map +1 -0
- package/dist/commands/create-infra.d.ts +20 -0
- package/dist/commands/create-infra.js +63 -0
- package/dist/commands/create-infra.js.map +1 -0
- package/dist/commands/create-manifest.d.ts +10 -0
- package/dist/commands/create-manifest.js +21 -0
- package/dist/commands/create-manifest.js.map +1 -0
- package/dist/commands/create-packages.d.ts +17 -0
- package/dist/commands/create-packages.js +69 -0
- package/dist/commands/create-packages.js.map +1 -0
- package/dist/commands/create-pipeline.d.ts +15 -0
- package/dist/commands/create-pipeline.js +57 -0
- package/dist/commands/create-pipeline.js.map +1 -0
- package/dist/commands/create-scaffolding.d.ts +12 -0
- package/dist/commands/create-scaffolding.js +137 -0
- package/dist/commands/create-scaffolding.js.map +1 -0
- package/dist/commands/create-staging.d.ts +13 -0
- package/dist/commands/create-staging.js +17 -0
- package/dist/commands/create-staging.js.map +1 -0
- package/dist/commands/create-transforms.d.ts +10 -0
- package/dist/commands/create-transforms.js +33 -0
- package/dist/commands/create-transforms.js.map +1 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.js +155 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/delete.d.ts +2 -0
- package/dist/commands/delete.js +83 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.js +371 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/domain.d.ts +2 -0
- package/dist/commands/domain.js +90 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +153 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.js +107 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +217 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +142 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +235 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +90 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +113 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/run.d.ts +14 -0
- package/dist/commands/run.js +196 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/settings.d.ts +3 -0
- package/dist/commands/settings.js +278 -0
- package/dist/commands/settings.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +88 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.js +242 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/credentials/config.d.ts +12 -0
- package/dist/credentials/config.js +196 -0
- package/dist/credentials/config.js.map +1 -0
- package/dist/credentials/encryption.d.ts +9 -0
- package/dist/credentials/encryption.js +100 -0
- package/dist/credentials/encryption.js.map +1 -0
- package/dist/credentials/keychain.d.ts +8 -0
- package/dist/credentials/keychain.js +236 -0
- package/dist/credentials/keychain.js.map +1 -0
- package/dist/credentials/railway.d.ts +9 -0
- package/dist/credentials/railway.js +69 -0
- package/dist/credentials/railway.js.map +1 -0
- package/dist/llm/client.d.ts +59 -0
- package/dist/llm/client.js +530 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/operations.d.ts +39 -0
- package/dist/llm/operations.js +131 -0
- package/dist/llm/operations.js.map +1 -0
- package/dist/llm/rate-limiter.d.ts +20 -0
- package/dist/llm/rate-limiter.js +57 -0
- package/dist/llm/rate-limiter.js.map +1 -0
- package/dist/llm/scaffolding.d.ts +51 -0
- package/dist/llm/scaffolding.js +118 -0
- package/dist/llm/scaffolding.js.map +1 -0
- package/dist/manifest/drift.d.ts +6 -0
- package/dist/manifest/drift.js +45 -0
- package/dist/manifest/drift.js.map +1 -0
- package/dist/manifest/reader.d.ts +12 -0
- package/dist/manifest/reader.js +99 -0
- package/dist/manifest/reader.js.map +1 -0
- package/dist/manifest/types.d.ts +31 -0
- package/dist/manifest/types.js +2 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/manifest/writer.d.ts +9 -0
- package/dist/manifest/writer.js +142 -0
- package/dist/manifest/writer.js.map +1 -0
- package/dist/mcp/client.d.ts +21 -0
- package/dist/mcp/client.js +213 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/lifecycle.d.ts +12 -0
- package/dist/mcp/lifecycle.js +149 -0
- package/dist/mcp/lifecycle.js.map +1 -0
- package/dist/mcp/project-guard.d.ts +14 -0
- package/dist/mcp/project-guard.js +27 -0
- package/dist/mcp/project-guard.js.map +1 -0
- package/dist/prompts/operations.d.ts +1 -0
- package/dist/prompts/operations.js +160 -0
- package/dist/prompts/operations.js.map +1 -0
- package/dist/prompts/scaffolding.d.ts +1 -0
- package/dist/prompts/scaffolding.js +331 -0
- package/dist/prompts/scaffolding.js.map +1 -0
- package/dist/prompts/version.d.ts +2 -0
- package/dist/prompts/version.js +3 -0
- package/dist/prompts/version.js.map +1 -0
- package/dist/sandbox/command-allowlist.d.ts +6 -0
- package/dist/sandbox/command-allowlist.js +103 -0
- package/dist/sandbox/command-allowlist.js.map +1 -0
- package/dist/sandbox/package-allowlist.d.ts +6 -0
- package/dist/sandbox/package-allowlist.js +49 -0
- package/dist/sandbox/package-allowlist.js.map +1 -0
- package/dist/sandbox/scanner.d.ts +25 -0
- package/dist/sandbox/scanner.js +196 -0
- package/dist/sandbox/scanner.js.map +1 -0
- package/dist/sandbox/staging.d.ts +12 -0
- package/dist/sandbox/staging.js +107 -0
- package/dist/sandbox/staging.js.map +1 -0
- package/dist/scaffold/frameworks/express.d.ts +3 -0
- package/dist/scaffold/frameworks/express.js +242 -0
- package/dist/scaffold/frameworks/express.js.map +1 -0
- package/dist/scaffold/frameworks/hono.d.ts +3 -0
- package/dist/scaffold/frameworks/hono.js +238 -0
- package/dist/scaffold/frameworks/hono.js.map +1 -0
- package/dist/scaffold/frameworks/nextjs.d.ts +3 -0
- package/dist/scaffold/frameworks/nextjs.js +447 -0
- package/dist/scaffold/frameworks/nextjs.js.map +1 -0
- package/dist/scaffold/frameworks/tanstack-start.d.ts +3 -0
- package/dist/scaffold/frameworks/tanstack-start.js +280 -0
- package/dist/scaffold/frameworks/tanstack-start.js.map +1 -0
- package/dist/scaffold/index.d.ts +4 -0
- package/dist/scaffold/index.js +138 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/integrations/backend.d.ts +3 -0
- package/dist/scaffold/integrations/backend.js +403 -0
- package/dist/scaffold/integrations/backend.js.map +1 -0
- package/dist/scaffold/integrations/posthog.d.ts +2 -0
- package/dist/scaffold/integrations/posthog.js +21 -0
- package/dist/scaffold/integrations/posthog.js.map +1 -0
- package/dist/scaffold/integrations/sentry.d.ts +2 -0
- package/dist/scaffold/integrations/sentry.js +16 -0
- package/dist/scaffold/integrations/sentry.js.map +1 -0
- package/dist/scaffold/integrations/storybook.d.ts +2 -0
- package/dist/scaffold/integrations/storybook.js +79 -0
- package/dist/scaffold/integrations/storybook.js.map +1 -0
- package/dist/scaffold/integrations/vitest.d.ts +2 -0
- package/dist/scaffold/integrations/vitest.js +37 -0
- package/dist/scaffold/integrations/vitest.js.map +1 -0
- package/dist/scaffold/package-builder.d.ts +3 -0
- package/dist/scaffold/package-builder.js +136 -0
- package/dist/scaffold/package-builder.js.map +1 -0
- package/dist/scaffold/packages.d.ts +4 -0
- package/dist/scaffold/packages.js +144 -0
- package/dist/scaffold/packages.js.map +1 -0
- package/dist/scaffold/types.d.ts +20 -0
- package/dist/scaffold/types.js +2 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-guard.d.ts +5 -0
- package/dist/utils/auth-guard.js +52 -0
- package/dist/utils/auth-guard.js.map +1 -0
- package/dist/utils/cleanup.d.ts +2 -0
- package/dist/utils/cleanup.js +37 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/constraints.d.ts +11 -0
- package/dist/utils/constraints.js +162 -0
- package/dist/utils/constraints.js.map +1 -0
- package/dist/utils/display.d.ts +32 -0
- package/dist/utils/display.js +103 -0
- package/dist/utils/display.js.map +1 -0
- package/dist/utils/global-config.d.ts +8 -0
- package/dist/utils/global-config.js +39 -0
- package/dist/utils/global-config.js.map +1 -0
- package/dist/utils/pm.d.ts +4 -0
- package/dist/utils/pm.js +40 -0
- package/dist/utils/pm.js.map +1 -0
- package/dist/utils/prerequisites.d.ts +9 -0
- package/dist/utils/prerequisites.js +96 -0
- package/dist/utils/prerequisites.js.map +1 -0
- package/dist/utils/prompts.d.ts +25 -0
- package/dist/utils/prompts.js +148 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/validation.d.ts +9 -0
- package/dist/utils/validation.js +38 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/versions/compat.d.ts +13 -0
- package/dist/versions/compat.js +127 -0
- package/dist/versions/compat.js.map +1 -0
- package/dist/versions/integrity.d.ts +9 -0
- package/dist/versions/integrity.js +60 -0
- package/dist/versions/integrity.js.map +1 -0
- package/dist/versions/parser.d.ts +13 -0
- package/dist/versions/parser.js +106 -0
- package/dist/versions/parser.js.map +1 -0
- package/dist/versions/registry.d.ts +30 -0
- package/dist/versions/registry.js +165 -0
- package/dist/versions/registry.js.map +1 -0
- package/package.json +58 -0
- package/scripts/publish.sh +254 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createAuditLogger } from '../audit/logger.js';
|
|
4
|
+
import { isLlmConfigured } from '../credentials/config.js';
|
|
5
|
+
import { createLlmClient } from '../llm/client.js';
|
|
6
|
+
import { createRateLimiter } from '../llm/rate-limiter.js';
|
|
7
|
+
import { findWorkspaceRoot, isKittWorkspace, readManifest } from '../manifest/reader.js';
|
|
8
|
+
import { createMcpClient } from '../mcp/client.js';
|
|
9
|
+
import { shutdownMcpServer, spawnMcpServer } from '../mcp/lifecycle.js';
|
|
10
|
+
import { error, info, renderContextBanner, success, warn } from '../utils/display.js';
|
|
11
|
+
import { validateSelectionConstraints, resolveIntegrations } from '../utils/constraints.js';
|
|
12
|
+
import { runSelectionFlow, CancelError } from '../utils/prompts.js';
|
|
13
|
+
import { wireSharedPackages } from './create-packages.js';
|
|
14
|
+
import { recordAppInManifest } from './create-manifest.js';
|
|
15
|
+
import { executeCreateScaffolding } from './create-scaffolding.js';
|
|
16
|
+
import { stageNewFiles } from './create-staging.js';
|
|
17
|
+
import { confirmAndApply } from './create-confirm.js';
|
|
18
|
+
import { executeTransformPipeline } from './create-transforms.js';
|
|
19
|
+
import { executeCommandPipeline } from './create-pipeline.js';
|
|
20
|
+
import { provisionInfrastructure } from './create-infra.js';
|
|
21
|
+
import { getCommand } from '../utils/pm.js';
|
|
22
|
+
import { resolveDevScript, spawnDevServer, startDevServerWithUrl } from './run.js';
|
|
23
|
+
import { readAutoOpenBrowser } from './settings.js';
|
|
24
|
+
export default async function createCommand(context, _args) {
|
|
25
|
+
const workspaceDir = findWorkspaceRoot(process.cwd()) ?? '.';
|
|
26
|
+
if (!isKittWorkspace(workspaceDir)) {
|
|
27
|
+
error('Not a KITT workspace. Run /init first.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const manifest = readManifest(workspaceDir);
|
|
31
|
+
if (!manifest) {
|
|
32
|
+
error('Could not read workspace manifest.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!(await isLlmConfigured())) {
|
|
36
|
+
error('LLM provider is not configured. Run /login llm first.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const logger = createAuditLogger(workspaceDir);
|
|
40
|
+
logger.cmd('/create', 'START');
|
|
41
|
+
let selections;
|
|
42
|
+
try {
|
|
43
|
+
selections = await runSelectionFlow(workspaceDir);
|
|
44
|
+
}
|
|
45
|
+
catch (selectionError) {
|
|
46
|
+
if (selectionError instanceof CancelError)
|
|
47
|
+
return;
|
|
48
|
+
throw selectionError;
|
|
49
|
+
}
|
|
50
|
+
const constraintResult = validateSelectionConstraints(selections, manifest);
|
|
51
|
+
if (!constraintResult.valid) {
|
|
52
|
+
error(constraintResult.error ?? 'Invalid selection.');
|
|
53
|
+
logger.cmd('/create', 'FAILED', constraintResult.error);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const resolvedIntegrations = resolveIntegrations(selections, manifest);
|
|
57
|
+
const appDir = join(workspaceDir, 'apps', selections.appName);
|
|
58
|
+
mkdirSync(appDir, { recursive: true });
|
|
59
|
+
let mcpClient = null;
|
|
60
|
+
try {
|
|
61
|
+
const rateLimiter = createRateLimiter();
|
|
62
|
+
const llmClient = await createLlmClient(rateLimiter);
|
|
63
|
+
const server = await spawnMcpServer();
|
|
64
|
+
mcpClient = await createMcpClient(server);
|
|
65
|
+
await mcpClient.initialize();
|
|
66
|
+
const scaffoldResult = await executeCreateScaffolding({
|
|
67
|
+
llmClient,
|
|
68
|
+
selections,
|
|
69
|
+
resolvedIntegrations,
|
|
70
|
+
manifest,
|
|
71
|
+
workspaceDir,
|
|
72
|
+
});
|
|
73
|
+
if (context.debug) {
|
|
74
|
+
const { inputTokens, outputTokens } = scaffoldResult.debug;
|
|
75
|
+
const llmConfig = await import('../credentials/config.js').then((m) => m.getLlmConfig());
|
|
76
|
+
if (llmConfig) {
|
|
77
|
+
renderContextBanner({ inputTokens, outputTokens, provider: llmConfig.provider, model: llmConfig.model });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const stagingResult = stageNewFiles(scaffoldResult.scaffolding.newFiles, workspaceDir);
|
|
81
|
+
const confirmResult = await confirmAndApply({
|
|
82
|
+
workspaceDir,
|
|
83
|
+
stagedFiles: stagingResult.stagedFiles,
|
|
84
|
+
diffSummary: stagingResult.diffSummary,
|
|
85
|
+
scanResult: stagingResult.scanResult,
|
|
86
|
+
dryRun: context.dryRun,
|
|
87
|
+
});
|
|
88
|
+
if (confirmResult === 'rejected') {
|
|
89
|
+
logger.cmd('/create', 'FAILED', 'user rejected staged changes');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { actions } = wireSharedPackages({
|
|
93
|
+
appName: selections.appName,
|
|
94
|
+
selections,
|
|
95
|
+
resolvedIntegrations,
|
|
96
|
+
manifest,
|
|
97
|
+
workspaceDir,
|
|
98
|
+
});
|
|
99
|
+
for (const action of actions) {
|
|
100
|
+
info(`Package ${action.packageName} ${action.action}.`);
|
|
101
|
+
}
|
|
102
|
+
if (scaffoldResult.scaffolding.astTransforms.length > 0) {
|
|
103
|
+
const transformResult = executeTransformPipeline(scaffoldResult.scaffolding.astTransforms, workspaceDir);
|
|
104
|
+
for (const transformError of transformResult.validationErrors) {
|
|
105
|
+
warn(`AST transform skipped: ${transformError}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
executeCommandPipeline(scaffoldResult.scaffolding.commands, {
|
|
109
|
+
workspaceDir,
|
|
110
|
+
packageManager: manifest.workspace.packageManager,
|
|
111
|
+
dryRun: context.dryRun,
|
|
112
|
+
});
|
|
113
|
+
const infraResult = await provisionInfrastructure({
|
|
114
|
+
llmClient,
|
|
115
|
+
mcpClient,
|
|
116
|
+
appName: selections.appName,
|
|
117
|
+
selections,
|
|
118
|
+
resolvedIntegrations,
|
|
119
|
+
manifest,
|
|
120
|
+
});
|
|
121
|
+
recordAppInManifest({
|
|
122
|
+
appName: selections.appName,
|
|
123
|
+
selections,
|
|
124
|
+
resolvedIntegrations,
|
|
125
|
+
manifest,
|
|
126
|
+
workspaceDir,
|
|
127
|
+
serviceId: infraResult.serviceId,
|
|
128
|
+
});
|
|
129
|
+
logger.cmd('/create', 'SUCCESS');
|
|
130
|
+
success(`App ${selections.appName} created.`);
|
|
131
|
+
if (existsSync(appDir)) {
|
|
132
|
+
const runCmd = getCommand(manifest.workspace.packageManager, 'run');
|
|
133
|
+
const scriptResolution = resolveDevScript(appDir);
|
|
134
|
+
if (scriptResolution.found) {
|
|
135
|
+
const autoOpen = readAutoOpenBrowser();
|
|
136
|
+
if (autoOpen) {
|
|
137
|
+
await startDevServerWithUrl(appDir, runCmd, scriptResolution.script, selections.appName);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await spawnDevServer(appDir, runCmd, scriptResolution.script, selections.appName);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (createError) {
|
|
146
|
+
const detail = createError instanceof Error ? createError.message : String(createError);
|
|
147
|
+
error(`Create failed: ${detail}`);
|
|
148
|
+
logger.cmd('/create', 'FAILED', detail);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
mcpClient?.close();
|
|
152
|
+
await shutdownMcpServer();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { createAuditLogger } from '../audit/logger.js';
|
|
5
|
+
import { findWorkspaceRoot, isKittWorkspace, reconcileManifest } from '../manifest/reader.js';
|
|
6
|
+
import { removeApp, writeManifest } from '../manifest/writer.js';
|
|
7
|
+
import { error, info, success, warn } from '../utils/display.js';
|
|
8
|
+
function removePackages(manifest, packageNames) {
|
|
9
|
+
const nextPackages = { ...manifest.packages };
|
|
10
|
+
for (const name of packageNames) {
|
|
11
|
+
delete nextPackages[name];
|
|
12
|
+
}
|
|
13
|
+
return { ...manifest, packages: nextPackages };
|
|
14
|
+
}
|
|
15
|
+
export default async function deleteCommand(context, args) {
|
|
16
|
+
const workspaceDir = findWorkspaceRoot(process.cwd()) ?? '.';
|
|
17
|
+
if (!isKittWorkspace(workspaceDir)) {
|
|
18
|
+
error('Not a KITT workspace.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const manifest = reconcileManifest(workspaceDir);
|
|
22
|
+
if (!manifest) {
|
|
23
|
+
error('Could not read manifest.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const appNames = Object.keys(manifest.apps);
|
|
27
|
+
if (appNames.length === 0) {
|
|
28
|
+
info('No apps to delete.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let appName = '';
|
|
32
|
+
if (context.yes && args[0]) {
|
|
33
|
+
appName = args[0];
|
|
34
|
+
if (!manifest.apps[appName]) {
|
|
35
|
+
error(`App "${appName}" not found.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const selection = await p.select({
|
|
41
|
+
message: 'Select app to delete:',
|
|
42
|
+
options: appNames.map((name) => ({ value: name, label: name })),
|
|
43
|
+
});
|
|
44
|
+
if (p.isCancel(selection)) {
|
|
45
|
+
warn('Delete cancelled.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
appName = selection;
|
|
49
|
+
}
|
|
50
|
+
const app = manifest.apps[appName];
|
|
51
|
+
info(`App: ${appName} (${app.type}, ${app.framework})`);
|
|
52
|
+
info(`Integrations: ${app.integrations.join(', ') || 'none'}`);
|
|
53
|
+
const confirmation = await p.confirm({
|
|
54
|
+
message: `Delete "${appName}" and all its files?`,
|
|
55
|
+
});
|
|
56
|
+
if (p.isCancel(confirmation) || !confirmation) {
|
|
57
|
+
warn('Delete cancelled.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const logger = createAuditLogger(workspaceDir);
|
|
61
|
+
rmSync(join(workspaceDir, 'apps', appName), { recursive: true, force: true });
|
|
62
|
+
let nextManifest = removeApp(manifest, appName);
|
|
63
|
+
const orphanedPackages = Object.entries(nextManifest.packages)
|
|
64
|
+
.filter(([, pkg]) => pkg.consumers.length === 0)
|
|
65
|
+
.map(([name]) => name);
|
|
66
|
+
if (orphanedPackages.length > 0) {
|
|
67
|
+
const toRemove = await p.multiselect({
|
|
68
|
+
message: 'These packages have no remaining consumers. Select which to delete:',
|
|
69
|
+
options: orphanedPackages.map((name) => ({ value: name, label: name })),
|
|
70
|
+
initialValues: orphanedPackages,
|
|
71
|
+
});
|
|
72
|
+
if (!p.isCancel(toRemove) && toRemove.length > 0) {
|
|
73
|
+
for (const packageName of toRemove) {
|
|
74
|
+
rmSync(join(workspaceDir, 'packages', packageName), { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
nextManifest = removePackages(nextManifest, toRemove);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
writeManifest(workspaceDir, nextManifest);
|
|
80
|
+
success(`App "${appName}" deleted.`);
|
|
81
|
+
logger.cmd('/delete', 'SUCCESS');
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppContext } from '../types.js';
|
|
2
|
+
export declare function generateRailwayToml(framework: string, packageManager: string): string;
|
|
3
|
+
export declare function ensureRailwayToml(appName: string, framework: string, packageManager: string): boolean;
|
|
4
|
+
export declare function deployTemplateCommand(context: AppContext, args: string[]): Promise<void>;
|
|
5
|
+
export default function deployCommand(context: AppContext, args: string[], commandKey: string): Promise<void>;
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { createAuditLogger } from '../audit/logger.js';
|
|
5
|
+
import { isLlmConfigured } from '../credentials/config.js';
|
|
6
|
+
import { createLlmClient } from '../llm/client.js';
|
|
7
|
+
import { buildOperationsContext, executeOperations } from '../llm/operations.js';
|
|
8
|
+
import { createRateLimiter } from '../llm/rate-limiter.js';
|
|
9
|
+
import { findWorkspaceRoot, isKittWorkspace, reconcileManifest } from '../manifest/reader.js';
|
|
10
|
+
import { createMcpClient } from '../mcp/client.js';
|
|
11
|
+
import { shutdownMcpServer, spawnMcpServer } from '../mcp/lifecycle.js';
|
|
12
|
+
import { createProjectGuard } from '../mcp/project-guard.js';
|
|
13
|
+
import { error, info, warn } from '../utils/display.js';
|
|
14
|
+
const SECRET_PATTERNS = [
|
|
15
|
+
'SECRET',
|
|
16
|
+
'KEY',
|
|
17
|
+
'TOKEN',
|
|
18
|
+
'PASSWORD',
|
|
19
|
+
'CREDENTIAL',
|
|
20
|
+
'PRIVATE',
|
|
21
|
+
'API_KEY',
|
|
22
|
+
'DATABASE_URL',
|
|
23
|
+
'REDIS_URL',
|
|
24
|
+
'DSN',
|
|
25
|
+
'CONNECTION_STRING',
|
|
26
|
+
];
|
|
27
|
+
const DEPLOY_SCAN_IGNORED_DIRS = new Set(['.git', '.kitt', 'node_modules', 'dist', '.next', '.vinxi']);
|
|
28
|
+
const APPROVED_DEPLOY_TEMPLATES = ['PostgreSQL', 'MySQL', 'Redis', 'MinIO'];
|
|
29
|
+
function normalizePathForMatch(filePath) {
|
|
30
|
+
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
31
|
+
}
|
|
32
|
+
function toPatternRegex(pattern) {
|
|
33
|
+
const escaped = pattern
|
|
34
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
35
|
+
.replace(/\*\*/g, '__DOUBLE_STAR__')
|
|
36
|
+
.replace(/\*/g, '[^/]*')
|
|
37
|
+
.replace(/__DOUBLE_STAR__/g, '.*');
|
|
38
|
+
return new RegExp(`^${escaped}$`);
|
|
39
|
+
}
|
|
40
|
+
function matchesGitignorePattern(filePath, pattern) {
|
|
41
|
+
const normalizedPath = normalizePathForMatch(filePath);
|
|
42
|
+
const normalizedPattern = normalizePathForMatch(pattern);
|
|
43
|
+
if (!normalizedPattern.includes('/')) {
|
|
44
|
+
return toPatternRegex(normalizedPattern).test(basename(normalizedPath));
|
|
45
|
+
}
|
|
46
|
+
return toPatternRegex(normalizedPattern).test(normalizedPath);
|
|
47
|
+
}
|
|
48
|
+
function loadGitignorePatterns(workspaceDir) {
|
|
49
|
+
const gitignorePath = join(workspaceDir, '.gitignore');
|
|
50
|
+
if (!existsSync(gitignorePath)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return readFileSync(gitignorePath, 'utf-8')
|
|
54
|
+
.split(/\r?\n/)
|
|
55
|
+
.map((line) => line.trim())
|
|
56
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
57
|
+
}
|
|
58
|
+
function isIgnoredByGitignore(filePath, patterns) {
|
|
59
|
+
let ignored = false;
|
|
60
|
+
for (const rawPattern of patterns) {
|
|
61
|
+
const isNegated = rawPattern.startsWith('!');
|
|
62
|
+
const pattern = isNegated ? rawPattern.slice(1) : rawPattern;
|
|
63
|
+
if (pattern.length === 0) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const matches = matchesGitignorePattern(filePath, pattern);
|
|
67
|
+
if (!matches) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
ignored = !isNegated;
|
|
71
|
+
}
|
|
72
|
+
return ignored;
|
|
73
|
+
}
|
|
74
|
+
function hasSecretPattern(content) {
|
|
75
|
+
const variableRegex = /^([A-Z_][A-Z0-9_]*)\s*=/gm;
|
|
76
|
+
for (const match of content.matchAll(variableRegex)) {
|
|
77
|
+
const variableName = (match[1] ?? '').toUpperCase();
|
|
78
|
+
if (SECRET_PATTERNS.some((pattern) => variableName.includes(pattern))) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const normalized = content.toUpperCase();
|
|
83
|
+
return SECRET_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
84
|
+
}
|
|
85
|
+
function isEnvFileName(fileName) {
|
|
86
|
+
return fileName === '.env' || fileName.startsWith('.env.');
|
|
87
|
+
}
|
|
88
|
+
function listEnvFiles(workspaceDir) {
|
|
89
|
+
const envFiles = [];
|
|
90
|
+
const queue = [workspaceDir];
|
|
91
|
+
while (queue.length > 0) {
|
|
92
|
+
const currentDir = queue.pop();
|
|
93
|
+
if (!currentDir) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const entryPath = join(currentDir, entry.name);
|
|
99
|
+
const relativePath = normalizePathForMatch(entryPath.replace(`${workspaceDir}/`, ''));
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
if (DEPLOY_SCAN_IGNORED_DIRS.has(entry.name)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
queue.push(entryPath);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (entry.isFile() && isEnvFileName(entry.name)) {
|
|
108
|
+
envFiles.push(relativePath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return envFiles;
|
|
113
|
+
}
|
|
114
|
+
function scanEnvFilesForSecrets(workspaceDir) {
|
|
115
|
+
const gitignorePatterns = loadGitignorePatterns(workspaceDir);
|
|
116
|
+
const envFiles = listEnvFiles(workspaceDir);
|
|
117
|
+
const blockedFiles = [];
|
|
118
|
+
for (const envFile of envFiles) {
|
|
119
|
+
if (isIgnoredByGitignore(envFile, gitignorePatterns)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const fullPath = join(workspaceDir, envFile);
|
|
123
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
124
|
+
if (hasSecretPattern(content)) {
|
|
125
|
+
blockedFiles.push(envFile);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return blockedFiles;
|
|
129
|
+
}
|
|
130
|
+
function getSelectedApps(args, manifest) {
|
|
131
|
+
const appNames = Object.keys(manifest.apps);
|
|
132
|
+
const selectionArg = args[0] ?? '';
|
|
133
|
+
if (selectionArg.length > 0) {
|
|
134
|
+
if (selectionArg.toLowerCase() === 'all') {
|
|
135
|
+
return appNames;
|
|
136
|
+
}
|
|
137
|
+
if (!manifest.apps[selectionArg]) {
|
|
138
|
+
error(`App "${selectionArg}" not found.`);
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
return [selectionArg];
|
|
142
|
+
}
|
|
143
|
+
return p
|
|
144
|
+
.select({
|
|
145
|
+
message: 'Select app to deploy:',
|
|
146
|
+
options: [
|
|
147
|
+
{ value: 'all', label: 'all' },
|
|
148
|
+
...appNames.map((name) => ({ value: name, label: name })),
|
|
149
|
+
],
|
|
150
|
+
})
|
|
151
|
+
.then((selection) => {
|
|
152
|
+
if (p.isCancel(selection)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
if (selection === 'all') {
|
|
156
|
+
return appNames;
|
|
157
|
+
}
|
|
158
|
+
return [selection];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async function getEnvironmentName(context) {
|
|
162
|
+
if (context.env && context.env.length > 0) {
|
|
163
|
+
return context.env;
|
|
164
|
+
}
|
|
165
|
+
const selection = await p.select({
|
|
166
|
+
message: 'Select Railway environment:',
|
|
167
|
+
options: [{ value: 'production', label: 'production' }],
|
|
168
|
+
initialValue: 'production',
|
|
169
|
+
});
|
|
170
|
+
if (p.isCancel(selection)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return selection;
|
|
174
|
+
}
|
|
175
|
+
export function generateRailwayToml(framework, packageManager) {
|
|
176
|
+
const healthcheckPath = framework === 'hono' || framework === 'expressjs' ? '/health' : '/';
|
|
177
|
+
const deployEnv = framework === 'nextjs' ? '\n[deploy.env]\nPORT = "3000"\n' : '';
|
|
178
|
+
return `[build]\nbuilder = "NIXPACKS"\nbuildCommand = "${packageManager} run build"\n\n[deploy]\nstartCommand = "${packageManager} run start"\nhealthcheckPath = "${healthcheckPath}"\nhealthcheckTimeout = 300\nrestartPolicyType = "ON_FAILURE"\nrestartPolicyMaxRetries = 3${deployEnv}`;
|
|
179
|
+
}
|
|
180
|
+
export function ensureRailwayToml(appName, framework, packageManager) {
|
|
181
|
+
const railwayTomlPath = join('apps', appName, 'railway.toml');
|
|
182
|
+
if (existsSync(railwayTomlPath)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
writeFileSync(railwayTomlPath, generateRailwayToml(framework, packageManager), 'utf-8');
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
export async function deployTemplateCommand(context, args) {
|
|
189
|
+
const workspaceDir = findWorkspaceRoot(process.cwd()) ?? '.';
|
|
190
|
+
const logger = createAuditLogger(workspaceDir);
|
|
191
|
+
logger.cmd('/deploy:template', 'START');
|
|
192
|
+
if (!isKittWorkspace(workspaceDir)) {
|
|
193
|
+
error('Not a KITT workspace.');
|
|
194
|
+
logger.cmd('/deploy:template', 'FAILED', 'not a workspace');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const manifest = reconcileManifest(workspaceDir);
|
|
198
|
+
if (!manifest) {
|
|
199
|
+
error('Could not read manifest.');
|
|
200
|
+
logger.cmd('/deploy:template', 'FAILED', 'manifest read failed');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const projectId = manifest.workspace.railway?.projectId;
|
|
204
|
+
if (!projectId) {
|
|
205
|
+
error('Workspace is not linked to a Railway project. Run /init or /link first.');
|
|
206
|
+
logger.cmd('/deploy:template', 'FAILED', 'missing linked railway project');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!(await isLlmConfigured())) {
|
|
210
|
+
error('LLM provider is not configured. Run /login llm first.');
|
|
211
|
+
logger.cmd('/deploy:template', 'FAILED', 'llm not configured');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const templateName = args[0]?.trim() ?? '';
|
|
215
|
+
if (templateName.length === 0) {
|
|
216
|
+
error('Template name is required. Usage: /deploy:template <templateName>');
|
|
217
|
+
logger.cmd('/deploy:template', 'FAILED', 'missing template name');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const allowedTemplateMap = new Map(APPROVED_DEPLOY_TEMPLATES.map((template) => [template.toLowerCase(), template]));
|
|
221
|
+
const normalizedTemplateName = templateName.toLowerCase();
|
|
222
|
+
const approvedTemplateName = allowedTemplateMap.get(normalizedTemplateName);
|
|
223
|
+
if (!approvedTemplateName) {
|
|
224
|
+
const allowedTemplates = APPROVED_DEPLOY_TEMPLATES.join(', ');
|
|
225
|
+
error(`Template "${templateName}" is not allowed. Allowed templates: ${allowedTemplates}.`);
|
|
226
|
+
logger.cmd('/deploy:template', 'FAILED', `template not allowed: ${templateName}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (context.dryRun) {
|
|
230
|
+
info(`[dry-run] Would deploy template: ${approvedTemplateName}`);
|
|
231
|
+
info('[dry-run] No changes were made.');
|
|
232
|
+
logger.cmd('/deploy:template', 'SUCCESS', 'dry-run completed');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
let mcpClient = null;
|
|
236
|
+
try {
|
|
237
|
+
const rateLimiter = createRateLimiter();
|
|
238
|
+
const llmClient = await createLlmClient(rateLimiter);
|
|
239
|
+
const server = await spawnMcpServer();
|
|
240
|
+
mcpClient = await createMcpClient(server);
|
|
241
|
+
await mcpClient.initialize();
|
|
242
|
+
const projectGuard = createProjectGuard(projectId);
|
|
243
|
+
const operationsContext = buildOperationsContext('deploy:template', [approvedTemplateName], manifest);
|
|
244
|
+
const operationsResult = await executeOperations({
|
|
245
|
+
llmClient,
|
|
246
|
+
mcpClient,
|
|
247
|
+
context: operationsContext,
|
|
248
|
+
projectGuard,
|
|
249
|
+
});
|
|
250
|
+
info(operationsResult);
|
|
251
|
+
logger.cmd('/deploy:template', 'SUCCESS');
|
|
252
|
+
}
|
|
253
|
+
catch (operationError) {
|
|
254
|
+
const detail = operationError instanceof Error ? operationError.message : String(operationError);
|
|
255
|
+
error(`Deploy template command failed: ${detail}`);
|
|
256
|
+
logger.cmd('/deploy:template', 'FAILED', detail);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
mcpClient?.close();
|
|
260
|
+
await shutdownMcpServer();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export default async function deployCommand(context, args, commandKey) {
|
|
264
|
+
if (commandKey === 'deploy:template') {
|
|
265
|
+
await deployTemplateCommand(context, args);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const workspaceDir = findWorkspaceRoot(process.cwd()) ?? '.';
|
|
269
|
+
const logger = createAuditLogger(workspaceDir);
|
|
270
|
+
logger.cmd('/deploy', 'START');
|
|
271
|
+
if (!isKittWorkspace(workspaceDir)) {
|
|
272
|
+
error('Not a KITT workspace.');
|
|
273
|
+
logger.cmd('/deploy', 'FAILED', 'not a workspace');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const manifest = reconcileManifest(workspaceDir);
|
|
277
|
+
if (!manifest) {
|
|
278
|
+
error('Could not read manifest.');
|
|
279
|
+
logger.cmd('/deploy', 'FAILED', 'manifest read failed');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const projectId = manifest.workspace.railway?.projectId;
|
|
283
|
+
if (!projectId) {
|
|
284
|
+
error('Workspace is not linked to a Railway project. Run /init or /link first.');
|
|
285
|
+
logger.cmd('/deploy', 'FAILED', 'missing linked railway project');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!(await isLlmConfigured())) {
|
|
289
|
+
error('LLM provider is not configured. Run /login llm first.');
|
|
290
|
+
logger.cmd('/deploy', 'FAILED', 'llm not configured');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const secretFiles = scanEnvFilesForSecrets(workspaceDir);
|
|
294
|
+
if (secretFiles.length > 0) {
|
|
295
|
+
const uniqueSecretFiles = Array.from(new Set(secretFiles)).sort();
|
|
296
|
+
error(`Deployment blocked. Potential secrets found in: ${uniqueSecretFiles.join(', ')}`);
|
|
297
|
+
logger.security('BLOCKED', `deploy blocked by secret scan: ${uniqueSecretFiles.join(', ')}`);
|
|
298
|
+
logger.cmd('/deploy', 'FAILED', 'secret scan blocked');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const appNames = Object.keys(manifest.apps);
|
|
302
|
+
if (appNames.length === 0) {
|
|
303
|
+
error('No apps found in manifest.');
|
|
304
|
+
logger.cmd('/deploy', 'FAILED', 'no apps found');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const selectedAppsResult = await getSelectedApps(args, manifest);
|
|
308
|
+
if (selectedAppsResult === null) {
|
|
309
|
+
warn('Deploy command cancelled.');
|
|
310
|
+
logger.cmd('/deploy', 'FAILED', 'app selection cancelled');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (selectedAppsResult.length === 0) {
|
|
314
|
+
logger.cmd('/deploy', 'FAILED', 'app not found');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const environment = await getEnvironmentName(context);
|
|
318
|
+
if (!environment) {
|
|
319
|
+
warn('Deploy command cancelled.');
|
|
320
|
+
logger.cmd('/deploy', 'FAILED', 'environment selection cancelled');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
for (const appName of selectedAppsResult) {
|
|
324
|
+
const app = manifest.apps[appName];
|
|
325
|
+
const generated = ensureRailwayToml(appName, app.framework, manifest.workspace.packageManager);
|
|
326
|
+
if (generated) {
|
|
327
|
+
info(`Generated apps/${appName}/railway.toml for ${app.framework}.`);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
info(`Using existing apps/${appName}/railway.toml.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (context.dryRun) {
|
|
334
|
+
info('[dry-run] Would deploy the following apps:');
|
|
335
|
+
for (const appName of selectedAppsResult) {
|
|
336
|
+
info(` • ${appName}`);
|
|
337
|
+
}
|
|
338
|
+
info(`[dry-run] Target environment: ${environment}`);
|
|
339
|
+
info('[dry-run] No changes were made.');
|
|
340
|
+
logger.cmd('/deploy', 'SUCCESS', 'dry-run completed');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let mcpClient = null;
|
|
344
|
+
try {
|
|
345
|
+
const rateLimiter = createRateLimiter();
|
|
346
|
+
const llmClient = await createLlmClient(rateLimiter);
|
|
347
|
+
const server = await spawnMcpServer();
|
|
348
|
+
mcpClient = await createMcpClient(server);
|
|
349
|
+
await mcpClient.initialize();
|
|
350
|
+
const projectGuard = createProjectGuard(projectId);
|
|
351
|
+
const operationsContext = buildOperationsContext('deploy', [...selectedAppsResult, '--env', environment], manifest);
|
|
352
|
+
const operationsResult = await executeOperations({
|
|
353
|
+
llmClient,
|
|
354
|
+
mcpClient,
|
|
355
|
+
context: operationsContext,
|
|
356
|
+
projectGuard,
|
|
357
|
+
});
|
|
358
|
+
info(operationsResult);
|
|
359
|
+
logger.cmd('/deploy', 'SUCCESS');
|
|
360
|
+
}
|
|
361
|
+
catch (operationError) {
|
|
362
|
+
const detail = operationError instanceof Error ? operationError.message : String(operationError);
|
|
363
|
+
error(`Deploy command failed: ${detail}`);
|
|
364
|
+
logger.cmd('/deploy', 'FAILED', detail);
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
mcpClient?.close();
|
|
368
|
+
await shutdownMcpServer();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=deploy.js.map
|