@wpelevator/wp-release 0.12.0 → 0.14.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 +150 -19
- package/dist/changeset-config.d.ts +91 -5
- package/dist/changeset-config.js +108 -11
- package/dist/changeset-dependents.d.ts +19 -8
- package/dist/changeset-dependents.js +96 -34
- package/dist/changeset-files.d.ts +6 -5
- package/dist/changeset-files.js +7 -5
- package/dist/changeset-packages.d.ts +55 -8
- package/dist/changeset-packages.js +85 -14
- package/dist/changeset-prompt.d.ts +119 -0
- package/dist/changeset-prompt.js +203 -0
- package/dist/changeset-release.d.ts +43 -5
- package/dist/changeset-release.js +99 -16
- package/dist/changesets.d.ts +6 -2
- package/dist/changesets.js +18 -12
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +51 -14
- package/dist/commands/args.d.ts +0 -21
- package/dist/commands/args.js +1 -35
- package/dist/commands/changeset-add.d.ts +5 -2
- package/dist/commands/changeset-add.js +109 -9
- package/dist/commands/changeset-args.d.ts +24 -5
- package/dist/commands/changeset-args.js +57 -14
- package/dist/commands/changeset-status.d.ts +3 -1
- package/dist/commands/changeset-status.js +2 -1
- package/dist/commands/changeset-tag.d.ts +1 -1
- package/dist/commands/changeset-tag.js +6 -3
- package/dist/commands/changeset-version.d.ts +7 -1
- package/dist/commands/changeset-version.js +17 -4
- package/dist/commands/checksums.js +2 -2
- package/dist/commands/command.d.ts +5 -0
- package/dist/commands/deploy-git.d.ts +24 -4
- package/dist/commands/deploy-git.js +57 -3
- package/dist/commands/deploy-svn.d.ts +24 -4
- package/dist/commands/deploy-svn.js +57 -2
- package/dist/commands/keygen.d.ts +3 -0
- package/dist/commands/keygen.js +6 -1
- package/dist/commands/package-deploys.d.ts +86 -0
- package/dist/commands/package-deploys.js +113 -0
- package/dist/commands/sign.d.ts +19 -1
- package/dist/commands/sign.js +47 -12
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +43 -24
- package/dist/commands/zip.d.ts +12 -8
- package/dist/commands/zip.js +135 -78
- package/dist/ed25519.d.ts +25 -0
- package/dist/ed25519.js +44 -0
- package/dist/file-selection.d.ts +21 -0
- package/dist/file-selection.js +35 -0
- package/dist/git-deploy.d.ts +32 -0
- package/dist/git-deploy.js +57 -1
- package/dist/git.d.ts +17 -0
- package/dist/git.js +23 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.js +7 -3
- package/dist/minisign.d.ts +41 -0
- package/dist/minisign.js +128 -0
- package/dist/package-deploy.d.ts +133 -0
- package/dist/package-deploy.js +323 -0
- package/dist/package-zip.d.ts +52 -0
- package/dist/package-zip.js +78 -0
- package/dist/release-repository.d.ts +18 -5
- package/dist/release-repository.js +51 -11
- package/dist/release-tags.d.ts +3 -1
- package/dist/release-tags.js +5 -2
- package/dist/signing.d.ts +64 -9
- package/dist/signing.js +113 -40
- package/dist/ssh-signature.d.ts +43 -0
- package/dist/ssh-signature.js +156 -0
- package/dist/summary.d.ts +3 -3
- package/dist/summary.js +2 -2
- package/dist/tag-format.d.ts +23 -14
- package/dist/tag-format.js +26 -40
- package/dist/version-files.d.ts +8 -8
- package/dist/version-files.js +22 -18
- package/dist/versions.d.ts +28 -3
- package/dist/versions.js +84 -9
- package/package.json +2 -2
package/dist/commands/sign.js
CHANGED
|
@@ -2,8 +2,29 @@ import { resolve } from 'node:path';
|
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
3
|
import { ValidationError } from "../errors.js";
|
|
4
4
|
import { getSignSummary } from "../summary.js";
|
|
5
|
-
import { SIGNING_KEY_ENV, getPublicKey, parseSigningKey, writeSignature, } from "../signing.js";
|
|
6
|
-
import { getBoolean } from "./command.js";
|
|
5
|
+
import { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_FORMATS, SIGNING_KEY_ENV, getPublicKey, getPublicKeys, isSignatureFormat, parseSigningKey, writeSignature, } from "../signing.js";
|
|
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
|
+
}
|
|
7
28
|
export default {
|
|
8
29
|
name: 'sign',
|
|
9
30
|
usage: 'sign <file>... [options]',
|
|
@@ -13,21 +34,32 @@ export default {
|
|
|
13
34
|
manifest
|
|
14
35
|
|
|
15
36
|
Options:
|
|
16
|
-
--
|
|
37
|
+
--format <names> Signature formats to write, comma-separated or
|
|
38
|
+
repeated, like wordpress,minisign,ssh (default:
|
|
39
|
+
wordpress):
|
|
40
|
+
wordpress <file>.sig for WordPress core's
|
|
41
|
+
verify_file_signature()
|
|
42
|
+
minisign <file>.minisig for "minisign -V"
|
|
43
|
+
ssh <file>.sshsig for "ssh-keygen -Y verify"
|
|
44
|
+
with the "file" namespace
|
|
45
|
+
--force Overwrite existing signature files
|
|
17
46
|
|
|
18
47
|
The signing key is read from the ${SIGNING_KEY_ENV} environment variable, as a
|
|
19
48
|
base64 32-byte Ed25519 seed or 64-byte libsodium secret key. There's no flag
|
|
20
49
|
for it, since it would show in the process list.
|
|
21
50
|
|
|
22
|
-
The signature is the one WordPress core's verify_file_signature()
|
|
23
|
-
base64 Ed25519 signature of the raw SHA-384 digest of the file.
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
The wordpress signature is the one WordPress core's verify_file_signature()
|
|
52
|
+
checks: a base64 Ed25519 signature of the raw SHA-384 digest of the file.
|
|
53
|
+
All formats are signed by the same key. Sign both the ZIP and the checksums
|
|
54
|
+
manifest. Use --verbose to print the public key of the signing key in the
|
|
55
|
+
form of each format.`,
|
|
26
56
|
options: {
|
|
57
|
+
format: { type: 'string', multiple: true },
|
|
27
58
|
force: { type: 'boolean' },
|
|
28
59
|
},
|
|
29
60
|
async run({ values, positionals, cwd, logger, }) {
|
|
30
61
|
const encoded = process.env[SIGNING_KEY_ENV];
|
|
62
|
+
const formats = parseSignatureFormats(getStrings(values, 'format'));
|
|
31
63
|
if (!positionals.length) {
|
|
32
64
|
throw new ValidationError('Pass the files to sign.');
|
|
33
65
|
}
|
|
@@ -36,22 +68,25 @@ that belongs to the signing key.`,
|
|
|
36
68
|
}
|
|
37
69
|
const privateKey = parseSigningKey(encoded);
|
|
38
70
|
const publicKey = getPublicKey(privateKey);
|
|
71
|
+
const publicKeys = getPublicKeys(privateKey);
|
|
39
72
|
const paths = positionals.map((file) => resolve(cwd, file));
|
|
40
73
|
await Promise.all(paths.map(async (path) => {
|
|
41
74
|
if (!(await stat(path).catch(() => null))?.isFile()) {
|
|
42
75
|
throw new ValidationError(`${path} is not a file.`);
|
|
43
76
|
}
|
|
44
77
|
}));
|
|
45
|
-
logger.debug(`Signing with the public key ${
|
|
78
|
+
formats.forEach((format) => logger.debug(`Signing ${format} with the public key ${publicKeys[format]}.`));
|
|
46
79
|
const signatures = [];
|
|
47
80
|
const force = getBoolean(values, 'force');
|
|
81
|
+
const jobs = paths.flatMap((path) => formats.map((format) => ({ path, format })));
|
|
48
82
|
// Sign in order, so that a failure leaves a predictable set of files.
|
|
49
|
-
for (const path of
|
|
50
|
-
const signaturePath = await writeSignature(path, privateKey, force);
|
|
83
|
+
for (const { path, format } of jobs) {
|
|
84
|
+
const signaturePath = await writeSignature(path, privateKey, force, format);
|
|
51
85
|
logger.info(signaturePath);
|
|
52
86
|
signatures.push(signaturePath);
|
|
53
87
|
}
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
const result = { signatures, formats, publicKey, publicKeys };
|
|
89
|
+
logger.summary((context) => getSignSummary(context, result));
|
|
90
|
+
return { exitCode: 0, result };
|
|
56
91
|
},
|
|
57
92
|
};
|
package/dist/commands/verify.js
CHANGED
|
@@ -4,28 +4,29 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { basename, isAbsolute, join, posix, resolve } from 'node:path';
|
|
5
5
|
import { readChecksums, verifyChecksums } from "../checksums.js";
|
|
6
6
|
import { downloadFile, isUrl } from "../download.js";
|
|
7
|
-
import { loadFileRules, parseExclude } from "../file-selection.js";
|
|
7
|
+
import { ignoreOwnFile, ignoreOwnSignature, loadFileRules, parseExclude, } from "../file-selection.js";
|
|
8
8
|
import { ValidationError } from "../errors.js";
|
|
9
9
|
import { readPackageMeta } from "../package-meta.js";
|
|
10
10
|
import { logProblems } from "../report.js";
|
|
11
11
|
import { getVerifySummary } from "../summary.js";
|
|
12
12
|
import { extractZip, readZipEntries } from "../unzip.js";
|
|
13
|
-
import {
|
|
14
|
-
import { checkDirectory, getSourceDirArg,
|
|
13
|
+
import { DEFAULT_SIGNATURE_FORMAT, SIGNATURE_FORMATS, getSignaturePath, isSignatureFormat, parsePublicKey, verifyFileSignature, } from "../signing.js";
|
|
14
|
+
import { checkDirectory, getSourceDirArg, sourceDirArgs } from "./args.js";
|
|
15
15
|
import { getString, getStrings } from "./command.js";
|
|
16
16
|
/**
|
|
17
17
|
* Fail unless the manifest has a valid signature file.
|
|
18
18
|
*
|
|
19
19
|
* @param checksumsPath Manifest path.
|
|
20
|
-
* @param publicKey
|
|
20
|
+
* @param publicKey Public key in any form that parsePublicKey() takes.
|
|
21
|
+
* @param format Signature format.
|
|
21
22
|
* @throws {ValidationError} If the signature is missing or doesn't match.
|
|
22
23
|
*/
|
|
23
|
-
async function checkSignature(checksumsPath, publicKey) {
|
|
24
|
-
const signaturePath = checksumsPath
|
|
24
|
+
async function checkSignature(checksumsPath, publicKey, format) {
|
|
25
|
+
const signaturePath = getSignaturePath(checksumsPath, format);
|
|
25
26
|
if (!existsSync(signaturePath)) {
|
|
26
27
|
throw new ValidationError(`${signaturePath} doesn't exist.`);
|
|
27
28
|
}
|
|
28
|
-
if (!(await verifyFileSignature(checksumsPath, signaturePath, parsePublicKey(publicKey)))) {
|
|
29
|
+
if (!(await verifyFileSignature(checksumsPath, signaturePath, parsePublicKey(publicKey), format))) {
|
|
29
30
|
throw new ValidationError(`${signaturePath} isn't a valid signature of ${checksumsPath} for the public key.`);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -111,15 +112,16 @@ function getEmbeddedPath(checksumsPath, embedded) {
|
|
|
111
112
|
*
|
|
112
113
|
* @param checksumsPath Manifest path.
|
|
113
114
|
* @param path Relative path of the embedded manifest, if any.
|
|
115
|
+
* @param format Signature format.
|
|
114
116
|
* @return Pairs of the originals and their copies.
|
|
115
117
|
*/
|
|
116
|
-
function getCopyPairs(checksumsPath, path) {
|
|
118
|
+
function getCopyPairs(checksumsPath, path, format) {
|
|
117
119
|
return path
|
|
118
120
|
? [
|
|
119
121
|
{ original: checksumsPath, copy: path },
|
|
120
122
|
{
|
|
121
|
-
original: checksumsPath
|
|
122
|
-
copy: path
|
|
123
|
+
original: getSignaturePath(checksumsPath, format),
|
|
124
|
+
copy: getSignaturePath(path, format),
|
|
123
125
|
},
|
|
124
126
|
]
|
|
125
127
|
: [];
|
|
@@ -172,12 +174,13 @@ async function hasSameContents(original, file) {
|
|
|
172
174
|
* @param input.manifest Checksums manifest.
|
|
173
175
|
* @param input.checksumsPath Manifest path.
|
|
174
176
|
* @param input.embedded Explicit path of the embedded manifest.
|
|
177
|
+
* @param input.format Signature format of the manifest signature.
|
|
175
178
|
* @return The copies that were found to differ or be missing, and the paths of the existing copies.
|
|
176
179
|
* @throws {ValidationError} If an explicit path can't be verified.
|
|
177
180
|
*/
|
|
178
181
|
async function checkEmbeddedCopies(input) {
|
|
179
|
-
const { dir, manifest, checksumsPath, embedded } = input;
|
|
180
|
-
const pairs = getCopyPairs(checksumsPath, getEmbeddedPath(checksumsPath, embedded));
|
|
182
|
+
const { dir, manifest, checksumsPath, embedded, format } = input;
|
|
183
|
+
const pairs = getCopyPairs(checksumsPath, getEmbeddedPath(checksumsPath, embedded), format);
|
|
181
184
|
if (embedded) {
|
|
182
185
|
checkExplicitPairs(pairs, manifest);
|
|
183
186
|
}
|
|
@@ -268,17 +271,19 @@ async function findVersionMismatch(dir, manifest) {
|
|
|
268
271
|
* @param input.checksumsPath
|
|
269
272
|
* @param input.exclude
|
|
270
273
|
* @param input.embedded
|
|
274
|
+
* @param input.format
|
|
271
275
|
* @param input.logger
|
|
272
276
|
* @return Exit code and result.
|
|
273
277
|
*/
|
|
274
278
|
async function verifyTarget(input) {
|
|
275
|
-
const { target, manifest, checksumsPath, exclude, embedded, logger } = input;
|
|
279
|
+
const { target, manifest, checksumsPath, exclude, embedded, format, logger, } = input;
|
|
276
280
|
const { dir } = target;
|
|
277
281
|
const copies = await checkEmbeddedCopies({
|
|
278
282
|
dir,
|
|
279
283
|
manifest,
|
|
280
284
|
checksumsPath,
|
|
281
285
|
embedded,
|
|
286
|
+
format,
|
|
282
287
|
});
|
|
283
288
|
const rules = ignoreOwnSignature(ignoreOwnFile(await loadFileRules({ exclude: parseExclude(exclude) }), dir, checksumsPath), dir, checksumsPath);
|
|
284
289
|
copies.paths.forEach((copy) => ignoreOwnFile(rules, dir, join(dir, copy)));
|
|
@@ -338,17 +343,25 @@ Options:
|
|
|
338
343
|
builds for a release, instead of <source-dir>
|
|
339
344
|
--root-folder <name> Folder in the ZIP (default: the one folder that
|
|
340
345
|
every entry is in)
|
|
341
|
-
--embedded <path> Where the manifest and its
|
|
342
|
-
files, like includes/checksums.json. Both the
|
|
343
|
-
--checksums file and its
|
|
346
|
+
--embedded <path> Where the manifest and its signature are shipped in
|
|
347
|
+
the files, like includes/checksums.json. Both the
|
|
348
|
+
--checksums file and its signature must exist, both
|
|
344
349
|
copies must be shipped, and they must be identical
|
|
345
350
|
to them. Without this option, a file with the name
|
|
346
|
-
of the --checksums file at the root (and its
|
|
347
|
-
is a copy only when it exists, the
|
|
348
|
-
exists and the manifest doesn't
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
351
|
+
of the --checksums file at the root (and its
|
|
352
|
+
signature) is a copy only when it exists, the
|
|
353
|
+
matching original exists and the manifest doesn't
|
|
354
|
+
list it
|
|
355
|
+
--public-key <key> Ed25519 public key, as base64, a minisign public key
|
|
356
|
+
or an ssh-ed25519 public key. Checks the signature
|
|
357
|
+
of the manifest, as written by "wp-release sign",
|
|
358
|
+
before comparing any file, and fails when it's
|
|
359
|
+
missing or invalid
|
|
360
|
+
--signature-format <name>
|
|
361
|
+
Format of the manifest signature: wordpress
|
|
362
|
+
(<checksums>.sig), minisign (<checksums>.minisig)
|
|
363
|
+
or ssh (<checksums>.sshsig) (default: wordpress).
|
|
364
|
+
Also selects the signature copy for --embedded
|
|
352
365
|
--exclude <patterns> Gitignore-style patterns to skip in both the directory
|
|
353
366
|
and the manifest, comma-separated or repeated
|
|
354
367
|
|
|
@@ -361,7 +374,7 @@ Unlike "checksums" and "zip", verify doesn't read .distignore, so that an
|
|
|
361
374
|
ignore file inside the checked directory can't hide added files. Use
|
|
362
375
|
--exclude to skip files that aren't part of the release.
|
|
363
376
|
|
|
364
|
-
A manifest can't list itself. A copy of the manifest and of its
|
|
377
|
+
A manifest can't list itself. A copy of the manifest and of its signature in
|
|
365
378
|
the checked files is therefore skipped, but it must be identical to the
|
|
366
379
|
--checksums file. A path that the manifest lists is never skipped, and a
|
|
367
380
|
file that isn't a matching copy is reported as extra. See --embedded for
|
|
@@ -372,6 +385,7 @@ where the copies are looked for.`,
|
|
|
372
385
|
'root-folder': { type: 'string' },
|
|
373
386
|
embedded: { type: 'string' },
|
|
374
387
|
'public-key': { type: 'string' },
|
|
388
|
+
'signature-format': { type: 'string' },
|
|
375
389
|
...sourceDirArgs,
|
|
376
390
|
exclude: { type: 'string', multiple: true },
|
|
377
391
|
},
|
|
@@ -389,8 +403,12 @@ where the copies are looked for.`,
|
|
|
389
403
|
}
|
|
390
404
|
const checksumsPath = resolve(cwd, checksums);
|
|
391
405
|
const publicKey = getString(values, 'public-key');
|
|
406
|
+
const format = getString(values, 'signature-format') ?? DEFAULT_SIGNATURE_FORMAT;
|
|
407
|
+
if (!isSignatureFormat(format)) {
|
|
408
|
+
throw new ValidationError(`Unknown signature format ${format}, use ${Object.keys(SIGNATURE_FORMATS).join(', ')}.`);
|
|
409
|
+
}
|
|
392
410
|
if (publicKey) {
|
|
393
|
-
await checkSignature(checksumsPath, publicKey);
|
|
411
|
+
await checkSignature(checksumsPath, publicKey, format);
|
|
394
412
|
logger.info(`Signature of ${checksumsPath} is valid.`);
|
|
395
413
|
}
|
|
396
414
|
const manifest = await readChecksums(checksumsPath);
|
|
@@ -407,6 +425,7 @@ where the copies are looked for.`,
|
|
|
407
425
|
checksumsPath,
|
|
408
426
|
exclude: getStrings(values, 'exclude'),
|
|
409
427
|
embedded: getString(values, 'embedded'),
|
|
428
|
+
format,
|
|
410
429
|
logger,
|
|
411
430
|
});
|
|
412
431
|
}
|
package/dist/commands/zip.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import type { ZipPackageResult } from '../package-zip.ts';
|
|
1
2
|
import type { CommandOutcome } from './command.ts';
|
|
2
3
|
/** Result of the zip command. */
|
|
3
|
-
export interface ZipResult {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
4
|
+
export interface ZipResult extends ZipPackageResult {
|
|
5
|
+
/** Plugin or theme slug. */
|
|
6
|
+
slug: string;
|
|
7
|
+
/** Version in the filename and the checksums manifest, null when it's unknown. */
|
|
8
|
+
version: string | null;
|
|
9
|
+
/** Zipped package, with --package. */
|
|
10
|
+
package?: string;
|
|
10
11
|
}
|
|
11
12
|
declare const _default: {
|
|
12
13
|
name: string;
|
|
@@ -14,6 +15,9 @@ declare const _default: {
|
|
|
14
15
|
summary: string;
|
|
15
16
|
help: string;
|
|
16
17
|
options: {
|
|
18
|
+
package: {
|
|
19
|
+
type: "string";
|
|
20
|
+
};
|
|
17
21
|
checksums: {
|
|
18
22
|
type: "boolean";
|
|
19
23
|
};
|
|
@@ -39,6 +43,6 @@ declare const _default: {
|
|
|
39
43
|
type: "string";
|
|
40
44
|
};
|
|
41
45
|
};
|
|
42
|
-
run({ values, positionals, cwd, logger, }: import("./command.ts").CommandContext): Promise<CommandOutcome<ZipResult>>;
|
|
46
|
+
run({ values, positionals, cwd, configFile, logger, }: import("./command.ts").CommandContext): Promise<CommandOutcome<ZipResult>>;
|
|
43
47
|
};
|
|
44
48
|
export default _default;
|
package/dist/commands/zip.js
CHANGED
|
@@ -1,49 +1,122 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
1
|
import { basename, resolve } from 'node:path';
|
|
3
|
-
import { CHECKSUMS_EXTENSION, createChecksums, writeChecksums, } from "../checksums.js";
|
|
4
2
|
import { ValidationError } from "../errors.js";
|
|
5
|
-
import {
|
|
3
|
+
import { resolveFileSelection } from "../file-selection.js";
|
|
6
4
|
import { FILENAME_FORMAT, formatFilename } from "../filename.js";
|
|
5
|
+
import { getPackageFiles } from "../package-deploy.js";
|
|
7
6
|
import { readPackageMeta } from "../package-meta.js";
|
|
7
|
+
import { zipPackage } from "../package-zip.js";
|
|
8
8
|
import { getZipSummary } from "../summary.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
const
|
|
9
|
+
import { checkDirectory, fileSelectionArgs, getFileSelectionInput, getSourceDirArg, resolveOutput, sourceDirArgs, } from "./args.js";
|
|
10
|
+
import { getBoolean, getString, getStrings } from "./command.js";
|
|
11
|
+
/** Options of a source directory and its files, which --package takes from the config. */
|
|
12
|
+
const SOURCE_FLAGS = [
|
|
13
|
+
'source-dir',
|
|
14
|
+
'distignore',
|
|
15
|
+
'no-distignore',
|
|
16
|
+
'exclude',
|
|
17
|
+
'exclude-gitattributes',
|
|
18
|
+
];
|
|
13
19
|
/**
|
|
14
|
-
* Get the
|
|
15
|
-
*
|
|
20
|
+
* Get the configured package to zip, failing for the options that set the
|
|
21
|
+
* source directory and the files instead.
|
|
16
22
|
*
|
|
17
|
-
* @param
|
|
18
|
-
* @
|
|
23
|
+
* @param values Parsed CLI values.
|
|
24
|
+
* @param positionals Positional arguments.
|
|
25
|
+
* @return The package identifier or path, or undefined without --package.
|
|
26
|
+
* @throws {ValidationError} For an empty --package, or --package with a source directory or file selection option.
|
|
19
27
|
*/
|
|
20
|
-
function
|
|
21
|
-
const name =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
function getPackageArg(values, positionals) {
|
|
29
|
+
const name = getString(values, 'package')?.trim();
|
|
30
|
+
const conflicts = SOURCE_FLAGS.filter((flag) => undefined !== getString(values, flag) ||
|
|
31
|
+
getBoolean(values, flag) ||
|
|
32
|
+
getStrings(values, flag).length);
|
|
33
|
+
if ('' === name) {
|
|
34
|
+
throw new ValidationError('--package <name> must not be empty.');
|
|
35
|
+
}
|
|
36
|
+
if (name && (positionals.length || conflicts.length)) {
|
|
37
|
+
throw new ValidationError(`--package takes the directory and files of the package from the config, so it can't be combined with ${positionals.length
|
|
38
|
+
? 'a source directory'
|
|
39
|
+
: conflicts.map((flag) => `--${flag}`).join(', ')}.`);
|
|
40
|
+
}
|
|
41
|
+
return name;
|
|
25
42
|
}
|
|
26
43
|
/**
|
|
27
|
-
*
|
|
44
|
+
* Describe the files of a directory with the slug and the version from the
|
|
45
|
+
* options, or from the plugin or theme headers.
|
|
28
46
|
*
|
|
29
|
-
* @param
|
|
30
|
-
* @param
|
|
31
|
-
* @
|
|
47
|
+
* @param values Parsed CLI values.
|
|
48
|
+
* @param sourceDir Directory to zip.
|
|
49
|
+
* @param selection File selection.
|
|
50
|
+
* @param slug Default slug.
|
|
51
|
+
* @return Source directory, file selection, slug, version and type.
|
|
32
52
|
*/
|
|
33
|
-
function
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
async function describeSource(values, sourceDir, selection, slug) {
|
|
54
|
+
const name = getString(values, 'slug') ?? slug;
|
|
55
|
+
const meta = await readPackageMeta({ dir: sourceDir, slug: name });
|
|
56
|
+
return {
|
|
57
|
+
sourceDir,
|
|
58
|
+
selection,
|
|
59
|
+
slug: name,
|
|
60
|
+
version: getString(values, 'version') ?? meta?.version ?? null,
|
|
61
|
+
...(meta ? { type: meta.type } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the files of a source directory from the CLI options.
|
|
66
|
+
*
|
|
67
|
+
* @param values Parsed CLI values.
|
|
68
|
+
* @param positionals Positional arguments.
|
|
69
|
+
* @param cwd Working directory.
|
|
70
|
+
* @return Source directory, file selection, slug and version.
|
|
71
|
+
*/
|
|
72
|
+
async function getDirectorySource(values, positionals, cwd) {
|
|
73
|
+
const sourceDir = resolve(cwd, getSourceDirArg(values, positionals));
|
|
74
|
+
await checkDirectory(sourceDir);
|
|
75
|
+
return describeSource(values, sourceDir, resolveFileSelection({
|
|
76
|
+
cwd,
|
|
77
|
+
sourceDir,
|
|
78
|
+
...getFileSelectionInput(values),
|
|
79
|
+
}), basename(cwd));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the directory of a configured package with the files config of its
|
|
83
|
+
* package group, which its deploys use too.
|
|
84
|
+
*
|
|
85
|
+
* @param values Parsed CLI values.
|
|
86
|
+
* @param cwd Repository root with the wp-release config.
|
|
87
|
+
* @param name Package identifier or path under the configured root.
|
|
88
|
+
* @param configFile Config file, instead of the conventional one.
|
|
89
|
+
* @return Source directory, file selection, slug, version and package.
|
|
90
|
+
* @throws {ValidationError} Without a config or for an unknown package.
|
|
91
|
+
*/
|
|
92
|
+
async function getPackageSource(values, cwd, name, configFile) {
|
|
93
|
+
const pkg = await getPackageFiles({
|
|
94
|
+
cwd,
|
|
95
|
+
package: name,
|
|
96
|
+
...(configFile ? { configFile } : {}),
|
|
97
|
+
});
|
|
98
|
+
const source = await describeSource(values, pkg.directory, resolveFileSelection({
|
|
99
|
+
cwd,
|
|
100
|
+
sourceDir: pkg.directory,
|
|
101
|
+
...pkg.files,
|
|
102
|
+
}), basename(pkg.directory));
|
|
103
|
+
return { ...source, package: pkg.name };
|
|
38
104
|
}
|
|
39
105
|
export default {
|
|
40
106
|
name: 'zip',
|
|
41
|
-
usage: 'zip [<source-dir>] [options]',
|
|
107
|
+
usage: 'zip [<source-dir>] [options] | zip --package <name> [options]',
|
|
42
108
|
summary: 'Create a ZIP archive of a plugin or theme with a {slug}/ root folder.',
|
|
43
109
|
help: `Arguments:
|
|
44
110
|
<source-dir> Directory to archive (default: .), also accepted
|
|
45
111
|
as --source-dir <dir>
|
|
46
112
|
|
|
113
|
+
Configured packages (instead of the source directory and the file selection
|
|
114
|
+
options):
|
|
115
|
+
--package <name> Zip this package of the wp-release config, by
|
|
116
|
+
identifier or path under the configured root,
|
|
117
|
+
like plugins/alpha, with the files config of its
|
|
118
|
+
package group, the same files as its deploys
|
|
119
|
+
|
|
47
120
|
Options:
|
|
48
121
|
--output <file> ZIP file (default: the --filename-format name with
|
|
49
122
|
.zip in the current directory)
|
|
@@ -64,7 +137,13 @@ Options:
|
|
|
64
137
|
--force Overwrite existing output files
|
|
65
138
|
|
|
66
139
|
Entries are sorted and use a fixed timestamp and file mode, so the same files
|
|
67
|
-
always give the same ZIP
|
|
140
|
+
always give the same ZIP.
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
wp-release zip dist --slug my-plugin --checksums
|
|
144
|
+
Zip a build with its checksums manifest
|
|
145
|
+
wp-release zip --package plugins/alpha --checksums
|
|
146
|
+
Zip a package of the config with its configured files`,
|
|
68
147
|
options: {
|
|
69
148
|
slug: { type: 'string' },
|
|
70
149
|
version: { type: 'string' },
|
|
@@ -74,65 +153,43 @@ always give the same ZIP.`,
|
|
|
74
153
|
output: { type: 'string' },
|
|
75
154
|
...sourceDirArgs,
|
|
76
155
|
...fileSelectionArgs,
|
|
156
|
+
package: { type: 'string' },
|
|
77
157
|
checksums: { type: 'boolean' },
|
|
78
158
|
force: { type: 'boolean' },
|
|
79
159
|
},
|
|
80
|
-
async run({ values, positionals, cwd, logger, }) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
? getChecksumsPath(zipPath)
|
|
95
|
-
: null;
|
|
96
|
-
const selection = resolveFileSelection({
|
|
97
|
-
cwd,
|
|
98
|
-
sourceDir,
|
|
99
|
-
...getFileSelectionInput(values),
|
|
100
|
-
});
|
|
101
|
-
const rules = ignoreOwnSignature(ignoreOwnFile(await loadFileRules(selection), sourceDir, zipPath), sourceDir, zipPath);
|
|
102
|
-
if (checksumsPath) {
|
|
103
|
-
ignoreOwnSignature(ignoreOwnFile(rules, sourceDir, checksumsPath), sourceDir, checksumsPath);
|
|
104
|
-
}
|
|
105
|
-
const files = await selectFiles(sourceDir, rules);
|
|
106
|
-
if (!files.length) {
|
|
107
|
-
throw new ValidationError(`No files to archive in ${sourceDir}.`);
|
|
108
|
-
}
|
|
109
|
-
checkOverwrite([zipPath, checksumsPath ?? ''].filter(Boolean), force);
|
|
110
|
-
await createZip({
|
|
111
|
-
sourceDir,
|
|
112
|
-
files,
|
|
113
|
-
zipPath,
|
|
160
|
+
async run({ values, positionals, cwd, configFile, logger, }) {
|
|
161
|
+
const name = getPackageArg(values, positionals);
|
|
162
|
+
const source = name
|
|
163
|
+
? await getPackageSource(values, cwd, name, configFile)
|
|
164
|
+
: await getDirectorySource(values, positionals, cwd);
|
|
165
|
+
const filename = formatFilename(getString(values, 'filename-format') ?? FILENAME_FORMAT, { slug: source.slug, version: source.version });
|
|
166
|
+
const zipped = await zipPackage({
|
|
167
|
+
sourceDir: source.sourceDir,
|
|
168
|
+
selection: source.selection,
|
|
169
|
+
zipPath: resolveOutput({
|
|
170
|
+
cwd,
|
|
171
|
+
output: getString(values, 'output'),
|
|
172
|
+
filename: `${filename}.zip`,
|
|
173
|
+
}),
|
|
114
174
|
rootFolder: getBoolean(values, 'no-root-folder')
|
|
115
175
|
? null
|
|
116
|
-
: getString(values, 'root-folder') ?? slug,
|
|
117
|
-
|
|
176
|
+
: getString(values, 'root-folder') ?? source.slug,
|
|
177
|
+
slug: source.slug,
|
|
178
|
+
version: source.version,
|
|
179
|
+
...(source.type ? { type: source.type } : {}),
|
|
180
|
+
checksums: getBoolean(values, 'checksums'),
|
|
181
|
+
force: getBoolean(values, 'force'),
|
|
118
182
|
});
|
|
119
|
-
logger.debug(`Archived ${files
|
|
120
|
-
logger.info(zipPath);
|
|
121
|
-
if (checksumsPath) {
|
|
122
|
-
|
|
123
|
-
sourceDir,
|
|
124
|
-
rules,
|
|
125
|
-
slug,
|
|
126
|
-
version,
|
|
127
|
-
type: meta?.type,
|
|
128
|
-
});
|
|
129
|
-
await writeChecksums(checksumsPath, manifest, force);
|
|
130
|
-
logger.info(checksumsPath);
|
|
183
|
+
logger.debug(`Archived ${zipped.files} files from ${source.sourceDir}.`);
|
|
184
|
+
logger.info(zipped.zipPath);
|
|
185
|
+
if (zipped.checksumsPath) {
|
|
186
|
+
logger.info(zipped.checksumsPath);
|
|
131
187
|
}
|
|
132
188
|
const result = {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
189
|
+
...zipped,
|
|
190
|
+
slug: source.slug,
|
|
191
|
+
version: source.version,
|
|
192
|
+
...(source.package ? { package: source.package } : {}),
|
|
136
193
|
};
|
|
137
194
|
logger.summary((context) => getZipSummary(context, result));
|
|
138
195
|
return { exitCode: 0, result };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { KeyObject } from 'node:crypto';
|
|
2
|
+
export declare const PUBLIC_KEY_BYTES = 32;
|
|
3
|
+
/**
|
|
4
|
+
* Get the raw 32 bytes of an Ed25519 public key.
|
|
5
|
+
*
|
|
6
|
+
* @param key Private or public key.
|
|
7
|
+
* @return Public key bytes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getRawPublicKey(key: KeyObject): Buffer;
|
|
10
|
+
/**
|
|
11
|
+
* Get an Ed25519 public key from its raw 32 bytes.
|
|
12
|
+
*
|
|
13
|
+
* @param bytes Public key bytes.
|
|
14
|
+
* @return Public key.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createRawPublicKey(bytes: Buffer): KeyObject;
|
|
17
|
+
/**
|
|
18
|
+
* Get the raw digest of a file, which the signature formats sign instead of
|
|
19
|
+
* the whole file.
|
|
20
|
+
*
|
|
21
|
+
* @param path File path.
|
|
22
|
+
* @param algorithm Node.js hash algorithm, like sha384 or blake2b512.
|
|
23
|
+
* @return Digest bytes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function digestFile(path: string, algorithm: string): Promise<Buffer>;
|
package/dist/ed25519.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createHash, createPublicKey } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
export const PUBLIC_KEY_BYTES = 32;
|
|
4
|
+
// DER prefix of a SPKI Ed25519 public key, followed by the 32-byte key.
|
|
5
|
+
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
6
|
+
/**
|
|
7
|
+
* Get the raw 32 bytes of an Ed25519 public key.
|
|
8
|
+
*
|
|
9
|
+
* @param key Private or public key.
|
|
10
|
+
* @return Public key bytes.
|
|
11
|
+
*/
|
|
12
|
+
export function getRawPublicKey(key) {
|
|
13
|
+
return ('private' === key.type ? createPublicKey(key) : key)
|
|
14
|
+
.export({ format: 'der', type: 'spki' })
|
|
15
|
+
.subarray(SPKI_PREFIX.length);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get an Ed25519 public key from its raw 32 bytes.
|
|
19
|
+
*
|
|
20
|
+
* @param bytes Public key bytes.
|
|
21
|
+
* @return Public key.
|
|
22
|
+
*/
|
|
23
|
+
export function createRawPublicKey(bytes) {
|
|
24
|
+
return createPublicKey({
|
|
25
|
+
key: Buffer.concat([SPKI_PREFIX, bytes]),
|
|
26
|
+
format: 'der',
|
|
27
|
+
type: 'spki',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the raw digest of a file, which the signature formats sign instead of
|
|
32
|
+
* the whole file.
|
|
33
|
+
*
|
|
34
|
+
* @param path File path.
|
|
35
|
+
* @param algorithm Node.js hash algorithm, like sha384 or blake2b512.
|
|
36
|
+
* @return Digest bytes.
|
|
37
|
+
*/
|
|
38
|
+
export async function digestFile(path, algorithm) {
|
|
39
|
+
const hash = createHash(algorithm);
|
|
40
|
+
for await (const chunk of createReadStream(path)) {
|
|
41
|
+
hash.update(chunk);
|
|
42
|
+
}
|
|
43
|
+
return hash.digest();
|
|
44
|
+
}
|