@expressots/cli 3.0.0 → 4.0.0-preview.3

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