beez-rp 0.2.0 → 0.3.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/CHANGELOG.md +22 -0
- package/README.md +30 -4
- package/bin/beez-rp.js +10 -1
- package/package.json +7 -2
- package/src/constants/cli.js +1 -0
- package/src/constants/create-version.js +169 -0
- package/src/constants/guard-publish.js +29 -0
- package/src/constants/index.js +1 -0
- package/src/create-version/artifact.js +324 -0
- package/src/create-version/config.js +16 -0
- package/src/create-version/index.js +23 -2
- package/src/create-version/npm.js +359 -22
- package/src/create-version/plan.js +8 -1
- package/src/create-version/process.js +20 -7
- package/src/create-version/run.js +207 -13
- package/src/create-version/state.js +25 -2
- package/src/guard-publish.js +74 -0
- package/src/index.js +2 -0
- package/types/constants/cli.d.ts +1 -0
- package/types/constants/create-version.d.ts +118 -0
- package/types/constants/guard-publish.d.ts +25 -0
- package/types/constants/index.d.ts +1 -0
- package/types/create-version/artifact.d.ts +113 -0
- package/types/create-version/config.d.ts +4 -0
- package/types/create-version/index.d.ts +3 -2
- package/types/create-version/npm.d.ts +133 -8
- package/types/create-version/process.d.ts +10 -1
- package/types/create-version/run.d.ts +2 -0
- package/types/guard-publish.d.ts +48 -0
- package/types/index.d.ts +2 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locates and verifies the package archive prepared for a release, so
|
|
3
|
+
* `publish: "npm"` publishes exactly the artifact the project verified
|
|
4
|
+
* instead of repacking the working tree.
|
|
5
|
+
*
|
|
6
|
+
* Patterns are relative to the repository root, use `/` separators and
|
|
7
|
+
* replace `{version}` and `{name}` (the tarball base name `npm pack` uses, so
|
|
8
|
+
* `@scope/pkg` becomes `scope-pkg`). Inside a single path segment, `*` matches
|
|
9
|
+
* anything and `{sha256}` matches a SHA-256 digest that must equal the
|
|
10
|
+
* archive checksum (for example `releases/{version}-{sha256}/{name}-{version}.tgz`).
|
|
11
|
+
* `{sha256}` may repeat, in one segment or several: every occurrence must
|
|
12
|
+
* declare the same digest.
|
|
13
|
+
*
|
|
14
|
+
* Projects pack with `npm pack --ignore-scripts`, which is reproducible: the same
|
|
15
|
+
* checkout always yields the same bytes. So the archive is verified by comparing
|
|
16
|
+
* its SHA-512 integrity with the one `npm pack --dry-run` reports for the release
|
|
17
|
+
* checkout, and the manifest must not rely on rewrites only pnpm applies when packing.
|
|
18
|
+
* The archive is moved out of the package root during that dry run, so npm never
|
|
19
|
+
* counts it as part of the package it describes.
|
|
20
|
+
*
|
|
21
|
+
* @module create-version/artifact
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { copyFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
ARTIFACT_HOLDING_DIRECTORY_PREFIX,
|
|
31
|
+
ARTIFACT_NAME_PLACEHOLDER,
|
|
32
|
+
ARTIFACT_SEGMENT_WILDCARD,
|
|
33
|
+
ARTIFACT_SHA256_PLACEHOLDER,
|
|
34
|
+
ARTIFACT_VERSION_PLACEHOLDER,
|
|
35
|
+
CROSS_DEVICE_RENAME_ERROR_CODE,
|
|
36
|
+
NPM_INTEGRITY_ALGORITHM,
|
|
37
|
+
PACKAGE_SCOPE_PATTERN,
|
|
38
|
+
PACKED_SCOPE_REPLACEMENT,
|
|
39
|
+
PNPM_HOISTED_PUBLISH_CONFIG_KEYS,
|
|
40
|
+
PNPM_PACK_REWRITTEN_SPECIFIER_PATTERN,
|
|
41
|
+
PUBLISH_CONFIG_FIELD,
|
|
42
|
+
PUBLISHED_DEPENDENCY_FIELDS,
|
|
43
|
+
SAFE_ARTIFACT_PATH_PATTERN,
|
|
44
|
+
SHA256_HEX_PATTERN_SOURCE,
|
|
45
|
+
} from "../constants/create-version.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {{ path: string, expectedSha256: string | null }} PreparedArtifact
|
|
49
|
+
* @typedef {Record<string, unknown>} PackageManifest
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/** Characters with regular-expression meaning, escaped in literal segment text. */
|
|
53
|
+
const REGEXP_SPECIAL_CHARACTERS_PATTERN = /[.+?^${}()|[\]\\]/gu;
|
|
54
|
+
|
|
55
|
+
/** Expression source of any run of characters inside one path segment. */
|
|
56
|
+
const SEGMENT_CHARACTERS_SOURCE = "[^/]*";
|
|
57
|
+
|
|
58
|
+
/** Expression source of the first `{sha256}` of a segment: captures the declared digest. */
|
|
59
|
+
const SHA256_CAPTURE_SOURCE = `(?<sha256>${SHA256_HEX_PATTERN_SOURCE})`;
|
|
60
|
+
|
|
61
|
+
/** Expression source of a repeated `{sha256}` in the same segment: must equal the captured digest. */
|
|
62
|
+
const SHA256_BACKREFERENCE_SOURCE = "\\k<sha256>";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Converts an npm package name into the tarball base name `npm pack` and `pnpm pack` use.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} packageName - npm package name, optionally scoped.
|
|
68
|
+
* @returns {string} Name without `@` and with the scope joined by `-` (`@scope/pkg` → `scope-pkg`); unscoped names are unchanged.
|
|
69
|
+
*/
|
|
70
|
+
function toPackedName(packageName) {
|
|
71
|
+
return packageName.replace(PACKAGE_SCOPE_PATTERN, PACKED_SCOPE_REPLACEMENT);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Replaces the `{version}` and `{name}` placeholders of an artifact pattern; `{sha256}` stays.
|
|
76
|
+
* `{name}` becomes the tarball base name, so scoped packages never add a path separator.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} pattern - Configured pattern.
|
|
79
|
+
* @param {{ version: string, packageName: string }} release - Version and npm package name.
|
|
80
|
+
* @returns {string} Pattern with placeholders replaced.
|
|
81
|
+
*/
|
|
82
|
+
export function expandArtifactPattern(pattern, { version, packageName }) {
|
|
83
|
+
return pattern.replaceAll(ARTIFACT_VERSION_PLACEHOLDER, version).replaceAll(ARTIFACT_NAME_PLACEHOLDER, toPackedName(packageName));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Compiles one path segment: `*` matches anything but `/`, `{sha256}` captures a digest.
|
|
88
|
+
* A repeated `{sha256}` becomes a backreference, so every occurrence must be the same digest.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} segment - Segment of an expanded pattern.
|
|
91
|
+
* @returns {RegExp} Anchored expression; the digest, when present, is the `sha256` group.
|
|
92
|
+
*/
|
|
93
|
+
function compileSegment(segment) {
|
|
94
|
+
const [firstPart, ...partsAfterDigests] = segment
|
|
95
|
+
.split(ARTIFACT_SHA256_PLACEHOLDER)
|
|
96
|
+
.map((part) =>
|
|
97
|
+
part
|
|
98
|
+
.split(ARTIFACT_SEGMENT_WILDCARD)
|
|
99
|
+
.map((literal) => literal.replace(REGEXP_SPECIAL_CHARACTERS_PATTERN, "\\$&"))
|
|
100
|
+
.join(SEGMENT_CHARACTERS_SOURCE)
|
|
101
|
+
);
|
|
102
|
+
const source = partsAfterDigests.reduce(
|
|
103
|
+
(compiled, part, index) => `${compiled}${index === 0 ? SHA256_CAPTURE_SOURCE : SHA256_BACKREFERENCE_SOURCE}${part}`,
|
|
104
|
+
firstPart
|
|
105
|
+
);
|
|
106
|
+
return new RegExp(`^${source}$`, "u");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Finds the newest file that matches an artifact pattern.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} repositoryRoot - Repository root.
|
|
113
|
+
* @param {string} pattern - Configured pattern with `{version}` and optional `{name}`, `*` and `{sha256}`.
|
|
114
|
+
* @param {{ version: string, packageName: string }} release - Version and npm package name.
|
|
115
|
+
* @returns {PreparedArtifact | null} Path relative to the root with `/` separators and the digest its path declares, or `null`.
|
|
116
|
+
* Paths whose segments declare different digests never match.
|
|
117
|
+
*/
|
|
118
|
+
export function findPreparedArtifact(repositoryRoot, pattern, release) {
|
|
119
|
+
const segments = expandArtifactPattern(pattern, release).split("/").filter(Boolean);
|
|
120
|
+
/** @type {PreparedArtifact[]} */
|
|
121
|
+
let candidates = [{ path: "", expectedSha256: null }];
|
|
122
|
+
|
|
123
|
+
for (const [index, segment] of segments.entries()) {
|
|
124
|
+
const isLast = index === segments.length - 1;
|
|
125
|
+
const matcher = compileSegment(segment);
|
|
126
|
+
/** @type {PreparedArtifact[]} */
|
|
127
|
+
const next = [];
|
|
128
|
+
|
|
129
|
+
for (const candidate of candidates) {
|
|
130
|
+
const absoluteDirectory = path.join(repositoryRoot, candidate.path);
|
|
131
|
+
if (!existsSync(absoluteDirectory) || !statSync(absoluteDirectory).isDirectory()) continue;
|
|
132
|
+
|
|
133
|
+
for (const entry of readdirSync(absoluteDirectory, { withFileTypes: true })) {
|
|
134
|
+
const match = matcher.exec(entry.name);
|
|
135
|
+
if (!match || !(isLast ? entry.isFile() : entry.isDirectory())) continue;
|
|
136
|
+
|
|
137
|
+
const declaredSha256 = match.groups?.sha256 ?? null;
|
|
138
|
+
if (declaredSha256 && candidate.expectedSha256 && declaredSha256 !== candidate.expectedSha256) continue;
|
|
139
|
+
|
|
140
|
+
next.push({
|
|
141
|
+
path: candidate.path ? `${candidate.path}/${entry.name}` : entry.name,
|
|
142
|
+
expectedSha256: declaredSha256 ?? candidate.expectedSha256,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
candidates = next;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const newest = candidates
|
|
151
|
+
.map((candidate) => ({ candidate, modifiedAt: statSync(path.join(repositoryRoot, candidate.path)).mtimeMs }))
|
|
152
|
+
.sort((left, right) => right.modifiedAt - left.modifiedAt)[0];
|
|
153
|
+
|
|
154
|
+
return newest?.candidate ?? null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Checks that an artifact path can be passed to `npm publish` on a shell command line.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} artifactPath - Relative path found by {@link findPreparedArtifact}.
|
|
161
|
+
* @returns {boolean} Whether the path only uses safe characters.
|
|
162
|
+
*/
|
|
163
|
+
export function isSafeArtifactPath(artifactPath) {
|
|
164
|
+
return SAFE_ARTIFACT_PATH_PATTERN.test(artifactPath) && !artifactPath.split("/").includes("..");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Computes the SHA-256 of a file.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} filePath - Absolute path.
|
|
171
|
+
* @returns {string} Lowercase hexadecimal digest.
|
|
172
|
+
*/
|
|
173
|
+
export function computeSha256(filePath) {
|
|
174
|
+
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Computes the npm integrity of a file: the same `sha512-<base64>` string that
|
|
180
|
+
* `npm pack --json` reports for the archive it writes.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} filePath - Absolute path.
|
|
183
|
+
* @returns {string} Subresource-integrity string.
|
|
184
|
+
*/
|
|
185
|
+
export function computeNpmIntegrity(filePath) {
|
|
186
|
+
return `${NPM_INTEGRITY_ALGORITHM}-${createHash(NPM_INTEGRITY_ALGORITHM).update(readFileSync(filePath)).digest("base64")}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Tells whether a value is a plain JSON object.
|
|
191
|
+
*
|
|
192
|
+
* @param {unknown} value - Any value.
|
|
193
|
+
* @returns {value is Record<string, unknown>} Whether it is a non-array object.
|
|
194
|
+
*/
|
|
195
|
+
function isRecord(value) {
|
|
196
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Lists what a manifest needs from `pnpm pack` and npm would publish as it is: `workspace:`,
|
|
201
|
+
* `catalog:` or `jsr:` specifiers in published dependency maps, and `publishConfig` keys pnpm hoists onto the
|
|
202
|
+
* manifest (such as `exports`, `main` or `bin`; npm only reads `publishConfig` as configuration).
|
|
203
|
+
* Every other `publishConfig` key is npm configuration and is accepted.
|
|
204
|
+
*
|
|
205
|
+
* @param {PackageManifest} manifest - `package.json` of the release checkout.
|
|
206
|
+
* @returns {string[]} Problems in Spanish; empty when npm packs the package as pnpm would.
|
|
207
|
+
*/
|
|
208
|
+
export function findPnpmPackRewrites(manifest) {
|
|
209
|
+
/** @type {string[]} */
|
|
210
|
+
const problems = [];
|
|
211
|
+
|
|
212
|
+
for (const field of PUBLISHED_DEPENDENCY_FIELDS) {
|
|
213
|
+
const dependencies = manifest[field];
|
|
214
|
+
if (!isRecord(dependencies)) continue;
|
|
215
|
+
for (const [dependencyName, specifier] of Object.entries(dependencies)) {
|
|
216
|
+
if (typeof specifier === "string" && PNPM_PACK_REWRITTEN_SPECIFIER_PATTERN.test(specifier)) {
|
|
217
|
+
problems.push(`${field}.${dependencyName} usa "${specifier}", que solo pnpm reescribe al empaquetar`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const publishConfig = manifest[PUBLISH_CONFIG_FIELD];
|
|
223
|
+
if (isRecord(publishConfig)) {
|
|
224
|
+
for (const key of Object.keys(publishConfig)) {
|
|
225
|
+
if (PNPM_HOISTED_PUBLISH_CONFIG_KEYS.includes(key)) {
|
|
226
|
+
problems.push(`${PUBLISH_CONFIG_FIELD}.${key} no es configuración de npm (solo pnpm lo aplica al empaquetar)`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return problems;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Moves a file, falling back to copy and delete when the destination is on another file system
|
|
236
|
+
* (`rename` fails with `EXDEV`, for example when the OS temp directory is another drive).
|
|
237
|
+
*
|
|
238
|
+
* @param {string} sourcePath - Absolute path of the file to move.
|
|
239
|
+
* @param {string} destinationPath - Absolute destination path.
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
function moveFile(sourcePath, destinationPath) {
|
|
243
|
+
try {
|
|
244
|
+
renameSync(sourcePath, destinationPath);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (!(error instanceof Error) || /** @type {NodeJS.ErrnoException} */ (error).code !== CROSS_DEVICE_RENAME_ERROR_CODE) {
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
copyFileSync(sourcePath, destinationPath);
|
|
250
|
+
unlinkSync(sourcePath);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Runs an operation with the prepared archive moved out of the package root, and always puts it
|
|
256
|
+
* back afterwards, also when the operation fails.
|
|
257
|
+
*
|
|
258
|
+
* `npm pack --dry-run` describes every file npm would pack now. When the package has no `files`
|
|
259
|
+
* allowlist and neither `.npmignore` nor `.gitignore` excludes the archive directory, the archive
|
|
260
|
+
* written by `prepare` (for example `releases/pkg-1.0.0.tgz`) would be packed into the package it
|
|
261
|
+
* is compared with, changing the integrity and rejecting a valid artifact. Hiding it reproduces the
|
|
262
|
+
* package root `prepare` packed: a directory left empty is ignored by npm, as it was then.
|
|
263
|
+
*
|
|
264
|
+
* @template T
|
|
265
|
+
* @param {string} repositoryRoot - Package root.
|
|
266
|
+
* @param {string} artifactPath - Archive relative to the root, found by {@link findPreparedArtifact}.
|
|
267
|
+
* @param {() => Promise<T>} operation - Runs while the archive is outside the package root.
|
|
268
|
+
* @param {string} [parentDirectory] - Where the holding directory is created; defaults to the OS temp directory.
|
|
269
|
+
* @returns {Promise<T>} The operation result.
|
|
270
|
+
*/
|
|
271
|
+
export async function withArtifactOutsidePackageRoot(repositoryRoot, artifactPath, operation, parentDirectory = tmpdir()) {
|
|
272
|
+
const archivePath = path.join(repositoryRoot, artifactPath);
|
|
273
|
+
const holdingDirectory = mkdtempSync(path.join(parentDirectory, ARTIFACT_HOLDING_DIRECTORY_PREFIX));
|
|
274
|
+
const heldArchivePath = path.join(holdingDirectory, path.basename(archivePath));
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
moveFile(archivePath, heldArchivePath);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
rmSync(holdingDirectory, { recursive: true, force: true });
|
|
280
|
+
throw new Error(`beez-rp create-version: no se pudo apartar ${artifactPath} para verificarlo`, { cause: error });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
return await operation();
|
|
285
|
+
} finally {
|
|
286
|
+
try {
|
|
287
|
+
moveFile(heldArchivePath, archivePath);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// The holding directory is kept so the archive is never lost.
|
|
290
|
+
throw new Error(`beez-rp create-version: no se pudo devolver ${artifactPath} a su lugar; quedó en ${heldArchivePath}`, { cause: error });
|
|
291
|
+
}
|
|
292
|
+
rmSync(holdingDirectory, { recursive: true, force: true });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Verifies a prepared archive before publishing it: the SHA-256 its path declares (when the
|
|
298
|
+
* pattern uses `{sha256}`) and the SHA-512 integrity `npm pack --dry-run` reports for the release
|
|
299
|
+
* checkout, so the archive is byte for byte what npm packs from that commit.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} repositoryRoot - Repository root.
|
|
302
|
+
* @param {PreparedArtifact} artifact - Archive found by {@link findPreparedArtifact}.
|
|
303
|
+
* @param {string} expectedIntegrity - `integrity` of `npm pack --dry-run --json --ignore-scripts`.
|
|
304
|
+
* @returns {string[]} Problems in Spanish; empty when the archive can be published.
|
|
305
|
+
*/
|
|
306
|
+
export function verifyPreparedArtifact(repositoryRoot, artifact, expectedIntegrity) {
|
|
307
|
+
const archivePath = path.join(repositoryRoot, artifact.path);
|
|
308
|
+
|
|
309
|
+
if (artifact.expectedSha256) {
|
|
310
|
+
const actualSha256 = computeSha256(archivePath);
|
|
311
|
+
if (actualSha256 !== artifact.expectedSha256) {
|
|
312
|
+
return [`el SHA-256 del tarball (${actualSha256}) no coincide con el de su ruta (${artifact.expectedSha256})`];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const actualIntegrity = computeNpmIntegrity(archivePath);
|
|
317
|
+
if (actualIntegrity !== expectedIntegrity) {
|
|
318
|
+
return [
|
|
319
|
+
`el tarball no es lo que npm empaquetaría de este commit (integrity ${actualIntegrity}; npm pack --dry-run informa ${expectedIntegrity})`,
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
@@ -15,6 +15,7 @@ import path from "node:path";
|
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
+
ARTIFACT_VERSION_PLACEHOLDER,
|
|
18
19
|
CHANGELOG_LANGUAGE,
|
|
19
20
|
CREATE_VERSION_CONFIG_FILE,
|
|
20
21
|
CREATE_VERSION_CONFIG_FILES,
|
|
@@ -51,6 +52,7 @@ import { RELEASE_TYPE_ORDER } from "../constants/versions.js";
|
|
|
51
52
|
* migrations?: MigrationsAdapter | null,
|
|
52
53
|
* prepare?: string[] | ReleaseHook | null,
|
|
53
54
|
* publish?: "npm" | ReleaseHook | null,
|
|
55
|
+
* artifact?: string | null,
|
|
54
56
|
* summary?: string[],
|
|
55
57
|
* }} CreateVersionConfig
|
|
56
58
|
* `summary` lines replace `{version}` with the released version.
|
|
@@ -64,6 +66,7 @@ import { RELEASE_TYPE_ORDER } from "../constants/versions.js";
|
|
|
64
66
|
* migrations: MigrationsAdapter | null,
|
|
65
67
|
* prepare: string[] | ReleaseHook | null,
|
|
66
68
|
* publish: "npm" | ReleaseHook | null,
|
|
69
|
+
* artifact: string | null,
|
|
67
70
|
* summary: string[],
|
|
68
71
|
* }} ResolvedCreateVersionConfig
|
|
69
72
|
*/
|
|
@@ -175,6 +178,16 @@ export function resolveCreateVersionConfig(rawConfig) {
|
|
|
175
178
|
throw invalidField("migrations.targetHint", "a string");
|
|
176
179
|
}
|
|
177
180
|
|
|
181
|
+
const artifact = config.artifact ?? null;
|
|
182
|
+
if (artifact !== null) {
|
|
183
|
+
if (typeof artifact !== "string" || !artifact.includes(ARTIFACT_VERSION_PLACEHOLDER)) {
|
|
184
|
+
throw invalidField("artifact", `a path pattern containing ${ARTIFACT_VERSION_PLACEHOLDER}, such as releases/{version}-*/{name}-{version}.tgz`);
|
|
185
|
+
}
|
|
186
|
+
if (publish !== NPM_PUBLISHER) {
|
|
187
|
+
throw invalidField("artifact", `used only with publish: "${NPM_PUBLISHER}"`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
178
191
|
const summary = config.summary ?? [];
|
|
179
192
|
if (!isStringList(summary)) {
|
|
180
193
|
throw invalidField("summary", "a list of lines");
|
|
@@ -194,12 +207,15 @@ export function resolveCreateVersionConfig(rawConfig) {
|
|
|
194
207
|
migrations,
|
|
195
208
|
prepare: /** @type {string[] | ReleaseHook | null} */ (prepare),
|
|
196
209
|
publish: /** @type {"npm" | ReleaseHook | null} */ (publish),
|
|
210
|
+
artifact: /** @type {string | null} */ (artifact),
|
|
197
211
|
summary,
|
|
198
212
|
};
|
|
199
213
|
}
|
|
200
214
|
|
|
201
215
|
/**
|
|
202
216
|
* Imports `beez-rp.config.mjs` or `beez-rp.config.js` from the repository root and validates it.
|
|
217
|
+
* It is imported once per process: after syncing `main` the command stops and asks to run it
|
|
218
|
+
* again, so a new process imports the updated file and everything it imports.
|
|
203
219
|
*
|
|
204
220
|
* @param {string} repositoryRoot - Repository root.
|
|
205
221
|
* @returns {Promise<ResolvedCreateVersionConfig>} Resolved configuration.
|
|
@@ -9,10 +9,31 @@
|
|
|
9
9
|
* @module create-version
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
export {
|
|
13
|
+
computeNpmIntegrity,
|
|
14
|
+
computeSha256,
|
|
15
|
+
expandArtifactPattern,
|
|
16
|
+
findPnpmPackRewrites,
|
|
17
|
+
findPreparedArtifact,
|
|
18
|
+
isSafeArtifactPath,
|
|
19
|
+
verifyPreparedArtifact,
|
|
20
|
+
withArtifactOutsidePackageRoot,
|
|
21
|
+
} from "./artifact.js";
|
|
12
22
|
export { defineCreateVersionConfig, loadCreateVersionConfig, resolveCreateVersionConfig } from "./config.js";
|
|
13
23
|
export { ReleaseStepError } from "./errors.js";
|
|
14
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
buildNpmAuthConfigLine,
|
|
26
|
+
buildNpmPublishArguments,
|
|
27
|
+
buildNpmPublishEnvironment,
|
|
28
|
+
buildNpmViewArguments,
|
|
29
|
+
lookupPublishedVersions,
|
|
30
|
+
parseNpmPackDryRunOutput,
|
|
31
|
+
publishToNpm,
|
|
32
|
+
readNpmPackIntegrity,
|
|
33
|
+
resolvePublishRegistry,
|
|
34
|
+
withNpmAuthConfig,
|
|
35
|
+
} from "./npm.js";
|
|
15
36
|
export { DEFAULT_CAPABILITIES, RELEASE_USAGE, buildReleasePlan, describeFeatureBranchGaps, parseReleaseArguments } from "./plan.js";
|
|
16
|
-
export { createGitReader, listCommits, parseCommitLog, readPackageVersionAt, runCaptured, runCommandLine, runInherited } from "./process.js";
|
|
37
|
+
export { createGitReader, listCommits, parseCommitLog, readPackageManifestAt, readPackageVersionAt, runCaptured, runCommandLine, runInherited } from "./process.js";
|
|
17
38
|
export { createHookContext, runCreateVersion } from "./run.js";
|
|
18
39
|
export { collectReleaseState, findLastRelease, readChangelogState } from "./state.js";
|