Skip to content

Release with Attestations #186

Release with Attestations

Release with Attestations #186

Workflow file for this run

name: Release with Attestations
# ----------------------------------------------------------------------------
# Architecture (PR: workflow performance overhaul — May 2026)
# ----------------------------------------------------------------------------
# Previous design ran a single monolithic "Prepare Release" job (~41 min) that
# built the app, then ran unit tests, then E2E tests sequentially — followed
# by a "Build Release Package" job that REBUILT the app from scratch (~10 min)
# and a "Create Release and Deploy" job that built+tested ONCE MORE for npm
# publish. Total wall-clock: ~68 min.
#
# This version splits the workflow into parallel jobs, each ≤ 10 min:
#
# prepare → version + commit/tag (no build/test)
# ├── build → npm run build (single source of truth for dist)
# ├── unit-tests → vitest + coverage (parallel)
# └── typedoc → API docs (parallel)
# ├── e2e (matrix) → cypress shards (parallel; needs build)
# ├── package → SBOM + attestations (needs build)
# │ └── release → GitHub Release (needs prepare, build, package, unit-tests, e2e)
# ├── deploy-docs → docs/ → GitHub Pages (needs build, tests, typedoc)
# └── npm-publish → build:lib + publish (runs LAST; only if every
# upstream job succeeded)
#
# Wall-clock target: ~25 min critical path; every individual job ≤ 10 min.
# ----------------------------------------------------------------------------
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (vX.Y.Z format)"
required: true
default: "v1.0.0"
prerelease:
description: "Is this a pre-release?"
type: boolean
default: false
push:
tags:
- "v*"
# Restrict top-level permissions to minimum required defaults
permissions: read-all
jobs:
# --------------------------------------------------------------------------
# 1. prepare — version + commit/tag only (≤2 min)
# --------------------------------------------------------------------------
prepare:
name: Prepare Release
runs-on: ubuntu-26.04
permissions:
contents: write # Required for git auto-commit
outputs:
version: ${{ steps.get-version.outputs.version }}
npm_version: ${{ steps.get-version.outputs.npm_version }}
is_prerelease: ${{ github.event.inputs.prerelease || 'false' }}
# Commit SHA to check out in downstream jobs — picks up the version-bump
# commit when triggered via workflow_dispatch.
release_sha: ${{ steps.resolve-sha.outputs.sha }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# No fetch-depth: 0 needed for the version bump — saves ~2 min.
- 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
NPM_VERSION="${VERSION#v}"
echo "npm_version=${NPM_VERSION}" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}"
echo "npm version: ${NPM_VERSION}"
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Set Version for release
if: github.event_name == 'workflow_dispatch'
run: |
PLAIN_VERSION="${{ github.event.inputs.version }}"
PLAIN_VERSION="${PLAIN_VERSION#v}"
npm version $PLAIN_VERSION --no-git-tag-version
- uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.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 }}"
- name: Resolve release SHA
id: resolve-sha
run: |
SHA=$(git rev-parse HEAD)
echo "sha=${SHA}" >> $GITHUB_OUTPUT
echo "Release SHA: ${SHA}"
# --------------------------------------------------------------------------
# 2. build — single canonical `npm run build` (≤12 min cap, target ≤10)
# Uploads `dist/` + the release zip + checksum as artifacts. ALL
# downstream jobs consume these instead of rebuilding.
# --------------------------------------------------------------------------
build:
name: Build Application
needs: [prepare]
runs-on: ubuntu-26.04
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Create release artifacts
run: |
echo "📦 Creating release zip + checksum from dist/..."
cd dist
zip -r ../riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip .
cd ..
sha256sum riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip \
> riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
echo "✅ Release artifacts created"
- name: Upload dist artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
retention-days: 1
if-no-files-found: error
- name: Upload release zip
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-zip
path: |
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
retention-days: 7
if-no-files-found: error
# --------------------------------------------------------------------------
# 3. unit-tests — vitest + coverage (parallel with build, ≤8 min)
# --------------------------------------------------------------------------
unit-tests:
name: Unit Tests
needs: [prepare]
runs-on: ubuntu-26.04
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Run unit tests with coverage
# Coverage `reportsDirectory`, coverage reporters, and test
# reporter `outputFile`s are all configured in vitest.config.js
# (see test.coverage.reportsDirectory / test.reporters). Do not
# re-specify them on the CLI — passing `--reporter` here would
# replace the configured reporter array, silently dropping the
# html and json-with-outputFile reporters.
#
# `coverage.reportOnFailure: true` in vitest.config.js ensures
# `builds/coverage/` survives a failing run; without it Vitest
# calls `cleanAfterRun()` on failure and wipes the directory.
#
# NODE_OPTIONS: v8 coverage with `all: true` across 500+ source
# files requires extra heap; without this the process is OOM-killed
# mid-report-generation (exits 0 but writes nothing to builds/).
env:
NODE_OPTIONS: "--max-old-space-size=4096"
run: |
mkdir -p builds/test-results builds/coverage
set -o pipefail
npx vitest run --coverage 2>&1 | tee builds/test-results/vitest-console.log
# Defense-in-depth: fail attribution stays on Unit Tests rather
# than the downstream deploy-docs job ("Artifact not found").
if [ -z "$(find builds -type f -print -quit)" ]; then
echo "::error::No unit-test or coverage files found under builds/ — check vitest/coverage configuration."
exit 1
fi
- name: Upload coverage + test reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: unit-test-reports
path: builds/
retention-days: 7
if-no-files-found: warn
# --------------------------------------------------------------------------
# 4. typedoc — API docs (parallel with build, ≤5 min)
# --------------------------------------------------------------------------
typedoc:
name: Generate TypeDoc
needs: [prepare]
runs-on: ubuntu-26.04
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Generate API documentation
run: npm run typedoc
- name: Upload typedoc artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: typedoc
path: api/
retention-days: 7
if-no-files-found: error
# --------------------------------------------------------------------------
# 5. e2e — Cypress, sharded across spec files (parallel; ≤10 min/shard)
# Consumes the pre-built dist artifact, so there is no inner `npm run
# build` (the previous `npm run e2e` script did one redundantly).
# --------------------------------------------------------------------------
e2e:
name: E2E (${{ matrix.shard.name }})
needs: [prepare, build]
runs-on: ubuntu-26.04
permissions:
contents: read
strategy:
fail-fast: false # All shards report, even if one fails
matrix:
shard:
- name: critical
specs: "cypress/e2e/homepage.cy.js,cypress/e2e/accessibility.cy.js,cypress/e2e/sitemap.cy.js"
- name: dashboards
specs: "cypress/e2e/dashboard-page.cy.js,cypress/e2e/dashboards.cy.js,cypress/e2e/dashboards-no-scroll.cy.js"
- name: dashboards-extra
specs: "cypress/e2e/all-dashboards.cy.js,cypress/e2e/politician-dashboard.cy.js"
- name: news
specs: "cypress/e2e/news-page.cy.js,cypress/e2e/news-articles.cy.js"
- name: i18n
specs: "cypress/e2e/multi-language-sanity.cy.js"
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Download dist artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Run Cypress (${{ matrix.shard.name }} shard)
# Mirror the original `npm run e2e` semantics exactly:
# start-server-and-test preview http://localhost:4173 cypress:run
# where cypress:run = `cypress run --config video=false` (default
# Electron browser). We capture stdout via `tee` so the existing
# `scripts/generate-e2e-results-html.cjs` can rebuild the
# docs/cypress/index.html report from the log.
shell: bash
env:
NODE_OPTIONS: "--max-old-space-size=8192"
CYPRESS_VIDEO: "false"
run: |
mkdir -p builds/cypress
set -o pipefail
npx start-server-and-test preview http://localhost:4173 \
"npx cypress run --config video=false --spec '${{ matrix.shard.specs }}'" \
2>&1 | tee "builds/cypress/e2e-output-${{ matrix.shard.name }}.log"
TEST_EXIT_CODE=${PIPESTATUS[0]}
# Per-shard screenshot snapshot (only present on failure)
if [ -d cypress/screenshots ]; then
mkdir -p "builds/cypress/screenshots"
cp -r cypress/screenshots/. "builds/cypress/screenshots/" || true
fi
exit ${TEST_EXIT_CODE}
# Upload per-shard cypress artifacts (log + any screenshots). The
# deploy-docs job downloads ALL shards and merges them into a single
# builds/cypress/ so the existing report generator + screenshot
# gallery work identically to the pre-refactor pipeline.
- name: Upload cypress artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cypress-artifacts-${{ matrix.shard.name }}
path: builds/cypress/
retention-days: 7
if-no-files-found: warn
# --------------------------------------------------------------------------
# 6. package — SBOM + SLSA attestations (≤5 min)
# Consumes the release-zip artifact built by `build`, so no rebuild.
# --------------------------------------------------------------------------
package:
name: Build Release Package
needs: [prepare, build]
runs-on: ubuntu-26.04
permissions:
contents: read
id-token: write # Required for OIDC
attestations: write # Required for SBOM + build attestations
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Download release zip
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-zip
path: .
- name: Generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
id: sbom
with:
format: spdx-json
output-file: riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
artifact-name: riksdagsmonitor-${{ needs.prepare.outputs.version }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
id: attest
with:
subject-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
- name: Copy artifact attestation for zip
run: cp ${{ steps.attest.outputs.bundle-path }} riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
- name: Generate SBOM attestation
id: attestsbom
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
sbom-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
- name: Copy SBOM attestation for zip
run: cp ${{ steps.attestsbom.outputs.bundle-path }} riksdagsmonitor-${{ 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: |
riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl
if-no-files-found: error
# --------------------------------------------------------------------------
# 7. deploy-docs — assemble docs/ and push to GitHub Pages (≤8 min)
# Depends on all reports being ready (typedoc + tests + e2e).
# --------------------------------------------------------------------------
deploy-docs:
name: Deploy Documentation
needs: [prepare, build, unit-tests, typedoc, e2e]
runs-on: ubuntu-26.04
permissions:
contents: write # Required for github-pages-deploy-action
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Download unit-test reports
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: unit-test-reports
path: builds/
# Download EVERY e2e shard artifact and merge into builds/cypress/.
# `merge-multiple: true` flattens cypress-artifacts-critical/...,
# cypress-artifacts-news/... etc. into the same builds/cypress/
# tree, so per-shard logs + screenshots all land in one folder.
- name: Download e2e (cypress) artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: cypress-artifacts-*
path: builds/cypress/
merge-multiple: true
# The HTML generator (scripts/generate-e2e-results-html.cjs) reads a
# single `e2e-output.log`. Concatenate the per-shard logs in a
# deterministic order so the rendered report covers every shard.
- name: Aggregate cypress shard logs
shell: bash
run: |
mkdir -p builds/cypress
if compgen -G "builds/cypress/e2e-output-*.log" > /dev/null; then
for f in $(ls builds/cypress/e2e-output-*.log | sort); do
echo "===== $(basename "$f") ====="
cat "$f"
echo
done > builds/cypress/e2e-output.log
echo "✅ Aggregated $(ls builds/cypress/e2e-output-*.log | wc -l) shard log(s) → builds/cypress/e2e-output.log"
else
echo "⚠️ No shard logs found — index.html will be a placeholder."
fi
- name: Download TypeDoc
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: typedoc
path: api/
- name: Clean old docs
run: |
rm -rf docs/api/* docs/cypress/* docs/test-results/* docs/coverage/* docs/dependencies/* || true
- name: Copy API documentation to docs
run: |
mkdir -p docs/api
if [ -d "api" ]; then
cp -r api/* docs/api/ || true
fi
- name: Generate dependencies documentation
run: npm run docs:dependencies
- name: Copy test reports to docs
run: |
npm run build:test-reports
node scripts/generate-e2e-results-html.cjs || echo "⚠️ Failed to generate E2E test results HTML"
node scripts/generate-dependency-report-html.cjs || echo "⚠️ Failed to generate dependency report HTML"
- name: Create documentation index
run: |
cat > docs/index.html << 'DOCSEOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Riksdagsmonitor Documentation Hub</title>
<style>
:root{--bg:#0a0e27;--bg2:#1a1e3d;--cyan:#00d9ff;--green:#00ff88;--red:#ff006e;--yellow:#ffbe0b;--text:#e0e0e0;--muted:#808080;--card:rgba(26,30,61,.6);--border:rgba(0,217,255,.3)}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;line-height:1.6;color:var(--text);background:linear-gradient(135deg,var(--bg) 0%,var(--bg2) 100%);min-height:100vh;padding:2rem}
.container{max-width:1200px;margin:0 auto}
h1{color:var(--cyan);font-size:2.5rem;margin-bottom:.5rem;text-shadow:0 0 10px rgba(0,217,255,.5)}
.subtitle{color:var(--red);font-size:1.2rem;margin-bottom:1rem}
.release-info{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1rem 1.5rem;margin-bottom:2rem;font-size:.9rem;color:var(--muted)}
.release-info strong{color:var(--cyan)}
h2{color:var(--yellow);font-size:1.3rem;margin:2rem 0 1rem;padding-bottom:.5rem;border-bottom:1px solid rgba(0,217,255,.15)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-bottom:2rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1.5rem;transition:all .3s ease}
.card:hover{border-color:var(--cyan);box-shadow:0 0 20px rgba(0,217,255,.4);transform:translateY(-4px)}
.card h3{color:var(--yellow);font-size:1.2rem;margin-bottom:.75rem}
.card p{margin-bottom:1rem;color:#b0b0b0;font-size:.9rem}
.card .links{display:flex;flex-wrap:wrap;gap:.5rem}
.card .links a{display:inline-block;padding:.5rem 1rem;background:linear-gradient(135deg,#00d9ff 0%,#0099cc 100%);color:#0a0e27;text-decoration:none;border-radius:4px;font-weight:700;font-size:.85rem;transition:all .3s ease}
.card .links a:hover{background:linear-gradient(135deg,#00ffff 0%,#00d9ff 100%);box-shadow:0 0 10px rgba(0,217,255,.6)}
.card .links a.secondary{background:rgba(0,217,255,.1);color:var(--cyan);border:1px solid var(--border)}
.card .links a.secondary:hover{background:rgba(0,217,255,.2)}
.footer{text-align:center;color:var(--muted);margin-top:3rem;padding-top:2rem;border-top:1px solid rgba(0,217,255,.2);font-size:.85rem}
</style>
</head>
<body>
<div class="container">
<h1>📚 Riksdagsmonitor Documentation Hub</h1>
<p class="subtitle">Swedish Parliament Intelligence Platform — Technical Documentation</p>
<div class="release-info">
📅 Generated during release workflow · <strong>Riksdagsmonitor</strong> by Hack23 AB
</div>
<h2>📖 API &amp; Architecture</h2>
<div class="grid">
<div class="card">
<h3>📖 API Documentation</h3>
<p>Comprehensive TypeDoc-generated API reference for all TypeScript modules, dashboard components, and utility functions.</p>
<div class="links">
<a href="api/index.html">View API Docs →</a>
</div>
</div>
</div>
<h2>🧪 Testing &amp; Quality</h2>
<div class="grid">
<div class="card">
<h3>🧪 Unit Test Results</h3>
<p>Interactive Vitest HTML report with test tree, filtering, duration breakdown, and re-run capability (built-in @vitest/ui reporter).</p>
<div class="links">
<a href="test-results/html/index.html">Vitest HTML Report →</a>
<a href="test-results/vitest-results.json" class="secondary">JSON</a>
</div>
</div>
<div class="card">
<h3>🎭 E2E Test Results</h3>
<p>Cypress end-to-end test reports with spec-level breakdown, timing per spec, screenshot gallery, and searchable log.</p>
<div class="links">
<a href="cypress/index.html">View E2E Reports →</a>
</div>
</div>
<div class="card">
<h3>📊 Test Coverage</h3>
<p>Istanbul HTML coverage report with per-file line/branch/function/statement metrics, source annotation, and directory breakdown (built-in @vitest/coverage-v8).</p>
<div class="links">
<a href="coverage/index.html">Istanbul Report →</a>
<a href="coverage/lcov-report/index.html" class="secondary">LCOV</a>
<a href="coverage/coverage-final.json" class="secondary">JSON</a>
</div>
</div>
</div>
<h2>📦 Dependencies &amp; Supply Chain</h2>
<div class="grid">
<div class="card">
<h3>📦 Dependency Report</h3>
<p>Interactive dependency report with package counts, search, prod/dev badges, and expandable full tree view.</p>
<div class="links">
<a href="dependencies/index.html">View Dependencies →</a>
<a href="dependencies/dependency-tree.json" class="secondary">JSON</a>
<a href="dependencies/dependency-tree.txt" class="secondary">TXT</a>
</div>
</div>
</div>
<h2>🔗 Quick Links</h2>
<div class="grid">
<div class="card">
<h3>🏠 Main Site</h3>
<p>Return to the main Riksdagsmonitor platform to explore Swedish Parliament intelligence dashboards.</p>
<div class="links">
<a href="../index.html">Go to Main Site →</a>
</div>
</div>
</div>
<div class="footer">
<p>Generated during release workflow · Riksdagsmonitor by Hack23 AB · Licensed under Apache License 2.0</p>
</div>
</div>
</body>
</html>
DOCSEOF
echo "✅ Documentation index created at docs/index.html"
- name: Create .nojekyll file
run: touch docs/.nojekyll
- name: Update sitemap
run: npm run docs:sitemap
- name: Deploy Documentation to GitHub Pages
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
with:
folder: docs
target-folder: docs
branch: main
clean: false
commit-message: "docs: update documentation for ${{ needs.prepare.outputs.version || 'development' }}"
# --------------------------------------------------------------------------
# 8. release — create GitHub Release with artifacts (≤3 min)
# --------------------------------------------------------------------------
release:
name: Create GitHub Release
needs: [prepare, build, package, unit-tests, e2e]
runs-on: ubuntu-26.04
permissions:
contents: write # Required to create releases
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
# release-drafter reads .github/release-drafter.yml from the default
# branch (api driven), so a shallow checkout is sufficient here.
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Download release zip
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-zip
path: artifacts/build
- name: Download security artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: security-artifacts
path: artifacts/security
# NOTE: release-drafter emits benign warnings about "pull_request_target.*"
# not being known webhook names (release-drafter/release-drafter#1369).
- name: Draft Release Notes
id: release-drafter
uses: release-drafter/release-drafter@693d20e7c1ce1a81d3a41962f85914253b518449 # v7.3.1
with:
version: ${{ needs.prepare.outputs.version }}
tag: ${{ needs.prepare.outputs.version }}
name: Riksdagsmonitor ${{ needs.prepare.outputs.version }}
publish: false
prerelease: ${{ needs.prepare.outputs.is_prerelease }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
tag: ${{ needs.prepare.outputs.version }}
name: Riksdagsmonitor ${{ needs.prepare.outputs.version }}
body: |
${{ steps.release-drafter.outputs.body }}
## 📦 Release Artifacts
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip` - Production build
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256` - Checksum for verification
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json` - SBOM (Software Bill of Materials)
- `*.intoto.jsonl` - SLSA Build Provenance Attestations
## 📦 npm Package
Shared types, theme system, and utilities are available as an npm package:
```bash
npm install riksdagsmonitor
```
## 📚 Documentation
- [API Documentation](https://riksdagsmonitor.com/docs/api/)
- [Test Coverage Report](https://riksdagsmonitor.com/docs/coverage/)
- [E2E Test Reports](https://riksdagsmonitor.com/docs/cypress/)
- [Documentation Hub](https://riksdagsmonitor.com/docs/)
## 🔐 Security
All artifacts include SLSA Build Provenance attestations and SBOM for supply chain security.
Verify attestations using the GitHub CLI:
```bash
gh attestation verify riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip -R Hack23/riksdagsmonitor
```
generateReleaseNotes: false
immutableCreate: true
draft: false
prerelease: ${{ needs.prepare.outputs.is_prerelease }}
artifacts: |
artifacts/build/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
artifacts/build/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl
token: ${{ secrets.GITHUB_TOKEN }}
# --------------------------------------------------------------------------
# 9. npm-publish — build:lib + publish (≤5 min) — RUNS LAST
# Only executes after every upstream job (build, unit-tests, typedoc,
# e2e, package, deploy-docs, release) has succeeded, so a broken
# build / failing tests / failed deployment never leak a tag onto npm.
# Skips the heavy `npm run build` (that's only needed for the website,
# not the library package) AND the duplicate `npm test` (already
# passed in unit-tests).
# --------------------------------------------------------------------------
npm-publish:
name: Publish to npm
needs: [prepare, build, unit-tests, typedoc, e2e, package, deploy-docs, release]
runs-on: ubuntu-26.04
permissions:
contents: read
id-token: write # Required for npm provenance
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare.outputs.release_sha }}
- name: Setup Node.js for npm publish
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Lint library source
run: npm run lint
- name: Build library package
run: npm run build:lib
- name: Verify package contents
run: |
echo "📦 Package contents preview:"
PACK_OUTPUT="$(npm pack --dry-run 2>&1)"
printf '%s\n' "$PACK_OUTPUT"
echo ""
echo "📊 Package size summary:"
printf '%s\n' "$PACK_OUTPUT" | grep -E "^npm notice (package size|unpacked size|total files)" || true
- name: Publish to npm with provenance
run: npm publish --ignore-scripts --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: riksdagsmonitor@${{ needs.prepare.outputs.npm_version }}"
echo "🔗 npm URL: https://www.npmjs.com/package/riksdagsmonitor"
# --------------------------------------------------------------------------
# 10. summary — fan-in status job (≤1 min)
# --------------------------------------------------------------------------
summary:
name: Deployment Summary
needs: [prepare, release, deploy-docs, npm-publish]
if: always()
runs-on: ubuntu-26.04
steps:
- name: Print summary
run: |
echo "🎉 Release ${{ needs.prepare.outputs.version }} pipeline complete"
echo ""
echo "Job results:"
echo " - release: ${{ needs.release.result }}"
echo " - deploy-docs: ${{ needs.deploy-docs.result }}"
echo " - npm-publish: ${{ needs.npm-publish.result }}"
echo ""
echo "📍 Deployments:"
echo " - GitHub Pages (main branch)"
echo ""
echo "📚 Documentation:"
echo " - https://riksdagsmonitor.com/docs/"