@wpelevator/wp-release 0.15.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 +366 -39
- package/dist/changeset-config.d.ts +8 -0
- package/dist/changeset-config.js +3 -0
- package/dist/changeset-release.d.ts +9 -3
- package/dist/changeset-release.js +23 -14
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +12 -0
- package/dist/commands/artifact-signing.d.ts +20 -0
- package/dist/commands/artifact-signing.js +42 -0
- package/dist/commands/build.d.ts +32 -0
- package/dist/commands/build.js +91 -0
- package/dist/commands/changeset-add.d.ts +55 -6
- package/dist/commands/changeset-add.js +12 -3
- package/dist/commands/changeset-args.js +2 -2
- package/dist/commands/changeset-project-args.d.ts +63 -0
- package/dist/commands/changeset-project-args.js +114 -0
- package/dist/commands/changeset-status.d.ts +50 -1
- package/dist/commands/changeset-status.js +4 -2
- package/dist/commands/changeset-tag.d.ts +55 -6
- package/dist/commands/changeset-tag.js +7 -4
- package/dist/commands/changeset-version.d.ts +55 -6
- package/dist/commands/changeset-version.js +4 -2
- package/dist/commands/checksums.d.ts +4 -1
- package/dist/commands/checksums.js +27 -6
- 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/commands/package-source.d.ts +43 -0
- package/dist/commands/package-source.js +101 -0
- package/dist/commands/verify.d.ts +7 -1
- package/dist/commands/verify.js +53 -17
- package/dist/commands/zip.d.ts +20 -8
- package/dist/commands/zip.js +109 -124
- 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 +28 -0
- package/dist/index.js +16 -0
- package/dist/integrity.d.ts +33 -0
- package/dist/integrity.js +115 -0
- package/dist/output-files.d.ts +7 -0
- package/dist/output-files.js +25 -0
- package/dist/package-build.d.ts +33 -0
- package/dist/package-build.js +112 -0
- package/dist/package-files.d.ts +25 -0
- package/dist/package-files.js +24 -0
- package/dist/package-zip.d.ts +14 -2
- package/dist/package-zip.js +129 -38
- package/dist/release-tags.js +6 -0
- package/dist/single-project.d.ts +32 -0
- package/dist/single-project.js +144 -0
- package/dist/summary.d.ts +9 -0
- package/dist/summary.js +25 -0
- package/dist/zip.d.ts +3 -0
- package/dist/zip.js +16 -6
- package/package.json +20 -3
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# WP Release
|
|
2
2
|
|
|
3
|
-
Package, checksum, 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
|
-
|
|
8
|
-
|
|
9
|
-
Option names 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. See [Comparison with existing tools](#comparison-with-existing-tools) for the differences.
|
|
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.
|
|
10
8
|
|
|
11
9
|
## Install
|
|
12
10
|
|
|
@@ -14,7 +12,7 @@ Option names follow the existing community tools (10up WordPress.org actions, `w
|
|
|
14
12
|
npm install --save-dev @wpelevator/wp-release
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client
|
|
15
|
+
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client. Git deployments, changeset commands and the optional `--exclude-gitattributes` flag need Git.
|
|
18
16
|
|
|
19
17
|
The package is an ES module with TypeScript type definitions. CommonJS projects can load it with `require( '@wpelevator/wp-release' )`, which Node.js supports for ES modules without top-level `await` since 22.12.
|
|
20
18
|
|
|
@@ -26,6 +24,7 @@ npx wp-release --help
|
|
|
26
24
|
|
|
27
25
|
| Command | Description |
|
|
28
26
|
|---|---|
|
|
27
|
+
| `wp-release build [<source-dir>] --output <dir> [options]` | Copy selected release files into a directory. |
|
|
29
28
|
| `wp-release zip [<source-dir>] [options]` | Create a ZIP archive with a `{slug}/` root folder, respecting `.distignore`. |
|
|
30
29
|
| `wp-release checksums [<source-dir>] [options]` | Write the MD5 and SHA-256 hashes of every distributed file to a checksums manifest. |
|
|
31
30
|
| `wp-release sign <file>... [options]` | Sign files with Ed25519, writing a detached `<file>.sig` next to each, or minisign and SSH signatures with `--format`. |
|
|
@@ -36,12 +35,21 @@ npx wp-release --help
|
|
|
36
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. |
|
|
37
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. |
|
|
38
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. |
|
|
39
43
|
| `wp-release changeset-add [options]` | Record the release impact of a pull request. |
|
|
40
44
|
| `wp-release changeset-status [options]` | List pending releases and check changed packages for changesets. |
|
|
41
45
|
| `wp-release changeset-version [options]` | Apply version bumps and changelog entries. |
|
|
42
46
|
| `wp-release changeset-tag [options]` | Create missing package release tags. |
|
|
43
47
|
|
|
44
|
-
Run `npx wp-release <command> --help` for the options of each command.
|
|
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`.
|
|
49
|
+
|
|
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.
|
|
51
|
+
|
|
52
|
+
Prefer plain outputs in steps that run conditionally, since a skipped step has empty outputs. When reading JSON from a step that may be skipped, use a fallback such as `fromJSON( steps.tag.outputs.result || '{"tags":[]}' ).tags[0].tag`. For several tags or deploys, read the arrays in `result` to build a job matrix or process each item.
|
|
45
53
|
|
|
46
54
|
### GitHub Actions annotations and job summaries
|
|
47
55
|
|
|
@@ -65,6 +73,7 @@ Each command also adds a section to the [job summary](https://docs.github.com/en
|
|
|
65
73
|
|---|---|
|
|
66
74
|
| `deploy-svn` | Plugin, linked to its WordPress.org page, version, written SVN paths, the commit linked to its WordPress.org changeset (or the dry run or no-op result), and the SVN changes in a collapsed table. |
|
|
67
75
|
| `deploy-git` | Repository, release commit and the pushed branch and tag. |
|
|
76
|
+
| `build` | Output directory, number of copied files and optional manifest; requested signatures and public keys. |
|
|
68
77
|
| `zip` | ZIP, number of files and checksums manifest; with `--sign`, signature files and the public key of each written format. |
|
|
69
78
|
| `checksums` | Manifest and number of files. |
|
|
70
79
|
| `sign` | Signature files and the public key of each written format. |
|
|
@@ -76,11 +85,63 @@ Each command also adds a section to the [job summary](https://docs.github.com/en
|
|
|
76
85
|
|
|
77
86
|
A failed command adds its error to the summary, or a table of its problems with their files and lines, listing up to 100 and counting the rest. The Markdown of the summaries isn't part of the library API. File paths are relative to `GITHUB_WORKSPACE`, or the current directory without it. Files outside it, like those of an extracted ZIP, get an annotation without a file. Multi-line messages stay one annotation.
|
|
78
87
|
|
|
79
|
-
Use global `--config <path>` to select a config; relative paths resolve from the current directory. Otherwise,
|
|
88
|
+
Use global `--config <path>` to select a config; relative paths resolve from the current directory. Otherwise, commands using the config discover `wp-release.config.js`, `.mjs` or `.cjs` at the repository root. Global `--tag-format <format>` replaces the `tagFormat` of the config for one command, see [Packages and tags](#packages-and-tags).
|
|
89
|
+
|
|
90
|
+
### Changesets support
|
|
91
|
+
|
|
92
|
+
The changeset commands manage versioning, changelogs and release tags for single-package repositories and monorepos, including this repository. They read Changesets-style Markdown and load JavaScript configs with descriptors for npm and Composer manifests, plugin and theme headers, readme Stable tags, PHP constants, and Git tags. Use JavaScript configs with ESM (`export default`) or CommonJS (`module.exports`). Frontmatter uses `package-name: patch|minor|major` entries. For pre-releases, use the built-in [pre-release workflow](#pre-releases); the parser supports this simple frontmatter format rather than general YAML or the Changesets pre mode.
|
|
80
93
|
|
|
81
|
-
|
|
94
|
+
#### Single-project repositories without a config file
|
|
95
|
+
|
|
96
|
+
The `changeset-*` commands can release one project at the repository root without a `wp-release.config.*` file. Run them from the root directory. With no version descriptor flags, they detect a root `package.json` or `composer.json` with a version, a plugin entry PHP file with a `Plugin Name` header, or a theme `style.css` with a `Theme Name` header. A detected plugin also updates the Stable tag in `readme.txt`, or `readme.md` when no Stable tag is found in `readme.txt`. Multiple version sources, multiple plugin entry files and npm workspaces require explicit descriptors or a config; files belonging to different projects aren't silently synchronized. A directory without a recognized version source still requires a config or explicit descriptors.
|
|
97
|
+
|
|
98
|
+
For a root npm package, the complete workflow needs no project flags:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx wp-release changeset-add --bump minor --summary "Add a new feature."
|
|
102
|
+
npx wp-release changeset-status --since origin/main
|
|
103
|
+
npx wp-release changeset-version --dry-run
|
|
104
|
+
npx wp-release changeset-version
|
|
105
|
+
# Review and commit the updated version and changelog files.
|
|
106
|
+
npx wp-release changeset-tag --push
|
|
107
|
+
```
|
|
82
108
|
|
|
83
|
-
|
|
109
|
+
A single root package is selected automatically for `changeset-add --bump` when `--since` isn't supplied, and for `changeset-tag --version`; `--since` still selects changed packages. npm and Composer projects use their root manifest's name as the changeset identifier when that manifest supplies the version; other projects use the root directory's basename. Keep that directory name consistent between local checkouts and CI, or use a config with a manifest-based `idFrom`. The fallback uses `.changeset/`, `CHANGELOG.md`, tags named `v{version}`, and the `origin` remote. There is no default comparison branch: pass `--since` or `--base-branch` when status checks should compare changes. Readme changelog updates are opt-in.
|
|
110
|
+
|
|
111
|
+
Explicit descriptors replace automatic detection and define the entire version target list. For a plugin whose version appears in its header, readme and a PHP constant:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx wp-release changeset-version \
|
|
115
|
+
--version-from wordpress-plugin \
|
|
116
|
+
--version-wordpress-plugin my-plugin.php \
|
|
117
|
+
--version-wordpress-stable-tag readme.txt \
|
|
118
|
+
--version-php-constant 'my-plugin.php#MY_PLUGIN_VERSION'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The same project flags belong on `changeset-add`, `changeset-status`, `changeset-version` and `changeset-tag`. Keep them consistent across all four commands, for example in command-specific npm scripts. An accompanying `package.json` used for build tools isn't changed unless it is included explicitly.
|
|
122
|
+
|
|
123
|
+
| Flag | Version descriptor |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `--version-package-json <file>` | npm manifest version source and target |
|
|
126
|
+
| `--version-composer-json <file>` | Composer manifest version source and target; a target without a `version` field is skipped, while a source must have one |
|
|
127
|
+
| `--version-wordpress-plugin <file>` | WordPress plugin Version header source and target |
|
|
128
|
+
| `--version-wordpress-theme <file>` | WordPress theme Version header source and target |
|
|
129
|
+
| `--version-wordpress-stable-tag <file>` | WordPress readme Stable tag source and target; keeps its stable version during pre-releases and snapshots |
|
|
130
|
+
| `--version-php-constant <file>#<constant>` | PHP `define()` or `const` version source and target |
|
|
131
|
+
| `--version-git-tag <pattern>` | Current version from existing stable Git tags, with a pattern such as `v{version}`; source-only, never a version target |
|
|
132
|
+
|
|
133
|
+
File descriptor flags are repeatable. The source is inferred from a sole descriptor, or a unique descriptor other than a Stable tag. Otherwise use `--version-from <kind>`, where kind is `package-json`, `composer-json`, `wordpress-plugin`, `wordpress-theme`, `wordpress-stable-tag`, `php-constant` or `git-tag`. For repeated kinds, qualify the selector with the exact descriptor value: `--version-from package-json:package.json` or `--version-from 'php-constant:my-plugin.php#MY_PLUGIN_VERSION'`. Argument order doesn't select the source. A Git source requires a matching existing stable tag; its pattern selects the tags to read, while `--tag-format` controls tags to create. With multiple descriptors, explicitly select `git-tag` if it is the authoritative source.
|
|
134
|
+
|
|
135
|
+
| Shared option | Config equivalent |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `--base-branch <branch>` | `baseBranch`, used with the remote for the default comparison ref |
|
|
138
|
+
| `--remote <name>` | `remote`, used for comparisons and pushing release tags |
|
|
139
|
+
| `--changesets-dir <dir>` | `changesets`, relative to the repository root |
|
|
140
|
+
| `--readme-changelog` / `--no-readme-changelog` | `readmeChangelog`, independent of updating the readme Stable tag |
|
|
141
|
+
| `--changelog-insert-after <marker>` | `changelogInsertAfter`; must exist in `CHANGELOG.md` and, with readme changelog updates, the plugin readme |
|
|
142
|
+
| `--tag-format <format>` | `tagFormat`, replacing the default `v{version}` |
|
|
143
|
+
|
|
144
|
+
An explicit `--config` must load successfully. Otherwise a discovered config takes precedence over the single-project fallback. Single-project descriptor and settings flags cannot be mixed with a config file; put those settings in the config instead. The existing global `--tag-format` override works in both modes. Config files remain the place for package layouts, dependents, custom version and changelog callbacks, deployment destinations and distribution settings. Packaging and deployment commands retain their existing config requirements.
|
|
84
145
|
|
|
85
146
|
Typical release flow, from a feature pull request to the publish workflows triggered by release tags:
|
|
86
147
|
|
|
@@ -127,6 +188,23 @@ export default defineConfig( {
|
|
|
127
188
|
|
|
128
189
|
`version.from` selects the current version; `version.to` lists release targets. Use `phpConstantVersion( { file, constant } )` to sync a PHP constant.
|
|
129
190
|
|
|
191
|
+
#### Single-project checks in GitHub Actions
|
|
192
|
+
|
|
193
|
+
There are no dedicated build or changeset wrapper actions. Install the CLI in your project and run it directly. A root npm package can check a pull request with:
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
- uses: actions/checkout@v7
|
|
197
|
+
with:
|
|
198
|
+
fetch-depth: 0
|
|
199
|
+
- uses: actions/setup-node@v6
|
|
200
|
+
with:
|
|
201
|
+
node-version: 24
|
|
202
|
+
- run: npm ci
|
|
203
|
+
- run: npx --no-install wp-release changeset-status --since origin/main
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
For a plugin with several version targets, put the same descriptor flags in npm scripts for all four changeset commands. Full Git history and tags are needed for comparisons, Git version sources and release notes. Keep the checkout directory name consistent when it supplies the changeset identifier. Directory artifacts use `npx --no-install wp-release build --output dist/my-plugin --github-output`; upload that directory with your artifact action. `build` writes manifest sidecars outside the directory and does not accept the ZIP-only embedding, codec or manifest-filename flags.
|
|
207
|
+
|
|
130
208
|
#### Packages and tags
|
|
131
209
|
|
|
132
210
|
The config describes the repository, so wp-release assumes no directory structure or naming convention of its own:
|
|
@@ -143,7 +221,7 @@ The config describes the repository, so wp-release assumes no directory structur
|
|
|
143
221
|
Deploy templates also have `{branch}`, `{ref}` and `{revision}` (see [Deploy configured packages](#deploy-configured-packages)), and any template can be a callback that receives the package, for names that the placeholders don't cover, like the last directory of the path.
|
|
144
222
|
- **`tagFormat`** names the release tags, `{path}/{version}` by default, like `plugins/alpha/1.2.0`. Empty placeholders leave no leading or repeated slashes in a tag, so a package at the root gets plain version tags, like `1.2.0`, from the same default. Deploy templates, like repository URLs, are filled in as they are. A `gitTagVersion()` version source reads the tags with its own `pattern`, also `{path}/{version}` by default, so set both when you change the format.
|
|
145
223
|
- **`--tag-format <format>`** replaces the `tagFormat` of the config for one command, for a workflow or job that names its tags differently, like `--tag-format '{name}@{version}'`. Every command that creates or reads the same tags needs the same format: `changeset-tag`, which creates them and finds the previous tag for the release notes, `changeset-version --prerelease`, which numbers pre-releases from them, and `deploy-git` and `deploy-svn --release-ref`, which read the package and version of a tag.
|
|
146
|
-
- **Slugs** are a WordPress concept: the folder of a plugin or theme. Only the commands that produce something for WordPress use one: the `{slug}/` root folder and name of a `zip`, the `checksums` manifest, the WordPress.org slug of `deploy-svn`, and `version-check`. Each takes `--slug`, which defaults to the name of the
|
|
224
|
+
- **Slugs** are a WordPress concept: the folder of a plugin or theme. Only the commands that produce something for WordPress use one: the `{slug}/` root folder and name of a `zip`, the `checksums` manifest, the WordPress.org slug of `deploy-svn`, and `version-check`. Each takes `--slug`, which defaults to the name of the working directory, or of the package directory with `zip --package`, and an SVN deploy target takes `slug`, which defaults to the name of the package directory. Tags and Git deploy targets don't have a slug.
|
|
147
225
|
- **`remote`** is the git remote that `changeset-tag --push` pushes tags to and that the base branch is compared on, `origin` by default.
|
|
148
226
|
|
|
149
227
|
The default strategy bumps stable `x.y.z` versions with SemVer rules, and the `x.y` and `x.y.z.w` versions common in WordPress plugins the same way: the bumped part is raised, the parts after it reset to `0`, and the version keeps its number of parts. A minor bump of `2.1` is `2.2` and a major bump is `3.0`, while a patch bump adds the third part, `2.1.1`. A patch bump of `1.2.3.4` is `1.2.4.0`, so raising only the fourth part needs a custom strategy. Configure `nextVersion( { package, bump, currentVersion, changesets } )` for other formats; changeset bumps remain `patch`, `minor` or `major`. Pre-releases are added to the next version with `--prerelease`, described below.
|
|
@@ -215,7 +293,7 @@ The `changeset-version` result lists each release with its `package`, `bump`, `c
|
|
|
215
293
|
|
|
216
294
|
After merging the release PR, `changeset-tag --since <merge-commit>^1 --push` pushes tags only for packages changed by that release. It pushes one tag at a time because GitHub doesn't trigger workflows for a push of more than three tags. wp-release doesn't create GitHub Releases: with `--json` or `--github-output`, each entry of `tags` has the `tag`, a `title` like `plugins/alpha@1.2.0`, the release `notes` and a `prerelease` flag, for a workflow step like [`softprops/action-gh-release`](https://github.com/softprops/action-gh-release) to create the release (see the example below). For initial adoption, preview all missing tags with `changeset-tag --dry-run` (without `--since`), then push if needed. To release each package with a separate workflow instead, `changeset-tag --since <merge-commit>^1 --untagged --github-output` lists the releases that don't have a tag yet, without creating any. Tag pushes need Git write credentials.
|
|
217
295
|
|
|
218
|
-
To release a single package, for example from a manual workflow, pass `--package <name>`, repeatable, and with one package `--version <version>` to fail unless the package is at that version. Packages versioned by git tags have no version file, so for them `--version` is the version to tag. Before a tag is created, every `version.to` target must match the version, so a plugin `Version` header that disagrees with its readme `Stable tag` blocks the tag. Versions with a pre-release part, like `1.0.0-rc.1`, have `prerelease` set, so their GitHub Releases can be marked as pre-releases:
|
|
296
|
+
To release a single package, for example from a manual workflow, pass `--package <name>`, repeatable, and with one package `--version <version>` to fail unless the package is at that version. Packages versioned by git tags have no version file, so for them `--version` is the version to tag. Before a tag is created, every `version.to` target must match the version, so a plugin `Version` header that disagrees with its readme `Stable tag` blocks the tag. Versions with a pre-release part, like `1.0.0-rc.1`, have `prerelease` set, so their GitHub Releases can be marked as pre-releases. This example selects one package, so `tags[0]` is its release; these fields are nested in the JSON result:
|
|
219
297
|
|
|
220
298
|
```yaml
|
|
221
299
|
- name: Tag a release
|
|
@@ -268,6 +346,7 @@ npm publish --workspace packages/library --tag pr-251
|
|
|
268
346
|
|
|
269
347
|
| Capability | CLI command | GitHub Action |
|
|
270
348
|
|---|---|---|
|
|
349
|
+
| Build a directory | `wp-release build` | |
|
|
271
350
|
| Build a ZIP | `wp-release zip` | [`wpelevator/wp-release-zip`](https://github.com/wpelevator/wp-release-zip) |
|
|
272
351
|
| Create a checksums manifest | `wp-release checksums` | [`wpelevator/wp-release-checksums`](https://github.com/wpelevator/wp-release-checksums) |
|
|
273
352
|
| Sign files | `wp-release sign` | [`wpelevator/wp-release-sign`](https://github.com/wpelevator/wp-release-sign) |
|
|
@@ -276,12 +355,15 @@ npm publish --workspace packages/library --tag pr-251
|
|
|
276
355
|
| Deploy to WordPress.org SVN | `wp-release deploy-svn` | [`wpelevator/wp-release-deploy-svn`](https://github.com/wpelevator/wp-release-deploy-svn) |
|
|
277
356
|
| Deploy to a Git repository | `wp-release deploy-git` | |
|
|
278
357
|
|
|
279
|
-
|
|
358
|
+
The names below are scoped to their commands. `--output` selects the primary artifact of each command; ZIP packaging uses `--checksums-file` for its accompanying manifest:
|
|
280
359
|
|
|
281
360
|
| Concept | CLI | GitHub Action | Library | Meaning |
|
|
282
361
|
|---|---|---|---|---|
|
|
283
362
|
| Plugin or theme directory | `<source-dir>` argument or `--source-dir` | `source-dir` | `sourceDir` | Directory that a command reads, whether it's the repository or a build directory. |
|
|
284
|
-
|
|
|
363
|
+
| Build directory | `build --output <dir>` | — | `outputDir` | Directory written by `build`; files go directly inside it. |
|
|
364
|
+
| ZIP file | `zip --output <file>` | `output` in ZIP action | `zipPath` | Destination passed to `zipPackage()`; also returned as `zipPath`. |
|
|
365
|
+
| Standalone checksums file | `checksums --output <file>` | `output` in checksums action | first argument of `writeChecksums()` | Manifest destination relative to the working directory; the CLI returns its absolute path as `checksumsPath`. |
|
|
366
|
+
| Checksums filename when zipping | `zip --checksums-file <file>` | `checksums-file` in ZIP action | `checksumsFile` | Relative manifest filename beside the ZIP or inside its package root when embedded. |
|
|
285
367
|
| ZIP to deploy | `--from-zip <file>` | `from-zip` | `fromZip` | Existing ZIP read as the deploy source. |
|
|
286
368
|
| Git tag to compare | `--git-tag <tag>` | | `gitTag` | Git tag checked by `version-check`. |
|
|
287
369
|
| SVN tag to create | `--svn-tag <version>` | `svn-tag` | `svnTag` | Tag written by `deploy-svn`, distinct from a git tag. |
|
|
@@ -291,18 +373,20 @@ Each concept has one name, written in the syntax of each interface:
|
|
|
291
373
|
| Source history | `--split-prefix <dir>` | | `splitPrefix` | Directory of a monorepo package whose history, from `git subtree split`, becomes the source history of `deploy-git` releases. |
|
|
292
374
|
| SVN credentials | `--svn-username <name>`, `SVN_USERNAME` and `SVN_PASSWORD` env vars | `svn-username`, `svn-password` | `svnUsername`, `svnPassword` | Needed only to commit. There's no password flag, since it would show in the process list; the action inputs and library options fall back to the env vars. |
|
|
293
375
|
| Log format | `--log-format <text\|github>`, `--ci` for `github` | always `github` | `writer: new TextWriter()` or `new GitHubActionsWriter()` | How the log is printed: plain text, or GitHub Actions annotations and job summaries. Each name matches the start of its writer class. |
|
|
294
|
-
| Release config | `--config <path>` | `config` in ZIP and SVN actions | `configFile` | Explicit JavaScript config path for
|
|
376
|
+
| Release config | `--config <path>` | `config` in ZIP, checksums and SVN actions | `configFile` | Explicit JavaScript config path for package selection, integrity settings or deployment. |
|
|
295
377
|
| Release tag format | `--tag-format <format>` | `tag-format` in SVN action | `tagFormat` | How `release-ref` identifies a package and its version. |
|
|
296
378
|
| Release ref to deploy | `--release-ref <ref>` | `release-ref` in SVN action | `ref` | Read configured packages and deployment targets from a release ref. |
|
|
297
379
|
| Assets sync | `--no-assets` to skip | `assets: false` to skip | `assets` | Booleans are positive in the library and the action; `--no-assets` is only the CLI negation, never `noAssets` or a `no-assets` input. |
|
|
298
380
|
|
|
299
|
-
CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs use kebab-case like `zip-path`. The ZIP action supports `package` and `config`, and the SVN action supports `release-ref`, repeated `package` values as newline-separated input, `list`, `config` and `tag-format`.
|
|
381
|
+
CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs use kebab-case like `zip-path`. The ZIP action supports `package` and `config`, the checksums action supports `config` for codec selection, and the SVN action supports `release-ref`, repeated `package` values as newline-separated input, `list`, `config` and `tag-format`.
|
|
382
|
+
|
|
383
|
+
The ZIP CLI result uses `checksumsEmbedded` for the package-relative embedded manifest path. Its `signatures` array includes external absolute paths and embedded package-relative paths, while `checksumsSignatures` contains only manifest signatures. The ZIP action exposes those locations as `checksums-embedded`, `signatures` for external signatures, and `signatures-embedded` for embedded signatures, so asset uploads receive local files. With CLI `--github-output`, signature arrays remain in the JSON `result` output.
|
|
300
384
|
|
|
301
385
|
Library classes are named after the capability (`SvnDeploy`, `Svn`, `GitDeploy`) without repeating "release".
|
|
302
386
|
|
|
303
387
|
### File selection
|
|
304
388
|
|
|
305
|
-
`zip`, `checksums`, `deploy-svn` and `deploy-git` select files the same way:
|
|
389
|
+
`build`, `zip`, `checksums`, `deploy-svn` and `deploy-git` select files the same way:
|
|
306
390
|
|
|
307
391
|
| Option | Description |
|
|
308
392
|
|---|---|
|
|
@@ -319,6 +403,25 @@ The commands read the same headers as WordPress. The main plugin file is the top
|
|
|
319
403
|
|
|
320
404
|
`--version` defaults to the `Version` header in `zip`, `checksums` and `version-check`, and `checksums --type` defaults to the detected package type.
|
|
321
405
|
|
|
406
|
+
### Build a directory
|
|
407
|
+
|
|
408
|
+
`build` and `zip` use the same internal helper to load file rules, exclude their outputs, select release files and reject an empty selection. `build` copies the selected files directly into `--output <dir>`, without a slug wrapper or ZIP. Checksums and manifest signatures are optional. It supports the shared file selection flags above and `--package <name>` with `--config <path>` for configured package rules. A configured package cannot be combined with a source directory or file selection flags.
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
wp-release build --output dist/code-prettify
|
|
412
|
+
wp-release build --package plugins/alpha --output dist/alpha
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The destination is required and automatically excluded when it is inside the source. It cannot be the source directory or an ancestor, including through symlinked parents. Existing destinations require `--force`, which replaces the directory and removes stale files; existing files and symbolic links are rejected. Files are copied to a temporary directory before the existing build is replaced. Selected symbolic links are rejected as with ZIP packaging. `--json` returns `outputDir`, `files` and, for configured builds, `package`; it also returns `checksumsPath` when a manifest is generated, and `signatures` and `publicKey` when signing is requested. `--github-output` exposes these results too.
|
|
416
|
+
|
|
417
|
+
`--checksums` writes `<output-dir>.checksums.json` beside the directory and hashes the copied build files. `--sign-checksums` generates and signs that manifest, implying `--checksums`. Use `--slug` and `--version` to override its metadata. The manifest and its signatures are excluded from the source selection, so rebuilding with `--force` does not package previous sidecars. Existing manifests and requested signature files also require `--force`; signing keys, formats and signature destinations are validated before creating or replacing the build. `--sign` is unsupported because a directory has no single file to sign.
|
|
418
|
+
|
|
419
|
+
```sh
|
|
420
|
+
wp-release build --output dist/code-prettify --sign-checksums
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
In the Deploy workflow, a directory build can be deployed with `wp-release deploy-svn dist/code-prettify --slug code-prettify --trunk --svn-tag "$GITHUB_REF_NAME" --no-distignore`. Keep `--from-zip` when the SVN deployment must match the attached ZIP exactly. Run dependency installation and compilation before `build`; it only selects and copies files.
|
|
424
|
+
|
|
322
425
|
### ZIP
|
|
323
426
|
|
|
324
427
|
The [`wp-release-zip`](https://github.com/wpelevator/wp-release-zip) GitHub Action runs this command.
|
|
@@ -331,12 +434,37 @@ wp-release zip dist --slug my-plugin --checksums
|
|
|
331
434
|
|
|
332
435
|
- `--root-folder <name>` changes the root folder and `--no-root-folder` puts the files at the root of the ZIP.
|
|
333
436
|
- `--filename-format` changes the name, like `--filename-format {slug}` for `my-plugin.zip`.
|
|
334
|
-
- Entries are sorted and use a fixed timestamp and file mode,
|
|
437
|
+
- Entries are sorted and use a fixed timestamp and file mode. Built-in metadata is deterministic except for embedded minisign signatures, which include their signing timestamp; custom codec callbacks control their own encoded bytes.
|
|
335
438
|
- `--checksums` also writes the [checksums manifest](#checksums) of the archived files next to the ZIP, with `.checksums.json` instead of `.zip`.
|
|
336
|
-
- `--sign`
|
|
439
|
+
- `--sign` signs only the ZIP with `WP_RELEASE_SIGNING_KEY`. `--sign-checksums` generates and signs only the checksums manifest, implying `--checksums`. Use both flags to sign both files. External signatures are written alongside their files; embedded manifest signatures are written inside the package. The default format is WordPress `.sig`, unless integrity configuration selects another format.
|
|
440
|
+
- `--sign-format` selects formats for requested signatures, such as `--sign-format wordpress,minisign,ssh`. It requires `--sign` or `--sign-checksums` and never enables signing by itself. Signing keys, formats and existing signature destinations are validated before writing the ZIP or manifest. `--force` also overwrites requested signatures. Job summaries include signatures and public keys; JSON results include `signatures` and `publicKey`.
|
|
337
441
|
- An existing ZIP or manifest isn't overwritten without `--force`. A ZIP or manifest written inside the source directory is never archived.
|
|
338
442
|
- `--package <name>` zips a package of the [configured packages](#deploy-configured-packages), by identifier or path under the configured root like `plugins/alpha`, instead of a source directory: its directory with the `files` config of its package group, the same files that its deploys select. The slug defaults to the directory name and the version to the `Version` header, and `--slug` and `--version` still set them. Any package of the config works, with or without a version or deploy targets. To zip what a deploy just released, pass the `package` and `version` of the `deploy-git --release-ref` result, in the same job and from the same build, so the ZIP has exactly the files of the release tag in the release repository. It can't be combined with `<source-dir>` or the file selection options. The `--json` result has the `package`, `slug` and `version`. `zipPackage()` builds the same ZIP from a script, with `getPackageFiles()` for the directory and the files of a package.
|
|
339
443
|
|
|
444
|
+
To embed a signed manifest with a stable filename, use:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
wp-release zip dist --checksums-file checksums.json --embed-checksums --sign-checksums
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
See [Checksums](#checksums) for filenames, placement, formats and configuration.
|
|
451
|
+
|
|
452
|
+
### Signing targets for builds and ZIPs
|
|
453
|
+
|
|
454
|
+
| Flag | `zip` | `build` |
|
|
455
|
+
|---|---|---|
|
|
456
|
+
| `--checksums` | Generate an unsigned manifest beside the ZIP. | Generate an unsigned manifest beside the directory. |
|
|
457
|
+
| `--sign` | Sign only the ZIP. | Unsupported. |
|
|
458
|
+
| `--sign-checksums` | Generate and sign the manifest. | Generate and sign the manifest. |
|
|
459
|
+
| `--sign-format` | Choose formats for explicitly requested signatures. | Same; requires `--sign-checksums`. |
|
|
460
|
+
|
|
461
|
+
```sh
|
|
462
|
+
wp-release zip --slug code-prettify --sign --sign-checksums
|
|
463
|
+
wp-release build --output dist/code-prettify --sign-checksums --sign-format minisign
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
These flags deliberately replace the former implicit signing behavior: `zip --checksums --sign` now leaves the manifest unsigned, and `--sign-format` alone is rejected. Add `--sign-checksums` to sign a manifest and `--sign` to sign a ZIP. The standalone `checksums` and `sign` commands remain available for workflows that write and sign a manifest inside the source before packaging it.
|
|
467
|
+
|
|
340
468
|
### Checksums
|
|
341
469
|
|
|
342
470
|
The [`wp-release-checksums`](https://github.com/wpelevator/wp-release-checksums) GitHub Action runs this command.
|
|
@@ -365,26 +493,92 @@ wp-release checksums dist --version 1.2.0
|
|
|
365
493
|
|
|
366
494
|
Because the manifest lists the files and not the ZIP, it stays valid for any copy of the same files: the GitHub release ZIP, the ZIP that WordPress.org builds from SVN, or an installed plugin on a site.
|
|
367
495
|
|
|
368
|
-
|
|
496
|
+
#### Filenames, embedding and signing
|
|
369
497
|
|
|
370
|
-
|
|
498
|
+
`--checksums-file <file>` enables checksums and sets a relative filename independently of the ZIP name. Without `--embed-checksums`, it resolves against the ZIP output directory; with embedding, it resolves against the package root inside the ZIP (the archive root with `--no-root-folder`). Nested paths are supported, but absolute paths, backslashes, drive letters, control characters and dot segments are rejected. Embedding emits no external manifest and never modifies the source directory. Embedded minisign signatures contain a signing timestamp, so ZIP bytes can differ across signing times; unsigned metadata and WordPress/SSH signatures remain deterministic. Selected source files must not collide with generated manifest or signature entries.
|
|
371
499
|
|
|
372
500
|
```bash
|
|
373
|
-
|
|
501
|
+
# External manifest beside releases/my-plugin.1.5.1.zip.
|
|
502
|
+
wp-release zip dist --output releases/my-plugin.1.5.1.zip --checksums-file checksums.json --sign-checksums
|
|
503
|
+
|
|
504
|
+
# Signed manifest inside the package, with a stable filename.
|
|
505
|
+
wp-release zip dist --output releases/my-plugin.1.5.1.zip --checksums-file checksums.json --embed-checksums --sign-checksums
|
|
506
|
+
|
|
507
|
+
# Sign the completed downloadable archive separately.
|
|
508
|
+
wp-release sign releases/my-plugin.1.5.1.zip
|
|
509
|
+
|
|
510
|
+
# Verify installed files or an archive using its embedded manifest.
|
|
511
|
+
wp-release verify installed-plugin --public-key "$PUBLIC_KEY"
|
|
512
|
+
wp-release verify --zip releases/my-plugin.1.5.1.zip --public-key "$PUBLIC_KEY"
|
|
374
513
|
```
|
|
375
514
|
|
|
376
|
-
-
|
|
377
|
-
- `verify` doesn't read `.distignore`, so that an ignore file inside the checked directory can't hide added files. `--exclude <patterns>` skips paths in both the directory and the manifest, for files that aren't part of the release, such as caches.
|
|
378
|
-
- Manifest paths outside the package folder (absolute or with `..`) are rejected.
|
|
379
|
-
- `--zip <file|url>` checks the files of a ZIP instead of a directory. The ZIP can be a URL, such as the one that WordPress.org builds for a release, and its single root folder is found automatically (or set `--root-folder`). The ZIP is extracted to a temporary directory after the same safety checks as `deploy-svn --from-zip`.
|
|
380
|
-
- The version in the manifest must match the `Version` header of the checked files, so that an older release with its own valid manifest can't pass as a newer one.
|
|
381
|
-
- A manifest can't list itself. A copy of the manifest and of its signature inside the checked files, at the root with the same names as the `--checksums` file or at the path of `--embedded`, is skipped when comparing files, but it must be identical to the `--checksums` file and its signature. The signature is the `.sig` file, or the file of `--signature-format`. A path that the manifest lists is never skipped, and a file that isn't a matching copy is reported as not in the checksums. See [Manifest inside the plugin](#manifest-inside-the-plugin).
|
|
515
|
+
`--sign-checksums` enables checksums and signs only the manifest, before embedding when requested. It reads `WP_RELEASE_SIGNING_KEY` and uses `--sign-format wordpress,minisign,ssh` to select formats; each signature travels with the manifest. `--sign-format` selects formats for explicitly requested signing targets and never enables signing. `--sign` signs only the final ZIP; `--sign-checksums` controls the manifest in either placement mode. Use both flags to sign both targets. `--force` permits replacing external output files and signatures. `--json` reports external manifests in `checksumsPath`, embedded package-relative paths in `checksumsEmbedded`, and manifest signatures in `checksumsSignatures`.
|
|
382
516
|
|
|
383
|
-
|
|
517
|
+
The default WordPress filename remains the ZIP basename with `.checksums.json` instead of `.zip`, in either placement mode. Choose `--checksums-file checksums.json` for a stable name. The version remains in the JSON content.
|
|
518
|
+
|
|
519
|
+
#### Formats
|
|
520
|
+
|
|
521
|
+
`--checksums-format` chooses the manifest format. `wordpress-json` (default) uses the WordPress-compatible structure shown above. `sha256sum` uses GNU-compatible SHA-256 lines and escaping, with `SHA256SUMS` as its default filename. It carries no package identity or version, and standard `sha256sum --check` checks listed files without rejecting additional files; `wp-release verify` also checks for extra files.
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
wp-release zip dist --embed-checksums --checksums-format sha256sum --sign-checksums --sign-format minisign
|
|
525
|
+
wp-release verify --zip my-plugin.1.5.1.zip --checksums-format sha256sum --public-key "$MINISIGN_PUBLIC_KEY" --signature-format minisign
|
|
526
|
+
# From the extracted package directory:
|
|
527
|
+
minisign -Vm SHA256SUMS -P "$MINISIGN_PUBLIC_KEY"
|
|
528
|
+
sha256sum --check SHA256SUMS
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`--checksums-algorithms md5,sha256` selects JSON digest fields; lists replace configured lists. Supported algorithms are MD5 (for compatibility) and SHA-256. The `sha256sum` codec requires SHA-256. The standalone `checksums` command also accepts `--checksums-format`, keeping `--output` relative to the working directory.
|
|
532
|
+
|
|
533
|
+
#### Configuration and custom codecs
|
|
534
|
+
|
|
535
|
+
The optional top-level `integrity` configuration maps to these flags. Explicit CLI values override configuration; `--no-embed-checksums` and `--no-sign-checksums` disable their configured behaviors. Existing release configurations retain their `packages` array; use `packages: []` for a configuration containing only integrity settings.
|
|
536
|
+
|
|
537
|
+
```js
|
|
538
|
+
export default {
|
|
539
|
+
packages: [],
|
|
540
|
+
integrity: {
|
|
541
|
+
codec: 'wordpress-json', // --checksums-format
|
|
542
|
+
algorithms: ['md5', 'sha256'], // --checksums-algorithms
|
|
543
|
+
file: 'checksums.json', // --checksums-file
|
|
544
|
+
embed: true, // --embed-checksums
|
|
545
|
+
sign: { formats: ['wordpress'] }, // --sign-checksums and --sign-format
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Use `codec: 'sha256sum'`, `algorithms: ['sha256']`, `file: 'SHA256SUMS'` and `sign: { formats: ['minisign'] }` for the GNU setup. Custom codecs provide `id`, a default `filename`, and `encode`/`decode` callbacks, which may be asynchronous. Generation validates the round trip before writing artifacts: codecs must preserve all selected file paths, represented digests and any represented package identity. They receive and return the existing WordPress-compatible `ChecksumsManifest` model (`plugin` or `theme`, optional `version`, and a `files` map of relative paths to digest fields), keeping format customization separate from selection, hashing and signing.
|
|
551
|
+
|
|
552
|
+
```js
|
|
553
|
+
export default {
|
|
554
|
+
packages: [],
|
|
555
|
+
integrity: {
|
|
556
|
+
file: 'metadata/integrity.json',
|
|
557
|
+
embed: true,
|
|
558
|
+
sign: { formats: ['wordpress'] },
|
|
559
|
+
codec: {
|
|
560
|
+
id: 'custom-v1',
|
|
561
|
+
filename: 'integrity.json',
|
|
562
|
+
encode(manifest) {
|
|
563
|
+
return Buffer.from(JSON.stringify({ schema: 'custom-v1', payload: manifest }) + '\n');
|
|
564
|
+
},
|
|
565
|
+
decode(bytes) {
|
|
566
|
+
const document = JSON.parse(Buffer.from(bytes).toString('utf8'));
|
|
567
|
+
if (document.schema !== 'custom-v1') {
|
|
568
|
+
throw new Error('Unknown integrity schema.');
|
|
569
|
+
}
|
|
570
|
+
return document.payload;
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
`zip` and `checksums` read the caller's release configuration. For verification of custom codecs or configured filenames, pass `--config wp-release.config.mjs` explicitly. See [Verify](#verify).
|
|
384
578
|
|
|
385
|
-
|
|
579
|
+
#### Manifest inside the plugin
|
|
386
580
|
|
|
387
|
-
|
|
581
|
+
For a manifest that is also published as a separate release asset or deployed directly from a source directory, write and sign it in that directory before packaging. Use `zip --embed-checksums` as described [above](#filenames-embedding-and-signing) when only the archive should contain the generated metadata. The source-directory workflow uses the same manifest for installed copies and release assets:
|
|
388
582
|
|
|
389
583
|
```bash
|
|
390
584
|
wp-release checksums build --slug my-plugin --output build/checksums.json
|
|
@@ -393,7 +587,7 @@ wp-release zip build --slug my-plugin
|
|
|
393
587
|
wp-release deploy-svn build --trunk --svn-tag 1.2.0
|
|
394
588
|
```
|
|
395
589
|
|
|
396
|
-
- Write the manifest into the directory that is zipped and deployed.
|
|
590
|
+
- Write the manifest into the directory that is zipped and deployed. The `checksums` command leaves its output manifest and its signatures (`<name>.sig`, `<name>.minisig` and `<name>.sshsig`) out of the hashes, so running it again with the same output path and `--force` gives the same manifest. `verify` checks matching copies as described below. Sign it last, since the signature covers the exact bytes of the manifest.
|
|
397
591
|
- `zip` and `deploy-svn` carry the pair like any other file. Don't use `zip --checksums` here, since it would write a second manifest that lists the first one. Attach `build/checksums.json` and `build/checksums.json.sig` to the GitHub release as they are.
|
|
398
592
|
- The signature covers file hashes and not a ZIP, so it stays valid for the ZIP that WordPress.org builds from SVN. Check what WordPress.org serves after a deploy, with the manifest and public key from your build:
|
|
399
593
|
|
|
@@ -406,6 +600,25 @@ wp-release deploy-svn build --trunk --svn-tag 1.2.0
|
|
|
406
600
|
|
|
407
601
|
The public key has to come from somewhere other than the plugin that it checks, because someone who can overwrite the plugin files can also replace a key and a signature inside them. A copy of the manifest also doesn't prevent a downgrade to an older release that was signed too, so the installing side has to enforce a minimum version.
|
|
408
602
|
|
|
603
|
+
### Verify
|
|
604
|
+
|
|
605
|
+
`verify` checks a directory against a manifest from `checksums` or from the WordPress.org checksums API, and reports every changed, missing and extra file. It exits with `2` unless all files match:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
wp-release verify wp-content/plugins/my-plugin --checksums my-plugin.1.2.0.checksums.json
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
- A file matches when its SHA-256 equals the manifest. Manifests without SHA-256 fall back to MD5, and a hash can also be a list of accepted values.
|
|
612
|
+
- `verify` doesn't read `.distignore`, so that an ignore file inside the checked directory can't hide added files. `--exclude <patterns>` skips paths in both the directory and the manifest, for files that aren't part of the release, such as caches.
|
|
613
|
+
- Manifest paths outside the package folder (absolute or with `..`) are rejected.
|
|
614
|
+
- `--zip <file|url>` checks the files of a ZIP instead of a directory. The ZIP can be a URL, such as the one that WordPress.org builds for a release, and its single root folder is found automatically (or set `--root-folder`). The ZIP is extracted to a temporary directory after the same safety checks as `deploy-svn --from-zip`.
|
|
615
|
+
- The version in the manifest must match the `Version` header of the checked files, so that an older release with its own valid manifest can't pass as a newer one.
|
|
616
|
+
- A manifest can't list itself. A copy of the manifest and of its signature inside the checked files, at the root with the same names as the `--checksums` file or at the path of `--embedded`, is skipped when comparing files, but it must be identical to the `--checksums` file and its signature. The signature is the `.sig` file, or the file of `--signature-format`. A path that the manifest lists is never skipped, and a file that isn't a matching copy is reported as not in the checksums. See [Manifest inside the plugin](#manifest-inside-the-plugin).
|
|
617
|
+
|
|
618
|
+
A checksums manifest proves only that the files match the manifest. With `--public-key <key>`, `verify` first checks `<checksums>.sig` against the Ed25519 public key and fails with exit code `2` before comparing any file when the signature is missing or invalid, so that the manifest itself can be trusted. `--signature-format minisign` or `ssh` checks `<checksums>.minisig` or `<checksums>.sshsig` instead. The key can be the base64 key, the minisign public key or the `ssh-ed25519` public key, since they're forms of the same key. See [Sign](#sign).
|
|
619
|
+
|
|
620
|
+
Verification never executes configuration from inside a downloaded package. Without `--checksums`, `verify` reads the embedded filename selected by `--checksums-file`, explicit configuration, or the codec default. For a versioned embedded filename, pass that filename explicitly. With a public key, signatures are checked before decoding; the selected signature format must exist (WordPress `.sig` by default; pass `--signature-format minisign` or `ssh` for those formats), and all other present supported signature formats are also authenticated when verifying the embedded manifest. Without a public key, checksums detect changes but do not authenticate the publisher. Trusted public keys must come from outside the package.
|
|
621
|
+
|
|
409
622
|
### Sign
|
|
410
623
|
|
|
411
624
|
The [`wp-release-sign`](https://github.com/wpelevator/wp-release-sign) GitHub Action runs this command.
|
|
@@ -420,7 +633,7 @@ WP_RELEASE_SIGNING_KEY=… wp-release sign my-plugin.1.2.0.zip my-plugin.1.2.0.c
|
|
|
420
633
|
- The key is read from the `WP_RELEASE_SIGNING_KEY` environment variable only, as a base64 32-byte Ed25519 seed or a 64-byte libsodium secret key, the formats that `sodium_crypto_sign_seed_keypair()` and the update-pilot-server package signing key take. There's no flag for it, since it would show in the process list, and it's never logged.
|
|
421
634
|
- `--verbose` prints the public key of the signing key in the form of each written format. `--json` returns it as `publicKey`, and in every form as `publicKeys`. Pass it to `verify --public-key` and to the plugin that checks the signatures.
|
|
422
635
|
- An existing signature file isn't overwritten without `--force`.
|
|
423
|
-
- `zip --sign` signs
|
|
636
|
+
- `zip --sign` signs only the ZIP; add `--sign-checksums` to sign its manifest too. `signFiles()` signs files from a script.
|
|
424
637
|
|
|
425
638
|
#### Signature formats
|
|
426
639
|
|
|
@@ -440,7 +653,7 @@ wp-release sign my-plugin.1.2.0.zip --format wordpress,minisign,ssh
|
|
|
440
653
|
- `ssh` is the armored `SSHSIG` format that `ssh-keygen -Y sign` writes, with a SHA-512 digest and the `file` namespace, so that the signature can't pass as one for commits or another namespace. It's written to `.sshsig` instead of the `.sig` of `ssh-keygen`, which the `wordpress` format uses. Put the SSH public key in an `allowed_signers` file as `<identity> ssh-ed25519 AAAA…`.
|
|
441
654
|
- All formats are Ed25519 signatures by the same key, so one key and one secret cover all of them. The tests check each format with `minisign`, `ssh-keygen` and libsodium, and check the signatures that `minisign` and `ssh-keygen` write with `wp-release verify`.
|
|
442
655
|
|
|
443
|
-
Generate a key pair with `wp-release keygen`. It prints the private seed once as `WP_RELEASE_SIGNING_KEY=…` and the matching public key, also as a minisign public key and an `ssh-ed25519` public key.
|
|
656
|
+
Generate a key pair with `wp-release keygen`. It prints the private seed once as `WP_RELEASE_SIGNING_KEY=…` and the matching public key, also as a minisign public key and an `ssh-ed25519` public key. Generate keys locally and store the private seed in your secret manager or CI secrets. Distribute the public key to verifiers. `keygen --github-output` is rejected to keep the private seed out of workflow outputs; regular and `--json` output both include the seed.
|
|
444
657
|
|
|
445
658
|
### Version check
|
|
446
659
|
|
|
@@ -506,7 +719,7 @@ Validation:
|
|
|
506
719
|
- `--readme-only` can't be combined with `--svn-tag`, since a tag is a release snapshot that a readme update never changes.
|
|
507
720
|
- `--version` defaults to `--svn-tag`, then the `Version` header of the deployed files; when `--version` and `--svn-tag` are both set they must match.
|
|
508
721
|
- Pre-release versions like `1.0.0-rc.1` can be tagged, but syncing one to `trunk/` warns, since trunk is for the development version.
|
|
509
|
-
- `--trunk` without `--svn-tag` works but warns,
|
|
722
|
+
- `--trunk` without `--svn-tag` works but warns, to remind you that updating trunk alone doesn't create a version tag for stable downloads.
|
|
510
723
|
- A version mismatch throws a `VersionMismatchError`, a `ValidationError` with the `mismatches` and a problem for each, with its file and line. See [Logging](#logging).
|
|
511
724
|
|
|
512
725
|
### Deploy to Git
|
|
@@ -587,6 +800,83 @@ A deploy releases the files of the working tree, so build the packages first. On
|
|
|
587
800
|
|
|
588
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.
|
|
589
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
|
+
|
|
590
880
|
### Exit codes
|
|
591
881
|
|
|
592
882
|
| Code | Meaning |
|
|
@@ -647,7 +937,7 @@ const { releases } = await release.version( { dryRun: true } );
|
|
|
647
937
|
const { tags } = await release.tag( { dryRun: true } );
|
|
648
938
|
```
|
|
649
939
|
|
|
650
|
-
`ChangesetRelease` runs the `changeset-*` commands: `add()`, `getStatus()`, `version()` and `tag()`, plus `getPlan()` and `getPackages()`. Each call reads the configuration, packages and pending changesets again, so one instance can version and then tag. It takes a git runner from `createGitRunner()` as `git`, so that tests can replace it. The functions `addChangeset()`, `getChangesetStatus()`, `versionChangesets()`, `tagChangesets()`, `getChangesetReleasePlan()` and `getChangesetPackages()` do the same with the default runners, in one call.
|
|
940
|
+
`ChangesetRelease` runs the `changeset-*` commands: `add()`, `getStatus()`, `version()` and `tag()`, plus `getPlan()` and `getPackages()`. Each call reads the configuration, packages and pending changesets again, so one instance can version and then tag. It takes a git runner from `createGitRunner()` as `git`, so that tests can replace it. Without a config file, it detects a single root project. Its optional `singleProject` settings accept `versions` (the existing version descriptors), `versionFrom` (the same selector as `--version-from`), `baseBranch`, `remote`, `changesets`, `readmeChangelog` and `changelogInsertAfter`; `tagFormat` remains a repository option. The functions `addChangeset()`, `getChangesetStatus()`, `versionChangesets()`, `tagChangesets()`, `getChangesetReleasePlan()` and `getChangesetPackages()` do the same with the default runners, in one call.
|
|
651
941
|
|
|
652
942
|
### Logging
|
|
653
943
|
|
|
@@ -738,9 +1028,24 @@ const { valid, changed, missing, extra } = await verifyChecksums( {
|
|
|
738
1028
|
|
|
739
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`.
|
|
740
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
|
+
|
|
741
1046
|
## Development
|
|
742
1047
|
|
|
743
|
-
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.
|
|
744
1049
|
|
|
745
1050
|
```bash
|
|
746
1051
|
npm run build --workspace @wpelevator/wp-release
|
|
@@ -752,6 +1057,8 @@ npm run lint --workspace @wpelevator/wp-release
|
|
|
752
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.
|
|
753
1058
|
- `lint` runs ESLint and a `tsc` type check.
|
|
754
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
|
+
|
|
755
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.
|
|
756
1063
|
|
|
757
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.
|
|
@@ -760,10 +1067,10 @@ The `wpelevator-source` export condition points at `src/index.ts`. The wp-releas
|
|
|
760
1067
|
|
|
761
1068
|
wp-release has two layers, so that it fits any repository layout:
|
|
762
1069
|
|
|
763
|
-
- **Primitives** work on the directory, file or values they're given and know nothing about the repository: `zip <dir>`, `checksums`, `sign`, `verify`, `version-check`, `deploy-git <dir
|
|
764
|
-
- **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, `deploy-git` and `deploy-svn --release-ref`, and `zip --package` belong to it, and the global `--config` and `--tag-format` options select or adjust the config. 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.
|
|
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.
|
|
765
1072
|
|
|
766
|
-
|
|
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.
|
|
767
1074
|
|
|
768
1075
|
The GitHub Actions are adapters to these commands: they use the same flag names and `execute()` entry point, with secrets supplied through environment variables. The SVN action injects a command runner that checks or installs SVN only when a deployment runs its first SVN command; config planning and deployment behavior remain in `wp-release`.
|
|
769
1076
|
|
|
@@ -780,6 +1087,7 @@ The changeset release modules, from the entry point down:
|
|
|
780
1087
|
| Module | Responsibility |
|
|
781
1088
|
|---|---|
|
|
782
1089
|
| `changeset-release.ts` | `ChangesetRelease`, which plans releases and runs `add()`, `getStatus()`, `version()` and `tag()`, and the function form of each operation. |
|
|
1090
|
+
| `single-project.ts` | Resolving the config-free root project, its version source and targets, and shared release settings. |
|
|
783
1091
|
| `changeset-config.ts` | Loading and validating `wp-release.config.js`, and the version source and target helpers. |
|
|
784
1092
|
| `changeset-packages.ts` | Discovering the packages of the configured globs, with or without a version, and finding the changed ones. |
|
|
785
1093
|
| `changeset-files.ts` | Reading, writing and consuming changeset files. |
|
|
@@ -795,6 +1103,23 @@ The changeset release modules, from the entry point down:
|
|
|
795
1103
|
| `file-journal.ts` | `FileJournal`, which restores every file a failed `changeset-version` run changed. |
|
|
796
1104
|
| `versions.ts` | Version checks and SemVer precedence. |
|
|
797
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
|
+
|
|
798
1123
|
## Comparison with existing tools
|
|
799
1124
|
|
|
800
1125
|
| Tool | What it does | How `wp-release` differs |
|
|
@@ -803,5 +1128,7 @@ The changeset release modules, from the entry point down:
|
|
|
803
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. |
|
|
804
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. |
|
|
805
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. |
|
|
806
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. |
|
|
807
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. |
|