@wpelevator/wp-release 0.14.0 → 0.16.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 +256 -36
- package/dist/changeset-config.d.ts +3 -0
- package/dist/changeset-release.d.ts +9 -3
- package/dist/changeset-release.js +23 -14
- package/dist/cli.d.ts +17 -7
- package/dist/cli.js +27 -2
- package/dist/commands/args.d.ts +11 -0
- package/dist/commands/args.js +23 -0
- package/dist/commands/artifact-signing.d.ts +20 -0
- package/dist/commands/artifact-signing.js +42 -0
- package/dist/commands/build.d.ts +32 -0
- package/dist/commands/build.js +91 -0
- package/dist/commands/changeset-add.d.ts +55 -6
- package/dist/commands/changeset-add.js +12 -3
- package/dist/commands/changeset-args.js +2 -2
- package/dist/commands/changeset-project-args.d.ts +63 -0
- package/dist/commands/changeset-project-args.js +114 -0
- package/dist/commands/changeset-status.d.ts +50 -1
- package/dist/commands/changeset-status.js +4 -2
- package/dist/commands/changeset-tag.d.ts +55 -6
- package/dist/commands/changeset-tag.js +7 -4
- package/dist/commands/changeset-version.d.ts +55 -6
- package/dist/commands/changeset-version.js +4 -2
- package/dist/commands/checksums.d.ts +4 -1
- package/dist/commands/checksums.js +27 -6
- package/dist/commands/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/package-source.d.ts +43 -0
- package/dist/commands/package-source.js +101 -0
- package/dist/commands/sign.d.ts +0 -9
- package/dist/commands/sign.js +10 -47
- package/dist/commands/verify.d.ts +7 -1
- package/dist/commands/verify.js +53 -17
- package/dist/commands/zip.d.ts +25 -2
- package/dist/commands/zip.js +136 -110
- package/dist/index.d.ts +9 -2
- package/dist/index.js +3 -1
- package/dist/integrity.d.ts +33 -0
- package/dist/integrity.js +115 -0
- package/dist/output-files.d.ts +7 -0
- package/dist/output-files.js +25 -0
- package/dist/package-build.d.ts +33 -0
- package/dist/package-build.js +112 -0
- package/dist/package-files.d.ts +25 -0
- package/dist/package-files.js +24 -0
- package/dist/package-zip.d.ts +14 -2
- package/dist/package-zip.js +129 -38
- package/dist/release-tags.js +6 -0
- package/dist/signing.d.ts +36 -0
- package/dist/signing.js +52 -1
- package/dist/single-project.d.ts +32 -0
- package/dist/single-project.js +144 -0
- package/dist/summary.d.ts +9 -0
- package/dist/summary.js +25 -0
- package/dist/zip.d.ts +3 -0
- package/dist/zip.js +16 -6
- package/package.json +6 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { CHECKSUMS_EXTENSION, createChecksums, writeChecksums, } from "./checksums.js";
|
|
2
|
+
import { checkOutputFiles } from "./output-files.js";
|
|
3
|
+
import { copyFile, lstat, mkdir, mkdtemp, realpath, rename, rm, } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
|
|
5
|
+
import { ValidationError } from "./errors.js";
|
|
6
|
+
import { isErrorCode } from "./file-selection.js";
|
|
7
|
+
import { getPackageFilesToRelease } from "./package-files.js";
|
|
8
|
+
/**
|
|
9
|
+
* Get the manifest path alongside a build directory.
|
|
10
|
+
*
|
|
11
|
+
* @param outputDir Build directory path.
|
|
12
|
+
* @return Absolute manifest path.
|
|
13
|
+
*/
|
|
14
|
+
export function getBuildChecksumsPath(outputDir) {
|
|
15
|
+
return resolve(outputDir) + CHECKSUMS_EXTENSION;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve existing ancestors too, so symlinked parents cannot overlap the source.
|
|
19
|
+
*
|
|
20
|
+
* @param path Directory path.
|
|
21
|
+
* @return Canonical path, including any missing child directories.
|
|
22
|
+
*/
|
|
23
|
+
async function resolveDirectoryPath(path) {
|
|
24
|
+
try {
|
|
25
|
+
return await realpath(path);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isErrorCode(error, 'ENOENT')) {
|
|
29
|
+
return join(await resolveDirectoryPath(dirname(path)), basename(path));
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Copy the release selection into a directory, replacing an existing build only with force.
|
|
36
|
+
*
|
|
37
|
+
* @param input Source selection, destination and overwrite option.
|
|
38
|
+
* @return Destination and number of copied files.
|
|
39
|
+
*/
|
|
40
|
+
export async function buildPackage(input) {
|
|
41
|
+
const sourceDir = await realpath(input.sourceDir);
|
|
42
|
+
const outputDir = resolve(input.outputDir);
|
|
43
|
+
const canonicalOutput = await resolveDirectoryPath(outputDir);
|
|
44
|
+
const sourceFromOutput = relative(canonicalOutput, sourceDir);
|
|
45
|
+
const outputContainsSource = !sourceFromOutput ||
|
|
46
|
+
(!isAbsolute(sourceFromOutput) &&
|
|
47
|
+
sourceFromOutput !== '..' &&
|
|
48
|
+
!sourceFromOutput.startsWith('../'));
|
|
49
|
+
if (outputContainsSource) {
|
|
50
|
+
throw new ValidationError('The build output must not be the source directory or an ancestor of it.');
|
|
51
|
+
}
|
|
52
|
+
const existing = await lstat(outputDir).catch((error) => {
|
|
53
|
+
if (isErrorCode(error, 'ENOENT')) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
if (existing && (!input.force || !existing.isDirectory())) {
|
|
59
|
+
throw new ValidationError(`${outputDir} already exists; use --force to replace an existing directory.`);
|
|
60
|
+
}
|
|
61
|
+
const checksumsPath = input.checksums
|
|
62
|
+
? getBuildChecksumsPath(outputDir)
|
|
63
|
+
: null;
|
|
64
|
+
if (checksumsPath) {
|
|
65
|
+
checkOutputFiles([checksumsPath], input.force ?? false);
|
|
66
|
+
}
|
|
67
|
+
const { files, rules } = await getPackageFilesToRelease({
|
|
68
|
+
sourceDir,
|
|
69
|
+
selection: input.selection,
|
|
70
|
+
outputs: [
|
|
71
|
+
canonicalOutput,
|
|
72
|
+
...(checksumsPath
|
|
73
|
+
? [canonicalOutput + CHECKSUMS_EXTENSION]
|
|
74
|
+
: []),
|
|
75
|
+
],
|
|
76
|
+
excludeSignatures: !!checksumsPath,
|
|
77
|
+
operation: 'build',
|
|
78
|
+
});
|
|
79
|
+
await mkdir(dirname(outputDir), { recursive: true });
|
|
80
|
+
const staging = await mkdtemp(join(dirname(outputDir), '.wp-release-build-'));
|
|
81
|
+
try {
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const target = join(staging, file);
|
|
84
|
+
await mkdir(dirname(target), { recursive: true });
|
|
85
|
+
await copyFile(join(sourceDir, file), target);
|
|
86
|
+
}
|
|
87
|
+
const manifest = checksumsPath
|
|
88
|
+
? await createChecksums({
|
|
89
|
+
sourceDir: staging,
|
|
90
|
+
rules,
|
|
91
|
+
slug: input.slug ?? basename(sourceDir),
|
|
92
|
+
version: input.version,
|
|
93
|
+
type: input.type,
|
|
94
|
+
})
|
|
95
|
+
: null;
|
|
96
|
+
if (existing) {
|
|
97
|
+
await rm(outputDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
await rename(staging, outputDir);
|
|
100
|
+
if (checksumsPath && manifest) {
|
|
101
|
+
await writeChecksums(checksumsPath, manifest, input.force);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await rm(staging, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
outputDir,
|
|
109
|
+
files: files.length,
|
|
110
|
+
...(checksumsPath ? { checksumsPath } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileRules, FileSelection } from './file-selection.ts';
|
|
2
|
+
interface PackageFilesInput {
|
|
3
|
+
/** Directory whose release files are selected. */
|
|
4
|
+
sourceDir: string;
|
|
5
|
+
/** Resolved ignore file and exclusion options. */
|
|
6
|
+
selection: FileSelection;
|
|
7
|
+
/** Absolute output paths to leave out of the release. */
|
|
8
|
+
outputs: string[];
|
|
9
|
+
/** Also exclude signatures of the output files. */
|
|
10
|
+
excludeSignatures?: boolean;
|
|
11
|
+
/** Operation name used in an empty-selection error. */
|
|
12
|
+
operation: 'build' | 'archive';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Prepare the release file selection shared by directory builds and ZIPs.
|
|
16
|
+
*
|
|
17
|
+
* @param input Source, file selection and output exclusions.
|
|
18
|
+
* @return Sorted file paths and the rules used to select them.
|
|
19
|
+
* @throws {ValidationError} When no release files are selected.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getPackageFilesToRelease(input: PackageFilesInput): Promise<{
|
|
22
|
+
files: string[];
|
|
23
|
+
rules: FileRules;
|
|
24
|
+
}>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.js";
|
|
2
|
+
import { ignoreOwnFile, ignoreOwnSignature, loadFileRules, selectFiles, } from "./file-selection.js";
|
|
3
|
+
/**
|
|
4
|
+
* Prepare the release file selection shared by directory builds and ZIPs.
|
|
5
|
+
*
|
|
6
|
+
* @param input Source, file selection and output exclusions.
|
|
7
|
+
* @return Sorted file paths and the rules used to select them.
|
|
8
|
+
* @throws {ValidationError} When no release files are selected.
|
|
9
|
+
*/
|
|
10
|
+
export async function getPackageFilesToRelease(input) {
|
|
11
|
+
const { sourceDir, selection, outputs, excludeSignatures, operation } = input;
|
|
12
|
+
const rules = await loadFileRules(selection);
|
|
13
|
+
outputs.forEach((path) => {
|
|
14
|
+
ignoreOwnFile(rules, sourceDir, path);
|
|
15
|
+
if (excludeSignatures) {
|
|
16
|
+
ignoreOwnSignature(rules, sourceDir, path);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const files = await selectFiles(sourceDir, rules);
|
|
20
|
+
if (!files.length) {
|
|
21
|
+
throw new ValidationError(`No files to ${operation} in ${sourceDir}.`);
|
|
22
|
+
}
|
|
23
|
+
return { files, rules };
|
|
24
|
+
}
|
package/dist/package-zip.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { IntegrityConfig } from './integrity.ts';
|
|
2
|
+
import type { SignFilesResult, SignatureFormat } from './signing.ts';
|
|
1
3
|
import type { PackageType } from './checksums.ts';
|
|
2
4
|
import type { FileSelection } from './file-selection.ts';
|
|
3
5
|
export interface ZipPackageInput {
|
|
@@ -15,8 +17,13 @@ export interface ZipPackageInput {
|
|
|
15
17
|
version?: string | null;
|
|
16
18
|
/** Package type for the checksums manifest. */
|
|
17
19
|
type?: PackageType;
|
|
18
|
-
/**
|
|
20
|
+
/** Generate a checksums manifest beside the archive or embedded in it. */
|
|
19
21
|
checksums?: boolean;
|
|
22
|
+
checksumsFile?: string;
|
|
23
|
+
embedChecksums?: boolean;
|
|
24
|
+
integrity?: IntegrityConfig;
|
|
25
|
+
/** Sign the manifest only, before embedding it. */
|
|
26
|
+
signChecksums?: SignatureFormat[];
|
|
20
27
|
/** Overwrite existing output files. */
|
|
21
28
|
force?: boolean;
|
|
22
29
|
}
|
|
@@ -24,8 +31,13 @@ export interface ZipPackageInput {
|
|
|
24
31
|
export interface ZipPackageResult {
|
|
25
32
|
/** Absolute path of the ZIP. */
|
|
26
33
|
zipPath: string;
|
|
27
|
-
/** Absolute path of the
|
|
34
|
+
/** Absolute path of the external manifest; absent when disabled or embedded. */
|
|
28
35
|
checksumsPath?: string;
|
|
36
|
+
/** Relative path inside the package, only in embedded mode. */
|
|
37
|
+
checksumsEmbedded?: string;
|
|
38
|
+
/** Manifest signatures, external paths or package-relative embedded paths. */
|
|
39
|
+
checksumsSignatures?: string[];
|
|
40
|
+
checksumsSigning?: SignFilesResult;
|
|
29
41
|
/** Number of archived files. */
|
|
30
42
|
files: number;
|
|
31
43
|
}
|
package/dist/package-zip.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { checkChecksumsFile, resolveManifestCodec, encodeIntegrity, } from "./integrity.js";
|
|
5
|
+
import { checkSigningInput, signFiles, getSignaturePath } from "./signing.js";
|
|
6
|
+
import { CHECKSUMS_EXTENSION, createChecksums } from "./checksums.js";
|
|
3
7
|
import { ValidationError } from "./errors.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
8
|
+
import { checkOutputFiles } from "./output-files.js";
|
|
9
|
+
import { getPackageFilesToRelease } from "./package-files.js";
|
|
10
|
+
import { createZip, isValidRootFolder } from "./zip.js";
|
|
6
11
|
const ZIP_EXTENSION = '.zip';
|
|
7
12
|
/**
|
|
8
13
|
* Get the checksums manifest path next to an archive, with .checksums.json
|
|
@@ -17,19 +22,6 @@ export function getChecksumsPath(zipPath) {
|
|
|
17
22
|
: zipPath;
|
|
18
23
|
return name + CHECKSUMS_EXTENSION;
|
|
19
24
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Fail before writing anything when an output file exists.
|
|
22
|
-
*
|
|
23
|
-
* @param paths Output paths.
|
|
24
|
-
* @param force Whether existing files are overwritten.
|
|
25
|
-
* @throws {ValidationError} If a file exists and force isn't set.
|
|
26
|
-
*/
|
|
27
|
-
function checkOverwrite(paths, force) {
|
|
28
|
-
const existing = paths.filter((path) => !force && existsSync(path));
|
|
29
|
-
if (existing.length) {
|
|
30
|
-
throw new ValidationError(`${existing.join(' and ')} already exists, pass --force to overwrite it.`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
25
|
/**
|
|
34
26
|
* Write a ZIP of the selected files of a plugin, theme or package, and
|
|
35
27
|
* optionally the checksums manifest of the same files next to it. The zip
|
|
@@ -44,35 +36,134 @@ function checkOverwrite(paths, force) {
|
|
|
44
36
|
*/
|
|
45
37
|
export async function zipPackage(input) {
|
|
46
38
|
const { sourceDir, zipPath, force = false } = input;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
if (input.rootFolder && !isValidRootFolder(input.rootFolder)) {
|
|
40
|
+
throw new ValidationError(`Invalid root folder "${input.rootFolder}", use a single folder name.`);
|
|
41
|
+
}
|
|
42
|
+
const enabled = input.checksums ||
|
|
43
|
+
input.checksumsFile ||
|
|
44
|
+
input.embedChecksums ||
|
|
45
|
+
input.signChecksums;
|
|
46
|
+
const codec = resolveManifestCodec(input.integrity?.codec);
|
|
47
|
+
const file = checkChecksumsFile(input.checksumsFile ??
|
|
48
|
+
(codec.id === 'wordpress-json'
|
|
49
|
+
? basename(getChecksumsPath(zipPath))
|
|
50
|
+
: codec.filename));
|
|
51
|
+
const checksumsPath = enabled && !input.embedChecksums
|
|
52
|
+
? resolve(dirname(zipPath), file)
|
|
53
|
+
: null;
|
|
54
|
+
if (checksumsPath === resolve(zipPath)) {
|
|
55
|
+
throw new ValidationError('The ZIP and checksums file must have different destinations.');
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if (input.signChecksums) {
|
|
58
|
+
checkSigningInput({
|
|
59
|
+
paths: checksumsPath ? [checksumsPath] : [],
|
|
60
|
+
formats: input.signChecksums,
|
|
61
|
+
force,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const outputs = [zipPath, checksumsPath].filter((path) => null !== path);
|
|
65
|
+
const { files, rules } = await getPackageFilesToRelease({
|
|
57
66
|
sourceDir,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
selection: input.selection,
|
|
68
|
+
outputs,
|
|
69
|
+
excludeSignatures: true,
|
|
70
|
+
operation: 'archive',
|
|
62
71
|
});
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
checkOutputFiles(outputs, force);
|
|
73
|
+
const entries = {};
|
|
74
|
+
let checksumsSigning;
|
|
75
|
+
let stage;
|
|
76
|
+
const metadata = [
|
|
77
|
+
file,
|
|
78
|
+
...['wordpress', 'minisign', 'ssh'].map((format) => getSignaturePath(file, format)),
|
|
79
|
+
];
|
|
80
|
+
if (enabled &&
|
|
81
|
+
input.embedChecksums &&
|
|
82
|
+
metadata.some((path) => files.some((selected) => selected === path ||
|
|
83
|
+
selected.startsWith(path + '/') ||
|
|
84
|
+
path.startsWith(selected + '/')))) {
|
|
85
|
+
throw new ValidationError('Embedded checksums collide with selected source files. Remove the generated metadata or exclude it from the payload.');
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
if (enabled) {
|
|
89
|
+
const manifest = await createChecksums({
|
|
90
|
+
sourceDir,
|
|
91
|
+
rules,
|
|
92
|
+
slug: input.slug,
|
|
93
|
+
version: input.version ?? null,
|
|
94
|
+
type: input.type,
|
|
95
|
+
});
|
|
96
|
+
const algorithms = input.integrity?.algorithms;
|
|
97
|
+
if (algorithms) {
|
|
98
|
+
if (!algorithms.length ||
|
|
99
|
+
algorithms.some((algorithm) => !['md5', 'sha256'].includes(algorithm))) {
|
|
100
|
+
throw new ValidationError('Checksums algorithms must be md5 and/or sha256.');
|
|
101
|
+
}
|
|
102
|
+
Object.values(manifest.files).forEach((hashes) => {
|
|
103
|
+
if (!algorithms.includes('md5')) {
|
|
104
|
+
delete hashes.md5;
|
|
105
|
+
}
|
|
106
|
+
if (!algorithms.includes('sha256')) {
|
|
107
|
+
delete hashes.sha256;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const bytes = await encodeIntegrity(codec, manifest);
|
|
112
|
+
stage = await mkdtemp(join(tmpdir(), 'wp-release-integrity-'));
|
|
113
|
+
const stagedFile = join(stage, file);
|
|
114
|
+
await mkdir(dirname(stagedFile), { recursive: true });
|
|
115
|
+
await writeFile(stagedFile, bytes);
|
|
116
|
+
if (input.signChecksums) {
|
|
117
|
+
checksumsSigning = await signFiles({
|
|
118
|
+
paths: [stagedFile],
|
|
119
|
+
formats: input.signChecksums,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (input.embedChecksums) {
|
|
123
|
+
entries[file] = bytes;
|
|
124
|
+
for (const format of input.signChecksums ?? []) {
|
|
125
|
+
entries[getSignaturePath(file, format)] =
|
|
126
|
+
await readFile(getSignaturePath(stagedFile, format));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (checksumsPath) {
|
|
130
|
+
await mkdir(dirname(checksumsPath), { recursive: true });
|
|
131
|
+
await writeFile(checksumsPath, bytes, {
|
|
132
|
+
flag: force ? 'w' : 'wx',
|
|
133
|
+
});
|
|
134
|
+
for (const format of input.signChecksums ?? []) {
|
|
135
|
+
await writeFile(getSignaturePath(checksumsPath, format), await readFile(getSignaturePath(stagedFile, format)), { flag: force ? 'w' : 'wx' });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
await mkdir(dirname(zipPath), { recursive: true });
|
|
140
|
+
await createZip({
|
|
65
141
|
sourceDir,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
142
|
+
files,
|
|
143
|
+
entries,
|
|
144
|
+
zipPath,
|
|
145
|
+
rootFolder: input.rootFolder,
|
|
146
|
+
force,
|
|
70
147
|
});
|
|
71
|
-
await writeChecksums(checksumsPath, manifest, force);
|
|
72
148
|
}
|
|
149
|
+
finally {
|
|
150
|
+
if (stage) {
|
|
151
|
+
await rm(stage, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const signatures = (input.signChecksums ?? []).map((format) => getSignaturePath(checksumsPath ?? file, format));
|
|
73
155
|
return {
|
|
74
156
|
zipPath,
|
|
75
157
|
...(checksumsPath ? { checksumsPath } : {}),
|
|
76
|
-
|
|
158
|
+
...(enabled && input.embedChecksums
|
|
159
|
+
? { checksumsEmbedded: file }
|
|
160
|
+
: {}),
|
|
161
|
+
...(checksumsSigning
|
|
162
|
+
? {
|
|
163
|
+
checksumsSigning: { ...checksumsSigning, signatures },
|
|
164
|
+
checksumsSignatures: signatures,
|
|
165
|
+
}
|
|
166
|
+
: {}),
|
|
167
|
+
files: files.length + Object.keys(entries).length,
|
|
77
168
|
};
|
|
78
169
|
}
|
package/dist/release-tags.js
CHANGED
|
@@ -12,6 +12,12 @@ import { compareVersions, isPrerelease, isVersion } from "./versions.js";
|
|
|
12
12
|
* @throws {ValidationError} For unknown packages, or an expected version without a single package.
|
|
13
13
|
*/
|
|
14
14
|
export function selectTagPackages(packages, names, version) {
|
|
15
|
+
if (undefined !== version &&
|
|
16
|
+
!names.length &&
|
|
17
|
+
1 === packages.length &&
|
|
18
|
+
'.' === packages[0]?.path) {
|
|
19
|
+
names = [packages[0].name];
|
|
20
|
+
}
|
|
15
21
|
if (undefined !== version && 1 !== names.length) {
|
|
16
22
|
throw new ValidationError('An expected version requires exactly one package.');
|
|
17
23
|
}
|
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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ChangesetConfig, PackageVersionSource } from './changeset-config.ts';
|
|
2
|
+
/** Options used only when changesets have no JavaScript release config. */
|
|
3
|
+
export interface SingleProjectOptions {
|
|
4
|
+
/** Explicit descriptors replace automatic version detection. Git tags are source-only. */
|
|
5
|
+
versions?: PackageVersionSource[];
|
|
6
|
+
/** Descriptor kind, optionally qualified with :file or :file#constant. */
|
|
7
|
+
versionFrom?: string;
|
|
8
|
+
baseBranch?: string;
|
|
9
|
+
remote?: string;
|
|
10
|
+
changesets?: string;
|
|
11
|
+
readmeChangelog?: boolean;
|
|
12
|
+
changelogInsertAfter?: string;
|
|
13
|
+
}
|
|
14
|
+
/** CLI names of version descriptor kinds. */
|
|
15
|
+
export declare const VERSION_KINDS: {
|
|
16
|
+
readonly 'package-json': "package-json";
|
|
17
|
+
readonly 'composer-json': "composer-json";
|
|
18
|
+
readonly 'wordpress-plugin': "wordpress-plugin-header";
|
|
19
|
+
readonly 'wordpress-theme': "wordpress-theme-header";
|
|
20
|
+
readonly 'wordpress-stable-tag': "wordpress-stable-tag";
|
|
21
|
+
readonly 'php-constant': "php-constant";
|
|
22
|
+
readonly 'git-tag': "git-tag";
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Build the root package config, without creating a configuration file.
|
|
26
|
+
*
|
|
27
|
+
* @param cwd Project root directory.
|
|
28
|
+
* @param options Explicit descriptors and shared release settings.
|
|
29
|
+
* @param tagFormat Tag format override, otherwise v{version}.
|
|
30
|
+
* @return Root package config, or undefined without a version source.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveSingleProjectConfig(cwd: string, options?: SingleProjectOptions, tagFormat?: string): Promise<ChangesetConfig | undefined>;
|