diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 922319e..f6abe6d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -176,7 +176,14 @@ jobs: build: needs: identity - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 30 + env: + LIBTMUX_DOCS_PORT: ruby + LIBTMUX_DOCS_VERSION: ${{ matrix.version }} + LIBTMUX_DOCS_SOURCE_REPOSITORY: ${{ needs.identity.outputs.source-repository }} + LIBTMUX_DOCS_CHECKOUT_RUBY: ${{ github.workspace }}/port + LIBTMUX_DOCS_GENERATOR_CHECKOUT: ${{ github.workspace }}/docs-generator strategy: fail-fast: false matrix: ${{ fromJSON(needs.identity.outputs.matrix) }} @@ -186,9 +193,11 @@ jobs: repository: ${{ needs.identity.outputs.source-repository }} ref: ${{ needs.identity.outputs.source-ref }} fetch-depth: 0 + path: port persist-credentials: false - id: source + working-directory: port env: SELECTED_REF: ${{ needs.identity.outputs.source-ref }} run: | @@ -204,40 +213,58 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.sha }} - path: .docs-generator + path: docs-generator persist-credentials: false - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: libtmux/docs - ref: 8fda4b89071621b9ed2c4722a68d61f6ecf9c1c4 - path: .site + ref: 99532a7ec07ae3af3ef399941e81a1d6424720cb + path: docs persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '26' + + - name: Snapshot source and exporter before native generation + working-directory: docs + env: + LIBTMUX_DOCS_SOURCE_SHA: ${{ steps.source.outputs.sha }} + run: node scripts/publication-provenance.mjs snapshot "$RUNNER_TEMP/build-inputs.json" "$PWD" + - uses: ruby/setup-ruby@762794c140bbeda0f1224786aa33b4b46783a6c1 # v1.326.0 with: ruby-version: '4.0.7' - bundler-cache: true + working-directory: port + bundler-cache: false + + - name: Install the selected Ruby bundle + working-directory: port + env: + BUNDLE_FROZEN: 'true' + run: bundle install + + - name: Install tmux for native MCP discovery + run: | + sudo apt-get update -qq + sudo apt-get install --no-install-recommends -y -qq tmux - name: Export the selected Ruby source + working-directory: port run: | - bundle exec ruby .docs-generator/scripts/export-docs \ - --source "$GITHUB_WORKSPACE" \ - --output "$GITHUB_WORKSPACE/docs/_build" + bundle exec ruby "$GITHUB_WORKSPACE/docs-generator/scripts/export-docs" \ + --source "$GITHUB_WORKSPACE/port" \ + --output "$GITHUB_WORKSPACE/port/docs/_build" - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 with: - package_json_file: .site/package.json - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: '26' - cache: pnpm - cache-dependency-path: .site/pnpm-lock.yaml + package_json_file: docs/package.json - run: pnpm install --frozen-lockfile - working-directory: .site + working-directory: docs - name: Build the selected Ruby documentation tree - working-directory: .site + working-directory: docs env: LIBTMUX_DOCS_PORT: ruby LIBTMUX_DOCS_VERSION: ${{ matrix.version }} @@ -246,13 +273,30 @@ jobs: LIBTMUX_DOCS_RESOLVES_TO: ${{ matrix.resolvesTo }} LIBTMUX_DOCS_SOURCE_REF: ${{ needs.identity.outputs.source-ref }} LIBTMUX_DOCS_SOURCE_SHA: ${{ steps.source.outputs.sha }} - LIBTMUX_DOCS_CHECKOUT_RUBY: ${{ github.workspace }} + LIBTMUX_DOCS_INPUT_SNAPSHOT: ${{ runner.temp }}/build-inputs.json run: ./scripts/build-site.sh --ports ruby --versions "${{ matrix.version }}" --skip-refs --skip-pagefind - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + - id: content + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: docs-ruby-${{ matrix.version }} - path: .site/_site/en/ruby/${{ matrix.version }} + path: docs/_site/en/ruby/${{ matrix.version }} + if-no-files-found: error + retention-days: 1 + include-hidden-files: true + + - name: Describe the uploaded artifact + working-directory: docs + env: + ARTIFACT_ID: ${{ steps.content.outputs.artifact-id }} + ARTIFACT_DIGEST: ${{ steps.content.outputs.artifact-digest }} + ARTIFACT_NAME: docs-ruby-${{ matrix.version }} + SOURCE_SHA: ${{ steps.source.outputs.sha }} + run: node scripts/publication-provenance.mjs descriptor "$RUNNER_TEMP/publication.json" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docs-ruby-${{ matrix.version }}-publication + path: ${{ runner.temp }}/publication.json if-no-files-found: error retention-days: 1 @@ -265,7 +309,7 @@ jobs: permissions: contents: read id-token: write - uses: libtmux/docs/.github/workflows/reusable-deploy.yml@8fda4b89071621b9ed2c4722a68d61f6ecf9c1c4 + uses: libtmux/docs/.github/workflows/reusable-deploy.yml@99532a7ec07ae3af3ef399941e81a1d6424720cb with: path-prefix: ruby/${{ matrix.version }} artifact: docs-ruby-${{ matrix.version }} diff --git a/test/unit/docs_workflow_test.rb b/test/unit/docs_workflow_test.rb index b961756..15756ee 100644 --- a/test/unit/docs_workflow_test.rb +++ b/test/unit/docs_workflow_test.rb @@ -1,12 +1,59 @@ # frozen_string_literal: true require "minitest/autorun" +require "yaml" class DocsWorkflowTest < Minitest::Test + def workflow + YAML.load_file(File.expand_path("../../.github/workflows/docs.yml", __dir__)) + end + def test_preview_publication_uses_the_preview_role workflow = File.read(File.expand_path("../../.github/workflows/docs.yml", __dir__)) assert_includes workflow, "role-arn: ${{ matrix.environment == 'docs-preview' && secrets.LIBTMUX_DOCS_PREVIEW_ROLE_ARN || secrets.LIBTMUX_DOCS_ROLE_ARN }}" end + + def test_source_exporter_and_docs_use_separate_checkouts + build = workflow.fetch("jobs").fetch("build") + steps = build.fetch("steps") + checkouts = steps.select { |step| step.fetch("uses", "").start_with?("actions/checkout@") } + assert_equal %w[port docs-generator docs], checkouts.map { |step| step.fetch("with").fetch("path") } + assert_equal "${{ github.workspace }}/port", build.fetch("env").fetch("LIBTMUX_DOCS_CHECKOUT_RUBY") + assert_equal "${{ github.workspace }}/docs-generator", build.fetch("env").fetch("LIBTMUX_DOCS_GENERATOR_CHECKOUT") + snapshot = steps.index { |step| step.fetch("run", "").include?("publication-provenance.mjs snapshot") } + exporter = steps.index { |step| step.fetch("name", "").start_with?("Export the selected") } + assert snapshot && exporter && snapshot < exporter + assert_equal "port", steps.fetch(exporter).fetch("working-directory") + assert_includes steps.fetch(exporter).fetch("run"), '--source "$GITHUB_WORKSPACE/port"' + tree = steps.find { |step| step.fetch("name", "").start_with?("Build the selected") } + assert_equal "${{ runner.temp }}/build-inputs.json", tree.fetch("env").fetch("LIBTMUX_DOCS_INPUT_SNAPSHOT") + assert_equal "${{ steps.source.outputs.sha }}", tree.fetch("env").fetch("LIBTMUX_DOCS_SOURCE_SHA") + end + + def test_descriptor_uses_the_exact_content_upload + jobs = workflow.fetch("jobs") + steps = jobs.fetch("build").fetch("steps") + content = steps.find { |step| step["id"] == "content" }.fetch("with") + assert_equal true, content.fetch("include-hidden-files") + descriptor = steps.find { |step| step.fetch("run", "").include?("publication-provenance.mjs descriptor") } + assert_equal "${{ steps.content.outputs.artifact-id }}", descriptor.fetch("env").fetch("ARTIFACT_ID") + assert_equal "${{ steps.content.outputs.artifact-digest }}", descriptor.fetch("env").fetch("ARTIFACT_DIGEST") + assert_equal content.fetch("name"), descriptor.fetch("env").fetch("ARTIFACT_NAME") + assert steps.any? { |step| step.dig("with", "name") == "#{content.fetch('name')}-publication" } + docs = steps.find { |step| step.dig("with", "repository") == "libtmux/docs" } + assert_match(/\A[0-9a-f]{40}\z/, docs.fetch("with").fetch("ref")) + assert jobs.fetch("publish").fetch("uses").end_with?("@#{docs.fetch('with').fetch('ref')}") + end + + def test_arbitrary_source_builds_do_not_restore_caches + steps = workflow.fetch("jobs").fetch("build").fetch("steps") + ruby = steps.find { |step| step.fetch("uses", "").start_with?("ruby/setup-ruby@") } + assert_equal false, ruby.fetch("with").fetch("bundler-cache") + node = steps.find { |step| step.fetch("uses", "").start_with?("actions/setup-node@") } + refute node.fetch("with").key?("cache") + bundle = steps.find { |step| step["run"] == "bundle install" } + assert_equal "true", bundle.fetch("env").fetch("BUNDLE_FROZEN") + end end