Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/wiki
Submodule wiki updated from df4904 to 46c969
139 changes: 139 additions & 0 deletions .github/workflows/reports-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Native GitHub Pages Reports

on:
workflow_call:

permissions:
contents: read

concurrency:
group: reports-pages
cancel-in-progress: false
Comment on lines +10 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Separate deployment and pull-request concurrency groups

Because the packaged wrapper triggers both pull requests and pushes to main, this workflow-level constant places every build and production deployment in the same concurrency group. GitHub allows only one running and one pending run by default, and a newer run replaces the pending one even when cancel-in-progress is false (GitHub concurrency documentation). Thus, while one PR build runs, a pending main deployment can be canceled by another PR update and never publish; use event/ref-specific groups or apply deployment concurrency only to the deploy job.

Useful? React with 👍 / 👎.


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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fetch history before generating Git metrics

This checkout omits fetch-depth, so the pinned action fetches only one commit by default (actions/checkout documentation). MetricsCommand nevertheless always invokes PhpMetrics with --git git (src/Console/Command/MetricsCommand.php:136-142), causing published history-derived metrics to reflect only the shallow tip rather than the repository history. The legacy reports workflow already avoids this with fetch-depth: 0; apply the same setting to this consumer checkout.

Useful? React 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'
<?php
$manifest = [
'require' => ['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'
<?php
require 'vendor/autoload.php';
$name = 'fast-forward/phpdoc-bootstrap-template';
$version = Composer\InstalledVersions::getPrettyVersion($name);
if ($version === null || version_compare(ltrim($version, 'v'), '2.1.0', '<')) {
throw new RuntimeException('The documentation design system requires template 2.1.0 or newer.');
}
echo json_encode([
'template_version' => $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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow report builds without coverage output

When a consumer has no testable PHP source, ReportsCommand deliberately skips coverage and returns success after generating the applicable report surfaces (src/Console/Command/ReportsCommand.php:194-201), so coverage/index.html is absent. This unconditional assertion then fails an otherwise successful native Pages build, although the operator guide does not require tests or autoloaded PHP source; detect generated surfaces as the legacy reports workflow does instead of requiring coverage.

Useful? React with 👍 / 👎.

test -f .dev-tools/reports/metrics/index.html
- name: Verify generated identity assets
id: identity
run: |
php <<'PHP'
<?php
$template = 'vendor/fast-forward/phpdoc-bootstrap-template/data/';
$outputs = [];
foreach (['dash-reading.png' => '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
Comment on lines +138 to +139

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry checksum mismatches during Pages propagation

When a deployment changes either asset, Pages or its CDN can temporarily return the previous file with HTTP 200. These one-shot checksum commands then fail immediately because curl considers the preceding downloads successful; --retry-all-errors only retries when curl encounters an error, as documented in the curl manual. Wrap each download and checksum comparison in the bounded retry loop so normal propagation does not mark a successful deployment as failed.

Useful? React with 👍 / 👎.

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
11 changes: 9 additions & 2 deletions docs/usage/github-actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
<https://github.com/php-fast-forward/dev-tools/blob/main/resources/github-actions-optional/reports-native.md>`_
before adopting it. The legacy ``reports.yml`` workflow remains unchanged.

Dependabot Required Test Statuses
---------------------------------

Expand Down
13 changes: 13 additions & 0 deletions resources/github-actions-optional/reports-native.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions resources/github-actions-optional/reports-native.yml
Original file line number Diff line number Diff line change
@@ -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
Loading