@girardmedia/bootspring 1.2.0 → 2.0.3
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 +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Deploy Workflow Engine
|
|
3
|
+
*
|
|
4
|
+
* Handles deployment workflows with pre-deployment validation,
|
|
5
|
+
* target detection, and execution.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module core/deploy-workflow
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const { execSync, spawn } = require('child_process');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Deployment targets configuration
|
|
17
|
+
*/
|
|
18
|
+
const DEPLOY_TARGETS = {
|
|
19
|
+
vercel: {
|
|
20
|
+
name: 'Vercel',
|
|
21
|
+
description: 'Deploy to Vercel (recommended for Next.js)',
|
|
22
|
+
detect: ['vercel.json', '.vercel'],
|
|
23
|
+
cli: 'vercel',
|
|
24
|
+
installCmd: 'npm install -g vercel',
|
|
25
|
+
deployCmd: 'vercel --prod',
|
|
26
|
+
previewCmd: 'vercel',
|
|
27
|
+
frameworks: ['nextjs', 'react', 'vue', 'svelte', 'nuxt', 'astro'],
|
|
28
|
+
envFile: '.env.production',
|
|
29
|
+
docs: 'https://vercel.com/docs'
|
|
30
|
+
},
|
|
31
|
+
railway: {
|
|
32
|
+
name: 'Railway',
|
|
33
|
+
description: 'Deploy to Railway (great for full-stack apps)',
|
|
34
|
+
detect: ['railway.json', 'railway.toml', '.railway'],
|
|
35
|
+
cli: 'railway',
|
|
36
|
+
installCmd: 'npm install -g @railway/cli',
|
|
37
|
+
deployCmd: 'railway up',
|
|
38
|
+
previewCmd: 'railway up --detach',
|
|
39
|
+
frameworks: ['nextjs', 'node', 'python', 'go', 'rust'],
|
|
40
|
+
envFile: '.env.production',
|
|
41
|
+
docs: 'https://docs.railway.app'
|
|
42
|
+
},
|
|
43
|
+
fly: {
|
|
44
|
+
name: 'Fly.io',
|
|
45
|
+
description: 'Deploy to Fly.io (edge deployment)',
|
|
46
|
+
detect: ['fly.toml'],
|
|
47
|
+
cli: 'fly',
|
|
48
|
+
installCmd: 'curl -L https://fly.io/install.sh | sh',
|
|
49
|
+
deployCmd: 'fly deploy',
|
|
50
|
+
previewCmd: 'fly deploy --build-only',
|
|
51
|
+
frameworks: ['docker', 'node', 'go', 'rust', 'python'],
|
|
52
|
+
envFile: '.env.production',
|
|
53
|
+
docs: 'https://fly.io/docs'
|
|
54
|
+
},
|
|
55
|
+
netlify: {
|
|
56
|
+
name: 'Netlify',
|
|
57
|
+
description: 'Deploy to Netlify (static sites & serverless)',
|
|
58
|
+
detect: ['netlify.toml', '.netlify'],
|
|
59
|
+
cli: 'netlify',
|
|
60
|
+
installCmd: 'npm install -g netlify-cli',
|
|
61
|
+
deployCmd: 'netlify deploy --prod',
|
|
62
|
+
previewCmd: 'netlify deploy',
|
|
63
|
+
frameworks: ['react', 'vue', 'svelte', 'gatsby', 'astro', 'nextjs'],
|
|
64
|
+
envFile: '.env.production',
|
|
65
|
+
docs: 'https://docs.netlify.com'
|
|
66
|
+
},
|
|
67
|
+
docker: {
|
|
68
|
+
name: 'Docker',
|
|
69
|
+
description: 'Build Docker image for deployment',
|
|
70
|
+
detect: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'],
|
|
71
|
+
cli: 'docker',
|
|
72
|
+
installCmd: 'Install Docker Desktop from https://docker.com',
|
|
73
|
+
deployCmd: 'docker build -t app . && docker push',
|
|
74
|
+
previewCmd: 'docker build -t app .',
|
|
75
|
+
frameworks: ['any'],
|
|
76
|
+
envFile: '.env.production',
|
|
77
|
+
docs: 'https://docs.docker.com'
|
|
78
|
+
},
|
|
79
|
+
aws: {
|
|
80
|
+
name: 'AWS Amplify',
|
|
81
|
+
description: 'Deploy to AWS Amplify',
|
|
82
|
+
detect: ['amplify.yml', 'amplify'],
|
|
83
|
+
cli: 'amplify',
|
|
84
|
+
installCmd: 'npm install -g @aws-amplify/cli',
|
|
85
|
+
deployCmd: 'amplify push',
|
|
86
|
+
previewCmd: 'amplify status',
|
|
87
|
+
frameworks: ['react', 'nextjs', 'vue', 'angular'],
|
|
88
|
+
envFile: '.env.production',
|
|
89
|
+
docs: 'https://docs.amplify.aws'
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Deployment phases
|
|
95
|
+
*/
|
|
96
|
+
const DEPLOY_PHASES = {
|
|
97
|
+
validate: {
|
|
98
|
+
name: 'Pre-flight Validation',
|
|
99
|
+
description: 'Validate project readiness for deployment',
|
|
100
|
+
required: true
|
|
101
|
+
},
|
|
102
|
+
target: {
|
|
103
|
+
name: 'Target Detection',
|
|
104
|
+
description: 'Detect or configure deployment target',
|
|
105
|
+
required: true
|
|
106
|
+
},
|
|
107
|
+
build: {
|
|
108
|
+
name: 'Build',
|
|
109
|
+
description: 'Build production assets',
|
|
110
|
+
required: true
|
|
111
|
+
},
|
|
112
|
+
quality: {
|
|
113
|
+
name: 'Quality Checks',
|
|
114
|
+
description: 'Run pre-deploy quality gates',
|
|
115
|
+
required: false
|
|
116
|
+
},
|
|
117
|
+
deploy: {
|
|
118
|
+
name: 'Deploy',
|
|
119
|
+
description: 'Execute deployment',
|
|
120
|
+
required: true
|
|
121
|
+
},
|
|
122
|
+
verify: {
|
|
123
|
+
name: 'Verification',
|
|
124
|
+
description: 'Verify deployment success',
|
|
125
|
+
required: false
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* DeployWorkflowEngine class
|
|
131
|
+
*/
|
|
132
|
+
class DeployWorkflowEngine {
|
|
133
|
+
constructor(projectRoot, options = {}) {
|
|
134
|
+
this.projectRoot = projectRoot;
|
|
135
|
+
this.bootspringDir = path.join(projectRoot, '.bootspring');
|
|
136
|
+
this.deployDir = path.join(this.bootspringDir, 'deploy');
|
|
137
|
+
this.statePath = path.join(this.deployDir, 'workflow-state.json');
|
|
138
|
+
this.options = options;
|
|
139
|
+
this.state = null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if workflow exists
|
|
144
|
+
*/
|
|
145
|
+
hasWorkflow() {
|
|
146
|
+
return fs.existsSync(this.statePath);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Initialize workflow
|
|
151
|
+
*/
|
|
152
|
+
initializeWorkflow(target = null) {
|
|
153
|
+
// Ensure directories exist
|
|
154
|
+
if (!fs.existsSync(this.deployDir)) {
|
|
155
|
+
fs.mkdirSync(this.deployDir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.state = {
|
|
159
|
+
version: '1.0.0',
|
|
160
|
+
startedAt: new Date().toISOString(),
|
|
161
|
+
lastUpdated: new Date().toISOString(),
|
|
162
|
+
target: target,
|
|
163
|
+
phases: {},
|
|
164
|
+
config: {
|
|
165
|
+
dryRun: this.options.dryRun || false,
|
|
166
|
+
skipQuality: this.options.skipQuality || false,
|
|
167
|
+
env: this.options.env || 'production'
|
|
168
|
+
},
|
|
169
|
+
history: []
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Initialize phases
|
|
173
|
+
for (const [phaseId, phase] of Object.entries(DEPLOY_PHASES)) {
|
|
174
|
+
this.state.phases[phaseId] = {
|
|
175
|
+
name: phase.name,
|
|
176
|
+
status: 'pending',
|
|
177
|
+
required: phase.required,
|
|
178
|
+
startedAt: null,
|
|
179
|
+
completedAt: null,
|
|
180
|
+
result: null,
|
|
181
|
+
error: null
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.saveState();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load state
|
|
190
|
+
*/
|
|
191
|
+
loadState() {
|
|
192
|
+
if (fs.existsSync(this.statePath)) {
|
|
193
|
+
this.state = JSON.parse(fs.readFileSync(this.statePath, 'utf-8'));
|
|
194
|
+
}
|
|
195
|
+
return this.state;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Save state
|
|
200
|
+
*/
|
|
201
|
+
saveState() {
|
|
202
|
+
this.state.lastUpdated = new Date().toISOString();
|
|
203
|
+
fs.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get next phase
|
|
208
|
+
*/
|
|
209
|
+
getNextPhase() {
|
|
210
|
+
const phaseOrder = ['validate', 'target', 'build', 'quality', 'deploy', 'verify'];
|
|
211
|
+
|
|
212
|
+
for (const phaseId of phaseOrder) {
|
|
213
|
+
const phase = this.state.phases[phaseId];
|
|
214
|
+
if (phase.status === 'pending' || phase.status === 'in_progress') {
|
|
215
|
+
return phaseId;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Start phase
|
|
224
|
+
*/
|
|
225
|
+
startPhase(phaseId) {
|
|
226
|
+
this.state.phases[phaseId].status = 'in_progress';
|
|
227
|
+
this.state.phases[phaseId].startedAt = new Date().toISOString();
|
|
228
|
+
this.saveState();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Complete phase
|
|
233
|
+
*/
|
|
234
|
+
completePhase(phaseId, result = null) {
|
|
235
|
+
this.state.phases[phaseId].status = 'completed';
|
|
236
|
+
this.state.phases[phaseId].completedAt = new Date().toISOString();
|
|
237
|
+
this.state.phases[phaseId].result = result;
|
|
238
|
+
this.state.history.push({
|
|
239
|
+
phase: phaseId,
|
|
240
|
+
action: 'completed',
|
|
241
|
+
timestamp: new Date().toISOString()
|
|
242
|
+
});
|
|
243
|
+
this.saveState();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Fail phase
|
|
248
|
+
*/
|
|
249
|
+
failPhase(phaseId, error) {
|
|
250
|
+
this.state.phases[phaseId].status = 'failed';
|
|
251
|
+
this.state.phases[phaseId].completedAt = new Date().toISOString();
|
|
252
|
+
this.state.phases[phaseId].error = error;
|
|
253
|
+
this.saveState();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Skip phase
|
|
258
|
+
*/
|
|
259
|
+
skipPhase(phaseId) {
|
|
260
|
+
this.state.phases[phaseId].status = 'skipped';
|
|
261
|
+
this.saveState();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get progress
|
|
266
|
+
*/
|
|
267
|
+
getProgress() {
|
|
268
|
+
const phases = Object.entries(this.state.phases).map(([id, phase]) => ({
|
|
269
|
+
id,
|
|
270
|
+
...phase
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
const completed = phases.filter(p => p.status === 'completed').length;
|
|
274
|
+
const total = phases.filter(p => p.required).length;
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
phases,
|
|
278
|
+
target: this.state.target,
|
|
279
|
+
overall: {
|
|
280
|
+
completed,
|
|
281
|
+
total,
|
|
282
|
+
percentage: Math.round((completed / total) * 100)
|
|
283
|
+
},
|
|
284
|
+
startedAt: this.state.startedAt,
|
|
285
|
+
lastUpdated: this.state.lastUpdated,
|
|
286
|
+
isComplete: phases.every(p =>
|
|
287
|
+
p.status === 'completed' || p.status === 'skipped' || !p.required
|
|
288
|
+
)
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Reset workflow
|
|
294
|
+
*/
|
|
295
|
+
resetWorkflow() {
|
|
296
|
+
if (fs.existsSync(this.deployDir)) {
|
|
297
|
+
fs.rmSync(this.deployDir, { recursive: true });
|
|
298
|
+
}
|
|
299
|
+
this.state = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ==========================================
|
|
303
|
+
// Phase Implementations
|
|
304
|
+
// ==========================================
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Run validation phase
|
|
308
|
+
*/
|
|
309
|
+
async runValidation() {
|
|
310
|
+
const checks = {
|
|
311
|
+
packageJson: false,
|
|
312
|
+
nodeModules: false,
|
|
313
|
+
buildScript: false,
|
|
314
|
+
gitClean: false,
|
|
315
|
+
envFile: false
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const issues = [];
|
|
319
|
+
const warnings = [];
|
|
320
|
+
|
|
321
|
+
// Check package.json
|
|
322
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
323
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
324
|
+
checks.packageJson = true;
|
|
325
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
326
|
+
|
|
327
|
+
// Check build script
|
|
328
|
+
if (pkg.scripts?.build) {
|
|
329
|
+
checks.buildScript = true;
|
|
330
|
+
} else {
|
|
331
|
+
issues.push('No build script found in package.json');
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
issues.push('No package.json found');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check node_modules
|
|
338
|
+
if (fs.existsSync(path.join(this.projectRoot, 'node_modules'))) {
|
|
339
|
+
checks.nodeModules = true;
|
|
340
|
+
} else {
|
|
341
|
+
warnings.push('node_modules not found - run npm install first');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Check git status
|
|
345
|
+
try {
|
|
346
|
+
const gitStatus = execSync('git status --porcelain', {
|
|
347
|
+
cwd: this.projectRoot,
|
|
348
|
+
encoding: 'utf-8'
|
|
349
|
+
});
|
|
350
|
+
if (!gitStatus.trim()) {
|
|
351
|
+
checks.gitClean = true;
|
|
352
|
+
} else {
|
|
353
|
+
warnings.push('Uncommitted changes detected');
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
warnings.push('Not a git repository');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Check .env.production or .env.local
|
|
360
|
+
const envPaths = ['.env.production', '.env.local', '.env'];
|
|
361
|
+
for (const envPath of envPaths) {
|
|
362
|
+
if (fs.existsSync(path.join(this.projectRoot, envPath))) {
|
|
363
|
+
checks.envFile = true;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (!checks.envFile) {
|
|
368
|
+
warnings.push('No environment file found (.env.production, .env.local, or .env)');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const passed = issues.length === 0;
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
passed,
|
|
375
|
+
checks,
|
|
376
|
+
issues,
|
|
377
|
+
warnings,
|
|
378
|
+
summary: passed ? 'Validation passed' : `${issues.length} issues found`
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Detect deployment target
|
|
384
|
+
*/
|
|
385
|
+
async detectTarget() {
|
|
386
|
+
const detected = [];
|
|
387
|
+
const recommendations = [];
|
|
388
|
+
|
|
389
|
+
// Check for existing configs
|
|
390
|
+
for (const [targetId, target] of Object.entries(DEPLOY_TARGETS)) {
|
|
391
|
+
for (const detectFile of target.detect) {
|
|
392
|
+
const filePath = path.join(this.projectRoot, detectFile);
|
|
393
|
+
if (fs.existsSync(filePath)) {
|
|
394
|
+
detected.push({
|
|
395
|
+
id: targetId,
|
|
396
|
+
name: target.name,
|
|
397
|
+
file: detectFile,
|
|
398
|
+
confidence: 'high'
|
|
399
|
+
});
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Check CLI availability
|
|
406
|
+
const available = [];
|
|
407
|
+
for (const [targetId, target] of Object.entries(DEPLOY_TARGETS)) {
|
|
408
|
+
if (this.checkCliInstalled(target.cli)) {
|
|
409
|
+
available.push({
|
|
410
|
+
id: targetId,
|
|
411
|
+
name: target.name,
|
|
412
|
+
cli: target.cli
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Detect framework and recommend
|
|
418
|
+
const framework = this.detectFramework();
|
|
419
|
+
if (framework) {
|
|
420
|
+
for (const [targetId, target] of Object.entries(DEPLOY_TARGETS)) {
|
|
421
|
+
if (target.frameworks.includes(framework) || target.frameworks.includes('any')) {
|
|
422
|
+
recommendations.push({
|
|
423
|
+
id: targetId,
|
|
424
|
+
name: target.name,
|
|
425
|
+
reason: `Works well with ${framework}`
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Set state target if exactly one detected
|
|
432
|
+
if (detected.length === 1 && !this.state.target) {
|
|
433
|
+
this.state.target = detected[0].id;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
detected,
|
|
438
|
+
available,
|
|
439
|
+
recommendations,
|
|
440
|
+
framework,
|
|
441
|
+
selected: this.state.target
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Check if CLI is installed
|
|
447
|
+
*/
|
|
448
|
+
checkCliInstalled(cli) {
|
|
449
|
+
try {
|
|
450
|
+
execSync(`which ${cli}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
451
|
+
return true;
|
|
452
|
+
} catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Detect framework
|
|
459
|
+
*/
|
|
460
|
+
detectFramework() {
|
|
461
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
462
|
+
if (!fs.existsSync(packageJsonPath)) return null;
|
|
463
|
+
|
|
464
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
465
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
466
|
+
|
|
467
|
+
if (deps.next) return 'nextjs';
|
|
468
|
+
if (deps['@angular/core']) return 'angular';
|
|
469
|
+
if (deps.vue) return 'vue';
|
|
470
|
+
if (deps.svelte) return 'svelte';
|
|
471
|
+
if (deps.gatsby) return 'gatsby';
|
|
472
|
+
if (deps.astro) return 'astro';
|
|
473
|
+
if (deps.nuxt) return 'nuxt';
|
|
474
|
+
if (deps.react) return 'react';
|
|
475
|
+
if (deps.express) return 'node';
|
|
476
|
+
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Set target
|
|
482
|
+
*/
|
|
483
|
+
setTarget(targetId) {
|
|
484
|
+
if (!DEPLOY_TARGETS[targetId]) {
|
|
485
|
+
throw new Error(`Unknown target: ${targetId}`);
|
|
486
|
+
}
|
|
487
|
+
this.state.target = targetId;
|
|
488
|
+
this.saveState();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Run build phase
|
|
493
|
+
*/
|
|
494
|
+
async runBuild() {
|
|
495
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
496
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
497
|
+
|
|
498
|
+
const buildScript = pkg.scripts?.build;
|
|
499
|
+
if (!buildScript) {
|
|
500
|
+
throw new Error('No build script found');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (this.state.config.dryRun) {
|
|
504
|
+
return {
|
|
505
|
+
dryRun: true,
|
|
506
|
+
command: 'npm run build',
|
|
507
|
+
skipped: true
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Execute build
|
|
512
|
+
return new Promise((resolve, reject) => {
|
|
513
|
+
const buildProcess = spawn('npm', ['run', 'build'], {
|
|
514
|
+
cwd: this.projectRoot,
|
|
515
|
+
stdio: 'pipe',
|
|
516
|
+
shell: true
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
let stdout = '';
|
|
520
|
+
let stderr = '';
|
|
521
|
+
|
|
522
|
+
buildProcess.stdout.on('data', (data) => {
|
|
523
|
+
stdout += data.toString();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
buildProcess.stderr.on('data', (data) => {
|
|
527
|
+
stderr += data.toString();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
buildProcess.on('close', (code) => {
|
|
531
|
+
if (code === 0) {
|
|
532
|
+
resolve({
|
|
533
|
+
success: true,
|
|
534
|
+
command: 'npm run build',
|
|
535
|
+
output: stdout.slice(-500) // Last 500 chars
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
reject(new Error(`Build failed with code ${code}: ${stderr.slice(-200)}`));
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Run quality checks
|
|
546
|
+
*/
|
|
547
|
+
async runQualityChecks() {
|
|
548
|
+
if (this.state.config.skipQuality) {
|
|
549
|
+
return { skipped: true };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const checks = {
|
|
553
|
+
lint: { passed: null, output: '' },
|
|
554
|
+
typecheck: { passed: null, output: '' },
|
|
555
|
+
test: { passed: null, output: '' }
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
559
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
560
|
+
|
|
561
|
+
// Run lint if available
|
|
562
|
+
if (pkg.scripts?.lint) {
|
|
563
|
+
try {
|
|
564
|
+
execSync('npm run lint', { cwd: this.projectRoot, encoding: 'utf-8', stdio: 'pipe' });
|
|
565
|
+
checks.lint.passed = true;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
checks.lint.passed = false;
|
|
568
|
+
checks.lint.output = error.message;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Run typecheck if TypeScript
|
|
573
|
+
if (pkg.scripts?.typecheck || pkg.scripts?.['type-check']) {
|
|
574
|
+
const script = pkg.scripts.typecheck ? 'typecheck' : 'type-check';
|
|
575
|
+
try {
|
|
576
|
+
execSync(`npm run ${script}`, { cwd: this.projectRoot, encoding: 'utf-8', stdio: 'pipe' });
|
|
577
|
+
checks.typecheck.passed = true;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
checks.typecheck.passed = false;
|
|
580
|
+
checks.typecheck.output = error.message;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Run tests if available (but don't fail deploy on test failure)
|
|
585
|
+
if (pkg.scripts?.test) {
|
|
586
|
+
try {
|
|
587
|
+
execSync('npm test -- --passWithNoTests', { cwd: this.projectRoot, encoding: 'utf-8', stdio: 'pipe' });
|
|
588
|
+
checks.test.passed = true;
|
|
589
|
+
} catch {
|
|
590
|
+
checks.test.passed = false;
|
|
591
|
+
// Don't fail on test failures, just warn
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const allPassed = Object.values(checks).every(c => c.passed === null || c.passed === true);
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
passed: allPassed,
|
|
599
|
+
checks,
|
|
600
|
+
summary: allPassed ? 'All quality checks passed' : 'Some checks failed'
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Run deployment
|
|
606
|
+
*/
|
|
607
|
+
async runDeploy() {
|
|
608
|
+
const target = DEPLOY_TARGETS[this.state.target];
|
|
609
|
+
if (!target) {
|
|
610
|
+
throw new Error('No deployment target configured');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Check CLI is installed
|
|
614
|
+
if (!this.checkCliInstalled(target.cli)) {
|
|
615
|
+
throw new Error(`${target.cli} CLI not installed. Run: ${target.installCmd}`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (this.state.config.dryRun) {
|
|
619
|
+
return {
|
|
620
|
+
dryRun: true,
|
|
621
|
+
target: target.name,
|
|
622
|
+
command: target.deployCmd,
|
|
623
|
+
skipped: true
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Execute deployment
|
|
628
|
+
const deployCmd = this.state.config.env === 'preview' ? target.previewCmd : target.deployCmd;
|
|
629
|
+
|
|
630
|
+
return new Promise((resolve, reject) => {
|
|
631
|
+
const deployProcess = spawn(deployCmd, [], {
|
|
632
|
+
cwd: this.projectRoot,
|
|
633
|
+
stdio: 'pipe',
|
|
634
|
+
shell: true
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
let stdout = '';
|
|
638
|
+
let stderr = '';
|
|
639
|
+
|
|
640
|
+
deployProcess.stdout.on('data', (data) => {
|
|
641
|
+
stdout += data.toString();
|
|
642
|
+
process.stdout.write(data);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
deployProcess.stderr.on('data', (data) => {
|
|
646
|
+
stderr += data.toString();
|
|
647
|
+
process.stderr.write(data);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
deployProcess.on('close', (code) => {
|
|
651
|
+
if (code === 0) {
|
|
652
|
+
// Try to extract URL from output
|
|
653
|
+
const urlMatch = stdout.match(/https?:\/\/[^\s]+/);
|
|
654
|
+
resolve({
|
|
655
|
+
success: true,
|
|
656
|
+
target: target.name,
|
|
657
|
+
url: urlMatch ? urlMatch[0] : null,
|
|
658
|
+
output: stdout.slice(-500)
|
|
659
|
+
});
|
|
660
|
+
} else {
|
|
661
|
+
reject(new Error(`Deployment failed with code ${code}`));
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Run verification
|
|
669
|
+
*/
|
|
670
|
+
async runVerification() {
|
|
671
|
+
const deployResult = this.state.phases.deploy.result;
|
|
672
|
+
|
|
673
|
+
if (!deployResult?.url) {
|
|
674
|
+
return {
|
|
675
|
+
skipped: true,
|
|
676
|
+
reason: 'No deployment URL available'
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Simple HTTP check
|
|
681
|
+
try {
|
|
682
|
+
const https = require('https');
|
|
683
|
+
const http = require('http');
|
|
684
|
+
const protocol = deployResult.url.startsWith('https') ? https : http;
|
|
685
|
+
|
|
686
|
+
return new Promise((resolve) => {
|
|
687
|
+
const req = protocol.get(deployResult.url, (res) => {
|
|
688
|
+
resolve({
|
|
689
|
+
passed: res.statusCode >= 200 && res.statusCode < 400,
|
|
690
|
+
statusCode: res.statusCode,
|
|
691
|
+
url: deployResult.url
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
req.on('error', () => {
|
|
696
|
+
resolve({
|
|
697
|
+
passed: false,
|
|
698
|
+
error: 'Could not reach deployment URL'
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
req.setTimeout(10000, () => {
|
|
703
|
+
req.destroy();
|
|
704
|
+
resolve({
|
|
705
|
+
passed: false,
|
|
706
|
+
error: 'Request timed out'
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
} catch {
|
|
711
|
+
return {
|
|
712
|
+
skipped: true,
|
|
713
|
+
reason: 'Verification not available'
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Get target info
|
|
720
|
+
*/
|
|
721
|
+
getTargetInfo(targetId) {
|
|
722
|
+
return DEPLOY_TARGETS[targetId] || null;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Get all targets
|
|
727
|
+
*/
|
|
728
|
+
getAllTargets() {
|
|
729
|
+
return Object.entries(DEPLOY_TARGETS).map(([id, target]) => ({
|
|
730
|
+
id,
|
|
731
|
+
...target
|
|
732
|
+
}));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Generate deployment config for target
|
|
737
|
+
*/
|
|
738
|
+
generateTargetConfig(targetId) {
|
|
739
|
+
const target = DEPLOY_TARGETS[targetId];
|
|
740
|
+
if (!target) return null;
|
|
741
|
+
|
|
742
|
+
switch (targetId) {
|
|
743
|
+
case 'vercel':
|
|
744
|
+
return {
|
|
745
|
+
file: 'vercel.json',
|
|
746
|
+
content: JSON.stringify({
|
|
747
|
+
'$schema': 'https://openapi.vercel.sh/vercel.json',
|
|
748
|
+
'buildCommand': 'npm run build',
|
|
749
|
+
'devCommand': 'npm run dev',
|
|
750
|
+
'installCommand': 'npm install'
|
|
751
|
+
}, null, 2)
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
case 'railway':
|
|
755
|
+
return {
|
|
756
|
+
file: 'railway.toml',
|
|
757
|
+
content: `[build]
|
|
758
|
+
builder = "nixpacks"
|
|
759
|
+
|
|
760
|
+
[deploy]
|
|
761
|
+
startCommand = "npm start"
|
|
762
|
+
restartPolicyType = "on_failure"
|
|
763
|
+
restartPolicyMaxRetries = 3
|
|
764
|
+
`
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
case 'fly':
|
|
768
|
+
return {
|
|
769
|
+
file: 'fly.toml',
|
|
770
|
+
content: `app = "my-app"
|
|
771
|
+
primary_region = "iad"
|
|
772
|
+
|
|
773
|
+
[build]
|
|
774
|
+
builder = "heroku/buildpacks:20"
|
|
775
|
+
|
|
776
|
+
[http_service]
|
|
777
|
+
internal_port = 3000
|
|
778
|
+
force_https = true
|
|
779
|
+
auto_stop_machines = true
|
|
780
|
+
auto_start_machines = true
|
|
781
|
+
min_machines_running = 0
|
|
782
|
+
`
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
case 'netlify':
|
|
786
|
+
return {
|
|
787
|
+
file: 'netlify.toml',
|
|
788
|
+
content: `[build]
|
|
789
|
+
command = "npm run build"
|
|
790
|
+
publish = ".next"
|
|
791
|
+
|
|
792
|
+
[[plugins]]
|
|
793
|
+
package = "@netlify/plugin-nextjs"
|
|
794
|
+
`
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
case 'docker':
|
|
798
|
+
return {
|
|
799
|
+
file: 'Dockerfile',
|
|
800
|
+
content: `FROM node:20-alpine AS base
|
|
801
|
+
|
|
802
|
+
FROM base AS deps
|
|
803
|
+
WORKDIR /app
|
|
804
|
+
COPY package*.json ./
|
|
805
|
+
RUN npm ci
|
|
806
|
+
|
|
807
|
+
FROM base AS builder
|
|
808
|
+
WORKDIR /app
|
|
809
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
810
|
+
COPY . .
|
|
811
|
+
RUN npm run build
|
|
812
|
+
|
|
813
|
+
FROM base AS runner
|
|
814
|
+
WORKDIR /app
|
|
815
|
+
ENV NODE_ENV production
|
|
816
|
+
COPY --from=builder /app/public ./public
|
|
817
|
+
COPY --from=builder /app/.next/standalone ./
|
|
818
|
+
COPY --from=builder /app/.next/static ./.next/static
|
|
819
|
+
|
|
820
|
+
EXPOSE 3000
|
|
821
|
+
ENV PORT 3000
|
|
822
|
+
CMD ["node", "server.js"]
|
|
823
|
+
`
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
default:
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
module.exports = {
|
|
833
|
+
DeployWorkflowEngine,
|
|
834
|
+
DEPLOY_TARGETS,
|
|
835
|
+
DEPLOY_PHASES
|
|
836
|
+
};
|