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
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,41 @@ jobs:
echo "::error file=plugin.php::Version mismatch: package.json says ${npm_version}, plugin.php says ${plugin_version}."
exit 1
fi

# The action pins in the docs are what consumers copy, so a stale one
# quietly sends people to the previous release. This is the check that
# CONTRIBUTING.md used to ask a human to run by hand.
- name: The documented action pins name the current version
run: |
version="v$(node -p "require('./package.json').version")"

stale="$(git grep -n 'wp-pattern-library@v' \
-- README.md docs/05-github-action.md examples/ \
| grep -v "wp-pattern-library@${version}" || true)"

if [ -n "${stale}" ]; then
echo "::error::These action pins should read ${version}:"
echo "${stale}"
exit 1
fi

# The plugin writes the manifest and the CLI reads it. Their agreement is
# the one compatibility contract that is not the release version, and it
# otherwise surfaces only as a failed run in a consumer's repository.
- name: The manifest version the plugin writes is the one the CLI reads
run: |
php_version="$(sed -n "s/^const MANIFEST_VERSION = \([0-9]\+\);/\1/p" inc/manifest.php)"
js_version="$(sed -n "s/^const SUPPORTED_MANIFEST_VERSION = \([0-9]\+\);/\1/p" src/manifest.mjs)"

echo "inc/manifest.php: ${php_version}"
echo "src/manifest.mjs: ${js_version}"

if [ -z "${php_version}" ] || [ -z "${js_version}" ]; then
echo "::error::Could not read a manifest version from one of the pair; has a declaration been reformatted?"
exit 1
fi

if [ "${php_version}" != "${js_version}" ]; then
echo "::error file=src/manifest.mjs::Manifest version mismatch: the plugin writes ${php_version}, the CLI reads ${js_version}."
exit 1
fi
28 changes: 15 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,30 +244,32 @@ matter. Keep the two in step.
Releases are cut from `main`. One tag drives all three artifacts, so the
versions have to agree before the tag exists.

1. **Bump the version, in one commit.** Two of these are checked by CI, which
fails the pull request if they disagree. The other three are documentation,
and nothing will tell you if you forget them:

| File | What to change | Checked? |
| ---- | -------------- | -------- |
| `package.json` | `version` | yes |
| `plugin.php` | the `Version:` header | yes |
| `README.md` | the `uses: humanmade/wp-pattern-library@vX.Y.Z` pin | no |
| `docs/05-github-action.md` | the same pin, twice | no |
| `examples/refresh-pattern-library.yml` | the same pin | no |
1. **Bump the version, in one commit.** All five are checked by CI, which fails
the pull request if they disagree:

| File | What to change |
| ---- | -------------- |
| `package.json` | `version` |
| `plugin.php` | the `Version:` header |
| `README.md` | the `uses: humanmade/wp-pattern-library@vX.Y.Z` pin |
| `docs/05-github-action.md` | the same pin, twice |
| `examples/refresh-pattern-library.yml` | the same pin |

The action pins are what consumers copy, so a stale one sends people to the
previous release. List all four and check they read as the new version:
previous release. To see all four before pushing:

```bash
git grep -n "wp-pattern-library@v" -- README.md docs/05-github-action.md examples/
```

The action itself needs no bump. It runs the package version matching the ref
it was used at, read from `github.action_ref` at run time.

Then confirm nothing anywhere still names the version you bumped *from*.
Substitute the old version; it should print no lines:

```bash
git grep -n "0\.3\.0" -- ':(exclude)package-lock.json' ':(exclude)composer.lock'
git grep -n "0\.4\.0" -- ':(exclude)package-lock.json' ':(exclude)composer.lock'
```
2. **Merge to `main`.**
3. **Run the [Tag and Release](../../actions/workflows/tag-and-release.yml)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Three separate packages ship from this repository, all from the same tag:
| ---------------- | ------------------------------- | ----------------------------------------------- |
| WordPress plugin | `humanmade/wp-pattern-library` | `composer require humanmade/wp-pattern-library` |
| CLI | `@humanmade/wp-pattern-library` | `npm install -D @humanmade/wp-pattern-library` |
| GitHub Action | `humanmade/wp-pattern-library` | `uses: humanmade/wp-pattern-library@v0.4.0` |
| GitHub Action | `humanmade/wp-pattern-library` | `uses: humanmade/wp-pattern-library@v0.4.1` |

The plugin belongs on the site you capture from. The CLI runs wherever you generate the library — your machine, or CI — so the site itself never needs Node. The action is a thin wrapper around the CLI for running the whole thing in a workflow; pin it to a released tag, as there is deliberately no moving `@v1` tag while the package is pre-1.0.

