Build, Attest and Release #154
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build, Attest and Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to release (vX.Y.Z format)" | |
| required: true | |
| default: "v0.1.0" | |
| prerelease: | |
| description: "Is this a pre-release?" | |
| type: boolean | |
| default: false | |
| # Restrict top-level permissions to minimum required defaults | |
| permissions: read-all | |
| # Resilience against transient npm registry / mirror failures. | |
| # Applied to every job; npm honours these env vars natively. | |
| env: | |
| NPM_CONFIG_FETCH_RETRIES: "5" | |
| NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "20000" | |
| NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "120000" | |
| NPM_CONFIG_FETCH_TIMEOUT: "300000" | |
| jobs: | |
| prepare: | |
| name: Prepare Release | |
| runs-on: ubuntu-26.04 | |
| # Only prepare job needs write permissions for commit and tagging | |
| permissions: | |
| contents: write # Required for git auto-commit | |
| outputs: | |
| version: ${{ steps.get-version.outputs.version }} | |
| is_prerelease: ${{ github.event.inputs.prerelease || 'false' }} | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 | |
| with: | |
| egress-policy: audit | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ vars.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| persist-credentials: true | |
| fetch-depth: 0 | |
| - name: Get version | |
| id: get-version | |
| run: | | |
| if [[ "${{ github.event_name }}" == "push" ]]; then | |
| VERSION=${GITHUB_REF#refs/tags/} | |
| else | |
| VERSION=${{ github.event.inputs.version }} | |
| fi | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "Version: ${VERSION}" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "26" | |
| # Built-in npm cache (no extra actions/cache step needed). | |
| # Cache key is automatically derived from package-lock.json + Node major version. | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| # TypeScript compilation check | |
| - name: TypeScript compilation check | |
| run: npm run build || npx tsc --noEmit | |
| # Run tests if available | |
| - name: Run tests | |
| run: npm test || echo "No tests configured yet" | |
| continue-on-error: true | |
| - name: Set Version for release | |
| if: github.event_name == 'workflow_dispatch' | |
| run: | | |
| PLAIN_VERSION="${{ github.event.inputs.version }}" | |
| # Remove 'v' prefix if present | |
| PLAIN_VERSION="${PLAIN_VERSION#v}" | |
| npm version $PLAIN_VERSION --no-git-tag-version | |
| - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 | |
| if: github.event_name == 'workflow_dispatch' | |
| with: | |
| commit_message: "chore(release): bump version to ${{ github.event.inputs.version }}" | |
| tagging_message: "${{ github.event.inputs.version }}" | |
| commit_user_name: "hack23-automation[bot]" | |
| commit_user_email: "304996498+hack23-automation[bot]@users.noreply.github.com" | |
| # Documentation as Code - Generate all documentation for this release | |
| - name: Clean old documentation | |
| run: npm run docs:clean || echo "First time generating docs" | |
| - name: Generate API documentation with TypeDoc (HTML) | |
| run: npm run docs | |
| - name: Generate API documentation with TypeDoc (Markdown) | |
| run: npm run docs:md | |
| - name: Run tests with coverage and generate reports | |
| run: | | |
| npm run test:coverage || true | |
| mkdir -p docs/coverage | |
| if [ -d "coverage" ] && [ "$(ls -A coverage)" ]; then | |
| cp -r coverage/* docs/coverage/ | |
| else | |
| echo "Warning: No coverage files to copy" | |
| fi | |
| - name: Generate unit test reports | |
| run: | | |
| mkdir -p builds/test-results docs/test-results | |
| npm run test:unit -- --reporter=default --reporter=html --reporter=json --outputFile.html=./builds/test-results/index.html --outputFile.json=./builds/test-results/results.json || true | |
| if [ -d "builds/test-results" ] && [ "$(ls -A builds/test-results 2>/dev/null)" ]; then | |
| cp -r builds/test-results/* docs/test-results/ | |
| fi | |
| echo "Unit test reports generated at $(date -u +%Y-%m-%dT%H:%M:%SZ)" > docs/test-results/status.txt | |
| - name: Generate E2E test reports | |
| run: | | |
| mkdir -p builds/e2e-results docs/e2e-results | |
| npm run test:e2e -- --reporter=default --reporter=html --reporter=json --outputFile.html=./builds/e2e-results/index.html --outputFile.json=./builds/e2e-results/results.json || true | |
| if [ -d "builds/e2e-results" ] && [ "$(ls -A builds/e2e-results 2>/dev/null)" ]; then | |
| cp -r builds/e2e-results/* docs/e2e-results/ | |
| fi | |
| echo "E2E test reports generated at $(date -u +%Y-%m-%dT%H:%M:%SZ)" > docs/e2e-results/status.txt | |
| - name: Generate documentation sitemap | |
| run: npm run docs:sitemap | |
| - name: Create version marker | |
| run: echo "Version ${{ needs.prepare.outputs.version || github.ref_name }} deployed at $(date -u +%Y-%m-%dT%H:%M:%SZ)" > docs/version.txt | |
| - name: Commit documentation to main branch | |
| uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 | |
| with: | |
| commit_message: "docs: update documentation for ${{ steps.get-version.outputs.version || github.ref_name }}" | |
| commit_user_name: "hack23-automation[bot]" | |
| commit_user_email: "304996498+hack23-automation[bot]@users.noreply.github.com" | |
| file_pattern: "docs/**" | |
| skip_fetch: false | |
| skip_checkout: false | |
| - name: Deploy Documentation to GitHub Pages | |
| uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0 | |
| with: | |
| folder: docs | |
| branch: gh-pages | |
| clean: false | |
| commit-message: "docs: deploy documentation for ${{ steps.get-version.outputs.version || github.ref_name }}" | |
| commit_user_name: "hack23-automation[bot]" | |
| commit_user_email: "304996498+hack23-automation[bot]@users.noreply.github.com" | |
| build: | |
| name: Build Release Package | |
| needs: [prepare] | |
| runs-on: ubuntu-26.04 | |
| # Build job needs specific permissions for attestations | |
| permissions: | |
| contents: read | |
| id-token: write # Required for OIDC | |
| attestations: write # Required for SBOM and build attestations | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 | |
| with: | |
| egress-policy: audit | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ vars.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| persist-credentials: true | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'push' && github.ref || github.event_name == 'workflow_dispatch' && github.event.inputs.version || '' }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "26" | |
| # Built-in npm cache (single source of truth — no extra actions/cache step). | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build MCP Server | |
| run: npm run build | |
| env: | |
| APP_VERSION: ${{ needs.prepare.outputs.version }} | |
| - name: Create release artifacts | |
| run: | | |
| # Create package directory | |
| mkdir -p release-package | |
| # Copy built files | |
| cp -r dist release-package/ 2>/dev/null || echo "No dist directory" | |
| cp -r src release-package/ 2>/dev/null || echo "No src directory" | |
| # Copy essential files | |
| cp package.json release-package/ | |
| cp package-lock.json release-package/ 2>/dev/null || echo "No package-lock.json" | |
| cp README.md release-package/ 2>/dev/null || echo "No README.md" | |
| cp LICENSE* release-package/ 2>/dev/null || echo "No LICENSE" | |
| # Create the zip file | |
| cd release-package | |
| zip -r ../european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip . | |
| cd .. | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: build-artifacts | |
| path: | | |
| release-package/ | |
| european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip | |
| if-no-files-found: error | |
| - name: Generate SBOM | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| id: sbom | |
| with: | |
| format: spdx-json | |
| output-file: european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json | |
| artifact-name: european-parliament-mcp-server-${{ needs.prepare.outputs.version }} | |
| - name: Generate artifact attestation | |
| uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 | |
| id: attest | |
| with: | |
| subject-path: european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip | |
| - name: Copy artifact attestation for zip | |
| run: cp ${{ steps.attest.outputs.bundle-path }} european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl | |
| - name: Generate SBOM attestation | |
| id: attestsbom | |
| uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 | |
| with: | |
| subject-path: european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip | |
| sbom-path: european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json | |
| - name: Copy SBOM attestation for zip | |
| run: cp ${{ steps.attestsbom.outputs.bundle-path }} european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl | |
| - name: Upload security artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: security-artifacts | |
| path: | | |
| european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json | |
| european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl | |
| european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl | |
| if-no-files-found: error | |
| release: | |
| name: Create Release | |
| needs: [prepare, build] | |
| runs-on: ubuntu-26.04 | |
| # Release job needs specific permissions to create GitHub releases | |
| permissions: | |
| contents: write # Required to create releases | |
| id-token: write # Required for OIDC | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 | |
| with: | |
| egress-policy: audit | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ vars.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| persist-credentials: true | |
| fetch-depth: 0 | |
| ref: main # Always use main branch | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: build-artifacts | |
| path: artifacts/build | |
| - name: Download security artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: security-artifacts | |
| path: artifacts/security | |
| - name: Draft Release Notes | |
| id: release-drafter | |
| uses: release-drafter/release-drafter@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7.7.0 | |
| with: | |
| version: ${{ needs.prepare.outputs.version }} | |
| tag: ${{ needs.prepare.outputs.version }} | |
| name: European Parliament MCP Server ${{ needs.prepare.outputs.version }} | |
| publish: false | |
| prerelease: ${{ needs.prepare.outputs.is_prerelease }} | |
| disable-autolabeler: true | |
| env: | |
| GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} | |
| # Create GitHub Release with all artifacts | |
| - name: Create GitHub Release | |
| uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 | |
| with: | |
| tag: ${{ needs.prepare.outputs.version }} | |
| name: European Parliament MCP Server ${{ needs.prepare.outputs.version }} | |
| body: ${{ steps.release-drafter.outputs.body }} | |
| generateReleaseNotes: false | |
| immutableCreate: true | |
| draft: false | |
| prerelease: ${{ needs.prepare.outputs.is_prerelease }} | |
| artifacts: | | |
| artifacts/build/european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip | |
| artifacts/security/european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json | |
| artifacts/security/european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl | |
| artifacts/security/european-parliament-mcp-server-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "26" | |
| # Built-in npm cache (single source of truth — no extra actions/cache step). | |
| cache: "npm" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build package | |
| run: npm run build | |
| - name: Verify package contents | |
| run: | | |
| echo "📦 Package contents preview:" | |
| npm pack --dry-run | |
| echo "" | |
| echo "📋 Package files:" | |
| tar -tzf $(npm pack 2>/dev/null | tail -n 1) | head -20 | |
| - name: Publish to npm with provenance | |
| run: npm publish --provenance --access public | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_CONFIG_PROVENANCE: true | |
| - name: Verify npm package | |
| run: | | |
| echo "✅ Package published successfully!" | |
| echo "📦 Package: european-parliament-mcp-server@${{ needs.prepare.outputs.version }}" | |
| echo "🔗 npm URL: https://www.npmjs.com/package/european-parliament-mcp-server" | |
| echo "" | |
| echo "To install:" | |
| echo "npm install european-parliament-mcp-server@${{ needs.prepare.outputs.version }}" | |
| echo "" | |
| echo "To verify provenance:" | |
| echo "npm audit signatures" |