This repository was archived by the owner on Nov 14, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
376 lines (323 loc) · 13.3 KB
/
main.yml
File metadata and controls
376 lines (323 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# =============================================================================
# WORKFLOW: Main Branch Release Pipeline
# PURPOSE: Automate version management, releases, and security scanning on main
# TRIGGERS: Push to main branch (merges, direct commits)
# OUTPUTS: GitHub release with artifacts, NPM package, Docker image
# =============================================================================
name: Main
on:
push:
branches: [main]
# Prevent concurrent runs on the same ref to avoid race conditions during releases
# cancel-in-progress: false ensures releases complete even if new commits arrive
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
# SECURITY: Required permissions for release automation
# contents: write - Create releases and tags
# id-token: write - Generate SLSA attestations for supply chain security
# attestations: write - Attach attestations to artifacts
# security-events: write - Upload security scan results
# actions: read - Access workflow runs and artifacts
# packages: write - Push Docker images to GitHub Container Registry
permissions:
contents: write
id-token: write
attestations: write
security-events: write
actions: read
packages: write
jobs:
# =============================================================================
# VALIDATION PHASE
# Runs all quality checks in parallel to ensure code meets standards
# =============================================================================
validate:
# Reusable workflow handles: audit, typecheck, lint, format, tests
# FAILS IF: Any check fails, tests don't meet 80% coverage threshold
uses: ./.github/workflows/reusable-validate.yml
secrets:
DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }}
# =============================================================================
# SECURITY SCANNING PHASE
# Parallel security scans to identify vulnerabilities before release
# =============================================================================
# Scans TypeScript/JavaScript for common security issues (XSS, SQL injection, etc.)
security:
uses: ./.github/workflows/reusable-security.yml
# =============================================================================
# UNIFIED BUILD PHASE
# Single build job that creates artifacts to be reused throughout the workflow
# =============================================================================
build:
runs-on: ubuntu-latest
outputs:
artifact-name: dist-${{ github.sha }}
changed: ${{ steps.version.outputs.changed }}
version: ${{ steps.version.outputs.version }}
tag_sha: ${{ steps.tag.outputs.sha }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_TOKEN }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0
run_install: false
standalone: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Version packages
id: version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Custom script validates changesets and determines version
# FAILS IF: feat/fix commits exist without changesets
# Outputs: changed=true/false, version=X.Y.Z
node .github/scripts/version-and-release.js
- name: Commit version changes
if: steps.version.outputs.changed == 'true'
run: |
# Configure git with GitHub Actions bot identity
git config --local user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
git config --local user.name "${{ github.actor }}"
# Stage version-related changes
git add package.json CHANGELOG.md .changeset
# Commit with [skip actions] to prevent workflow recursion
git commit -m "chore(release): v${{ steps.version.outputs.version }} [skip actions]"
# Push changes to origin
git push origin main
echo "✅ Version changes committed and pushed"
- name: Create and push tag
# Create tag BEFORE building artifacts so they're associated with the tag
id: tag
if: steps.version.outputs.changed == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
# Configure git
git config --local user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
git config --local user.name "${{ github.actor }}"
# Create annotated tag
git tag -a "v${VERSION}" -m "Release v${VERSION}"
# Push tag to origin
git push origin "v${VERSION}"
# Get the tag SHA for artifact naming
TAG_SHA=$(git rev-list -n 1 "v${VERSION}")
echo "sha=${TAG_SHA}" >> $GITHUB_OUTPUT
echo "📌 Tag SHA for artifacts: ${TAG_SHA}"
- name: Build TypeScript
if: steps.version.outputs.changed == 'true'
run: |
pnpm build
echo "✅ Built TypeScript once for entire workflow"
- name: Generate artifact manifest
if: steps.version.outputs.changed == 'true'
run: |
# Create a manifest of what's been built
cat > build-manifest.json <<EOF
{
"build_sha": "${{ github.sha }}",
"build_time": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"node_version": "$(node --version)",
"pnpm_version": "$(pnpm --version)",
"typescript_version": "$(pnpm list typescript --json | jq -r '.dependencies.typescript.version')",
"files": $(find dist -type f -name "*.js" | jq -R . | jq -s .)
}
EOF
echo "📋 Generated build manifest with $(find dist -type f -name "*.js" | wc -l) JavaScript files"
- name: Upload build artifact
if: steps.version.outputs.changed == 'true'
uses: actions/upload-artifact@v4
with:
name: dist-${{ github.sha }}
path: |
dist/
package.json
pnpm-lock.yaml
build-manifest.json
retention-days: 1 # Only needed for this workflow run
# =============================================================================
# PREPARE RELEASE ASSETS PHASE
# Centralized job for preparing all release artifacts (Docker, binaries, etc.)
# =============================================================================
docker:
name: Build Docker Image
needs: [validate, security, build]
if: vars.ENABLE_DOCKER_RELEASE == 'true' && needs.build.outputs.changed == 'true'
uses: ./.github/workflows/reusable-docker.yml
with:
platforms: 'linux/amd64,linux/arm64'
save-artifact: true
artifact-name: 'docker-image-${{ needs.build.outputs.version }}'
image-name: 'deepsource-mcp-server'
version: ${{ needs.build.outputs.version }}
tag_sha: ${{ github.sha }}
build_artifact: ${{ needs.build.outputs.artifact-name }}
npm:
name: Prepare NPM Package
needs: [validate, security, build]
if: vars.ENABLE_NPM_RELEASE == 'true' && needs.build.outputs.changed == 'true'
runs-on: ubuntu-latest
outputs:
built: ${{ steps.pack.outputs.built }}
artifact_name: ${{ steps.pack.outputs.artifact_name }}
tarball_name: ${{ steps.pack.outputs.tarball_name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.build.outputs.artifact-name }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0
run_install: false
standalone: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
# Install all dependencies for packaging (dev and prod)
run: pnpm install --frozen-lockfile
- name: Create NPM package
id: pack
run: |
# Create the NPM package tarball
# Use tail -1 to get just the filename, as npm pack may output additional text
NPM_PACKAGE=$(npm pack 2>/dev/null | tail -1)
echo "📦 Created NPM package: $NPM_PACKAGE"
# Generate metadata using github.sha for consistent naming with publish workflow
ARTIFACT_NAME="npm-package-${{ needs.build.outputs.version }}-${{ github.sha }}"
{
echo "artifact_name=$ARTIFACT_NAME"
echo "tarball_name=$NPM_PACKAGE"
echo "built=true"
} >> $GITHUB_OUTPUT
# Create manifest of included files for verification
npm pack --dry-run --json 2>/dev/null | jq -r '.[0].files[].path' > npm-package-manifest.txt
echo "📋 Package contains $(wc -l < npm-package-manifest.txt) files"
- name: Upload NPM package artifact
uses: actions/upload-artifact@v4
with:
name: npm-package-${{ needs.build.outputs.version }}-${{ github.sha }}
path: |
*.tgz
npm-package-manifest.txt
retention-days: 7
- name: Generate attestations for NPM package
uses: actions/attest-build-provenance@v2
with:
subject-path: '*.tgz'
# =============================================================================
# GITHUB RELEASE CREATION PHASE
# Creates GitHub release as the final step after version is committed
# =============================================================================
create-release:
name: Create GitHub Release
needs: [build, docker, npm]
# Run if build succeeded AND docker/npm either succeeded or were skipped
if: |
needs.build.outputs.changed == 'true' &&
!cancelled() &&
(needs.docker.result == 'success' || needs.docker.result == 'skipped') &&
(needs.npm.result == 'success' || needs.npm.result == 'skipped')
runs-on: ubuntu-latest
outputs:
released: ${{ steps.release.outputs.released }}
version: ${{ needs.build.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Checkout the newly created tag
ref: v${{ needs.build.outputs.version }}
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.build.outputs.artifact-name }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0
run_install: false
standalone: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
# Only production dependencies needed for SBOM generation
# Skip scripts to avoid running husky (dev dependency)
run: pnpm install --prod --frozen-lockfile --ignore-scripts
- name: Generate SBOM
run: pnpm sbom
- name: Create release artifacts
run: |
VERSION="${{ needs.build.outputs.version }}"
TAG_SHA="${{ needs.build.outputs.tag_sha }}"
tar -czf dist-${VERSION}-${TAG_SHA:0:7}.tar.gz dist/
zip -r dist-${VERSION}-${TAG_SHA:0:7}.zip dist/
- name: Extract release notes
run: |
VERSION="${{ needs.build.outputs.version }}"
awk -v version="## $VERSION" '
$0 ~ version { flag=1; next }
/^## [0-9]/ && flag { exit }
flag { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "Release v$VERSION" > release-notes.md
fi
# =============================================================================
# SUPPLY CHAIN SECURITY
# Generate attestations BEFORE creating release to avoid race condition
# This ensures the Main workflow is complete before triggering Publish workflow
# =============================================================================
- name: Generate attestations
# Generate SLSA provenance attestations for supply chain security
# Requires id-token: write permission
uses: actions/attest-build-provenance@v2
with:
subject-path: |
dist/**/*.js
sbom.cdx.json
dist-*-*.tar.gz
dist-*-*.zip
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.build.outputs.version }}
name: v${{ needs.build.outputs.version }}
body_path: release-notes.md
draft: false
prerelease: false
make_latest: true
files: |
sbom.cdx.json
dist-${{ needs.build.outputs.version }}-*.tar.gz
dist-${{ needs.build.outputs.version }}-*.zip
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
- name: Set release output
id: release
run: |
echo "released=true" >> $GITHUB_OUTPUT
echo "✅ Released version ${{ needs.build.outputs.version }}"