webspresso 0.0.105 ā 0.0.106
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/.agents/AGENTS.md +15 -2
- package/.agents/CLI.md +219 -7
- package/.agents/FLAGS.md +118 -0
- package/.agents/LOGGING.md +93 -0
- package/.agents/METRICS.md +127 -0
- package/.agents/MIGRATION_JOBS.md +95 -0
- package/.agents/PLUGINS.md +10 -3
- package/.agents/QUEUE.md +42 -0
- package/.agents/RATE_LIMIT.md +147 -0
- package/.agents/SCALE_OUT.md +109 -0
- package/.agents/SECRETS.md +117 -0
- package/AGENTS.md +10 -0
- package/bin/commands/add-aws.js +396 -0
- package/bin/commands/add-ci.js +523 -0
- package/bin/commands/add-cloudflare.js +240 -0
- package/bin/commands/add-cloudrun.js +381 -0
- package/bin/commands/add-migration-job.js +396 -0
- package/bin/commands/assets-sync.js +209 -0
- package/bin/commands/client-generate.js +52 -0
- package/bin/commands/console.js +53 -0
- package/bin/commands/db-migrate.js +47 -2
- package/bin/commands/db-studio.js +84 -0
- package/bin/commands/deploy-check.js +242 -13
- package/bin/commands/export.js +87 -0
- package/bin/commands/secrets-pull.js +208 -0
- package/bin/utils/db.js +7 -1
- package/bin/webspresso.js +22 -0
- package/core/app.js +421 -0
- package/core/auth/authenticate.js +93 -0
- package/core/auth/index.js +34 -4
- package/core/auth/manager.js +50 -5
- package/core/auth/middleware.js +16 -68
- package/core/auth/redis-session.js +1008 -0
- package/core/auth/sessions.js +156 -0
- package/core/cache/cdn.js +296 -0
- package/core/cache/index.js +12 -0
- package/core/cache/tagged-lru.js +9 -1
- package/core/console/commands.js +148 -0
- package/core/console/context.js +260 -0
- package/core/console/eval.js +70 -0
- package/core/console/index.js +22 -0
- package/core/console/repl.js +102 -0
- package/core/errors/domain.js +1 -0
- package/core/events/dispatcher.js +7 -0
- package/core/events/signal.js +7 -0
- package/core/export/discovery.js +191 -0
- package/core/export/exporter.js +277 -0
- package/core/export/index.js +24 -0
- package/core/flags/evaluator.js +252 -0
- package/core/flags/hasher.js +37 -0
- package/core/flags/index.js +21 -0
- package/core/flags/manager.js +263 -0
- package/core/flags/store.js +126 -0
- package/core/health/middleware.js +3 -3
- package/core/http/adapter.js +74 -0
- package/core/http/express-adapter.js +46 -0
- package/core/http/hono-adapter.js +531 -0
- package/core/http/index.js +41 -0
- package/core/http/pipeline.js +485 -0
- package/core/index.js +336 -0
- package/core/limits/middleware.js +9 -1
- package/core/logger/formatters.js +264 -0
- package/core/logger/index.js +260 -0
- package/core/logger/trace.js +137 -0
- package/core/metrics/collectors.js +125 -0
- package/core/metrics/index.js +45 -0
- package/core/metrics/middleware.js +230 -0
- package/core/metrics/registry.js +366 -0
- package/core/orm/d1.js +265 -0
- package/core/orm/index.js +105 -5
- package/core/orm/migrate-cloud.js +194 -0
- package/core/orm/pool.js +97 -0
- package/core/orm/query-builder.js +9 -0
- package/core/queue/adapters/cloudflare.js +131 -0
- package/core/queue/adapters/pubsub.js +400 -0
- package/core/queue/adapters/sqs.js +419 -0
- package/core/queue/adapters/wait-until.js +77 -0
- package/core/queue/index.js +8 -0
- package/core/queue/manager.js +11 -0
- package/core/rate-limit/index.js +22 -0
- package/core/rate-limit/limiter.js +219 -0
- package/core/rate-limit/stores/memory.js +129 -0
- package/core/rate-limit/stores/redis.js +151 -0
- package/core/rate-limit/utils.js +112 -0
- package/core/resilience/circuit-breaker.js +31 -0
- package/core/secrets/index.js +113 -0
- package/core/secrets/manager.js +308 -0
- package/core/secrets/providers/aws.js +288 -0
- package/core/secrets/providers/env.js +44 -0
- package/core/secrets/providers/gcp.js +307 -0
- package/core/secrets/providers/memory.js +76 -0
- package/core/shutdown/shutdown-manager.js +9 -0
- package/core/stream/index.js +19 -0
- package/core/stream/text-stream.js +326 -0
- package/core/studio/index.js +43 -0
- package/core/studio/inspector.js +258 -0
- package/core/studio/server.js +325 -0
- package/core/studio/ui.js +681 -0
- package/index.d.ts +934 -2
- package/index.js +125 -3
- package/package.json +3 -1
- package/plugins/admin-panel/index.js +12 -5
- package/plugins/api-key/index.js +3 -2
- package/plugins/audit-log/index.js +3 -1
- package/plugins/basic-auth/index.js +3 -2
- package/plugins/cors.js +3 -2
- package/plugins/csrf/index.js +4 -3
- package/plugins/csv/index.js +8 -5
- package/plugins/email/index.js +11 -9
- package/plugins/index.js +3 -0
- package/plugins/queue/index.js +18 -2
- package/plugins/redirect/index.js +7 -2
- package/plugins/seo-checker/index.js +2 -2
- package/plugins/site-analytics/index.js +3 -1
- package/plugins/swagger.js +8 -2
- package/plugins/upload/gcs-provider.js +148 -0
- package/plugins/upload/index.js +132 -74
- package/plugins/upload/s3-provider.js +257 -0
- package/plugins/webhooks/index.js +27 -23
- package/plugins/xlsx/index.js +7 -5
- package/src/api/api-loader.js +12 -95
- package/src/api/define-api.js +1 -0
- package/src/client-runtime/constants.js +10 -0
- package/src/client-runtime/mount.js +2 -3
- package/src/dev/client-gen.js +458 -0
- package/src/discovery/scan-routes.js +5 -2
- package/src/file-router.js +2 -0
- package/src/helpers.js +53 -0
- package/src/pages/page-loader.js +12 -205
- package/src/plugin-manager.js +9 -1
- package/src/server.js +231 -140
- package/src/services/executor.js +9 -0
- package/src/services/registry.js +9 -1
- package/src/services/validator.js +38 -20
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add CI/CD Command
|
|
3
|
+
* Scaffolds zero-secret GitHub Actions workflows (OIDC / WIF) & GitLab CI pipelines
|
|
4
|
+
* for Google Cloud Run, AWS App Runner / ECS, Cloudflare Workers, and automated test gates.
|
|
5
|
+
*
|
|
6
|
+
* @module bin/commands/add-ci
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate standard CI workflow (.github/workflows/ci.yml)
|
|
16
|
+
* Runs tests and deploy:check verification across Node versions
|
|
17
|
+
* @param {Object} options
|
|
18
|
+
* @param {string} [options.branch='main']
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function generateBaseCiWorkflow(options = {}) {
|
|
22
|
+
const branch = options.branch || 'main';
|
|
23
|
+
|
|
24
|
+
return `name: Webspresso CI & Preflight
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
push:
|
|
28
|
+
branches: [ ${branch} ]
|
|
29
|
+
pull_request:
|
|
30
|
+
branches: [ ${branch} ]
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
test-and-verify:
|
|
34
|
+
name: Test & Deployment Preflight
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
strategy:
|
|
37
|
+
matrix:
|
|
38
|
+
node-version: [20.x, 22.x]
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout code
|
|
42
|
+
uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- name: Setup Node.js \${{ matrix.node-version }}
|
|
45
|
+
uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version: \${{ matrix.node-version }}
|
|
48
|
+
cache: 'npm'
|
|
49
|
+
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: npm ci
|
|
52
|
+
|
|
53
|
+
- name: Run Unit & Integration Tests
|
|
54
|
+
run: npm test
|
|
55
|
+
|
|
56
|
+
- name: Run Webspresso Preflight Check
|
|
57
|
+
run: npx webspresso deploy:check --strict
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate Google Cloud Run Deployment Workflow with Workload Identity Federation (Keyless OIDC)
|
|
63
|
+
* @param {Object} options
|
|
64
|
+
* @param {string} options.appName
|
|
65
|
+
* @param {string} [options.branch='main']
|
|
66
|
+
* @param {string} [options.region='us-central1']
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
function generateCloudRunWorkflow(options = {}) {
|
|
70
|
+
const {
|
|
71
|
+
appName = 'webspresso-app',
|
|
72
|
+
branch = 'main',
|
|
73
|
+
region = 'us-central1',
|
|
74
|
+
} = options;
|
|
75
|
+
|
|
76
|
+
return `name: Deploy to Google Cloud Run
|
|
77
|
+
|
|
78
|
+
on:
|
|
79
|
+
push:
|
|
80
|
+
branches: [ ${branch} ]
|
|
81
|
+
|
|
82
|
+
# Permissions required for Google Workload Identity Federation (WIF) OIDC authentication
|
|
83
|
+
permissions:
|
|
84
|
+
contents: read
|
|
85
|
+
id-token: write
|
|
86
|
+
|
|
87
|
+
env:
|
|
88
|
+
SERVICE_NAME: ${appName}
|
|
89
|
+
REGION: ${region}
|
|
90
|
+
AR_LOCATION: ${region}
|
|
91
|
+
AR_REPOSITORY: webspresso-apps
|
|
92
|
+
|
|
93
|
+
jobs:
|
|
94
|
+
deploy:
|
|
95
|
+
name: Test, Build & Deploy to Cloud Run
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
|
|
98
|
+
steps:
|
|
99
|
+
- name: Checkout repository
|
|
100
|
+
uses: actions/checkout@v4
|
|
101
|
+
|
|
102
|
+
- name: Setup Node.js
|
|
103
|
+
uses: actions/setup-node@v4
|
|
104
|
+
with:
|
|
105
|
+
node-version: '22'
|
|
106
|
+
cache: 'npm'
|
|
107
|
+
|
|
108
|
+
- name: Install dependencies
|
|
109
|
+
run: npm ci
|
|
110
|
+
|
|
111
|
+
- name: Run Test Suite
|
|
112
|
+
run: npm test
|
|
113
|
+
|
|
114
|
+
- name: Cloud Run Preflight Audit
|
|
115
|
+
run: npx webspresso deploy:check --platform cloudrun --strict
|
|
116
|
+
|
|
117
|
+
# Keyless OIDC Authentication with Google Cloud
|
|
118
|
+
# Requires: WORKLOAD_IDENTITY_PROVIDER and SERVICE_ACCOUNT GitHub secrets
|
|
119
|
+
- name: Authenticate to Google Cloud (Workload Identity)
|
|
120
|
+
uses: google-github-actions/auth@v2
|
|
121
|
+
with:
|
|
122
|
+
workload_identity_provider: \${{ secrets.GCP_WIF_PROVIDER }}
|
|
123
|
+
service_account: \${{ secrets.GCP_WIF_SERVICE_ACCOUNT }}
|
|
124
|
+
|
|
125
|
+
- name: Setup Google Cloud SDK
|
|
126
|
+
uses: google-github-actions/setup-gcloud@v2
|
|
127
|
+
|
|
128
|
+
- name: Configure Docker for Artifact Registry
|
|
129
|
+
run: gcloud auth configure-docker \${{ env.AR_LOCATION }}-docker.pkg.dev --quiet
|
|
130
|
+
|
|
131
|
+
- name: Build Docker Container
|
|
132
|
+
run: |
|
|
133
|
+
IMAGE_TAG="\${{ env.AR_LOCATION }}-docker.pkg.dev/\${{ secrets.GCP_PROJECT_ID }}/\${{ env.AR_REPOSITORY }}/\${{ env.SERVICE_NAME }}:\${{ github.sha }}"
|
|
134
|
+
LATEST_TAG="\${{ env.AR_LOCATION }}-docker.pkg.dev/\${{ secrets.GCP_PROJECT_ID }}/\${{ env.AR_REPOSITORY }}/\${{ env.SERVICE_NAME }}:latest"
|
|
135
|
+
docker build -t "$IMAGE_TAG" -t "$LATEST_TAG" .
|
|
136
|
+
docker push "$IMAGE_TAG"
|
|
137
|
+
docker push "$LATEST_TAG"
|
|
138
|
+
|
|
139
|
+
- name: Deploy to Serverless Cloud Run
|
|
140
|
+
run: |
|
|
141
|
+
IMAGE_TAG="\${{ env.AR_LOCATION }}-docker.pkg.dev/\${{ secrets.GCP_PROJECT_ID }}/\${{ env.AR_REPOSITORY }}/\${{ env.SERVICE_NAME }}:\${{ github.sha }}"
|
|
142
|
+
gcloud run deploy \${{ env.SERVICE_NAME }} \\
|
|
143
|
+
--image "$IMAGE_TAG" \\
|
|
144
|
+
--region \${{ env.REGION }} \\
|
|
145
|
+
--platform managed \\
|
|
146
|
+
--allow-unauthenticated
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate AWS Deployment Workflow with IAM GitHub OIDC Role (Keyless)
|
|
152
|
+
* @param {Object} options
|
|
153
|
+
* @param {string} options.appName
|
|
154
|
+
* @param {string} [options.branch='main']
|
|
155
|
+
* @param {string} [options.region='us-east-1']
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
158
|
+
function generateAwsWorkflow(options = {}) {
|
|
159
|
+
const {
|
|
160
|
+
appName = 'webspresso-app',
|
|
161
|
+
branch = 'main',
|
|
162
|
+
region = 'us-east-1',
|
|
163
|
+
} = options;
|
|
164
|
+
|
|
165
|
+
return `name: Deploy to AWS (App Runner / ECS)
|
|
166
|
+
|
|
167
|
+
on:
|
|
168
|
+
push:
|
|
169
|
+
branches: [ ${branch} ]
|
|
170
|
+
|
|
171
|
+
# Permissions required to request GitHub OIDC token for AWS IAM role assumption
|
|
172
|
+
permissions:
|
|
173
|
+
id-token: write
|
|
174
|
+
contents: read
|
|
175
|
+
|
|
176
|
+
env:
|
|
177
|
+
AWS_REGION: ${region}
|
|
178
|
+
ECR_REPOSITORY: ${appName}
|
|
179
|
+
SERVICE_NAME: ${appName}
|
|
180
|
+
|
|
181
|
+
jobs:
|
|
182
|
+
deploy:
|
|
183
|
+
name: Test, Build & Deploy to AWS
|
|
184
|
+
runs-on: ubuntu-latest
|
|
185
|
+
|
|
186
|
+
steps:
|
|
187
|
+
- name: Checkout repository
|
|
188
|
+
uses: actions/checkout@v4
|
|
189
|
+
|
|
190
|
+
- name: Setup Node.js
|
|
191
|
+
uses: actions/setup-node@v4
|
|
192
|
+
with:
|
|
193
|
+
node-version: '22'
|
|
194
|
+
cache: 'npm'
|
|
195
|
+
|
|
196
|
+
- name: Install dependencies
|
|
197
|
+
run: npm ci
|
|
198
|
+
|
|
199
|
+
- name: Run Test Suite
|
|
200
|
+
run: npm test
|
|
201
|
+
|
|
202
|
+
- name: AWS Preflight Audit
|
|
203
|
+
run: npx webspresso deploy:check --platform aws --strict
|
|
204
|
+
|
|
205
|
+
# Keyless OIDC Authentication with AWS IAM Role
|
|
206
|
+
- name: Configure AWS Credentials (OIDC)
|
|
207
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
208
|
+
with:
|
|
209
|
+
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
210
|
+
aws-region: \${{ env.AWS_REGION }}
|
|
211
|
+
|
|
212
|
+
- name: Log in to Amazon ECR
|
|
213
|
+
id: login-ecr
|
|
214
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
215
|
+
|
|
216
|
+
- name: Build, tag, and push image to Amazon ECR
|
|
217
|
+
env:
|
|
218
|
+
ECR_REGISTRY: \${{ steps.login-ecr.outputs.registry }}
|
|
219
|
+
IMAGE_TAG: \${{ github.sha }}
|
|
220
|
+
run: |
|
|
221
|
+
docker build -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" -t "$ECR_REGISTRY/$ECR_REPOSITORY:latest" .
|
|
222
|
+
docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
|
|
223
|
+
docker push "$ECR_REGISTRY/$ECR_REPOSITORY:latest"
|
|
224
|
+
|
|
225
|
+
# App Runner auto-syncs on ECR push, or trigger manual redeploy:
|
|
226
|
+
- name: Update AWS Deployment
|
|
227
|
+
run: |
|
|
228
|
+
if [ -n "\${{ secrets.AWS_APP_RUNNER_ARN }}" ]; then
|
|
229
|
+
aws apprunner start-deployment --service-arn \${{ secrets.AWS_APP_RUNNER_ARN }}
|
|
230
|
+
fi
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate Cloudflare Workers Deployment Workflow
|
|
236
|
+
* @param {Object} options
|
|
237
|
+
* @param {string} [options.branch='main']
|
|
238
|
+
* @returns {string}
|
|
239
|
+
*/
|
|
240
|
+
function generateCloudflareWorkflow(options = {}) {
|
|
241
|
+
const { branch = 'main' } = options;
|
|
242
|
+
|
|
243
|
+
return `name: Deploy to Cloudflare Workers
|
|
244
|
+
|
|
245
|
+
on:
|
|
246
|
+
push:
|
|
247
|
+
branches: [ ${branch} ]
|
|
248
|
+
|
|
249
|
+
jobs:
|
|
250
|
+
deploy:
|
|
251
|
+
name: Test & Deploy to Edge
|
|
252
|
+
runs-on: ubuntu-latest
|
|
253
|
+
|
|
254
|
+
steps:
|
|
255
|
+
- name: Checkout repository
|
|
256
|
+
uses: actions/checkout@v4
|
|
257
|
+
|
|
258
|
+
- name: Setup Node.js
|
|
259
|
+
uses: actions/setup-node@v4
|
|
260
|
+
with:
|
|
261
|
+
node-version: '22'
|
|
262
|
+
cache: 'npm'
|
|
263
|
+
|
|
264
|
+
- name: Install dependencies
|
|
265
|
+
run: npm ci
|
|
266
|
+
|
|
267
|
+
- name: Run Test Suite
|
|
268
|
+
run: npm test
|
|
269
|
+
|
|
270
|
+
- name: Cloudflare Preflight Audit
|
|
271
|
+
run: npx webspresso deploy:check --platform cloudflare --strict
|
|
272
|
+
|
|
273
|
+
- name: Deploy to Cloudflare Workers
|
|
274
|
+
uses: cloudflare/wrangler-action@v3
|
|
275
|
+
with:
|
|
276
|
+
apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Generate GitLab CI pipeline configuration (.gitlab-ci.yml)
|
|
282
|
+
* @param {Object} options
|
|
283
|
+
* @param {string} [options.platform='generic']
|
|
284
|
+
* @returns {string}
|
|
285
|
+
*/
|
|
286
|
+
function generateGitLabCi(options = {}) {
|
|
287
|
+
const { platform = 'generic' } = options;
|
|
288
|
+
|
|
289
|
+
let platformAudit = 'npx webspresso deploy:check --strict';
|
|
290
|
+
if (platform === 'cloudrun') {
|
|
291
|
+
platformAudit = 'npx webspresso deploy:check --platform cloudrun --strict';
|
|
292
|
+
} else if (platform === 'aws') {
|
|
293
|
+
platformAudit = 'npx webspresso deploy:check --platform aws --strict';
|
|
294
|
+
} else if (platform === 'cloudflare') {
|
|
295
|
+
platformAudit = 'npx webspresso deploy:check --platform cloudflare --strict';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return `# ==============================================================================
|
|
299
|
+
# GitLab CI/CD Pipeline for Webspresso
|
|
300
|
+
# ==============================================================================
|
|
301
|
+
|
|
302
|
+
image: node:22-alpine
|
|
303
|
+
|
|
304
|
+
stages:
|
|
305
|
+
- test
|
|
306
|
+
- verify
|
|
307
|
+
- deploy
|
|
308
|
+
|
|
309
|
+
cache:
|
|
310
|
+
paths:
|
|
311
|
+
- node_modules/
|
|
312
|
+
- .npm/
|
|
313
|
+
|
|
314
|
+
test:
|
|
315
|
+
stage: test
|
|
316
|
+
script:
|
|
317
|
+
- npm ci --cache .npm --prefer-offline
|
|
318
|
+
- npm test
|
|
319
|
+
rules:
|
|
320
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
321
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
322
|
+
|
|
323
|
+
preflight_check:
|
|
324
|
+
stage: verify
|
|
325
|
+
script:
|
|
326
|
+
- npm ci --cache .npm --prefer-offline
|
|
327
|
+
- ${platformAudit}
|
|
328
|
+
rules:
|
|
329
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
330
|
+
|
|
331
|
+
deploy_production:
|
|
332
|
+
stage: deploy
|
|
333
|
+
image: docker:27
|
|
334
|
+
services:
|
|
335
|
+
- docker:dind
|
|
336
|
+
script:
|
|
337
|
+
- echo "Deploying Webspresso container to production..."
|
|
338
|
+
- docker build -t webspresso-app:$CI_COMMIT_SHA .
|
|
339
|
+
rules:
|
|
340
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Detect cloud platform from project artifacts
|
|
346
|
+
* @param {string} cwd
|
|
347
|
+
* @returns {'cloudrun'|'aws'|'cloudflare'|'generic'}
|
|
348
|
+
*/
|
|
349
|
+
function detectTargetPlatform(cwd) {
|
|
350
|
+
if (fs.existsSync(path.join(cwd, 'cloudbuild.yaml')) || fs.existsSync(path.join(cwd, '.gcloudignore'))) {
|
|
351
|
+
return 'cloudrun';
|
|
352
|
+
}
|
|
353
|
+
if (fs.existsSync(path.join(cwd, 'apprunner.yaml')) || fs.existsSync(path.join(cwd, 'ecs-task-definition.json'))) {
|
|
354
|
+
return 'aws';
|
|
355
|
+
}
|
|
356
|
+
if (fs.existsSync(path.join(cwd, 'wrangler.toml'))) {
|
|
357
|
+
return 'cloudflare';
|
|
358
|
+
}
|
|
359
|
+
return 'generic';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Register command with Commander
|
|
364
|
+
* @param {import('commander').Command} program
|
|
365
|
+
*/
|
|
366
|
+
function registerCommand(program) {
|
|
367
|
+
program
|
|
368
|
+
.command('add:ci')
|
|
369
|
+
.alias('add:workflow')
|
|
370
|
+
.alias('add:github-actions')
|
|
371
|
+
.description('Scaffold zero-secret GitHub Actions CI/CD workflows and GitLab CI pipelines')
|
|
372
|
+
.option('-p, --platform <platform>', 'Target platform (cloudrun, aws, cloudflare, generic, all, or auto)', 'auto')
|
|
373
|
+
.option('-b, --branch <branch>', 'Default trigger branch', 'main')
|
|
374
|
+
.option('--region <region>', 'Cloud region override')
|
|
375
|
+
.option('--gitlab', 'Also generate .gitlab-ci.yml pipeline configuration', false)
|
|
376
|
+
.option('--force', 'Overwrite existing workflow files', false)
|
|
377
|
+
.action(async (...actionArgs) => {
|
|
378
|
+
const cmd = actionArgs[actionArgs.length - 1];
|
|
379
|
+
const options = (cmd && typeof cmd.opts === 'function')
|
|
380
|
+
? cmd.opts()
|
|
381
|
+
: (actionArgs.find((a) => a && typeof a === 'object') || {});
|
|
382
|
+
|
|
383
|
+
const cwd = process.cwd();
|
|
384
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
385
|
+
if (!fs.existsSync(pkgPath)) {
|
|
386
|
+
console.error('ā package.json not found. Please run this command in a Webspresso project root.');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let pkg = {};
|
|
391
|
+
try {
|
|
392
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
393
|
+
} catch (_) {}
|
|
394
|
+
|
|
395
|
+
const appName = pkg.name || path.basename(cwd);
|
|
396
|
+
const branch = options.branch || 'main';
|
|
397
|
+
const force = Boolean(options.force);
|
|
398
|
+
let platform = options.platform || 'auto';
|
|
399
|
+
|
|
400
|
+
if (platform === 'auto') {
|
|
401
|
+
platform = detectTargetPlatform(cwd);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
console.log(`\nš¤ Scaffolding CI/CD Pipelines for "${appName}"...`);
|
|
405
|
+
console.log(`Target Platform : ${platform.toUpperCase()}`);
|
|
406
|
+
console.log(`Trigger Branch : ${branch}\n`);
|
|
407
|
+
|
|
408
|
+
const workflowsDir = path.join(cwd, '.github', 'workflows');
|
|
409
|
+
if (!fs.existsSync(workflowsDir)) {
|
|
410
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 1. Base CI workflow (ci.yml)
|
|
414
|
+
const baseCiPath = path.join(workflowsDir, 'ci.yml');
|
|
415
|
+
if (fs.existsSync(baseCiPath) && !force) {
|
|
416
|
+
console.log(`ā ļø .github/workflows/ci.yml already exists. Skipping (use --force to overwrite).`);
|
|
417
|
+
} else {
|
|
418
|
+
const baseContent = generateBaseCiWorkflow({ branch });
|
|
419
|
+
fs.writeFileSync(baseCiPath, baseContent, 'utf8');
|
|
420
|
+
console.log(`ā
Created .github/workflows/ci.yml (Test & Preflight gate)`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 2. Platform-Specific Workflow
|
|
424
|
+
if (platform === 'cloudrun' || platform === 'all') {
|
|
425
|
+
const crPath = path.join(workflowsDir, 'deploy-cloudrun.yml');
|
|
426
|
+
if (fs.existsSync(crPath) && !force) {
|
|
427
|
+
console.log(`ā ļø .github/workflows/deploy-cloudrun.yml already exists. Skipping.`);
|
|
428
|
+
} else {
|
|
429
|
+
const crContent = generateCloudRunWorkflow({
|
|
430
|
+
appName,
|
|
431
|
+
branch,
|
|
432
|
+
region: options.region || 'us-central1',
|
|
433
|
+
});
|
|
434
|
+
fs.writeFileSync(crPath, crContent, 'utf8');
|
|
435
|
+
console.log(`ā
Created .github/workflows/deploy-cloudrun.yml (Keyless WIF OIDC)`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (platform === 'aws' || platform === 'all') {
|
|
440
|
+
const awsPath = path.join(workflowsDir, 'deploy-aws.yml');
|
|
441
|
+
if (fs.existsSync(awsPath) && !force) {
|
|
442
|
+
console.log(`ā ļø .github/workflows/deploy-aws.yml already exists. Skipping.`);
|
|
443
|
+
} else {
|
|
444
|
+
const awsContent = generateAwsWorkflow({
|
|
445
|
+
appName,
|
|
446
|
+
branch,
|
|
447
|
+
region: options.region || 'us-east-1',
|
|
448
|
+
});
|
|
449
|
+
fs.writeFileSync(awsPath, awsContent, 'utf8');
|
|
450
|
+
console.log(`ā
Created .github/workflows/deploy-aws.yml (Keyless IAM OIDC)`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (platform === 'cloudflare' || platform === 'all') {
|
|
455
|
+
const cfPath = path.join(workflowsDir, 'deploy-cloudflare.yml');
|
|
456
|
+
if (fs.existsSync(cfPath) && !force) {
|
|
457
|
+
console.log(`ā ļø .github/workflows/deploy-cloudflare.yml already exists. Skipping.`);
|
|
458
|
+
} else {
|
|
459
|
+
const cfContent = generateCloudflareWorkflow({ branch });
|
|
460
|
+
fs.writeFileSync(cfPath, cfContent, 'utf8');
|
|
461
|
+
console.log(`ā
Created .github/workflows/deploy-cloudflare.yml (Wrangler Action)`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 3. GitLab CI pipeline if requested
|
|
466
|
+
if (options.gitlab) {
|
|
467
|
+
const gitlabPath = path.join(cwd, '.gitlab-ci.yml');
|
|
468
|
+
if (fs.existsSync(gitlabPath) && !force) {
|
|
469
|
+
console.log(`ā ļø .gitlab-ci.yml already exists. Skipping.`);
|
|
470
|
+
} else {
|
|
471
|
+
const gitlabContent = generateGitLabCi({ platform });
|
|
472
|
+
fs.writeFileSync(gitlabPath, gitlabContent, 'utf8');
|
|
473
|
+
console.log(`ā
Created .gitlab-ci.yml`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 4. Update package.json scripts
|
|
478
|
+
let modifiedPkg = false;
|
|
479
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
480
|
+
if (!pkg.scripts['ci:verify']) {
|
|
481
|
+
pkg.scripts['ci:verify'] = 'npm test && webspresso deploy:check --strict';
|
|
482
|
+
modifiedPkg = true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (modifiedPkg) {
|
|
486
|
+
try {
|
|
487
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
488
|
+
console.log(`ā
Added "ci:verify" script to package.json`);
|
|
489
|
+
} catch (_) {}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
console.log('\nš CI/CD Pipeline scaffolding complete!');
|
|
493
|
+
console.log('\nSecurity & Next Steps:');
|
|
494
|
+
if (platform === 'cloudrun') {
|
|
495
|
+
console.log(' 1. Configure Workload Identity Pool in GCP IAM:');
|
|
496
|
+
console.log(' gcloud iam workload-identity-pools create "github-pool" --location="global"');
|
|
497
|
+
console.log(' 2. Set GitHub Repository Secrets:');
|
|
498
|
+
console.log(' - GCP_PROJECT_ID');
|
|
499
|
+
console.log(' - GCP_WIF_PROVIDER (e.g. projects/123/locations/global/workloadIdentityPools/github-pool/providers/github-provider)');
|
|
500
|
+
console.log(' - GCP_WIF_SERVICE_ACCOUNT (e.g. github-deployer@my-project.iam.gserviceaccount.com)\n');
|
|
501
|
+
} else if (platform === 'aws') {
|
|
502
|
+
console.log(' 1. Configure GitHub OIDC Identity Provider in AWS IAM.');
|
|
503
|
+
console.log(' 2. Set GitHub Repository Secrets:');
|
|
504
|
+
console.log(' - AWS_DEPLOY_ROLE_ARN (e.g. arn:aws:iam::123456789012:role/GitHubActionsDeployRole)\n');
|
|
505
|
+
} else if (platform === 'cloudflare') {
|
|
506
|
+
console.log(' 1. Set GitHub Repository Secret:');
|
|
507
|
+
console.log(' - CLOUDFLARE_API_TOKEN\n');
|
|
508
|
+
} else {
|
|
509
|
+
console.log(' 1. Push code to your git remote: git push origin main');
|
|
510
|
+
console.log(' 2. GitHub Actions will automatically run test & deploy:check gates.\n');
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
module.exports = {
|
|
516
|
+
registerCommand,
|
|
517
|
+
generateBaseCiWorkflow,
|
|
518
|
+
generateCloudRunWorkflow,
|
|
519
|
+
generateAwsWorkflow,
|
|
520
|
+
generateCloudflareWorkflow,
|
|
521
|
+
generateGitLabCi,
|
|
522
|
+
detectTargetPlatform,
|
|
523
|
+
};
|