Skip to content
Open
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
1 change: 1 addition & 0 deletions node-packages/wp-tooling/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- `lint/i18n` scaffold — a standalone `phpcs.i18n.xml.dist` running only `WordPress.WP.I18n` against the project's text domain, plus a `lint:i18n` composer script. Standalone because the `text_domain` property is project-specific and the engine never edits an existing `phpcs.xml.dist`. The `text_domain` input is `required` (discovered from `.wp-tooling.json` `textDomain` when present) rather than defaulted, because the sniff silently loses its `MissingArgDomain` / `TextDomainMismatch` checks when the property is wrong or unset. The rendered ruleset deliberately omits `default` from the allowed domains — listing it downgrades a missing domain argument from the `MissingArgDomain` error to the `MissingArgDomainDefault` warning — with a commented opt-in for projects that intentionally reuse core strings. Companion to the i18n lens skill in `rtcamp/wp-devtools`.
- Remote scaffolds — a scaffold's `scaffold.json` + templates can live in another repo. `scaffolds/sources.json` lists the source repos (pinned `{ repository, ref, path }`); each repo publishes a `scaffolds/index.json` enumerating the scaffolds it offers, which the registry fetches to discover them (one PR in the owning repo adds/changes a scaffold; wp-tooling only changes to onboard a new repo). Manifests + templates are fetched on `add`, cached under `${XDG_CACHE_HOME:-$HOME/.cache}/wp-tooling/remote/` and validated with ETag conditional requests (`304 Not Modified` serves the cache; movable tags refresh when they move). New error code `EFETCHFAIL` (network/HTTP) distinct from `EBADSCAFFOLD` (bad index/manifest). `list` is online-preferred with a cache fallback and reports unreachable sources as warnings; `validate --remote` fetches + schema-validates each index + manifest; `wp-tooling cache clear` empties the cache. Dormant by default — no `sources.json` ships.
- Engine-side input discovery (`discover_from`) — an input declaration can say where to source its value from the project, so the engine fills it instead of the caller guessing. Resolves from `composer.json` / `package.json` (dotted paths; `autoload.psr-4` yields the root namespace) and `.wp-tooling.json`, with precedence `supplied → discovered → default`. Fail-safe: a missing or malformed project file falls through to the input's `default`, so a project without those files behaves exactly as before the resolver existed. Adds an optional `transform` step for derived inputs (`json-escape` doubles backslashes for the PSR-4 composer key). The inputs the engine actually rendered with are surfaced on `execute()` as `engine.inputs`. Bundled `setup/psr4` + `wp/*` scaffolds annotated with `discover_from`.
- Feature toggle layer — a scaffold may declare an optional `feature` block (`config_key`, `owned_files`, `confirm_remove`, `gitignore`) marking it as a toggleable project feature. New TTY-free `enable` / `disable` / `status` verbs create or remove the owned files idempotently, manage `.gitignore` lines (Mustache-rendered against resolved inputs), prompt before deleting consumer-editable files (`confirm_remove`, overridable with `--force`), and persist on/off state in `.wp-tooling.json`. New `wp-tooling features` command — lists feature status by default, with `--enable` / `--disable` to toggle (plus `--json`, `--force`, `--no-install`, `--dry-run`); `setup/tailwind` ships as the first such feature. Additive — the `feature` block never affects the `add` / `execute` path.
Expand Down
30 changes: 30 additions & 0 deletions node-packages/wp-tooling/scaffolds/lint/i18n/scaffold.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"slug": "i18n",
"category": "lint",
"name": "PHPCS - i18n",
"description": "Standalone PHP_CodeSniffer ruleset running only WordPress.WP.I18n, configured with the project's text domain, plus a `lint:i18n` composer script. Separate from the main phpcs.xml.dist because the text domain is project-specific and the engine never edits existing files.",
"source": "template",
"files": [
{
"src": "templates/phpcs.i18n.xml.dist.mustache",
"dest": "phpcs.i18n.xml.dist"
}
],
"inputs": [
{
"key": "text_domain",
"description": "The project's text domain, exactly as it appears in the plugin header's `Text Domain:` (or the theme's style.css). The sniff cannot report a wrong domain without it, so it is required rather than guessed.",
"discover_from": "config:textDomain",
"required": true
}
],
"composer_dev_dependencies": {
"squizlabs/php_codesniffer": "^3.7",
"wp-coding-standards/wpcs": "^3.0"
},
"scripts": {
"composer": {
"lint:i18n": "phpcs --standard=phpcs.i18n.xml.dist"
}
}
Comment on lines +21 to +29
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0"?>
<ruleset name="Project i18n">
<description>Translation-readiness checks: the WordPress.WP.I18n sniff, bound to this project's text domain.</description>

<!--
Scaffolded by @rtcamp/wp-tooling. Run with `composer lint:i18n`.

Standalone on purpose: the `text_domain` property is project-specific and
the scaffold engine never edits an existing phpcs.xml.dist. Once you are
happy with it, copying the rule block below into your main ruleset makes
the check part of every `composer lint` run.

Without the text_domain property the sniff cannot report a wrong domain
at all — MissingArgDomain and TextDomainMismatch go silent while the
placeholder and translators-comment checks still run. That is why this
file exists.
-->

<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="{{text_domain}}"/>
<!--
Deliberately strict: `default` (core's own domain) is NOT listed.
Adding it lets strings reuse core translations without a mismatch,
but it also downgrades a *missing* domain argument from the
`MissingArgDomain` error to the `MissingArgDomainDefault` warning —
because an omitted domain then resolves to an allowed one. Add the
line below only if the project intentionally reuses core strings,
and expect that trade-off:

<element value="default"/>
-->
</property>
</properties>
</rule>

<!-- The sniff only reads PHP; limiting extensions keeps CSS/JS out of the run. -->
<arg name="extensions" value="php"/>
Comment on lines +38 to +39

<file>./</file>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/build/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<!-- Installed Claude Code skills ship deliberately-defective eval fixtures;
linting them reports seeded defects as if they were the project's own. -->
<exclude-pattern>*/.claude/*</exclude-pattern>
Comment on lines +46 to +48

<!--
Project-specific overrides go here. The common one: block render files
generated by @wordpress/create-block carry the block's own text domain
rather than the project's, so they read as a mismatch. Uncomment if that
applies — but only after confirming each domain really is intentional,
because this silences a real check on real source:

<rule ref="WordPress.WP.I18n">
<exclude-pattern>src/blocks/*/render.php</exclude-pattern>
</rule>
-->
</ruleset>
Loading