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.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/cli.js +306 -0
- package/branding.json +8 -0
- package/package.json +72 -0
- package/src/__tests__/cli-smoke.test.js +46 -0
- package/src/blueprint/__tests__/blueprint.test.js +116 -0
- package/src/blueprint/blueprint.js +181 -0
- package/src/blueprint/default.blueprint.json +78 -0
- package/src/blueprint/index.js +10 -0
- package/src/blueprint/loader.js +101 -0
- package/src/blueprint/schema-kit.js +161 -0
- package/src/blueprint/schema.js +78 -0
- package/src/branding/__tests__/branding.test.js +49 -0
- package/src/branding/index.js +48 -0
- package/src/commands/__tests__/commands.test.js +83 -0
- package/src/commands/check.js +71 -0
- package/src/commands/cleanup.js +347 -0
- package/src/commands/customize.js +263 -0
- package/src/commands/doctor.js +84 -0
- package/src/commands/env.js +75 -0
- package/src/commands/finalize.js +68 -0
- package/src/commands/generate/ci-cd.js +378 -0
- package/src/commands/generate/deploy-advanced.js +253 -0
- package/src/commands/generate/deploy.js +99 -0
- package/src/commands/generate/env.template.js +221 -0
- package/src/commands/generate/index.js +7 -0
- package/src/commands/generate/module.js +836 -0
- package/src/commands/generate/page.js +1415 -0
- package/src/commands/generate/test-scaffold.js +279 -0
- package/src/commands/generate/theme.js +67 -0
- package/src/commands/generate-resource.js +133 -0
- package/src/commands/index.js +9 -0
- package/src/commands/init.js +350 -0
- package/src/commands/make/resource.js +298 -0
- package/src/commands/preset.js +57 -0
- package/src/commands/remove.js +170 -0
- package/src/commands/rename.js +54 -0
- package/src/commands/rollback.js +90 -0
- package/src/commands/wizard.js +303 -0
- package/src/core/__tests__/generator.test.js +67 -0
- package/src/core/__tests__/marker-strategy.test.js +57 -0
- package/src/core/__tests__/resource-definition.test.js +32 -0
- package/src/core/generator.js +542 -0
- package/src/core/marker-strategy.js +138 -0
- package/src/core/resource-definition.js +346 -0
- package/src/core/state-tracker.js +67 -0
- package/src/core/template-loader.js +163 -0
- package/src/engine/__tests__/engine.test.js +306 -0
- package/src/engine/index.js +21 -0
- package/src/engine/injector.js +198 -0
- package/src/engine/pipeline.js +138 -0
- package/src/engine/transaction.js +105 -0
- package/src/engine/validator.js +190 -0
- package/src/index.js +4 -0
- package/src/recipes/__tests__/recipe.test.js +128 -0
- package/src/recipes/builtin/module.json +22 -0
- package/src/recipes/builtin/page.json +21 -0
- package/src/recipes/builtin/resource.json +35 -0
- package/src/recipes/condition.js +147 -0
- package/src/recipes/index.js +11 -0
- package/src/recipes/loader.js +95 -0
- package/src/recipes/recipe.js +89 -0
- package/src/recipes/schema.js +47 -0
- package/src/schemas/__tests__/schemas.test.js +67 -0
- package/src/schemas/index.js +18 -0
- package/src/schemas/options.js +38 -0
- package/src/schemas/resource.js +112 -0
- package/src/services/__tests__/reporter.test.js +98 -0
- package/src/services/clock.js +31 -0
- package/src/services/index.js +43 -0
- package/src/services/reporter.js +136 -0
- package/src/templates/resource/api.js.ejs +39 -0
- package/src/templates/resource/components/form.jsx.ejs +81 -0
- package/src/templates/resource/components/table.jsx.ejs +68 -0
- package/src/templates/resource/controller.js.ejs +154 -0
- package/src/templates/resource/hooks.js.ejs +46 -0
- package/src/templates/resource/model.js.ejs +64 -0
- package/src/templates/resource/page-detail.jsx.ejs +55 -0
- package/src/templates/resource/page-form.jsx.ejs +30 -0
- package/src/templates/resource/page-inline.jsx.ejs +74 -0
- package/src/templates/resource/page-modal.jsx.ejs +98 -0
- package/src/templates/resource/page-page.jsx.ejs +99 -0
- package/src/templates/resource/page-sidepanel.jsx.ejs +100 -0
- package/src/templates/resource/routes.js.ejs +35 -0
- package/src/templates/resource/service.js.ejs +132 -0
- package/src/templates/resource/test.ejs +71 -0
- package/src/templates/resource/types.ts.ejs +17 -0
- package/src/templates/resource/validator.js.ejs +26 -0
- package/src/templates/snippets/lazy-import.ejs +1 -0
- package/src/templates/snippets/nav-entry.ejs +1 -0
- package/src/templates/snippets/route-entry.ejs +5 -0
- package/src/templates/snippets/route-mount.ejs +1 -0
- package/src/utils/fieldValidators.js +371 -0
- package/src/utils/logging/logger.js +47 -0
- package/src/utils/namingUtils.js +38 -0
- 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
|
+
}
|