Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 2 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
indent_size = 4

[*.{yml,yaml,json}]
[*.{yml,yaml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[*.txt]
end_of_line = crlf
12 changes: 7 additions & 5 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ body:
label: Affected plugin (or scaffold)
options:
- linked-orders-for-woocommerce
- internal-comments
- locked-payment-methods-for-woocommerce
- Monorepo scaffold (root composer/npm scripts, wp-env, CI workflows)
- Other / unsure
validations:
Expand All @@ -18,10 +20,10 @@ body:
attributes:
label: Affected area
options:
- Plugin runtime (Plugin singleton, components, framework wiring)
- Runtime (boot, components, framework wiring)
- Composer scoping pipeline (scoper.inc.php, post-autoload-dump)
- Changelog tooling (composer changelog:* scripts)
- CI workflows (quality.yml, tests.yml, split-plugins.yml)
- CI workflows (.github/workflows/)
- wp-env / integration / E2E test infrastructure
- Cross-plugin interactions (multiple plugins active together)
- Documentation / readme
Expand All @@ -41,21 +43,21 @@ body:
id: php-version
attributes:
label: PHP version
placeholder: '8.5.0'
placeholder: 'x.y.z'
validations:
required: true

- type: input
id: wp-version
attributes:
label: WordPress version
placeholder: '7.0'
placeholder: 'x.y.z'

- type: input
id: wc-version
attributes:
label: WooCommerce version (if applicable)
placeholder: '11.2.1'
placeholder: 'x.y.z'

- type: textarea
id: reproduction
Expand Down
6 changes: 4 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ body:
label: Target plugin (or scaffold)
options:
- linked-orders-for-woocommerce
- internal-comments
- locked-payment-methods-for-woocommerce
- Monorepo scaffold (root composer/npm scripts, wp-env, CI workflows)
- New plugin proposal
validations:
Expand All @@ -18,10 +20,10 @@ body:
attributes:
label: Target area
options:
- Plugin runtime (components, framework wiring, settings, hooks)
- Runtime (boot, components, framework wiring)
- Composer scoping pipeline
- Changelog tooling
- CI workflows
- CI workflows (.github/workflows/)
- wp-env / integration / E2E test infrastructure
- Documentation
- New area / unsure
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## Checklist

- [ ] Changelog fragment added (`composer changelog:add` in the changed plugin's directory, or at the root for the monorepo itself)
- [ ] Changelog fragment added (`composer changelog:add` in the changed plugin's directory)
- [ ] Tests added/updated
- [ ] `composer quality-check` passes locally in every changed plugin
- [ ] Plugin header and `readme.txt` updated if runtime requirements changed
Expand Down
3 changes: 2 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ updates:
dependency-type: development

- package-ecosystem: github-actions
# Dependabot reads .github/workflows only for the root; any other directory is scanned for its own workflow files.
directories:
- /
- /plugins/*
- /plugins/*/.github/workflows
schedule:
interval: weekly
day: monday
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ on:
branches: [trunk]
pull_request:
schedule:
# Weekly Monday at 07:00 UTC — staggered after dependabot (06:00) so PRs from
# dependency bumps surface audit results in the same morning batch.
# Weekly, so advisories published against an unchanged lock surface without a push.
- cron: '0 7 * * 1'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand All @@ -24,8 +27,7 @@ jobs:
fail-fast: false
matrix:
project: ${{ fromJSON(needs.plugins.outputs.projects) }}
# npm gate audits production deps only — the dev-tooling graph (wp-scripts/playwright trees) is advisory via dependabot alerts.
uses: ahegyes/wordpress-configs/.github/workflows/reusable-supply-chain-audit.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
# Only production npm packages gate the audit, since a release ships none of the development tooling.
uses: ahegyes/wordpress-configs/.github/workflows/reusable-supply-chain-audit.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: ${{ matrix.project }}
npm-audit-flags: '--omit=dev --audit-level=high'
4 changes: 3 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ jobs:
actions: read
contents: read
security-events: write
uses: ahegyes/wordpress-configs/.github/workflows/reusable-codeql.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
uses: ahegyes/wordpress-configs/.github/workflows/reusable-codeql.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
languages: '["actions", "javascript-typescript"]'
48 changes: 42 additions & 6 deletions .github/workflows/plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ name: Plugins
# Every directory under plugins/ is a plugin whose main file and mirror its own files name, so a new plugin joins every lane without an edit to the workflows that call this one.
on:
workflow_call:
inputs:
list-wordpress:
description: 'Whether to list each plugin''s WordPress versions, which only the Tests workflow reads.'
type: boolean
required: false
default: false
outputs:
plugins:
description: 'JSON array of the plugin directories under plugins/, each named after its slug.'
Expand All @@ -17,8 +23,11 @@ on:
description: 'JSON array of { plugin, repo } objects, the mirror each plugin splits to, as its composer.json name gives it.'
value: ${{ jobs.list.outputs.mirrors }}
wordpress:
description: 'JSON array of { plugin, wp } objects: the newest WordPress release of every branch from each plugin''s Requires at least up.'
value: ${{ jobs.list.outputs.wordpress }}
description: 'JSON array of { plugin, wp } objects: the newest WordPress release of every branch from each plugin''s Requires at least up. Empty unless list-wordpress is true.'
value: ${{ jobs.wordpress.outputs.wordpress }}
newest-wordpress:
description: 'The newest WordPress release the wordpress output lists, for the shared environment. Empty unless list-wordpress is true.'
value: ${{ jobs.wordpress.outputs.newest }}

permissions:
contents: read
Expand All @@ -33,7 +42,6 @@ jobs:
projects: ${{ steps.plugins.outputs.projects }}
readmes: ${{ steps.plugins.outputs.readmes }}
mirrors: ${{ steps.plugins.outputs.mirrors }}
wordpress: ${{ steps.wordpress.outputs.wordpress }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -50,6 +58,11 @@ jobs:
echo "::error::plugins/$plugin must be a kebab-case slug holding its main file, $plugin.php."
exit 1
fi
domain=$(jq -r '.extra["text-domain"] // empty' "plugins/$plugin/composer.json")
if [ "$domain" != "$plugin" ]; then
echo "::error::plugins/$plugin/composer.json's extra.text-domain must be its directory's name, $plugin — got '$domain'."
exit 1
fi
repo=$(jq -r '.name // empty' "plugins/$plugin/composer.json")
if ! [[ "$repo" =~ ^ahegyes/[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
echo "::error::plugins/$plugin/composer.json's name must be its mirror, ahegyes/<repository> — got '$repo'."
Expand All @@ -60,20 +73,40 @@ jobs:
readmes=$(jq -c --arg plugin "$plugin" '. + [$plugin]' <<< "$readmes")
fi
done
# A release tag names the plugin it releases, so a tag naming no plugin would otherwise split nothing and pass.
if [ "$GITHUB_REF_TYPE" = 'tag' ] && ! jq -e --arg plugin "${GITHUB_REF_NAME%%/*}" 'index($plugin) != null' <<< "$plugins" > /dev/null; then
echo "::error::The tag $GITHUB_REF_NAME names no plugin under plugins/; a release tag reads <slug>/vX.Y.Z."
exit 1
fi
{
echo "plugins=$plugins"
echo "projects=$(jq -c '["."] + map("plugins/" + .)' <<< "$plugins")"
echo "readmes=$readmes"
echo "mirrors=$mirrors"
} >> "$GITHUB_OUTPUT"

# Only the Tests workflow reads the versions, so a failure reaching WordPress's version API holds back no split or other lane.
wordpress:
name: List the WordPress versions
needs: list
if: inputs.list-wordpress
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
wordpress: ${{ steps.wordpress.outputs.wordpress }}
newest: ${{ steps.wordpress.outputs.newest }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# WordPress's version API offers the newest release of every branch, so new releases and raised floors join without an edit here.
- name: List the newest WordPress release of every branch from each plugin's floor up
id: wordpress
env:
PLUGINS: ${{ steps.plugins.outputs.plugins }}
PLUGINS: ${{ needs.list.outputs.plugins }}
run: |
offers=$(curl -fsS https://api.wordpress.org/core/version-check/1.7/)
offers=$(curl -fsS --retry 3 --retry-all-errors --max-time 20 https://api.wordpress.org/core/version-check/1.7/)
pairs='[]'
for plugin in $(jq -r '.[]' <<< "$PLUGINS"); do
floor=$(sed -nE 's|^[[:space:]/*#@]*Requires at least:[[:space:]]*([0-9]+(\.[0-9]+)*).*$|\1|p' "plugins/$plugin/$plugin.php" | head -n 1)
Expand All @@ -83,4 +116,7 @@ jobs:
fi
pairs=$(printf '%s' "$offers" | jq -c --arg floor "$floor" --arg plugin "$plugin" --argjson pairs "$pairs" '[.offers[].version | select((split(".")[0:2] | map(tonumber)) >= ($floor | split(".")[0:2] | map(tonumber)))] | unique | if length == 0 then error("No WordPress release at or above \($floor) for \($plugin).") else $pairs + map({ plugin: $plugin, wp: . }) end')
done
echo "wordpress=$pairs" >> "$GITHUB_OUTPUT"
{
echo "wordpress=$pairs"
echo "newest=$(jq -r 'map(.wp) | max_by(split(".") | map(tonumber))' <<< "$pairs")"
} >> "$GITHUB_OUTPUT"
49 changes: 15 additions & 34 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches: [trunk]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand All @@ -14,29 +18,30 @@ jobs:
uses: ./.github/workflows/plugins.yml

lint-php:
name: PHP Lint (${{ matrix.plugin }})
name: Lint PHP (${{ matrix.plugin }})
needs: plugins
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.plugins.outputs.plugins) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-lint.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-lint.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: plugins/${{ matrix.plugin }}
php-version: '8.5'
scripts: '["lint:php:phpcs", "lint:php:phpcs:tests", "lint:php:phpstan"]'
scripts: '["lint:php:phpcs", "lint:php:phpcs:tests", "lint:php:phpstan", "changelog:validate"]'

lint-scripts-styles:
name: Scripts and styles lint (${{ matrix.plugin }})
name: Lint scripts and styles (${{ matrix.project }})
needs: plugins
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.plugins.outputs.plugins) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-scripts-styles-lint.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
project: ${{ fromJSON(needs.plugins.outputs.projects) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-scripts-styles-lint.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: plugins/${{ matrix.plugin }}
scripts: '["lint:scripts", "lint:styles", "lint:types"]'
project-path: ${{ matrix.project }}
# The root holds only the shared environment's spec and configs, so it has no styles to lint.
scripts: ${{ matrix.project == '.' && '["lint:scripts", "lint:types"]' || '["lint:scripts", "lint:styles", "lint:types"]' }}

block-json:
name: block.json schema (${{ matrix.plugin }})
Expand All @@ -45,7 +50,7 @@ jobs:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.plugins.outputs.plugins) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-block-json-check.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
uses: ahegyes/wordpress-configs/.github/workflows/reusable-block-json-check.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: plugins/${{ matrix.plugin }}

Expand All @@ -56,7 +61,7 @@ jobs:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.plugins.outputs.plugins) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-syntax-check.yml@8d53efd292e4abee4a9c57b0e379434f54bedbe9
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-syntax-check.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: plugins/${{ matrix.plugin }}
php-versions: '["7.4"]'
Expand All @@ -80,27 +85,3 @@ jobs:
with:
path: plugins/${{ matrix.plugin }}/readme.txt
fail-on: error

changelog:
name: Changelog fragments (${{ matrix.project }})
needs: plugins
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(needs.plugins.outputs.projects) }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
with:
php-version: '8.5'
coverage: none
tools: composer:v2
- uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
with:
working-directory: ${{ matrix.project }}
- run: composer changelog:validate
working-directory: ${{ matrix.project }}
42 changes: 42 additions & 0 deletions .github/workflows/release-rehearsal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Release Rehearsal

# The mirrors take no pull requests, so each plugin's own release workflow first runs on its tag; this builds, checks and tests every plugin's release here without publishing it.
on:
push:
branches: [trunk]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
plugins:
name: Plugins
uses: ./.github/workflows/plugins.yml

release:
name: Release (${{ matrix.plugin }})
needs: plugins
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJSON(needs.plugins.outputs.plugins) }}
# A reusable workflow cannot raise the caller's token, so this job grants every scope the release's jobs declare.
permissions:
actions: read
attestations: write
contents: write
id-token: write
uses: ahegyes/wordpress-configs/.github/workflows/reusable-release.yml@b107001cce9f2960495d9d50481c8540d228f60c
with:
project-path: plugins/${{ matrix.plugin }}
plugin-slug: ${{ matrix.plugin }}
wp-env-config-file: '.wp-env.tests.json'
# Both parse every PHP file with php-parser 4, which stops at PHP 8.4 syntax such as protected(set).
exclude-checks: 'prefixing,direct_file_access'
wp-org: ${{ contains(fromJSON(needs.plugins.outputs.readmes), matrix.plugin) }}
publish: false
5 changes: 4 additions & 1 deletion .github/workflows/split-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
echo "ref=refs/heads/trunk" >> "$GITHUB_OUTPUT"
elif [[ "$GITHUB_REF_NAME" =~ ^${PLUGIN}/v(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){2}$ ]]; then
echo "ref=refs/tags/${GITHUB_REF_NAME#"$PLUGIN/"}" >> "$GITHUB_OUTPUT"
elif [[ "$GITHUB_REF_NAME" == "$PLUGIN/"* ]]; then
echo "::error::The tag $GITHUB_REF_NAME names $PLUGIN but is no release tag, which reads $PLUGIN/vMAJOR.MINOR.PATCH."
exit 1
fi

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -58,7 +61,7 @@ jobs:
run: |
# v1.0.1 is the latest splitsh-lite release with a published linux/amd64 binary (v2.0.0 was tagged with no assets).
curl -fsSL -o splitsh.tar.gz https://github.com/splitsh/lite/releases/download/v1.0.1/lite_linux_amd64.tar.gz
# sha256 computed from the v1.0.1 release asset (upstream publishes no checksums — trust-on-first-use pin).
# Upstream publishes no checksums, so this hash pins the v1.0.1 asset on first use.
echo "2539301ce5e21d0ca44b689d0dd2c1b20d9f9e996c1fe6c462afb8af4e7141cc splitsh.tar.gz" | sha256sum -c -
tar -xzf splitsh.tar.gz
sudo mv splitsh-lite /usr/local/bin/
Expand Down
Loading
Loading