@wpelevator/wp-release 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -6
- package/dist/changeset-config.d.ts +5 -0
- package/dist/changeset-config.js +3 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +10 -0
- package/dist/commands/i18n-audit.d.ts +2 -0
- package/dist/commands/i18n-audit.js +2 -0
- package/dist/commands/i18n-compile.d.ts +6 -0
- package/dist/commands/i18n-compile.js +76 -0
- package/dist/commands/i18n-json.d.ts +2 -0
- package/dist/commands/i18n-json.js +2 -0
- package/dist/commands/i18n-mo.d.ts +2 -0
- package/dist/commands/i18n-mo.js +2 -0
- package/dist/commands/i18n-php.d.ts +2 -0
- package/dist/commands/i18n-php.js +2 -0
- package/dist/commands/i18n-pot.d.ts +2 -0
- package/dist/commands/i18n-pot.js +2 -0
- package/dist/commands/i18n.d.ts +12 -0
- package/dist/commands/i18n.js +174 -0
- package/dist/i18n/atomic-output.d.ts +23 -0
- package/dist/i18n/atomic-output.js +90 -0
- package/dist/i18n/audit.d.ts +7 -0
- package/dist/i18n/audit.js +69 -0
- package/dist/i18n/catalog.d.ts +54 -0
- package/dist/i18n/catalog.js +86 -0
- package/dist/i18n/compile.d.ts +27 -0
- package/dist/i18n/compile.js +71 -0
- package/dist/i18n/extraction.d.ts +45 -0
- package/dist/i18n/extraction.js +105 -0
- package/dist/i18n/files.d.ts +7 -0
- package/dist/i18n/files.js +45 -0
- package/dist/i18n/generate.d.ts +49 -0
- package/dist/i18n/generate.js +240 -0
- package/dist/i18n/javascript.d.ts +7 -0
- package/dist/i18n/javascript.js +143 -0
- package/dist/i18n/metadata.d.ts +16 -0
- package/dist/i18n/metadata.js +117 -0
- package/dist/i18n/options.d.ts +52 -0
- package/dist/i18n/options.js +156 -0
- package/dist/i18n/packages.d.ts +24 -0
- package/dist/i18n/packages.js +86 -0
- package/dist/i18n/php.d.ts +9 -0
- package/dist/i18n/php.js +238 -0
- package/dist/i18n/placeholders.d.ts +9 -0
- package/dist/i18n/placeholders.js +13 -0
- package/dist/i18n/po.d.ts +24 -0
- package/dist/i18n/po.js +180 -0
- package/dist/i18n/pot.d.ts +16 -0
- package/dist/i18n/pot.js +119 -0
- package/dist/i18n/translation-json.d.ts +14 -0
- package/dist/i18n/translation-json.js +96 -0
- package/dist/i18n/translation-php.d.ts +6 -0
- package/dist/i18n/translation-php.js +35 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +14 -0
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# WP Release
|
|
2
2
|
|
|
3
|
-
Package, checksum, sign, verify and version-check WordPress plugins and themes, deploy plugins to the WordPress.org plugin SVN repository, and deploy plugins, themes and packages to Git repositories. Usable as a CLI, as a library, and through the [`wp-release-zip`](https://github.com/wpelevator/wp-release-zip), [`wp-release-checksums`](https://github.com/wpelevator/wp-release-checksums), [`wp-release-sign`](https://github.com/wpelevator/wp-release-sign) and [`wp-release-deploy-svn`](https://github.com/wpelevator/wp-release-deploy-svn) GitHub Actions.
|
|
3
|
+
Package, checksum, sign, verify and version-check WordPress plugins and themes, extract and compile their translations, deploy plugins to the WordPress.org plugin SVN repository, and deploy plugins, themes and packages to Git repositories. Usable as a CLI, as a library, and through the [`wp-release-zip`](https://github.com/wpelevator/wp-release-zip), [`wp-release-checksums`](https://github.com/wpelevator/wp-release-checksums), [`wp-release-sign`](https://github.com/wpelevator/wp-release-sign) and [`wp-release-deploy-svn`](https://github.com/wpelevator/wp-release-deploy-svn) GitHub Actions.
|
|
4
4
|
|
|
5
5
|
Themes are released by uploading a ZIP on WordPress.org, so `deploy-svn` is for plugins only; use `zip` to build the theme upload.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Packaging and deploy options follow the existing community tools (10up WordPress.org actions, `wp dist-archive`, `wp-scripts plugin-zip`, `grunt-wp-deploy`) so that migrating needs few or no renames. The internationalization commands favor simpler, CI-friendly options over matching `wp i18n`. See [Comparison with existing tools](#comparison-with-existing-tools) for the differences.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -35,12 +35,17 @@ npx wp-release --help
|
|
|
35
35
|
| `wp-release deploy-git [<source-dir>] --git-url <url> [options]` | Deploy the files of a plugin, theme or package to a Git repository as a release commit. |
|
|
36
36
|
| `wp-release deploy-git --release-ref <ref> [--package <name>] [--list]` | Deploy the configured packages of a release tag or branch to their Git targets. |
|
|
37
37
|
| `wp-release deploy-svn --release-ref <ref> [--package <name>] [--list]` | Deploy the configured plugin of a release tag to WordPress.org. |
|
|
38
|
+
| `wp-release i18n-pot [<source-dir>] [options]` | Extract translatable strings into a reproducible POT file. |
|
|
39
|
+
| `wp-release i18n-audit [<source-dir>] [options]` | Report translation string problems without writing files. |
|
|
40
|
+
| `wp-release i18n-mo <file.po> [options]` | Compile a translated PO file into MO. |
|
|
41
|
+
| `wp-release i18n-json <file.po> --domain <domain> [options]` | Compile the JavaScript strings of a translated PO file into WordPress script JSON files. |
|
|
42
|
+
| `wp-release i18n-php <file.po> [options]` | Compile a translated PO file into a WordPress `.l10n.php` file. |
|
|
38
43
|
| `wp-release changeset-add [options]` | Record the release impact of a pull request. |
|
|
39
44
|
| `wp-release changeset-status [options]` | List pending releases and check changed packages for changesets. |
|
|
40
45
|
| `wp-release changeset-version [options]` | Apply version bumps and changelog entries. |
|
|
41
46
|
| `wp-release changeset-tag [options]` | Create missing package release tags. |
|
|
42
47
|
|
|
43
|
-
Run `npx wp-release <command> --help` for the options of each command. Directory-based commands accept a first `<source-dir>` argument or `--source-dir`, defaulting to the current directory. `sign`
|
|
48
|
+
Run `npx wp-release <command> --help` for the options of each command. Directory-based commands accept a first `<source-dir>` argument or `--source-dir`, defaulting to the current directory. `sign` and the `i18n-mo`, `i18n-json` and `i18n-php` compilers take file arguments, `keygen` generates keys, and changeset commands operate on the configured repository. Every command prints its result as JSON with `--json`.
|
|
44
49
|
|
|
45
50
|
In GitHub Actions, `--github-output` writes the complete result as JSON to `steps.<id>.outputs.result` and exposes each top-level string, number or boolean as a plain output. Use `steps.zip.outputs.zipPath` for a ZIP path, for example. Arrays and objects remain in `result`: tag metadata is read with `fromJSON( steps.tag.outputs.result ).tags[0].tag`, even when only one package is selected. There is no plain `steps.tag.outputs.tag` output.
|
|
46
51
|
|
|
@@ -795,6 +800,83 @@ A deploy releases the files of the working tree, so build the packages first. On
|
|
|
795
800
|
|
|
796
801
|
Other monorepo tools split the same way with less of the release in the config: [splitsh-lite](https://github.com/splitsh/lite), [Symplify MonorepoBuilder](https://github.com/symplify/monorepo-builder) and [`danharrin/monorepo-split-github-action`](https://github.com/danharrin/monorepo-split-github-action) push the source history of each package directory, with the packages and repositories listed in the workflow or a matrix, while `deploy-git` releases the built files on top of that history, so `vendor/` and `build/` reach Composer and WordPress installs. Like the publishers of [GoReleaser](https://goreleaser.com/), targets are lists with templates, and pre-releases skip the targets where they would replace the stable version.
|
|
797
802
|
|
|
803
|
+
### WordPress internationalization
|
|
804
|
+
|
|
805
|
+
`i18n-pot` extracts plugin/theme strings and writes a reproducible UTF-8 POT. `i18n-audit` reads the same source and returns findings without writing files. Both run with Node alone: no PHP, WordPress installation, database, WP-CLI or network requests. Extraction is independent of `files`, `.distignore` and Git `export-ignore` distribution rules.
|
|
806
|
+
|
|
807
|
+
```sh
|
|
808
|
+
wp-release i18n-pot .
|
|
809
|
+
wp-release i18n-pot --package plugins/agent-pilot
|
|
810
|
+
wp-release i18n-pot --all
|
|
811
|
+
wp-release i18n-pot --package plugins/agent-pilot --check
|
|
812
|
+
wp-release i18n-audit --package plugins/agent-pilot --json
|
|
813
|
+
wp-release i18n-audit . --strict --suppress conflicting-comments
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Config-free source directories must have plugin/theme headers. The actual main file is discovered using the package metadata reader, including plugins whose main filename differs from their slug. The selected domain defaults to `Text Domain`, then the directory name. Calls with omitted domains belong to WordPress's `default` domain; block metadata without a domain inherits the package domain. `--all-domains` explicitly combines domains. A block's explicit `textdomain` always controls its messages.
|
|
817
|
+
|
|
818
|
+
`--package` selects one configured package and permits extraction-specific overrides; `--all` requires configuration and selects enabled WordPress packages only. Source directories, package selectors and `--all` are mutually exclusive. Config-free directory mode reads only its explicit options and metadata; it does not import repository config implicitly. A disabled package fails when explicitly selected. Configuration uses `i18n` at the root and on package entries, including an exact `glob` for an individual package, following the existing group model. Package entries remain non-overlapping; there is no additional package manifest configuration source.
|
|
819
|
+
|
|
820
|
+
```js
|
|
821
|
+
export default defineConfig( {
|
|
822
|
+
root: 'packages',
|
|
823
|
+
i18n: {
|
|
824
|
+
headers: { 'Report-Msgid-Bugs-To': 'https://example.org/issues' },
|
|
825
|
+
fileComment: `Copyright (C) Example Company
|
|
826
|
+
This file is distributed under the same license as the Example plugin.`,
|
|
827
|
+
},
|
|
828
|
+
packages: [ {
|
|
829
|
+
glob: 'plugins/*',
|
|
830
|
+
i18n: {
|
|
831
|
+
include: [ '**/*.{php,js,jsx,ts,tsx,mjs,cjs}', '**/block.json', '**/theme.json' ],
|
|
832
|
+
exclude: [ '**/node_modules/**', '**/vendor/**', '**/tests/**', '**/build/**' ],
|
|
833
|
+
destination: 'languages',
|
|
834
|
+
strict: false,
|
|
835
|
+
},
|
|
836
|
+
} ],
|
|
837
|
+
} );
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
Precedence is defaults, root configuration, package group/individual entry, then explicit command options. Scalars override, lists replace inherited lists, and gettext headers merge by key, case-insensitively, preserving intentionally empty values. A header set to `false` is removed, like `'Project-Id-Version': false` to keep the plugin version out of the POT so that it changes only when strings change; a later layer can set it again. `Content-Type` can't be removed, since it declares the UTF-8 charset. Options include `enabled`, `domain`, `allDomains`, `include`, `exclude`, `destination`, `headers`, `fileComment`, `references`, `strict`, `legacyGlobals` and audit `suppress`. `fileComment` adds static text above the POT header; each line is emitted as a gettext comment, and no copyright year is generated. The CLI accepts the same setting with `--file-comment`. Repeat `--include` or `--exclude` for multiple globs; these use case-sensitive [minimatch](https://github.com/isaacs/minimatch) semantics with `/` paths, `**`, brace expansion and dotfile matching, without gitignore negation or comma splitting. Default excludes cover dependency directories, dot directories, tests, minified files and generated translations. Source/build exclusions are a repository choice; this repository excludes `build`, `tools` and isolated vendors in its config.
|
|
841
|
+
|
|
842
|
+
The default output is `<Domain Path>/<text-domain>.pot`, or `languages/<text-domain>.pot`. A leading `/` in `Domain Path` is package-relative, and configured destinations cannot escape the package. `--destination` changes the package-relative directory. An explicit `--output <file.pot>` resolves against the command's working directory and requires one package. All multi-package destinations are validated before writing; each completed package replaces its file atomically, independently of other packages. `--check` compares bytes without writing and fails if the POT is absent or stale. Source references are preserved by default; `--no-references` omits them. `POT-Creation-Date` and `PO-Revision-Date` default to empty values; no current date or year is inserted. Name/version headers come from metadata, and `--header NAME=VALUE` overrides any default, including with an empty value, and `--header NAME` removes the header. The POT lists the plugin or theme header strings first, then the strings of PHP, JavaScript, `block.json` and other metadata files in source order, so related strings stay together for translators. Each entry has its extracted comments, references and flags before the strings, in the usual gettext layout. The default headers come in a fixed order, and custom headers follow them. No generator header or generated copyright comment is added; set `fileComment` for a static one.
|
|
843
|
+
|
|
844
|
+
#### Supported extraction syntax
|
|
845
|
+
|
|
846
|
+
PHP extraction uses `php-parser` in PHP 8.3 mode, tested with attributes, enums, match expressions, promoted properties and readonly classes. Supported WordPress functions are `__`, `_e`, `_x`, `_ex`, `_n`, `_nx`, `_n_noop`, `_nx_noop`, `esc_attr__`, `esc_attr_e`, `esc_attr_x`, `esc_html__`, `esc_html_e` and `esc_html_x`. Global and explicitly global calls, namespace fallback and explicit function imports/aliases are supported. Known file-local declarations and unrelated namespaced imports are excluded; methods and static methods are never translation calls. Named arguments resolve by PHP parameter name. Literal strings, static concatenation and non-interpolated heredoc/nowdoc preserve decoded escapes, Unicode and newlines. Counts may be dynamic; IDs, contexts and selected domains must be static.
|
|
847
|
+
|
|
848
|
+
JavaScript extraction uses Babel's parser and lexical scope traversal, without transforms or project Babel config. JS, JSX, TS, TSX, MJS, CJS, MTS and CTS are supported. Named imports/aliases and namespace imports from `@wordpress/i18n`, unshadowed `wp.i18n` calls and unbound canonical legacy `__`, `_x`, `_n` and `_nx` calls extract strings. `--no-legacy-globals` requires imported or `wp.i18n` bindings. Locally shadowed/reassigned imports are excluded. Static literals, templates without substitutions, concatenation and TypeScript assertions are supported. Destructured CommonJS imports, transpiled `(0, i18n.__)` forms and arbitrary minified bundles are outside the supported syntax; scan source and use explicit source/build mapping for compiled JSON.
|
|
849
|
+
|
|
850
|
+
A `translators:` comment attaches to the first translation call whose containing expression or nearest statement immediately follows it. Whitespace, parsed comments and ternary branch punctuation may intervene, but unrelated statements or argument expressions may not. Comments before outer wrappers reach nested translation calls; a comment is consumed once, so unrelated sibling calls do not inherit it. Multiline comment content is preserved. WordPress plugin/theme header labels serialize as extracted comments. Bundled metadata tables follow [WordPress 6.8 block fields](https://github.com/WordPress/wordpress-develop/blob/6.8/src/wp-includes/block-i18n.json) and [theme fields](https://github.com/WordPress/wordpress-develop/blob/6.8/src/wp-includes/theme-i18n.json), including styles, variations, presets, template names and nested block settings. Their upstream data license is GPL-2.0-or-later; tables are maintained intentionally with fixtures and never fetched during generation.
|
|
851
|
+
|
|
852
|
+
Invalid syntax, unreadable selected files, selected source symlinks, empty/reserved message IDs, conflicting plural definitions and output errors fail generation. Unsupported dynamic arguments warn and are fatal with `--strict`. Audit adds `placeholder-comment`, `unordered-placeholders`, `plural-placeholders`, `empty-message` and `conflicting-comments` findings; suppress these explicitly by code where justified. Placeholders support `%%`, positional indexes, numeric width/precision, padding/flags and PHP/JS sprintf conversion types. Audit checks extraction and message quality, not every untranslated user-facing string. Audit fails on errors; strict audit also fails on warnings. Failed checks return exit code `2`; unexpected I/O/option-loading errors follow the existing CLI error handling. `--json`, GitHub annotations, summaries and step outputs use the existing CLI reporting facilities.
|
|
853
|
+
|
|
854
|
+
#### Compile translated catalogs
|
|
855
|
+
|
|
856
|
+
Runtime formats require a translated PO, not an untranslated POT. PO updating, fuzzy matching and automatic translation are not provided. Compilation validates `Language` and `Plural-Forms`, excludes fuzzy, obsolete and untranslated entries, preserves contexts and plural forms, and never changes the input PO. An incompletely translated plural entry is excluded as a unit. Gettext parsing handles source encoding; runtime outputs use UTF-8. Each output is replaced atomically; JSON outputs are independent writes, and obsolete output files are not deleted automatically.
|
|
857
|
+
|
|
858
|
+
```sh
|
|
859
|
+
wp-release i18n-mo languages/my-plugin-lv.po
|
|
860
|
+
wp-release i18n-php languages/my-plugin-lv.po
|
|
861
|
+
wp-release i18n-json languages/my-plugin-lv.po --domain my-plugin --map scripts.json --output languages
|
|
862
|
+
wp-release i18n-mo languages/my-plugin-lv.po --check
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
MO and `.l10n.php` default beside the PO, with `.mo` and `.l10n.php` extensions; `--output` selects an explicit file relative to cwd. WordPress PHP catalogs require WordPress 6.5 or later. JSON requires an explicit domain; `--output` is a directory, defaulting to the PO directory. `--check` compares proposed bytes without writing and exits `2` on absent/stale outputs. Compiler commands are primitives and do not select configured packages.
|
|
866
|
+
|
|
867
|
+
A script map is a JSON object from normalized package-relative source references to arrays of normalized runtime script paths. Many source files can feed one bundle, and one source can feed several scripts. An explicit map selects only listed references; an empty target array excludes that source. Without a map, JS/MJS/CJS references use identity paths, while JSX/TypeScript references require mapping. PHP-only references do not enter JSON catalogs.
|
|
868
|
+
|
|
869
|
+
```json
|
|
870
|
+
{
|
|
871
|
+
"src/blocks/editor.tsx": [ "build/editor.js", "build/shared.js" ],
|
|
872
|
+
"src/components/panel.js": [ "build/editor.js" ]
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
JSON filenames are `<domain>-<Language>-<md5(runtime-path)>.json`; the WordPress `default` domain uses `<Language>-<md5(runtime-path)>.json`. WordPress's `.min.js` normalization applies consistently to grouping, hashing and the JSON `source`. Jed keys preserve context through `\u0004`, plural arrays contain all translated forms, and revision dates come from the PO metadata. `.l10n.php` writes safely escaped data arrays with NUL-separated plural translations; catalog text is never treated as executable PHP.
|
|
877
|
+
|
|
878
|
+
The package can be installed and used in any WordPress plugin or theme project; repository package selection is optional, and directory mode works without a wp-release config.
|
|
879
|
+
|
|
798
880
|
### Exit codes
|
|
799
881
|
|
|
800
882
|
| Code | Meaning |
|
|
@@ -946,9 +1028,24 @@ const { valid, changed, missing, extra } = await verifyChecksums( {
|
|
|
946
1028
|
|
|
947
1029
|
`loadFileRules()` reads the ignore file and `--exclude` patterns into a gitignore matcher, optionally adds Git `export-ignore` paths, and `selectFiles()` lists the files it doesn't ignore. `compareChecksums()` compares two hash maps without reading any files, and `parseChecksums()` validates manifest JSON. `extractZip()` extracts a ZIP after the same checks as `deploy-svn --from-zip`.
|
|
948
1030
|
|
|
1031
|
+
Translations:
|
|
1032
|
+
```js
|
|
1033
|
+
import { extractCatalog, auditCatalog, serializePot, generatePackagePot } from '@wpelevator/wp-release';
|
|
1034
|
+
|
|
1035
|
+
const { catalog, diagnostics, files } = await extractCatalog( {
|
|
1036
|
+
sourceDir: '/project/plugin',
|
|
1037
|
+
options: { domain: 'my-plugin' },
|
|
1038
|
+
} );
|
|
1039
|
+
const findings = auditCatalog( catalog );
|
|
1040
|
+
const pot = serializePot( catalog );
|
|
1041
|
+
const result = await generatePackagePot( { sourceDir: '/project/plugin', check: true } );
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
Parsers return messages and structured diagnostics with stable codes, severity and source-relative locations. Catalog messages retain IDs, contexts, plurals, translations, translator comments, flags and structured references. `aggregateCatalog()` selects/merges a domain, unions references/comments and diagnoses plural conflicts. `resolveI18nPackages()` is the separate config adapter; extraction and serialization primitives know nothing about the repository. Library functions do not log, parse process arguments or set exit codes. Extraction results report unique files consulted, including the main plugin/theme file used for headers and header strings even when source globs exclude it. `parseTranslatedPo()`, `compileMo()`, `compileScriptTranslations()`, `compilePhpTranslations()` and `compileTranslations()` expose the same typed operations to library callers.
|
|
1045
|
+
|
|
949
1046
|
## Development
|
|
950
1047
|
|
|
951
|
-
The source is TypeScript in `src/`, with tests next to the files they test (`*.test.ts`). The published package contains only the compiled `dist/` with JavaScript and `.d.ts` files.
|
|
1048
|
+
The source is TypeScript in `src/`, with tests next to the files they test (`*.test.ts`). The published package contains only the compiled `dist/` with JavaScript and `.d.ts` files; tests and translation fixtures stay in the repository.
|
|
952
1049
|
|
|
953
1050
|
```bash
|
|
954
1051
|
npm run build --workspace @wpelevator/wp-release
|
|
@@ -960,6 +1057,8 @@ npm run lint --workspace @wpelevator/wp-release
|
|
|
960
1057
|
- `test` runs the `.ts` tests directly with `node --test`, using Node.js type stripping, so it needs Node.js 22.18 or later but no build. The SVN integration tests in `svn-deploy.integration.test.ts` create repositories with `svnadmin` and deploy to their `file://` URLs; they're skipped when `svn` or `svnadmin` isn't installed.
|
|
961
1058
|
- `lint` runs ESLint and a `tsc` type check.
|
|
962
1059
|
|
|
1060
|
+
The i18n tests validate gettext round trips, script catalogs and generated PHP data without launching PHP, WP-CLI or WordPress, covering contexts, plural forms, comments, flags, Unicode, escaping and PHP-looking input as data.
|
|
1061
|
+
|
|
963
1062
|
The `wpelevator-source` export condition points at `src/index.ts`. The wp-release tests and monorepo CLI scripts use it to work with the source instead of a `dist/` build. Action builds use the declared dependency's default export, which resolves to `dist/` for the workspace copy and the published package. Build wp-release before building the actions.
|
|
964
1063
|
|
|
965
1064
|
`execute( 'deploy-svn', args, { cwd, logger, svnRunner } )` runs a single-plugin deployment or a configured plan or deployment. The optional `svnRunner` replaces the external command runner in both modes; without it, the command uses its normal runner.
|
|
@@ -968,8 +1067,8 @@ The `wpelevator-source` export condition points at `src/index.ts`. The wp-releas
|
|
|
968
1067
|
|
|
969
1068
|
wp-release has two layers, so that it fits any repository layout:
|
|
970
1069
|
|
|
971
|
-
- **Primitives** work on the directory, file or values they're given and know nothing about the repository: `build <dir>`, `zip <dir>`, `checksums`, `sign`, `verify`, `version-check`, `deploy-git <dir
|
|
972
|
-
- **The config layer** reads `wp-release.config.js` for everything about the repository: where packages are (`root` and the package globs), how their versions and tags are named (`version`, `tagFormat`), which files they ship (`files`), where they deploy (`deploy`) and the git `remote`. The changeset commands (with a single-project fallback when no config exists), `deploy-git` and `deploy-svn --release-ref`,
|
|
1070
|
+
- **Primitives** work on the directory, file or values they're given and know nothing about the repository: `build <dir>`, `zip <dir>`, `checksums`, `sign`, `verify`, `version-check`, `deploy-git <dir>`, `deploy-svn <dir>`, `i18n-pot <dir>`, `i18n-audit <dir>` and the `i18n-mo`, `i18n-json` and `i18n-php` compilers.
|
|
1071
|
+
- **The config layer** reads `wp-release.config.js` for everything about the repository: where packages are (`root` and the package globs), how their versions and tags are named (`version`, `tagFormat`), which files they ship (`files`), how their translations are extracted (`i18n`), where they deploy (`deploy`) and the git `remote`. The changeset commands (with a single-project fallback when no config exists), `deploy-git` and `deploy-svn --release-ref`, `build --package` and `zip --package`, and `i18n-pot` and `i18n-audit` with `--package` or `--all` belong to it, and the global `--config` and `--tag-format` options select or adjust the config. `zip` and `checksums` also read caller configuration for integrity settings even without `--package`; verification reads it only when explicitly selected. Only `--release-ref` reads which package and version a ref releases. A primitive with a config mode, like `deploy-git --release-ref` beside `deploy-git <dir>` or `zip --package` beside `zip <dir>`, only takes the package directory, its files and its targets from the config there, and otherwise works the same.
|
|
973
1072
|
|
|
974
1073
|
Configured repositories use the layout and naming conventions their config sets. Config-free changeset releases use the root-only detection and defaults described above.
|
|
975
1074
|
|
|
@@ -1004,6 +1103,23 @@ The changeset release modules, from the entry point down:
|
|
|
1004
1103
|
| `file-journal.ts` | `FileJournal`, which restores every file a failed `changeset-version` run changed. |
|
|
1005
1104
|
| `versions.ts` | Version checks and SemVer precedence. |
|
|
1006
1105
|
|
|
1106
|
+
The internationalization modules in `src/i18n/`, from the entry points down:
|
|
1107
|
+
|
|
1108
|
+
| Module | Responsibility |
|
|
1109
|
+
|---|---|
|
|
1110
|
+
| `packages.ts` | `resolveI18nPackages()`, the config adapter that selects a directory, one package or all packages and resolves their options and POT paths. |
|
|
1111
|
+
| `options.ts` | Validating and merging the `i18n` options of the config layers and the CLI. |
|
|
1112
|
+
| `generate.ts` | `extractCatalog()` and `generatePackagePot()`, which read the selected files of a package and write or check its POT. |
|
|
1113
|
+
| `compile.ts` | `compileTranslations()`, which compiles a translated PO into MO, script JSON or `.l10n.php` and writes or checks the outputs. |
|
|
1114
|
+
| `files.ts` | Selecting source files with the include and exclude globs, independently of the distribution file rules. |
|
|
1115
|
+
| `php.ts`, `javascript.ts` and `metadata.ts` | Parsers for PHP, JavaScript and TypeScript, and `block.json`, `theme.json` and plugin and theme headers. |
|
|
1116
|
+
| `extraction.ts` | The translation functions and their arguments, shared by the parsers. |
|
|
1117
|
+
| `catalog.ts` | Catalog types, merging messages of a domain and the diagnostics. |
|
|
1118
|
+
| `audit.ts` and `placeholders.ts` | Audit findings and the sprintf placeholders that they check. |
|
|
1119
|
+
| `pot.ts` | Serializing a catalog as a POT. |
|
|
1120
|
+
| `po.ts`, `translation-json.ts` and `translation-php.ts` | Parsing translated PO files and compiling MO, script JSON and `.l10n.php`. |
|
|
1121
|
+
| `atomic-output.ts` | Validating output paths and replacing output files atomically. |
|
|
1122
|
+
|
|
1007
1123
|
## Comparison with existing tools
|
|
1008
1124
|
|
|
1009
1125
|
| Tool | What it does | How `wp-release` differs |
|
|
@@ -1012,5 +1128,7 @@ The changeset release modules, from the entry point down:
|
|
|
1012
1128
|
| [`grunt-wp-deploy`](https://github.com/stephenharris/grunt-wp-deploy) | Grunt task with `deploy_trunk` and `deploy_tag` booleans, interactive password prompts and a `Stable tag` check. | No Grunt dependency and never interactive. `--readme-only` is a separate flag, where `grunt-wp-deploy` can only update trunk with the full build. Assets are independent of trunk writes. |
|
|
1013
1129
|
| [codeinwp theme deploy](https://github.com/codeinwp/action-wordpress-theme-deploy) | Fork of the 10up plugin action that commits themes to the themes SVN repository. | Doesn't deploy themes. Theme releases are ZIP uploads on WordPress.org; `zip` builds the upload with the theme folder as the root. |
|
|
1014
1130
|
| [`wp dist-archive`](https://github.com/wp-cli/dist-archive-command) | WP-CLI command that zips a project using `.distignore`, with `--plugin-dirname` and `--filename-format`. | Same `.distignore` file and `{name}.{version}` style filename format, but needs only Node.js instead of PHP and WP-CLI. `.distignore` is matched with full gitignore syntax, including `!` negation, and `--distignore` / `--exclude` let you point at another ignore file or add patterns on the command line. Builds are reproducible, and `--checksums` writes a manifest next to the ZIP. |
|
|
1131
|
+
| [`wp i18n make-pot`](https://developer.wordpress.org/cli/commands/i18n/make-pot/) | WP-CLI command that extracts PHP, JavaScript, `block.json` and `theme.json` strings into a POT, with `--domain`, `--include`/`--exclude`, `--headers`, `--file-comment`, `--merge`/`--subtract` and audit warnings. | `i18n-pot` and `i18n-audit` need only Node.js, write byte-for-byte reproducible POTs without dates, and `--check` fails CI on a stale POT. Headers are set one at a time with a repeatable `--header NAME=VALUE` instead of a JSON object, and `--include`/`--exclude` take one glob each, so patterns never need escaping or comma splitting. `--no-references` and `--all-domains` say what they do. Monorepos keep these settings once in `wp-release.config.js` and select packages with `--package` or `--all` instead of running the command for every directory. The audit is a separate read-only command with stable finding codes for `--suppress`, instead of warnings mixed into generation. There's no `--merge`, `--subtract` or `--skip-*`; `--include` and `--exclude` cover the same needs. |
|
|
1132
|
+
| [`wp i18n make-mo`, `make-json` and `make-php`](https://developer.wordpress.org/cli/commands/i18n/) | WP-CLI commands that compile PO files into MO, script JSON and `.l10n.php` files. `make-json` maps sources with `--use-map` and removes JavaScript strings from the PO unless `--no-purge` is passed. | `i18n-mo`, `i18n-json` and `i18n-php` compile one translated PO at a time, never change it, and leave out fuzzy, obsolete and untranslated entries. `i18n-json` takes a many-to-many `--map` from source files to built scripts, and JSX and TypeScript sources need one, so catalogs are never written for files WordPress doesn't load. `--check` compares the outputs without writing. There's no `update-po`, so update PO files from the POT with gettext's `msgmerge` or a translation platform. |
|
|
1015
1133
|
| [`wp plugin verify-checksums`](https://developer.wordpress.org/cli/commands/plugin/verify-checksums/) | WP-CLI command that checks installed plugins against the WordPress.org checksums API. | Reads the same manifest format, but from a file, so it also works for plugins and themes that aren't hosted on WordPress.org and for build directories in CI. `checksums` writes the manifest that WordPress.org generates for its own plugins. |
|
|
1016
1134
|
| [`wp-scripts plugin-zip`](https://github.com/WordPress/gutenberg/tree/trunk/packages/scripts#plugin-zip) | Zips files from `package.json` `files` or a fixed allowlist, named after the npm package, with `--root-folder` / `--no-root-folder`. | Reuses the root folder flags, selects files with `.distignore` by default and can optionally honor Git `export-ignore` attributes, puts the version from the plugin headers in the filename and streams the archive instead of building it in memory. |
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { I18nOptions } from './i18n/options.ts';
|
|
1
2
|
import type { IntegrityConfig } from './integrity.ts';
|
|
2
3
|
import type { Changeset, ChangesetBump, ChangesetPackage } from './changesets.ts';
|
|
3
4
|
export type PackageVersionSource = {
|
|
@@ -59,6 +60,8 @@ export interface PackageVersionConfig {
|
|
|
59
60
|
export type PackageIdSource = 'package.json#name' | 'composer.json#name' | 'path';
|
|
60
61
|
/** One directory pattern that describes packages sharing release behavior. */
|
|
61
62
|
export interface ChangesetPackageGroup {
|
|
63
|
+
/** Extraction settings, independent of release file rules. */
|
|
64
|
+
i18n?: I18nOptions;
|
|
62
65
|
/** Package directories to discover, relative to the configured package root. */
|
|
63
66
|
glob: string;
|
|
64
67
|
/** Package paths excluded from this group, relative to root. */
|
|
@@ -218,6 +221,8 @@ export interface DependentsConfig {
|
|
|
218
221
|
}
|
|
219
222
|
/** JavaScript configuration for changeset-aware releases. */
|
|
220
223
|
export interface ChangesetConfig {
|
|
224
|
+
/** Extraction defaults for configured WordPress packages. */
|
|
225
|
+
i18n?: I18nOptions;
|
|
221
226
|
/** Default checksum format, placement and manifest signing for ZIPs. */
|
|
222
227
|
integrity?: IntegrityConfig;
|
|
223
228
|
/** Branch receiving ordinary changes. */
|
package/dist/changeset-config.js
CHANGED
|
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { validateI18nOptions } from "./i18n/options.js";
|
|
9
10
|
import { access } from 'node:fs/promises';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import { pathToFileURL } from 'node:url';
|
|
@@ -171,6 +172,7 @@ function validateChangesetConfig(config, configFile) {
|
|
|
171
172
|
throw new ValidationError(`wp-release config ${configFile} must default-export a configuration object.`);
|
|
172
173
|
}
|
|
173
174
|
const candidate = config;
|
|
175
|
+
validateI18nOptions(candidate.i18n, `${configFile}.i18n`);
|
|
174
176
|
if (!Array.isArray(candidate.packages)) {
|
|
175
177
|
throw new ValidationError(`wp-release config ${configFile} must define a packages array.`);
|
|
176
178
|
}
|
|
@@ -267,6 +269,7 @@ function validateChangesetConfig(config, configFile) {
|
|
|
267
269
|
!Array.isArray(group.version.to)))) {
|
|
268
270
|
throw new ValidationError(`wp-release config ${configFile} has an invalid packages[${index}] entry.`);
|
|
269
271
|
}
|
|
272
|
+
validateI18nOptions(group.i18n, `${configFile}.packages[${index}].i18n`);
|
|
270
273
|
if (undefined !== group.idFrom &&
|
|
271
274
|
!['path', 'package.json#name', 'composer.json#name'].includes(group.idFrom)) {
|
|
272
275
|
throw new ValidationError(`wp-release config ${configFile} packages[${index}].idFrom is not supported.`);
|
package/dist/cli.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export declare function formatGitHubOutputs(result: unknown): string;
|
|
|
59
59
|
* Results of the commands that can be run with execute(), by command name.
|
|
60
60
|
*/
|
|
61
61
|
export interface CommandResults {
|
|
62
|
+
'i18n-mo': import('./i18n/compile.ts').CompileTranslationsResult;
|
|
63
|
+
'i18n-json': import('./i18n/compile.ts').CompileTranslationsResult;
|
|
64
|
+
'i18n-php': import('./i18n/compile.ts').CompileTranslationsResult;
|
|
65
|
+
'i18n-pot': import('./commands/i18n.ts').I18nCommandResult;
|
|
66
|
+
'i18n-audit': import('./commands/i18n.ts').I18nCommandResult;
|
|
62
67
|
build: import('./commands/build.ts').BuildResult;
|
|
63
68
|
'deploy-svn': import('./commands/deploy-svn.ts').DeploySvnResult;
|
|
64
69
|
zip: ZipResult;
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { appendFileSync } from 'node:fs';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
|
+
import i18nMo from "./commands/i18n-mo.js";
|
|
5
|
+
import i18nJson from "./commands/i18n-json.js";
|
|
6
|
+
import i18nPhp from "./commands/i18n-php.js";
|
|
7
|
+
import i18nPot from "./commands/i18n-pot.js";
|
|
8
|
+
import i18nAudit from "./commands/i18n-audit.js";
|
|
4
9
|
import zip from "./commands/zip.js";
|
|
5
10
|
import build from "./commands/build.js";
|
|
6
11
|
import checksums from "./commands/checksums.js";
|
|
@@ -23,6 +28,11 @@ export const EXIT_SUCCESS = 0;
|
|
|
23
28
|
export const EXIT_FAILURE = 1;
|
|
24
29
|
export const EXIT_INVALID = 2;
|
|
25
30
|
export const commands = [
|
|
31
|
+
i18nMo,
|
|
32
|
+
i18nJson,
|
|
33
|
+
i18nPhp,
|
|
34
|
+
i18nPot,
|
|
35
|
+
i18nAudit,
|
|
26
36
|
build,
|
|
27
37
|
zip,
|
|
28
38
|
checksums,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { compileTranslations } from "../i18n/compile.js";
|
|
3
|
+
import { ValidationError } from "../errors.js";
|
|
4
|
+
import { readJson } from "../json-files.js";
|
|
5
|
+
import { getBoolean, getString } from "./command.js";
|
|
6
|
+
/**
|
|
7
|
+
* Flat compiler adapters: translated PO input, independent of package/source discovery.
|
|
8
|
+
* @param format
|
|
9
|
+
*/
|
|
10
|
+
export function createI18nCompileCommand(format) {
|
|
11
|
+
return {
|
|
12
|
+
name: `i18n-${format}`,
|
|
13
|
+
usage: `i18n-${format} <file.po> [options]`,
|
|
14
|
+
summary: `Compile translated PO into ${'php' === format ? 'WordPress .l10n.php' : format.toUpperCase()} files.`,
|
|
15
|
+
help: `Options:
|
|
16
|
+
--output <path> Output file (${format === 'json' ? 'directory for JSON' : 'defaults beside the PO'})
|
|
17
|
+
--check Compare generated bytes without writing
|
|
18
|
+
${'json' === format
|
|
19
|
+
? ' --domain <domain> Required text domain\n --map <file.json> Source paths to runtime script path arrays (many-to-many)'
|
|
20
|
+
: ''}
|
|
21
|
+
|
|
22
|
+
Requires Language and Plural-Forms headers. Fuzzy, obsolete and untranslated
|
|
23
|
+
entries are excluded. Input PO files are never changed. No PHP/WP-CLI runtime
|
|
24
|
+
is needed; compilation validates data before independent atomic output writes.`,
|
|
25
|
+
options: {
|
|
26
|
+
output: { type: 'string' },
|
|
27
|
+
check: { type: 'boolean' },
|
|
28
|
+
...('json' === format
|
|
29
|
+
? {
|
|
30
|
+
domain: { type: 'string' },
|
|
31
|
+
map: { type: 'string' },
|
|
32
|
+
}
|
|
33
|
+
: {}),
|
|
34
|
+
},
|
|
35
|
+
async run({ values, positionals, cwd, logger }) {
|
|
36
|
+
if (1 !== positionals.length || !positionals[0]?.trim()) {
|
|
37
|
+
throw new ValidationError(`i18n-${format} requires exactly one input PO file.`);
|
|
38
|
+
}
|
|
39
|
+
const output = getString(values, 'output');
|
|
40
|
+
const map = getString(values, 'map');
|
|
41
|
+
if ('' === output?.trim() || '' === map?.trim()) {
|
|
42
|
+
throw new ValidationError('--output and --map paths must not be empty.');
|
|
43
|
+
}
|
|
44
|
+
let scriptMap;
|
|
45
|
+
if (map) {
|
|
46
|
+
const parsed = await readJson(resolve(cwd, map));
|
|
47
|
+
if (!parsed ||
|
|
48
|
+
'object' !== typeof parsed ||
|
|
49
|
+
Array.isArray(parsed)) {
|
|
50
|
+
throw new ValidationError('The script map must be an object of source paths to runtime path arrays.');
|
|
51
|
+
}
|
|
52
|
+
scriptMap = parsed;
|
|
53
|
+
}
|
|
54
|
+
const result = await compileTranslations({
|
|
55
|
+
sourcePath: resolve(cwd, positionals[0]),
|
|
56
|
+
format,
|
|
57
|
+
...(output ? { outputPath: resolve(cwd, output) } : {}),
|
|
58
|
+
domain: getString(values, 'domain'),
|
|
59
|
+
scriptMap,
|
|
60
|
+
check: getBoolean(values, 'check'),
|
|
61
|
+
});
|
|
62
|
+
const stale = result.outputs.filter((outputFile) => !outputFile.current);
|
|
63
|
+
for (const outputFile of result.outputs) {
|
|
64
|
+
logger.info(outputFile.outputPath);
|
|
65
|
+
}
|
|
66
|
+
for (const outputFile of stale) {
|
|
67
|
+
logger.error('Translation output is missing or stale.', {
|
|
68
|
+
file: outputFile.outputPath,
|
|
69
|
+
title: 'stale-translation',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
logger.summary(() => `### wp-release i18n-${format}\n\n${result.messages} translated messages for ${result.locale}; ${result.outputs.length} outputs.\n`);
|
|
73
|
+
return { exitCode: stale.length ? 2 : 0, result };
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PackagePotResult } from '../i18n/generate.ts';
|
|
2
|
+
import type { Command } from './command.ts';
|
|
3
|
+
export interface I18nCommandResult {
|
|
4
|
+
packages: Array<PackagePotResult & {
|
|
5
|
+
package?: string;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Small command adapters share selection/reporting; domain logic stays in the library.
|
|
10
|
+
* @param operation
|
|
11
|
+
*/
|
|
12
|
+
export declare function createI18nCommand(operation: 'pot' | 'audit'): Command;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { hasFatalDiagnostics } from "../i18n/catalog.js";
|
|
3
|
+
import { extractCatalog, generatePackagePot } from "../i18n/generate.js";
|
|
4
|
+
import { auditCatalog } from "../i18n/audit.js";
|
|
5
|
+
import { resolveI18nPackages } from "../i18n/packages.js";
|
|
6
|
+
import { ValidationError } from "../errors.js";
|
|
7
|
+
import { getBoolean, getString, getStrings } from "./command.js";
|
|
8
|
+
function cliOptions(values) {
|
|
9
|
+
const headers = {};
|
|
10
|
+
for (const header of getStrings(values, 'header')) {
|
|
11
|
+
const index = header.indexOf('=');
|
|
12
|
+
if (0 === index || !header) {
|
|
13
|
+
throw new ValidationError('--header requires NAME=VALUE, with an empty value allowed, or NAME to remove the header.');
|
|
14
|
+
}
|
|
15
|
+
if (-1 === index) {
|
|
16
|
+
headers[header] = false;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
headers[header.slice(0, index)] = header.slice(index + 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const domain = getString(values, 'domain');
|
|
23
|
+
const destination = getString(values, 'destination');
|
|
24
|
+
const fileComment = getString(values, 'file-comment');
|
|
25
|
+
return {
|
|
26
|
+
...(undefined !== domain ? { domain } : {}),
|
|
27
|
+
...(undefined !== destination ? { destination } : {}),
|
|
28
|
+
...(undefined !== fileComment ? { fileComment } : {}),
|
|
29
|
+
...(values.include
|
|
30
|
+
? { include: getStrings(values, 'include') }
|
|
31
|
+
: {}),
|
|
32
|
+
...(values.exclude
|
|
33
|
+
? { exclude: getStrings(values, 'exclude') }
|
|
34
|
+
: {}),
|
|
35
|
+
...(values.suppress
|
|
36
|
+
? { suppress: getStrings(values, 'suppress') }
|
|
37
|
+
: {}),
|
|
38
|
+
...(getBoolean(values, 'strict') ? { strict: true } : {}),
|
|
39
|
+
...(getBoolean(values, 'all-domains') ? { allDomains: true } : {}),
|
|
40
|
+
...(getBoolean(values, 'no-references')
|
|
41
|
+
? { references: false }
|
|
42
|
+
: {}),
|
|
43
|
+
...(getBoolean(values, 'no-legacy-globals')
|
|
44
|
+
? { legacyGlobals: false }
|
|
45
|
+
: {}),
|
|
46
|
+
...(Object.keys(headers).length ? { headers } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function report(context, diagnostics, sourceDir) {
|
|
50
|
+
for (const diagnostic of diagnostics) {
|
|
51
|
+
context.logger[diagnostic.severity](diagnostic.message, {
|
|
52
|
+
file: resolve(sourceDir, diagnostic.file),
|
|
53
|
+
line: diagnostic.line,
|
|
54
|
+
title: diagnostic.code,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Small command adapters share selection/reporting; domain logic stays in the library.
|
|
60
|
+
* @param operation
|
|
61
|
+
*/
|
|
62
|
+
export function createI18nCommand(operation) {
|
|
63
|
+
return {
|
|
64
|
+
name: `i18n-${operation}`,
|
|
65
|
+
usage: `i18n-${operation} [<source-dir>] [options]`,
|
|
66
|
+
summary: 'pot' === operation
|
|
67
|
+
? 'Extract WordPress source strings into a reproducible POT.'
|
|
68
|
+
: 'Audit WordPress source strings without writing files.',
|
|
69
|
+
help: `Options:
|
|
70
|
+
--package <name> Configured package (extraction overrides allowed)
|
|
71
|
+
--all All enabled configured WordPress packages
|
|
72
|
+
--source-dir <dir> Plugin/theme directory, or positional argument (default .)
|
|
73
|
+
--domain <domain> Select one domain (default Text Domain, then slug)
|
|
74
|
+
--all-domains Explicitly combine domains
|
|
75
|
+
--include <glob> Repeatable source globs, replacing inherited includes
|
|
76
|
+
--exclude <glob> Repeatable exclusion globs, replacing inherited excludes
|
|
77
|
+
--destination <dir> Package-relative POT directory
|
|
78
|
+
--header <NAME=VALUE> Repeatable gettext header override; NAME alone removes it
|
|
79
|
+
${'pot' === operation
|
|
80
|
+
? ' --file-comment <text> Static comment prepended to the POT\n'
|
|
81
|
+
: ''}
|
|
82
|
+
--no-references Omit source references
|
|
83
|
+
--no-legacy-globals JS requires imports or wp.i18n calls
|
|
84
|
+
--strict Warnings fail with exit code 2
|
|
85
|
+
${'pot' === operation
|
|
86
|
+
? ' --output <file.pot> Explicit output relative to cwd; one package only\n --check Compare bytes without writing'
|
|
87
|
+
: ' --suppress <code> Repeatable audit finding codes to suppress'}
|
|
88
|
+
|
|
89
|
+
No PHP, WordPress, WP-CLI or network needed. Distribution file rules do not apply.
|
|
90
|
+
Each package writes atomically; a multi-package run is not a transaction.`,
|
|
91
|
+
options: {
|
|
92
|
+
package: { type: 'string' },
|
|
93
|
+
all: { type: 'boolean' },
|
|
94
|
+
'source-dir': { type: 'string' },
|
|
95
|
+
domain: { type: 'string' },
|
|
96
|
+
'all-domains': { type: 'boolean' },
|
|
97
|
+
include: { type: 'string', multiple: true },
|
|
98
|
+
exclude: { type: 'string', multiple: true },
|
|
99
|
+
destination: { type: 'string' },
|
|
100
|
+
header: { type: 'string', multiple: true },
|
|
101
|
+
'no-references': { type: 'boolean' },
|
|
102
|
+
'no-legacy-globals': { type: 'boolean' },
|
|
103
|
+
strict: { type: 'boolean' },
|
|
104
|
+
...('pot' === operation
|
|
105
|
+
? {
|
|
106
|
+
'file-comment': { type: 'string' },
|
|
107
|
+
output: { type: 'string' },
|
|
108
|
+
check: { type: 'boolean' },
|
|
109
|
+
}
|
|
110
|
+
: { suppress: { type: 'string', multiple: true } }),
|
|
111
|
+
},
|
|
112
|
+
async run(context) {
|
|
113
|
+
const { values, positionals, cwd, configFile, logger } = context;
|
|
114
|
+
if (positionals.length > 1 ||
|
|
115
|
+
(positionals.length && undefined !== values['source-dir'])) {
|
|
116
|
+
throw new ValidationError('Pass only one source directory.');
|
|
117
|
+
}
|
|
118
|
+
const source = getString(values, 'source-dir');
|
|
119
|
+
if (undefined !== source && !source.trim()) {
|
|
120
|
+
throw new ValidationError('--source-dir must not be empty.');
|
|
121
|
+
}
|
|
122
|
+
const output = getString(values, 'output');
|
|
123
|
+
if (undefined !== output && !output.trim()) {
|
|
124
|
+
throw new ValidationError('--output must not be empty.');
|
|
125
|
+
}
|
|
126
|
+
const selected = await resolveI18nPackages({
|
|
127
|
+
cwd,
|
|
128
|
+
configFile,
|
|
129
|
+
package: getString(values, 'package'),
|
|
130
|
+
all: getBoolean(values, 'all'),
|
|
131
|
+
sourceDir: getString(values, 'source-dir') ?? positionals[0],
|
|
132
|
+
options: cliOptions(values),
|
|
133
|
+
validateOutputs: 'pot' === operation,
|
|
134
|
+
...(output ? { outputPath: resolve(cwd, output) } : {}),
|
|
135
|
+
});
|
|
136
|
+
const result = { packages: [] };
|
|
137
|
+
let failed = false;
|
|
138
|
+
for (const pkg of selected) {
|
|
139
|
+
let generated;
|
|
140
|
+
if ('pot' === operation) {
|
|
141
|
+
generated = await generatePackagePot({
|
|
142
|
+
...pkg,
|
|
143
|
+
check: getBoolean(values, 'check'),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const extracted = await extractCatalog(pkg);
|
|
148
|
+
generated = {
|
|
149
|
+
outputPath: pkg.outputPath,
|
|
150
|
+
messages: extracted.catalog.messages.length,
|
|
151
|
+
files: extracted.files,
|
|
152
|
+
diagnostics: [
|
|
153
|
+
...extracted.diagnostics,
|
|
154
|
+
...auditCatalog(extracted.catalog, pkg.options.suppress),
|
|
155
|
+
],
|
|
156
|
+
written: false,
|
|
157
|
+
current: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
result.packages.push({
|
|
161
|
+
...generated,
|
|
162
|
+
...(pkg.package ? { package: pkg.package } : {}),
|
|
163
|
+
});
|
|
164
|
+
failed ||= hasFatalDiagnostics(generated.diagnostics, pkg.options.strict);
|
|
165
|
+
report(context, generated.diagnostics, pkg.sourceDir);
|
|
166
|
+
logger.info(`${pkg.package ?? pkg.sourceDir}: ${generated.messages} messages, ${generated.files} files.`);
|
|
167
|
+
}
|
|
168
|
+
logger.summary(() => `### wp-release i18n-${operation}\n\n${result.packages
|
|
169
|
+
.map((pkg) => `- ${pkg.package ?? pkg.outputPath}: ${pkg.messages} messages, ${pkg.diagnostics.length} diagnostics.`)
|
|
170
|
+
.join('\n')}\n`);
|
|
171
|
+
return { exitCode: failed ? 2 : 0, result };
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|