@girardmedia/bootspring 2.0.21 → 2.0.23
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/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Deploy CLI
|
|
3
|
+
*
|
|
4
|
+
* Deploy projects to various hosting platforms with
|
|
5
|
+
* pre-deployment validation and quality checks.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module cli/deploy
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
|
|
14
|
+
// Type interfaces for JS modules
|
|
15
|
+
interface Colors {
|
|
16
|
+
reset: string;
|
|
17
|
+
bold: string;
|
|
18
|
+
dim: string;
|
|
19
|
+
cyan: string;
|
|
20
|
+
green: string;
|
|
21
|
+
yellow: string;
|
|
22
|
+
red: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PrintModule {
|
|
26
|
+
header(msg: string): void;
|
|
27
|
+
info(msg: string): void;
|
|
28
|
+
success(msg: string): void;
|
|
29
|
+
warning(msg: string): void;
|
|
30
|
+
error(msg: string): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface UtilsModule {
|
|
34
|
+
COLORS: Colors;
|
|
35
|
+
print: PrintModule;
|
|
36
|
+
parseArgs(args: string[]): ParsedArgs;
|
|
37
|
+
createSpinner(text: string): Spinner;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ParsedArgs {
|
|
41
|
+
_: string[];
|
|
42
|
+
preview?: boolean | undefined;
|
|
43
|
+
'skip-quality'?: boolean | undefined;
|
|
44
|
+
'dry-run'?: boolean | undefined;
|
|
45
|
+
force?: boolean | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Spinner {
|
|
49
|
+
start(): Spinner;
|
|
50
|
+
stop(): void;
|
|
51
|
+
succeed(text: string): void;
|
|
52
|
+
fail(text: string): void;
|
|
53
|
+
warn(text: string): void;
|
|
54
|
+
info(text: string): void;
|
|
55
|
+
text: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface DeployPhase {
|
|
59
|
+
name: string;
|
|
60
|
+
required: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface DeployTarget {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
cli: string;
|
|
68
|
+
installCmd: string;
|
|
69
|
+
deployCmd: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface DeployProgress {
|
|
73
|
+
isComplete: boolean;
|
|
74
|
+
overall: {
|
|
75
|
+
percentage: number;
|
|
76
|
+
};
|
|
77
|
+
target?: string | undefined;
|
|
78
|
+
startedAt?: string | undefined;
|
|
79
|
+
phases: Array<{
|
|
80
|
+
name: string;
|
|
81
|
+
status: string;
|
|
82
|
+
required: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ValidationResult {
|
|
87
|
+
passed: boolean;
|
|
88
|
+
checks: Record<string, boolean>;
|
|
89
|
+
issues: string[];
|
|
90
|
+
warnings: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface TargetDetectionResult {
|
|
94
|
+
selected?: string | undefined;
|
|
95
|
+
detected: Array<{ id: string; name: string }>;
|
|
96
|
+
available: Array<{ name: string }>;
|
|
97
|
+
framework?: string | undefined;
|
|
98
|
+
recommendations: Array<{ id: string }>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface BuildResult {
|
|
102
|
+
dryRun?: boolean | undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface QualityResult {
|
|
106
|
+
passed: boolean;
|
|
107
|
+
skipped: boolean;
|
|
108
|
+
checks: Record<string, { passed: boolean | null }>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface DeployResult {
|
|
112
|
+
target: string;
|
|
113
|
+
url?: string | undefined;
|
|
114
|
+
dryRun?: boolean | undefined;
|
|
115
|
+
success?: boolean | undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface VerifyResult {
|
|
119
|
+
passed: boolean;
|
|
120
|
+
skipped: boolean;
|
|
121
|
+
statusCode?: number | undefined;
|
|
122
|
+
dryRun?: boolean | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface GeneratedConfig {
|
|
126
|
+
file: string;
|
|
127
|
+
content: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface PhaseData {
|
|
131
|
+
result?: DeployResult | undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface WorkflowState {
|
|
135
|
+
target: string;
|
|
136
|
+
phases: Record<string, PhaseData>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface DeployWorkflowEngineInstance {
|
|
140
|
+
hasWorkflow(): boolean;
|
|
141
|
+
loadState(): void;
|
|
142
|
+
getProgress(): DeployProgress;
|
|
143
|
+
initializeWorkflow(target: string | null): void;
|
|
144
|
+
getNextPhase(): string | null;
|
|
145
|
+
startPhase(phaseId: string): void;
|
|
146
|
+
completePhase(phaseId: string, result: unknown): void;
|
|
147
|
+
failPhase(phaseId: string, message: string): void;
|
|
148
|
+
skipPhase(phaseId: string): void;
|
|
149
|
+
runValidation(): Promise<ValidationResult>;
|
|
150
|
+
detectTarget(): Promise<TargetDetectionResult>;
|
|
151
|
+
setTarget(targetId: string): void;
|
|
152
|
+
runBuild(): Promise<BuildResult>;
|
|
153
|
+
runQualityChecks(): Promise<QualityResult>;
|
|
154
|
+
runDeploy(): Promise<DeployResult>;
|
|
155
|
+
runVerification(): Promise<VerifyResult>;
|
|
156
|
+
getTargetInfo(targetId: string): DeployTarget;
|
|
157
|
+
getAllTargets(): DeployTarget[];
|
|
158
|
+
checkCliInstalled(cli: string): boolean;
|
|
159
|
+
generateTargetConfig(targetId: string): GeneratedConfig | null;
|
|
160
|
+
resetWorkflow(): void;
|
|
161
|
+
state: WorkflowState;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface DeployWorkflowEngineClass {
|
|
165
|
+
new (projectRoot: string, options?: { dryRun?: boolean; skipQuality?: boolean; env?: string }): DeployWorkflowEngineInstance;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface DeployWorkflowModule {
|
|
169
|
+
DeployWorkflowEngine: DeployWorkflowEngineClass;
|
|
170
|
+
DEPLOY_TARGETS: Record<string, DeployTarget>;
|
|
171
|
+
DEPLOY_PHASES: Record<string, DeployPhase>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const utils = require('../core/utils') as UtilsModule;
|
|
175
|
+
const { DeployWorkflowEngine, DEPLOY_TARGETS, DEPLOY_PHASES } = require('../core/deploy-workflow') as DeployWorkflowModule;
|
|
176
|
+
|
|
177
|
+
// Get project root
|
|
178
|
+
const projectRoot = process.cwd();
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Show deploy help
|
|
182
|
+
*/
|
|
183
|
+
function showHelp(): void {
|
|
184
|
+
console.log(`
|
|
185
|
+
${utils.COLORS.bold}Bootspring Deploy${utils.COLORS.reset}
|
|
186
|
+
Deploy your project to production
|
|
187
|
+
|
|
188
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
189
|
+
bootspring deploy Deploy to detected/configured target
|
|
190
|
+
bootspring deploy <target> Deploy to specific target
|
|
191
|
+
bootspring deploy status Show deployment status
|
|
192
|
+
bootspring deploy targets List available targets
|
|
193
|
+
bootspring deploy init <target> Initialize deployment config
|
|
194
|
+
bootspring deploy reset Reset deployment state
|
|
195
|
+
|
|
196
|
+
${utils.COLORS.bold}Targets:${utils.COLORS.reset}
|
|
197
|
+
vercel Vercel (recommended for Next.js)
|
|
198
|
+
railway Railway (full-stack apps)
|
|
199
|
+
fly Fly.io (edge deployment)
|
|
200
|
+
netlify Netlify (static sites & serverless)
|
|
201
|
+
docker Docker (build image)
|
|
202
|
+
aws AWS Amplify
|
|
203
|
+
|
|
204
|
+
${utils.COLORS.bold}Options:${utils.COLORS.reset}
|
|
205
|
+
--preview Preview deployment (not production)
|
|
206
|
+
--skip-quality Skip quality checks
|
|
207
|
+
--dry-run Show what would be deployed
|
|
208
|
+
--force Force deployment even with warnings
|
|
209
|
+
|
|
210
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
211
|
+
bootspring deploy # Auto-detect and deploy
|
|
212
|
+
bootspring deploy vercel # Deploy to Vercel
|
|
213
|
+
bootspring deploy --preview # Deploy preview
|
|
214
|
+
bootspring deploy init vercel # Create vercel.json
|
|
215
|
+
bootspring deploy targets # List all targets
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Start deployment workflow
|
|
221
|
+
*/
|
|
222
|
+
async function deployStart(args: ParsedArgs): Promise<void> {
|
|
223
|
+
const target = args._[0] || null;
|
|
224
|
+
const preview = args.preview || false;
|
|
225
|
+
const skipQuality = args['skip-quality'] || false;
|
|
226
|
+
const dryRun = args['dry-run'] || false;
|
|
227
|
+
const force = args.force || false;
|
|
228
|
+
|
|
229
|
+
const workflow = new DeployWorkflowEngine(projectRoot, {
|
|
230
|
+
dryRun,
|
|
231
|
+
skipQuality,
|
|
232
|
+
env: preview ? 'preview' : 'production'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Check for existing workflow
|
|
236
|
+
if (workflow.hasWorkflow()) {
|
|
237
|
+
workflow.loadState();
|
|
238
|
+
const progress = workflow.getProgress();
|
|
239
|
+
|
|
240
|
+
if (!progress.isComplete) {
|
|
241
|
+
utils.print.info('Existing deployment in progress.');
|
|
242
|
+
console.log(` Progress: ${progress.overall.percentage}% complete`);
|
|
243
|
+
console.log(` Target: ${progress.target || 'not set'}`);
|
|
244
|
+
console.log('');
|
|
245
|
+
utils.print.info('Use "bootspring deploy reset" to start fresh');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Initialize workflow
|
|
251
|
+
utils.print.header('Bootspring Deploy');
|
|
252
|
+
console.log(preview ? 'Preview deployment' : 'Production deployment');
|
|
253
|
+
if (dryRun) {
|
|
254
|
+
console.log(`${utils.COLORS.yellow}[DRY RUN]${utils.COLORS.reset}`);
|
|
255
|
+
}
|
|
256
|
+
console.log('');
|
|
257
|
+
|
|
258
|
+
workflow.initializeWorkflow(target);
|
|
259
|
+
|
|
260
|
+
// Run workflow
|
|
261
|
+
await runWorkflowLoop(workflow, { force, dryRun });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Run the workflow loop
|
|
266
|
+
*/
|
|
267
|
+
async function runWorkflowLoop(workflow: DeployWorkflowEngineInstance, options: { force?: boolean; dryRun?: boolean } = {}): Promise<void> {
|
|
268
|
+
while (true) {
|
|
269
|
+
const nextPhaseId = workflow.getNextPhase();
|
|
270
|
+
|
|
271
|
+
if (!nextPhaseId) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const phase = DEPLOY_PHASES[nextPhaseId];
|
|
276
|
+
|
|
277
|
+
// Run the phase
|
|
278
|
+
workflow.startPhase(nextPhaseId);
|
|
279
|
+
const spinner = utils.createSpinner(`${phase?.name || nextPhaseId}...`).start();
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
let result: unknown;
|
|
283
|
+
|
|
284
|
+
switch (nextPhaseId) {
|
|
285
|
+
case 'validate':
|
|
286
|
+
result = await workflow.runValidation();
|
|
287
|
+
if ((result as ValidationResult).passed) {
|
|
288
|
+
spinner.succeed('Pre-flight validation passed');
|
|
289
|
+
} else {
|
|
290
|
+
spinner.fail('Pre-flight validation failed');
|
|
291
|
+
displayValidationResults(result as ValidationResult);
|
|
292
|
+
if (!options.force) {
|
|
293
|
+
utils.print.error('Fix issues above or use --force to continue');
|
|
294
|
+
workflow.failPhase(nextPhaseId, 'Validation failed');
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
utils.print.warning('Continuing with --force');
|
|
298
|
+
}
|
|
299
|
+
displayValidationResults(result as ValidationResult);
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
case 'target':
|
|
303
|
+
result = await workflow.detectTarget();
|
|
304
|
+
if ((result as TargetDetectionResult).selected) {
|
|
305
|
+
const target = workflow.getTargetInfo((result as TargetDetectionResult).selected as string);
|
|
306
|
+
spinner.succeed(`Target: ${target.name}`);
|
|
307
|
+
} else if ((result as TargetDetectionResult).detected.length > 0) {
|
|
308
|
+
const detected = (result as TargetDetectionResult).detected;
|
|
309
|
+
spinner.succeed(`Detected: ${detected.map(t => t.name).join(', ')}`);
|
|
310
|
+
// Auto-select first detected
|
|
311
|
+
workflow.setTarget(detected[0]?.id || '');
|
|
312
|
+
(result as TargetDetectionResult).selected = detected[0]?.id;
|
|
313
|
+
} else {
|
|
314
|
+
spinner.fail('No deployment target detected');
|
|
315
|
+
displayTargetSelection(result as TargetDetectionResult);
|
|
316
|
+
utils.print.error('Run "bootspring deploy init <target>" to configure');
|
|
317
|
+
workflow.failPhase(nextPhaseId, 'No target');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
displayTargetResults(result as TargetDetectionResult);
|
|
321
|
+
break;
|
|
322
|
+
|
|
323
|
+
case 'build':
|
|
324
|
+
if (options.dryRun) {
|
|
325
|
+
spinner.info('Build: [DRY RUN] Would run npm run build');
|
|
326
|
+
result = { dryRun: true };
|
|
327
|
+
} else {
|
|
328
|
+
spinner.text = 'Building...';
|
|
329
|
+
result = await workflow.runBuild();
|
|
330
|
+
spinner.succeed('Build successful');
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case 'quality':
|
|
335
|
+
result = await workflow.runQualityChecks();
|
|
336
|
+
if ((result as QualityResult).skipped) {
|
|
337
|
+
spinner.info('Quality checks skipped');
|
|
338
|
+
} else if ((result as QualityResult).passed) {
|
|
339
|
+
spinner.succeed('Quality checks passed');
|
|
340
|
+
} else {
|
|
341
|
+
spinner.warn('Some quality checks failed');
|
|
342
|
+
}
|
|
343
|
+
displayQualityResults(result as QualityResult);
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case 'deploy':
|
|
347
|
+
if (options.dryRun) {
|
|
348
|
+
const target = workflow.getTargetInfo(workflow.state.target);
|
|
349
|
+
spinner.info(`Deploy: [DRY RUN] Would run: ${target.deployCmd}`);
|
|
350
|
+
result = { dryRun: true };
|
|
351
|
+
} else {
|
|
352
|
+
spinner.text = 'Deploying...';
|
|
353
|
+
console.log('');
|
|
354
|
+
result = await workflow.runDeploy();
|
|
355
|
+
console.log('');
|
|
356
|
+
spinner.succeed(`Deployed to ${(result as DeployResult).target}`);
|
|
357
|
+
if ((result as DeployResult).url) {
|
|
358
|
+
console.log(` ${utils.COLORS.green}URL:${utils.COLORS.reset} ${(result as DeployResult).url}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
|
|
363
|
+
case 'verify':
|
|
364
|
+
if (options.dryRun) {
|
|
365
|
+
spinner.info('Verification: [DRY RUN] Skipped');
|
|
366
|
+
result = { dryRun: true };
|
|
367
|
+
} else {
|
|
368
|
+
result = await workflow.runVerification();
|
|
369
|
+
if ((result as VerifyResult).skipped) {
|
|
370
|
+
spinner.info('Verification skipped');
|
|
371
|
+
} else if ((result as VerifyResult).passed) {
|
|
372
|
+
spinner.succeed(`Verification passed (HTTP ${(result as VerifyResult).statusCode})`);
|
|
373
|
+
} else {
|
|
374
|
+
spinner.warn('Verification failed');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
|
|
379
|
+
default:
|
|
380
|
+
spinner.warn(`Unknown phase: ${nextPhaseId}`);
|
|
381
|
+
workflow.skipPhase(nextPhaseId);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
workflow.completePhase(nextPhaseId, result);
|
|
386
|
+
|
|
387
|
+
} catch (error) {
|
|
388
|
+
spinner.fail(`Failed: ${(error as Error).message}`);
|
|
389
|
+
workflow.failPhase(nextPhaseId, (error as Error).message);
|
|
390
|
+
|
|
391
|
+
if (phase?.required) {
|
|
392
|
+
utils.print.error('Deployment failed.');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
console.log('');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Deployment complete
|
|
401
|
+
displayCompletionSummary(workflow, options);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Display validation results
|
|
406
|
+
*/
|
|
407
|
+
function displayValidationResults(result: ValidationResult): void {
|
|
408
|
+
console.log('');
|
|
409
|
+
|
|
410
|
+
for (const [check, passed] of Object.entries(result.checks)) {
|
|
411
|
+
const icon = passed ?
|
|
412
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
413
|
+
`${utils.COLORS.yellow}○${utils.COLORS.reset}`;
|
|
414
|
+
console.log(` ${icon} ${formatCheckName(check)}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (result.issues.length > 0) {
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log(` ${utils.COLORS.red}Issues:${utils.COLORS.reset}`);
|
|
420
|
+
for (const issue of result.issues) {
|
|
421
|
+
console.log(` - ${issue}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (result.warnings.length > 0) {
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log(` ${utils.COLORS.yellow}Warnings:${utils.COLORS.reset}`);
|
|
428
|
+
for (const warning of result.warnings) {
|
|
429
|
+
console.log(` - ${warning}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Display target results
|
|
436
|
+
*/
|
|
437
|
+
function displayTargetResults(result: TargetDetectionResult): void {
|
|
438
|
+
if (result.framework) {
|
|
439
|
+
console.log(` ${utils.COLORS.dim}Framework: ${result.framework}${utils.COLORS.reset}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (result.available.length > 0 && !result.selected) {
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log(' Available CLIs:');
|
|
445
|
+
for (const target of result.available.slice(0, 3)) {
|
|
446
|
+
console.log(` ${utils.COLORS.cyan}${target.name}${utils.COLORS.reset}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Display target selection options
|
|
453
|
+
*/
|
|
454
|
+
function displayTargetSelection(result: TargetDetectionResult): void {
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log('Recommended targets for your project:');
|
|
457
|
+
|
|
458
|
+
for (const rec of result.recommendations.slice(0, 3)) {
|
|
459
|
+
const target = DEPLOY_TARGETS[rec.id];
|
|
460
|
+
if (target) {
|
|
461
|
+
console.log(` ${utils.COLORS.cyan}${rec.id}${utils.COLORS.reset} - ${target.description}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log('Run: bootspring deploy init <target>');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Display quality results
|
|
471
|
+
*/
|
|
472
|
+
function displayQualityResults(result: QualityResult): void {
|
|
473
|
+
if (result.skipped) return;
|
|
474
|
+
|
|
475
|
+
console.log('');
|
|
476
|
+
for (const [check, status] of Object.entries(result.checks)) {
|
|
477
|
+
if (status.passed === null) continue;
|
|
478
|
+
|
|
479
|
+
const icon = status.passed ?
|
|
480
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
481
|
+
`${utils.COLORS.yellow}○${utils.COLORS.reset}`;
|
|
482
|
+
console.log(` ${icon} ${check}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Display completion summary
|
|
488
|
+
*/
|
|
489
|
+
function displayCompletionSummary(workflow: DeployWorkflowEngineInstance, options: { dryRun?: boolean }): void {
|
|
490
|
+
const deployResult = workflow.state.phases.deploy?.result;
|
|
491
|
+
|
|
492
|
+
console.log('');
|
|
493
|
+
|
|
494
|
+
if (options.dryRun) {
|
|
495
|
+
utils.print.info('Dry run complete - no changes made');
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (deployResult?.url) {
|
|
500
|
+
utils.print.success('Deployment successful!');
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(` ${utils.COLORS.bold}URL:${utils.COLORS.reset} ${deployResult.url}`);
|
|
503
|
+
} else if (deployResult?.success) {
|
|
504
|
+
utils.print.success('Deployment successful!');
|
|
505
|
+
} else {
|
|
506
|
+
utils.print.warning('Deployment completed with warnings');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
console.log('');
|
|
510
|
+
utils.print.info('Next steps:');
|
|
511
|
+
console.log(' 1. Verify your deployment is working');
|
|
512
|
+
console.log(' 2. Set up environment variables if needed');
|
|
513
|
+
console.log(' 3. Configure custom domain (optional)');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Format check name
|
|
518
|
+
*/
|
|
519
|
+
function formatCheckName(name: string): string {
|
|
520
|
+
const names: Record<string, string> = {
|
|
521
|
+
packageJson: 'package.json exists',
|
|
522
|
+
nodeModules: 'Dependencies installed',
|
|
523
|
+
buildScript: 'Build script available',
|
|
524
|
+
gitClean: 'Git working tree clean',
|
|
525
|
+
envFile: 'Environment file exists'
|
|
526
|
+
};
|
|
527
|
+
return names[name] || name;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Show deployment status
|
|
532
|
+
*/
|
|
533
|
+
async function deployStatus(): Promise<void> {
|
|
534
|
+
const workflow = new DeployWorkflowEngine(projectRoot);
|
|
535
|
+
|
|
536
|
+
if (!workflow.hasWorkflow()) {
|
|
537
|
+
utils.print.info('No deployment workflow started.');
|
|
538
|
+
utils.print.info('Run "bootspring deploy" to begin.');
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
workflow.loadState();
|
|
543
|
+
const progress = workflow.getProgress();
|
|
544
|
+
|
|
545
|
+
utils.print.header('Deployment Status');
|
|
546
|
+
|
|
547
|
+
console.log(`Progress: ${progress.overall.percentage}% complete`);
|
|
548
|
+
console.log(`Target: ${progress.target || 'not set'}`);
|
|
549
|
+
console.log(`Started: ${progress.startedAt ? new Date(progress.startedAt).toLocaleString() : 'N/A'}`);
|
|
550
|
+
console.log('');
|
|
551
|
+
|
|
552
|
+
console.log('Phases:');
|
|
553
|
+
for (const phase of progress.phases) {
|
|
554
|
+
const statusIcon = getStatusIcon(phase.status);
|
|
555
|
+
const required = phase.required ? '' : ' (optional)';
|
|
556
|
+
console.log(` ${statusIcon} ${phase.name}${required}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (progress.isComplete) {
|
|
560
|
+
console.log('');
|
|
561
|
+
const deployResult = workflow.state.phases.deploy?.result;
|
|
562
|
+
if (deployResult?.url) {
|
|
563
|
+
console.log(`URL: ${deployResult.url}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* List available targets
|
|
570
|
+
*/
|
|
571
|
+
function listTargets(): void {
|
|
572
|
+
const workflow = new DeployWorkflowEngine(projectRoot);
|
|
573
|
+
|
|
574
|
+
utils.print.header('Deployment Targets');
|
|
575
|
+
console.log('');
|
|
576
|
+
|
|
577
|
+
const targets = workflow.getAllTargets();
|
|
578
|
+
|
|
579
|
+
for (const target of targets) {
|
|
580
|
+
const installed = workflow.checkCliInstalled(target.cli);
|
|
581
|
+
const icon = installed ?
|
|
582
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
583
|
+
`${utils.COLORS.dim}○${utils.COLORS.reset}`;
|
|
584
|
+
|
|
585
|
+
console.log(` ${icon} ${utils.COLORS.cyan}${target.id}${utils.COLORS.reset}`);
|
|
586
|
+
console.log(` ${utils.COLORS.dim}${target.description}${utils.COLORS.reset}`);
|
|
587
|
+
console.log(` CLI: ${target.cli} ${installed ? '(installed)' : '(not installed)'}`);
|
|
588
|
+
console.log('');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
console.log('Install a CLI to enable deployment:');
|
|
592
|
+
console.log(` ${utils.COLORS.dim}npm install -g vercel${utils.COLORS.reset}`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Initialize deployment config
|
|
597
|
+
*/
|
|
598
|
+
async function deployInit(args: ParsedArgs): Promise<void> {
|
|
599
|
+
const targetId = args._[1];
|
|
600
|
+
|
|
601
|
+
if (!targetId) {
|
|
602
|
+
utils.print.error('Specify a target: bootspring deploy init <target>');
|
|
603
|
+
console.log('');
|
|
604
|
+
console.log('Available targets: vercel, railway, fly, netlify, docker, aws');
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const workflow = new DeployWorkflowEngine(projectRoot);
|
|
609
|
+
const config = workflow.generateTargetConfig(targetId);
|
|
610
|
+
|
|
611
|
+
if (!config) {
|
|
612
|
+
utils.print.error(`Unknown target: ${targetId}`);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const filePath = path.join(projectRoot, config.file);
|
|
617
|
+
|
|
618
|
+
if (fs.existsSync(filePath)) {
|
|
619
|
+
utils.print.warning(`${config.file} already exists`);
|
|
620
|
+
utils.print.info('Use --force to overwrite');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const spinner = utils.createSpinner(`Creating ${config.file}...`).start();
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
fs.writeFileSync(filePath, config.content);
|
|
628
|
+
spinner.succeed(`Created ${config.file}`);
|
|
629
|
+
|
|
630
|
+
const target = workflow.getTargetInfo(targetId);
|
|
631
|
+
console.log('');
|
|
632
|
+
utils.print.info('Next steps:');
|
|
633
|
+
console.log(` 1. Install CLI: ${target.installCmd}`);
|
|
634
|
+
console.log(` 2. Login: ${target.cli} login`);
|
|
635
|
+
console.log(` 3. Deploy: bootspring deploy ${targetId}`);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
spinner.fail(`Failed: ${(error as Error).message}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Reset deployment workflow
|
|
643
|
+
*/
|
|
644
|
+
async function deployReset(): Promise<void> {
|
|
645
|
+
const workflow = new DeployWorkflowEngine(projectRoot);
|
|
646
|
+
|
|
647
|
+
if (!workflow.hasWorkflow()) {
|
|
648
|
+
utils.print.info('No workflow to reset.');
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
workflow.resetWorkflow();
|
|
653
|
+
utils.print.success('Deployment workflow reset.');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Get status icon
|
|
658
|
+
*/
|
|
659
|
+
function getStatusIcon(status: string): string {
|
|
660
|
+
switch (status) {
|
|
661
|
+
case 'completed':
|
|
662
|
+
return `${utils.COLORS.green}✓${utils.COLORS.reset}`;
|
|
663
|
+
case 'in_progress':
|
|
664
|
+
return `${utils.COLORS.cyan}●${utils.COLORS.reset}`;
|
|
665
|
+
case 'failed':
|
|
666
|
+
return `${utils.COLORS.red}✗${utils.COLORS.reset}`;
|
|
667
|
+
case 'skipped':
|
|
668
|
+
return `${utils.COLORS.dim}○${utils.COLORS.reset}`;
|
|
669
|
+
default:
|
|
670
|
+
return `${utils.COLORS.dim}○${utils.COLORS.reset}`;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Main entry point
|
|
676
|
+
*/
|
|
677
|
+
export async function run(args: string[]): Promise<void> {
|
|
678
|
+
const parsedArgs = utils.parseArgs(args);
|
|
679
|
+
const subcommand = parsedArgs._[0];
|
|
680
|
+
|
|
681
|
+
switch (subcommand) {
|
|
682
|
+
case 'status':
|
|
683
|
+
return deployStatus();
|
|
684
|
+
|
|
685
|
+
case 'targets':
|
|
686
|
+
case 'list':
|
|
687
|
+
return listTargets();
|
|
688
|
+
|
|
689
|
+
case 'init':
|
|
690
|
+
return deployInit(parsedArgs);
|
|
691
|
+
|
|
692
|
+
case 'reset':
|
|
693
|
+
return deployReset();
|
|
694
|
+
|
|
695
|
+
case 'help':
|
|
696
|
+
case '--help':
|
|
697
|
+
case '-h':
|
|
698
|
+
return showHelp();
|
|
699
|
+
|
|
700
|
+
default:
|
|
701
|
+
// Start deployment (subcommand might be a target)
|
|
702
|
+
return deployStart(parsedArgs);
|
|
703
|
+
}
|
|
704
|
+
}
|