Skip to content

feat(scaffolds)!: target rtcamp/wp-primitives ^2.0 - #70

Merged
AnuragVasanwala merged 7 commits into
mainfrom
v1.0.0/task/wp-primitives-rename
Oct 1, 2026
Merged

AnuragVasanwala merged 7 commits into
mainfrom
v1.0.0/task/wp-primitives-rename

Conversation

@Adi-ty

@Adi-ty Adi-ty commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Moves every scaffold, test, doc and skill from rtcamp/wp-framework ^1.0 to rtcamp/wp-primitives ^2.0 (namespace rtCamp\WPPrimitives\), following rtCamp/wp-primitives#98. It also brings over the scaffold fixes from #63; that PR keeps its VIP scaffolds and lens.

Closes

Closes #69

Changes

  • fix(scaffolds): harden generation and tooling
  • feat(scaffolds)!: target rtcamp/wp-primitives ^2.0
    • New package, namespace and @link across scaffolds, tests and snippets.
    • docs/wp-framework-contract.md is renamed to docs/wp-primitives-contract.md.
    • BREAKING CHANGELOG entry.

How I verified

npm run check
grep -rnE 'wp-framework|WPFramework' node-packages/wp-tooling --exclude-dir=node_modules --exclude=CHANGELOG.md

SMOKE="$(mktemp -d)"
CLI=node-packages/wp-tooling/bin/wp-tooling.js
node "$CLI" add wp/cpt --cwd "$SMOKE" --json --slug=book --singular=Book --plural=Books | grep -o '"rtcamp/wp-primitives":"[^"]*"'
grep -n 'WPPrimitives' "$SMOKE/includes/PostTypes/Book.php"
node "$CLI" add wp/block-dynamic --cwd "$SMOKE" --json --slug=guide --title="Editor's \"guide\"" > /dev/null
php -l "$SMOKE/includes/Blocks/Guide.php"
  • npm run check passes: 1,288 tests.
  • The grep finds only the contract doc's history note, which is kept on purpose.
  • The smoke test prints "rtcamp/wp-primitives":"^2.0", the rtCamp\WPPrimitives\ import, and No syntax errors detected.
  • A plugin generated from every PHP scaffold against #98's head (560b189) has 0 rtCampWP and rtCampWP-Basic errors across 84 files.

Reviewer notes

  • Don't merge until #98 merges, the repo is renamed and v2.0.0 is tagged.
  • Block JS follows upstream prettier, so in features-plugin-skeleton (which uses wp-prettier) new blocks need npm run lint:js:fix.

