diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ee86336f..87f8ab38 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,58 +7,34 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: pages cancel-in-progress: true jobs: - build: + gates: + uses: ./.github/workflows/gates.yml + with: + upload-dist: true + + deploy: + needs: gates runs-on: ubuntu-latest + environment: github-pages + permissions: + contents: read + pages: write + id-token: write steps: - - uses: actions/checkout@v7 - - - uses: pnpm/action-setup@v6 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - run: pnpm install --frozen-lockfile - - - run: pnpm licenses:check - - - run: pnpm attribution:check - - run: pnpm coverage:check - - - run: pnpm lint - - - run: pnpm test - - - run: pnpm build - - - uses: actions/cache@v6 - id: pw-cache + - uses: actions/download-artifact@v8 with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - - if: steps.pw-cache.outputs.cache-hit != 'true' - run: pnpm exec playwright install chromium - # OS deps are not cacheable; the runner image must not be relied on. - - run: pnpm exec playwright install-deps chromium - - run: pnpm exec playwright test + name: web-dist + path: dist - uses: actions/upload-pages-artifact@v5.0.0 with: path: dist - deploy: - needs: build - runs-on: ubuntu-latest - environment: github-pages - steps: - uses: actions/deploy-pages@v5 id: deployment diff --git a/.github/workflows/gates.yml b/.github/workflows/gates.yml new file mode 100644 index 00000000..e4b6c583 --- /dev/null +++ b/.github/workflows/gates.yml @@ -0,0 +1,74 @@ +name: Gates + +# The one frontend gate list pr.yml, deploy.yml and release.yml share. +on: + workflow_call: + inputs: + upload-dist: + description: upload dist/ as the web-dist artifact for a later job + type: boolean + default: false + outputs: + version: + description: package.json version + value: ${{ jobs.gates.outputs.version }} + +permissions: + contents: read + +jobs: + gates: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + + # A missing field prints the word undefined, which the guard would let through as a tag. + - id: version + run: | + VERSION=$(node -p "require('./package.json').version || ''") + echo "version=${VERSION:?package.json has no version}" >> "$GITHUB_OUTPUT" + + - run: pnpm install --frozen-lockfile + + - run: pnpm licenses:check + + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check licenses advisories + manifest-path: ./src-tauri/Cargo.toml + + - run: pnpm attribution:check + - run: pnpm coverage:check + + - run: pnpm lint + + - run: pnpm test + + - run: pnpm build + + - uses: actions/cache@v6 + id: pw-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + - if: steps.pw-cache.outputs.cache-hit != 'true' + run: pnpm exec playwright install chromium + # OS deps are not cacheable, so the runner image must not be relied on. + - run: pnpm exec playwright install-deps chromium + - run: pnpm exec playwright test + + - if: inputs.upload-dist + uses: actions/upload-artifact@v7 + with: + name: web-dist + path: dist + if-no-files-found: error diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index c15b4216..49e63e48 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -5,6 +5,9 @@ on: branches: [main, prod] tags: ['v*'] +permissions: + contents: read + jobs: mirror: runs-on: ubuntu-latest diff --git a/.github/workflows/msix.yml b/.github/workflows/msix.yml index 5ee0961f..414fcc7b 100644 --- a/.github/workflows/msix.yml +++ b/.github/workflows/msix.yml @@ -15,6 +15,9 @@ on: - 'packages/mcp-server/scripts/**' - '.github/workflows/msix.yml' +permissions: + contents: read + concurrency: group: msix-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index de254189..c5862f33 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,44 +6,10 @@ on: push: branches: [main, prod] +permissions: + contents: read + jobs: + # The prod ruleset requires the check context lint-test-build / gates, so neither job name may change. lint-test-build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - - uses: pnpm/action-setup@v6 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - run: pnpm install --frozen-lockfile - - - run: pnpm licenses:check - - - uses: EmbarkStudios/cargo-deny-action@v2 - with: - command: check licenses advisories - manifest-path: ./src-tauri/Cargo.toml - - - run: pnpm attribution:check - - run: pnpm coverage:check - - - run: pnpm lint - - - run: pnpm test - - - run: pnpm build - - - uses: actions/cache@v6 - id: pw-cache - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - - if: steps.pw-cache.outputs.cache-hit != 'true' - run: pnpm exec playwright install chromium - # OS deps are not cacheable; the runner image must not be relied on. - - run: pnpm exec playwright install-deps chromium - - run: pnpm exec playwright test + uses: ./.github/workflows/gates.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f78a00d..c28dccf5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,72 +3,25 @@ name: Release on: workflow_dispatch: +permissions: + contents: read + concurrency: group: release jobs: gates: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v7 - - - uses: pnpm/action-setup@v6 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - run: pnpm install --frozen-lockfile - - - uses: swatinem/rust-cache@v2 - with: - workspaces: './src-tauri -> target' - - - run: pnpm licenses:check + uses: ./.github/workflows/gates.yml + with: + upload-dist: true - - run: pnpm attribution:check - - run: pnpm coverage:check - - - run: pnpm lint - - - run: pnpm test - - # Regenerate before `pnpm build` so the Vite plugin ships the freshly - # validated .md into dist/; the diff fails the build on any drift. - - name: rust attribution freshness - run: | - curl -fsSL https://github.com/EmbarkStudios/cargo-about/releases/download/0.9.1/cargo-about-0.9.1-x86_64-unknown-linux-musl.tar.gz \ - | tar xz --strip-components=1 -C /tmp - cd src-tauri - cargo fetch - /tmp/cargo-about generate about.hbs -o THIRD-PARTY-LICENSES-RUST.md - git diff --exit-code -- THIRD-PARTY-LICENSES-RUST.md - - - run: pnpm build - - - uses: EmbarkStudios/cargo-deny-action@v2 - with: - command: check licenses advisories - manifest-path: ./src-tauri/Cargo.toml - - - id: version - run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" - - - name: stage web dist for the release zip - run: cp .github/release/web-README.md dist/README.md - - - uses: actions/upload-artifact@v7 - with: - name: web-dist - path: dist + rust: + uses: ./.github/workflows/rust.yml # Single creation point: tauri-action's own tag lookup races across matrix # legs and can split assets over duplicate drafts (tauri-action#914). create-release: - needs: gates + needs: [gates, rust] runs-on: ubuntu-latest permissions: contents: write @@ -173,7 +126,7 @@ jobs: [ "$(printf '%s\n%s\n' "$MAX" "$FLOOR" | sort -V | tail -1)" = "$FLOOR" ] || { echo "$MAX is newer than the floor $FLOOR"; exit 1; } msix: - needs: gates + needs: [gates, rust] uses: ./.github/workflows/msix.yml upload-web-dist: @@ -182,11 +135,16 @@ jobs: permissions: contents: write steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 with: name: web-dist path: dist + - name: stage the self-hosting readme + run: cp .github/release/web-README.md dist/README.md + - name: zip browser build run: | cd dist diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6927ba1a..7972b6f2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,8 +1,9 @@ name: Rust -# src-tauri gets no other PR gate, because Rust and the packaging data only matter in a release build. +# Also called from release.yml, so a release runs these gates on its own commit. # Path-filtered because the Tauri dependency compile is too heavy for pure-frontend PRs. on: + workflow_call: pull_request: branches: [main, prod] paths: ['src-tauri/**', '.github/workflows/rust.yml'] @@ -10,6 +11,9 @@ on: branches: [main, prod] paths: ['src-tauri/**', '.github/workflows/rust.yml'] +permissions: + contents: read + defaults: run: working-directory: ./src-tauri @@ -33,6 +37,26 @@ jobs: components: rustfmt - run: cargo fmt --all --check + # Only Cargo.lock and about.* can stale the notice, and both sit under this workflow's path filter. + attribution: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: swatinem/rust-cache@v2 + with: + workspaces: './src-tauri -> target' + shared-key: attribution + cache-targets: false + # bash here runs with pipefail, so a failed download cannot hide behind tar. + - name: rust attribution freshness + shell: bash + run: | + curl -fsSL https://github.com/EmbarkStudios/cargo-about/releases/download/0.9.1/cargo-about-0.9.1-x86_64-unknown-linux-musl.tar.gz \ + | tar xz --strip-components=1 -C /tmp + cargo fetch --locked + /tmp/cargo-about generate about.hbs -o THIRD-PARTY-LICENSES-RUST.md + git diff --exit-code -- THIRD-PARTY-LICENSES-RUST.md + clippy-test: name: clippy-test (${{ matrix.platform }}) strategy: @@ -60,7 +84,8 @@ jobs: - uses: swatinem/rust-cache@v2 with: workspaces: './src-tauri -> target' + shared-key: clippy-${{ matrix.platform }} - - run: cargo clippy --all-targets -- -D warnings + - run: cargo clippy --all-targets --locked -- -D warnings - - run: cargo test + - run: cargo test --locked