chore(deps): update dependency cosign to v3.1.2 #10144
Workflow file for this run
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
| # SPDX-FileCopyrightText: 2026 Alexey Zhokhov | |
| # SPDX-License-Identifier: Apache-2.0 | |
| name: Docs | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '17 4 * * *' | |
| permissions: | |
| actions: read | |
| contents: read | |
| env: | |
| DOCS_SITE_URL: https://jackin.tailrocks.com | |
| JACKIN_REPO_BLOB_URL: https://github.com/jackin-project/jackin/blob/main | |
| JACKIN_REPO_EDIT_URL: https://github.com/jackin-project/jackin/edit/main | |
| concurrency: | |
| group: pages-${{ github.event_name }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Schedule (cron) runs only `check-deployed` (line ~210) by design — | |
| # all other jobs gate themselves out on `event_name != 'schedule'`. | |
| # The cron path verifies live deployed-site links; it has nothing to | |
| # validate against the source tree at schedule time. | |
| changes: | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| docs: ${{ steps.dispatch.outputs.docs || steps.filter.outputs.docs }} | |
| source: ${{ steps.dispatch.outputs.source || steps.filter.outputs.source }} | |
| result-reuse: ${{ steps.docs-result.outputs.hit }} | |
| result-name: ${{ steps.docs-result.outputs.name }} | |
| docs-xtask-contract: ${{ steps.contracts.outputs.docs-xtask }} | |
| deployment-reuse: ${{ steps.deployed.outputs.reuse }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Force docs=true on workflow_dispatch | |
| id: dispatch | |
| if: github.event_name == 'workflow_dispatch' | |
| shell: bash | |
| run: | | |
| { | |
| echo "docs=true" | |
| echo "source=true" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Resolve Docs xtask contract | |
| id: contracts | |
| run: echo "docs-xtask=$(scripts/ci/docs-xtask-contract.sh)" >> "$GITHUB_OUTPUT" | |
| - name: Classify changed paths | |
| id: filter | |
| if: github.event_name != 'workflow_dispatch' | |
| uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 | |
| with: | |
| token: "" | |
| base: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| filters: | | |
| docs: | |
| - 'AGENTS.md' | |
| - 'BRANCHING.md' | |
| - 'COMMITS.md' | |
| - 'CONTRIBUTING.md' | |
| - 'PROJECT_STRUCTURE.md' | |
| - 'PULL_REQUESTS.md' | |
| - 'README.md' | |
| - 'RULES.md' | |
| - 'TESTING.md' | |
| - '.github/**/*.md' | |
| - '.github/**/*.yml' | |
| - '.github/workflows/docs.yml' | |
| - '.github/actions/check-deployed-docs/**' | |
| - 'scripts/ci/docs-xtask-contract.sh' | |
| - 'scripts/ci/docs-site-contract.sh' | |
| - 'scripts/ci/codebook-contract.sh' | |
| - 'docs/**' | |
| source: | |
| - 'crates/**' | |
| - 'docker/**/*.fish' | |
| - 'docker/**/*.sh' | |
| - 'docker/**/*.toml' | |
| - '.github/workflows/docs.yml' | |
| - 'scripts/ci/docs-xtask-contract.sh' | |
| - 'scripts/ci/docs-site-contract.sh' | |
| - 'scripts/ci/codebook-contract.sh' | |
| - name: Find input-identical successful Docs result | |
| id: docs-result | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| digest=$( | |
| { | |
| printf 'tracked-paths=%s\n' "$(git ls-files -z | sha256sum | cut -d ' ' -f 1)" | |
| while IFS= read -r -d '' path; do | |
| printf '%s\0%s\n' "$path" "$(git hash-object "$path")" | |
| done < <( | |
| git ls-files -z -- \ | |
| '.github/actions/check-deployed-docs/**' \ | |
| '.github/actions/download-ci-xtask/**' \ | |
| '.github/**/*.md' \ | |
| '.github/**/*.yml' \ | |
| '.github/workflows/docs.yml' \ | |
| 'AGENTS.md' \ | |
| 'BRANCHING.md' \ | |
| 'COMMITS.md' \ | |
| 'CONTRIBUTING.md' \ | |
| 'Cargo.lock' \ | |
| 'Cargo.toml' \ | |
| 'PROJECT_STRUCTURE.md' \ | |
| 'PULL_REQUESTS.md' \ | |
| 'README.md' \ | |
| 'RULES.md' \ | |
| 'TESTING.md' \ | |
| 'crates/**/*.rs' \ | |
| 'crates/**/*.toml' \ | |
| 'crates/*/README.md' \ | |
| 'docker/**/*.fish' \ | |
| 'docker/**/*.sh' \ | |
| 'docker/**/*.toml' \ | |
| 'docs/**' \ | |
| 'mise.lock' \ | |
| 'mise.toml' \ | |
| 'scripts/ci/docs-xtask-contract.sh' \ | |
| 'scripts/ci/docs-site-contract.sh' \ | |
| 'scripts/ci/codebook-contract.sh' | |
| ) | |
| } | sha256sum | cut -d ' ' -f 1 | |
| ) | |
| name="docs-result-v1-${digest}" | |
| artifact_id= | |
| if ! artifact_id=$( | |
| gh api "repos/${REPOSITORY}/actions/artifacts?name=${name}&per_page=10" \ | |
| --jq '.artifacts | map(select(.expired == false)) | first | .id // empty' | |
| ); then | |
| echo "::warning::Docs result lookup failed; running the complete selected contract" | |
| artifact_id= | |
| fi | |
| if [ -n "$artifact_id" ]; then | |
| echo "::notice::reusing input-identical successful Docs result" | |
| hit=true | |
| else | |
| hit=false | |
| fi | |
| { | |
| echo "hit=$hit" | |
| echo "name=$name" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Find semantically identical successful Pages deployment | |
| id: deployed | |
| if: >- | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| reuse=false | |
| deployments=$( | |
| gh api "repos/${REPOSITORY}/deployments?environment=github-pages&per_page=10" \ | |
| --jq '.[] | [.id, .sha] | @tsv' | |
| ) | |
| while IFS=$'\t' read -r deployment_id source_sha; do | |
| if [ -z "$deployment_id" ] || [ -z "$source_sha" ]; then | |
| continue | |
| fi | |
| state=$( | |
| gh api "repos/${REPOSITORY}/deployments/${deployment_id}/statuses?per_page=1" \ | |
| --jq '.[0].state // empty' | |
| ) | |
| if [ "$state" != success ]; then | |
| continue | |
| fi | |
| if ! git cat-file -e "$source_sha^{commit}" 2>/dev/null && \ | |
| ! git fetch --no-tags --depth=1 origin "$source_sha"; then | |
| break | |
| fi | |
| if [ "$(scripts/ci/docs-site-contract.sh "$source_sha")" = \ | |
| "$(scripts/ci/docs-site-contract.sh)" ]; then | |
| echo "::notice::reusing semantically identical successful Pages deployment" | |
| reuse=true | |
| fi | |
| break | |
| done <<< "$deployments" | |
| echo "reuse=$reuse" >> "$GITHUB_OUTPUT" | |
| # The docs site links to repository files via the <RepoFile path="…" /> | |
| # component, which renders a `blob/main` URL that lychee remaps to the | |
| # PR checkout. A source-only PR that renames or deletes a referenced | |
| # file would not match the docs path filter and would silently merge | |
| # with broken links. Run a cheap standalone repo-link check on every | |
| # non-schedule event so source renames cannot bypass the docs link | |
| # contract documented in docs/AGENTS.md. | |
| repo-link-check: | |
| if: github.event_name != 'schedule' && needs.changes.outputs.result-reuse != 'true' | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| DOCS_XTASK: ${{ github.workspace }}/.ci-tools/GitHub/jackin-xtask | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4 | |
| with: | |
| version: "2026.6.11" | |
| cache_key_prefix: "mise-v2" | |
| cache_save: ${{ github.ref == 'refs/heads/main' }} | |
| working_directory: docs | |
| install_args: "bun lychee" | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - uses: ./.github/actions/download-ci-xtask | |
| with: | |
| lane: GitHub | |
| xtask-contract: ${{ needs.changes.outputs.docs-xtask-contract }} | |
| include-tools: "false" | |
| # Plan 049 residual: generate crate README → Fumadocs pages so repo-link | |
| # checks see the same generated MDX the docs build ships (gitignored). | |
| - name: Generate crate pages | |
| working-directory: docs | |
| run: bun run scripts/gen-crate-pages.ts | |
| - name: Register xtask problem matcher | |
| run: echo "::add-matcher::.github/problem-matchers/xtask.json" | |
| - name: Check source repository links | |
| run: | | |
| "$DOCS_XTASK" docs repo-links | |
| # README links are not part of the generated docs site. Check them live | |
| # without cache so a malformed or removed destination fails this PR. | |
| - name: Check README links | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| MISE_CONFIG_FILE=docs/mise.toml mise exec -- lychee \ | |
| --config docs/lychee.toml \ | |
| --cache=false \ | |
| --root-dir "${{ github.workspace }}" \ | |
| README.md | |
| - name: Check brand prose | |
| run: | | |
| "$DOCS_XTASK" docs brand | |
| - name: Check behavioral-spec test citations | |
| run: | | |
| env -u CI "$DOCS_XTASK" docs specs | |
| - name: Check roadmap sidebar completeness | |
| run: | | |
| "$DOCS_XTASK" roadmap audit | |
| - name: Check research sidebar completeness | |
| run: | | |
| "$DOCS_XTASK" research check | |
| docs-link-check: | |
| needs: changes | |
| if: >- | |
| needs.changes.outputs.docs == 'true' && | |
| (needs.changes.outputs.result-reuse != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Find repository-wide successful link result | |
| id: link-result | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| REPOSITORY_ID: ${{ github.event.repository.id }} | |
| run: | | |
| set -euo pipefail | |
| name="docs-links-v2-${RUNNER_OS}-$(scripts/ci/docs-link-contract.sh)" | |
| artifact=$( | |
| gh api "repos/${REPOSITORY}/actions/artifacts?name=${name}&per_page=10" \ | |
| | jq -c --argjson repository_id "$REPOSITORY_ID" \ | |
| '[.artifacts[] | select(.expired == false and .workflow_run.head_repository_id == $repository_id)] | sort_by(.created_at) | last // {}' | |
| ) | |
| artifact_id=$(jq -r '.id // empty' <<< "$artifact") | |
| created_at=$(jq -r '.created_at // empty' <<< "$artifact") | |
| verified=false | |
| refresh=true | |
| if [ -n "$artifact_id" ]; then | |
| created_epoch=$(date -u -d "$created_at" +%s) | |
| if [ $(( $(date -u +%s) - created_epoch )) -lt 259200 ]; then | |
| verified=true | |
| refresh=false | |
| fi | |
| fi | |
| { | |
| echo "name=$name" | |
| echo "refresh=$refresh" | |
| echo "verified=$verified" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Restore input-identical built site | |
| id: built-site | |
| if: >- | |
| steps.link-result.outputs.verified != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true') | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| name="docs-site-v3-${RUNNER_OS}-$(scripts/ci/docs-site-contract.sh)" | |
| artifact_id= | |
| exact=false | |
| if ! artifact_id=$( | |
| gh api "repos/${REPOSITORY}/actions/artifacts?name=${name}&per_page=10" \ | |
| --jq '.artifacts | map(select(.expired == false)) | first | .id // empty' | |
| ); then | |
| echo "::warning::built-site artifact lookup failed; rebuilding the site" | |
| artifact_id= | |
| fi | |
| if [ -n "$artifact_id" ]; then | |
| exact=true | |
| fi | |
| if [ -n "$artifact_id" ]; then | |
| mkdir -p docs/.output/public | |
| gh api "repos/${REPOSITORY}/actions/artifacts/${artifact_id}/zip" \ | |
| > docs/.output/.site.zip | |
| python3 -m zipfile -e docs/.output/.site.zip docs/.output/public | |
| rm docs/.output/.site.zip | |
| html=$(find docs/.output/public -type f -name '*.html' -print -quit) | |
| if [ -n "$html" ]; then | |
| echo "::notice::reusing input-identical built site" | |
| hit=true | |
| else | |
| echo "::warning::built-site artifact is invalid; rebuilding the site" | |
| rm -rf docs/.output/public | |
| hit=false | |
| exact=false | |
| fi | |
| else | |
| hit=false | |
| fi | |
| { | |
| echo "hit=$hit" | |
| echo "exact=$exact" | |
| echo "name=$name" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Setup mise | |
| if: >- | |
| steps.link-result.outputs.verified != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true') | |
| uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4 | |
| with: | |
| version: "2026.6.11" | |
| cache_key_prefix: "mise-v2" | |
| cache_save: ${{ github.ref == 'refs/heads/main' }} | |
| working_directory: docs | |
| install_args: ${{ steps.built-site.outputs.hit == 'true' && 'lychee' || 'bun node lychee' }} | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - name: Cache bun modules | |
| if: >- | |
| (steps.link-result.outputs.verified != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true')) && | |
| steps.built-site.outputs.hit != 'true' | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: bun-${{ runner.os }}-${{ hashFiles('docs/bun.lock') }} | |
| restore-keys: bun-${{ runner.os }}- | |
| - name: Install dependencies | |
| if: >- | |
| (steps.link-result.outputs.verified != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true')) && | |
| steps.built-site.outputs.hit != 'true' | |
| working-directory: docs | |
| run: bun ci | |
| - name: Build site | |
| if: >- | |
| (steps.link-result.outputs.verified != 'true' || | |
| (github.ref == 'refs/heads/main' && | |
| needs.changes.outputs.deployment-reuse != 'true')) && | |
| steps.built-site.outputs.hit != 'true' | |
| working-directory: docs | |
| run: bun run build | |
| - name: Cache lychee responses | |
| if: steps.link-result.outputs.verified != 'true' | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: .lycheecache | |
| key: lychee-v1-${{ runner.os }}-${{ hashFiles('docs/lychee.toml', 'scripts/ci/docs-link-check.sh') }} | |
| restore-keys: lychee-v1-${{ runner.os }}- | |
| - name: Check built site links | |
| if: steps.link-result.outputs.verified != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| scripts/ci/docs-link-check.sh | |
| - name: Stage repository-wide successful link result | |
| if: steps.link-result.outputs.refresh == 'true' | |
| run: | | |
| mkdir -p ci-results | |
| printf '%s\n' '${{ github.sha }}' > ci-results/docs-links | |
| - name: Publish repository-wide successful link result | |
| if: >- | |
| steps.link-result.outputs.refresh == 'true' && | |
| (github.ref == 'refs/heads/main' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository)) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ steps.link-result.outputs.name }} | |
| path: ci-results/docs-links | |
| compression-level: 0 | |
| retention-days: 7 | |
| - name: Publish input-identical built site | |
| if: >- | |
| steps.built-site.outcome != 'skipped' && | |
| steps.built-site.outputs.exact != 'true' && | |
| (github.ref == 'refs/heads/main' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository)) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ steps.built-site.outputs.name }} | |
| path: docs/.output/public | |
| compression-level: 0 | |
| retention-days: 7 | |
| - name: Upload artifact | |
| if: >- | |
| needs.changes.outputs.deployment-reuse != 'true' && | |
| (github.event_name == 'push' || | |
| (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')) | |
| uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 | |
| with: | |
| path: docs/.output/public | |
| prepare-codebook: | |
| needs: changes | |
| if: >- | |
| github.event_name != 'schedule' && | |
| needs.changes.outputs.result-reuse != 'true' && | |
| (needs.changes.outputs.docs == 'true' || needs.changes.outputs.source == 'true') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Restore resolved Codebook binary | |
| id: codebook-artifact | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| contract=$(scripts/ci/codebook-contract.sh) | |
| name="codebook-lsp-v1-${RUNNER_OS}-${RUNNER_ARCH}-${contract}" | |
| mkdir -p .ci-codebook | |
| artifact_id= | |
| if ! artifact_id=$( | |
| gh api "repos/${REPOSITORY}/actions/artifacts?name=${name}&per_page=10" \ | |
| --jq '.artifacts[] | select(.expired == false) | .id' \ | |
| | head -n 1 | |
| ); then | |
| echo "::warning::Codebook artifact lookup failed; preparing the binary" | |
| artifact_id= | |
| fi | |
| if [ -n "$artifact_id" ]; then | |
| gh api "repos/${REPOSITORY}/actions/artifacts/${artifact_id}/zip" \ | |
| > .ci-codebook/.artifact.zip | |
| python3 -m zipfile -e .ci-codebook/.artifact.zip .ci-codebook | |
| rm .ci-codebook/.artifact.zip | |
| hit=true | |
| else | |
| hit=false | |
| fi | |
| { | |
| echo "hit=$hit" | |
| echo "name=$name" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Build missing Codebook binary once | |
| if: steps.codebook-artifact.outputs.hit != 'true' | |
| uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4 | |
| with: | |
| version: "2026.6.11" | |
| cache_key_prefix: "mise-v2" | |
| cache_save: ${{ github.ref == 'refs/heads/main' }} | |
| install_args: "cargo-binstall cargo:codebook-lsp" | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - name: Stage Codebook binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ steps.codebook-artifact.outputs.hit }}" != "true" ]; then | |
| cp "$(mise which codebook-lsp)" .ci-codebook/codebook-lsp | |
| strip .ci-codebook/codebook-lsp | |
| fi | |
| chmod +x .ci-codebook/codebook-lsp | |
| - name: Publish resolved Codebook binary | |
| if: >- | |
| steps.codebook-artifact.outputs.hit != 'true' && | |
| (github.ref == 'refs/heads/main' || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository)) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ steps.codebook-artifact.outputs.name }} | |
| path: .ci-codebook/codebook-lsp | |
| compression-level: 0 | |
| retention-days: 7 | |
| spell-check-docs: | |
| needs: [changes, prepare-codebook] | |
| if: >- | |
| github.event_name != 'schedule' && | |
| needs.changes.outputs.result-reuse != 'true' && | |
| needs.changes.outputs.docs == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Download prepared Codebook binary | |
| uses: ./.github/actions/download-codebook | |
| - name: Run Codebook docs spell check | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| spell_files=() | |
| while IFS= read -r path; do | |
| spell_files+=("$path") | |
| done < <( | |
| git ls-files \ | |
| 'AGENTS.md' \ | |
| 'BRANCHING.md' \ | |
| 'COMMITS.md' \ | |
| 'CONTRIBUTING.md' \ | |
| 'PROJECT_STRUCTURE.md' \ | |
| 'PULL_REQUESTS.md' \ | |
| 'README.md' \ | |
| 'RULES.md' \ | |
| 'TESTING.md' \ | |
| '.github/**/*.md' \ | |
| '.github/**/*.yml' \ | |
| 'docs/**/*.md' \ | |
| 'docs/**/*.mdx' | |
| ) | |
| printf 'Spell checking %s docs/prose file(s):\n' "${#spell_files[@]}" | |
| printf ' %s\n' "${spell_files[@]}" | |
| chmod +x .ci-codebook/codebook-lsp | |
| .ci-codebook/codebook-lsp --root . lint --unique "${spell_files[@]}" | |
| spell-check-source: | |
| needs: [changes, prepare-codebook] | |
| if: >- | |
| github.event_name != 'schedule' && | |
| needs.changes.outputs.result-reuse != 'true' && | |
| needs.changes.outputs.source == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Download prepared Codebook binary | |
| uses: ./.github/actions/download-codebook | |
| - name: Run Codebook source spell check | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| spell_files=() | |
| while IFS= read -r path; do | |
| spell_files+=("$path") | |
| done < <( | |
| git ls-files \ | |
| 'crates/**/*.rs' \ | |
| 'crates/**/*.toml' \ | |
| 'docker/**/*.fish' \ | |
| 'docker/**/*.sh' \ | |
| 'docker/**/*.toml' \ | |
| 'docker/**/*.zsh' | |
| ) | |
| printf 'Spell checking %s source file(s):\n' "${#spell_files[@]}" | |
| printf ' %s\n' "${spell_files[@]}" | |
| chmod +x .ci-codebook/codebook-lsp | |
| .ci-codebook/codebook-lsp --root . lint --unique "${spell_files[@]}" | |
| deploy: | |
| # docs-link-check covers full-site lychee + docs build; repo-link-check | |
| # covers <RepoFile /> path validity. Both must pass before publishing. | |
| # | |
| # GitHub Pages can report a terminal `deployment_failed` for a valid | |
| # artifact, then accept the same artifact/SHA on the next deployment. | |
| # Keep retries scoped to that opaque Pages handoff instead of rerunning | |
| # the whole workflow or retrying source/build/link validation. | |
| needs: [changes, docs-link-check, repo-link-check] | |
| if: >- | |
| always() && | |
| (github.event_name == 'push' || | |
| (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')) && | |
| needs.changes.outputs.deployment-reuse != 'true' && | |
| needs.docs-link-check.result == 'success' && | |
| (needs.repo-link-check.result == 'success' || | |
| needs.repo-link-check.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| outputs: | |
| page-url: ${{ steps.deploy1.outputs.page_url || steps.deploy2.outputs.page_url || steps.deploy3.outputs.page_url }} | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deploy1.outputs.page_url || steps.deploy2.outputs.page_url || steps.deploy3.outputs.page_url }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Deploy to GitHub Pages | |
| id: deploy1 | |
| continue-on-error: true | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 | |
| - name: Back off before Pages deploy retry 2 | |
| if: steps.deploy1.outcome == 'failure' | |
| shell: bash | |
| run: | | |
| echo "::notice::GitHub Pages deployment failed on attempt 1; retrying after 30 seconds" | |
| sleep 30 | |
| - name: Deploy to GitHub Pages (retry 2) | |
| id: deploy2 | |
| if: steps.deploy1.outcome == 'failure' | |
| continue-on-error: true | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 | |
| - name: Back off before Pages deploy retry 3 | |
| if: steps.deploy1.outcome == 'failure' && steps.deploy2.outcome == 'failure' | |
| shell: bash | |
| run: | | |
| echo "::notice::GitHub Pages deployment failed on attempt 2; retrying after 60 seconds" | |
| sleep 60 | |
| - name: Deploy to GitHub Pages (retry 3) | |
| id: deploy3 | |
| if: steps.deploy1.outcome == 'failure' && steps.deploy2.outcome == 'failure' | |
| continue-on-error: true | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 | |
| - name: Require Pages deployment success | |
| if: steps.deploy1.outcome != 'success' && steps.deploy2.outcome != 'success' && steps.deploy3.outcome != 'success' | |
| shell: bash | |
| run: | | |
| echo "::error::GitHub Pages deployment failed after 3 attempts" | |
| exit 1 | |
| verify-deployed: | |
| needs: [changes, deploy] | |
| if: >- | |
| always() && | |
| needs.changes.outputs.deployment-reuse != 'true' && | |
| needs.deploy.result == 'success' && | |
| (github.event_name == 'push' || | |
| (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # Build a sitemap URL that is robust against future | |
| # actions/deploy-pages versions changing whether `page_url` | |
| # carries a trailing slash. The current contract has one, but | |
| # normalising explicitly costs nothing and removes the implicit | |
| # dependency. | |
| - name: Resolve sitemap URL | |
| id: sitemap | |
| shell: bash | |
| env: | |
| PAGE_URL: ${{ needs.deploy.outputs.page-url }} | |
| run: | | |
| set -euo pipefail | |
| echo "url=${PAGE_URL%/}/sitemap.xml" >> "$GITHUB_OUTPUT" | |
| - name: Verify deployed docs links | |
| uses: ./.github/actions/check-deployed-docs | |
| with: | |
| sitemap-url: ${{ steps.sitemap.outputs.url }} | |
| edit-url: ${{ env.JACKIN_REPO_EDIT_URL }} | |
| blob-url: ${{ env.JACKIN_REPO_BLOB_URL }} | |
| github-token: ${{ github.token }} | |
| external-links: "false" | |
| check-deployed: | |
| # Schedule verifies the live deployed site against the main-branch source. | |
| # Non-main workflow_dispatch runs validate the branch's source/build/link | |
| # surface only; remapping the deployed main site against a feature checkout | |
| # produces false missing-file failures after source moves. | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Verify deployed docs links | |
| uses: ./.github/actions/check-deployed-docs | |
| with: | |
| sitemap-url: ${{ env.DOCS_SITE_URL }}/sitemap.xml | |
| edit-url: ${{ env.JACKIN_REPO_EDIT_URL }} | |
| blob-url: ${{ env.JACKIN_REPO_BLOB_URL }} | |
| github-token: ${{ github.token }} | |
| # Skip on schedule because schedule runs only `check-deployed` and a | |
| # one-job aggregator on schedule has no value. | |
| docs-required: | |
| if: always() && github.event_name != 'schedule' | |
| needs: [changes, repo-link-check, docs-link-check, prepare-codebook, spell-check-docs, spell-check-source, deploy, verify-deployed, check-deployed] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: ./.github/actions/aggregate-needs | |
| with: | |
| needs-json: ${{ toJSON(needs) }} | |
| workflow-label: docs | |
| - name: Audit every Docs job's performance | |
| if: always() | |
| env: | |
| EXPECT_CLEAN: ${{ needs.changes.outputs.result-reuse == 'true' }} | |
| GH_TOKEN: ${{ github.token }} | |
| WORKFLOW_LABEL: Docs | |
| run: scripts/ci/audit-workflow-performance.sh | |
| - name: Stage input-identical successful Docs result | |
| if: >- | |
| needs.changes.outputs.result-reuse != 'true' && | |
| (github.ref == 'refs/heads/main' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository)) | |
| run: printf '%s\n' "source-sha=${GITHUB_SHA}" > docs-result.txt | |
| - name: Publish input-identical successful Docs result | |
| if: >- | |
| needs.changes.outputs.result-reuse != 'true' && | |
| (github.ref == 'refs/heads/main' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository)) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ needs.changes.outputs.result-name }} | |
| path: docs-result.txt | |
| compression-level: 0 | |
| retention-days: 7 |