Adi-ty added 2 commits October 1, 2026 16:01
wp-framework is renamed to wp-primitives in 2.0.0 (rtCamp/wp-primitives#98).
- composer deps and WPPrimitives namespace across templates, tests, snippets
- docs/wp-framework-contract.md renamed to docs/wp-primitives-contract.md
- block JS keeps WordPress spacing; docblock and CHANGELOG follow-ups

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Several discovery, escaping, path-safety, and archive-preflight issues remain, while upstream PR #98 is still open.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity · 2 Low severity

Open (7)
What changed in this PR

Retargets WP Tooling scaffolds to rtcamp/wp-primitives 2.0 while incorporating scaffold, PHPCS, discovery, and release-tool hardening.

Changes:

  • Migrates generated dependencies, namespaces, documentation, and skills to WP Primitives.
  • Improves escaping, PSR-4 discovery, PHP import sorting, PHPCS scaffolds, and tests.
  • Adds theme release support and oversized-archive warnings.
File Description
tests/​scaffolds/​schema-parity.test.js Tests schema-validator parity.
tests/​scaffolds/​render.test.js Tests new escaping and path transforms.
tests/​scaffolds/​php-imports.test.js Tests PHP import normalization.
tests/​scaffolds/​discover-sources.test.js Tests header and autoload discovery.
tests/​scaffolds/​bundled-manifests.test.js Expands bundled scaffold integration coverage.
tests/​release/​zip.test.js Tests archive-size warnings.
tests/​release/​bump.test.js Tests theme version bumping.
src/​scaffolds/​validate.js Derives keys and validates discovery sources.
src/​scaffolds/​schema.js Exposes shapes and adds transforms.
src/​scaffolds/​render.js Implements escaping and project-root transforms.
src/​scaffolds/​registry.js Adds discovery, normalization, and path guards.
src/​scaffolds/​php-imports.js Sorts rendered PHP imports.
src/​release/​zip.js Tracks payload sizes and warnings.
src/​release/​index.js Exports generalized entry discovery.
src/​release/​context.js Supports plugin and theme entries.
src/​cli/​commands/​release-zip.js Prints archive warnings.
skills/​setup/​SKILL.md Updates setup and PHPCS guidance.
skills/​scaffold/​SKILL.md Protects example regions during wiring.
skills/​scaffold/​evals/​evals.json Adds safe-wiring evaluation coverage.
scaffolds/​wp/​user-role/​templates/​user-role.php.mustache Migrates and hardens user-role output.
scaffolds/​wp/​user-role/​templates/​test.php.mustache Improves generated user-role tests.
scaffolds/​wp/​user-role/​scaffold.json Updates dependency and discovery inputs.
scaffolds/​wp/​taxonomy/​templates/​test.php.mustache Improves taxonomy tests.
scaffolds/​wp/​taxonomy/​templates/​taxonomy.php.mustache Migrates and escapes taxonomy labels.
scaffolds/​wp/​taxonomy/​scaffold.json Updates taxonomy inputs and dependency.
scaffolds/​wp/​shortcode/​templates/​test.php.mustache Avoids global hook execution in tests.
scaffolds/​wp/​shortcode/​templates/​shortcode.php.mustache Migrates shortcode implementation.
scaffolds/​wp/​shortcode/​scaffold.json Updates shortcode discovery and dependency.
scaffolds/​wp/​settings-page/​templates/​test.php.mustache Improves settings registration tests.
scaffolds/​wp/​settings-page/​templates/​settings-page.php.mustache Escapes settings-page labels.
scaffolds/​wp/​settings-page/​scaffold.json Adds escaped label inputs.
scaffolds/​wp/​rest/​templates/​test.php.mustache Corrects REST registration testing.
scaffolds/​wp/​rest/​templates/​controller.php.mustache Migrates REST controller contract.
scaffolds/​wp/​rest/​scaffold.json Updates REST discovery and dependency.
scaffolds/​wp/​registrable/​templates/​test.php.mustache Improves Registrable tests.
scaffolds/​wp/​registrable/​templates/​registrable.php.mustache Migrates Registrable interface.
scaffolds/​wp/​registrable/​scaffold.json Updates discovery and dependency.
scaffolds/​wp/​module/​templates/​test.php.mustache Tests protected module class lists.
scaffolds/​wp/​module/​templates/​module.php.mustache Matches protected module contract.
scaffolds/​wp/​module/​scaffold.json Updates module discovery and dependency.
scaffolds/​wp/​cron/​templates/​test.php.mustache Documents cron test behavior.
scaffolds/​wp/​cron/​templates/​cron.php.mustache Migrates cron interface.
scaffolds/​wp/​cron/​scaffold.json Updates cron discovery and dependency.
scaffolds/​wp/​cpt/​templates/​test.php.mustache Improves CPT tests.
scaffolds/​wp/​cpt/​templates/​post-type.php.mustache Migrates and escapes CPT labels.
scaffolds/​wp/​cpt/​scaffold.json Adds escaped labels and new dependency.
scaffolds/​wp/​cli/​templates/​test.php.mustache Migrates CLI test contract.
scaffolds/​wp/​cli/​templates/​command.php.mustache Migrates CLI command contract.
scaffolds/​wp/​cli/​scaffold.json Updates CLI discovery and dependency.
scaffolds/​wp/​block-interactive/​templates/​view.js.mustache Updates generated JavaScript formatting.
scaffolds/​wp/​block-interactive/​templates/​test.php.mustache Migrates interactive-block tests.
scaffolds/​wp/​block-interactive/​templates/​index.js.mustache Updates block registration formatting.
scaffolds/​wp/​block-interactive/​templates/​edit.js.mustache Escapes editor display text.
scaffolds/​wp/​block-interactive/​templates/​block.json.mustache Escapes block metadata.
scaffolds/​wp/​block-interactive/​templates/​block-registrar.php.mustache Derives build paths from project root.
scaffolds/​wp/​block-interactive/​scaffold.json Adds escaped and root-derived inputs.
scaffolds/​wp/​block-dynamic/​templates/​test.php.mustache Improves dynamic-block tests.
scaffolds/​wp/​block-dynamic/​templates/​index.js.mustache Updates block registration formatting.
scaffolds/​wp/​block-dynamic/​templates/​edit.js.mustache Escapes editor display text.
scaffolds/​wp/​block-dynamic/​templates/​block.json.mustache Escapes block metadata.
scaffolds/​wp/​block-dynamic/​templates/​block-class.php.mustache Fixes contract visibility and paths.
scaffolds/​wp/​block-dynamic/​scaffold.json Adds escaped and root-derived inputs.
scaffolds/​wp/​admin-page/​templates/​test.php.mustache Improves admin-page tests.
scaffolds/​wp/​admin-page/​templates/​admin-page.php.mustache Migrates and escapes admin labels.
scaffolds/​wp/​admin-page/​scaffold.json Adds escaped labels and dependency.
scaffolds/​wp-api/​speculation/​templates/​test.php.mustache Migrates speculation tests.
scaffolds/​wp-api/​speculation/​templates/​speculation.php.mustache Migrates speculation interface.
scaffolds/​wp-api/​speculation/​scaffold.json Updates discovery and dependency.
scaffolds/​wp-api/​script-module/​templates/​test.php.mustache Updates script-module tests.
scaffolds/​wp-api/​script-module/​templates/​script-module.php.mustache Fixes paths and dependency normalization.
scaffolds/​wp-api/​script-module/​scaffold.json Adds project-root discovery.
scaffolds/​wp-api/​block-bindings/​templates/​test.php.mustache Migrates block-binding tests.
scaffolds/​wp-api/​block-bindings/​templates/​block-bindings.php.mustache Escapes binding labels.
scaffolds/​wp-api/​block-bindings/​scaffold.json Adds escaped labels and dependency.
scaffolds/​utility/​transients/​scaffold.json Migrates Transients utility wiring.
scaffolds/​utility/​timer/​scaffold.json Migrates Timer utility wiring.
scaffolds/​utility/​logger/​scaffold.json Migrates Logger utility wiring.
scaffolds/​utility/​feature-selector/​scaffold.json Migrates FeatureSelector wiring.
scaffolds/​utility/​cache/​scaffold.json Migrates Cache utility wiring.
scaffolds/​setup/​phpunit/​templates/​bootstrap.php.mustache Adds package and strict-types metadata.
scaffolds/​setup/​phpunit/​scaffold.json Adds test namespace discovery.
scaffolds/​setup/​perf/​templates/​server-profile.php Documents PHPCS exceptions.
scaffolds/​setup/​claude-skills/​templates/​setup-SKILL.md Synchronizes setup skill guidance.
scaffolds/​setup/​claude-skills/​templates/​scaffold-SKILL.md Synchronizes wiring safeguards.
scaffolds/​setup/​claude-skills/​templates/​scaffold-evals.json Synchronizes scaffold evaluations.
scaffolds/​lint/​phpcs/​vip/​scaffold.json Corrects VIP package and plugin wiring.
scaffolds/​lint/​phpcs/​full/​templates/​phpcs.xml.dist.mustache Generates an rtCampWP ruleset.
scaffolds/​lint/​phpcs/​full/​scaffold.json Targets the shared strict standard.
scaffolds/​lint/​phpcs/​core/​templates/​phpcs.xml.dist.mustache Generates an rtCampWP-Basic ruleset.
scaffolds/​lint/​phpcs/​core/​scaffold.json Targets the shared basic standard.
docs/​wp-primitives-contract.md Renames and updates the package contract.
docs/​examples.md Updates generated-command examples.
docs/​authoring-scaffolds.md Documents discovery and transforms.
docs/​ai-orchestration.md Updates engine and wiring contracts.
CHANGELOG.md Records breaking migration and fixes.
AGENTS.md Updates the contract-document path.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

description: w.description || '',
}));
);
if (targetFile.startsWith('../') || path.isAbsolute(targetFile)) {
version: p.version,
fileCount: entries.length,
byteSize: archive.length,
warnings: p.warnings,
}
const selector = spec.slice(colon + 1);
if (selector === 'autoload.psr-4' || selector === 'autoload.psr-0') {
if (/^autoload(-dev)?\.psr-[04]$/.test(selector)) {
An apostrophe or backslash in --description produced a PHP string literal that did not parse. The description now goes through a derived description_php input with the php-escape transform, as the other display-text scaffolds do, and wp/cli joins the quoted display text test.
The new discover_from allow-list rejected code:*, which docs/ai-orchestration.md documents as resolved by the orchestrating skill, so project and remote manifests using it failed validation.
path.posix.normalize('a/../..') returns '..' with no trailing slash, which slipped past the startsWith('../') check.
The discovery section still said plugin-header:* was the skill's job, while the engine resolves it and the contract above says so.
…ctly

Acme\Blog against Acme_Blog sorted the same way under a whole-string compare, so the test could not catch a regression. Acme\Blog against AcmeZ only passes segment-wise.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 17:05
@AnuragVasanwala
AnuragVasanwala merged commit 3a27755 into main Oct 1, 2026
5 checks passed
@AnuragVasanwala
AnuragVasanwala deleted the v1.0.0/task/wp-primitives-rename branch October 1, 2026 17:06
github-actions Bot pushed a commit that referenced this pull request Oct 1, 2026
* fix(scaffolds): harden generation and tooling

* feat(scaffolds)!: target rtcamp/wp-primitives ^2.0

wp-framework is renamed to wp-primitives in 2.0.0 (rtCamp/wp-primitives#98).
- composer deps and WPPrimitives namespace across templates, tests, snippets
- docs/wp-framework-contract.md renamed to docs/wp-primitives-contract.md
- block JS keeps WordPress spacing; docblock and CHANGELOG follow-ups

* fix(scaffolds): escape the wp/cli command description

An apostrophe or backslash in --description produced a PHP string literal that did not parse. The description now goes through a derived description_php input with the php-escape transform, as the other display-text scaffolds do, and wp/cli joins the quoted display text test.

* fix(scaffolds): accept skill-resolved code: discover sources

The new discover_from allow-list rejected code:*, which docs/ai-orchestration.md documents as resolved by the orchestrating skill, so project and remote manifests using it failed validation.

* fix(scaffolds): drop wiring targets that normalise to exactly ..

path.posix.normalize('a/../..') returns '..' with no trailing slash, which slipped past the startsWith('../') check.

* docs(ai-orchestration): list plugin-header as engine-resolved

The discovery section still said plugin-header:* was the skill's job, while the engine resolves it and the contract above says so.

* test(php-imports): use a pair only the segment-wise sort orders correctly

Acme\Blog against Acme_Blog sorted the same way under a whole-string compare, so the test could not catch a regression. Acme\Blog against AcmeZ only passes segment-wise.

---------

Co-authored-by: AnuragVasanwala <75766877+AnuragVasanwala@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The external v2 tag is still absent, and unresolved discovery, archive-memory, and acceptance-criteria issues remain.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity · 1 Low severity

Open (4)
Resolved since last review (4)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Plugin header discovery stops at the first closing comment

node-packages/​wp-tooling/​src/​scaffolds/​registry.js:923

Stopping at the first */ makes discovery ignore a valid WordPress header when a license/doc comment precedes it. WordPress scans the full first 8 KB, and a representative file with /** License */ before the plugin header resolves here to the fallback my-plugin; parse the comment containing Plugin Name/Theme Name (while still preventing later code comments from overriding header values).

Medium severity Missing recursive scaffold scan for stale wp-framework references

node-packages/​wp-tooling/​tests/​scaffolds/​bundled-manifests.test.js:824

Issue #69 explicitly requires a recursive regression test that rejects wp-framework or WPFramework anywhere under scaffolds/. These assertions update known values but no such guard was added, so a copied stale scaffold can silently reintroduce the old dependency or namespace; add the requested catalogue-wide scan.

Comment on lines +1060 to +1066
if (source === 'plugin-header') {
const headers = discovery['plugin-header'];
if (!headers) {
return undefined;
}
const value = headers[spec.slice(colon + 1)];
return typeof value === 'string' && value !== '' ? value : undefined;
AnuragVasanwala added a commit that referenced this pull request Oct 1, 2026
The merge from main kept the older wording and dropped the entry for the wp/cli description fix that #70 shipped.
AnuragVasanwala added a commit that referenced this pull request Oct 1, 2026
* fix(scaffolds): harden generation and tooling

* feat(scaffolds)!: target rtcamp/wp-primitives ^2.0

wp-framework is renamed to wp-primitives in 2.0.0 (rtCamp/wp-primitives#98).
- composer deps and WPPrimitives namespace across templates, tests, snippets
- docs/wp-framework-contract.md renamed to docs/wp-primitives-contract.md
- block JS keeps WordPress spacing; docblock and CHANGELOG follow-ups

* ci: lint and test every pull request and push to main

Nothing ran the suites on a pull request, while every merge to main is
split straight onto the npm/* branches that the skeletons and the shared
workflows install from. A broken merge therefore reached new installs
with no gate in between.

The new workflow runs every workspace lint and Jest suite with coverage
thresholds on Node 22.19 (the engines floor) and 24, and installs and
tests each composer package standalone, the way it ships after the
subtree split, on PHP 8.2, 8.3 and 8.4.

* ci(release): repair diverged mirrors and narrow secret and token scope

Both mirrors already carry a commit the split did not make (an org bot
merged a Copilot review workflow into rtCamp/wp-phpcs and
rtCamp/wp-phpstan on 2026-08-13). The release refuses to fast-forward
over that, so the next tag would fail before reaching Packagist, and the
only way out was a force push that also mints a throwaway tag.

- release-php: add a resync_branch dispatch input that forces just the
  mirror branch back onto the split and pushes no tag, and say exactly
  that in the error when a release does not fast-forward.
- release-php: reference the deploy keys by name instead of
  secrets[matrix.secret_name], which hands the job the whole secrets
  context, and check the secret name before the indirect lookup.
- split-npm-packages: grant contents: write to the split job only.

* chore(composer): add Packagist metadata to wp-phpcs and wp-phpstan

Both packages are about to be submitted to Packagist from their split
mirrors, which are read-only. Point homepage, support.issues and
support.source at the monorepo so the Packagist page sends issues and
contributions to the right place, and name rtCamp as the author.

* fix(detect-changes): add --strict and randomise the GitHub output delimiter

A failed git diff counted as zero changes, which skipped every job gated
on the counts and left the run green. rtCamp/wp-shared-workflows works
around it by grepping stderr. --strict (detectChanges({ strict: true }))
now exits 1 with a DetectChangesError instead; it is off by default so
existing callers see no change, and the stderr keeps the 'git diff
failed' phrase the shared workflow matches.

Multi-line outputs used the fixed heredoc delimiter EOF_WP_TOOLING, but
the file list comes from the pull request: a file named EOF_WP_TOOLING
followed by one named php-count=0 closed the heredoc early and overrode
the real count. Each output now gets a random delimiter that no value
line may equal. coverage-gate shares the formatter and gets the same fix.

New tests run detect-changes against real git repositories (pull request
base, push before-SHA, new-branch fallback, explicit base, soft and
strict failure) and pin the forged-output case.

* fix(scaffolds): install the @rtCamp config packages from git, not npm

lint/eslint, lint/stylelint and setup/tailwind reported
@rtcamp/eslint-config, @rtcamp/stylelint-config and
@rtcamp/tailwind-config at ^0.1.0. None of the three is on the npm
registry (each returns 404, and the packages are at 1.0.0), so the
reported install failed with E404, and features --enable tailwind
--record-deps wrote that spec into package.json, breaking the project's
next npm install.

Use the git specs the theme and plugin skeletons already install from
(github:rtCamp/wp-tooling#npm/<package>). Verified in a scratch project:
the ^0.1.0 spec fails with E404 and the git spec installs.

* test(features): cover the features CLI end to end and fix two report bugs

The features command (also embedded by the theme init through
runFeatures) had 38% line coverage: nothing exercised argument parsing,
status output, the human report, the interactive flow or the JSON error
path. The new suites run it against the bundled Tailwind feature in
throwaway projects (status, enable, --record-deps, --dry-run, disable
with and without --force, unknown ids, prompts, cancellation, a failing
change) and against a mocked npm for the install success and failure
reports.

They found two bugs, fixed here:
- after a failed npm install, the retry commands put runtime
  dependencies under --save-dev;
- a failed interactive change read 'Failed to enabling <id>'.

* test(add): cover the human report sections and the interactive wizard

The add command sat at 58% line coverage: the interactive wizard
(prompting for missing inputs, confirm, spinner, cancel, dry run) and
most human report sections (composer installs, secrets, wiring,
scaffolded tests, skipped files, warnings, the available-scaffolds list
for an unknown id) never ran in tests. The new suite drives them with
the real Wizard and stubbed prompts.

* test(init): cover the FeatureApi helpers skeleton feature hooks rely on

The theme's Tailwind toggle flips a PHP define through readDefine and
setDefine, and its HMR toggle edits .env.local through readEnv and
setEnv; none of those, nor writeFlag or hasDep, had a test. The new suite
pins reading and writing, that every mutation's journal entry restores
exactly what it changed (including directories a write created), that
remove refuses directories, and that paths cannot leave the project.

Fixes what it found: the key and define name were interpolated into a
regular expression, so a name containing a metacharacter such as '.'
matched other lines. Both are now escaped.

* test: cover manage-mode flags, list CLI errors and the Tailwind webpack hooks

- init manage mode driven by flags, the way CI and AI callers run it
  (--features, --enable, --disable, --yes alone refused, undeclared keys
  rejected), plus the read-only status view (drift, unknown state,
  retired keys);
- wp-tooling list: space-separated flag forms, argument errors, the
  empty table, and a registry that fails to load, in plain and --json
  output;
- tailwind-config: GenerateTailwindThemePlugin.apply(), which wires the
  run, watchRun (skip rebuilds that did not touch theme.json) and
  afterEmit hooks. Its functions go from 60% to 100% covered.

* docs(changelog): restore the wp/cli description escaping note

The merge from main kept the older wording and dropped the entry for the wp/cli description fix that #70 shipped.

* docs(examples): install @rtcamp/eslint-config at ^1.0.0

The package publishes to npm as 1.0.0, and ^0.1.0 only matches versions below 0.2.0.

* fix(ci): export DetectChangesError from @rtcamp/wp-tooling/ci

The class was exported from src/ci/detect-changes.js but not from the ci barrel, so consumers of the documented @rtcamp/wp-tooling/ci entry point could not catch it by type. Adds the export and a test next to the existing GateError one.

* docs(changelog): separate the Unreleased sections

---------

Co-authored-by: Aditya Singh <adityasinghboss1234@gmail.com>
Co-authored-by: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request Oct 1, 2026
* fix(scaffolds): harden generation and tooling

* feat(scaffolds)!: target rtcamp/wp-primitives ^2.0

wp-framework is renamed to wp-primitives in 2.0.0 (rtCamp/wp-primitives#98).
- composer deps and WPPrimitives namespace across templates, tests, snippets
- docs/wp-framework-contract.md renamed to docs/wp-primitives-contract.md
- block JS keeps WordPress spacing; docblock and CHANGELOG follow-ups

* ci: lint and test every pull request and push to main

Nothing ran the suites on a pull request, while every merge to main is
split straight onto the npm/* branches that the skeletons and the shared
workflows install from. A broken merge therefore reached new installs
with no gate in between.

The new workflow runs every workspace lint and Jest suite with coverage
thresholds on Node 22.19 (the engines floor) and 24, and installs and
tests each composer package standalone, the way it ships after the
subtree split, on PHP 8.2, 8.3 and 8.4.

* ci(release): repair diverged mirrors and narrow secret and token scope

Both mirrors already carry a commit the split did not make (an org bot
merged a Copilot review workflow into rtCamp/wp-phpcs and
rtCamp/wp-phpstan on 2026-08-13). The release refuses to fast-forward
over that, so the next tag would fail before reaching Packagist, and the
only way out was a force push that also mints a throwaway tag.

- release-php: add a resync_branch dispatch input that forces just the
  mirror branch back onto the split and pushes no tag, and say exactly
  that in the error when a release does not fast-forward.
- release-php: reference the deploy keys by name instead of
  secrets[matrix.secret_name], which hands the job the whole secrets
  context, and check the secret name before the indirect lookup.
- split-npm-packages: grant contents: write to the split job only.

* chore(composer): add Packagist metadata to wp-phpcs and wp-phpstan

Both packages are about to be submitted to Packagist from their split
mirrors, which are read-only. Point homepage, support.issues and
support.source at the monorepo so the Packagist page sends issues and
contributions to the right place, and name rtCamp as the author.

* fix(detect-changes): add --strict and randomise the GitHub output delimiter

A failed git diff counted as zero changes, which skipped every job gated
on the counts and left the run green. rtCamp/wp-shared-workflows works
around it by grepping stderr. --strict (detectChanges({ strict: true }))
now exits 1 with a DetectChangesError instead; it is off by default so
existing callers see no change, and the stderr keeps the 'git diff
failed' phrase the shared workflow matches.

Multi-line outputs used the fixed heredoc delimiter EOF_WP_TOOLING, but
the file list comes from the pull request: a file named EOF_WP_TOOLING
followed by one named php-count=0 closed the heredoc early and overrode
the real count. Each output now gets a random delimiter that no value
line may equal. coverage-gate shares the formatter and gets the same fix.

New tests run detect-changes against real git repositories (pull request
base, push before-SHA, new-branch fallback, explicit base, soft and
strict failure) and pin the forged-output case.

* fix(scaffolds): install the @rtCamp config packages from git, not npm

lint/eslint, lint/stylelint and setup/tailwind reported
@rtcamp/eslint-config, @rtcamp/stylelint-config and
@rtcamp/tailwind-config at ^0.1.0. None of the three is on the npm
registry (each returns 404, and the packages are at 1.0.0), so the
reported install failed with E404, and features --enable tailwind
--record-deps wrote that spec into package.json, breaking the project's
next npm install.

Use the git specs the theme and plugin skeletons already install from
(github:rtCamp/wp-tooling#npm/<package>). Verified in a scratch project:
the ^0.1.0 spec fails with E404 and the git spec installs.

* test(features): cover the features CLI end to end and fix two report bugs

The features command (also embedded by the theme init through
runFeatures) had 38% line coverage: nothing exercised argument parsing,
status output, the human report, the interactive flow or the JSON error
path. The new suites run it against the bundled Tailwind feature in
throwaway projects (status, enable, --record-deps, --dry-run, disable
with and without --force, unknown ids, prompts, cancellation, a failing
change) and against a mocked npm for the install success and failure
reports.

They found two bugs, fixed here:
- after a failed npm install, the retry commands put runtime
  dependencies under --save-dev;
- a failed interactive change read 'Failed to enabling <id>'.

* test(add): cover the human report sections and the interactive wizard

The add command sat at 58% line coverage: the interactive wizard
(prompting for missing inputs, confirm, spinner, cancel, dry run) and
most human report sections (composer installs, secrets, wiring,
scaffolded tests, skipped files, warnings, the available-scaffolds list
for an unknown id) never ran in tests. The new suite drives them with
the real Wizard and stubbed prompts.

* test(init): cover the FeatureApi helpers skeleton feature hooks rely on

The theme's Tailwind toggle flips a PHP define through readDefine and
setDefine, and its HMR toggle edits .env.local through readEnv and
setEnv; none of those, nor writeFlag or hasDep, had a test. The new suite
pins reading and writing, that every mutation's journal entry restores
exactly what it changed (including directories a write created), that
remove refuses directories, and that paths cannot leave the project.

Fixes what it found: the key and define name were interpolated into a
regular expression, so a name containing a metacharacter such as '.'
matched other lines. Both are now escaped.

* test: cover manage-mode flags, list CLI errors and the Tailwind webpack hooks

- init manage mode driven by flags, the way CI and AI callers run it
  (--features, --enable, --disable, --yes alone refused, undeclared keys
  rejected), plus the read-only status view (drift, unknown state,
  retired keys);
- wp-tooling list: space-separated flag forms, argument errors, the
  empty table, and a registry that fails to load, in plain and --json
  output;
- tailwind-config: GenerateTailwindThemePlugin.apply(), which wires the
  run, watchRun (skip rebuilds that did not touch theme.json) and
  afterEmit hooks. Its functions go from 60% to 100% covered.

* docs(changelog): restore the wp/cli description escaping note

The merge from main kept the older wording and dropped the entry for the wp/cli description fix that #70 shipped.

* docs(examples): install @rtcamp/eslint-config at ^1.0.0

The package publishes to npm as 1.0.0, and ^0.1.0 only matches versions below 0.2.0.

* fix(ci): export DetectChangesError from @rtcamp/wp-tooling/ci

The class was exported from src/ci/detect-changes.js but not from the ci barrel, so consumers of the documented @rtcamp/wp-tooling/ci entry point could not catch it by type. Adds the export and a test next to the existing GateError one.

* docs(changelog): separate the Unreleased sections

---------

Co-authored-by: Aditya Singh <adityasinghboss1234@gmail.com>
Co-authored-by: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request Oct 1, 2026
* fix(scaffolds): harden generation and tooling

* feat(scaffolds)!: target rtcamp/wp-primitives ^2.0

wp-framework is renamed to wp-primitives in 2.0.0 (rtCamp/wp-primitives#98).
- composer deps and WPPrimitives namespace across templates, tests, snippets
- docs/wp-framework-contract.md renamed to docs/wp-primitives-contract.md
- block JS keeps WordPress spacing; docblock and CHANGELOG follow-ups

* ci: lint and test every pull request and push to main

Nothing ran the suites on a pull request, while every merge to main is
split straight onto the npm/* branches that the skeletons and the shared
workflows install from. A broken merge therefore reached new installs
with no gate in between.

The new workflow runs every workspace lint and Jest suite with coverage
thresholds on Node 22.19 (the engines floor) and 24, and installs and
tests each composer package standalone, the way it ships after the
subtree split, on PHP 8.2, 8.3 and 8.4.

* ci(release): repair diverged mirrors and narrow secret and token scope

Both mirrors already carry a commit the split did not make (an org bot
merged a Copilot review workflow into rtCamp/wp-phpcs and
rtCamp/wp-phpstan on 2026-08-13). The release refuses to fast-forward
over that, so the next tag would fail before reaching Packagist, and the
only way out was a force push that also mints a throwaway tag.

- release-php: add a resync_branch dispatch input that forces just the
  mirror branch back onto the split and pushes no tag, and say exactly
  that in the error when a release does not fast-forward.
- release-php: reference the deploy keys by name instead of
  secrets[matrix.secret_name], which hands the job the whole secrets
  context, and check the secret name before the indirect lookup.
- split-npm-packages: grant contents: write to the split job only.

* chore(composer): add Packagist metadata to wp-phpcs and wp-phpstan

Both packages are about to be submitted to Packagist from their split
mirrors, which are read-only. Point homepage, support.issues and
support.source at the monorepo so the Packagist page sends issues and
contributions to the right place, and name rtCamp as the author.

* fix(detect-changes): add --strict and randomise the GitHub output delimiter

A failed git diff counted as zero changes, which skipped every job gated
on the counts and left the run green. rtCamp/wp-shared-workflows works
around it by grepping stderr. --strict (detectChanges({ strict: true }))
now exits 1 with a DetectChangesError instead; it is off by default so
existing callers see no change, and the stderr keeps the 'git diff
failed' phrase the shared workflow matches.

Multi-line outputs used the fixed heredoc delimiter EOF_WP_TOOLING, but
the file list comes from the pull request: a file named EOF_WP_TOOLING
followed by one named php-count=0 closed the heredoc early and overrode
the real count. Each output now gets a random delimiter that no value
line may equal. coverage-gate shares the formatter and gets the same fix.

New tests run detect-changes against real git repositories (pull request
base, push before-SHA, new-branch fallback, explicit base, soft and
strict failure) and pin the forged-output case.

* fix(scaffolds): install the @rtCamp config packages from git, not npm

lint/eslint, lint/stylelint and setup/tailwind reported
@rtcamp/eslint-config, @rtcamp/stylelint-config and
@rtcamp/tailwind-config at ^0.1.0. None of the three is on the npm
registry (each returns 404, and the packages are at 1.0.0), so the
reported install failed with E404, and features --enable tailwind
--record-deps wrote that spec into package.json, breaking the project's
next npm install.

Use the git specs the theme and plugin skeletons already install from
(github:rtCamp/wp-tooling#npm/<package>). Verified in a scratch project:
the ^0.1.0 spec fails with E404 and the git spec installs.

* test(features): cover the features CLI end to end and fix two report bugs

The features command (also embedded by the theme init through
runFeatures) had 38% line coverage: nothing exercised argument parsing,
status output, the human report, the interactive flow or the JSON error
path. The new suites run it against the bundled Tailwind feature in
throwaway projects (status, enable, --record-deps, --dry-run, disable
with and without --force, unknown ids, prompts, cancellation, a failing
change) and against a mocked npm for the install success and failure
reports.

They found two bugs, fixed here:
- after a failed npm install, the retry commands put runtime
  dependencies under --save-dev;
- a failed interactive change read 'Failed to enabling <id>'.

* test(add): cover the human report sections and the interactive wizard

The add command sat at 58% line coverage: the interactive wizard
(prompting for missing inputs, confirm, spinner, cancel, dry run) and
most human report sections (composer installs, secrets, wiring,
scaffolded tests, skipped files, warnings, the available-scaffolds list
for an unknown id) never ran in tests. The new suite drives them with
the real Wizard and stubbed prompts.

* test(init): cover the FeatureApi helpers skeleton feature hooks rely on

The theme's Tailwind toggle flips a PHP define through readDefine and
setDefine, and its HMR toggle edits .env.local through readEnv and
setEnv; none of those, nor writeFlag or hasDep, had a test. The new suite
pins reading and writing, that every mutation's journal entry restores
exactly what it changed (including directories a write created), that
remove refuses directories, and that paths cannot leave the project.

Fixes what it found: the key and define name were interpolated into a
regular expression, so a name containing a metacharacter such as '.'
matched other lines. Both are now escaped.

* test: cover manage-mode flags, list CLI errors and the Tailwind webpack hooks

- init manage mode driven by flags, the way CI and AI callers run it
  (--features, --enable, --disable, --yes alone refused, undeclared keys
  rejected), plus the read-only status view (drift, unknown state,
  retired keys);
- wp-tooling list: space-separated flag forms, argument errors, the
  empty table, and a registry that fails to load, in plain and --json
  output;
- tailwind-config: GenerateTailwindThemePlugin.apply(), which wires the
  run, watchRun (skip rebuilds that did not touch theme.json) and
  afterEmit hooks. Its functions go from 60% to 100% covered.

* docs(changelog): restore the wp/cli description escaping note

The merge from main kept the older wording and dropped the entry for the wp/cli description fix that #70 shipped.

* docs(examples): install @rtcamp/eslint-config at ^1.0.0

The package publishes to npm as 1.0.0, and ^0.1.0 only matches versions below 0.2.0.

* fix(ci): export DetectChangesError from @rtcamp/wp-tooling/ci

The class was exported from src/ci/detect-changes.js but not from the ci barrel, so consumers of the documented @rtcamp/wp-tooling/ci entry point could not catch it by type. Adds the export and a test next to the existing GateError one.

* docs(changelog): separate the Unreleased sections

---------

Co-authored-by: Aditya Singh <adityasinghboss1234@gmail.com>
Co-authored-by: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[scaffolds] Rename rtcamp/wp-framework to rtcamp/wp-primitives across wp-tooling

3 participants