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
123 changes: 123 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: CI

on:
push:
branches: [ main ]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
php:
name: PHP ${{ matrix.php }} — lint, analyse
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# The floor is what composer.json requires; the ceiling is what a current
# Altis or VIP environment actually runs.
php: [ '8.1', '8.4' ]

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:v2

- name: Install dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0

- name: PHPCS
run: composer lint

- name: PHPStan
run: composer analyze

php-tests:
name: PHPUnit
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm

- name: Set up PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.1'
coverage: none
tools: composer:v2

- name: Install dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0

- run: npm ci

# The plugin is glue over the pattern registry, the block renderer and the
# roles API, so the suite runs against a real WordPress rather than mocks.
# wp-env brings that up in Docker, which the runner already provides.
- name: Start WordPress
run: npm run env:start

- name: PHPUnit
run: npm run test:php

- name: WordPress debug log
if: failure()
run: npx wp-env run tests-cli -- cat /var/www/html/wp-content/debug.log || true

javascript:
name: JavaScript — lint, test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm

- run: npm ci

- name: ESLint
run: npm run lint:js

- name: Node test runner
run: npm run test:js

versions:
name: Versions agree
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# One tag drives the Composer package, the npm package and the action, so a
# manifest served by one version and read by another is the failure mode
# this check exists to prevent. See CONTRIBUTING.md.
- name: package.json and plugin.php declare the same version
run: |
npm_version="$(node -p "require('./package.json').version")"
plugin_version="$(sed -n 's/^ \* Version: *//p' plugin.php | head -1 | tr -d '[:space:]')"

echo "package.json: ${npm_version}"
echo "plugin.php: ${plugin_version}"

if [ "${npm_version}" != "${plugin_version}" ]; then
echo "::error file=plugin.php::Version mismatch: package.json says ${npm_version}, plugin.php says ${plugin_version}."
exit 1
fi
59 changes: 59 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy documentation to GitHub Pages

on:
push:
branches: [ main ]
paths:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch:

# Enough for the deployment to GitHub Pages, and no more.
permissions:
contents: read
pages: write
id-token: write

# One deployment at a time; a newer push supersedes an in-flight one.
concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
working-directory: ./docs
ruby-version: '3.3'
bundler-cache: true

- name: Set up Pages
id: pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0

- name: Build with Jekyll
working-directory: ./docs
env:
JEKYLL_ENV: production
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"

- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: ./docs/_site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
84 changes: 84 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Publish the npm package when a release is published.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm worried this won't run automatically like you think, since there's many scenarios where dispatched actions can't trigger each other: It's possible that creating the release with tag-and-release (versus manually) doesn't actually dispatch this subsequent action. Worth testing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think you were right. I had to configure this workflow as a Trusted Publish in the npm package settings. Going to try tagging and releasing once I merge this to see if it gets published correctly to npm.

#
# Packagist needs nothing here: it picks `humanmade/wp-pattern-library` up from
# the same tag through its GitHub hook. This workflow covers the other half of
# the pair, so one tag really does mean the same code in both registries.
#
# Authentication is npm trusted publishing: npmjs.com holds a trusted publisher
# for this package naming this repository, this workflow file and the `npm`
# environment, and npm exchanges the OIDC token minted below for a short-lived
# publish credential. There is no NPM_TOKEN to store or rotate, and the four
# facts npm matches on are all visible here — moving or renaming this file, or
# dropping the environment, breaks publishing until the trusted publisher is
# updated to match.
#
# The `release: published` trigger only fires for a release created by a personal
# access token. GitHub deliberately does not let the automatic GITHUB_TOKEN
# trigger further workflow runs, so tag-and-release.yml has to cut the release
# with its RELEASE_TOKEN secret for this to run on its own. Without that secret,
# dispatch this workflow by hand after the release exists.

name: Publish to npm

on:
release:
types: [ published ]
workflow_dispatch:

permissions:
contents: read
# Required to mint the OIDC token npm exchanges for publish rights. It is also
# what ties the published tarball to this workflow run and commit, which npm
# records as provenance.
id-token: write

jobs:
npm:
name: npm publish
runs-on: ubuntu-latest
environment: npm

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.release.tag_name || github.ref }}

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
registry-url: https://registry.npmjs.org
cache: npm

