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.
Files changed (134) hide show
  1. package/.agents/AGENTS.md +15 -2
  2. package/.agents/CLI.md +219 -7
  3. package/.agents/FLAGS.md +118 -0
  4. package/.agents/LOGGING.md +93 -0
  5. package/.agents/METRICS.md +127 -0
  6. package/.agents/MIGRATION_JOBS.md +95 -0
  7. package/.agents/PLUGINS.md +10 -3
  8. package/.agents/QUEUE.md +42 -0
  9. package/.agents/RATE_LIMIT.md +147 -0
  10. package/.agents/SCALE_OUT.md +109 -0
  11. package/.agents/SECRETS.md +117 -0
  12. package/AGENTS.md +10 -0
  13. package/bin/commands/add-aws.js +396 -0
  14. package/bin/commands/add-ci.js +523 -0
  15. package/bin/commands/add-cloudflare.js +240 -0
  16. package/bin/commands/add-cloudrun.js +381 -0
  17. package/bin/commands/add-migration-job.js +396 -0
  18. package/bin/commands/assets-sync.js +209 -0
  19. package/bin/commands/client-generate.js +52 -0
  20. package/bin/commands/console.js +53 -0
  21. package/bin/commands/db-migrate.js +47 -2
  22. package/bin/commands/db-studio.js +84 -0
  23. package/bin/commands/deploy-check.js +242 -13
  24. package/bin/commands/export.js +87 -0
  25. package/bin/commands/secrets-pull.js +208 -0
  26. package/bin/utils/db.js +7 -1
  27. package/bin/webspresso.js +22 -0
  28. package/core/app.js +421 -0
  29. package/core/auth/authenticate.js +93 -0
  30. package/core/auth/index.js +34 -4
  31. package/core/auth/manager.js +50 -5
  32. package/core/auth/middleware.js +16 -68
  33. package/core/auth/redis-session.js +1008 -0
  34. package/core/auth/sessions.js +156 -0
  35. package/core/cache/cdn.js +296 -0
  36. package/core/cache/index.js +12 -0
  37. package/core/cache/tagged-lru.js +9 -1
  38. package/core/console/commands.js +148 -0
  39. package/core/console/context.js +260 -0
  40. package/core/console/eval.js +70 -0
  41. package/core/console/index.js +22 -0
  42. package/core/console/repl.js +102 -0
  43. package/core/errors/domain.js +1 -0
  44. package/core/events/dispatcher.js +7 -0
  45. package/core/events/signal.js +7 -0
  46. package/core/export/discovery.js +191 -0
  47. package/core/export/exporter.js +277 -0
  48. package/core/export/index.js +24 -0
  49. package/core/flags/evaluator.js +252 -0
  50. package/core/flags/hasher.js +37 -0
  51. package/core/flags/index.js +21 -0
  52. package/core/flags/manager.js +263 -0
  53. package/core/flags/store.js +126 -0
  54. package/core/health/middleware.js +3 -3
  55. package/core/http/adapter.js +74 -0
  56. package/core/http/express-adapter.js +46 -0
  57. package/core/http/hono-adapter.js +531 -0
  58. package/core/http/index.js +41 -0
  59. package/core/http/pipeline.js +485 -0
  60. package/core/index.js +336 -0
  61. package/core/limits/middleware.js +9 -1
  62. package/core/logger/formatters.js +264 -0
  63. package/core/logger/index.js +260 -0
  64. package/core/logger/trace.js +137 -0
  65. package/core/metrics/collectors.js +125 -0
  66. package/core/metrics/index.js +45 -0
  67. package/core/metrics/middleware.js +230 -0
  68. package/core/metrics/registry.js +366 -0
  69. package/core/orm/d1.js +265 -0
  70. package/core/orm/index.js +105 -5
  71. package/core/orm/migrate-cloud.js +194 -0
  72. package/core/orm/pool.js +97 -0
  73. package/core/orm/query-builder.js +9 -0
  74. package/core/queue/adapters/cloudflare.js +131 -0
  75. package/core/queue/adapters/pubsub.js +400 -0
  76. package/core/queue/adapters/sqs.js +419 -0
  77. package/core/queue/adapters/wait-until.js +77 -0
  78. package/core/queue/index.js +8 -0
  79. package/core/queue/manager.js +11 -0
  80. package/core/rate-limit/index.js +22 -0
  81. package/core/rate-limit/limiter.js +219 -0
  82. package/core/rate-limit/stores/memory.js +129 -0
  83. package/core/rate-limit/stores/redis.js +151 -0
  84. package/core/rate-limit/utils.js +112 -0
  85. package/core/resilience/circuit-breaker.js +31 -0
  86. package/core/secrets/index.js +113 -0
  87. package/core/secrets/manager.js +308 -0
  88. package/core/secrets/providers/aws.js +288 -0
  89. package/core/secrets/providers/env.js +44 -0
  90. package/core/secrets/providers/gcp.js +307 -0
  91. package/core/secrets/providers/memory.js +76 -0
  92. package/core/shutdown/shutdown-manager.js +9 -0
  93. package/core/stream/index.js +19 -0
  94. package/core/stream/text-stream.js +326 -0
  95. package/core/studio/index.js +43 -0
  96. package/core/studio/inspector.js +258 -0
  97. package/core/studio/server.js +325 -0
  98. package/core/studio/ui.js +681 -0
  99. package/index.d.ts +934 -2
  100. package/index.js +125 -3
  101. package/package.json +3 -1
  102. package/plugins/admin-panel/index.js +12 -5
  103. package/plugins/api-key/index.js +3 -2
  104. package/plugins/audit-log/index.js +3 -1
  105. package/plugins/basic-auth/index.js +3 -2
  106. package/plugins/cors.js +3 -2
  107. package/plugins/csrf/index.js +4 -3
  108. package/plugins/csv/index.js +8 -5
  109. package/plugins/email/index.js +11 -9
  110. package/plugins/index.js +3 -0
  111. package/plugins/queue/index.js +18 -2
  112. package/plugins/redirect/index.js +7 -2
  113. package/plugins/seo-checker/index.js +2 -2
  114. package/plugins/site-analytics/index.js +3 -1
  115. package/plugins/swagger.js +8 -2
  116. package/plugins/upload/gcs-provider.js +148 -0
  117. package/plugins/upload/index.js +132 -74
  118. package/plugins/upload/s3-provider.js +257 -0
  119. package/plugins/webhooks/index.js +27 -23
  120. package/plugins/xlsx/index.js +7 -5
  121. package/src/api/api-loader.js +12 -95
  122. package/src/api/define-api.js +1 -0
  123. package/src/client-runtime/constants.js +10 -0
  124. package/src/client-runtime/mount.js +2 -3
  125. package/src/dev/client-gen.js +458 -0
  126. package/src/discovery/scan-routes.js +5 -2
  127. package/src/file-router.js +2 -0
  128. package/src/helpers.js +53 -0
  129. package/src/pages/page-loader.js +12 -205
  130. package/src/plugin-manager.js +9 -1
  131. package/src/server.js +231 -140
  132. package/src/services/executor.js +9 -0
  133. package/src/services/registry.js +9 -1
  134. 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
+ };