@wpelevator/wp-release 0.13.0 → 0.15.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 +83 -25
- package/dist/changeset-config.d.ts +8 -6
- package/dist/changeset-config.js +7 -5
- package/dist/changeset-packages.d.ts +12 -2
- package/dist/changeset-packages.js +13 -1
- package/dist/changeset-release.d.ts +5 -2
- package/dist/changeset-release.js +12 -6
- package/dist/cli.d.ts +16 -7
- package/dist/cli.js +58 -11
- package/dist/commands/args.d.ts +11 -21
- package/dist/commands/args.js +24 -35
- package/dist/commands/changeset-add.d.ts +1 -1
- package/dist/commands/changeset-add.js +3 -2
- package/dist/commands/changeset-status.d.ts +1 -1
- package/dist/commands/changeset-status.js +2 -1
- package/dist/commands/changeset-tag.d.ts +1 -1
- package/dist/commands/changeset-tag.js +4 -2
- package/dist/commands/changeset-version.d.ts +1 -1
- package/dist/commands/changeset-version.js +2 -1
- package/dist/commands/checksums.js +2 -2
- package/dist/commands/command.d.ts +5 -0
- package/dist/commands/deploy-git.d.ts +1 -1
- package/dist/commands/deploy-git.js +2 -1
- package/dist/commands/deploy-svn.d.ts +11 -15
- package/dist/commands/deploy-svn.js +26 -5
- package/dist/commands/keygen.d.ts +3 -0
- package/dist/commands/keygen.js +6 -1
- package/dist/commands/package-deploys.d.ts +8 -0
- package/dist/commands/package-deploys.js +5 -0
- package/dist/commands/sign.d.ts +10 -1
- package/dist/commands/sign.js +29 -31
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +43 -24
- package/dist/commands/zip.d.ts +23 -8
- package/dist/commands/zip.js +171 -73
- package/dist/ed25519.d.ts +25 -0
- package/dist/ed25519.js +44 -0
- package/dist/file-selection.d.ts +21 -0
- package/dist/file-selection.js +35 -0
- package/dist/git.d.ts +17 -0
- package/dist/git.js +23 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +6 -3
- package/dist/minisign.d.ts +41 -0
- package/dist/minisign.js +128 -0
- package/dist/package-deploy.d.ts +28 -0
- package/dist/package-deploy.js +41 -6
- package/dist/package-zip.d.ts +52 -0
- package/dist/package-zip.js +78 -0
- package/dist/release-repository.d.ts +4 -3
- package/dist/release-repository.js +5 -4
- package/dist/signing.d.ts +100 -9
- package/dist/signing.js +165 -41
- package/dist/ssh-signature.d.ts +43 -0
- package/dist/ssh-signature.js +156 -0
- package/dist/summary.d.ts +3 -3
- package/dist/summary.js +2 -2
- package/dist/tag-format.d.ts +21 -12
- package/dist/tag-format.js +25 -39
- package/dist/version-files.d.ts +6 -7
- package/dist/version-files.js +10 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,8 +28,8 @@ npx wp-release --help
|
|
|
28
28
|
|---|---|
|
|
29
29
|
| `wp-release zip [<source-dir>] [options]` | Create a ZIP archive with a `{slug}/` root folder, respecting `.distignore`. |
|
|
30
30
|
| `wp-release checksums [<source-dir>] [options]` | Write the MD5 and SHA-256 hashes of every distributed file to a checksums manifest. |
|
|
31
|
-
| `wp-release sign <file>... [options]` | Sign files with Ed25519, writing a detached `<file>.sig` next to each
|
|
32
|
-
| `wp-release keygen` | Generate a base64 Ed25519 private seed and public key
|
|
31
|
+
| `wp-release sign <file>... [options]` | Sign files with Ed25519, writing a detached `<file>.sig` next to each, or minisign and SSH signatures with `--format`. |
|
|
32
|
+
| `wp-release keygen` | Generate a base64 Ed25519 private seed and its public key, also as minisign and SSH public keys. |
|
|
33
33
|
| `wp-release verify [<source-dir>] --checksums <file> [options]` | Check a plugin or theme directory, or a ZIP, against a checksums manifest. |
|
|
34
34
|
| `wp-release version-check [<source-dir>] [options]` | Check that the `Version` header, readme `Stable tag`, PHP constants and git tag agree. |
|
|
35
35
|
| `wp-release deploy-svn [<source-dir>] [options]` | Deploy a plugin to the WordPress.org plugin SVN repository. |
|
|
@@ -41,7 +41,7 @@ npx wp-release --help
|
|
|
41
41
|
| `wp-release changeset-version [options]` | Apply version bumps and changelog entries. |
|
|
42
42
|
| `wp-release changeset-tag [options]` | Create missing package release tags. |
|
|
43
43
|
|
|
44
|
-
Run `npx wp-release <command> --help` for the options of each command. Every command reads the plugin or theme directory from the first argument or `--source-dir`, defaulting to the current directory, and prints its result as JSON with `--json`. In GitHub Actions, `--github-output` sets the result as JSON to the `result` output of the step instead, for later steps and jobs to read with `fromJSON( steps.<id>.outputs.result )`.
|
|
44
|
+
Run `npx wp-release <command> --help` for the options of each command. Every command reads the plugin or theme directory from the first argument or `--source-dir`, defaulting to the current directory, and prints its result as JSON with `--json`. In GitHub Actions, `--github-output` sets the result as JSON to the `result` output of the step instead, for later steps and jobs to read with `fromJSON( steps.<id>.outputs.result )`. Each top-level string, number or boolean of the result is also its own output, like `steps.<id>.outputs.zipPath` for `zip`, so a step reads it without `fromJSON()`; lists, like the `tags` of `changeset-tag` or the `deploys` of `deploy-git`, are only in `result`. Since GitHub evaluates the `env` of a step also when its `if` is false, and `fromJSON()` fails for the empty output of a skipped step, read the plain outputs in steps that run conditionally.
|
|
45
45
|
|
|
46
46
|
### GitHub Actions annotations and job summaries
|
|
47
47
|
|
|
@@ -65,9 +65,9 @@ Each command also adds a section to the [job summary](https://docs.github.com/en
|
|
|
65
65
|
|---|---|
|
|
66
66
|
| `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
67
|
| `deploy-git` | Repository, release commit and the pushed branch and tag. |
|
|
68
|
-
| `zip` | ZIP, number of files and checksums manifest. |
|
|
68
|
+
| `zip` | ZIP, number of files and checksums manifest; with `--sign`, signature files and the public key of each written format. |
|
|
69
69
|
| `checksums` | Manifest and number of files. |
|
|
70
|
-
| `sign` | Signature files and the public key. |
|
|
70
|
+
| `sign` | Signature files and the public key of each written format. |
|
|
71
71
|
| `verify` | That all files match, or the counts and a table of the changed, missing and extra files. |
|
|
72
72
|
| `version-check` | The matching version, or a table of the mismatches with their files and lines. |
|
|
73
73
|
| `changeset-status` | Each changed package with whether it has a changeset. |
|
|
@@ -76,7 +76,7 @@ Each command also adds a section to the [job summary](https://docs.github.com/en
|
|
|
76
76
|
|
|
77
77
|
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
78
|
|
|
79
|
-
Use global `--config <path>` to select a config; relative paths resolve from the current directory. Otherwise, Changesets commands discover `wp-release.config.js`, `.mjs` or `.cjs` there.
|
|
79
|
+
Use global `--config <path>` to select a config; relative paths resolve from the current directory. Otherwise, Changesets commands discover `wp-release.config.js`, `.mjs` or `.cjs` there. Global `--tag-format <format>` replaces the `tagFormat` of the config for one command, see [Packages and tags](#packages-and-tags).
|
|
80
80
|
|
|
81
81
|
### Changesets support (experimental)
|
|
82
82
|
|
|
@@ -125,13 +125,32 @@ export default defineConfig( {
|
|
|
125
125
|
} );
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
`version.from` selects the current version; `version.to` lists release targets.
|
|
128
|
+
`version.from` selects the current version; `version.to` lists release targets. Use `phpConstantVersion( { file, constant } )` to sync a PHP constant.
|
|
129
|
+
|
|
130
|
+
#### Packages and tags
|
|
131
|
+
|
|
132
|
+
The config describes the repository, so wp-release assumes no directory structure or naming convention of its own:
|
|
133
|
+
|
|
134
|
+
- **Packages** are the directories that the `glob` of a `packages` entry matches under `root`, with `*`, `?` and `**`, like `plugins/*`, or `.` for a repository with one package at its root. The settings of an entry apply to each of its packages. Discovery never looks inside `.git`, `node_modules`, `vendor` or `dist` directories. A package is identified by its path, or with `idFrom` by the name in its `package.json` or `composer.json`, and commands that take a package accept either.
|
|
135
|
+
- **Templates** name the tags and the deploy targets of a package with these placeholders:
|
|
136
|
+
|
|
137
|
+
| Placeholder | Value | `plugins/alpha` | Root package |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `{path}` | Package directory relative to `root`, empty for a package at the root | `plugins/alpha` | (empty) |
|
|
140
|
+
| `{name}` | Package identifier | `plugins/alpha`, or its manifest name with `idFrom` | the manifest name, or the directory name |
|
|
141
|
+
| `{version}` | Released version | `1.2.0` | `1.2.0` |
|
|
142
|
+
|
|
143
|
+
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
|
+
- **`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
|
+
- **`--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 current 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
|
+
- **`remote`** is the git remote that `changeset-tag --push` pushes tags to and that the base branch is compared on, `origin` by default.
|
|
129
148
|
|
|
130
149
|
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.
|
|
131
150
|
|
|
132
|
-
Without `--since`, `changeset-status` compares against `origin
|
|
151
|
+
Without `--since`, `changeset-status` compares against `<remote>/<baseBranch>`, where `remote` in the config defaults to `origin` and is also the remote that `changeset-tag --push` pushes tags to; fetch that ref or pass `--since` when using a fork or different upstream. `loadChangesetConfig()` discovers a conventional config at the repository root, unless `--config <path>` selects one explicitly.
|
|
133
152
|
|
|
134
|
-
Add release notes in a feature PR with `npx wp-release changeset-add`. In a terminal, it lists the packages, marks the ones that changed since
|
|
153
|
+
Add release notes in a feature PR with `npx wp-release changeset-add`. In a terminal, it lists the packages, marks the ones that changed since `<remote>/<baseBranch>` (or `--since <ref>`) without a changeset, and asks for the packages to release, a `patch`, `minor` or `major` bump for each, and the release note. Choose packages by number, identifier or path, or press Enter to release the changed ones; `none` adds an empty changeset. Changed files include uncommitted and untracked ones, since changesets are usually added before committing.
|
|
135
154
|
|
|
136
155
|
Without a terminal, or for scripts, pass the changeset as options: `npx wp-release changeset-add --package @vendor/plugin --bump patch --summary "Fix the checkout validation."`. Repeat package and bump flags for multiple packages, or use `--empty` when no release is needed. A package can also be named by its path under `root`, like `plugins/alpha`, and the changeset records its identifier; `changeset-version --package` and `changeset-tag --package` accept paths too. A single `--bump` without `--package` releases every changed package without a changeset, like `changeset-add --bump patch --summary "…"` to fix a failing `changeset-status` check. `--json` and `--github-output` never ask.
|
|
137
156
|
|
|
@@ -262,7 +281,7 @@ Each concept has one name, written in the syntax of each interface:
|
|
|
262
281
|
| Concept | CLI | GitHub Action | Library | Meaning |
|
|
263
282
|
|---|---|---|---|---|
|
|
264
283
|
| 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. |
|
|
265
|
-
| Generated file | `--output <file>` | | `outputPath` | File written by `zip` or `checksums`. |
|
|
284
|
+
| Generated file | `--output <file>` | `output` | `outputPath` | File written by `zip` or `checksums`. |
|
|
266
285
|
| ZIP to deploy | `--from-zip <file>` | `from-zip` | `fromZip` | Existing ZIP read as the deploy source. |
|
|
267
286
|
| Git tag to compare | `--git-tag <tag>` | | `gitTag` | Git tag checked by `version-check`. |
|
|
268
287
|
| SVN tag to create | `--svn-tag <version>` | `svn-tag` | `svnTag` | Tag written by `deploy-svn`, distinct from a git tag. |
|
|
@@ -272,9 +291,14 @@ Each concept has one name, written in the syntax of each interface:
|
|
|
272
291
|
| 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. |
|
|
273
292
|
| 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. |
|
|
274
293
|
| 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 configured packaging or deployment. |
|
|
295
|
+
| Release tag format | `--tag-format <format>` | `tag-format` in SVN action | `tagFormat` | How `release-ref` identifies a package and its version. |
|
|
296
|
+
| Release ref to deploy | `--release-ref <ref>` | `release-ref` in SVN action | `ref` | Read configured packages and deployment targets from a release ref. |
|
|
275
297
|
| 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. |
|
|
276
298
|
|
|
277
|
-
CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs use kebab-case like `zip-path`.
|
|
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`.
|
|
300
|
+
|
|
301
|
+
Library classes are named after the capability (`SvnDeploy`, `Svn`, `GitDeploy`) without repeating "release".
|
|
278
302
|
|
|
279
303
|
### File selection
|
|
280
304
|
|
|
@@ -309,7 +333,9 @@ wp-release zip dist --slug my-plugin --checksums
|
|
|
309
333
|
- `--filename-format` changes the name, like `--filename-format {slug}` for `my-plugin.zip`.
|
|
310
334
|
- Entries are sorted and use a fixed timestamp and file mode, so the same files always give the same ZIP.
|
|
311
335
|
- `--checksums` also writes the [checksums manifest](#checksums) of the archived files next to the ZIP, with `.checksums.json` instead of `.zip`.
|
|
336
|
+
- `--sign` also [signs](#sign) the ZIP and the manifest with the `WP_RELEASE_SIGNING_KEY` key in the same command that writes them, like `wp-release sign` with the same files, writing a WordPress `.sig` next to each. `--sign-format <names>` picks the [signature formats](#signature-formats), like the `--format` of `sign`, such as `--sign-format wordpress,minisign`, and implies `--sign`. The signature formats, signing key and existing signature paths are checked before writing the ZIP or manifest, so correcting invalid signing options permits a retry without `--force`. `--force` also overwrites existing signatures. With the GitHub log format, the job summary includes the signatures and the public key of each written format. The result lists the signature files in `signatures` and the public key of the signing key in `publicKey`.
|
|
312
337
|
- An existing ZIP or manifest isn't overwritten without `--force`. A ZIP or manifest written inside the source directory is never archived.
|
|
338
|
+
- `--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.
|
|
313
339
|
|
|
314
340
|
### Checksums
|
|
315
341
|
|
|
@@ -352,9 +378,9 @@ wp-release verify wp-content/plugins/my-plugin --checksums my-plugin.1.2.0.check
|
|
|
352
378
|
- Manifest paths outside the package folder (absolute or with `..`) are rejected.
|
|
353
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`.
|
|
354
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.
|
|
355
|
-
- A manifest can't list itself. A copy of the manifest and of its
|
|
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).
|
|
356
382
|
|
|
357
|
-
A checksums manifest proves only that the files match the manifest. With `--public-key <key>`, `verify` first checks `<checksums>.sig` against the
|
|
383
|
+
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).
|
|
358
384
|
|
|
359
385
|
### Manifest inside the plugin
|
|
360
386
|
|
|
@@ -367,7 +393,7 @@ wp-release zip build --slug my-plugin
|
|
|
367
393
|
wp-release deploy-svn build --trunk --svn-tag 1.2.0
|
|
368
394
|
```
|
|
369
395
|
|
|
370
|
-
- Write the manifest into the directory that is zipped and deployed. It lists every file except itself, and `checksums`, `zip` and `verify` also skip its `<name>.sig`, so running `checksums` again on a build that already has the pair gives the same manifest. Sign it last, since the signature covers the exact bytes of the manifest.
|
|
396
|
+
- Write the manifest into the directory that is zipped and deployed. It lists every file except itself, and `checksums`, `zip` and `verify` also skip its signatures (`<name>.sig`, `<name>.minisig` and `<name>.sshsig`), so running `checksums` again on a build that already has the pair gives the same manifest. Sign it last, since the signature covers the exact bytes of the manifest.
|
|
371
397
|
- `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.
|
|
372
398
|
- 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:
|
|
373
399
|
|
|
@@ -376,7 +402,7 @@ wp-release deploy-svn build --trunk --svn-tag 1.2.0
|
|
|
376
402
|
--checksums build/checksums.json --public-key "$PUBLIC_KEY"
|
|
377
403
|
```
|
|
378
404
|
|
|
379
|
-
- The manifest can have any name and location, for example `--output build/includes/signatures/checksums.json`, and missing folders are created. Tell `verify` where the shipped copy is with `--embedded <path>`, like `--embedded includes/signatures/checksums.json`. Both the `--checksums` file and its
|
|
405
|
+
- The manifest can have any name and location, for example `--output build/includes/signatures/checksums.json`, and missing folders are created. Tell `verify` where the shipped copy is with `--embedded <path>`, like `--embedded includes/signatures/checksums.json`. Both the `--checksums` file and its signature must then exist, both copies must be shipped, and they're skipped when comparing files and must be identical to the originals. The manifest can't list the embedded path. Without `--embedded`, `verify` looks for the manifest name at the root, and skips a file only when it exists, the matching original exists and the manifest doesn't list it. Otherwise it's checked like any other file, so a listed file that has the name of the manifest can't be deleted unnoticed, and a shipped `.sig` without an original is reported as not in the checksums.
|
|
380
406
|
|
|
381
407
|
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.
|
|
382
408
|
|
|
@@ -392,10 +418,29 @@ WP_RELEASE_SIGNING_KEY=… wp-release sign my-plugin.1.2.0.zip my-plugin.1.2.0.c
|
|
|
392
418
|
|
|
393
419
|
- The signature is the one WordPress core's `verify_file_signature()` checks: a base64 Ed25519 signature of the raw SHA-384 digest of the file. The tests check it against `sodium_crypto_sign_verify_detached()` in PHP. A `.sig` file can hold several signatures, one per line, and a file is valid when any of them match, which allows a key rotation window.
|
|
394
420
|
- 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.
|
|
395
|
-
- `--verbose` prints the public key of the signing key
|
|
396
|
-
- An existing
|
|
421
|
+
- `--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
|
+
- An existing signature file isn't overwritten without `--force`.
|
|
423
|
+
- `zip --sign` signs a ZIP and its manifest as it writes them, and `signFiles()` signs files from a script.
|
|
424
|
+
|
|
425
|
+
#### Signature formats
|
|
426
|
+
|
|
427
|
+
The same key can also sign in formats that tools outside WordPress check, for people who download a release by hand or verify it in other pipelines. Pick them with `--format`, comma-separated or repeated; the default is `wordpress`:
|
|
397
428
|
|
|
398
|
-
|
|
429
|
+
```bash
|
|
430
|
+
wp-release sign my-plugin.1.2.0.zip --format wordpress,minisign,ssh
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
| Format | File | Checked by |
|
|
434
|
+
|---|---|---|
|
|
435
|
+
| `wordpress` | `<file>.sig` | WordPress core's `verify_file_signature()`, update-client and `wp-release verify` |
|
|
436
|
+
| `minisign` | `<file>.minisig` | [minisign](https://jedisct1.github.io/minisign/) and compatible tools: `minisign -Vm <file> -P <minisign public key>` |
|
|
437
|
+
| `ssh` | `<file>.sshsig` | OpenSSH 8.1 or later: `ssh-keygen -Y verify -f allowed_signers -I <identity> -n file -s <file>.sshsig < <file>` |
|
|
438
|
+
|
|
439
|
+
- `minisign` signs the BLAKE2b-512 digest of the file, like `minisign -S` does by default, and adds a trusted comment with the signing time and file name that a second signature covers. minisign generates a random key ID for its keys, but the key here is a plain Ed25519 seed, so wp-release derives the key ID from the public key. `wp-release verify` doesn't compare key IDs, so signatures of keys that minisign generated verify too.
|
|
440
|
+
- `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
|
+
- 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
|
+
|
|
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. Store the private seed securely; never commit it or put it in a workflow log. Don't run `keygen` in CI or with `--github-output`, since those outputs can expose the private seed. The public key can be distributed to verifiers.
|
|
399
444
|
|
|
400
445
|
### Version check
|
|
401
446
|
|
|
@@ -508,11 +553,11 @@ packages: [
|
|
|
508
553
|
deploy: {
|
|
509
554
|
git: [
|
|
510
555
|
// The release repository of each plugin, with its history.
|
|
511
|
-
{ repository:
|
|
556
|
+
{ repository: ( { package: pkg } ) => `https://github.com/me/${ pkg.path.split( '/' ).pop() }.git`, message: 'Release {path} {ref} from {revision}' },
|
|
512
557
|
// A mirror that only gets release tags, named v1.2.0.
|
|
513
558
|
{ repository: ( { package: pkg } ) => `https://gitlab.com/me/${ pkg.path }.git`, branches: false, tag: 'v{version}' },
|
|
514
559
|
],
|
|
515
|
-
svn: { slug:
|
|
560
|
+
svn: { slug: ( { package: pkg } ) => `me-${ pkg.path.split( '/' ).pop() }`, assetsDir: '.wordpress-org' },
|
|
516
561
|
},
|
|
517
562
|
},
|
|
518
563
|
// A package that isn't versioned, like a Docker image, only deploys branches.
|
|
@@ -530,12 +575,13 @@ packages: [
|
|
|
530
575
|
`deploy-svn` has the same modes. `--release-ref` can't be combined with the options of a single target, like `--git-url`, `--tag` or `--svn-tag`, which come from the config.
|
|
531
576
|
|
|
532
577
|
- **Git targets** take a `repository` and release both branches and tags by default. `branches` limits the branches, like `[ 'main' ]`, or turns them off with `false`, and `tag` is the tag template in the repository, `{version}` by default, or `false` to not release tags. A release tag is created on top of the default branch of the repository, and a branch is released to the same branch. `history: false` uses the source commit instead of the package history from `git subtree split`, and `message` sets the release commit message, `Release {ref} from {revision}` by default.
|
|
533
|
-
- **SVN targets** only deploy release tags: a stable release syncs `trunk/`, creates its tag and syncs the `assetsDir`, and a pre-release only creates its tag. `slug
|
|
578
|
+
- **SVN targets** only deploy release tags: a stable release syncs `trunk/`, creates its tag and syncs the `assetsDir`, and a pre-release only creates its tag. `slug`, the WordPress.org plugin slug, defaults to the name of the package directory, `url` to the WordPress.org plugin repository, and `message` sets the commit message. Credentials come from `SVN_USERNAME` and `SVN_PASSWORD`, or `--svn-username`.
|
|
534
579
|
- **Several targets**: `git` and `svn` each take one target or a list, like a release repository and a mirror on another host. Each target is a separate deploy.
|
|
535
580
|
- **Credentials**: a GitHub repository URL needs no token in it, since `GITHUB_TOKEN` is sent as an authorization header (see above). A callback can still add credentials for other hosts.
|
|
536
|
-
- **Templates and callbacks**: `repository`, `slug` and `message` are templates with `{name}
|
|
581
|
+
- **Templates and callbacks**: `repository`, `slug` and `message` are templates with `{name}` and `{path}`, like the `tagFormat`, and `{version}`, `{branch}`, `{ref}` (the tag or branch that the deploy writes in the target) and `{revision}` (the source commit), or callbacks that receive `{ package, version, branch, ref, revision }`, where `package` has the `name`, `directory`, `path` and `type`. A callback can name a target after the package directory, choose another host for some packages, read the environment, or return nothing to skip the target for that package or ref; an empty `message` uses the default one. Credentials in URLs are never printed or listed.
|
|
582
|
+
- **Results**: with `--json` or `--github-output`, the result lists each deploy in `deploys` with its `package`, `label`, `target` (the repository or WordPress.org plugin, with credentials redacted), `targetRef` (the tag or branch written there), `version` or `branch`, and its `result` or `error`. A remote target repository also has its `targetHost`, like `github.com`, and `targetPath`, like `me/my-plugin`, without credentials, for a workflow step that calls the API of the forge that hosts it, such as to create the release of the deployed tag. The targets of `--list` have the same `host` and `path`, and `parseRepositoryUrl()` reads them from an HTTP(S), SSH or scp-like URL. `wp-release` doesn't call forge APIs itself.
|
|
537
583
|
- **Packages without a version**: a package entry without `version`, like a single directory with a `glob` of its path, isn't released with changesets, so changes to it don't need one, and it only deploys branches, since it has no release tags.
|
|
538
|
-
- **Files**: `files.exclude` adds gitignore-style patterns relative to the package directory, and `files.distignore` sets the ignore file relative to it or turns it off with `false`. A `files` at the root of the config applies to every package group, for the rules that all packages share, and the `files` of a group add their patterns after it and can replace its ignore file. Without `files`, each package's `.distignore` applies, like for `zip`; there's no repository-wide `.distignore`, since each one is relative to its package. `
|
|
584
|
+
- **Files**: `files.exclude` adds gitignore-style patterns relative to the package directory, and `files.distignore` sets the ignore file relative to it or turns it off with `false`. A `files` at the root of the config applies to every package group, for the rules that all packages share, and the `files` of a group add their patterns after it and can replace its ignore file. Without `files`, each package's `.distignore` applies, like for `zip`; there's no repository-wide `.distignore`, since each one is relative to its package. `zip --package <name>` zips a package with the same files, `getPackageFiles()` returns the directory and the file selection of any configured package, and `getDeployPackages()` lists the packages with deploy targets and their file selection.
|
|
539
585
|
|
|
540
586
|
A deploy releases the files of the working tree, so build the packages first. One job that builds once and runs `deploy-git --release-ref "$GITHUB_REF"` is the simplest setup, also for a branch that releases every package. A matrix with a job for each package from `--list` would build every package in every job, so build once and pass the built tree to the jobs as an artifact if you need one, for example for separate credentials.
|
|
541
587
|
|
|
@@ -706,10 +752,21 @@ npm run lint --workspace @wpelevator/wp-release
|
|
|
706
752
|
- `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.
|
|
707
753
|
- `lint` runs ESLint and a `tsc` type check.
|
|
708
754
|
|
|
709
|
-
The `wpelevator-source` export condition points at `src/index.ts`. The tests and
|
|
755
|
+
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
|
+
|
|
757
|
+
`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.
|
|
710
758
|
|
|
711
759
|
### Architecture
|
|
712
760
|
|
|
761
|
+
wp-release has two layers, so that it fits any repository layout:
|
|
762
|
+
|
|
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>` and `deploy-svn <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.
|
|
765
|
+
|
|
766
|
+
Nothing assumes a directory structure or naming convention that the config doesn't set.
|
|
767
|
+
|
|
768
|
+
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
|
+
|
|
713
770
|
The package uses ES modules as its unit of encapsulation, like most Node.js CLI tools: a module exports a few functions and keeps its helpers private, so there's no class to wrap them in. A class is used only when it holds state across steps or binds dependencies, so that callers and tests can replace them:
|
|
714
771
|
|
|
715
772
|
- **Workflows with steps** are classes, so that their steps share state and dependencies on the instance: `SvnDeploy` shares the temporary working copy across `prepare()`, `checkout()`, `sync()` and `commit()`, `GitDeploy` binds the options and the git runner to its steps, and `ChangesetRelease` binds the repository and the runners to `add()`, `version()` and `tag()`.
|
|
@@ -732,7 +789,8 @@ The changeset release modules, from the entry point down:
|
|
|
732
789
|
| `changelog.ts` | Changelog entries, and the `CHANGELOG.md` and readme changelog sections. |
|
|
733
790
|
| `release-tags.ts` | Choosing the version and previous tag of a release, and building its release notes. |
|
|
734
791
|
| `tag-format.ts` | Formatting tag names from the tag format. |
|
|
735
|
-
| `package-deploy.ts` | Resolving the configured deploys of a ref: the released packages, their targets and file selections, for `deploy-git` and `deploy-svn`. |
|
|
792
|
+
| `package-deploy.ts` | Resolving the configured deploys of a ref: the released packages, their targets and file selections, for `deploy-git` and `deploy-svn`, and the file selection of a package for `zip --package`. |
|
|
793
|
+
| `package-zip.ts` | `zipPackage()`, the ZIP and checksums manifest of the selected files of a package, for `zip` and the snapshot builds of a monorepo. |
|
|
736
794
|
| `release-repository.ts` | `ReleaseRepository`, the git commands of a release: tags, commits and changed files. |
|
|
737
795
|
| `file-journal.ts` | `FileJournal`, which restores every file a failed `changeset-version` run changed. |
|
|
738
796
|
| `versions.ts` | Version checks and SemVer precedence. |
|
|
@@ -62,8 +62,6 @@ export interface ChangesetPackageGroup {
|
|
|
62
62
|
glob: string;
|
|
63
63
|
/** Package paths excluded from this group, relative to root. */
|
|
64
64
|
exclude?: string[];
|
|
65
|
-
/** Optional type used by the {type} tag template. */
|
|
66
|
-
tagType?: string;
|
|
67
65
|
/** Manifest field used as the changeset name; defaults to `path`. */
|
|
68
66
|
idFrom?: PackageIdSource;
|
|
69
67
|
/**
|
|
@@ -107,7 +105,7 @@ export interface DeployPackage {
|
|
|
107
105
|
type: ChangesetPackage['type'];
|
|
108
106
|
}
|
|
109
107
|
/**
|
|
110
|
-
* A deploy setting: a template with {name}, {
|
|
108
|
+
* A deploy setting: a template with {name}, {path},
|
|
111
109
|
* {version}, {branch}, {ref} and {revision}, or a callback that receives the
|
|
112
110
|
* package and its release. An empty repository or slug skips the target, and
|
|
113
111
|
* an empty message uses the default one.
|
|
@@ -115,7 +113,7 @@ export interface DeployPackage {
|
|
|
115
113
|
export type DeployTemplate = string | ((input: DeployTargetInput) => string | undefined | Promise<string | undefined>);
|
|
116
114
|
/** A Git repository that `deploy-git` releases a package to. */
|
|
117
115
|
export interface GitDeployTarget {
|
|
118
|
-
/** Repository URL, like `https://github.com/me/{
|
|
116
|
+
/** Repository URL, like `https://github.com/me/{path}.git`. */
|
|
119
117
|
repository: DeployTemplate;
|
|
120
118
|
/** Branches released to the same branch of the repository: all with true (the default), none with false, or the listed ones. */
|
|
121
119
|
branches?: boolean | string[];
|
|
@@ -128,7 +126,7 @@ export interface GitDeployTarget {
|
|
|
128
126
|
}
|
|
129
127
|
/** A WordPress.org plugin that `deploy-svn` releases a package to. */
|
|
130
128
|
export interface SvnDeployTarget {
|
|
131
|
-
/**
|
|
129
|
+
/** WordPress.org plugin slug, defaults to the name of the package directory. */
|
|
132
130
|
slug?: DeployTemplate;
|
|
133
131
|
/** SVN repository URL with {slug}, defaults to WordPress.org. */
|
|
134
132
|
url?: string;
|
|
@@ -221,11 +219,13 @@ export interface DependentsConfig {
|
|
|
221
219
|
export interface ChangesetConfig {
|
|
222
220
|
/** Branch receiving ordinary changes. */
|
|
223
221
|
baseBranch?: string;
|
|
222
|
+
/** Git remote that tags are pushed to and the base branch is compared on, origin by default. */
|
|
223
|
+
remote?: string;
|
|
224
224
|
/** Directory containing Markdown changesets, relative to the repository root. */
|
|
225
225
|
changesets?: string;
|
|
226
226
|
/** Package groups to discover and release. */
|
|
227
227
|
packages: ChangesetPackageGroup[];
|
|
228
|
-
/** Git tag template supporting {name}, {
|
|
228
|
+
/** Git tag template supporting {name}, {path} and {version}. */
|
|
229
229
|
tagFormat?: string;
|
|
230
230
|
/** Directory against which package paths in tag formats are calculated. */
|
|
231
231
|
root?: string;
|
|
@@ -358,6 +358,8 @@ export declare function defineConfig(config: ChangesetConfig): ChangesetConfig;
|
|
|
358
358
|
export interface LoadChangesetConfigInput {
|
|
359
359
|
cwd?: string;
|
|
360
360
|
configFile?: string;
|
|
361
|
+
/** Tag format that replaces the tagFormat of the config, like from --tag-format. */
|
|
362
|
+
tagFormat?: string;
|
|
361
363
|
}
|
|
362
364
|
/**
|
|
363
365
|
* Load and validate the repository's JavaScript wp-release configuration.
|
package/dist/changeset-config.js
CHANGED
|
@@ -138,7 +138,12 @@ export async function loadChangesetConfig(input = {}) {
|
|
|
138
138
|
? loaded.default
|
|
139
139
|
: undefined;
|
|
140
140
|
validateChangesetConfig(config, configFile);
|
|
141
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
config: input.tagFormat
|
|
143
|
+
? { ...config, tagFormat: input.tagFormat }
|
|
144
|
+
: config,
|
|
145
|
+
file: configFile,
|
|
146
|
+
};
|
|
142
147
|
}
|
|
143
148
|
async function findConfigFile(cwd) {
|
|
144
149
|
for (const fileName of CONFIG_FILES) {
|
|
@@ -171,6 +176,7 @@ function validateChangesetConfig(config, configFile) {
|
|
|
171
176
|
}
|
|
172
177
|
for (const [key, value] of Object.entries({
|
|
173
178
|
baseBranch: candidate.baseBranch,
|
|
179
|
+
remote: candidate.remote,
|
|
174
180
|
changesets: candidate.changesets,
|
|
175
181
|
changelogInsertAfter: candidate.changelogInsertAfter,
|
|
176
182
|
root: candidate.root,
|
|
@@ -268,10 +274,6 @@ function validateChangesetConfig(config, configFile) {
|
|
|
268
274
|
if ('string' !== typeof group.glob || !group.glob.trim()) {
|
|
269
275
|
throw new ValidationError(`wp-release config ${configFile} packages[${index}].glob must be a non-empty string.`);
|
|
270
276
|
}
|
|
271
|
-
if ('string' !== typeof group.tagType &&
|
|
272
|
-
undefined !== group.tagType) {
|
|
273
|
-
throw new ValidationError(`wp-release config ${configFile} packages[${index}].tagType must be a string.`);
|
|
274
|
-
}
|
|
275
277
|
if (undefined !== group.exclude &&
|
|
276
278
|
(!Array.isArray(group.exclude) ||
|
|
277
279
|
group.exclude.some((item) => 'string' !== typeof item))) {
|
|
@@ -9,8 +9,8 @@ export interface ConfiguredPackage extends ChangesetPackage {
|
|
|
9
9
|
versionConfig: PackageVersionConfig;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* A discovered package
|
|
13
|
-
* version configuration.
|
|
12
|
+
* A discovered package with the configuration of its group, without reading
|
|
13
|
+
* its version, which may have no version or deploy configuration.
|
|
14
14
|
*/
|
|
15
15
|
export interface ConfiguredDeployPackage extends DeployPackage {
|
|
16
16
|
group: ChangesetPackageGroup;
|
|
@@ -32,6 +32,16 @@ export interface ChangedPackages {
|
|
|
32
32
|
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
33
33
|
*/
|
|
34
34
|
export declare function discoverPackages(repository: ReleaseRepository, config: ChangesetConfig): Promise<ConfiguredPackage[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Find the packages of every package group, with or without a version or
|
|
37
|
+
* deploy configuration, without reading their versions.
|
|
38
|
+
*
|
|
39
|
+
* @param repository Repository the packages are in.
|
|
40
|
+
* @param config Repository release configuration.
|
|
41
|
+
* @return Packages in the configured order.
|
|
42
|
+
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
43
|
+
*/
|
|
44
|
+
export declare function discoverConfiguredPackages(repository: ReleaseRepository, config: ChangesetConfig): Promise<ConfiguredDeployPackage[]>;
|
|
35
45
|
/**
|
|
36
46
|
* Find the packages of the package groups with deploy targets, including the
|
|
37
47
|
* groups without a version configuration, without reading their versions.
|
|
@@ -22,12 +22,24 @@ export async function discoverPackages(repository, config) {
|
|
|
22
22
|
const versionConfig = pkg.group.version;
|
|
23
23
|
packages.push({
|
|
24
24
|
...pkg,
|
|
25
|
-
currentVersion: await readVersion(repository, pkg.directory, versionConfig.from, pkg
|
|
25
|
+
currentVersion: await readVersion(repository, pkg.directory, versionConfig.from, pkg),
|
|
26
26
|
versionConfig,
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
return packages;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Find the packages of every package group, with or without a version or
|
|
33
|
+
* deploy configuration, without reading their versions.
|
|
34
|
+
*
|
|
35
|
+
* @param repository Repository the packages are in.
|
|
36
|
+
* @param config Repository release configuration.
|
|
37
|
+
* @return Packages in the configured order.
|
|
38
|
+
* @throws {ValidationError} For a glob without matches or a duplicate package name.
|
|
39
|
+
*/
|
|
40
|
+
export async function discoverConfiguredPackages(repository, config) {
|
|
41
|
+
return findGroupPackages(repository, config, config.packages);
|
|
42
|
+
}
|
|
31
43
|
/**
|
|
32
44
|
* Find the packages of the package groups with deploy targets, including the
|
|
33
45
|
* groups without a version configuration, without reading their versions.
|
|
@@ -4,6 +4,8 @@ import { ReleaseRepository } from './release-repository.ts';
|
|
|
4
4
|
export interface ChangesetRepository {
|
|
5
5
|
cwd: string;
|
|
6
6
|
configFile?: string;
|
|
7
|
+
/** Tag format that replaces the tagFormat of the config. */
|
|
8
|
+
tagFormat?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface ChangesetReleaseOptions extends ChangesetRepository {
|
|
9
11
|
/** Git runner, spawning git by default. */
|
|
@@ -71,7 +73,7 @@ export interface ChangesetTagResult {
|
|
|
71
73
|
}>;
|
|
72
74
|
}
|
|
73
75
|
export interface ChangesetStatusOptions {
|
|
74
|
-
/** Base ref to diff against,
|
|
76
|
+
/** Base ref to diff against, <remote>/<baseBranch> by default. */
|
|
75
77
|
since?: string;
|
|
76
78
|
/** Whether to include uncommitted and untracked files, like before adding a changeset. */
|
|
77
79
|
uncommitted?: boolean;
|
|
@@ -97,6 +99,7 @@ export interface ChangesetStatusResult {
|
|
|
97
99
|
export declare class ChangesetRelease {
|
|
98
100
|
readonly cwd: string;
|
|
99
101
|
readonly configFile: string | undefined;
|
|
102
|
+
readonly tagFormat: string | undefined;
|
|
100
103
|
readonly repository: ReleaseRepository;
|
|
101
104
|
/**
|
|
102
105
|
* @param options Repository root, config file and git runner.
|
|
@@ -134,7 +137,7 @@ export declare class ChangesetRelease {
|
|
|
134
137
|
*/
|
|
135
138
|
version(options?: ChangesetVersionOptions): Promise<VersionChangesetsResult>;
|
|
136
139
|
/**
|
|
137
|
-
* Create missing release tags, optionally pushing them to the
|
|
140
|
+
* Create missing release tags, optionally pushing them to the configured
|
|
138
141
|
* remote. Each tag comes with the title and notes of its release, for a
|
|
139
142
|
* workflow step to create the GitHub Release.
|
|
140
143
|
*
|
|
@@ -9,7 +9,7 @@ import { ValidationError } from "./errors.js";
|
|
|
9
9
|
import { FileJournal } from "./file-journal.js";
|
|
10
10
|
import { getPreviousTag, getReleaseNotes, getTagVersion, selectTagPackages, } from "./release-tags.js";
|
|
11
11
|
import { ReleaseRepository } from "./release-repository.js";
|
|
12
|
-
import { formatTag } from "./tag-format.js";
|
|
12
|
+
import { DEFAULT_TAG_FORMAT, formatTag } from "./tag-format.js";
|
|
13
13
|
import { verifyVersionTargets, writeVersion } from "./version-files.js";
|
|
14
14
|
import { getPrereleaseVersion, getSnapshotVersion, isPrerelease, } from "./versions.js";
|
|
15
15
|
const MISSING_CONFIG = 'No wp-release config found. Add wp-release.config.js or pass --config <path>.';
|
|
@@ -24,6 +24,7 @@ const MISSING_CONFIG = 'No wp-release config found. Add wp-release.config.js or
|
|
|
24
24
|
export class ChangesetRelease {
|
|
25
25
|
cwd;
|
|
26
26
|
configFile;
|
|
27
|
+
tagFormat;
|
|
27
28
|
repository;
|
|
28
29
|
/**
|
|
29
30
|
* @param options Repository root, config file and git runner.
|
|
@@ -31,6 +32,7 @@ export class ChangesetRelease {
|
|
|
31
32
|
constructor(options) {
|
|
32
33
|
this.cwd = options.cwd;
|
|
33
34
|
this.configFile = options.configFile;
|
|
35
|
+
this.tagFormat = options.tagFormat;
|
|
34
36
|
this.repository = new ReleaseRepository({
|
|
35
37
|
cwd: options.cwd,
|
|
36
38
|
runner: options.git,
|
|
@@ -144,7 +146,7 @@ export class ChangesetRelease {
|
|
|
144
146
|
};
|
|
145
147
|
}
|
|
146
148
|
/**
|
|
147
|
-
* Create missing release tags, optionally pushing them to the
|
|
149
|
+
* Create missing release tags, optionally pushing them to the configured
|
|
148
150
|
* remote. Each tag comes with the title and notes of its release, for a
|
|
149
151
|
* workflow step to create the GitHub Release.
|
|
150
152
|
*
|
|
@@ -163,7 +165,7 @@ export class ChangesetRelease {
|
|
|
163
165
|
: selected;
|
|
164
166
|
const results = [];
|
|
165
167
|
const dryRun = options.dryRun || options.untagged;
|
|
166
|
-
const tagFormat = context.config.tagFormat ??
|
|
168
|
+
const tagFormat = context.config.tagFormat ?? DEFAULT_TAG_FORMAT;
|
|
167
169
|
for (const pkg of packages) {
|
|
168
170
|
const version = await getTagVersion(pkg, options.version);
|
|
169
171
|
if (undefined !== options.version &&
|
|
@@ -196,7 +198,7 @@ export class ChangesetRelease {
|
|
|
196
198
|
// tags when more than three are pushed at once.
|
|
197
199
|
if (options.push && !dryRun) {
|
|
198
200
|
for (const item of results.filter((result) => result.created)) {
|
|
199
|
-
await this.repository.pushTag(item.tag);
|
|
201
|
+
await this.repository.pushTag(item.tag, context.config.remote ?? 'origin');
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
return {
|
|
@@ -216,7 +218,9 @@ export class ChangesetRelease {
|
|
|
216
218
|
const { config, packages, plan } = await this.requireContext();
|
|
217
219
|
const { changesets, releases } = plan;
|
|
218
220
|
const since = options.since ??
|
|
219
|
-
(config.baseBranch
|
|
221
|
+
(config.baseBranch
|
|
222
|
+
? `${config.remote ?? 'origin'}/${config.baseBranch}`
|
|
223
|
+
: undefined);
|
|
220
224
|
const changed = since
|
|
221
225
|
? await getChangedPackages(this.repository, packages, since, options.uncommitted)
|
|
222
226
|
: { packages: [], filesByPackage: {} };
|
|
@@ -266,7 +270,7 @@ export class ChangesetRelease {
|
|
|
266
270
|
* @throws {ValidationError} For an invalid pre-release or an existing version.
|
|
267
271
|
*/
|
|
268
272
|
async toPrereleases(context, releases, prerelease) {
|
|
269
|
-
const tagFormat = context.config.tagFormat ??
|
|
273
|
+
const tagFormat = context.config.tagFormat ?? DEFAULT_TAG_FORMAT;
|
|
270
274
|
return Promise.all(releases.map(async (release) => {
|
|
271
275
|
const configured = context.packages.find((pkg) => pkg.name === release.package.name);
|
|
272
276
|
const versions = configured
|
|
@@ -364,6 +368,7 @@ export class ChangesetRelease {
|
|
|
364
368
|
const loaded = await loadChangesetConfig({
|
|
365
369
|
cwd: this.cwd,
|
|
366
370
|
...(this.configFile ? { configFile: this.configFile } : {}),
|
|
371
|
+
...(this.tagFormat ? { tagFormat: this.tagFormat } : {}),
|
|
367
372
|
});
|
|
368
373
|
if (!loaded) {
|
|
369
374
|
return undefined;
|
|
@@ -429,6 +434,7 @@ export class ChangesetRelease {
|
|
|
429
434
|
const loaded = await loadChangesetConfig({
|
|
430
435
|
cwd: this.cwd,
|
|
431
436
|
...(this.configFile ? { configFile: this.configFile } : {}),
|
|
437
|
+
...(this.tagFormat ? { tagFormat: this.tagFormat } : {}),
|
|
432
438
|
});
|
|
433
439
|
if (!loaded) {
|
|
434
440
|
throw new ValidationError(MISSING_CONFIG);
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,9 +3,8 @@ import type { SignResult } from './commands/sign.ts';
|
|
|
3
3
|
import type { KeygenResult } from './commands/keygen.ts';
|
|
4
4
|
import type { ZipResult } from './commands/zip.ts';
|
|
5
5
|
import type { ChangesetVersionResult } from './commands/changeset-version.ts';
|
|
6
|
-
import { Logger } from './logger.ts';
|
|
7
6
|
import type { OutputStream } from './logger.ts';
|
|
8
|
-
import type { Command, CommandOutcome } from './commands/command.ts';
|
|
7
|
+
import type { CommandContext, Command, CommandOutcome } from './commands/command.ts';
|
|
9
8
|
/** Input stream that interactive prompts read from in a terminal. */
|
|
10
9
|
export type InputStream = NodeJS.ReadableStream & {
|
|
11
10
|
isTTY?: boolean;
|
|
@@ -44,10 +43,23 @@ export declare function getCommandHelp(command: Command): string;
|
|
|
44
43
|
* @return Command definition.
|
|
45
44
|
*/
|
|
46
45
|
export declare function findCommand(args: string[]): Command | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Format the result of a command as GitHub Actions step outputs: the whole
|
|
48
|
+
* result as JSON in "result", and each top-level string, number or boolean
|
|
49
|
+
* as its own output, like "zipPath", so that a workflow reads it without
|
|
50
|
+
* fromJSON(). Lists and objects are only in "result". Every value but the
|
|
51
|
+
* compact JSON, which has no line breaks, uses a random delimiter, so that a
|
|
52
|
+
* value with line breaks can't set other outputs.
|
|
53
|
+
*
|
|
54
|
+
* @param result Command result.
|
|
55
|
+
* @return Lines for the GITHUB_OUTPUT file.
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatGitHubOutputs(result: unknown): string;
|
|
47
58
|
/**
|
|
48
59
|
* Results of the commands that can be run with execute(), by command name.
|
|
49
60
|
*/
|
|
50
61
|
export interface CommandResults {
|
|
62
|
+
'deploy-svn': import('./commands/deploy-svn.ts').DeploySvnResult;
|
|
51
63
|
zip: ZipResult;
|
|
52
64
|
checksums: ChecksumsResult;
|
|
53
65
|
sign: SignResult;
|
|
@@ -68,14 +80,11 @@ export interface CommandResults {
|
|
|
68
80
|
* @param context.cwd Working directory that relative paths resolve against.
|
|
69
81
|
* @param context.configFile Explicit config file path, when supplied by a wrapper.
|
|
70
82
|
* @param context.logger Logger for the command output.
|
|
83
|
+
* @param context.svnRunner Optional SVN command runner supplied by a wrapper.
|
|
71
84
|
* @return Exit code and result of the command.
|
|
72
85
|
* @throws {Error} If the command is unknown, the arguments are invalid or the command fails.
|
|
73
86
|
*/
|
|
74
|
-
export declare function execute<Name extends keyof CommandResults>(name: Name, args: string[], context:
|
|
75
|
-
cwd: string;
|
|
76
|
-
logger: Logger;
|
|
77
|
-
configFile?: string;
|
|
78
|
-
}): Promise<CommandOutcome<CommandResults[Name]>>;
|
|
87
|
+
export declare function execute<Name extends keyof CommandResults>(name: Name, args: string[], context: Pick<CommandContext, 'cwd' | 'logger' | 'configFile' | 'svnRunner'>): Promise<CommandOutcome<CommandResults[Name]>>;
|
|
79
88
|
/**
|
|
80
89
|
* Run the CLI.
|
|
81
90
|
*
|