@expressots/cli 3.0.0 → 4.0.0-preview.2

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 (180) hide show
  1. package/README.md +41 -95
  2. package/bin/cicd/cli.d.ts +6 -0
  3. package/bin/cicd/cli.js +126 -0
  4. package/bin/cicd/form.d.ts +29 -0
  5. package/bin/cicd/form.js +345 -0
  6. package/bin/cicd/generators/azure-devops.d.ts +2 -0
  7. package/bin/cicd/generators/azure-devops.js +370 -0
  8. package/bin/cicd/generators/bitbucket.d.ts +2 -0
  9. package/bin/cicd/generators/bitbucket.js +217 -0
  10. package/bin/cicd/generators/circleci.d.ts +2 -0
  11. package/bin/cicd/generators/circleci.js +274 -0
  12. package/bin/cicd/generators/github-actions.d.ts +14 -0
  13. package/bin/cicd/generators/github-actions.js +426 -0
  14. package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
  15. package/bin/cicd/generators/gitlab-ci.js +237 -0
  16. package/bin/cicd/generators/index.d.ts +6 -0
  17. package/bin/cicd/generators/index.js +15 -0
  18. package/bin/cicd/generators/jenkins.d.ts +2 -0
  19. package/bin/cicd/generators/jenkins.js +248 -0
  20. package/bin/cicd/generators/template-loader.d.ts +17 -0
  21. package/bin/cicd/generators/template-loader.js +128 -0
  22. package/bin/cicd/index.d.ts +1 -0
  23. package/bin/cicd/index.js +5 -0
  24. package/bin/cli.d.ts +1 -1
  25. package/bin/cli.js +18 -3
  26. package/bin/commands/project.commands.d.ts +19 -6
  27. package/bin/commands/project.commands.js +390 -61
  28. package/bin/config/index.d.ts +5 -0
  29. package/bin/config/index.js +10 -0
  30. package/bin/config/manager.d.ts +98 -0
  31. package/bin/config/manager.js +222 -0
  32. package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
  33. package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
  34. package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
  35. package/bin/containerize/analyzers/project-analyzer.js +150 -0
  36. package/bin/containerize/cli.d.ts +4 -0
  37. package/bin/containerize/cli.js +113 -0
  38. package/bin/containerize/form.d.ts +15 -0
  39. package/bin/containerize/form.js +154 -0
  40. package/bin/containerize/generators/ci-generator.d.ts +31 -0
  41. package/bin/containerize/generators/ci-generator.js +936 -0
  42. package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
  43. package/bin/containerize/generators/docker-compose-generator.js +186 -0
  44. package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
  45. package/bin/containerize/generators/dockerfile-generator.js +635 -0
  46. package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
  47. package/bin/containerize/generators/kubernetes-generator.js +133 -0
  48. package/bin/containerize/generators/template-loader.d.ts +36 -0
  49. package/bin/containerize/generators/template-loader.js +129 -0
  50. package/bin/containerize/index.d.ts +4 -0
  51. package/bin/containerize/index.js +13 -0
  52. package/bin/containerize/presets/preset-registry.d.ts +20 -0
  53. package/bin/containerize/presets/preset-registry.js +102 -0
  54. package/bin/costs/cli.d.ts +5 -0
  55. package/bin/costs/cli.js +183 -0
  56. package/bin/costs/form.d.ts +44 -0
  57. package/bin/costs/form.js +412 -0
  58. package/bin/costs/index.d.ts +4 -0
  59. package/bin/costs/index.js +25 -0
  60. package/bin/costs/pricing-manager.d.ts +84 -0
  61. package/bin/costs/pricing-manager.js +342 -0
  62. package/bin/costs/providers/index.d.ts +32 -0
  63. package/bin/costs/providers/index.js +153 -0
  64. package/bin/costs/sources/api-source.d.ts +10 -0
  65. package/bin/costs/sources/api-source.js +32 -0
  66. package/bin/costs/sources/index.d.ts +6 -0
  67. package/bin/costs/sources/index.js +15 -0
  68. package/bin/costs/sources/local-json-source.d.ts +23 -0
  69. package/bin/costs/sources/local-json-source.js +59 -0
  70. package/bin/costs/sources/remote-json-source.d.ts +11 -0
  71. package/bin/costs/sources/remote-json-source.js +53 -0
  72. package/bin/costs/types.d.ts +53 -0
  73. package/bin/costs/types.js +5 -0
  74. package/bin/dev/cli.d.ts +4 -0
  75. package/bin/dev/cli.js +134 -0
  76. package/bin/dev/form.d.ts +36 -0
  77. package/bin/dev/form.js +254 -0
  78. package/bin/dev/index.d.ts +1 -0
  79. package/bin/dev/index.js +5 -0
  80. package/bin/generate/cli.js +29 -2
  81. package/bin/generate/form.d.ts +5 -1
  82. package/bin/generate/form.js +3 -3
  83. package/bin/generate/templates/nonopinionated/config.tpl +12 -0
  84. package/bin/generate/templates/nonopinionated/event.tpl +10 -0
  85. package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
  86. package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
  87. package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
  88. package/bin/generate/templates/opinionated/config.tpl +47 -0
  89. package/bin/generate/templates/opinionated/entity.tpl +1 -8
  90. package/bin/generate/templates/opinionated/event.tpl +15 -0
  91. package/bin/generate/templates/opinionated/guard.tpl +41 -0
  92. package/bin/generate/templates/opinionated/handler.tpl +23 -0
  93. package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
  94. package/bin/generate/utils/command-utils.d.ts +7 -3
  95. package/bin/generate/utils/command-utils.js +95 -31
  96. package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
  97. package/bin/generate/utils/nonopininated-cmd.js +100 -1
  98. package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
  99. package/bin/generate/utils/opinionated-cmd.js +112 -7
  100. package/bin/generate/utils/string-utils.d.ts +6 -0
  101. package/bin/generate/utils/string-utils.js +13 -1
  102. package/bin/help/form.js +11 -3
  103. package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
  104. package/bin/migrate/analyzers/platform-detector.js +116 -0
  105. package/bin/migrate/cli.d.ts +6 -0
  106. package/bin/migrate/cli.js +96 -0
  107. package/bin/migrate/form.d.ts +25 -0
  108. package/bin/migrate/form.js +347 -0
  109. package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
  110. package/bin/migrate/generators/compose-to-k8s.js +324 -0
  111. package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
  112. package/bin/migrate/generators/compose-to-railway.js +138 -0
  113. package/bin/migrate/generators/compose-to-render.d.ts +2 -0
  114. package/bin/migrate/generators/compose-to-render.js +148 -0
  115. package/bin/migrate/generators/generic-migration.d.ts +9 -0
  116. package/bin/migrate/generators/generic-migration.js +221 -0
  117. package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
  118. package/bin/migrate/generators/heroku-to-fly.js +291 -0
  119. package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
  120. package/bin/migrate/generators/heroku-to-railway.js +283 -0
  121. package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
  122. package/bin/migrate/generators/heroku-to-render.js +148 -0
  123. package/bin/migrate/generators/index.d.ts +7 -0
  124. package/bin/migrate/generators/index.js +17 -0
  125. package/bin/migrate/generators/template-loader.d.ts +21 -0
  126. package/bin/migrate/generators/template-loader.js +59 -0
  127. package/bin/migrate/index.d.ts +1 -0
  128. package/bin/migrate/index.js +5 -0
  129. package/bin/new/cli.js +21 -6
  130. package/bin/new/form.d.ts +25 -4
  131. package/bin/new/form.js +285 -70
  132. package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
  133. package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
  134. package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
  135. package/bin/profile/analyzers/image-analyzer.js +85 -0
  136. package/bin/profile/cli.d.ts +4 -0
  137. package/bin/profile/cli.js +92 -0
  138. package/bin/profile/form.d.ts +56 -0
  139. package/bin/profile/form.js +400 -0
  140. package/bin/profile/index.d.ts +1 -0
  141. package/bin/profile/index.js +5 -0
  142. package/bin/profile/optimizers/index.d.ts +19 -0
  143. package/bin/profile/optimizers/index.js +137 -0
  144. package/bin/providers/add/form.d.ts +1 -1
  145. package/bin/providers/add/form.js +27 -6
  146. package/bin/providers/create/form.js +2 -1
  147. package/bin/scripts/form.js +27 -5
  148. package/bin/studio/cli.d.ts +15 -0
  149. package/bin/studio/cli.js +166 -0
  150. package/bin/studio/index.d.ts +5 -0
  151. package/bin/studio/index.js +9 -0
  152. package/bin/templates/cache.d.ts +54 -0
  153. package/bin/templates/cache.js +180 -0
  154. package/bin/templates/cli.d.ts +8 -0
  155. package/bin/templates/cli.js +292 -0
  156. package/bin/templates/fetcher.d.ts +49 -0
  157. package/bin/templates/fetcher.js +208 -0
  158. package/bin/templates/index.d.ts +11 -0
  159. package/bin/templates/index.js +37 -0
  160. package/bin/templates/manager.d.ts +116 -0
  161. package/bin/templates/manager.js +323 -0
  162. package/bin/templates/renderer.d.ts +49 -0
  163. package/bin/templates/renderer.js +204 -0
  164. package/bin/templates/types.d.ts +51 -0
  165. package/bin/templates/types.js +5 -0
  166. package/bin/utils/add-module-to-container.d.ts +2 -2
  167. package/bin/utils/add-module-to-container.js +15 -5
  168. package/bin/utils/cli-ui.d.ts +30 -3
  169. package/bin/utils/cli-ui.js +95 -13
  170. package/bin/utils/index.d.ts +4 -0
  171. package/bin/utils/index.js +4 -0
  172. package/bin/utils/input-validation.d.ts +50 -0
  173. package/bin/utils/input-validation.js +143 -0
  174. package/bin/utils/package-manager-commands.d.ts +24 -0
  175. package/bin/utils/package-manager-commands.js +50 -0
  176. package/bin/utils/safe-spawn.d.ts +35 -0
  177. package/bin/utils/safe-spawn.js +51 -0
  178. package/bin/utils/update-tsconfig-paths.d.ts +35 -0
  179. package/bin/utils/update-tsconfig-paths.js +286 -0
  180. package/package.json +154 -154
