diff --git a/.github/wiki b/.github/wiki index df4904389..46c969e74 160000 --- a/.github/wiki +++ b/.github/wiki @@ -1 +1 @@ -Subproject commit df4904389de0d7cac75713d946113a35376caf43 +Subproject commit 46c969e7462dee289401aaf6049b59ac1fd8c91c diff --git a/.github/workflows/reports-native.yml b/.github/workflows/reports-native.yml new file mode 100644 index 000000000..30182b787 --- /dev/null +++ b/.github/workflows/reports-native.yml @@ -0,0 +1,139 @@ +name: Native GitHub Pages Reports + +on: + workflow_call: + +permissions: + contents: read + +concurrency: + group: reports-pages + cancel-in-progress: false + +jobs: + reports: + name: Generate Reports + runs-on: ubuntu-latest + outputs: + logo-sha: ${{ steps.identity.outputs.logo_sha }} + dash-sha: ${{ steps.identity.outputs.dash_sha }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Checkout dev-tools workflow helpers + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: php-fast-forward/dev-tools + ref: main + path: .dev-tools-actions + persist-credentials: false + - name: Resolve PHP version + id: php + uses: ./.dev-tools-actions/.github/actions/php/resolve-version + - name: Setup PHP and plugin-free dependencies + uses: ./.dev-tools-actions/.github/actions/php/setup-composer + with: + php-version: ${{ steps.php.outputs.php-version }} + extensions: pcov, pcntl, dom, intl, zip + coverage: pcov + root-version: dev-main + install-options: --prefer-dist --no-progress --no-interaction --no-plugins --no-scripts + - name: Install signed documentation generator in isolation + shell: bash + run: | + command -v gpg + mkdir -p "$RUNNER_TEMP/phpdoc-tool" + php <<'PHP' + ['phpdocumentor/shim' => '^3.9'], + 'config' => ['allow-plugins' => ['phpdocumentor/shim' => true]], + ]; + $json = json_encode($manifest, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); + if (file_put_contents(getenv('RUNNER_TEMP') . '/phpdoc-tool/composer.json', $json) === false) { + throw new RuntimeException('Cannot write the documentation tool manifest.'); + } + PHP + COMPOSER_HOME="$RUNNER_TEMP/phpdoc-home" composer install --working-dir="$RUNNER_TEMP/phpdoc-tool" --no-scripts --no-interaction --prefer-dist --no-progress 2>&1 | tee "$RUNNER_TEMP/phpdoc-install.log" + grep -F 'PHAR signature successfully verified' "$RUNNER_TEMP/phpdoc-install.log" + install -m 0755 "$RUNNER_TEMP/phpdoc-tool/vendor/bin/phpdoc" vendor/bin/phpdoc + - name: Verify selected template version + run: | + php <<'PHP' + $version, + 'template_reference' => Composer\InstalledVersions::getReference($name), + ], JSON_THROW_ON_ERROR) . PHP_EOL; + PHP + - name: Generate documentation, coverage and metrics + env: + COMPOSER_ROOT_VERSION: dev-main + run: | + dev-tools reports --target=.dev-tools/reports --coverage=.dev-tools/reports/coverage --metrics=.dev-tools/reports/metrics + touch .dev-tools/reports/.nojekyll + rm -rf .dev-tools/reports/cache + test -f .dev-tools/reports/index.html + test -f .dev-tools/reports/coverage/index.html + test -f .dev-tools/reports/metrics/index.html + - name: Verify generated identity assets + id: identity + run: | + php <<'PHP' + 'dash_sha', 'fast-forward-logo-dark.svg' => 'logo_sha'] as $name => $key) { + $generated = '.dev-tools/reports/images/' . $name; + if (!is_file($generated) || hash_file('sha256', $generated) !== hash_file('sha256', $template . $name)) { + throw new RuntimeException('The published documentation must contain the selected template asset: ' . $name); + } + $outputs[] = $key . '=' . hash_file('sha256', $generated); + } + if (file_put_contents(getenv('GITHUB_OUTPUT'), implode(PHP_EOL, $outputs) . PHP_EOL, FILE_APPEND) === false) { + throw new RuntimeException('Cannot expose identity hashes for deployment verification.'); + } + PHP + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: .dev-tools/reports + + deploy: + name: Deploy Reports to Pages + needs: reports + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + outputs: + page-url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 + + verify: + name: Verify Reports Deployment + needs: [reports, deploy] + runs-on: ubuntu-latest + steps: + - name: Verify public documentation identity + env: + PAGE_URL: ${{ needs.deploy.outputs.page-url }} + LOGO_SHA: ${{ needs.reports.outputs.logo-sha }} + DASH_SHA: ${{ needs.reports.outputs.dash-sha }} + run: | + curl --fail --location --retry 8 --retry-all-errors --retry-delay 5 --max-time 30 "${PAGE_URL}images/fast-forward-logo-dark.svg" -o "$RUNNER_TEMP/published-logo.svg" + curl --fail --location --retry 8 --retry-all-errors --retry-delay 5 --max-time 30 "${PAGE_URL}images/dash-reading.png" -o "$RUNNER_TEMP/published-dash.png" + printf '%s %s\n' "$LOGO_SHA" "$RUNNER_TEMP/published-logo.svg" | sha256sum --check --strict + printf '%s %s\n' "$DASH_SHA" "$RUNNER_TEMP/published-dash.png" | sha256sum --check --strict diff --git a/CHANGELOG.md b/CHANGELOG.md index 149d65d75..247cd366e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add an opt-in native GitHub Pages reports workflow with an isolated deployment job and verified documentation identity assets from template 2.1.0 or newer. + ### Changed - Refresh README and documentation with contextual Dash artwork for the DevTools quality pipeline (#360). diff --git a/docs/usage/github-actions.rst b/docs/usage/github-actions.rst index 6ae24571c..03acf34c7 100644 --- a/docs/usage/github-actions.rst +++ b/docs/usage/github-actions.rst @@ -71,11 +71,18 @@ repository-specific verification; ``dev-tools:sync`` does not install it. For the protected-branch-safe preview and publish model, see :doc:`../advanced/branch-protection-and-bot-commits`. -Workflow-only consumers do not need to declare ``fast-forward/dev-tools`` as a -local Composer dependency. The shared ``setup-composer`` action prefers the +The standard reusable workflows support workflow-only consumers without a +local ``fast-forward/dev-tools`` Composer dependency. The shared +``setup-composer`` action prefers the consumer ``vendor/bin/dev-tools`` when it exists and otherwise exposes a ``dev-tools`` wrapper backed by the checked-out ``.dev-tools-actions`` source. +For Pages configured to use GitHub Actions, the optional +``reports-native.yml`` workflow requires consumer-local DevTools and a template +version of at least 2.1.0. Follow the `native Pages operator guide +`_ +before adopting it. The legacy ``reports.yml`` workflow remains unchanged. + Dependabot Required Test Statuses --------------------------------- diff --git a/resources/github-actions-optional/reports-native.md b/resources/github-actions-optional/reports-native.md new file mode 100644 index 000000000..e1bb5f94f --- /dev/null +++ b/resources/github-actions-optional/reports-native.md @@ -0,0 +1,13 @@ +# Native GitHub Pages reports + +Use this optional wrapper only when the consumer's Pages settings use GitHub Actions as the build source. Keep the existing `reports.yml` reusable workflow for consumers publishing from `gh-pages`. + +Copy `reports-native.yml` into the consumer as `.github/workflows/reports.yml`. Review existing triggers before replacing a customized workflow. The wrapper builds PR artifacts, and deploys only `refs/heads/main`; it does not implement preview publication or cleanup schedules. It is intentionally outside automatic `dev-tools:sync` resources. + +This wrapper requires the consumer to install `fast-forward/dev-tools` locally through its own Composer manifest. It verifies the template under that consumer's vendor directory; it is not a projectless fallback-runtime wrapper. The ten audited native Pages consumers satisfy this precondition. + +The caller's `contents: read`, `pages: write` and `id-token: write` values are ceilings. The build job overrides them to read-only; the checkout-free deployment job alone receives Pages/OIDC writes. Package installation skips plugins and scripts. The official phpDocumentor shim is installed in a separate temporary Composer project allowing only that plugin, verifies the PHAR signature, and exposes the generated binary under the consumer's vendor/bin. The consumer's plugin allowlist is preserved. + +The build requires template 2.1.0 or newer, records its selected version/reference and validates rendered identity assets against the installed template. Deployment verification compares the public assets' SHA-256 values with those produced by the build. It preserves generation of documentation, coverage and metrics; failures stop publication. + +Existing ^2.0 template constraints accept 2.1.0. Consumers with a committed lock must update `fast-forward/phpdoc-bootstrap-template` before running this workflow. A locally cached vendor directory can still contain an older template; use clean dependency resolution and inspect the logged version. diff --git a/resources/github-actions-optional/reports-native.yml b/resources/github-actions-optional/reports-native.yml new file mode 100644 index 000000000..9e237e078 --- /dev/null +++ b/resources/github-actions-optional/reports-native.yml @@ -0,0 +1,17 @@ +name: "Fast Forward Reports" + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +# Reusable job ceilings; only the isolated deploy job can write Pages or request OIDC. +permissions: + contents: read + pages: write + id-token: write + +jobs: + reports: + uses: php-fast-forward/dev-tools/.github/workflows/reports-native.yml@main