@wpelevator/wp-release 0.7.0 → 0.9.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 +19 -7
- package/dist/changeset-config.d.ts +9 -0
- package/dist/changeset-config.js +4 -0
- package/dist/changeset-release.d.ts +9 -5
- package/dist/changeset-release.js +71 -5
- package/dist/changesets.d.ts +12 -0
- package/dist/changesets.js +10 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2 -0
- package/dist/commands/args.d.ts +1 -0
- package/dist/commands/args.js +2 -0
- package/dist/commands/changeset-version.js +1 -2
- package/dist/commands/checksums.js +2 -0
- package/dist/commands/deploy-git.js +1 -0
- package/dist/commands/deploy-svn.js +1 -0
- package/dist/commands/keygen.d.ts +17 -0
- package/dist/commands/keygen.js +23 -0
- package/dist/commands/zip.js +2 -0
- package/dist/deploy-options.d.ts +2 -0
- package/dist/deploy-options.js +1 -0
- package/dist/file-selection.d.ts +6 -0
- package/dist/file-selection.js +101 -5
- package/dist/git-deploy.d.ts +2 -0
- package/dist/git-deploy.js +1 -0
- package/dist/github.d.ts +24 -0
- package/dist/github.js +30 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +3 -2
- package/dist/signing.d.ts +9 -0
- package/dist/signing.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Option names follow the existing community tools (10up WordPress.org actions, `w
|
|
|
14
14
|
npm install --save-dev @wpelevator/wp-release
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client.
|
|
17
|
+
Requires Node.js 22.12 or later. `deploy-svn` also needs the `svn` command line client; the optional `--exclude-gitattributes` flag needs Git.
|
|
18
18
|
|
|
19
19
|
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
20
|
|
|
@@ -29,6 +29,7 @@ npx wp-release --help
|
|
|
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
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 for signing and verification. |
|
|
32
33
|
| `wp-release verify [<source-dir>] --checksums <file> [options]` | Check a plugin or theme directory, or a ZIP, against a checksums manifest. |
|
|
33
34
|
| `wp-release version-check [<source-dir>] [options]` | Check that the `Version` header, readme `Stable tag`, PHP constants and git tag agree. |
|
|
34
35
|
| `wp-release deploy-svn [<source-dir>] [options]` | Deploy a plugin to the WordPress.org plugin SVN repository. |
|
|
@@ -131,6 +132,16 @@ Add release notes in a feature PR with `npx wp-release changeset-add --package @
|
|
|
131
132
|
|
|
132
133
|
On the release branch, preview with `changeset-version --dry-run`, then run `changeset-version` to update version targets and changelogs. By default, new Markdown entries go before the first version heading; `changelogInsertAfter` sets a literal insertion marker for `CHANGELOG.md` and, with `readmeChangelog`, the plugin readme. Put the marker in both files; a missing marker is an error. Without a marker, plugin readmes use the `== Changelog ==` heading. Failed updates are rolled back for safe retries.
|
|
133
134
|
|
|
135
|
+
Configure `changelogEntry( { package, changeset, commit } )` to format the changelog entry of each changeset, where `commit` is the `{ hash, shortHash, subject }` of the commit that added the changeset file, if it's committed. The formatted entries go into `CHANGELOG.md`, the `changelog` of each release in the `changeset-version` result, and so the release notes; plugin readmes keep the plain summaries. `getGitHubPullRequest( { cwd, commit } )` finds the `{ number, url, author }` of the pull request that merged a commit with the authenticated `gh` CLI, for example to credit it:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
changelogEntry: async ( { changeset, commit } ) => {
|
|
139
|
+
const pullRequest = commit && ( await getGitHubPullRequest( { cwd: import.meta.dirname, commit: commit.hash } ) );
|
|
140
|
+
|
|
141
|
+
return pullRequest ? `${ changeset.summary } (#${ pullRequest.number } by @${ pullRequest.author })` : changeset.summary;
|
|
142
|
+
},
|
|
143
|
+
```
|
|
144
|
+
|
|
134
145
|
To release packages separately, for example with a release pull request for each, pass `--package <name>`, repeatable, to version only those packages. A changeset that also names other packages keeps only their entries and its summary, so it still applies to their releases with the same bump. Changesets without packages are left for a run without `--package`.
|
|
135
146
|
|
|
136
147
|
The `changeset-version` result lists each release with its `package`, `bump`, `currentVersion`, `nextVersion` and `changelog` entries, a Markdown list like the one added to `CHANGELOG.md`. Use it to name and describe the release pull request in a repository script, or call `versionChangesets()` from the library and format the entries of each release with `formatChangelogEntries( release.changesets )`.
|
|
@@ -178,15 +189,16 @@ CLI JSON results use camelCase keys like `zipPath`, and GitHub Action outputs us
|
|
|
178
189
|
|
|
179
190
|
### File selection
|
|
180
191
|
|
|
181
|
-
`zip`, `checksums` and `deploy-
|
|
192
|
+
`zip`, `checksums`, `deploy-svn` and `deploy-git` select files the same way:
|
|
182
193
|
|
|
183
194
|
| Option | Description |
|
|
184
195
|
|---|---|
|
|
185
196
|
| `--distignore <file>` | Path to the ignore file. Defaults to `.distignore` in the source directory when present; an explicitly passed file must exist. |
|
|
186
197
|
| `--no-distignore` | Don't read any ignore file. |
|
|
187
198
|
| `--exclude <patterns>` | Extra gitignore-style patterns, comma-separated or repeated, like `--exclude build/,assets/*,images/**/*`. They're applied after the ignore file, so a `!` pattern can also re-include a file. |
|
|
199
|
+
| `--exclude-gitattributes` | Also exclude paths marked `export-ignore` by Git attributes. This is opt-in and requires the source directory to be inside a Git repository. |
|
|
188
200
|
|
|
189
|
-
|
|
201
|
+
`.distignore` and `--exclude` use full gitignore syntax, including `!` negation. `--exclude-gitattributes` asks Git to evaluate `export-ignore` attributes, including nested `.gitattributes` files, then adds those paths to the exclusions. It is off by default, so releases don't unexpectedly depend on Git or change selection when a repository adds attributes. Git must be installed and the source directory must be inside a working tree; the flag is ignored by `deploy-svn --from-zip`, since the ZIP is already filtered. Version control metadata (`.git`, `.svn` and `.hg`, at any depth, including the `.git` files of worktrees and submodules) is always left out, also without an ignore file and when deploying from a ZIP, and no `!` pattern can re-include it. This keeps a repository's history out of ZIPs and deploys when the source directory is the repository root. The comma-separated `--exclude` list works like `--exclude` in `wp i18n make-pot`. Selected symbolic links are an error, since ZIP archives and WordPress.org SVN don't support them.
|
|
190
202
|
|
|
191
203
|
### Plugin and theme headers
|
|
192
204
|
|
|
@@ -294,7 +306,7 @@ WP_RELEASE_SIGNING_KEY=… wp-release sign my-plugin.1.2.0.zip my-plugin.1.2.0.c
|
|
|
294
306
|
- `--verbose` prints the public key of the signing key, which `--json` also returns as `publicKey`. Pass it to `verify --public-key` and to the plugin that checks the signatures.
|
|
295
307
|
- An existing `.sig` file isn't overwritten without `--force`.
|
|
296
308
|
|
|
297
|
-
|
|
309
|
+
Generate a key pair with `wp-release keygen`. It prints the private seed once as `WP_RELEASE_SIGNING_KEY=…` and the matching 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.
|
|
298
310
|
|
|
299
311
|
### Version check
|
|
300
312
|
|
|
@@ -528,7 +540,7 @@ const { valid, changed, missing, extra } = await verifyChecksums( {
|
|
|
528
540
|
} );
|
|
529
541
|
```
|
|
530
542
|
|
|
531
|
-
`loadFileRules()` reads the ignore file and `--exclude` patterns into a gitignore matcher 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`.
|
|
543
|
+
`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`.
|
|
532
544
|
|
|
533
545
|
## Development
|
|
534
546
|
|
|
@@ -550,9 +562,9 @@ The `wpelevator-source` export condition points at `src/index.ts`. The tests and
|
|
|
550
562
|
|
|
551
563
|
| Tool | What it does | How `wp-release` differs |
|
|
552
564
|
|---|---|---|
|
|
553
|
-
| [10up WordPress.org plugin deploy](https://github.com/10up/action-wordpress-plugin-deploy) and [readme/asset update](https://github.com/10up/action-wordpress-plugin-asset-update) actions | Two separate GitHub Actions configured through env vars. A deploy always syncs the build to `trunk/` and copies it to the tag; readme and asset updates need the second action. Assets come from `.wordpress-org` by default, and files are selected with `.distignore` or `.gitattributes`. Files are synced with `rsync`. | One `deploy-svn` command with independent write flags, so a release, a readme-only update and an assets-only update are the same command with different flags. Runs locally and in any CI, not only GitHub Actions, and syncs in Node.js without `rsync`. Checks that the `Version` header, readme `Stable tag` and SVN tag agree before writing, which the 10up actions don't. Assets are deployed only when `--assets-dir` is passed, instead of always syncing `.wordpress-org` when it exists, so a plugin's own `assets/` folder is never uploaded by accident. Adds `--exclude` for patterns without an ignore file and `--from-zip` to deploy the exact files of a ZIP. |
|
|
565
|
+
| [10up WordPress.org plugin deploy](https://github.com/10up/action-wordpress-plugin-deploy) and [readme/asset update](https://github.com/10up/action-wordpress-plugin-asset-update) actions | Two separate GitHub Actions configured through env vars. A deploy always syncs the build to `trunk/` and copies it to the tag; readme and asset updates need the second action. Assets come from `.wordpress-org` by default, and files are selected with `.distignore` or `.gitattributes`. Files are synced with `rsync`. | One `deploy-svn` command with independent write flags, so a release, a readme-only update and an assets-only update are the same command with different flags. Runs locally and in any CI, not only GitHub Actions, and syncs in Node.js without `rsync`. Checks that the `Version` header, readme `Stable tag` and SVN tag agree before writing, which the 10up actions don't. Assets are deployed only when `--assets-dir` is passed, instead of always syncing `.wordpress-org` when it exists, so a plugin's own `assets/` folder is never uploaded by accident. Adds `--exclude` for patterns without an ignore file, opt-in Git `export-ignore` support and `--from-zip` to deploy the exact files of a ZIP. |
|
|
554
566
|
| [`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. |
|
|
555
567
|
| [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. |
|
|
556
568
|
| [`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. |
|
|
557
569
|
| [`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. |
|
|
558
|
-
| [`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`
|
|
570
|
+
| [`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. |
|
|
@@ -89,6 +89,8 @@ export interface ChangesetConfig {
|
|
|
89
89
|
nextVersion?: (input: NextVersionInput) => string;
|
|
90
90
|
/** Format the release notes of a tag, for example to add links. */
|
|
91
91
|
releaseNotes?: (input: ReleaseNotesInput) => string | Promise<string>;
|
|
92
|
+
/** Format the CHANGELOG.md entry of a changeset, for example to credit its pull request. */
|
|
93
|
+
changelogEntry?: (input: ChangelogEntryInput) => string | Promise<string>;
|
|
92
94
|
}
|
|
93
95
|
/** Inputs supplied to a custom next-version strategy. */
|
|
94
96
|
export interface NextVersionInput {
|
|
@@ -117,6 +119,13 @@ export interface ReleaseNotesInput {
|
|
|
117
119
|
/** The default release notes: the changelog section, or the commit list without one. */
|
|
118
120
|
notes: string;
|
|
119
121
|
}
|
|
122
|
+
/** Inputs supplied to a custom changelog entry formatter. */
|
|
123
|
+
export interface ChangelogEntryInput {
|
|
124
|
+
package: ChangesetPackage;
|
|
125
|
+
changeset: Changeset;
|
|
126
|
+
/** Commit that added the changeset file, unless it isn't committed yet. */
|
|
127
|
+
commit?: ReleaseCommit;
|
|
128
|
+
}
|
|
120
129
|
export type WpReleaseConfig = ChangesetConfig;
|
|
121
130
|
export interface PackageJsonVersionOptions {
|
|
122
131
|
file?: string;
|
package/dist/changeset-config.js
CHANGED
|
@@ -194,6 +194,10 @@ function validateChangesetConfig(config, configFile) {
|
|
|
194
194
|
'function' !== typeof candidate.nextVersion) {
|
|
195
195
|
throw new ValidationError(`wp-release config ${configFile}.nextVersion must be a function.`);
|
|
196
196
|
}
|
|
197
|
+
if (undefined !== candidate.changelogEntry &&
|
|
198
|
+
'function' !== typeof candidate.changelogEntry) {
|
|
199
|
+
throw new ValidationError(`wp-release config ${configFile}.changelogEntry must be a function.`);
|
|
200
|
+
}
|
|
197
201
|
if (undefined !== candidate.releaseNotes &&
|
|
198
202
|
'function' !== typeof candidate.releaseNotes) {
|
|
199
203
|
throw new ValidationError(`wp-release config ${configFile}.releaseNotes must be a function.`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Changeset, ChangesetPackage, ChangesetReleasePlan, PackageRelease } from './changesets.ts';
|
|
1
|
+
import type { Changeset, ChangesetPackage, ChangesetReleasePlan, PackageRelease, VersionedRelease } from './changesets.ts';
|
|
2
2
|
export interface ChangesetRepository {
|
|
3
3
|
cwd: string;
|
|
4
4
|
configFile?: string;
|
|
@@ -9,6 +9,13 @@ export interface ChangesetVersionInput extends ChangesetRepository {
|
|
|
9
9
|
/** Version only these packages, leaving the other changesets pending. */
|
|
10
10
|
packages?: string[];
|
|
11
11
|
}
|
|
12
|
+
/** Versioned releases, the applied changesets and whether files were changed. */
|
|
13
|
+
export interface VersionChangesetsResult {
|
|
14
|
+
changesets: Changeset[];
|
|
15
|
+
releases: VersionedRelease[];
|
|
16
|
+
date: string;
|
|
17
|
+
changed: boolean;
|
|
18
|
+
}
|
|
12
19
|
export interface ChangesetTagInput extends ChangesetRepository {
|
|
13
20
|
dryRun?: boolean;
|
|
14
21
|
untagged?: boolean;
|
|
@@ -64,10 +71,7 @@ export declare function addChangeset(input: ChangesetRepository & {
|
|
|
64
71
|
* @param input Repository and versioning options.
|
|
65
72
|
* @return Planned releases and whether files were changed.
|
|
66
73
|
*/
|
|
67
|
-
export declare function versionChangesets(input: ChangesetVersionInput): Promise<
|
|
68
|
-
date: string;
|
|
69
|
-
changed: boolean;
|
|
70
|
-
}>;
|
|
74
|
+
export declare function versionChangesets(input: ChangesetVersionInput): Promise<VersionChangesetsResult>;
|
|
71
75
|
/**
|
|
72
76
|
* Create missing release tags, optionally pushing them to the configured remote.
|
|
73
77
|
*
|
|
@@ -6,7 +6,7 @@ import { promisify } from 'node:util';
|
|
|
6
6
|
import { loadChangesetConfig } from "./changeset-config.js";
|
|
7
7
|
import { findMainPluginFile } from "./package-meta.js";
|
|
8
8
|
import { findConstantValues } from "./version-check.js";
|
|
9
|
-
import {
|
|
9
|
+
import { formatChangelogList, formatChangeset, getChangesetBump, getNextVersion, parseChangeset, } from "./changesets.js";
|
|
10
10
|
import { ValidationError } from "./errors.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
/**
|
|
@@ -127,10 +127,12 @@ export async function versionChangesets(input) {
|
|
|
127
127
|
? selectReleasePlan(context.plan, packages, input.packages)
|
|
128
128
|
: context.plan;
|
|
129
129
|
const date = input.date ?? new Date().toISOString().slice(0, 10);
|
|
130
|
+
const changesetDirectory = path.resolve(input.cwd, config.changesets ?? '.changeset');
|
|
131
|
+
const releases = await getVersionedReleases(input.cwd, config, releasePlan.releases, changesetDirectory);
|
|
130
132
|
if (!input.dryRun) {
|
|
131
133
|
const journal = new Map();
|
|
132
134
|
try {
|
|
133
|
-
for (const release of
|
|
135
|
+
for (const release of releases) {
|
|
134
136
|
const configured = packages.find((pkg) => pkg.name === release.package.name);
|
|
135
137
|
if (!configured) {
|
|
136
138
|
throw new ValidationError(`Unknown package "${release.package.name}".`);
|
|
@@ -141,7 +143,6 @@ export async function versionChangesets(input) {
|
|
|
141
143
|
await verifyVersionTargets(input.cwd, configured, release.nextVersion);
|
|
142
144
|
await prependChangelog(configured.directory, release, date, config.readmeChangelog ?? false, config.changelogInsertAfter, journal);
|
|
143
145
|
}
|
|
144
|
-
const changesetDirectory = path.resolve(input.cwd, config.changesets ?? '.changeset');
|
|
145
146
|
for (const changeset of releasePlan.changesets) {
|
|
146
147
|
await consumeChangeset(path.join(changesetDirectory, changeset.fileName), changeset, input.packages, journal);
|
|
147
148
|
}
|
|
@@ -155,11 +156,76 @@ export async function versionChangesets(input) {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
return {
|
|
158
|
-
|
|
159
|
+
changesets: releasePlan.changesets,
|
|
160
|
+
releases,
|
|
159
161
|
date,
|
|
160
162
|
changed: !input.dryRun && releasePlan.changesets.length > 0,
|
|
161
163
|
};
|
|
162
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Format the changelog entries of each release, with the configured
|
|
167
|
+
* formatter and the commit that added each changeset.
|
|
168
|
+
*
|
|
169
|
+
* @param cwd Repository root.
|
|
170
|
+
* @param config Repository release configuration.
|
|
171
|
+
* @param releases Package releases.
|
|
172
|
+
* @param changesetDirectory Directory of the changeset files.
|
|
173
|
+
* @return Releases with their changelog entries.
|
|
174
|
+
*/
|
|
175
|
+
async function getVersionedReleases(cwd, config, releases, changesetDirectory) {
|
|
176
|
+
const commits = new Map();
|
|
177
|
+
const getCommit = (changeset) => {
|
|
178
|
+
const cached = commits.get(changeset.fileName) ??
|
|
179
|
+
getChangesetCommit(cwd, path.join(changesetDirectory, changeset.fileName));
|
|
180
|
+
commits.set(changeset.fileName, cached);
|
|
181
|
+
return cached;
|
|
182
|
+
};
|
|
183
|
+
return Promise.all(releases.map(async (release) => {
|
|
184
|
+
const entries = [];
|
|
185
|
+
// Format one at a time, so that formatters calling an API aren't rate limited.
|
|
186
|
+
for (const changeset of release.changesets) {
|
|
187
|
+
entries.push(await getChangelogEntry(config, release.package, changeset, getCommit));
|
|
188
|
+
}
|
|
189
|
+
return { ...release, changelog: formatChangelogList(entries) };
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
async function getChangelogEntry(config, pkg, changeset, getCommit) {
|
|
193
|
+
const summary = changeset.summary.trim();
|
|
194
|
+
if (!summary || !config.changelogEntry) {
|
|
195
|
+
return summary;
|
|
196
|
+
}
|
|
197
|
+
const commit = await getCommit(changeset);
|
|
198
|
+
const entry = await config.changelogEntry({
|
|
199
|
+
package: pkg,
|
|
200
|
+
changeset,
|
|
201
|
+
...(commit ? { commit } : {}),
|
|
202
|
+
});
|
|
203
|
+
if ('string' !== typeof entry) {
|
|
204
|
+
throw new ValidationError(`The changelogEntry formatter didn't return a string for changeset ${changeset.fileName}.`);
|
|
205
|
+
}
|
|
206
|
+
return entry.trim();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Find the commit that added a changeset file.
|
|
210
|
+
*
|
|
211
|
+
* @param cwd Repository root.
|
|
212
|
+
* @param file Changeset file.
|
|
213
|
+
* @return Commit, or undefined when the file isn't committed.
|
|
214
|
+
*/
|
|
215
|
+
async function getChangesetCommit(cwd, file) {
|
|
216
|
+
const output = await git(cwd, [
|
|
217
|
+
'log',
|
|
218
|
+
'--diff-filter=A',
|
|
219
|
+
'--format=%H%x1f%h%x1f%s',
|
|
220
|
+
'-1',
|
|
221
|
+
'--',
|
|
222
|
+
path.relative(cwd, file).split(path.sep).join('/'),
|
|
223
|
+
]);
|
|
224
|
+
const [hash, shortHash = '', subject = ''] = output
|
|
225
|
+
.trim()
|
|
226
|
+
.split('\x1f');
|
|
227
|
+
return hash ? { hash, shortHash, subject } : undefined;
|
|
228
|
+
}
|
|
163
229
|
/**
|
|
164
230
|
* Limit a release plan to some packages and the changesets that name them.
|
|
165
231
|
*
|
|
@@ -749,7 +815,7 @@ async function prependChangelog(directory, release, date, readmeChangelog, chang
|
|
|
749
815
|
const summaries = release.changesets
|
|
750
816
|
.map((item) => item.summary.trim())
|
|
751
817
|
.filter(Boolean);
|
|
752
|
-
const section = `## ${release.nextVersion} (${date})\n\n${
|
|
818
|
+
const section = `## ${release.nextVersion} (${date})\n\n${release.changelog}\n\n`;
|
|
753
819
|
const changelog = path.join(directory, 'CHANGELOG.md');
|
|
754
820
|
let previous = '';
|
|
755
821
|
try {
|
package/dist/changesets.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ export interface PackageRelease {
|
|
|
32
32
|
nextVersion: string;
|
|
33
33
|
changesets: Changeset[];
|
|
34
34
|
}
|
|
35
|
+
/** A package release with its formatted changelog entries, once versioned. */
|
|
36
|
+
export interface VersionedRelease extends PackageRelease {
|
|
37
|
+
/** Changelog entries as a Markdown list, as added to CHANGELOG.md. */
|
|
38
|
+
changelog: string;
|
|
39
|
+
}
|
|
35
40
|
/** Deterministic release plan assembled from pending changesets. */
|
|
36
41
|
export interface ChangesetReleasePlan {
|
|
37
42
|
changesets: Changeset[];
|
|
@@ -60,6 +65,13 @@ export declare function formatChangeset(changeset: Omit<Changeset, 'fileName'>):
|
|
|
60
65
|
* @return Markdown list, empty without summaries.
|
|
61
66
|
*/
|
|
62
67
|
export declare function formatChangelogEntries(changesets: Changeset[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* Format changelog entries as a Markdown list, indenting their extra lines.
|
|
70
|
+
*
|
|
71
|
+
* @param entries Changelog entries; empty ones are left out.
|
|
72
|
+
* @return Markdown list, empty without entries.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatChangelogList(entries: string[]): string;
|
|
63
75
|
/**
|
|
64
76
|
* Return the strongest bump requested for a package across changesets.
|
|
65
77
|
*
|
package/dist/changesets.js
CHANGED
|
@@ -71,8 +71,16 @@ export function formatChangeset(changeset) {
|
|
|
71
71
|
* @return Markdown list, empty without summaries.
|
|
72
72
|
*/
|
|
73
73
|
export function formatChangelogEntries(changesets) {
|
|
74
|
-
return changesets
|
|
75
|
-
|
|
74
|
+
return formatChangelogList(changesets.map((item) => item.summary.trim()));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format changelog entries as a Markdown list, indenting their extra lines.
|
|
78
|
+
*
|
|
79
|
+
* @param entries Changelog entries; empty ones are left out.
|
|
80
|
+
* @return Markdown list, empty without entries.
|
|
81
|
+
*/
|
|
82
|
+
export function formatChangelogList(entries) {
|
|
83
|
+
return entries
|
|
76
84
|
.filter(Boolean)
|
|
77
85
|
.map((item) => `- ${item.replaceAll(/\r?\n/g, '\n ')}`)
|
|
78
86
|
.join('\n');
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChecksumsResult } from './commands/checksums.ts';
|
|
2
2
|
import type { SignResult } from './commands/sign.ts';
|
|
3
|
+
import type { KeygenResult } from './commands/keygen.ts';
|
|
3
4
|
import type { ZipResult } from './commands/zip.ts';
|
|
4
5
|
import type { ChangesetVersionResult } from './commands/changeset-version.ts';
|
|
5
6
|
import { Logger } from './logger.ts';
|
|
@@ -44,6 +45,7 @@ export interface CommandResults {
|
|
|
44
45
|
zip: ZipResult;
|
|
45
46
|
checksums: ChecksumsResult;
|
|
46
47
|
sign: SignResult;
|
|
48
|
+
keygen: KeygenResult;
|
|
47
49
|
'changeset-add': import('./commands/changeset-add.ts').ChangesetAddResult;
|
|
48
50
|
'changeset-status': import('./commands/changeset-status.ts').ChangesetStatusResult;
|
|
49
51
|
'changeset-version': ChangesetVersionResult;
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseArgs } from 'node:util';
|
|
|
3
3
|
import zip from "./commands/zip.js";
|
|
4
4
|
import checksums from "./commands/checksums.js";
|
|
5
5
|
import sign from "./commands/sign.js";
|
|
6
|
+
import keygen from "./commands/keygen.js";
|
|
6
7
|
import verify from "./commands/verify.js";
|
|
7
8
|
import versionCheck from "./commands/version-check.js";
|
|
8
9
|
import deploySvn from "./commands/deploy-svn.js";
|
|
@@ -23,6 +24,7 @@ export const commands = [
|
|
|
23
24
|
zip,
|
|
24
25
|
checksums,
|
|
25
26
|
sign,
|
|
27
|
+
keygen,
|
|
26
28
|
verify,
|
|
27
29
|
versionCheck,
|
|
28
30
|
deploySvn,
|
package/dist/commands/args.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const fileSelectionArgs: ParseArgsOptionsConfig;
|
|
|
15
15
|
export declare function getFileSelectionInput(values: ParsedValues): {
|
|
16
16
|
distignore: string | false | undefined;
|
|
17
17
|
exclude: string[];
|
|
18
|
+
excludeGitattributes: boolean;
|
|
18
19
|
};
|
|
19
20
|
/**
|
|
20
21
|
* Check that a command got no more positional arguments than it accepts.
|
package/dist/commands/args.js
CHANGED
|
@@ -11,6 +11,7 @@ export const fileSelectionArgs = {
|
|
|
11
11
|
distignore: { type: 'string' },
|
|
12
12
|
'no-distignore': { type: 'boolean' },
|
|
13
13
|
exclude: { type: 'string', multiple: true },
|
|
14
|
+
'exclude-gitattributes': { type: 'boolean' },
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
17
|
* Map the parsed file selection flags to resolveFileSelection() input.
|
|
@@ -24,6 +25,7 @@ export function getFileSelectionInput(values) {
|
|
|
24
25
|
? false
|
|
25
26
|
: getString(values, 'distignore'),
|
|
26
27
|
exclude: getStrings(values, 'exclude'),
|
|
28
|
+
excludeGitattributes: getBoolean(values, 'exclude-gitattributes'),
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { versionChangesets } from "../changeset-release.js";
|
|
2
|
-
import { formatChangelogEntries } from "../changesets.js";
|
|
3
2
|
import { getChangesetVersionSummary } from "../summary.js";
|
|
4
3
|
import { parseChangesetVersionArgs, changesetVersionArgs, } from "./changeset-args.js";
|
|
5
4
|
export default {
|
|
@@ -38,7 +37,7 @@ changesets without packages are left for a run without --package.`,
|
|
|
38
37
|
bump: item.bump,
|
|
39
38
|
currentVersion: item.currentVersion,
|
|
40
39
|
nextVersion: item.nextVersion,
|
|
41
|
-
changelog:
|
|
40
|
+
changelog: item.changelog,
|
|
42
41
|
})),
|
|
43
42
|
},
|
|
44
43
|
};
|
|
@@ -28,6 +28,8 @@ Options:
|
|
|
28
28
|
--no-distignore Don't read any ignore file
|
|
29
29
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or
|
|
30
30
|
repeated, like build/,assets/*,images/**/*
|
|
31
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git
|
|
32
|
+
attributes (requires a Git working tree)
|
|
31
33
|
--force Overwrite an existing --output file
|
|
32
34
|
|
|
33
35
|
The manifest uses the WordPress.org plugin checksums format, with paths
|
|
@@ -46,6 +46,7 @@ Options:
|
|
|
46
46
|
--distignore <file> Ignore file (default: <source-dir>/.distignore when present)
|
|
47
47
|
--no-distignore Don't read any ignore file
|
|
48
48
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or repeated
|
|
49
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git attributes
|
|
49
50
|
--message <text> Release commit message
|
|
50
51
|
--dry-run Run everything except the push
|
|
51
52
|
|
|
@@ -60,6 +60,7 @@ Options:
|
|
|
60
60
|
--distignore <file> Ignore file (default: <source-dir>/.distignore when present)
|
|
61
61
|
--no-distignore Don't read any ignore file
|
|
62
62
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or repeated
|
|
63
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git attributes
|
|
63
64
|
--svn-url <url> Repository URL with {slug}
|
|
64
65
|
(default: https://plugins.svn.wordpress.org/{slug}/)
|
|
65
66
|
--svn-username <name> SVN username (env: SVN_USERNAME, password: SVN_PASSWORD)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CommandOutcome } from './command.ts';
|
|
2
|
+
/** Generated Ed25519 signing keys. */
|
|
3
|
+
export interface KeygenResult {
|
|
4
|
+
/** Base64 Ed25519 seed for WP_RELEASE_SIGNING_KEY. */
|
|
5
|
+
privateKey: string;
|
|
6
|
+
/** Base64 Ed25519 public key for verification. */
|
|
7
|
+
publicKey: string;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: {
|
|
10
|
+
name: string;
|
|
11
|
+
usage: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
help: string;
|
|
14
|
+
options: {};
|
|
15
|
+
run({ values, logger }: import("./command.ts").CommandContext): Promise<CommandOutcome<KeygenResult>>;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ValidationError } from "../errors.js";
|
|
2
|
+
import { generateSigningKeyPair } from "../signing.js";
|
|
3
|
+
import { getBoolean } from "./command.js";
|
|
4
|
+
export default {
|
|
5
|
+
name: 'keygen',
|
|
6
|
+
usage: 'keygen',
|
|
7
|
+
summary: 'Generate an Ed25519 signing key pair.',
|
|
8
|
+
help: `Generate a signing key pair for "wp-release sign" and "wp-release verify".
|
|
9
|
+
|
|
10
|
+
The private seed is printed once as WP_RELEASE_SIGNING_KEY. Store it securely
|
|
11
|
+
and never commit it or share it. The public key can be distributed to verifiers.`,
|
|
12
|
+
options: {},
|
|
13
|
+
async run({ values, logger }) {
|
|
14
|
+
if (getBoolean(values, 'github-output')) {
|
|
15
|
+
throw new ValidationError('keygen cannot be used with --github-output, which would store the private seed in a workflow output.');
|
|
16
|
+
}
|
|
17
|
+
const result = generateSigningKeyPair();
|
|
18
|
+
logger.info(`WP_RELEASE_SIGNING_KEY=${result.privateKey}`);
|
|
19
|
+
logger.info(`Public key: ${result.publicKey}`);
|
|
20
|
+
logger.info('Store the private seed securely; it cannot be recovered from the public key.');
|
|
21
|
+
return { exitCode: 0, result };
|
|
22
|
+
},
|
|
23
|
+
};
|
package/dist/commands/zip.js
CHANGED
|
@@ -56,6 +56,8 @@ Options:
|
|
|
56
56
|
--no-distignore Don't read any ignore file
|
|
57
57
|
--exclude <patterns> Extra gitignore-style patterns, comma-separated or
|
|
58
58
|
repeated, like build/,assets/*,images/**/*
|
|
59
|
+
--exclude-gitattributes Also exclude paths marked export-ignore in Git
|
|
60
|
+
attributes (requires a Git working tree)
|
|
59
61
|
--checksums Also write the checksums manifest of the archived
|
|
60
62
|
files next to the ZIP, with .checksums.json
|
|
61
63
|
instead of .zip
|
package/dist/deploy-options.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface DeployInput {
|
|
|
27
27
|
distignore?: string | false;
|
|
28
28
|
/** Extra gitignore-style patterns. */
|
|
29
29
|
exclude?: string | string[];
|
|
30
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
31
|
+
excludeGitattributes?: boolean;
|
|
30
32
|
/** Readme file name. */
|
|
31
33
|
readme?: string;
|
|
32
34
|
/** ZIP archive to deploy instead of sourceDir. */
|
package/dist/deploy-options.js
CHANGED
package/dist/file-selection.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface FileSelectionInput {
|
|
|
21
21
|
distignore?: string | false;
|
|
22
22
|
/** Extra gitignore-style patterns, comma or newline separated. */
|
|
23
23
|
exclude?: string | string[];
|
|
24
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
25
|
+
excludeGitattributes?: boolean;
|
|
24
26
|
}
|
|
25
27
|
export interface FileSelection {
|
|
26
28
|
/** Ignore file path, or null when disabled. */
|
|
@@ -29,6 +31,10 @@ export interface FileSelection {
|
|
|
29
31
|
distignoreRequired: boolean;
|
|
30
32
|
/** Extra gitignore-style patterns. */
|
|
31
33
|
exclude: string[];
|
|
34
|
+
/** Source directory, needed to resolve Git attributes. */
|
|
35
|
+
sourceDir?: string;
|
|
36
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
37
|
+
excludeGitattributes?: boolean;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Parse exclude patterns from a comma or newline separated string, an array
|
package/dist/file-selection.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { join, posix, resolve } from 'node:path';
|
|
1
|
+
import { realpath, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
3
3
|
import ignore from 'ignore';
|
|
4
4
|
import { ValidationError } from "./errors.js";
|
|
5
|
+
import { createGitRunner } from "./git.js";
|
|
5
6
|
export const DISTIGNORE_FILENAME = '.distignore';
|
|
6
7
|
/**
|
|
7
8
|
* Version control metadata that is never released, at any depth. Without
|
|
@@ -35,11 +36,13 @@ export function parseExclude(value) {
|
|
|
35
36
|
* @return Ignore file path (or null), whether it's required, and the patterns.
|
|
36
37
|
*/
|
|
37
38
|
export function resolveFileSelection(input) {
|
|
38
|
-
const { cwd, sourceDir, distignore, exclude } = input;
|
|
39
|
+
const { cwd, sourceDir, distignore, exclude, excludeGitattributes = false, } = input;
|
|
39
40
|
const selection = {
|
|
40
41
|
distignore: join(sourceDir, DISTIGNORE_FILENAME),
|
|
41
42
|
distignoreRequired: false,
|
|
42
43
|
exclude: parseExclude(exclude),
|
|
44
|
+
sourceDir,
|
|
45
|
+
excludeGitattributes,
|
|
43
46
|
};
|
|
44
47
|
if (false === distignore) {
|
|
45
48
|
selection.distignore = null;
|
|
@@ -61,8 +64,101 @@ export function resolveFileSelection(input) {
|
|
|
61
64
|
*/
|
|
62
65
|
export async function loadFileRules(selection) {
|
|
63
66
|
const { distignore = null, distignoreRequired = false, exclude = [], } = selection;
|
|
64
|
-
const rules =
|
|
65
|
-
|
|
67
|
+
const rules = ignore()
|
|
68
|
+
.add(await readIgnoreFile(distignore, distignoreRequired))
|
|
69
|
+
.add(exclude);
|
|
70
|
+
if (selection.excludeGitattributes) {
|
|
71
|
+
if (!selection.sourceDir) {
|
|
72
|
+
throw new ValidationError('--exclude-gitattributes requires a source directory.');
|
|
73
|
+
}
|
|
74
|
+
const excluded = await getExportIgnoredPaths(selection.sourceDir, rules);
|
|
75
|
+
rules.add(excluded.map((path) => `/${escapePattern(path)}`));
|
|
76
|
+
}
|
|
77
|
+
return rules.add(ALWAYS_EXCLUDED);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read Git's export-ignore attribute for the source files selected by the
|
|
81
|
+
* other ignore rules. Git itself evaluates .gitattributes, including nested
|
|
82
|
+
* files and its pattern syntax.
|
|
83
|
+
*
|
|
84
|
+
* @param sourceDir Directory whose files are being selected.
|
|
85
|
+
* @param rules Rules already loaded from .distignore and --exclude.
|
|
86
|
+
* @return Source-relative paths marked export-ignore.
|
|
87
|
+
*/
|
|
88
|
+
async function getExportIgnoredPaths(sourceDir, rules) {
|
|
89
|
+
const runner = createGitRunner();
|
|
90
|
+
let root;
|
|
91
|
+
try {
|
|
92
|
+
({ stdout: root } = await runner({
|
|
93
|
+
args: ['rev-parse', '--show-toplevel'],
|
|
94
|
+
cwd: sourceDir,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new ValidationError('--exclude-gitattributes requires the source directory to be inside a Git repository.');
|
|
99
|
+
}
|
|
100
|
+
root = await realpath(root.trim());
|
|
101
|
+
const sourceFromRoot = relative(root, await realpath(sourceDir));
|
|
102
|
+
if (isAbsolute(sourceFromRoot) ||
|
|
103
|
+
sourceFromRoot === '..' ||
|
|
104
|
+
sourceFromRoot.startsWith(`..${posix.sep}`)) {
|
|
105
|
+
throw new ValidationError('--exclude-gitattributes requires the source directory to be inside a Git repository.');
|
|
106
|
+
}
|
|
107
|
+
const candidates = await findAttributeCandidates(sourceDir, '', rules);
|
|
108
|
+
if (!candidates.length) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const pathsFromRoot = candidates.map((path) => posix.join(sourceFromRoot.split('\\').join('/'), path));
|
|
112
|
+
const { stdout } = await runner({
|
|
113
|
+
args: ['check-attr', '-z', '--stdin', 'export-ignore'],
|
|
114
|
+
cwd: root,
|
|
115
|
+
input: pathsFromRoot.map((path) => `${path}\0`).join(''),
|
|
116
|
+
});
|
|
117
|
+
const fields = stdout.split('\0');
|
|
118
|
+
const ignored = [];
|
|
119
|
+
for (let index = 0; index + 2 < fields.length; index += 3) {
|
|
120
|
+
const candidate = candidates[index / 3];
|
|
121
|
+
if (fields[index + 2] === 'set' && candidate) {
|
|
122
|
+
ignored.push(candidate);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return ignored;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* List regular files and symbolic links not already excluded by ignore rules.
|
|
129
|
+
* Symbolic links are included here so Git can exclude them before selectFiles
|
|
130
|
+
* applies its unsupported-link validation.
|
|
131
|
+
*
|
|
132
|
+
* @param sourceDir Source directory.
|
|
133
|
+
* @param dir Relative directory being traversed.
|
|
134
|
+
* @param rules Ignore rules.
|
|
135
|
+
* @return Relative paths to candidate files.
|
|
136
|
+
*/
|
|
137
|
+
async function findAttributeCandidates(sourceDir, dir, rules) {
|
|
138
|
+
const entries = await readdir(join(sourceDir, dir), {
|
|
139
|
+
withFileTypes: true,
|
|
140
|
+
});
|
|
141
|
+
const paths = await Promise.all(entries.map(async (entry) => {
|
|
142
|
+
const path = posix.join(dir, entry.name);
|
|
143
|
+
if (entry.isDirectory() && !rules.ignores(`${path}/`)) {
|
|
144
|
+
return findAttributeCandidates(sourceDir, path, rules);
|
|
145
|
+
}
|
|
146
|
+
if ((entry.isFile() || entry.isSymbolicLink()) &&
|
|
147
|
+
!rules.ignores(path)) {
|
|
148
|
+
return [path];
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}));
|
|
152
|
+
return paths.flat();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Escape a path for an anchored gitignore pattern.
|
|
156
|
+
*
|
|
157
|
+
* @param path Relative path.
|
|
158
|
+
* @return Pattern-safe path.
|
|
159
|
+
*/
|
|
160
|
+
function escapePattern(path) {
|
|
161
|
+
return path.replace(/[\\*?[\\]!#]/g, '\\$&');
|
|
66
162
|
}
|
|
67
163
|
/**
|
|
68
164
|
* Read the ignore file, or nothing when there's no file to read.
|
package/dist/git-deploy.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface GitDeployInput {
|
|
|
24
24
|
distignore?: string | false;
|
|
25
25
|
/** Extra gitignore-style patterns. */
|
|
26
26
|
exclude?: string | string[];
|
|
27
|
+
/** Also exclude paths marked export-ignore by Git attributes. */
|
|
28
|
+
excludeGitattributes?: boolean;
|
|
27
29
|
/** Commit message of the release. */
|
|
28
30
|
message?: string;
|
|
29
31
|
/** Run everything except the push. */
|
package/dist/git-deploy.js
CHANGED
package/dist/github.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A GitHub pull request of a commit.
|
|
3
|
+
*/
|
|
4
|
+
export interface GitHubPullRequest {
|
|
5
|
+
number: number;
|
|
6
|
+
url: string;
|
|
7
|
+
/** GitHub handle of the pull request author, without the @. */
|
|
8
|
+
author: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GetGitHubPullRequestInput {
|
|
11
|
+
/** Directory of the repository, whose GitHub remote `gh` uses. */
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Commit hash. */
|
|
14
|
+
commit: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Find the pull request that merged a commit, with the authenticated `gh`
|
|
18
|
+
* command line client.
|
|
19
|
+
*
|
|
20
|
+
* @param input Repository directory and commit.
|
|
21
|
+
* @return The first pull request of the commit, or undefined without one.
|
|
22
|
+
* @throws {ValidationError} If `gh` fails, like without authentication.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getGitHubPullRequest(input: GetGitHubPullRequestInput): Promise<GitHubPullRequest | undefined>;
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { ValidationError } from "./errors.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Find the pull request that merged a commit, with the authenticated `gh`
|
|
7
|
+
* command line client.
|
|
8
|
+
*
|
|
9
|
+
* @param input Repository directory and commit.
|
|
10
|
+
* @return The first pull request of the commit, or undefined without one.
|
|
11
|
+
* @throws {ValidationError} If `gh` fails, like without authentication.
|
|
12
|
+
*/
|
|
13
|
+
export async function getGitHubPullRequest(input) {
|
|
14
|
+
let stdout;
|
|
15
|
+
try {
|
|
16
|
+
({ stdout } = await execFileAsync('gh', ['api', `repos/{owner}/{repo}/commits/${input.commit}/pulls`], { cwd: input.cwd }));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
throw new ValidationError(`Could not find the pull request of commit ${input.commit}: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
const [pullRequest] = JSON.parse(stdout);
|
|
23
|
+
return pullRequest
|
|
24
|
+
? {
|
|
25
|
+
number: pullRequest.number,
|
|
26
|
+
url: pullRequest.html_url,
|
|
27
|
+
author: pullRequest.user?.login ?? '',
|
|
28
|
+
}
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,12 +9,12 @@ export { DISTIGNORE_FILENAME, parseExclude, resolveFileSelection, loadFileRules,
|
|
|
9
9
|
export { FILENAME_FORMAT, formatFilename } from './filename.ts';
|
|
10
10
|
export { CHECKSUMS_EXTENSION, CHECKSUM_ALGORITHMS, hashFile, hashFiles, createChecksums, writeChecksums, readChecksums, parseChecksums, compareChecksums, verifyChecksums, } from './checksums.ts';
|
|
11
11
|
export { HEADER_BYTES, PLUGIN_HEADERS, THEME_HEADERS, README_HEADERS, README_FILENAMES, parseFileHeaders, parseReadmeHeaders, readFileStart, findFileHeaderLine, findReadmeHeaderLine, findMainPluginFile, readPackageMeta, } from './package-meta.ts';
|
|
12
|
-
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from './signing.ts';
|
|
12
|
+
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from './signing.ts';
|
|
13
13
|
export { ZIP_MTIME, ZIP_FILE_MODE, createZip } from './zip.ts';
|
|
14
14
|
export { isUrl, downloadFile } from './download.ts';
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from './unzip.ts';
|
|
16
16
|
export { GIT_TAG_FORMAT, VersionMismatchError, getGitTagVersion, findConstantValues, findConstantDefinitions, findVersionMismatches, getVersionMismatchProblems, } from './version-check.ts';
|
|
17
|
-
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, getChangesetBump, getNextVersion, } from './changesets.ts';
|
|
17
|
+
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, formatChangelogList, getChangesetBump, getNextVersion, } from './changesets.ts';
|
|
18
18
|
export { addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from './changeset-release.ts';
|
|
19
19
|
export { defineConfig, packageJsonVersion, composerJsonVersion, wordpressPluginVersion, wordpressThemeVersion, wordpressStableTag, gitTagVersion, phpConstantVersion, loadChangesetConfig, } from './changeset-config.ts';
|
|
20
20
|
export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from './svn.ts';
|
|
@@ -26,6 +26,7 @@ export { getGitDeployInputFromArgs } from './commands/deploy-git.ts';
|
|
|
26
26
|
export type { CliIo, CommandResults } from './cli.ts';
|
|
27
27
|
export type { ChecksumsResult } from './commands/checksums.ts';
|
|
28
28
|
export type { SignResult } from './commands/sign.ts';
|
|
29
|
+
export type { KeygenResult } from './commands/keygen.ts';
|
|
29
30
|
export type { ZipResult } from './commands/zip.ts';
|
|
30
31
|
export type { Command, CommandContext, CommandOutcome, ParsedValues, } from './commands/command.ts';
|
|
31
32
|
export type { DeployArgsInput } from './commands/deploy-svn.ts';
|
|
@@ -40,12 +41,14 @@ export type { FindMainPluginFileInput, HeaderValues, PackageMeta, ParseFileHeade
|
|
|
40
41
|
export type { CreateZipInput } from './zip.ts';
|
|
41
42
|
export type { ExtractZipInput, ZipEntryInfo, ZipEntryType } from './unzip.ts';
|
|
42
43
|
export type { FindVersionMismatchesInput, GitTagInput, VersionCheckResult, VersionMismatch, } from './version-check.ts';
|
|
43
|
-
export type { Changeset, ChangesetBump, ChangesetPackages, ChangesetConfig, ChangesetPackageType, ChangesetPackage, PackageRelease, ChangesetReleasePlan, } from './changesets.ts';
|
|
44
|
-
export type { ChangesetRepository, ChangesetVersionInput, ChangesetTagInput, ChangesetTagResult, } from './changeset-release.ts';
|
|
45
|
-
export type { WpReleaseConfig, ChangesetPackageGroup, PackageVersionConfig, PackageVersionSource, PackageVersionTarget, PackageIdSource, NextVersionInput, ReleaseCommit, ReleaseNotesInput, PackageJsonVersionOptions, ComposerJsonVersionOptions, WordPressVersionFileOptions, GitTagVersionOptions, PhpConstantVersionOptions, LoadChangesetConfigInput, } from './changeset-config.ts';
|
|
44
|
+
export type { Changeset, ChangesetBump, ChangesetPackages, ChangesetConfig, ChangesetPackageType, ChangesetPackage, PackageRelease, VersionedRelease, ChangesetReleasePlan, } from './changesets.ts';
|
|
45
|
+
export type { ChangesetRepository, ChangesetVersionInput, VersionChangesetsResult, ChangesetTagInput, ChangesetTagResult, } from './changeset-release.ts';
|
|
46
|
+
export type { WpReleaseConfig, ChangesetPackageGroup, PackageVersionConfig, PackageVersionSource, PackageVersionTarget, PackageIdSource, NextVersionInput, ReleaseCommit, ReleaseNotesInput, ChangelogEntryInput, PackageJsonVersionOptions, ComposerJsonVersionOptions, WordPressVersionFileOptions, GitTagVersionOptions, PhpConstantVersionOptions, LoadChangesetConfigInput, } from './changeset-config.ts';
|
|
46
47
|
export type { SvnCheckoutInput, SvnCommandInput, SvnCommandOutput, SvnCommitInput, SvnCopyInput, SvnDepth, SvnOptions, SvnPathsInput, SvnPropsetInput, SvnRunner, SvnStatusEntry, SvnStatusType, SvnUpdateInput, } from './svn.ts';
|
|
47
48
|
export type { SyncDirectoryInput, SyncResult } from './sync.ts';
|
|
48
49
|
export type { SummaryContext } from './summary.ts';
|
|
50
|
+
export { getGitHubPullRequest } from './github.ts';
|
|
51
|
+
export type { GitHubPullRequest, GetGitHubPullRequestInput } from './github.ts';
|
|
49
52
|
export type { CheckoutPath, DeployResult } from './svn-deploy.ts';
|
|
50
53
|
export type { GitCommandInput, GitCommandOutput, GitRunner } from './git.ts';
|
|
51
54
|
export type { GitDeployInput, GitDeployOptions, GitDeployResult, } from './git-deploy.ts';
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,12 @@ export { DISTIGNORE_FILENAME, parseExclude, resolveFileSelection, loadFileRules,
|
|
|
9
9
|
export { FILENAME_FORMAT, formatFilename } from "./filename.js";
|
|
10
10
|
export { CHECKSUMS_EXTENSION, CHECKSUM_ALGORITHMS, hashFile, hashFiles, createChecksums, writeChecksums, readChecksums, parseChecksums, compareChecksums, verifyChecksums, } from "./checksums.js";
|
|
11
11
|
export { HEADER_BYTES, PLUGIN_HEADERS, THEME_HEADERS, README_HEADERS, README_FILENAMES, parseFileHeaders, parseReadmeHeaders, readFileStart, findFileHeaderLine, findReadmeHeaderLine, findMainPluginFile, readPackageMeta, } from "./package-meta.js";
|
|
12
|
-
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from "./signing.js";
|
|
12
|
+
export { SIGNATURE_EXTENSION, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getFileDigest, signFile, writeSignature, verifyFileSignature, } from "./signing.js";
|
|
13
13
|
export { ZIP_MTIME, ZIP_FILE_MODE, createZip } from "./zip.js";
|
|
14
14
|
export { isUrl, downloadFile } from "./download.js";
|
|
15
15
|
export { readZipEntries, getZipEntryErrors, extractZip } from "./unzip.js";
|
|
16
16
|
export { GIT_TAG_FORMAT, VersionMismatchError, getGitTagVersion, findConstantValues, findConstantDefinitions, findVersionMismatches, getVersionMismatchProblems, } from "./version-check.js";
|
|
17
|
-
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, getChangesetBump, getNextVersion, } from "./changesets.js";
|
|
17
|
+
export { CHANGESET_BUMPS, parseChangeset, formatChangeset, formatChangelogEntries, formatChangelogList, getChangesetBump, getNextVersion, } from "./changesets.js";
|
|
18
18
|
export { addChangeset, getChangesetPackages, getChangesetReleasePlan, getChangesetStatus, versionChangesets, tagChangesets, } from "./changeset-release.js";
|
|
19
19
|
export { defineConfig, packageJsonVersion, composerJsonVersion, wordpressPluginVersion, wordpressThemeVersion, wordpressStableTag, gitTagVersion, phpConstantVersion, loadChangesetConfig, } from "./changeset-config.js";
|
|
20
20
|
export { Svn, SvnError, createSvnRunner, parseStatus, toSvnPath, } from "./svn.js";
|
|
@@ -23,3 +23,4 @@ export { ASSET_MIME_TYPES, SvnDeploy } from "./svn-deploy.js";
|
|
|
23
23
|
export { GitError, createGitRunner, redactUrls } from "./git.js";
|
|
24
24
|
export { GitDeploy, isValidRefName, resolveGitDeployOptions, } from "./git-deploy.js";
|
|
25
25
|
export { getGitDeployInputFromArgs } from "./commands/deploy-git.js";
|
|
26
|
+
export { getGitHubPullRequest } from "./github.js";
|
package/dist/signing.d.ts
CHANGED
|
@@ -4,6 +4,15 @@ export declare const SIGNATURE_EXTENSION = ".sig";
|
|
|
4
4
|
* Environment variable with the base64 Ed25519 signing key.
|
|
5
5
|
*/
|
|
6
6
|
export declare const SIGNING_KEY_ENV = "WP_RELEASE_SIGNING_KEY";
|
|
7
|
+
/**
|
|
8
|
+
* Generate an Ed25519 key pair in the base64 formats used by wp-release.
|
|
9
|
+
*
|
|
10
|
+
* @return Private seed for WP_RELEASE_SIGNING_KEY and public verification key.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateSigningKeyPair(): {
|
|
13
|
+
privateKey: string;
|
|
14
|
+
publicKey: string;
|
|
15
|
+
};
|
|
7
16
|
/**
|
|
8
17
|
* Get an Ed25519 private key from the base64 format that sodium and the
|
|
9
18
|
* update-client signing_key setting use: a 32-byte seed or a 64-byte
|
package/dist/signing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, createPrivateKey, createPublicKey, sign, verify, } from 'node:crypto';
|
|
1
|
+
import { createHash, createPrivateKey, generateKeyPairSync, createPublicKey, sign, verify, } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { ValidationError } from "./errors.js";
|
|
@@ -14,6 +14,23 @@ const PUBLIC_KEY_BYTES = 32;
|
|
|
14
14
|
const PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
15
15
|
// DER prefix of a SPKI Ed25519 public key, followed by the 32-byte key.
|
|
16
16
|
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
17
|
+
/**
|
|
18
|
+
* Generate an Ed25519 key pair in the base64 formats used by wp-release.
|
|
19
|
+
*
|
|
20
|
+
* @return Private seed for WP_RELEASE_SIGNING_KEY and public verification key.
|
|
21
|
+
*/
|
|
22
|
+
export function generateSigningKeyPair() {
|
|
23
|
+
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
|
24
|
+
const privateJwk = privateKey.export({ format: 'jwk' });
|
|
25
|
+
const publicJwk = publicKey.export({ format: 'jwk' });
|
|
26
|
+
if (!privateJwk.d || !publicJwk.x) {
|
|
27
|
+
throw new ValidationError('Could not export the generated Ed25519 key pair.');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
privateKey: Buffer.from(privateJwk.d, 'base64url').toString('base64'),
|
|
31
|
+
publicKey: Buffer.from(publicJwk.x, 'base64url').toString('base64'),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
17
34
|
/**
|
|
18
35
|
* Decode a base64 key, accepting the base64url alphabet and whitespace.
|
|
19
36
|
*
|
package/package.json
CHANGED