@@ -0,0 +1,936 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateCIConfig = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const package_manager_commands_1 = require("../../utils/package-manager-commands");
11
+ async function generateCIConfig(options, analysis) {
12
+ const cwd = process.cwd();
13
+ const platform = options.ciPlatform || "github";
14
+ const strategy = options.ciStrategy || "comprehensive";
15
+ console.log(chalk_1.default.yellow(`📝 Generating CI/CD configuration for ${platform}...`));
16
+ if (platform === "all") {
17
+ await generateAllPlatforms(cwd, options, analysis);
18
+ }
19
+ else {
20
+ await generatePlatformConfig(cwd, platform, options, analysis);
21
+ }
22
+ }
23
+ exports.generateCIConfig = generateCIConfig;
24
+ async function generateAllPlatforms(cwd, options, analysis) {
25
+ const platforms = [
26
+ "github",
27
+ "gitlab",
28
+ "circleci",
29
+ "jenkins",
30
+ "bitbucket",
31
+ "azure",
32
+ ];
33
+ for (const platform of platforms) {
34
+ await generatePlatformConfig(cwd, platform, options, analysis);
35
+ }
36
+ console.log(chalk_1.default.green(` ✓ Generated CI/CD configs for all platforms`));
37
+ }
38
+ async function generatePlatformConfig(cwd, platform, options, analysis) {
39
+ if (platform !== "github") {
40
+ printSinglePipelinePlatformWarning(platform);
41
+ }
42
+ switch (platform) {
43
+ case "github":
44
+ await generateGitHubActions(cwd, options, analysis);
45
+ break;
46
+ case "gitlab":
47
+ await generateGitLabCI(cwd, options, analysis);
48
+ break;
49
+ case "circleci":
50
+ await generateCircleCI(cwd, options, analysis);
51
+ break;
52
+ case "jenkins":
53
+ await generateJenkinsfile(cwd, options, analysis);
54
+ break;
55
+ case "bitbucket":
56
+ await generateBitbucketPipelines(cwd, options, analysis);
57
+ break;
58
+ case "azure":
59
+ await generateAzureDevOps(cwd, options, analysis);
60
+ break;
61
+ }
62
+ }
63
+ /**
64
+ * Warn the user that, unlike GitHub Actions (which can host any
65
+ * number of workflow files side-by-side), this platform only
66
+ * supports a single canonical pipeline config — so the file we are
67
+ * about to write would overwrite anything `expressots cicd init`
68
+ * already produced for the same platform.
69
+ */
70
+ function printSinglePipelinePlatformWarning(platform) {
71
+ console.log(chalk_1.default.yellow(`\n⚠️ ${platform} only supports a single canonical pipeline config file.`));
72
+ console.log(chalk_1.default.gray(" The CD-focused output below will overwrite anything previously"));
73
+ console.log(chalk_1.default.gray(" generated by `expressots cicd init`. For non-GitHub platforms"));
74
+ console.log(chalk_1.default.gray(" prefer `cicd init` (which already includes Docker build/push/deploy).\n"));
75
+ }
76
+ // ==================== GITHUB ACTIONS ====================
77
+ async function generateGitHubActions(cwd, options, analysis) {
78
+ const githubDir = path_1.default.join(cwd, ".github", "workflows");
79
+ if (!fs_1.default.existsSync(githubDir)) {
80
+ fs_1.default.mkdirSync(githubDir, { recursive: true });
81
+ }
82
+ const workflow = generateGitHubActionsWorkflow(options, analysis);
83
+ // File name `cd-docker.yml` makes the CD intent explicit and
84
+ // avoids visual clash with `cicd init`'s `ci.yml`.
85
+ fs_1.default.writeFileSync(path_1.default.join(githubDir, "cd-docker.yml"), workflow, "utf-8");
86
+ console.log(chalk_1.default.green(` ✓ Created .github/workflows/cd-docker.yml`));
87
+ }
88
+ function generateGitHubActionsWorkflow(options, analysis) {
89
+ const strategy = options.ciStrategy || "comprehensive";
90
+ const includeSecurityScans = options.includeSecurityScans !== false;
91
+ const includeE2E = options.includeE2E === true;
92
+ const nodeVersion = analysis?.nodeVersion || "20";
93
+ const packageManager = analysis?.packageManager || "npm";
94
+ let workflow = `# GitHub Actions: Docker CD Pipeline
95
+ # Generated by ExpressoTS CLI (\`expressots containerize --include-ci\`)
96
+ #
97
+ # Scope: continuous DELIVERY for the Docker image (build, scan,
98
+ # push, deploy). For continuous INTEGRATION (lint, unit tests,
99
+ # coverage) generate a complementary \`ci.yml\` via:
100
+ # expressots cicd init github
101
+
102
+ name: Docker Build and Deploy
103
+
104
+ on:
105
+ push:
106
+ branches: [main, develop]
107
+ pull_request:
108
+ branches: [main]
109
+ workflow_dispatch:
110
+
111
+ env:
112
+ DOCKER_IMAGE: expressots-app
113
+ NODE_VERSION: '${nodeVersion}'
114
+
115
+ jobs:
116
+ `;
117
+ // Lint Job
118
+ workflow += ` lint:
119
+ name: Code Quality
120
+ runs-on: ubuntu-latest
121
+
122
+ steps:
123
+ - name: Checkout code
124
+ uses: actions/checkout@v4
125
+
126
+ - name: Setup Node.js
127
+ uses: actions/setup-node@v4
128
+ with:
129
+ node-version: \${{ env.NODE_VERSION }}
130
+ cache: '${packageManager}'
131
+
132
+ - name: Install dependencies
133
+ run: ${getInstallCommand(packageManager)}
134
+
135
+ - name: Run linter
136
+ run: ${packageManager} run lint
137
+
138
+ - name: Check formatting
139
+ run: ${packageManager} run format -- --check || echo "No format script found"
140
+ continue-on-error: true
141
+
142
+ `;
143
+ // Test Job
144
+ workflow += ` test:
145
+ name: Unit Tests
146
+ runs-on: ubuntu-latest
147
+ needs: lint
148
+
149
+ steps:
150
+ - name: Checkout code
151
+ uses: actions/checkout@v4
152
+
153
+ - name: Setup Node.js
154
+ uses: actions/setup-node@v4
155
+ with:
156
+ node-version: \${{ env.NODE_VERSION }}
157
+ cache: '${packageManager}'
158
+
159
+ - name: Install dependencies
160
+ run: ${getInstallCommand(packageManager)}
161
+
162
+ - name: Run unit tests
163
+ run: ${packageManager} run test
164
+
165
+ - name: Generate coverage report
166
+ run: ${packageManager} run coverage || echo "No coverage script"
167
+ continue-on-error: true
168
+
169
+ - name: Upload coverage to Codecov
170
+ uses: codecov/codecov-action@v4
171
+ with:
172
+ token: \${{ secrets.CODECOV_TOKEN }}
173
+ files: ./coverage/lcov.info
174
+ fail_ci_if_error: false
175
+ continue-on-error: true
176
+
177
+ `;
178
+ // Security Scan Job
179
+ if (strategy === "comprehensive" || includeSecurityScans) {
180
+ workflow += ` security:
181
+ name: Security Scans
182
+ runs-on: ubuntu-latest
183
+ needs: lint
184
+
185
+ steps:
186
+ - name: Checkout code
187
+ uses: actions/checkout@v4
188
+
189
+ - name: Run Trivy vulnerability scanner
190
+ uses: aquasecurity/trivy-action@master
191
+ with:
192
+ scan-type: 'fs'
193
+ scan-ref: '.'
194
+ format: 'sarif'
195
+ output: 'trivy-results.sarif'
196
+ continue-on-error: true
197
+
198
+ - name: Upload Trivy results to GitHub Security
199
+ uses: github/codeql-action/upload-sarif@v3
200
+ with:
201
+ sarif_file: 'trivy-results.sarif'
202
+ continue-on-error: true
203
+
204
+ - name: Run Snyk security scan
205
+ uses: snyk/actions/node@master
206
+ env:
207
+ SNYK_TOKEN: \${{ secrets.SNYK_TOKEN }}
208
+ with:
209
+ args: --severity-threshold=high
210
+ continue-on-error: true
211
+
212
+ `;
213
+ }
214
+ // Build and Push Job
215
+ workflow += ` build:
216
+ name: Build and Push Docker Image
217
+ runs-on: ubuntu-latest
218
+ needs: [test${strategy === "comprehensive" || includeSecurityScans ? ", security" : ""}]
219
+ permissions:
220
+ contents: read
221
+ packages: write
222
+
223
+ steps:
224
+ - name: Checkout code
225
+ uses: actions/checkout@v4
226
+
227
+ - name: Set up Docker Buildx
228
+ uses: docker/setup-buildx-action@v3
229
+
230
+ - name: Log in to Docker Hub
231
+ uses: docker/login-action@v3
232
+ with:
233
+ username: \${{ secrets.DOCKER_USERNAME }}
234
+ password: \${{ secrets.DOCKER_PASSWORD }}
235
+
236
+ - name: Extract metadata
237
+ id: meta
238
+ uses: docker/metadata-action@v5
239
+ with:
240
+ images: \${{ secrets.DOCKER_USERNAME }}/\${{ env.DOCKER_IMAGE }}
241
+ tags: |
242
+ type=ref,event=branch
243
+ type=ref,event=pr
244
+ type=semver,pattern={{version}}
245
+ type=semver,pattern={{major}}.{{minor}}
246
+ type=sha,prefix={{branch}}-
247
+
248
+ - name: Build and push Docker image
249
+ uses: docker/build-push-action@v5
250
+ with:
251
+ context: .
252
+ push: \${{ github.event_name != 'pull_request' }}
253
+ tags: \${{ steps.meta.outputs.tags }}
254
+ labels: \${{ steps.meta.outputs.labels }}
255
+ cache-from: type=gha
256
+ cache-to: type=gha,mode=max
257
+ build-args: |
258
+ NODE_VERSION=\${{ env.NODE_VERSION }}
259
+
260
+ - name: Scan Docker image for vulnerabilities
261
+ uses: aquasecurity/trivy-action@master
262
+ with:
263
+ image-ref: \${{ secrets.DOCKER_USERNAME }}/\${{ env.DOCKER_IMAGE }}:latest
264
+ format: 'table'
265
+ exit-code: '0'
266
+ continue-on-error: true
267
+
268
+ `;
269
+ // E2E Tests Job (if requested)
270
+ if (includeE2E) {
271
+ workflow += ` e2e:
272
+ name: End-to-End Tests
273
+ runs-on: ubuntu-latest
274
+ needs: build
275
+ if: github.event_name != 'pull_request'
276
+
277
+ steps:
278
+ - name: Checkout code
279
+ uses: actions/checkout@v4
280
+
281
+ - name: Log in to Docker Hub
282
+ uses: docker/login-action@v3
283
+ with:
284
+ username: \${{ secrets.DOCKER_USERNAME }}
285
+ password: \${{ secrets.DOCKER_PASSWORD }}
286
+
287
+ - name: Pull Docker image
288
+ run: docker pull \${{ secrets.DOCKER_USERNAME }}/\${{ env.DOCKER_IMAGE }}:latest
289
+
290
+ - name: Run E2E tests
291
+ run: |
292
+ docker-compose -f docker-compose.test.yml up -d
293
+ ${packageManager} run test:e2e || echo "No e2e tests configured"
294
+ docker-compose -f docker-compose.test.yml down
295
+
296
+ `;
297
+ }
298
+ // Deploy Job
299
+ workflow += ` deploy:
300
+ name: Deploy to ${options.environment || "production"}
301
+ runs-on: ubuntu-latest
302
+ needs: build
303
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
304
+ environment:
305
+ name: ${options.environment || "production"}
306
+ url: https://\${{ env.DOCKER_IMAGE }}.yourdomain.com
307
+
308
+ steps:
309
+ - name: Deploy notification
310
+ run: |
311
+ echo "Deploying \${{ env.DOCKER_IMAGE }} to ${options.environment || "production"}"
312
+ echo "Image: \${{ secrets.DOCKER_USERNAME }}/\${{ env.DOCKER_IMAGE }}:latest"
313
+
314
+ # Add your deployment steps here:
315
+ # - Deploy to Kubernetes
316
+ # - Deploy to AWS ECS
317
+ # - Deploy to Railway
318
+ # - Deploy to Fly.io
319
+ # - etc.
320
+
321
+ - name: Deployment placeholder
322
+ run: |
323
+ echo "⚠️ Add your deployment commands here"
324
+ echo "Examples:"
325
+ echo " - kubectl apply -f k8s/"
326
+ echo " - aws ecs update-service ..."
327
+ echo " - railway up"
328
+ `;
329
+ return workflow;
330
+ }
331
+ // ==================== GITLAB CI ====================
332
+ async function generateGitLabCI(cwd, options, analysis) {
333
+ const workflow = generateGitLabCIConfig(options, analysis);
334
+ fs_1.default.writeFileSync(path_1.default.join(cwd, ".gitlab-ci.yml"), workflow, "utf-8");
335
+ console.log(chalk_1.default.green(` ✓ Created .gitlab-ci.yml`));
336
+ }
337
+ function generateGitLabCIConfig(options, analysis) {
338
+ const nodeVersion = analysis?.nodeVersion || "20";
339
+ const packageManager = analysis?.packageManager || "npm";
340
+ const includeSecurityScans = options.includeSecurityScans !== false;
341
+ return `# GitLab CI/CD Pipeline
342
+ # Generated by ExpressoTS CLI
343
+
344
+ image: node:${nodeVersion}-alpine
345
+
346
+ variables:
347
+ DOCKER_IMAGE: expressots-app
348
+ DOCKER_DRIVER: overlay2
349
+
350
+ stages:
351
+ - lint
352
+ - test
353
+ - security
354
+ - build
355
+ - deploy
356
+
357
+ cache:
358
+ paths:
359
+ - node_modules/
360
+ - .${packageManager}/
361
+
362
+ # Lint Job
363
+ lint:
364
+ stage: lint
365
+ script:
366
+ - ${getInstallCommand(packageManager)}
367
+ - ${packageManager} run lint
368
+ rules:
369
+ - if: $CI_MERGE_REQUEST_ID
370
+ - if: $CI_COMMIT_BRANCH
371
+
372
+ # Test Job
373
+ test:
374
+ stage: test
375
+ script:
376
+ - ${getInstallCommand(packageManager)}
377
+ - ${packageManager} run test
378
+ - ${packageManager} run coverage || true
379
+ coverage: '/Statements\\s*:\\s*(\\d+\\.\\d+)%/'
380
+ artifacts:
381
+ reports:
382
+ coverage_report:
383
+ coverage_format: cobertura
384
+ path: coverage/cobertura-coverage.xml
385
+ rules:
386
+ - if: $CI_MERGE_REQUEST_ID
387
+ - if: $CI_COMMIT_BRANCH
388
+
389
+ ${includeSecurityScans
390
+ ? `# Security Scan Job
391
+ security:
392
+ stage: security
393
+ image: aquasec/trivy:latest
394
+ script:
395
+ - trivy fs --severity HIGH,CRITICAL .
396
+ allow_failure: true
397
+ rules:
398
+ - if: $CI_MERGE_REQUEST_ID
399
+ - if: $CI_COMMIT_BRANCH
400
+
401
+ dependency-scanning:
402
+ stage: security
403
+ script:
404
+ - ${getInstallCommand(packageManager)}
405
+ - ${packageManager} audit --audit-level=moderate
406
+ allow_failure: true
407
+ rules:
408
+ - if: $CI_MERGE_REQUEST_ID
409
+ - if: $CI_COMMIT_BRANCH
410
+ `
411
+ : ""}
412
+
413
+ # Build Docker Image
414
+ build:
415
+ stage: build
416
+ image: docker:latest
417
+ services:
418
+ - docker:dind
419
+ before_script:
420
+ - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
421
+ script:
422
+ - docker build -t $DOCKER_USERNAME/$DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA .
423
+ - docker tag $DOCKER_USERNAME/$DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA $DOCKER_USERNAME/$DOCKER_IMAGE:latest
424
+ - docker push $DOCKER_USERNAME/$DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA
425
+ - docker push $DOCKER_USERNAME/$DOCKER_IMAGE:latest
426
+ only:
427
+ - main
428
+ - develop
429
+
430
+ # Deploy Job
431
+ deploy:
432
+ stage: deploy
433
+ image: alpine:latest
434
+ script:
435
+ - echo "Deploying $DOCKER_IMAGE to ${options.environment || "production"}"
436
+ - echo "⚠️ Add your deployment commands here"
437
+ environment:
438
+ name: ${options.environment || "production"}
439
+ url: https://expressots-app.yourdomain.com
440
+ only:
441
+ - main
442
+ `;
443
+ }
444
+ // ==================== CIRCLECI ====================
445
+ async function generateCircleCI(cwd, options, analysis) {
446
+ const circleCIDir = path_1.default.join(cwd, ".circleci");
447
+ if (!fs_1.default.existsSync(circleCIDir)) {
448
+ fs_1.default.mkdirSync(circleCIDir, { recursive: true });
449
+ }
450
+ const config = generateCircleCIConfig(options, analysis);
451
+ fs_1.default.writeFileSync(path_1.default.join(circleCIDir, "config.yml"), config, "utf-8");
452
+ console.log(chalk_1.default.green(` ✓ Created .circleci/config.yml`));
453
+ }
454
+ function generateCircleCIConfig(options, analysis) {
455
+ const nodeVersion = analysis?.nodeVersion || "20";
456
+ const packageManager = analysis?.packageManager || "npm";
457
+ return `# CircleCI Configuration
458
+ # Generated by ExpressoTS CLI
459
+
460
+ version: 2.1
461
+
462
+ orbs:
463
+ node: circleci/node@5.1.0
464
+ docker: circleci/docker@2.4.0
465
+
466
+ executors:
467
+ node-executor:
468
+ docker:
469
+ - image: cimg/node:${nodeVersion}
470
+
471
+ jobs:
472
+ lint:
473
+ executor: node-executor
474
+ steps:
475
+ - checkout
476
+ - node/install-packages:
477
+ pkg-manager: ${packageManager}
478
+ - run:
479
+ name: Run linter
480
+ command: ${packageManager} run lint
481
+
482
+ test:
483
+ executor: node-executor
484
+ steps:
485
+ - checkout
486
+ - node/install-packages:
487
+ pkg-manager: ${packageManager}
488
+ - run:
489
+ name: Run tests
490
+ command: ${packageManager} run test
491
+ - run:
492
+ name: Generate coverage
493
+ command: ${packageManager} run coverage || true
494
+ - store_test_results:
495
+ path: ./coverage
496
+ - store_artifacts:
497
+ path: ./coverage
498
+
499
+ build-and-push:
500
+ executor: docker/docker
501
+ steps:
502
+ - checkout
503
+ - setup_remote_docker:
504
+ version: 20.10.24
505
+ - docker/check
506
+ - docker/build:
507
+ image: expressots-app
508
+ tag: latest,$CIRCLE_SHA1
509
+ - docker/push:
510
+ image: expressots-app
511
+ tag: latest,$CIRCLE_SHA1
512
+
513
+ deploy:
514
+ executor: node-executor
515
+ steps:
516
+ - checkout
517
+ - run:
518
+ name: Deploy application
519
+ command: |
520
+ echo "Deploying to ${options.environment || "production"}"
521
+ echo "⚠️ Add your deployment commands here"
522
+
523
+ workflows:
524
+ version: 2
525
+ build-test-deploy:
526
+ jobs:
527
+ - lint
528
+ - test:
529
+ requires:
530
+ - lint
531
+ - build-and-push:
532
+ requires:
533
+ - test
534
+ filters:
535
+ branches:
536
+ only:
537
+ - main
538
+ - develop
539
+ - deploy:
540
+ requires:
541
+ - build-and-push
542
+ filters:
543
+ branches:
544
+ only: main
545
+ `;
546
+ }
547
+ // ==================== JENKINS ====================
548
+ async function generateJenkinsfile(cwd, options, analysis) {
549
+ const config = generateJenkinsfileConfig(options, analysis);
550
+ fs_1.default.writeFileSync(path_1.default.join(cwd, "Jenkinsfile"), config, "utf-8");
551
+ console.log(chalk_1.default.green(` ✓ Created Jenkinsfile`));
552
+ }
553
+ function generateJenkinsfileConfig(options, analysis) {
554
+ const nodeVersion = analysis?.nodeVersion || "20";
555
+ const packageManager = analysis?.packageManager || "npm";
556
+ return `// Jenkinsfile
557
+ // Generated by ExpressoTS CLI
558
+
559
+ pipeline {
560
+ agent any
561
+
562
+ environment {
563
+ NODE_VERSION = '${nodeVersion}'
564
+ DOCKER_IMAGE = 'expressots-app'
565
+ DOCKER_REGISTRY = credentials('docker-hub-credentials')
566
+ }
567
+
568
+ stages {
569
+ stage('Checkout') {
570
+ steps {
571
+ checkout scm
572
+ }
573
+ }
574
+
575
+ stage('Setup') {
576
+ steps {
577
+ sh 'node --version'
578
+ sh '${getInstallCommand(packageManager)}'
579
+ }
580
+ }
581
+
582
+ stage('Lint') {
583
+ steps {
584
+ sh '${packageManager} run lint'
585
+ }
586
+ }
587
+
588
+ stage('Test') {
589
+ steps {
590
+ sh '${packageManager} run test'
591
+ sh '${packageManager} run coverage || true'
592
+ }
593
+ post {
594
+ always {
595
+ junit 'coverage/junit.xml'
596
+ publishHTML([
597
+ reportDir: 'coverage',
598
+ reportFiles: 'index.html',
599
+ reportName: 'Coverage Report'
600
+ ])
601
+ }
602
+ }
603
+ }
604
+
605
+ stage('Security Scan') {
606
+ steps {
607
+ sh '${packageManager} audit --audit-level=moderate || true'
608
+ }
609
+ }
610
+
611
+ stage('Build Docker Image') {
612
+ when {
613
+ branch 'main'
614
+ }
615
+ steps {
616
+ script {
617
+ docker.build("\${DOCKER_IMAGE}:\${BUILD_NUMBER}")
618
+ docker.build("\${DOCKER_IMAGE}:latest")
619
+ }
620
+ }
621
+ }
622
+
623
+ stage('Push Docker Image') {
624
+ when {
625
+ branch 'main'
626
+ }
627
+ steps {
628
+ script {
629
+ docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') {
630
+ docker.image("\${DOCKER_IMAGE}:\${BUILD_NUMBER}").push()
631
+ docker.image("\${DOCKER_IMAGE}:latest").push()
632
+ }
633
+ }
634
+ }
635
+ }
636
+
637
+ stage('Deploy') {
638
+ when {
639
+ branch 'main'
640
+ }
641
+ steps {
642
+ echo 'Deploying to ${options.environment || "production"}'
643
+ echo '⚠️ Add your deployment commands here'
644
+ // sh 'kubectl apply -f k8s/'
645
+ // sh 'helm upgrade --install myapp ./helm-chart'
646
+ }
647
+ }
648
+ }
649
+
650
+ post {
651
+ success {
652
+ echo 'Pipeline completed successfully!'
653
+ }
654
+ failure {
655
+ echo 'Pipeline failed!'
656
+ }
657
+ always {
658
+ cleanWs()
659
+ }
660
+ }
661
+ }
662
+ `;
663
+ }
664
+ // ==================== BITBUCKET PIPELINES ====================
665
+ async function generateBitbucketPipelines(cwd, options, analysis) {
666
+ const config = generateBitbucketPipelinesConfig(options, analysis);
667
+ fs_1.default.writeFileSync(path_1.default.join(cwd, "bitbucket-pipelines.yml"), config, "utf-8");
668
+ console.log(chalk_1.default.green(` ✓ Created bitbucket-pipelines.yml`));
669
+ }
670
+ function generateBitbucketPipelinesConfig(options, analysis) {
671
+ const nodeVersion = analysis?.nodeVersion || "20";
672
+ const packageManager = analysis?.packageManager || "npm";
673
+ return `# Bitbucket Pipelines Configuration
674
+ # Generated by ExpressoTS CLI
675
+
676
+ image: node:${nodeVersion}-alpine
677
+
678
+ definitions:
679
+ caches:
680
+ npm-cache: ~/.npm
681
+
682
+ steps:
683
+ - step: &lint
684
+ name: Lint
685
+ caches:
686
+ - npm-cache
687
+ script:
688
+ - ${getInstallCommand(packageManager)}
689
+ - ${packageManager} run lint
690
+
691
+ - step: &test
692
+ name: Test
693
+ caches:
694
+ - npm-cache
695
+ script:
696
+ - ${getInstallCommand(packageManager)}
697
+ - ${packageManager} run test
698
+ - ${packageManager} run coverage || true
699
+ artifacts:
700
+ - coverage/**
701
+
702
+ - step: &build-docker
703
+ name: Build and Push Docker Image
704
+ services:
705
+ - docker
706
+ script:
707
+ - docker build -t \${DOCKER_USERNAME}/expressots-app:\${BITBUCKET_COMMIT} .
708
+ - docker tag \${DOCKER_USERNAME}/expressots-app:\${BITBUCKET_COMMIT} \${DOCKER_USERNAME}/expressots-app:latest
709
+ - echo \${DOCKER_PASSWORD} | docker login -u \${DOCKER_USERNAME} --password-stdin
710
+ - docker push \${DOCKER_USERNAME}/expressots-app:\${BITBUCKET_COMMIT}
711
+ - docker push \${DOCKER_USERNAME}/expressots-app:latest
712
+
713
+ - step: &deploy
714
+ name: Deploy
715
+ deployment: ${options.environment || "production"}
716
+ script:
717
+ - echo "Deploying to ${options.environment || "production"}"
718
+ - echo "⚠️ Add your deployment commands here"
719
+
720
+ pipelines:
721
+ default:
722
+ - step: *lint
723
+ - step: *test
724
+
725
+ branches:
726
+ main:
727
+ - step: *lint
728
+ - step: *test
729
+ - step: *build-docker
730
+ - step: *deploy
731
+
732
+ develop:
733
+ - step: *lint
734
+ - step: *test
735
+ - step: *build-docker
736
+
737
+ pull-requests:
738
+ '**':
739
+ - step: *lint
740
+ - step: *test
741
+ `;
742
+ }
743
+ // ==================== AZURE DEVOPS ====================
744
+ async function generateAzureDevOps(cwd, options, analysis) {
745
+ const config = generateAzureDevOpsConfig(options, analysis);
746
+ fs_1.default.writeFileSync(path_1.default.join(cwd, "azure-pipelines.yml"), config, "utf-8");
747
+ console.log(chalk_1.default.green(` ✓ Created azure-pipelines.yml`));
748
+ }
749
+ function generateAzureDevOpsConfig(options, analysis) {
750
+ const nodeVersion = analysis?.nodeVersion || "20";
751
+ const packageManager = analysis?.packageManager || "npm";
752
+ const includeSecurityScans = options.includeSecurityScans !== false;
753
+ const azureNodeVersion = nodeVersion;
754
+ const azurePackageManager = packageManager;
755
+ const azureEnv = options.environment || "production";
756
+ // Use string concatenation to avoid ESLint escape issues with Azure DevOps $(var) syntax
757
+ const vmImage = "$(vmImageName)";
758
+ const nodeVer = "$(nodeVersion)";
759
+ const sysDir = "$(System.DefaultWorkingDirectory)";
760
+ const dockerReg = "$(dockerRegistryServiceConnection)";
761
+ const dockerImg = "$(dockerImage)";
762
+ const buildId = "$(Build.BuildId)";
763
+ let content = `# Azure DevOps Pipeline
764
+ # Generated by ExpressoTS CLI
765
+
766
+ trigger:
767
+ branches:
768
+ include:
769
+ - main
770
+ - develop
771
+
772
+ pr:
773
+ branches:
774
+ include:
775
+ - main
776
+
777
+ variables:
778
+ nodeVersion: '${azureNodeVersion}'
779
+ dockerImage: 'expressots-app'
780
+ vmImageName: 'ubuntu-latest'
781
+
782
+ stages:
783
+ - stage: Build
784
+ displayName: 'Build & Test'
785
+ jobs:
786
+ - job: Lint
787
+ displayName: 'Lint Code'
788
+ pool:
789
+ vmImage: ${vmImage}
790
+ steps:
791
+ - task: NodeTool@0
792
+ displayName: 'Install Node.js'
793
+ inputs:
794
+ versionSpec: '${nodeVer}'
795
+
796
+ - script: ${getInstallCommand(azurePackageManager)}
797
+ displayName: 'Install dependencies'
798
+
799
+ - script: ${azurePackageManager} run lint
800
+ displayName: 'Run linter'
801
+
802
+ - job: Test
803
+ displayName: 'Run Tests'
804
+ dependsOn: Lint
805
+ pool:
806
+ vmImage: ${vmImage}
807
+ steps:
808
+ - task: NodeTool@0
809
+ displayName: 'Install Node.js'
810
+ inputs:
811
+ versionSpec: '${nodeVer}'
812
+
813
+ - script: ${getInstallCommand(azurePackageManager)}
814
+ displayName: 'Install dependencies'
815
+
816
+ - script: ${azurePackageManager} run test
817
+ displayName: 'Run tests'
818
+
819
+ - script: ${azurePackageManager} run coverage || true
820
+ displayName: 'Generate coverage'
821
+
822
+ - task: PublishCodeCoverageResults@1
823
+ displayName: 'Publish code coverage'
824
+ inputs:
825
+ codeCoverageTool: 'Cobertura'
826
+ summaryFileLocation: '${sysDir}/coverage/cobertura-coverage.xml'
827
+ continueOnError: true
828
+
829
+ `;
830
+ if (includeSecurityScans) {
831
+ content += ` - job: Security
832
+ displayName: 'Security Scan'
833
+ dependsOn: Lint
834
+ pool:
835
+ vmImage: ${vmImage}
836
+ steps:
837
+ - task: NodeTool@0
838
+ displayName: 'Install Node.js'
839
+ inputs:
840
+ versionSpec: '${nodeVer}'
841
+
842
+ - script: ${getInstallCommand(azurePackageManager)}
843
+ displayName: 'Install dependencies'
844
+
845
+ - script: ${azurePackageManager} audit --audit-level=high || true
846
+ displayName: 'npm audit'
847
+ `;
848
+ }
849
+ content += ` - job: Build
850
+ displayName: 'Build Application'
851
+ dependsOn: Test
852
+ pool:
853
+ vmImage: ${vmImage}
854
+ steps:
855
+ - task: NodeTool@0
856
+ displayName: 'Install Node.js'
857
+ inputs:
858
+ versionSpec: '${nodeVer}'
859
+
860
+ - script: ${getInstallCommand(azurePackageManager)}
861
+ displayName: 'Install dependencies'
862
+
863
+ - script: ${azurePackageManager} run build
864
+ displayName: 'Build application'
865
+
866
+ - task: PublishBuildArtifacts@1
867
+ displayName: 'Publish artifacts'
868
+ inputs:
869
+ pathToPublish: 'dist'
870
+ artifactName: 'dist'
871
+
872
+ - stage: Docker
873
+ displayName: 'Docker Build & Push'
874
+ dependsOn: Build
875
+ condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
876
+ jobs:
877
+ - job: DockerBuild
878
+ displayName: 'Build & Push Docker Image'
879
+ pool:
880
+ vmImage: ${vmImage}
881
+ steps:
882
+ - task: Docker@2
883
+ displayName: 'Build Docker image'
884
+ inputs:
885
+ containerRegistry: '${dockerReg}'
886
+ repository: '${dockerImg}'
887
+ command: 'build'
888
+ Dockerfile: '**/Dockerfile'
889
+ tags: |
890
+ ${buildId}
891
+ latest
892
+
893
+ - task: Docker@2
894
+ displayName: 'Push Docker image'
895
+ inputs:
896
+ containerRegistry: '${dockerReg}'
897
+ repository: '${dockerImg}'
898
+ command: 'push'
899
+ tags: |
900
+ ${buildId}
901
+ latest
902
+
903
+ - stage: Deploy
904
+ displayName: 'Deploy'
905
+ dependsOn: Docker
906
+ condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
907
+ jobs:
908
+ - deployment: Deploy
909
+ displayName: 'Deploy to ${azureEnv}'
910
+ pool:
911
+ vmImage: ${vmImage}
912
+ environment: '${azureEnv}'
913
+ strategy:
914
+ runOnce:
915
+ deploy:
916
+ steps:
917
+ - script: |
918
+ echo "Deploying to ${azureEnv}"
919
+ echo "Add your deployment commands here"
920
+ displayName: 'Deploy'
921
+ `;
922
+ return content;
923
+ }
924
+ // ==================== HELPER FUNCTIONS ====================
925
+ /**
926
+ * Local alias kept to minimize template diff churn; delegates to the
927
+ * shared `getCiInstallCommand` helper so the install command stays
928
+ * consistent with what `cicd init` emits.
929
+ */
930
+ function getInstallCommand(packageManager) {
931
+ return (0, package_manager_commands_1.getCiInstallCommand)(packageManager);
932
+ }
933
+ // `getRunScriptCommand` is exposed by the shared helper module — we
934
+ // re-export to keep call sites inside this file short. Marked as
935
+ // "used" so eslint/tsc don't complain about the import.
936
+ void package_manager_commands_1.getRunScriptCommand;