@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.
- package/README.md +41 -95
- package/bin/cicd/cli.d.ts +6 -0
- package/bin/cicd/cli.js +126 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +345 -0
- package/bin/cicd/generators/azure-devops.d.ts +2 -0
- package/bin/cicd/generators/azure-devops.js +370 -0
- package/bin/cicd/generators/bitbucket.d.ts +2 -0
- package/bin/cicd/generators/bitbucket.js +217 -0
- package/bin/cicd/generators/circleci.d.ts +2 -0
- package/bin/cicd/generators/circleci.js +274 -0
- package/bin/cicd/generators/github-actions.d.ts +14 -0
- package/bin/cicd/generators/github-actions.js +426 -0
- package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
- package/bin/cicd/generators/gitlab-ci.js +237 -0
- package/bin/cicd/generators/index.d.ts +6 -0
- package/bin/cicd/generators/index.js +15 -0
- package/bin/cicd/generators/jenkins.d.ts +2 -0
- package/bin/cicd/generators/jenkins.js +248 -0
- package/bin/cicd/generators/template-loader.d.ts +17 -0
- package/bin/cicd/generators/template-loader.js +128 -0
- package/bin/cicd/index.d.ts +1 -0
- package/bin/cicd/index.js +5 -0
- package/bin/cli.d.ts +1 -1
- package/bin/cli.js +18 -3
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +390 -61
- package/bin/config/index.d.ts +5 -0
- package/bin/config/index.js +10 -0
- package/bin/config/manager.d.ts +98 -0
- package/bin/config/manager.js +222 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
- package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
- package/bin/containerize/analyzers/project-analyzer.js +150 -0
- package/bin/containerize/cli.d.ts +4 -0
- package/bin/containerize/cli.js +113 -0
- package/bin/containerize/form.d.ts +15 -0
- package/bin/containerize/form.js +154 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +936 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +186 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +635 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +133 -0
- package/bin/containerize/generators/template-loader.d.ts +36 -0
- package/bin/containerize/generators/template-loader.js +129 -0
- package/bin/containerize/index.d.ts +4 -0
- package/bin/containerize/index.js +13 -0
- package/bin/containerize/presets/preset-registry.d.ts +20 -0
- package/bin/containerize/presets/preset-registry.js +102 -0
- package/bin/costs/cli.d.ts +5 -0
- package/bin/costs/cli.js +183 -0
- package/bin/costs/form.d.ts +44 -0
- package/bin/costs/form.js +412 -0
- package/bin/costs/index.d.ts +4 -0
- package/bin/costs/index.js +25 -0
- package/bin/costs/pricing-manager.d.ts +84 -0
- package/bin/costs/pricing-manager.js +342 -0
- package/bin/costs/providers/index.d.ts +32 -0
- package/bin/costs/providers/index.js +153 -0
- package/bin/costs/sources/api-source.d.ts +10 -0
- package/bin/costs/sources/api-source.js +32 -0
- package/bin/costs/sources/index.d.ts +6 -0
- package/bin/costs/sources/index.js +15 -0
- package/bin/costs/sources/local-json-source.d.ts +23 -0
- package/bin/costs/sources/local-json-source.js +59 -0
- package/bin/costs/sources/remote-json-source.d.ts +11 -0
- package/bin/costs/sources/remote-json-source.js +53 -0
- package/bin/costs/types.d.ts +53 -0
- package/bin/costs/types.js +5 -0
- package/bin/dev/cli.d.ts +4 -0
- package/bin/dev/cli.js +134 -0
- package/bin/dev/form.d.ts +36 -0
- package/bin/dev/form.js +254 -0
- package/bin/dev/index.d.ts +1 -0
- package/bin/dev/index.js +5 -0
- package/bin/generate/cli.js +29 -2
- package/bin/generate/form.d.ts +5 -1
- package/bin/generate/form.js +3 -3
- package/bin/generate/templates/nonopinionated/config.tpl +12 -0
- package/bin/generate/templates/nonopinionated/event.tpl +10 -0
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
- package/bin/generate/templates/opinionated/config.tpl +47 -0
- package/bin/generate/templates/opinionated/entity.tpl +1 -8
- package/bin/generate/templates/opinionated/event.tpl +15 -0
- package/bin/generate/templates/opinionated/guard.tpl +41 -0
- package/bin/generate/templates/opinionated/handler.tpl +23 -0
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
- package/bin/generate/utils/command-utils.d.ts +7 -3
- package/bin/generate/utils/command-utils.js +95 -31
- package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
- package/bin/generate/utils/nonopininated-cmd.js +100 -1
- package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
- package/bin/generate/utils/opinionated-cmd.js +112 -7
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- package/bin/help/form.js +11 -3
- package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
- package/bin/migrate/analyzers/platform-detector.js +116 -0
- package/bin/migrate/cli.d.ts +6 -0
- package/bin/migrate/cli.js +96 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +347 -0
- package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
- package/bin/migrate/generators/compose-to-k8s.js +324 -0
- package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
- package/bin/migrate/generators/compose-to-railway.js +138 -0
- package/bin/migrate/generators/compose-to-render.d.ts +2 -0
- package/bin/migrate/generators/compose-to-render.js +148 -0
- package/bin/migrate/generators/generic-migration.d.ts +9 -0
- package/bin/migrate/generators/generic-migration.js +221 -0
- package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-fly.js +291 -0
- package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-railway.js +283 -0
- package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-render.js +148 -0
- package/bin/migrate/generators/index.d.ts +7 -0
- package/bin/migrate/generators/index.js +17 -0
- package/bin/migrate/generators/template-loader.d.ts +21 -0
- package/bin/migrate/generators/template-loader.js +59 -0
- package/bin/migrate/index.d.ts +1 -0
- package/bin/migrate/index.js +5 -0
- package/bin/new/cli.js +21 -6
- package/bin/new/form.d.ts +25 -4
- package/bin/new/form.js +285 -70
- package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
- package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
- package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
- package/bin/profile/analyzers/image-analyzer.js +85 -0
- package/bin/profile/cli.d.ts +4 -0
- package/bin/profile/cli.js +92 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +400 -0
- package/bin/profile/index.d.ts +1 -0
- package/bin/profile/index.js +5 -0
- package/bin/profile/optimizers/index.d.ts +19 -0
- package/bin/profile/optimizers/index.js +137 -0
- package/bin/providers/add/form.d.ts +1 -1
- package/bin/providers/add/form.js +27 -6
- package/bin/providers/create/form.js +2 -1
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +166 -0
- package/bin/studio/index.d.ts +5 -0
- package/bin/studio/index.js +9 -0
- package/bin/templates/cache.d.ts +54 -0
- package/bin/templates/cache.js +180 -0
- package/bin/templates/cli.d.ts +8 -0
- package/bin/templates/cli.js +292 -0
- package/bin/templates/fetcher.d.ts +49 -0
- package/bin/templates/fetcher.js +208 -0
- package/bin/templates/index.d.ts +11 -0
- package/bin/templates/index.js +37 -0
- package/bin/templates/manager.d.ts +116 -0
- package/bin/templates/manager.js +323 -0
- package/bin/templates/renderer.d.ts +49 -0
- package/bin/templates/renderer.js +204 -0
- package/bin/templates/types.d.ts +51 -0
- package/bin/templates/types.js +5 -0
- package/bin/utils/add-module-to-container.d.ts +2 -2
- package/bin/utils/add-module-to-container.js +15 -5
- package/bin/utils/cli-ui.d.ts +30 -3
- package/bin/utils/cli-ui.js +95 -13
- package/bin/utils/index.d.ts +4 -0
- package/bin/utils/index.js +4 -0
- package/bin/utils/input-validation.d.ts +50 -0
- package/bin/utils/input-validation.js +143 -0
- package/bin/utils/package-manager-commands.d.ts +24 -0
- package/bin/utils/package-manager-commands.js +50 -0
- package/bin/utils/safe-spawn.d.ts +35 -0
- package/bin/utils/safe-spawn.js +51 -0
- package/bin/utils/update-tsconfig-paths.d.ts +35 -0
- package/bin/utils/update-tsconfig-paths.js +286 -0
- 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;
|