Expand Down
73 changes: 62 additions & 11 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,65 @@ inputs:
required: false
default: '.'
version:
description: Version of @humanmade/wp-pattern-library to run.
description: >
Version of @humanmade/wp-pattern-library to run. Defaults to the ref this
action was used at, so `uses: humanmade/wp-pattern-library@v1.2.3` runs
CLI 1.2.3 and the two can never drift apart. A branch or commit ref has no
version to read, and falls back to `latest`.
required: false
default: 'latest'
default: ''

runs:
using: composite
steps:
# Playwright needs a browser. The official container image ships one, so skip
# the install when the workflow already runs in it.
# The CLI is resolved before the browser, and into a directory of its own.
#
# Its own, because the project being documented may depend on a different
# Playwright, and its node_modules is not this action's to write into.
#
# Before, because the browser that gets installed below has to match the
# Playwright this install resolves. Anything that guesses at that match
# ahead of time — a container image tag, a warm npx cache — is correct only
# until Playwright cuts its next minor release.
- name: Install the CLI
shell: bash
env:
REQUESTED_VERSION: ${{ inputs.version }}
ACTION_REF: ${{ github.action_ref }}
run: |
set -euo pipefail

version="${REQUESTED_VERSION}"
if [ -z "${version}" ]; then
case "${ACTION_REF}" in
v[0-9]*) version="${ACTION_REF#v}" ;;
*) version="latest" ;;
esac
fi
echo "Running @humanmade/wp-pattern-library@${version}"

dir="${RUNNER_TEMP}/wp-pattern-library"
mkdir -p "${dir}"
cd "${dir}"
npm init --yes > /dev/null
npm install --no-audit --no-fund "@humanmade/wp-pattern-library@${version}"

echo "${dir}/node_modules/.bin" >> "${GITHUB_PATH}"

# Run from the CLI's tree, so `playwright` here is the one the CLI will
# load. --with-deps installs system libraries through apt, which needs sudo
# and a minute of network; the official Playwright image already has them
# and says so by exporting PLAYWRIGHT_BROWSERS_PATH.
- name: Install Chromium
shell: bash
run: |
if [ -z "${PLAYWRIGHT_BROWSERS_PATH:-}" ] && [ ! -d "$HOME/.cache/ms-playwright" ]; then
npx --yes playwright install --with-deps chromium
set -euo pipefail

cd "${RUNNER_TEMP}/wp-pattern-library"
if [ -n "${PLAYWRIGHT_BROWSERS_PATH:-}" ]; then
npx playwright install chromium
else
npx playwright install --with-deps chromium
fi

- name: Build the pattern library
Expand All @@ -51,11 +96,17 @@ runs:
PATTERN_LIBRARY_WP_APP_PASSWORD: ${{ inputs.app-password }}
# Passed through the environment, never interpolated into the script: these
# carry secrets, and a value with a newline or a quote in it would otherwise
# be run as shell.
# be run as shell. OUTPUT_PATH is not a secret, but the same reasoning
# applies to any input a caller controls.
PATTERN_LIBRARY_EXTRA_HEADERS: ${{ inputs.extra-headers }}
OUTPUT_PATH: ${{ inputs.output-path }}
run: |
args=""
if [ -n "${{ inputs.output-path }}" ]; then
args="--output-dir=${{ inputs.output-path }}"
set -euo pipefail

# On PATH from the install step above, so this is the version resolved
# there rather than a second, independent resolution.
if [ -n "${OUTPUT_PATH}" ]; then
pattern-library build "--output-dir=${OUTPUT_PATH}"
else
pattern-library build
fi
npx --yes @humanmade/wp-pattern-library@${{ inputs.version }} build $args
32 changes: 23 additions & 9 deletions docs/05-github-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@ permissions:
jobs:
refresh:
runs-on: ubuntu-latest
# Ships Chromium and its system dependencies preinstalled, which saves a
# minute or so of apt traffic on every run.
container: mcr.microsoft.com/playwright:v1.62.1-noble

steps:
- uses: actions/checkout@v7
Expand All @@ -94,7 +91,7 @@ jobs:
node-version: 24

- name: Build the pattern library
uses: humanmade/wp-pattern-library@v0.4.0
uses: humanmade/wp-pattern-library@v0.4.1
with:
site-url: ${{ vars.PATTERN_LIBRARY_SITE }}
username: ${{ secrets.PATTERN_LIBRARY_WP_USER }}
Expand Down Expand Up @@ -124,9 +121,26 @@ To commit directly to a branch instead of opening a pull request, drop the last
| `output-path` | no | Overrides `outputDir` from the config file. |
| `working-directory` | no | Where `pattern-library.config.js` lives. Default `.`. |
| `extra-headers` | no | Headers for an origin behind an access proxy. |
| `version` | no | Version of the NPM package to run. Default `latest`. |
| `version` | no | Version of the NPM package to run. See below. |

