@wpelevator/wp-release 0.14.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 +16 -5
- package/dist/cli.d.ts +16 -7
- package/dist/cli.js +25 -2
- package/dist/commands/args.d.ts +11 -0
- package/dist/commands/args.js +23 -0
- package/dist/commands/command.d.ts +3 -0
- package/dist/commands/deploy-svn.d.ts +11 -15
- package/dist/commands/deploy-svn.js +25 -5
- package/dist/commands/sign.d.ts +0 -9
- package/dist/commands/sign.js +10 -47
- package/dist/commands/zip.d.ts +11 -0
- package/dist/commands/zip.js +49 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/signing.d.ts +36 -0
- package/dist/signing.js +52 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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,7 +65,7 @@ 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
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. |
|
|
@@ -281,7 +281,7 @@ Each concept has one name, written in the syntax of each interface:
|
|
|
281
281
|
| Concept | CLI | GitHub Action | Library | Meaning |
|
|
282
282
|
|---|---|---|---|---|
|
|
283
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. |
|
|
284
|
-
| Generated file | `--output <file>` | | `outputPath` | File written by `zip` or `checksums`. |
|
|
284
|
+
| Generated file | `--output <file>` | `output` | `outputPath` | File written by `zip` or `checksums`. |
|
|
285
285
|
| ZIP to deploy | `--from-zip <file>` | `from-zip` | `fromZip` | Existing ZIP read as the deploy source. |
|
|
286
286
|
| Git tag to compare | `--git-tag <tag>` | | `gitTag` | Git tag checked by `version-check`. |
|
|
287
287
|
| SVN tag to create | `--svn-tag <version>` | `svn-tag` | `svnTag` | Tag written by `deploy-svn`, distinct from a git tag. |
|
|
@@ -291,9 +291,14 @@ Each concept has one name, written in the syntax of each interface:
|
|
|
291
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. |
|
|
292
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. |
|
|
293
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. |
|
|
294
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. |
|
|
295
298
|
|
|
296
|
-
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".
|
|
297
302
|
|
|
298
303
|
### File selection
|
|
299
304
|
|
|
@@ -328,6 +333,7 @@ wp-release zip dist --slug my-plugin --checksums
|
|
|
328
333
|
- `--filename-format` changes the name, like `--filename-format {slug}` for `my-plugin.zip`.
|
|
329
334
|
- Entries are sorted and use a fixed timestamp and file mode, so the same files always give the same ZIP.
|
|
330
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`.
|
|
331
337
|
- An existing ZIP or manifest isn't overwritten without `--force`. A ZIP or manifest written inside the source directory is never archived.
|
|
332
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.
|
|
333
339
|
|
|
@@ -414,6 +420,7 @@ WP_RELEASE_SIGNING_KEY=… wp-release sign my-plugin.1.2.0.zip my-plugin.1.2.0.c
|
|
|
414
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.
|
|
415
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.
|
|
416
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.
|
|
417
424
|
|
|
418
425
|
#### Signature formats
|
|
419
426
|
|
|
@@ -745,7 +752,9 @@ npm run lint --workspace @wpelevator/wp-release
|
|
|
745
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.
|
|
746
753
|
- `lint` runs ESLint and a `tsc` type check.
|
|
747
754
|
|
|
748
|
-
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.
|
|
749
758
|
|
|
750
759
|
### Architecture
|
|
751
760
|
|
|
@@ -756,6 +765,8 @@ wp-release has two layers, so that it fits any repository layout:
|
|
|
756
765
|
|
|
757
766
|
Nothing assumes a directory structure or naming convention that the config doesn't set.
|
|
758
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
|
+
|
|
759
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:
|
|
760
771
|
|
|
761
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()`.
|
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
|
*
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { appendFileSync } from 'node:fs';
|
|
2
3
|
import { parseArgs } from 'node:util';
|
|
3
4
|
import zip from "./commands/zip.js";
|
|
@@ -177,6 +178,28 @@ function createLogWriter(values, streams) {
|
|
|
177
178
|
}
|
|
178
179
|
return create(streams);
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Format the result of a command as GitHub Actions step outputs: the whole
|
|
183
|
+
* result as JSON in "result", and each top-level string, number or boolean
|
|
184
|
+
* as its own output, like "zipPath", so that a workflow reads it without
|
|
185
|
+
* fromJSON(). Lists and objects are only in "result". Every value but the
|
|
186
|
+
* compact JSON, which has no line breaks, uses a random delimiter, so that a
|
|
187
|
+
* value with line breaks can't set other outputs.
|
|
188
|
+
*
|
|
189
|
+
* @param result Command result.
|
|
190
|
+
* @return Lines for the GITHUB_OUTPUT file.
|
|
191
|
+
*/
|
|
192
|
+
export function formatGitHubOutputs(result) {
|
|
193
|
+
const values = 'object' === typeof result && null !== result
|
|
194
|
+
? Object.entries(result).filter(([name, value]) => 'result' !== name &&
|
|
195
|
+
['string', 'number', 'boolean'].includes(typeof value))
|
|
196
|
+
: [];
|
|
197
|
+
const delimiter = `ghadelimiter_${randomUUID()}`;
|
|
198
|
+
return [
|
|
199
|
+
`result=${JSON.stringify(result)}\n`,
|
|
200
|
+
...values.map(([name, value]) => `${name}<<${delimiter}\n${String(value)}\n${delimiter}\n`),
|
|
201
|
+
].join('');
|
|
202
|
+
}
|
|
180
203
|
/**
|
|
181
204
|
* Get the GitHub Actions step output file for --github-output, checked before
|
|
182
205
|
* the command runs.
|
|
@@ -241,8 +264,7 @@ async function runCommand(command, args, io) {
|
|
|
241
264
|
stdout.write(JSON.stringify(result, null, '\t') + '\n');
|
|
242
265
|
}
|
|
243
266
|
if (githubOutputFile) {
|
|
244
|
-
|
|
245
|
-
appendFileSync(githubOutputFile, `result=${JSON.stringify(result)}\n`);
|
|
267
|
+
appendFileSync(githubOutputFile, formatGitHubOutputs(result));
|
|
246
268
|
}
|
|
247
269
|
return exitCode;
|
|
248
270
|
}
|
|
@@ -262,6 +284,7 @@ async function runCommand(command, args, io) {
|
|
|
262
284
|
* @param context.cwd Working directory that relative paths resolve against.
|
|
263
285
|
* @param context.configFile Explicit config file path, when supplied by a wrapper.
|
|
264
286
|
* @param context.logger Logger for the command output.
|
|
287
|
+
* @param context.svnRunner Optional SVN command runner supplied by a wrapper.
|
|
265
288
|
* @return Exit code and result of the command.
|
|
266
289
|
* @throws {Error} If the command is unknown, the arguments are invalid or the command fails.
|
|
267
290
|
*/
|
package/dist/commands/args.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ParseArgsOptionsConfig } from 'node:util';
|
|
2
|
+
import type { SignatureFormat } from '../signing.ts';
|
|
2
3
|
import type { ParsedValues } from './command.ts';
|
|
3
4
|
/**
|
|
4
5
|
* Options shared by the commands that select files with .distignore and
|
|
@@ -69,3 +70,13 @@ export interface ResolveOutputInput {
|
|
|
69
70
|
* @return Absolute file path.
|
|
70
71
|
*/
|
|
71
72
|
export declare function resolveOutput(input: ResolveOutputInput): string;
|
|
73
|
+
/**
|
|
74
|
+
* Get the signature formats from the --format values of sign or the
|
|
75
|
+
* --sign-format values of zip, which can be repeated
|
|
76
|
+
* or comma-separated.
|
|
77
|
+
*
|
|
78
|
+
* @param values Option values.
|
|
79
|
+
* @return Unique formats, the default when none are given.
|
|
80
|
+
* @throws {ValidationError} If a format is unknown.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseSignatureFormats(values: string[]): SignatureFormat[];
|
package/dist/commands/args.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { ValidationError } from "../errors.js";
|
|
4
|
+
import { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_FORMATS, isSignatureFormat, } from "../signing.js";
|
|
4
5
|
import { getBoolean, getString, getStrings } from "./command.js";
|
|
5
6
|
/**
|
|
6
7
|
* Options shared by the commands that select files with .distignore and
|
|
@@ -95,3 +96,25 @@ export function getSourceDirArg(values, positionals) {
|
|
|
95
96
|
export function resolveOutput(input) {
|
|
96
97
|
return resolve(input.cwd, input.output ?? input.filename);
|
|
97
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the signature formats from the --format values of sign or the
|
|
101
|
+
* --sign-format values of zip, which can be repeated
|
|
102
|
+
* or comma-separated.
|
|
103
|
+
*
|
|
104
|
+
* @param values Option values.
|
|
105
|
+
* @return Unique formats, the default when none are given.
|
|
106
|
+
* @throws {ValidationError} If a format is unknown.
|
|
107
|
+
*/
|
|
108
|
+
export function parseSignatureFormats(values) {
|
|
109
|
+
const names = values
|
|
110
|
+
.flatMap((value) => value.split(','))
|
|
111
|
+
.map((name) => name.trim())
|
|
112
|
+
.filter(Boolean);
|
|
113
|
+
const unknown = names.filter((name) => !isSignatureFormat(name));
|
|
114
|
+
if (unknown.length) {
|
|
115
|
+
throw new ValidationError(`Unknown signature format ${unknown.join(', ')}, use ${Object.keys(SIGNATURE_FORMATS).join(', ')}.`);
|
|
116
|
+
}
|
|
117
|
+
return names.length
|
|
118
|
+
? [...new Set(names.filter(isSignatureFormat))]
|
|
119
|
+
: [DEFAULT_SIGNATURE_FORMAT];
|
|
120
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ParseArgsOptionsConfig } from 'node:util';
|
|
2
2
|
import type { PromptTerminal } from '../changeset-prompt.ts';
|
|
3
3
|
import type { Logger } from '../logger.ts';
|
|
4
|
+
import type { SvnRunner } from '../svn.ts';
|
|
4
5
|
/**
|
|
5
6
|
* Parsed option values from node:util parseArgs().
|
|
6
7
|
*/
|
|
@@ -18,6 +19,8 @@ export interface CommandContext {
|
|
|
18
19
|
tagFormat?: string;
|
|
19
20
|
/** Logger for the command output. */
|
|
20
21
|
logger: Logger;
|
|
22
|
+
/** SVN command runner supplied by a wrapper, instead of spawning svn directly. */
|
|
23
|
+
svnRunner?: SvnRunner;
|
|
21
24
|
/** Terminal for interactive prompts, only when the CLI runs in one and prints no JSON. */
|
|
22
25
|
terminal?: PromptTerminal;
|
|
23
26
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { DeployInput } from '../deploy-options.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DeployResult } from '../svn-deploy.ts';
|
|
3
|
+
import type { PackageDeployPlanItem } from '../package-deploy.ts';
|
|
4
|
+
import type { CommandOutcome, ParsedValues } from './command.ts';
|
|
5
|
+
import type { PackageDeployOutcome } from './package-deploys.ts';
|
|
6
|
+
/** Results of a single deploy, a configured plan or configured deploys. */
|
|
7
|
+
export type DeploySvnResult = DeployResult | {
|
|
8
|
+
packages: PackageDeployPlanItem[];
|
|
9
|
+
} | {
|
|
10
|
+
deploys: PackageDeployOutcome<DeployResult>[];
|
|
11
|
+
};
|
|
3
12
|
export interface DeployArgsInput {
|
|
4
13
|
/** Parsed CLI values. */
|
|
5
14
|
values: ParsedValues;
|
|
@@ -76,19 +85,6 @@ declare const _default: {
|
|
|
76
85
|
type: "string";
|
|
77
86
|
};
|
|
78
87
|
};
|
|
79
|
-
run({ values, positionals, cwd, configFile, tagFormat, logger }: import("./command.ts").CommandContext): Promise<
|
|
80
|
-
exitCode: number;
|
|
81
|
-
result: {
|
|
82
|
-
packages: import("../package-deploy.ts").PackageDeployPlanItem[];
|
|
83
|
-
};
|
|
84
|
-
} | {
|
|
85
|
-
exitCode: number;
|
|
86
|
-
result: {
|
|
87
|
-
deploys: import("./package-deploys.ts").PackageDeployOutcome<import("../svn-deploy.ts").DeployResult>[];
|
|
88
|
-
};
|
|
89
|
-
} | {
|
|
90
|
-
exitCode: number;
|
|
91
|
-
result: import("../svn-deploy.ts").DeployResult;
|
|
92
|
-
}>;
|
|
88
|
+
run({ values, positionals, cwd, configFile, tagFormat, logger, svnRunner, }: import("./command.ts").CommandContext): Promise<CommandOutcome<DeploySvnResult>>;
|
|
93
89
|
};
|
|
94
90
|
export default _default;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveDeployOptions } from "../deploy-options.js";
|
|
2
2
|
import { getSvnDeploys } from "../package-deploy.js";
|
|
3
3
|
import { SvnDeploy } from "../svn-deploy.js";
|
|
4
|
+
import { Svn } from "../svn.js";
|
|
4
5
|
import { fileSelectionArgs, getFileSelectionInput, getSourceDirArg, sourceDirArgs, } from "./args.js";
|
|
5
6
|
import { getBoolean, getString } from "./command.js";
|
|
6
7
|
import { getPackageDeployArgs, listPackageDeploys, packageDeployArgs, runPackageDeploys, } from "./package-deploys.js";
|
|
@@ -23,6 +24,26 @@ const TARGET_FLAGS = [
|
|
|
23
24
|
'svn-url',
|
|
24
25
|
'message',
|
|
25
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Run an SVN deploy with the optional command runner supplied by a wrapper.
|
|
29
|
+
*
|
|
30
|
+
* @param input Deploy input.
|
|
31
|
+
* @param logger Logger for the deploy.
|
|
32
|
+
* @param runner Optional SVN command runner.
|
|
33
|
+
* @return Deploy result.
|
|
34
|
+
*/
|
|
35
|
+
async function runDeploy(input, logger, runner) {
|
|
36
|
+
const options = resolveDeployOptions(input);
|
|
37
|
+
const svn = runner
|
|
38
|
+
? new Svn({
|
|
39
|
+
runner,
|
|
40
|
+
username: options.svnUsername,
|
|
41
|
+
password: options.svnPassword,
|
|
42
|
+
logger,
|
|
43
|
+
})
|
|
44
|
+
: undefined;
|
|
45
|
+
return new SvnDeploy(options, logger, svn).run();
|
|
46
|
+
}
|
|
26
47
|
/**
|
|
27
48
|
* Map the parsed "deploy-svn" arguments to resolveDeployOptions() input.
|
|
28
49
|
* Flags that aren't passed stay undefined, so that the same defaults apply
|
|
@@ -135,7 +156,7 @@ Examples:
|
|
|
135
156
|
...packageDeployArgs,
|
|
136
157
|
'dry-run': { type: 'boolean' },
|
|
137
158
|
},
|
|
138
|
-
async run({ values, positionals, cwd, configFile, tagFormat, logger }) {
|
|
159
|
+
async run({ values, positionals, cwd, configFile, tagFormat, logger, svnRunner, }) {
|
|
139
160
|
const { ref, packages, list } = getPackageDeployArgs(values, positionals, TARGET_FLAGS);
|
|
140
161
|
if (ref) {
|
|
141
162
|
const deploys = await getSvnDeploys({
|
|
@@ -148,14 +169,13 @@ Examples:
|
|
|
148
169
|
});
|
|
149
170
|
return list
|
|
150
171
|
? listPackageDeploys(deploys, logger, ref)
|
|
151
|
-
: runPackageDeploys(deploys, (input) =>
|
|
172
|
+
: runPackageDeploys(deploys, (input) => runDeploy({
|
|
152
173
|
...input,
|
|
153
174
|
svnUsername: getString(values, 'svn-username'),
|
|
154
175
|
force: getBoolean(values, 'force'),
|
|
155
|
-
}
|
|
176
|
+
}, logger, svnRunner), logger, ref);
|
|
156
177
|
}
|
|
157
|
-
const
|
|
158
|
-
const result = await new SvnDeploy(options, logger).run();
|
|
178
|
+
const result = await runDeploy(getDeployInputFromArgs({ values, positionals, cwd }), logger, svnRunner);
|
|
159
179
|
return { exitCode: 0, result };
|
|
160
180
|
},
|
|
161
181
|
};
|
package/dist/commands/sign.d.ts
CHANGED
|
@@ -11,15 +11,6 @@ export interface SignResult {
|
|
|
11
11
|
/** Public key of the signing key in the form of each signature format. */
|
|
12
12
|
publicKeys: Record<SignatureFormat, string>;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Get the signature formats from the --format values, which can be repeated
|
|
16
|
-
* or comma-separated.
|
|
17
|
-
*
|
|
18
|
-
* @param values Option values.
|
|
19
|
-
* @return Unique formats, the default when none are given.
|
|
20
|
-
* @throws {ValidationError} If a format is unknown.
|
|
21
|
-
*/
|
|
22
|
-
export declare function parseSignatureFormats(values: string[]): SignatureFormat[];
|
|
23
14
|
declare const _default: {
|
|
24
15
|
name: string;
|
|
25
16
|
usage: string;
|
package/dist/commands/sign.js
CHANGED
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { stat } from 'node:fs/promises';
|
|
3
2
|
import { ValidationError } from "../errors.js";
|
|
4
3
|
import { getSignSummary } from "../summary.js";
|
|
5
|
-
import {
|
|
4
|
+
import { SIGNING_KEY_ENV, signFiles } from "../signing.js";
|
|
5
|
+
import { parseSignatureFormats } from "./args.js";
|
|
6
6
|
import { getBoolean, getStrings } from "./command.js";
|
|
7
|
-
/**
|
|
8
|
-
* Get the signature formats from the --format values, which can be repeated
|
|
9
|
-
* or comma-separated.
|
|
10
|
-
*
|
|
11
|
-
* @param values Option values.
|
|
12
|
-
* @return Unique formats, the default when none are given.
|
|
13
|
-
* @throws {ValidationError} If a format is unknown.
|
|
14
|
-
*/
|
|
15
|
-
export function parseSignatureFormats(values) {
|
|
16
|
-
const names = values
|
|
17
|
-
.flatMap((value) => value.split(','))
|
|
18
|
-
.map((name) => name.trim())
|
|
19
|
-
.filter(Boolean);
|
|
20
|
-
const unknown = names.filter((name) => !isSignatureFormat(name));
|
|
21
|
-
if (unknown.length) {
|
|
22
|
-
throw new ValidationError(`Unknown signature format ${unknown.join(', ')}, use ${Object.keys(SIGNATURE_FORMATS).join(', ')}.`);
|
|
23
|
-
}
|
|
24
|
-
return names.length
|
|
25
|
-
? [...new Set(names.filter(isSignatureFormat))]
|
|
26
|
-
: [DEFAULT_SIGNATURE_FORMAT];
|
|
27
|
-
}
|
|
28
7
|
export default {
|
|
29
8
|
name: 'sign',
|
|
30
9
|
usage: 'sign <file>... [options]',
|
|
@@ -58,34 +37,18 @@ form of each format.`,
|
|
|
58
37
|
force: { type: 'boolean' },
|
|
59
38
|
},
|
|
60
39
|
async run({ values, positionals, cwd, logger, }) {
|
|
61
|
-
const encoded = process.env[SIGNING_KEY_ENV];
|
|
62
40
|
const formats = parseSignatureFormats(getStrings(values, 'format'));
|
|
63
41
|
if (!positionals.length) {
|
|
64
42
|
throw new ValidationError('Pass the files to sign.');
|
|
65
43
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!(await stat(path).catch(() => null))?.isFile()) {
|
|
75
|
-
throw new ValidationError(`${path} is not a file.`);
|
|
76
|
-
}
|
|
77
|
-
}));
|
|
78
|
-
formats.forEach((format) => logger.debug(`Signing ${format} with the public key ${publicKeys[format]}.`));
|
|
79
|
-
const signatures = [];
|
|
80
|
-
const force = getBoolean(values, 'force');
|
|
81
|
-
const jobs = paths.flatMap((path) => formats.map((format) => ({ path, format })));
|
|
82
|
-
// Sign in order, so that a failure leaves a predictable set of files.
|
|
83
|
-
for (const { path, format } of jobs) {
|
|
84
|
-
const signaturePath = await writeSignature(path, privateKey, force, format);
|
|
85
|
-
logger.info(signaturePath);
|
|
86
|
-
signatures.push(signaturePath);
|
|
87
|
-
}
|
|
88
|
-
const result = { signatures, formats, publicKey, publicKeys };
|
|
44
|
+
const signed = await signFiles({
|
|
45
|
+
paths: positionals.map((file) => resolve(cwd, file)),
|
|
46
|
+
formats,
|
|
47
|
+
force: getBoolean(values, 'force'),
|
|
48
|
+
});
|
|
49
|
+
formats.forEach((format) => logger.debug(`Signed ${format} with the public key ${signed.publicKeys[format]}.`));
|
|
50
|
+
signed.signatures.forEach((path) => logger.info(path));
|
|
51
|
+
const result = { ...signed, formats };
|
|
89
52
|
logger.summary((context) => getSignSummary(context, result));
|
|
90
53
|
return { exitCode: 0, result };
|
|
91
54
|
},
|
package/dist/commands/zip.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export interface ZipResult extends ZipPackageResult {
|
|
|
8
8
|
version: string | null;
|
|
9
9
|
/** Zipped package, with --package. */
|
|
10
10
|
package?: string;
|
|
11
|
+
/** Absolute paths of the signatures of the ZIP and the manifest, with --sign. */
|
|
12
|
+
signatures?: string[];
|
|
13
|
+
/** Base64 public key of the signing key, with --sign. */
|
|
14
|
+
publicKey?: string;
|
|
11
15
|
}
|
|
12
16
|
declare const _default: {
|
|
13
17
|
name: string;
|
|
@@ -21,6 +25,13 @@ declare const _default: {
|
|
|
21
25
|
checksums: {
|
|
22
26
|
type: "boolean";
|
|
23
27
|
};
|
|
28
|
+
sign: {
|
|
29
|
+
type: "boolean";
|
|
30
|
+
};
|
|
31
|
+
'sign-format': {
|
|
32
|
+
type: "string";
|
|
33
|
+
multiple: true;
|
|
34
|
+
};
|
|
24
35
|
force: {
|
|
25
36
|
type: "boolean";
|
|
26
37
|
};
|
package/dist/commands/zip.js
CHANGED
|
@@ -4,9 +4,10 @@ import { resolveFileSelection } from "../file-selection.js";
|
|
|
4
4
|
import { FILENAME_FORMAT, formatFilename } from "../filename.js";
|
|
5
5
|
import { getPackageFiles } from "../package-deploy.js";
|
|
6
6
|
import { readPackageMeta } from "../package-meta.js";
|
|
7
|
-
import { zipPackage } from "../package-zip.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { getChecksumsPath, zipPackage } from "../package-zip.js";
|
|
8
|
+
import { SIGNING_KEY_ENV, checkSigningInput, signFiles } from "../signing.js";
|
|
9
|
+
import { getSignSummary, getZipSummary } from "../summary.js";
|
|
10
|
+
import { checkDirectory, fileSelectionArgs, getFileSelectionInput, getSourceDirArg, parseSignatureFormats, resolveOutput, sourceDirArgs, } from "./args.js";
|
|
10
11
|
import { getBoolean, getString, getStrings } from "./command.js";
|
|
11
12
|
/** Options of a source directory and its files, which --package takes from the config. */
|
|
12
13
|
const SOURCE_FLAGS = [
|
|
@@ -134,6 +135,14 @@ Options:
|
|
|
134
135
|
--checksums Also write the checksums manifest of the archived
|
|
135
136
|
files next to the ZIP, with .checksums.json
|
|
136
137
|
instead of .zip
|
|
138
|
+
--sign Also sign the ZIP and the checksums manifest with
|
|
139
|
+
the ${SIGNING_KEY_ENV} key, like
|
|
140
|
+
"wp-release sign", writing a WordPress .sig next
|
|
141
|
+
to each
|
|
142
|
+
--sign-format <names> Signature formats of --sign, comma-separated or
|
|
143
|
+
repeated, like wordpress,minisign,ssh, as the
|
|
144
|
+
--format of "wp-release sign" (default:
|
|
145
|
+
wordpress); implies --sign
|
|
137
146
|
--force Overwrite existing output files
|
|
138
147
|
|
|
139
148
|
Entries are sorted and use a fixed timestamp and file mode, so the same files
|
|
@@ -155,6 +164,8 @@ Examples:
|
|
|
155
164
|
...fileSelectionArgs,
|
|
156
165
|
package: { type: 'string' },
|
|
157
166
|
checksums: { type: 'boolean' },
|
|
167
|
+
sign: { type: 'boolean' },
|
|
168
|
+
'sign-format': { type: 'string', multiple: true },
|
|
158
169
|
force: { type: 'boolean' },
|
|
159
170
|
},
|
|
160
171
|
async run({ values, positionals, cwd, configFile, logger, }) {
|
|
@@ -163,14 +174,31 @@ Examples:
|
|
|
163
174
|
? await getPackageSource(values, cwd, name, configFile)
|
|
164
175
|
: await getDirectorySource(values, positionals, cwd);
|
|
165
176
|
const filename = formatFilename(getString(values, 'filename-format') ?? FILENAME_FORMAT, { slug: source.slug, version: source.version });
|
|
177
|
+
const zipPath = resolveOutput({
|
|
178
|
+
cwd,
|
|
179
|
+
output: getString(values, 'output'),
|
|
180
|
+
filename: `${filename}.zip`,
|
|
181
|
+
});
|
|
182
|
+
const signFormats = getStrings(values, 'sign-format');
|
|
183
|
+
const signing = getBoolean(values, 'sign') || signFormats.length
|
|
184
|
+
? {
|
|
185
|
+
paths: [
|
|
186
|
+
zipPath,
|
|
187
|
+
...(getBoolean(values, 'checksums')
|
|
188
|
+
? [getChecksumsPath(zipPath)]
|
|
189
|
+
: []),
|
|
190
|
+
],
|
|
191
|
+
formats: parseSignatureFormats(signFormats),
|
|
192
|
+
force: getBoolean(values, 'force'),
|
|
193
|
+
}
|
|
194
|
+
: undefined;
|
|
195
|
+
if (signing) {
|
|
196
|
+
checkSigningInput(signing);
|
|
197
|
+
}
|
|
166
198
|
const zipped = await zipPackage({
|
|
167
199
|
sourceDir: source.sourceDir,
|
|
168
200
|
selection: source.selection,
|
|
169
|
-
zipPath
|
|
170
|
-
cwd,
|
|
171
|
-
output: getString(values, 'output'),
|
|
172
|
-
filename: `${filename}.zip`,
|
|
173
|
-
}),
|
|
201
|
+
zipPath,
|
|
174
202
|
rootFolder: getBoolean(values, 'no-root-folder')
|
|
175
203
|
? null
|
|
176
204
|
: getString(values, 'root-folder') ?? source.slug,
|
|
@@ -185,13 +213,26 @@ Examples:
|
|
|
185
213
|
if (zipped.checksumsPath) {
|
|
186
214
|
logger.info(zipped.checksumsPath);
|
|
187
215
|
}
|
|
216
|
+
const signed = signing
|
|
217
|
+
? { ...(await signFiles(signing)), formats: signing.formats }
|
|
218
|
+
: undefined;
|
|
219
|
+
signed?.signatures.forEach((path) => logger.info(path));
|
|
188
220
|
const result = {
|
|
189
221
|
...zipped,
|
|
222
|
+
...(signed
|
|
223
|
+
? {
|
|
224
|
+
signatures: signed.signatures,
|
|
225
|
+
publicKey: signed.publicKey,
|
|
226
|
+
}
|
|
227
|
+
: {}),
|
|
190
228
|
slug: source.slug,
|
|
191
229
|
version: source.version,
|
|
192
230
|
...(source.package ? { package: source.package } : {}),
|
|
193
231
|
};
|
|
194
232
|
logger.summary((context) => getZipSummary(context, result));
|
|
233
|
+
if (signed) {
|
|
234
|
+
logger.summary((context) => getSignSummary(context, signed));
|
|
235
|
+
}
|
|
195
236
|
return { exitCode: 0, result };
|
|
196
237
|
},
|
|
197
238
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ 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 { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_EXTENSION, SIGNATURE_FORMATS, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getPublicKeys, getFileDigest, getSignaturePath, isSignatureFormat, signFile, writeSignature, verifyFileSignature, } from './signing.ts';
|
|
12
|
+
export { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_EXTENSION, SIGNATURE_FORMATS, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getPublicKeys, getFileDigest, getSignaturePath, isSignatureFormat, signFile, writeSignature, signFiles, verifyFileSignature, } from './signing.ts';
|
|
13
13
|
export type { SignatureFormat, SignatureFormatHandler } from './signing.ts';
|
|
14
14
|
export { MINISIGN_EXTENSION, createMinisignSignature, getMinisignPublicKey, parseMinisignPublicKey, verifyMinisignSignature, } from './minisign.ts';
|
|
15
15
|
export { SSH_SIGNATURE_EXTENSION, SSH_SIGNATURE_NAMESPACE, createSshSignature, getSshPublicKey, parseSshPublicKey, verifySshSignature, } from './ssh-signature.ts';
|
|
@@ -34,10 +34,11 @@ export type { PackageDeployOutcome } from './commands/package-deploys.ts';
|
|
|
34
34
|
export type { CliIo, CommandResults } from './cli.ts';
|
|
35
35
|
export type { ChecksumsResult } from './commands/checksums.ts';
|
|
36
36
|
export type { SignResult } from './commands/sign.ts';
|
|
37
|
+
export type { SignFilesInput, SignFilesResult } from './signing.ts';
|
|
37
38
|
export type { KeygenResult } from './commands/keygen.ts';
|
|
38
39
|
export type { ZipResult } from './commands/zip.ts';
|
|
39
40
|
export type { Command, CommandContext, CommandOutcome, ParsedValues, } from './commands/command.ts';
|
|
40
|
-
export type { DeployArgsInput } from './commands/deploy-svn.ts';
|
|
41
|
+
export type { DeployArgsInput, DeploySvnResult, } from './commands/deploy-svn.ts';
|
|
41
42
|
export type { Annotation, LogEntry, LogLevel, LogWriter, LoggerOptions, OutputStream, SummaryBuilder, TextWriterOptions, } from './logger.ts';
|
|
42
43
|
export type { GitHubActionsWriterOptions } from './github-actions-writer.ts';
|
|
43
44
|
export type { Problem } from './errors.ts';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ 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 { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_EXTENSION, SIGNATURE_FORMATS, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getPublicKeys, getFileDigest, getSignaturePath, isSignatureFormat, signFile, writeSignature, verifyFileSignature, } from "./signing.js";
|
|
12
|
+
export { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_EXTENSION, SIGNATURE_FORMATS, SIGNING_KEY_ENV, parseSigningKey, parsePublicKey, generateSigningKeyPair, getPublicKey, getPublicKeys, getFileDigest, getSignaturePath, isSignatureFormat, signFile, writeSignature, signFiles, verifyFileSignature, } from "./signing.js";
|
|
13
13
|
export { MINISIGN_EXTENSION, createMinisignSignature, getMinisignPublicKey, parseMinisignPublicKey, verifyMinisignSignature, } from "./minisign.js";
|
|
14
14
|
export { SSH_SIGNATURE_EXTENSION, SSH_SIGNATURE_NAMESPACE, createSshSignature, getSshPublicKey, parseSshPublicKey, verifySshSignature, } from "./ssh-signature.js";
|
|
15
15
|
export { ZIP_MTIME, ZIP_FILE_MODE, createZip } from "./zip.js";
|
package/dist/signing.d.ts
CHANGED
|
@@ -119,6 +119,42 @@ export declare function getPublicKeys(key: KeyObject): Record<SignatureFormat, s
|
|
|
119
119
|
* @throws {ValidationError} If the signature file exists and force isn't set.
|
|
120
120
|
*/
|
|
121
121
|
export declare function writeSignature(path: string, privateKey: KeyObject, force?: boolean, format?: SignatureFormat): Promise<string>;
|
|
122
|
+
export interface SignFilesInput {
|
|
123
|
+
/** Absolute paths of the files to sign. */
|
|
124
|
+
paths: string[];
|
|
125
|
+
/** Signature formats to write, the WordPress .sig by default. */
|
|
126
|
+
formats?: SignatureFormat[];
|
|
127
|
+
/** Base64 signing key, from the WP_RELEASE_SIGNING_KEY environment variable by default. */
|
|
128
|
+
key?: string;
|
|
129
|
+
/** Overwrite existing signature files. */
|
|
130
|
+
force?: boolean;
|
|
131
|
+
}
|
|
132
|
+
export interface SignFilesResult {
|
|
133
|
+
/** Absolute paths of the written signature files. */
|
|
134
|
+
signatures: string[];
|
|
135
|
+
/** Base64 public key of the signing key. */
|
|
136
|
+
publicKey: string;
|
|
137
|
+
/** Public key of the signing key in the form of each signature format. */
|
|
138
|
+
publicKeys: Record<SignatureFormat, string>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate the signing key and destinations before writing files. The input
|
|
142
|
+
* files need not exist yet, so zip can check before creating its artifacts.
|
|
143
|
+
*
|
|
144
|
+
* @param input Files, formats, key and whether to overwrite.
|
|
145
|
+
* @return Parsed signing key.
|
|
146
|
+
* @throws {ValidationError} Without a valid key or when a signature already exists without force.
|
|
147
|
+
*/
|
|
148
|
+
export declare function checkSigningInput(input: SignFilesInput): KeyObject;
|
|
149
|
+
/**
|
|
150
|
+
* Sign files with a signature file of each format next to each, in order,
|
|
151
|
+
* so that a failure leaves a predictable set of files.
|
|
152
|
+
*
|
|
153
|
+
* @param input Files, formats, key and whether to overwrite.
|
|
154
|
+
* @return The written signature files and the public keys.
|
|
155
|
+
* @throws {ValidationError} Without a key, for an invalid key, a missing file or an existing signature file without force.
|
|
156
|
+
*/
|
|
157
|
+
export declare function signFiles(input: SignFilesInput): Promise<SignFilesResult>;
|
|
122
158
|
/**
|
|
123
159
|
* Check a file against a signature file. A wordpress .sig file can hold one
|
|
124
160
|
* signature per line, and is valid when any of them match, so that a file
|
package/dist/signing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPrivateKey, generateKeyPairSync, sign, verify, } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { PUBLIC_KEY_BYTES, createRawPublicKey, digestFile, getRawPublicKey, } from "./ed25519.js";
|
|
4
5
|
import { ValidationError } from "./errors.js";
|
|
5
6
|
import { MINISIGN_EXTENSION, createMinisignSignature, getMinisignPublicKey, parseMinisignPublicKey, verifyMinisignSignature, } from "./minisign.js";
|
|
@@ -214,6 +215,56 @@ export async function writeSignature(path, privateKey, force = false, format = D
|
|
|
214
215
|
});
|
|
215
216
|
return signaturePath;
|
|
216
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Validate the signing key and destinations before writing files. The input
|
|
220
|
+
* files need not exist yet, so zip can check before creating its artifacts.
|
|
221
|
+
*
|
|
222
|
+
* @param input Files, formats, key and whether to overwrite.
|
|
223
|
+
* @return Parsed signing key.
|
|
224
|
+
* @throws {ValidationError} Without a valid key or when a signature already exists without force.
|
|
225
|
+
*/
|
|
226
|
+
export function checkSigningInput(input) {
|
|
227
|
+
const encoded = input.key ?? process.env[SIGNING_KEY_ENV];
|
|
228
|
+
const formats = input.formats ?? [DEFAULT_SIGNATURE_FORMAT];
|
|
229
|
+
if (!encoded?.trim()) {
|
|
230
|
+
throw new ValidationError(`Set the ${SIGNING_KEY_ENV} environment variable to the base64 Ed25519 signing key.`);
|
|
231
|
+
}
|
|
232
|
+
const privateKey = parseSigningKey(encoded);
|
|
233
|
+
const existing = input.paths
|
|
234
|
+
.flatMap((path) => formats.map((format) => getSignaturePath(path, format)))
|
|
235
|
+
.filter((path) => !input.force && existsSync(path));
|
|
236
|
+
if (existing.length) {
|
|
237
|
+
throw new ValidationError(`${existing.join(' and ')} already exists, pass --force to overwrite it.`);
|
|
238
|
+
}
|
|
239
|
+
return privateKey;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Sign files with a signature file of each format next to each, in order,
|
|
243
|
+
* so that a failure leaves a predictable set of files.
|
|
244
|
+
*
|
|
245
|
+
* @param input Files, formats, key and whether to overwrite.
|
|
246
|
+
* @return The written signature files and the public keys.
|
|
247
|
+
* @throws {ValidationError} Without a key, for an invalid key, a missing file or an existing signature file without force.
|
|
248
|
+
*/
|
|
249
|
+
export async function signFiles(input) {
|
|
250
|
+
const formats = input.formats ?? [DEFAULT_SIGNATURE_FORMAT];
|
|
251
|
+
const privateKey = checkSigningInput(input);
|
|
252
|
+
await Promise.all(input.paths.map(async (path) => {
|
|
253
|
+
if (!(await stat(path).catch(() => null))?.isFile()) {
|
|
254
|
+
throw new ValidationError(`${path} is not a file.`);
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
257
|
+
const signatures = [];
|
|
258
|
+
const jobs = input.paths.flatMap((path) => formats.map((format) => ({ path, format })));
|
|
259
|
+
for (const { path, format } of jobs) {
|
|
260
|
+
signatures.push(await writeSignature(path, privateKey, input.force, format));
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
signatures,
|
|
264
|
+
publicKey: getPublicKey(privateKey),
|
|
265
|
+
publicKeys: getPublicKeys(privateKey),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
217
268
|
/**
|
|
218
269
|
* Check a file against a signature file. A wordpress .sig file can hold one
|
|
219
270
|
* signature per line, and is valid when any of them match, so that a file
|
package/package.json
CHANGED