stackloom-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/bin/cli.js +306 -0
  4. package/branding.json +8 -0
  5. package/package.json +72 -0
  6. package/src/__tests__/cli-smoke.test.js +46 -0
  7. package/src/blueprint/__tests__/blueprint.test.js +116 -0
  8. package/src/blueprint/blueprint.js +181 -0
  9. package/src/blueprint/default.blueprint.json +78 -0
  10. package/src/blueprint/index.js +10 -0
  11. package/src/blueprint/loader.js +101 -0
  12. package/src/blueprint/schema-kit.js +161 -0
  13. package/src/blueprint/schema.js +78 -0
  14. package/src/branding/__tests__/branding.test.js +49 -0
  15. package/src/branding/index.js +48 -0
  16. package/src/commands/__tests__/commands.test.js +83 -0
  17. package/src/commands/check.js +71 -0
  18. package/src/commands/cleanup.js +347 -0
  19. package/src/commands/customize.js +263 -0
  20. package/src/commands/doctor.js +84 -0
  21. package/src/commands/env.js +75 -0
  22. package/src/commands/finalize.js +68 -0
  23. package/src/commands/generate/ci-cd.js +378 -0
  24. package/src/commands/generate/deploy-advanced.js +253 -0
  25. package/src/commands/generate/deploy.js +99 -0
  26. package/src/commands/generate/env.template.js +221 -0
  27. package/src/commands/generate/index.js +7 -0
  28. package/src/commands/generate/module.js +836 -0
  29. package/src/commands/generate/page.js +1415 -0
  30. package/src/commands/generate/test-scaffold.js +279 -0
  31. package/src/commands/generate/theme.js +67 -0
  32. package/src/commands/generate-resource.js +133 -0
  33. package/src/commands/index.js +9 -0
  34. package/src/commands/init.js +350 -0
  35. package/src/commands/make/resource.js +298 -0
  36. package/src/commands/preset.js +57 -0
  37. package/src/commands/remove.js +170 -0
  38. package/src/commands/rename.js +54 -0
  39. package/src/commands/rollback.js +90 -0
  40. package/src/commands/wizard.js +303 -0
  41. package/src/core/__tests__/generator.test.js +67 -0
  42. package/src/core/__tests__/marker-strategy.test.js +57 -0
  43. package/src/core/__tests__/resource-definition.test.js +32 -0
  44. package/src/core/generator.js +542 -0
  45. package/src/core/marker-strategy.js +138 -0
  46. package/src/core/resource-definition.js +346 -0
  47. package/src/core/state-tracker.js +67 -0
  48. package/src/core/template-loader.js +163 -0
  49. package/src/engine/__tests__/engine.test.js +306 -0
  50. package/src/engine/index.js +21 -0
  51. package/src/engine/injector.js +198 -0
  52. package/src/engine/pipeline.js +138 -0
  53. package/src/engine/transaction.js +105 -0
  54. package/src/engine/validator.js +190 -0
  55. package/src/index.js +4 -0
  56. package/src/recipes/__tests__/recipe.test.js +128 -0
  57. package/src/recipes/builtin/module.json +22 -0
  58. package/src/recipes/builtin/page.json +21 -0
  59. package/src/recipes/builtin/resource.json +35 -0
  60. package/src/recipes/condition.js +147 -0
  61. package/src/recipes/index.js +11 -0
  62. package/src/recipes/loader.js +95 -0
  63. package/src/recipes/recipe.js +89 -0
  64. package/src/recipes/schema.js +47 -0
  65. package/src/schemas/__tests__/schemas.test.js +67 -0
  66. package/src/schemas/index.js +18 -0
  67. package/src/schemas/options.js +38 -0
  68. package/src/schemas/resource.js +112 -0
  69. package/src/services/__tests__/reporter.test.js +98 -0
  70. package/src/services/clock.js +31 -0
  71. package/src/services/index.js +43 -0
  72. package/src/services/reporter.js +136 -0
  73. package/src/templates/resource/api.js.ejs +39 -0
  74. package/src/templates/resource/components/form.jsx.ejs +81 -0
  75. package/src/templates/resource/components/table.jsx.ejs +68 -0
  76. package/src/templates/resource/controller.js.ejs +154 -0
  77. package/src/templates/resource/hooks.js.ejs +46 -0
  78. package/src/templates/resource/model.js.ejs +64 -0
  79. package/src/templates/resource/page-detail.jsx.ejs +55 -0
  80. package/src/templates/resource/page-form.jsx.ejs +30 -0
  81. package/src/templates/resource/page-inline.jsx.ejs +74 -0
  82. package/src/templates/resource/page-modal.jsx.ejs +98 -0
  83. package/src/templates/resource/page-page.jsx.ejs +99 -0
  84. package/src/templates/resource/page-sidepanel.jsx.ejs +100 -0
  85. package/src/templates/resource/routes.js.ejs +35 -0
  86. package/src/templates/resource/service.js.ejs +132 -0
  87. package/src/templates/resource/test.ejs +71 -0
  88. package/src/templates/resource/types.ts.ejs +17 -0
  89. package/src/templates/resource/validator.js.ejs +26 -0
  90. package/src/templates/snippets/lazy-import.ejs +1 -0
  91. package/src/templates/snippets/nav-entry.ejs +1 -0
  92. package/src/templates/snippets/route-entry.ejs +5 -0
  93. package/src/templates/snippets/route-mount.ejs +1 -0
  94. package/src/utils/fieldValidators.js +371 -0
  95. package/src/utils/logging/logger.js +47 -0
  96. package/src/utils/namingUtils.js +38 -0
  97. package/src/utils/sanitize.js +200 -0
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CI/CD Pipeline Generators
5
+ */
6
+
7
+ export function generateGitHubActions() {
8
+ return `name: CI/CD Pipeline
9
+
10
+ on:
11
+ push:
12
+ branches: [ main, develop ]
13
+ pull_request:
14
+ branches: [ main ]
15
+
16
+ env:
17
+ NODE_VERSION: '20'
18
+ MONGO_VERSION: '6.0'
19
+
20
+ jobs:
21
+ test:
22
+ runs-on: ubuntu-latest
23
+
24
+ services:
25
+ mongodb:
26
+ image: mongo:${MONGO_VERSION}
27
+ ports:
28
+ - 27017:27017
29
+ options: >-
30
+ --health-cmd "echo 'db.runCommand(\"ping\").ok' | mongosh localhost:27017/test --quiet"
31
+ --health-interval 10s
32
+ --health-timeout 5s
33
+ --health-retries 5
34
+
35
+ strategy:
36
+ matrix:
37
+ node-version: [${'$'}{{ env.NODE_VERSION }}]
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - name: Use Node.js ${'$'}{{ matrix.node-version }}
43
+ uses: actions/setup-node@v4
44
+ with:
45
+ node-version: ${'$'}{{ matrix.node-version }}
46
+ cache: 'npm'
47
+
48
+ - name: Install dependencies
49
+ run: |
50
+ cd backend && npm ci
51
+ cd ../frontend && npm ci
52
+
53
+ - name: Run backend tests
54
+ run: cd backend && npm test
55
+ env:
56
+ MONGODB_URI: mongodb://localhost:27017/test
57
+ NODE_ENV: test
58
+
59
+ - name: Run frontend tests
60
+ run: cd frontend && npm test -- --watchAll=false
61
+ env:
62
+ CI: true
63
+
64
+ - name: Run lint
65
+ run: |
66
+ cd backend && npm run lint
67
+ cd frontend && npm run lint
68
+
69
+ - name: Build frontend
70
+ run: cd frontend && npm run build
71
+
72
+ security:
73
+ runs-on: ubuntu-latest
74
+ needs: test
75
+
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ - name: Run security audit
80
+ run: npm audit --audit-level=moderate
81
+
82
+ - name: Run SAST
83
+ uses: shiftleft/scan-action@v2
84
+ with:
85
+ output: reports/
86
+
87
+ - name: Upload security reports
88
+ uses: actions/upload-artifact@v4
89
+ with:
90
+ name: security-reports
91
+ path: reports/
92
+
93
+ build:
94
+ runs-on: ubuntu-latest
95
+ needs: [test, security]
96
+ if: github.ref == 'refs/heads/main'
97
+
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+
101
+ - name: Set up Docker Buildx
102
+ uses: docker/setup-buildx-action@v3
103
+
104
+ - name: Login to Docker Hub
105
+ uses: docker/login-action@v3
106
+ with:
107
+ username: ${'$'}{{ secrets.DOCKERHUB_USERNAME }}
108
+ password: ${'$'}{{ secrets.DOCKERHUB_TOKEN }}
109
+
110
+ - name: Build and push backend
111
+ uses: docker/build-push-action@v5
112
+ with:
113
+ context: ./backend
114
+ push: true
115
+ tags: ${'$'}{{ secrets.DOCKERHUB_USERNAME }}/app-backend:latest
116
+ cache-from: type=gha
117
+ cache-to: type=gha,mode=max
118
+
119
+ - name: Build and push frontend
120
+ uses: docker/build-push-action@v5
121
+ with:
122
+ context: ./frontend
123
+ push: true
124
+ tags: ${'$'}{{ secrets.DOCKERHUB_USERNAME }}/app-frontend:latest
125
+ cache-from: type=gha
126
+ cache-to: type=gha,mode=max
127
+
128
+ deploy:
129
+ runs-on: ubuntu-latest
130
+ needs: build
131
+ if: github.ref == 'refs/heads/main'
132
+
133
+ steps:
134
+ - uses: actions/checkout@v4
135
+
136
+ - name: Deploy to production
137
+ uses: appleboy/ssh-action@v1
138
+ with:
139
+ host: ${'$'}{{ secrets.SSH_HOST }}
140
+ username: ${'$'}{{ secrets.SSH_USER }}
141
+ key: ${'$'}{{ secrets.SSH_KEY }}
142
+ script: |
143
+ cd /opt/app
144
+ docker-compose pull
145
+ docker-compose up -d
146
+ docker system prune -f
147
+ `;
148
+ }
149
+
150
+ export function generateGitLabCI() {
151
+ return `stages:
152
+ - test
153
+ - security
154
+ - build
155
+ - deploy
156
+
157
+ variables:
158
+ NODE_VERSION: "20"
159
+ MONGO_VERSION: "6.0"
160
+ DOCKER_DRIVER: overlay2
161
+
162
+ services:
163
+ - mongo:${MONGO_VERSION}
164
+
165
+ test:backend:
166
+ stage: test
167
+ image: node:${NODE_VERSION}
168
+ before_script:
169
+ - npm ci
170
+ - npm run test:ci
171
+ script:
172
+ - npm run test
173
+ artifacts:
174
+ reports:
175
+ junit: junit.xml
176
+ when: always
177
+
178
+ test:frontend:
179
+ stage: test
180
+ image: node:${NODE_VERSION}
181
+ before_script:
182
+ - cd frontend
183
+ - npm ci
184
+ script:
185
+ - npm run test:ci
186
+ artifacts:
187
+ reports:
188
+ junit: junit.xml
189
+ when: always
190
+
191
+ lint:
192
+ stage: test
193
+ image: node:${NODE_VERSION}
194
+ script:
195
+ - npm run lint
196
+ allow_failure: true
197
+
198
+ sast:
199
+ stage: security
200
+ image: shiftleft/scan
201
+ script:
202
+ - scan --type --src . --out reports/
203
+ artifacts:
204
+ paths:
205
+ - reports/
206
+
207
+ dependency_scan:
208
+ stage: security
209
+ image: node:${NODE_VERSION}
210
+ script:
211
+ - npm audit --audit-level=moderate
212
+ allow_failure: true
213
+
214
+ build:frontend:
215
+ stage: build
216
+ image: node:${NODE_VERSION}
217
+ script:
218
+ - cd frontend
219
+ - npm ci
220
+ - npm run build
221
+ artifacts:
222
+ paths:
223
+ - frontend/dist/
224
+ expire_in: 1 week
225
+
226
+ docker:build:
227
+ stage: build
228
+ image: docker:latest
229
+ services:
230
+ - docker:dind
231
+ script:
232
+ - docker build -t \$CI_REGISTRY_IMAGE/backend:\$CI_COMMIT_SHA ./backend
233
+ - docker build -t \$CI_REGISTRY_IMAGE/frontend:\$CI_COMMIT_SHA ./frontend
234
+ - docker push \$CI_REGISTRY_IMAGE/backend:\$CI_COMMIT_SHA
235
+ - docker push \$CI_REGISTRY_IMAGE/frontend:\$CI_COMMIT_SHA
236
+ only:
237
+ - main
238
+
239
+ deploy:production:
240
+ stage: deploy
241
+ image: alpine:latest
242
+ before_script:
243
+ - apk add --no-cache openssh-client
244
+ - eval \$(ssh-agent -s)
245
+ - echo "\$SSH_PRIVATE_KEY" | tr -d '\\r' | ssh-add -
246
+ - mkdir -p ~/.ssh
247
+ - chmod 700 ~/.ssh
248
+ script:
249
+ - ssh -o StrictHostKeyChecking=no \$SSH_USER@\$SSH_HOST "cd /opt/app && docker-compose pull && docker-compose up -d"
250
+ only:
251
+ - main
252
+ `;
253
+ }
254
+
255
+ export function generateJenkinsfile() {
256
+ return `pipeline {
257
+ agent any
258
+
259
+ environment {
260
+ NODE_VERSION = '20'
261
+ MONGO_VERSION = '6.0'
262
+ DOCKER_REGISTRY = credentials('docker-registry')
263
+ }
264
+
265
+ stages {
266
+ stage('Checkout') {
267
+ steps {
268
+ checkout scm
269
+ }
270
+ }
271
+
272
+ stage('Setup') {
273
+ steps {
274
+ sh '''
275
+ nvm install \${NODE_VERSION}
276
+ nvm use \${NODE_VERSION}
277
+ '''
278
+ }
279
+ }
280
+
281
+ stage('Install Dependencies') {
282
+ steps {
283
+ sh '''
284
+ cd backend && npm ci
285
+ cd ../frontend && npm ci
286
+ '''
287
+ }
288
+ }
289
+
290
+ stage('Test') {
291
+ parallel {
292
+ stage('Backend Tests') {
293
+ steps {
294
+ sh '''
295
+ docker run -d --name mongo -p 27017:27017 mongo:\${MONGO_VERSION}
296
+ sleep 5
297
+ cd backend && MONGODB_URI=mongodb://localhost:27017/test npm test
298
+ docker stop mongo && docker rm mongo
299
+ '''
300
+ }
301
+ post {
302
+ always {
303
+ junit 'backend/test-results/*.xml'
304
+ }
305
+ }
306
+ }
307
+
308
+ stage('Frontend Tests') {
309
+ steps {
310
+ sh '''
311
+ cd frontend && CI=true npm test -- --watchAll=false
312
+ '''
313
+ }
314
+ post {
315
+ always {
316
+ junit 'frontend/test-results/*.xml'
317
+ }
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ stage('Security Scan') {
324
+ steps {
325
+ sh 'npm audit --audit-level=moderate'
326
+ }
327
+ }
328
+
329
+ stage('Build') {
330
+ steps {
331
+ sh '''
332
+ cd frontend && npm run build
333
+ '''
334
+ archiveArtifacts artifacts: 'frontend/build/**/*', fingerprint: true
335
+ }
336
+ }
337
+
338
+ stage('Docker Build') {
339
+ when {
340
+ branch 'main'
341
+ }
342
+ steps {
343
+ sh '''
344
+ docker build -t \${DOCKER_REGISTRY_USR}/app:latest .
345
+ docker push \${DOCKER_REGISTRY_USR}/app:latest
346
+ '''
347
+ }
348
+ }
349
+
350
+ stage('Deploy') {
351
+ when {
352
+ branch 'main'
353
+ }
354
+ steps {
355
+ sshagent(['production-server']) {
356
+ sh '''
357
+ ssh -o StrictHostKeyChecking=no user@production-server \
358
+ "cd /opt/app && docker-compose pull && docker-compose up -d"
359
+ '''
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ post {
366
+ always {
367
+ cleanWs()
368
+ }
369
+ success {
370
+ slackSend color: 'good', message: 'Pipeline succeeded!'
371
+ }
372
+ failure {
373
+ slackSend color: 'danger', message: 'Pipeline failed!'
374
+ }
375
+ }
376
+ }
377
+ `;
378
+ }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+
6
+ export async function generateDockerCompose(env = 'development') {
7
+ const configs = {
8
+ development: `version: '3.8'
9
+
10
+ services:
11
+ app:
12
+ build: .
13
+ ports:
14
+ - "3000:3000"
15
+ environment:
16
+ - NODE_ENV=development
17
+ - MONGODB_URI=mongodb://mongo:27017/app
18
+ volumes:
19
+ - .:/app
20
+ - /app/node_modules
21
+ depends_on:
22
+ - mongo
23
+ - redis
24
+ networks:
25
+ - app-network
26
+
27
+ mongo:
28
+ image: mongo:6.0
29
+ ports:
30
+ - "27017:27017"
31
+ volumes:
32
+ - mongo-data:/data/db
33
+ environment:
34
+ - MONGO_INITDB_ROOT_USERNAME=admin
35
+ - MONGO_INITDB_ROOT_PASSWORD=password
36
+ networks:
37
+ - app-network
38
+
39
+ redis:
40
+ image: redis:7-alpine
41
+ ports:
42
+ - "6379:6379"
43
+ volumes:
44
+ - redis-data:/data
45
+ networks:
46
+ - app-network
47
+
48
+ nginx:
49
+ image: nginx:alpine
50
+ ports:
51
+ - "80:80"
52
+ volumes:
53
+ - ./nginx.conf:/etc/nginx/nginx.conf
54
+ depends_on:
55
+ - app
56
+ networks:
57
+ - app-network
58
+
59
+ volumes:
60
+ mongo-data:
61
+ redis-data:
62
+
63
+ networks:
64
+ app-network:
65
+ driver: bridge
66
+ `,
67
+ production: `version: '3.8'
68
+
69
+ services:
70
+ app:
71
+ build:
72
+ context: .
73
+ target: production
74
+ ports:
75
+ - "3000:3000"
76
+ environment:
77
+ - NODE_ENV=production
78
+ - MONGODB_URI=mongodb://mongo:27017/app
79
+ depends_on:
80
+ - mongo
81
+ networks:
82
+ - app-network
83
+ deploy:
84
+ replicas: 3
85
+ restart_policy:
86
+ condition: on-failure
87
+ max_attempts: 3
88
+
89
+ mongo:
90
+ image: mongo:6.0
91
+ volumes:
92
+ - mongo-data:/data/db
93
+ environment:
94
+ - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
95
+ - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
96
+ networks:
97
+ - app-network
98
+
99
+ volumes:
100
+ mongo-data:
101
+
102
+ networks:
103
+ app-network:
104
+ driver: overlay
105
+ `
106
+ };
107
+
108
+ return configs[env] || configs.development;
109
+ }
110
+
111
+ export async function generateKubernetes(namespace = 'default') {
112
+ return `apiVersion: v1
113
+ kind: Namespace
114
+ metadata:
115
+ name: ${namespace}
116
+ ---
117
+ apiVersion: apps/v1
118
+ kind: Deployment
119
+ metadata:
120
+ name: app-deployment
121
+ namespace: ${namespace}
122
+ spec:
123
+ replicas: 3
124
+ selector:
125
+ matchLabels:
126
+ app: node-app
127
+ template:
128
+ metadata:
129
+ labels:
130
+ app: node-app
131
+ spec:
132
+ containers:
133
+ - name: app
134
+ image: your-registry/node-app:latest
135
+ ports:
136
+ - containerPort: 3000
137
+ env:
138
+ - name: NODE_ENV
139
+ value: "production"
140
+ - name: MONGODB_URI
141
+ valueFrom:
142
+ secretKeyRef:
143
+ name: app-secrets
144
+ key: mongodb-uri
145
+ resources:
146
+ requests:
147
+ memory: "256Mi"
148
+ cpu: "250m"
149
+ limits:
150
+ memory: "512Mi"
151
+ cpu: "500m"
152
+ livenessProbe:
153
+ httpGet:
154
+ path: /health
155
+ port: 3000
156
+ initialDelaySeconds: 30
157
+ periodSeconds: 10
158
+ readinessProbe:
159
+ httpGet:
160
+ path: /ready
161
+ port: 3000
162
+ initialDelaySeconds: 5
163
+ periodSeconds: 5
164
+ ---
165
+ apiVersion: v1
166
+ kind: Service
167
+ metadata:
168
+ name: app-service
169
+ namespace: ${namespace}
170
+ spec:
171
+ selector:
172
+ app: node-app
173
+ ports:
174
+ - port: 80
175
+ targetPort: 3000
176
+ type: LoadBalancer
177
+ ---
178
+ apiVersion: v1
179
+ kind: Secret
180
+ metadata:
181
+ name: app-secrets
182
+ namespace: ${namespace}
183
+ type: Opaque
184
+ data:
185
+ # echo -n 'your-mongodb-uri' | base64
186
+ mongodb-uri: <base64-encoded-uri>
187
+ `;
188
+ }
189
+
190
+ export async function generateNginxConfig(domain = 'example.com', ssl = false) {
191
+ if (ssl) {
192
+ return `server {
193
+ listen 80;
194
+ server_name ${domain};
195
+ return 301 https://$server_name$request_uri;
196
+ }
197
+
198
+ server {
199
+ listen 443 ssl http2;
200
+ server_name ${domain};
201
+
202
+ ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
203
+ ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
204
+ ssl_protocols TLSv1.2 TLSv1.3;
205
+ ssl_ciphers HIGH:!aNULL:!MD5;
206
+
207
+ location / {
208
+ proxy_pass http://app:3000;
209
+ proxy_http_version 1.1;
210
+ proxy_set_header Upgrade $http_upgrade;
211
+ proxy_set_header Connection 'upgrade';
212
+ proxy_set_header Host $host;
213
+ proxy_set_header X-Real-IP $remote_addr;
214
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
215
+ proxy_set_header X-Forwarded-Proto $scheme;
216
+ proxy_cache_bypass $http_upgrade;
217
+ }
218
+
219
+ location /api/ {
220
+ proxy_pass http://app:3000/api/;
221
+ proxy_http_version 1.1;
222
+ proxy_set_header Host $host;
223
+ proxy_set_header X-Real-IP $remote_addr;
224
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
225
+ }
226
+ }`;
227
+ }
228
+
229
+ return `server {
230
+ listen 80;
231
+ server_name ${domain};
232
+
233
+ location / {
234
+ proxy_pass http://localhost:3000;
235
+ proxy_http_version 1.1;
236
+ proxy_set_header Upgrade $http_upgrade;
237
+ proxy_set_header Connection 'upgrade';
238
+ proxy_set_header Host $host;
239
+ proxy_set_header X-Real-IP $remote_addr;
240
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
241
+ proxy_set_header X-Forwarded-Proto $scheme;
242
+ proxy_cache_bypass $http_upgrade;
243
+ }
244
+
245
+ location /api/ {
246
+ proxy_pass http://localhost:3000/api/;
247
+ proxy_http_version 1.1;
248
+ proxy_set_header Host $host;
249
+ proxy_set_header X-Real-IP $remote_addr;
250
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
251
+ }
252
+ }`;
253
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import fs from 'fs-extra';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+
8
+ export default async function generateDeployCmd(options) {
9
+ const spinner = ora();
10
+ const projectRoot = process.cwd();
11
+
12
+ // Targets might be "all"
13
+ const targets = options.target === 'all'
14
+ ? ['docker', 'vercel', 'railway']
15
+ : [options.target];
16
+
17
+ for (const target of targets) {
18
+ switch (target) {
19
+ case 'docker': {
20
+ spinner.start('Generating Dockerfile...');
21
+ const dockerfile = `FROM node:20-alpine AS builder
22
+ WORKDIR /app
23
+ COPY package*.json ./
24
+ RUN pnpm install --frozen-lockfile
25
+ COPY . .
26
+ RUN pnpm build
27
+
28
+ FROM node:20-alpine
29
+ WORKDIR /app
30
+ COPY --from=builder /app/dist ./dist
31
+ COPY package*.json ./
32
+ RUN pnpm install --prod --frozen-lockfile
33
+ EXPOSE 5000
34
+ CMD ["node", "backend/server.js"]
35
+ `;
36
+ await fs.writeFile(path.join(projectRoot, 'Dockerfile'), dockerfile);
37
+ spinner.succeed('Dockerfile created');
38
+
39
+ spinner.start('Generating docker-compose.yml...');
40
+ const dc = `version: '3.8'
41
+ services:
42
+ api:
43
+ build: .
44
+ ports:
45
+ - "5000:5000"
46
+ env_file:
47
+ - .env
48
+ `;
49
+ await fs.writeFile(path.join(projectRoot, 'docker-compose.yml'), dc);
50
+ spinner.succeed('docker-compose.yml created');
51
+ break;
52
+ }
53
+ case 'vercel': {
54
+ spinner.start('Generating vercel.json...');
55
+ const vercelJson = {
56
+ version: 2,
57
+ builds: [
58
+ { src: 'frontend/package.json', use: '@vercel/static-build', config: { distDir: 'dist' } },
59
+ { src: 'backend/package.json', use: '@vercel/node' },
60
+ ],
61
+ routes: [
62
+ { src: '/api/(.*)', dest: '/backend/server.js' },
63
+ { handle: 'filesystem' },
64
+ { src: '/(.*)', dest: '/frontend/index.html' },
65
+ ],
66
+ };
67
+ await fs.writeFile(path.join(projectRoot, 'vercel.json'), JSON.stringify(vercelJson, null, 2));
68
+ spinner.succeed('vercel.json created');
69
+ break;
70
+ }
71
+ case 'railway': {
72
+ spinner.start('Generating railway.yaml...');
73
+ const railwayYaml = `{
74
+ "build": {
75
+ "builder": "NIXPACKS",
76
+ "env": {
77
+ "NODE_VERSION": "20"
78
+ }
79
+ },
80
+ "deploy": {
81
+ "startCommand": "pnpm start",
82
+ "restartPolicy": {
83
+ "policy": "ON_FAILURE",
84
+ "delayMs": 5000
85
+ }
86
+ }
87
+ }
88
+ `;
89
+ await fs.writeFile(path.join(projectRoot, 'railway.yaml'), railwayYaml);
90
+ spinner.succeed('railway.yaml created');
91
+ break;
92
+ }
93
+ default:
94
+ console.log(chalk.yellow(`⚠ Unknown target: ${target}`));
95
+ }
96
+ }
97
+
98
+ console.log(chalk.green('✓ Deployment configuration complete.'));
99
+ }