Pin `version` to the same release as the action reference, so a run can't mix versions. Consuming workflows should pin the action to a released tag — there's deliberately no moving `@v1` tag while the package is pre-1.0.
Leave `version` alone. By default the action runs the package version matching
the ref it was used at, so `@v0.4.1` runs CLI 0.4.1 and the two cannot drift.
Set it only to test an unreleased package against a released action. A branch or
commit ref has no version to read and falls back to `latest`.

Pin the action to a released tag — there's deliberately no moving `@v1` tag
while the package is pre-1.0.

## Chromium

The action installs Chromium itself, matched to the Playwright version the CLI
resolves. Nothing to configure, and nothing that goes stale.

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.

Do we need to bump this in our consuming projects every time chromium updates? That feels clunky, but I'm probably misunderstanding

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.

No, this way we just need to remove the "container" from the consuming workflow.

Previously I was using container: mcr.microsoft.com/playwright:v1.62.1-noble which came with its own versions of Playwright and Chromium. This saved a minute or so installing those dependencies on the container, but it made the version of the action tightly coupled to container specified in the workflow that calls it.


Running the job in `mcr.microsoft.com/playwright` is supported and saves the
system-library half of that install, but it is only ever an optimisation: the
image's own Chromium is used when its Playwright happens to match, and a
matching build is fetched when it doesn't. Pick the image tag to suit the
runner, not the CLI.

## Triggers

Expand Down Expand Up @@ -234,7 +248,7 @@ Send the proxy's credentials alongside the application password:

```yaml
- name: Build the pattern library
uses: humanmade/wp-pattern-library@v0.4.0
uses: humanmade/wp-pattern-library@v0.4.1
with:
site-url: ${{ vars.PATTERN_LIBRARY_SITE }}
username: ${{ secrets.PATTERN_LIBRARY_WP_USER }}
Expand All @@ -261,13 +275,13 @@ These headers go on the manifest request and on every browser request made **to

## Running it elsewhere

The action is a thin wrapper. The CLI is a plain Node program, so GitLab CI, Bitbucket Pipelines, Buildkite or a cron job on a box all work the same way: install Node 24 and Chromium, set the three environment variables, run `npx @humanmade/wp-pattern-library build`.
The action is a thin wrapper. The CLI is a plain Node program, so GitLab CI, Bitbucket Pipelines, Buildkite or a cron job on a box all work the same way: install Node 24, run `npx playwright install --with-deps chromium` from the same install tree as the package, set the three environment variables, and run `npx @humanmade/wp-pattern-library build`.

## Troubleshooting

**The run captures the wrong site.** `PATTERN_LIBRARY_SITE` is a repository variable, so it's visible in the workflow log — check what the run actually printed.

**Chromium fails to install.** Use the `mcr.microsoft.com/playwright` container image, which ships it. The action skips its own install when it detects one.
**Chromium fails to install.** The install needs `apt` and passwordless `sudo` for its system libraries, which GitHub-hosted runners provide and a locked-down self-hosted runner may not. Run the job in the `mcr.microsoft.com/playwright` image, which ships those libraries; the action detects it and skips the `apt` half.

**The pull request is enormous.** Every screenshot changed, which usually means live content in query loops. See [screenshot churn]({{ site.baseurl }}/npm-package#troubleshooting).

Expand Down
5 changes: 1 addition & 4 deletions examples/refresh-pattern-library.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ permissions:
jobs:
refresh:
runs-on: ubuntu-latest
# Ships Chromium and its system dependencies preinstalled, which saves a
# minute or so of apt traffic on every run.
container: mcr.microsoft.com/playwright:v1.62.1-noble

steps:
- uses: actions/checkout@v7
Expand All @@ -53,7 +50,7 @@ jobs:
node-version: 24

- name: Build the pattern library
uses: humanmade/wp-pattern-library@v0.4.0
uses: humanmade/wp-pattern-library@v0.4.1
with:
site-url: ${{ inputs.site_url || vars.PATTERN_LIBRARY_SITE }}
username: ${{ secrets.PATTERN_LIBRARY_WP_USER }}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@humanmade/wp-pattern-library",
"version": "0.4.0",
"version": "0.4.1",
"description": "Generate a Markdown pattern library, with screenshots, from a WordPress site's registered block patterns.",
"license": "GPL-2.0-or-later",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/**
* Plugin Name: WP Pattern Library
* Description: Serves a manifest and isolated previews of the site's registered block patterns.
* Version: 0.4.0
* Version: 0.4.1
* License: GPL-2.0-or-later
* Requires PHP: 8.1
*
Expand Down