# Trusted publishing needs npm 11.5.1 or newer. Node 24 ships something
# recent enough today, but the bundled version moves with the runner
# image, so it is pinned forward here rather than left to chance.
- name: Use an npm that can publish over OIDC
run: npm install --global npm@^11.5.1

- run: npm ci

# Publishing is irreversible, so the gate that CI applies to every pull
# request is applied once more against the actual tag being released.
- name: The tag, package.json and plugin.php agree
run: |
set -euo pipefail

tag="${{ github.event.release.tag_name }}"
npm_version="$(node -p "require('./package.json').version")"
plugin_version="$(sed -n 's/^ \* Version: *//p' plugin.php | head -1 | tr -d '[:space:]')"

if [ -n "${tag}" ] && [ "${tag#v}" != "${npm_version}" ]; then
echo "::error::Tag ${tag} does not match package.json ${npm_version}."
exit 1
fi

if [ "${plugin_version}" != "${npm_version}" ]; then
echo "::error::plugin.php ${plugin_version} does not match package.json ${npm_version}."
exit 1
fi

# No NODE_AUTH_TOKEN: the credential comes from the OIDC exchange above.
# Provenance is generated automatically for a trusted-publisher release,
# so `--provenance` is not passed either.
- name: Publish
run: npm publish --access public
73 changes: 73 additions & 0 deletions .github/workflows/tag-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Cut a release: tag the target branch, then publish a GitHub release from it.
#
# The tagging and release mechanics live in humanmade/hm-github-actions, shared
# with ~23 other Human Made plugin repositories. Only the version check below is
# specific to this repository: one tag drives the Composer package, the npm
# package and the action, so those have to agree before the tag exists —
# afterwards it is too late.
#
# Publishing to npm is handled by publish.yml, which runs when the release this
# workflow creates is published. That only fires if the release is created by a
# personal access token: GitHub deliberately does not let the automatic
# GITHUB_TOKEN trigger further workflow runs. Set a RELEASE_TOKEN secret holding
# a PAT with `contents: write` on this repository, or dispatch "Publish to npm"
# by hand after cutting the release.

name: Tag and Release

on:
workflow_dispatch:
inputs:
version:
description: 'Version tag, e.g. v0.4.0. Must match package.json and plugin.php.'
required: true
target_branch:
description: 'Branch to tag.'
default: main
required: true

jobs:
check_versions:
name: Check the versions agree
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.target_branch }}

- name: The tag, package.json and plugin.php agree
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail

if [ -n "${VERSION##v*}" ]; then
echo "::error::Version must be tag-shaped, e.g. v0.4.0; got ${VERSION}."
exit 1
fi
bare="${VERSION#v}"

npm_version="$(node -p "require('./package.json').version")"
plugin_version="$(sed -n 's/^ \* Version: *//p' plugin.php | head -1 | tr -d '[:space:]')"

if [ "${npm_version}" != "${bare}" ] || [ "${plugin_version}" != "${bare}" ]; then
echo "::error::Version mismatch. Tag ${VERSION}, package.json ${npm_version}, plugin.php ${plugin_version}."
exit 1
fi

tag_and_release:
name: Tag and Release
needs: check_versions
permissions:
contents: write
uses: humanmade/hm-github-actions/.github/workflows/tag-and-release.yml@70b862a2bc0669a2123d5bf3d909642c8dc07483 # v0.5.0
with:
version: ${{ inputs.version }}
target_branch: ${{ inputs.target_branch }}
secrets:
# Falls back to GITHUB_TOKEN when unset — at the cost of publish.yml not
# being triggered by the release this creates.
token: ${{ secrets.RELEASE_TOKEN }}
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,22 @@ node_modules/
vendor/
.DS_Store
*.log

# PHPUnit
.phpunit.result.cache
.phpunit.cache/

# Jekyll build output for the documentation site
docs/_site/
docs/.jekyll-cache/
docs/.jekyll-metadata
docs/Gemfile.lock

# Local wp-env overrides
.wp-env.override.json

# Playwright MCP scratch output
.playwright-mcp/

# Local bundler configuration for the docs site
docs/.bundle/
12 changes: 12 additions & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"phpVersion": "8.2",
"plugins": [
"."
],
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"SCRIPT_DEBUG": true,
"WP_ENVIRONMENT_TYPE": "local"
}
}
Loading