@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
package/dist/commands/zip.js
CHANGED
|
@@ -1,107 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { checksumSigningArgs, getArtifactSigning } from "./artifact-signing.js";
|
|
2
2
|
import { ValidationError } from "../errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { basename, dirname, resolve, sep } from 'node:path';
|
|
4
|
+
import { loadChangesetConfig } from "../changeset-config.js";
|
|
5
|
+
import { resolveManifestCodec, checkChecksumsFile } from "../integrity.js";
|
|
6
|
+
import { getPackageArg, getPackageSource, getDirectorySource, } from "./package-source.js";
|
|
4
7
|
import { FILENAME_FORMAT, formatFilename } from "../filename.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { checkDirectory, fileSelectionArgs, getFileSelectionInput, getSourceDirArg, resolveOutput, sourceDirArgs, } from "./args.js";
|
|
8
|
+
import { getChecksumsPath, zipPackage } from "../package-zip.js";
|
|
9
|
+
import { SIGNING_KEY_ENV, signFiles, getSignaturePath } from "../signing.js";
|
|
10
|
+
import { getSignSummary, getZipSummary } from "../summary.js";
|
|
11
|
+
import { fileSelectionArgs, parseSignatureFormats, resolveOutput, sourceDirArgs, } from "./args.js";
|
|
10
12
|
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
|
-
];
|
|
19
|
-
/**
|
|
20
|
-
* Get the configured package to zip, failing for the options that set the
|
|
21
|
-
* source directory and the files instead.
|
|
22
|
-
*
|
|
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.
|
|
27
|
-
*/
|
|
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;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Describe the files of a directory with the slug and the version from the
|
|
45
|
-
* options, or from the plugin or theme headers.
|
|
46
|
-
*
|
|
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.
|
|
52
|
-
*/
|
|
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 };
|
|
104
|
-
}
|
|
105
13
|
export default {
|
|
106
14
|
name: 'zip',
|
|
107
15
|
usage: 'zip [<source-dir>] [options] | zip --package <name> [options]',
|
|
@@ -134,10 +42,24 @@ Options:
|
|
|
134
42
|
--checksums Also write the checksums manifest of the archived
|
|
135
43
|
files next to the ZIP, with .checksums.json
|
|
136
44
|
instead of .zip
|
|
45
|
+
--checksums-file <file> Relative manifest filename; implies checksums.
|
|
46
|
+
Beside the ZIP, or inside its package root with
|
|
47
|
+
--embed-checksums. No absolute paths or dot segments.
|
|
48
|
+
--checksums-format <format> wordpress-json (default) or sha256sum
|
|
49
|
+
--checksums-algorithms <names> md5,sha256 (default), comma-separated or repeated
|
|
50
|
+
--embed-checksums Embed manifest instead of writing it beside ZIP
|
|
51
|
+
--no-embed-checksums Override embedding configured in integrity
|
|
52
|
+
--sign-checksums Sign the manifest only, before embedding it
|
|
53
|
+
--no-sign-checksums Override configured manifest signing
|
|
54
|
+
--sign Sign only the ZIP with the ${SIGNING_KEY_ENV} key
|
|
55
|
+
--sign-format <names> Signature formats of --sign, comma-separated or
|
|
56
|
+
repeated, like wordpress,minisign,ssh, as the
|
|
57
|
+
--format of "wp-release sign" (default:
|
|
58
|
+
wordpress); requires --sign or --sign-checksums
|
|
137
59
|
--force Overwrite existing output files
|
|
138
60
|
|
|
139
|
-
Entries are sorted and use a fixed timestamp and file mode
|
|
140
|
-
|
|
61
|
+
Entries are sorted and use a fixed timestamp and file mode. Embedded minisign
|
|
62
|
+
signatures include their signing time; other built-in metadata is deterministic.
|
|
141
63
|
|
|
142
64
|
Examples:
|
|
143
65
|
wp-release zip dist --slug my-plugin --checksums
|
|
@@ -154,7 +76,14 @@ Examples:
|
|
|
154
76
|
...sourceDirArgs,
|
|
155
77
|
...fileSelectionArgs,
|
|
156
78
|
package: { type: 'string' },
|
|
157
|
-
|
|
79
|
+
...checksumSigningArgs,
|
|
80
|
+
'checksums-file': { type: 'string' },
|
|
81
|
+
'checksums-format': { type: 'string' },
|
|
82
|
+
'checksums-algorithms': { type: 'string', multiple: true },
|
|
83
|
+
'embed-checksums': { type: 'boolean' },
|
|
84
|
+
'no-embed-checksums': { type: 'boolean' },
|
|
85
|
+
'no-sign-checksums': { type: 'boolean' },
|
|
86
|
+
sign: { type: 'boolean' },
|
|
158
87
|
force: { type: 'boolean' },
|
|
159
88
|
},
|
|
160
89
|
async run({ values, positionals, cwd, configFile, logger, }) {
|
|
@@ -163,21 +92,89 @@ Examples:
|
|
|
163
92
|
? await getPackageSource(values, cwd, name, configFile)
|
|
164
93
|
: await getDirectorySource(values, positionals, cwd);
|
|
165
94
|
const filename = formatFilename(getString(values, 'filename-format') ?? FILENAME_FORMAT, { slug: source.slug, version: source.version });
|
|
95
|
+
const zipPath = resolveOutput({
|
|
96
|
+
cwd,
|
|
97
|
+
output: getString(values, 'output'),
|
|
98
|
+
filename: `${filename}.zip`,
|
|
99
|
+
});
|
|
100
|
+
const loaded = await loadChangesetConfig({ cwd, configFile });
|
|
101
|
+
const configured = loaded?.config.integrity;
|
|
102
|
+
const algorithms = getStrings(values, 'checksums-algorithms').flatMap((value) => value.split(','));
|
|
103
|
+
const integrity = {
|
|
104
|
+
...configured,
|
|
105
|
+
codec: getString(values, 'checksums-format') ?? configured?.codec,
|
|
106
|
+
...(algorithms.length
|
|
107
|
+
? { algorithms: algorithms }
|
|
108
|
+
: {}),
|
|
109
|
+
};
|
|
110
|
+
const codec = resolveManifestCodec(integrity.codec);
|
|
111
|
+
const embed = !getBoolean(values, 'no-embed-checksums') &&
|
|
112
|
+
(getBoolean(values, 'embed-checksums') ||
|
|
113
|
+
Boolean(configured?.embed));
|
|
114
|
+
const checksumsFile = checkChecksumsFile(getString(values, 'checksums-file') ??
|
|
115
|
+
configured?.file ??
|
|
116
|
+
(codec.id === 'wordpress-json'
|
|
117
|
+
? basename(getChecksumsPath(zipPath))
|
|
118
|
+
: codec.filename));
|
|
119
|
+
const manifestSign = !getBoolean(values, 'no-sign-checksums') &&
|
|
120
|
+
(getBoolean(values, 'sign-checksums') ||
|
|
121
|
+
Boolean(configured?.sign));
|
|
122
|
+
const checksums = getBoolean(values, 'checksums') ||
|
|
123
|
+
embed ||
|
|
124
|
+
manifestSign ||
|
|
125
|
+
Boolean(getString(values, 'checksums-file') ?? configured?.file);
|
|
126
|
+
const signFormats = getStrings(values, 'sign-format');
|
|
127
|
+
const configuredFormats = configured?.sign
|
|
128
|
+
? configured.sign.formats ?? []
|
|
129
|
+
: [];
|
|
130
|
+
const formats = parseSignatureFormats(signFormats.length ? signFormats : configuredFormats);
|
|
131
|
+
if (signFormats.length &&
|
|
132
|
+
!manifestSign &&
|
|
133
|
+
!getBoolean(values, 'sign')) {
|
|
134
|
+
throw new ValidationError('--sign-format requires --sign or --sign-checksums; it does not enable signing.');
|
|
135
|
+
}
|
|
136
|
+
const signing = getBoolean(values, 'sign')
|
|
137
|
+
? getArtifactSigning({
|
|
138
|
+
...values,
|
|
139
|
+
'sign-checksums': false,
|
|
140
|
+
'sign-format': formats,
|
|
141
|
+
}, {
|
|
142
|
+
artifact: zipPath,
|
|
143
|
+
checksums: resolve(dirname(zipPath), checksumsFile),
|
|
144
|
+
})
|
|
145
|
+
: undefined;
|
|
146
|
+
const externalManifest = checksums && !embed
|
|
147
|
+
? resolve(dirname(zipPath), checksumsFile)
|
|
148
|
+
: undefined;
|
|
149
|
+
const artifacts = [
|
|
150
|
+
zipPath,
|
|
151
|
+
...(externalManifest ? [externalManifest] : []),
|
|
152
|
+
...(signing
|
|
153
|
+
? formats.map((format) => getSignaturePath(zipPath, format))
|
|
154
|
+
: []),
|
|
155
|
+
...(externalManifest && manifestSign
|
|
156
|
+
? formats.map((format) => getSignaturePath(externalManifest, format))
|
|
157
|
+
: []),
|
|
158
|
+
];
|
|
159
|
+
if (new Set(artifacts).size !== artifacts.length ||
|
|
160
|
+
artifacts.some((path, index) => artifacts.some((other, otherIndex) => index !== otherIndex && other.startsWith(path + sep)))) {
|
|
161
|
+
throw new ValidationError('ZIP, manifest and signature destinations must be distinct.');
|
|
162
|
+
}
|
|
166
163
|
const zipped = await zipPackage({
|
|
167
164
|
sourceDir: source.sourceDir,
|
|
168
165
|
selection: source.selection,
|
|
169
|
-
zipPath
|
|
170
|
-
cwd,
|
|
171
|
-
output: getString(values, 'output'),
|
|
172
|
-
filename: `${filename}.zip`,
|
|
173
|
-
}),
|
|
166
|
+
zipPath,
|
|
174
167
|
rootFolder: getBoolean(values, 'no-root-folder')
|
|
175
168
|
? null
|
|
176
169
|
: getString(values, 'root-folder') ?? source.slug,
|
|
177
170
|
slug: source.slug,
|
|
178
171
|
version: source.version,
|
|
179
172
|
...(source.type ? { type: source.type } : {}),
|
|
180
|
-
checksums
|
|
173
|
+
checksums,
|
|
174
|
+
checksumsFile: checksums ? checksumsFile : undefined,
|
|
175
|
+
embedChecksums: embed,
|
|
176
|
+
integrity,
|
|
177
|
+
signChecksums: manifestSign ? formats : undefined,
|
|
181
178
|
force: getBoolean(values, 'force'),
|
|
182
179
|
});
|
|
183
180
|
logger.debug(`Archived ${zipped.files} files from ${source.sourceDir}.`);
|
|
@@ -185,13 +182,42 @@ Examples:
|
|
|
185
182
|
if (zipped.checksumsPath) {
|
|
186
183
|
logger.info(zipped.checksumsPath);
|
|
187
184
|
}
|
|
185
|
+
const signed = signing
|
|
186
|
+
? { ...(await signFiles(signing)), formats: signing.formats }
|
|
187
|
+
: undefined;
|
|
188
|
+
signed?.signatures.forEach((path) => logger.info(path));
|
|
188
189
|
const result = {
|
|
189
190
|
...zipped,
|
|
191
|
+
...(zipped.checksumsSigning
|
|
192
|
+
? {
|
|
193
|
+
signatures: zipped.checksumsSignatures,
|
|
194
|
+
publicKey: zipped.checksumsSigning.publicKey,
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
197
|
+
...(signed
|
|
198
|
+
? {
|
|
199
|
+
signatures: [
|
|
200
|
+
...signed.signatures,
|
|
201
|
+
...(zipped.checksumsSignatures ?? []),
|
|
202
|
+
],
|
|
203
|
+
publicKey: signed.publicKey,
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
190
206
|
slug: source.slug,
|
|
191
207
|
version: source.version,
|
|
192
208
|
...(source.package ? { package: source.package } : {}),
|
|
193
209
|
};
|
|
194
210
|
logger.summary((context) => getZipSummary(context, result));
|
|
211
|
+
const checksumsSigning = zipped.checksumsSigning;
|
|
212
|
+
if (checksumsSigning) {
|
|
213
|
+
logger.summary((context) => getSignSummary(context, {
|
|
214
|
+
...checksumsSigning,
|
|
215
|
+
formats,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
if (signed) {
|
|
219
|
+
logger.summary((context) => getSignSummary(context, signed));
|
|
220
|
+
}
|
|
195
221
|
return { exitCode: 0, result };
|
|
196
222
|
},
|
|
197
223
|
};
|
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';
|
|
@@ -58,3 +59,9 @@ export type { SummaryContext } from './summary.ts';
|
|
|
58
59
|
export type { CheckoutPath, DeployResult } from './svn-deploy.ts';
|
|
59
60
|
export type { GitCommandInput, GitCommandOutput, GitRunner, RepositoryLocation, } from './git.ts';
|
|
60
61
|
export type { GitDeployInput, GitDeployOptions, GitDeployResult, } from './git-deploy.ts';
|
|
62
|
+
export type { SingleProjectOptions } from './single-project.ts';
|
|
63
|
+
export { buildPackage } from './package-build.ts';
|
|
64
|
+
export type { BuildPackageInput, BuildPackageResult } from './package-build.ts';
|
|
65
|
+
export type { BuildResult } from './commands/build.ts';
|
|
66
|
+
export { wordpressCodec, sha256sumCodec, resolveManifestCodec, readIntegrity, } from './integrity.ts';
|
|
67
|
+
export type { ManifestCodec, IntegrityConfig } from './integrity.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";
|
|
@@ -27,3 +27,5 @@ export { GitDeploy, getDefaultToken, getGitAuthEnv, isValidRefName, resolveGitDe
|
|
|
27
27
|
export { getGitDeployInputFromArgs } from "./commands/deploy-git.js";
|
|
28
28
|
export { getDeployPackages, getGitDeploys, getPackageFiles, getSvnDeploys, } from "./package-deploy.js";
|
|
29
29
|
export { zipPackage } from "./package-zip.js";
|
|
30
|
+
export { buildPackage } from "./package-build.js";
|
|
31
|
+
export { wordpressCodec, sha256sumCodec, resolveManifestCodec, readIntegrity, } from "./integrity.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChecksumsManifest, ChecksumAlgorithm } from './checksums.ts';
|
|
2
|
+
import type { SignatureFormat } from './signing.ts';
|
|
3
|
+
/** Custom codecs receive the validated WordPress-compatible integrity model. */
|
|
4
|
+
export interface ManifestCodec {
|
|
5
|
+
id: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
encode: (manifest: ChecksumsManifest) => Uint8Array | Promise<Uint8Array>;
|
|
8
|
+
decode: (bytes: Uint8Array) => ChecksumsManifest | Promise<ChecksumsManifest>;
|
|
9
|
+
}
|
|
10
|
+
export interface IntegrityConfig {
|
|
11
|
+
codec?: 'wordpress-json' | 'sha256sum' | ManifestCodec;
|
|
12
|
+
algorithms?: ChecksumAlgorithm[];
|
|
13
|
+
file?: string;
|
|
14
|
+
embed?: boolean;
|
|
15
|
+
sign?: false | {
|
|
16
|
+
formats?: SignatureFormat[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const wordpressCodec: ManifestCodec;
|
|
20
|
+
export declare const sha256sumCodec: ManifestCodec;
|
|
21
|
+
export declare function resolveManifestCodec(value?: IntegrityConfig['codec']): ManifestCodec;
|
|
22
|
+
/**
|
|
23
|
+
* Relative artifact paths cannot escape their destination or use Windows paths.
|
|
24
|
+
* @param file
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkChecksumsFile(file: string): string;
|
|
27
|
+
export declare function readIntegrity(path: string, codec?: IntegrityConfig['codec']): Promise<ChecksumsManifest>;
|
|
28
|
+
/**
|
|
29
|
+
* Encode once and ensure the format preserves the selected payload hashes.
|
|
30
|
+
* @param codec
|
|
31
|
+
* @param manifest
|
|
32
|
+
*/
|
|
33
|
+
export declare function encodeIntegrity(codec: ManifestCodec, manifest: ChecksumsManifest): Promise<Buffer>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { parseChecksums, readChecksums } from "./checksums.js";
|
|
4
|
+
import { ValidationError } from "./errors.js";
|
|
5
|
+
export const wordpressCodec = {
|
|
6
|
+
id: 'wordpress-json',
|
|
7
|
+
filename: 'checksums.json',
|
|
8
|
+
encode: (manifest) => Buffer.from(JSON.stringify(manifest, null, '\t') + '\n'),
|
|
9
|
+
decode: (bytes) => parseChecksums(Buffer.from(bytes).toString('utf8')),
|
|
10
|
+
};
|
|
11
|
+
export const sha256sumCodec = {
|
|
12
|
+
id: 'sha256sum',
|
|
13
|
+
filename: 'SHA256SUMS',
|
|
14
|
+
encode(manifest) {
|
|
15
|
+
const lines = Object.entries(manifest.files)
|
|
16
|
+
.sort()
|
|
17
|
+
.map(([path, hashes]) => {
|
|
18
|
+
if ('string' !== typeof hashes.sha256) {
|
|
19
|
+
throw new ValidationError('sha256sum requires one SHA-256 digest per file.');
|
|
20
|
+
}
|
|
21
|
+
const escaped = /[\\\n\r]/.test(path);
|
|
22
|
+
const name = path
|
|
23
|
+
.replaceAll('\\', '\\\\')
|
|
24
|
+
.replaceAll('\n', '\\n')
|
|
25
|
+
.replaceAll('\r', '\\r');
|
|
26
|
+
return `${escaped ? '\\' : ''}${hashes.sha256} ${name}`;
|
|
27
|
+
});
|
|
28
|
+
return Buffer.from(lines.join('\n') + '\n');
|
|
29
|
+
},
|
|
30
|
+
decode(bytes) {
|
|
31
|
+
const files = Object.create(null);
|
|
32
|
+
for (const line of Buffer.from(bytes)
|
|
33
|
+
.toString('utf8')
|
|
34
|
+
.split('\n')
|
|
35
|
+
.filter(Boolean)) {
|
|
36
|
+
const match = /^(\\?)([a-fA-F0-9]{64}) [ *](.+)$/.exec(line);
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new ValidationError('Invalid SHA256SUMS entry.');
|
|
39
|
+
}
|
|
40
|
+
const path = match[1]
|
|
41
|
+
? (match[3] ?? '').replace(/\\([\\nr])/g, (_, char) => ({ n: '\n', r: '\r', '\\': '\\' })[char] ?? '')
|
|
42
|
+
: match[3] ?? '';
|
|
43
|
+
if (Object.hasOwn(files, path)) {
|
|
44
|
+
throw new ValidationError(`Duplicate checksum path: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
files[path] = { sha256: (match[2] ?? '').toLowerCase() };
|
|
47
|
+
}
|
|
48
|
+
return parseChecksums(JSON.stringify({ files }));
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export function resolveManifestCodec(value = 'wordpress-json') {
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
if (!['wordpress-json', 'sha256sum'].includes(value)) {
|
|
54
|
+
throw new ValidationError(`Unknown checksums format: ${value}.`);
|
|
55
|
+
}
|
|
56
|
+
return value === 'sha256sum' ? sha256sumCodec : wordpressCodec;
|
|
57
|
+
}
|
|
58
|
+
if (!value ||
|
|
59
|
+
typeof value.encode !== 'function' ||
|
|
60
|
+
typeof value.decode !== 'function' ||
|
|
61
|
+
!value.id ||
|
|
62
|
+
!value.filename) {
|
|
63
|
+
throw new ValidationError('A checksums codec needs id, filename, encode and decode.');
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Relative artifact paths cannot escape their destination or use Windows paths.
|
|
69
|
+
* @param file
|
|
70
|
+
*/
|
|
71
|
+
export function checkChecksumsFile(file) {
|
|
72
|
+
if (!file ||
|
|
73
|
+
/[\\\x00-\x1f:]/.test(file) ||
|
|
74
|
+
file
|
|
75
|
+
.split('/')
|
|
76
|
+
.some((part) => !part || part === '.' || part === '..')) {
|
|
77
|
+
throw new ValidationError('--checksums-file must be a relative file path with forward slashes and no dot segments.');
|
|
78
|
+
}
|
|
79
|
+
return file;
|
|
80
|
+
}
|
|
81
|
+
export async function readIntegrity(path, codec) {
|
|
82
|
+
const selected = resolveManifestCodec(codec ??
|
|
83
|
+
(basename(path) === 'SHA256SUMS'
|
|
84
|
+
? 'sha256sum'
|
|
85
|
+
: 'wordpress-json'));
|
|
86
|
+
const manifest = selected === wordpressCodec
|
|
87
|
+
? await readChecksums(path)
|
|
88
|
+
: await selected.decode(await readFile(path));
|
|
89
|
+
return parseChecksums(JSON.stringify(manifest), path);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Encode once and ensure the format preserves the selected payload hashes.
|
|
93
|
+
* @param codec
|
|
94
|
+
* @param manifest
|
|
95
|
+
*/
|
|
96
|
+
export async function encodeIntegrity(codec, manifest) {
|
|
97
|
+
const bytes = Buffer.from(await codec.encode(manifest));
|
|
98
|
+
const decoded = parseChecksums(JSON.stringify(await codec.decode(bytes)));
|
|
99
|
+
const expected = Object.keys(manifest.files);
|
|
100
|
+
const actual = Object.keys(decoded.files);
|
|
101
|
+
const preserved = expected.length === actual.length &&
|
|
102
|
+
actual.every((path) => {
|
|
103
|
+
const hashes = decoded.files[path];
|
|
104
|
+
return (Boolean(manifest.files[path]) &&
|
|
105
|
+
Object.entries(hashes ?? {}).every(([algorithm, digest]) => digest ===
|
|
106
|
+
manifest.files[path]?.[algorithm]));
|
|
107
|
+
});
|
|
108
|
+
if (!preserved ||
|
|
109
|
+
['plugin', 'theme', 'version'].some((key) => decoded[key] !== undefined &&
|
|
110
|
+
decoded[key] !==
|
|
111
|
+
manifest[key])) {
|
|
112
|
+
throw new ValidationError('The checksums codec must preserve payload paths, digests and any represented package identity.');
|
|
113
|
+
}
|
|
114
|
+
return bytes;
|
|
115
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate generated file destinations before writing release artifacts.
|
|
3
|
+
*
|
|
4
|
+
* @param paths Output file paths.
|
|
5
|
+
* @param force Whether existing regular files can be overwritten.
|
|
6
|
+
*/
|
|
7
|
+
export declare function checkOutputFiles(paths: string[], force: boolean): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { lstatSync } from 'node:fs';
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
import { isErrorCode } from "./file-selection.js";
|
|
4
|
+
/**
|
|
5
|
+
* Validate generated file destinations before writing release artifacts.
|
|
6
|
+
*
|
|
7
|
+
* @param paths Output file paths.
|
|
8
|
+
* @param force Whether existing regular files can be overwritten.
|
|
9
|
+
*/
|
|
10
|
+
export function checkOutputFiles(paths, force) {
|
|
11
|
+
for (const path of paths) {
|
|
12
|
+
let existing;
|
|
13
|
+
try {
|
|
14
|
+
existing = lstatSync(path);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!isErrorCode(error, 'ENOENT')) {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (existing && (!force || !existing.isFile())) {
|
|
22
|
+
throw new ValidationError(`${path} already exists, pass --force to overwrite a regular file.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PackageType } from './checksums.ts';
|
|
2
|
+
import type { FileSelection } from './file-selection.ts';
|
|
3
|
+
export interface BuildPackageInput {
|
|
4
|
+
sourceDir: string;
|
|
5
|
+
selection: FileSelection;
|
|
6
|
+
outputDir: string;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
/** Write a checksums manifest alongside the build directory. */
|
|
9
|
+
checksums?: boolean;
|
|
10
|
+
/** Slug and version for the manifest. */
|
|
11
|
+
slug?: string;
|
|
12
|
+
version?: string | null;
|
|
13
|
+
type?: PackageType;
|
|
14
|
+
}
|
|
15
|
+
export interface BuildPackageResult {
|
|
16
|
+
outputDir: string;
|
|
17
|
+
files: number;
|
|
18
|
+
checksumsPath?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the manifest path alongside a build directory.
|
|
22
|
+
*
|
|
23
|
+
* @param outputDir Build directory path.
|
|
24
|
+
* @return Absolute manifest path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getBuildChecksumsPath(outputDir: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Copy the release selection into a directory, replacing an existing build only with force.
|
|
29
|
+
*
|
|
30
|
+
* @param input Source selection, destination and overwrite option.
|
|
31
|
+
* @return Destination and number of copied files.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildPackage(input: BuildPackageInput): Promise<BuildPackageResult>;
|