From e1828345bd29fadea63317476fe590ed2f29f9f6 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Fri, 6 Nov 2026 05:04:16 +0530 Subject: [PATCH 1/2] feat: flag Interactivity API usages that WordPress 7.2 removes ci-check-iapi.yml runs @rtcamp/wp-tooling's iapi-check over the project and annotates each usage of an Interactivity API that WordPress 7.2 removes. wp-ci.yml runs it as the iapi job when JS, PHP or workflow files change, soft-fail unless iapi-check-fail is true, and ci-result counts it. The check ships in @rtcamp/wp-tooling 2.1.0. With an older wp-tooling the step warns that the command is missing and passes, so callers on the default ^2 install get the scan as soon as 2.1.0 is on npm. --- .github/workflows/ci-check-iapi.yml | 88 +++++++++++++++++++++++++++++ .github/workflows/wp-ci.yml | 31 +++++++++- README.md | 1 + examples/ci-check-iapi.yml | 14 +++++ 4 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci-check-iapi.yml create mode 100644 examples/ci-check-iapi.yml diff --git a/.github/workflows/ci-check-iapi.yml b/.github/workflows/ci-check-iapi.yml new file mode 100644 index 0000000..52b76e6 --- /dev/null +++ b/.github/workflows/ci-check-iapi.yml @@ -0,0 +1,88 @@ +name: CI / Check Interactivity API + +# WordPress 7.2 (due 2026-12-09) removes three Interactivity API behaviours deprecated since 6.8 +# and 6.9: data-wp-ignore, two-hyphen unique IDs (data-wp-watch--x, data-wp-on--click--x) and `!` +# applied to a function. wp-tooling iapi-check scans the project's PHP, HTML and JS for them. + +on: + workflow_call: + inputs: + runs-on: + description: "Runner for every job, as JSON: a label such as \"ubuntu-latest\" or a label list such as [\"self-hosted\"]. rtCamp private repos pass [\"self-hosted\"]." + type: string + default: '"ubuntu-latest"' + node-version: + description: "Node.js version used to run the wp-tooling CLI." + type: string + default: "22" + working-dir: + description: "Directory to scan, relative to the repo root." + type: string + default: "." + fail-on-findings: + description: "Fail the job when the scan finds a usage WordPress 7.2 breaks. Off by default: findings are warnings." + type: boolean + default: false + ignore-paths: + description: "Regex of paths to leave out of the scan. Empty uses the CLI's default (vendor, node_modules and build output)." + type: string + default: "" + wp-tooling-ref: + description: "Version or range of @rtcamp/wp-tooling to run from npm. Leave empty for ^2." + type: string + default: "" + +permissions: + contents: read + +jobs: + check-iapi: + name: iapi-check + runs-on: ${{ fromJSON(inputs.runs-on) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: ${{ inputs.node-version }} + # setup-node 5+ caches on its own when package.json names a packageManager, and fails + # when that lockfile is missing. This step installs no project dependencies. + package-manager-cache: false + + - name: Install wp-tooling + shell: bash + env: + WP_TOOLING_VERSION: ${{ inputs.wp-tooling-ref || '^2' }} + run: | + set -euo pipefail + npm install --ignore-scripts --no-audit --no-fund --prefix "$RUNNER_TEMP/wp-tooling" "@rtcamp/wp-tooling@$WP_TOOLING_VERSION" + npm ls --prefix "$RUNNER_TEMP/wp-tooling" @rtcamp/wp-tooling + echo "$RUNNER_TEMP/wp-tooling/node_modules/.bin" >> "$GITHUB_PATH" + + # Runs from the repo root with --working-dir as a scope, so annotation paths are + # repo-relative and attach to the pull request diff. + - name: Scan for Interactivity API usages WordPress 7.2 removes + shell: bash + env: + WORKING_DIR: ${{ inputs.working-dir }} + FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }} + IGNORE_PATHS: ${{ inputs.ignore-paths }} + run: | + set -euo pipefail + # A wp-tooling older than the command would fail every caller; say so and pass instead. + if ! wp-tooling iapi-check --help >/dev/null 2>&1; then + echo "::warning::The installed @rtcamp/wp-tooling has no iapi-check command, so the WordPress 7.2 Interactivity API scan was skipped." + exit 0 + fi + args=(--working-dir "$WORKING_DIR" --output github) + if [ "$FAIL_ON_FINDINGS" != "true" ]; then + args+=(--soft-fail) + fi + if [ -n "$IGNORE_PATHS" ]; then + args+=(--ignore "$IGNORE_PATHS") + fi + wp-tooling iapi-check "${args[@]}" diff --git a/.github/workflows/wp-ci.yml b/.github/workflows/wp-ci.yml index a53fc23..66bc9a1 100644 --- a/.github/workflows/wp-ci.yml +++ b/.github/workflows/wp-ci.yml @@ -22,7 +22,7 @@ on: description: >- Comma-separated job ids to drop, e.g. 'a11y, test-php'. Whitespace is ignored and an unknown id fails the run. Ids: lint-css, lint-js, lint-php, test-js, test-php, build, - build-artifact, a11y, artifact-gate. + build-artifact, a11y, artifact-gate, iapi. type: string default: "" enable-phpstan: @@ -156,6 +156,13 @@ on: description: "Minimum percentage (0 to 100) of changed executable lines that must be covered. Applies only when coverage is true." type: number default: 80 + iapi-check-fail: + description: >- + Fail the iapi job when it finds an Interactivity API usage that WordPress 7.2 removes + (data-wp-ignore, two-hyphen unique IDs, `!` on a function). Off by default: findings are + warnings. + type: boolean + default: false # project-type and working-dir are in the group so a monorepo caller invoking this workflow # once per unit does not have one call cancel its siblings. Uses the RAW input because @@ -203,7 +210,7 @@ jobs: # The preset as data, in one place, rather than a disjunction repeated in eight `if:`s. case "$PROJECT_TYPE" in - plugin|theme) preset="lint-css,lint-js,lint-php,test-js,test-php,build,build-artifact,a11y,artifact-gate" ;; + plugin|theme) preset="lint-css,lint-js,lint-php,test-js,test-php,build,build-artifact,a11y,artifact-gate,iapi" ;; package) preset="lint-php,test-php,artifact-gate" ;; *) echo "::error::project-type must be one of: plugin, theme, package (got '$PROJECT_TYPE')"; exit 1 ;; esac @@ -211,7 +218,7 @@ jobs: # Whitespace is dropped, so 'a11y, test-php' skips both, and an unknown id fails here # instead of silently running the job it was meant to drop. Skipping a job the preset # does not run is allowed, so one skip list can serve every unit in a caller's matrix. - known=",lint-css,lint-js,lint-php,test-js,test-php,build,build-artifact,a11y,artifact-gate," + known=",lint-css,lint-js,lint-php,test-js,test-php,build,build-artifact,a11y,artifact-gate,iapi," jobs=",${preset}," IFS=',' read -r -a skip_ids <<< "$(printf '%s' "$SKIP" | tr -d '[:space:]')" for id in "${skip_ids[@]}"; do @@ -464,6 +471,23 @@ jobs: gated-paths: ${{ inputs.gated-paths }} working-dir: ${{ needs.validate-inputs.outputs.working-dir }} + # WordPress 7.2 (2026-12-09) drops data-wp-ignore, two-hyphen unique IDs and `!` on a + # function. The scan covers the whole unit, not only changed files, since the code that breaks + # is usually old; it reports warnings unless iapi-check-fail is set. + iapi: + name: WP 7.2 Interactivity API + needs: [validate-inputs, detect-changes] + if: >- + (needs.detect-changes.outputs.js-count > 0 || needs.detect-changes.outputs.php-count > 0 + || needs.detect-changes.outputs.gha-count > 0) + && contains(needs.validate-inputs.outputs.jobs, ',iapi,') + uses: ./.github/workflows/ci-check-iapi.yml + with: + runs-on: ${{ inputs.runs-on }} + working-dir: ${{ needs.validate-inputs.outputs.working-dir }} + fail-on-findings: ${{ inputs.iapi-check-fail }} + wp-tooling-ref: ${{ inputs.wp-tooling-ref }} + # The one check to require in branch protection: " / CI result". The other check # names and their count change with project-type, skip, what changed and the PHP matrix, and a # skipped required check counts as passing. Runs on always(), so a failed, cancelled or @@ -482,6 +506,7 @@ jobs: - build-artifact - a11y - artifact-gate + - iapi if: always() runs-on: ${{ fromJSON(inputs.runs-on) }} steps: diff --git a/README.md b/README.md index 05dc77e..731462c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ you can copy as-is. | [`ci-lint-php.yml`](.github/workflows/ci-lint-php.yml) | PHPCS with `cs2pr` annotations + optional PHPStan | [example](examples/ci-lint-php.yml) | | [`ci-test-js.yml`](.github/workflows/ci-test-js.yml) | Jest | [example](examples/ci-test-js.yml) | | [`ci-test-php.yml`](.github/workflows/ci-test-php.yml) | PHPUnit, via `@wordpress/env` or standalone | [example](examples/ci-test-php.yml) | +| [`ci-check-iapi.yml`](.github/workflows/ci-check-iapi.yml) | Flags Interactivity API usages that WordPress 7.2 removes | [example](examples/ci-check-iapi.yml) | | [`ci-coverage-comment.yml`](.github/workflows/ci-coverage-comment.yml) | Posts the changed-line coverage summaries of a run as one pull request comment | [example](examples/ci-coverage-comment.yml) | | [`ci-test-a11y.yml`](.github/workflows/ci-test-a11y.yml) | pa11y-ci WCAG2AA | [example](examples/ci-test-a11y.yml) | | [`ci-build.yml`](.github/workflows/ci-build.yml) | Production build + optional artifact upload | [example](examples/ci-build.yml) | diff --git a/examples/ci-check-iapi.yml b/examples/ci-check-iapi.yml new file mode 100644 index 0000000..66ed7d3 --- /dev/null +++ b/examples/ci-check-iapi.yml @@ -0,0 +1,14 @@ +name: WordPress 7.2 readiness + +on: + pull_request: + +permissions: + contents: read + +jobs: + check-iapi: + uses: rtCamp/wp-shared-workflows/.github/workflows/ci-check-iapi.yml@v1 + with: + working-dir: themes/acme-theme + fail-on-findings: true From 754c95bdbbc5dcbd99267c196e64d4d70031b395 Mon Sep 17 00:00:00 2001 From: Aryan Jasala Date: Fri, 6 Nov 2026 05:54:24 +0530 Subject: [PATCH 2/2] feat: let wp-ci pass ignore-paths to the iapi job wp-ci.yml gains an iapi-ignore-paths input, forwarded to the iapi job's ignore-paths, so a block that keeps an old directive in deprecated.js on purpose can leave that file out without skipping the whole check. It is separate from ignore-paths, which filters detect-changes. When the installed wp-tooling has no iapi-check command, the job still warns and passes, unless the caller set iapi-check-fail: then it fails, since the caller asked for a failing check. --- .github/workflows/ci-check-iapi.yml | 7 ++++++- .github/workflows/wp-ci.yml | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-check-iapi.yml b/.github/workflows/ci-check-iapi.yml index 52b76e6..3f4cdcb 100644 --- a/.github/workflows/ci-check-iapi.yml +++ b/.github/workflows/ci-check-iapi.yml @@ -73,8 +73,13 @@ jobs: IGNORE_PATHS: ${{ inputs.ignore-paths }} run: | set -euo pipefail - # A wp-tooling older than the command would fail every caller; say so and pass instead. + # A wp-tooling older than the command would fail every caller; say so and pass instead, + # unless the caller asked for a failing check. if ! wp-tooling iapi-check --help >/dev/null 2>&1; then + if [ "$FAIL_ON_FINDINGS" = "true" ]; then + echo "::error::iapi-check-fail is set, but the installed @rtcamp/wp-tooling has no iapi-check command. Use wp-tooling 2.1.0 or later." + exit 1 + fi echo "::warning::The installed @rtcamp/wp-tooling has no iapi-check command, so the WordPress 7.2 Interactivity API scan was skipped." exit 0 fi diff --git a/.github/workflows/wp-ci.yml b/.github/workflows/wp-ci.yml index 3d32c35..8723691 100644 --- a/.github/workflows/wp-ci.yml +++ b/.github/workflows/wp-ci.yml @@ -163,6 +163,12 @@ on: warnings. type: boolean default: false + iapi-ignore-paths: + description: >- + Regex of paths the iapi job leaves out of its scan, such as a block's deprecated.js that + keeps an old directive on purpose. Separate from ignore-paths, which filters detect-changes. + type: string + default: "" # project-type and working-dir are in the group so a monorepo caller invoking this workflow # once per unit does not have one call cancel its siblings. Uses the RAW input because @@ -486,6 +492,7 @@ jobs: runs-on: ${{ inputs.runs-on }} working-dir: ${{ needs.validate-inputs.outputs.working-dir }} fail-on-findings: ${{ inputs.iapi-check-fail }} + ignore-paths: ${{ inputs.iapi-ignore-paths }} wp-tooling-ref: ${{ inputs.wp-tooling-ref }} # The one check to require in branch protection: " / CI result". The other check