@pnpm/releasing.commands 1100.6.4 → 1100.7.1
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 +80 -0
- package/lib/change/index.d.ts +42 -0
- package/lib/change/index.js +248 -0
- package/lib/deploy/createDeployFiles.d.ts +26 -0
- package/lib/deploy/createDeployFiles.js +255 -0
- package/lib/deploy/deploy.d.ts +12 -0
- package/lib/deploy/deploy.js +398 -0
- package/lib/deploy/deployHook.d.ts +2 -0
- package/lib/deploy/deployHook.js +15 -0
- package/lib/deploy/index.d.ts +2 -0
- package/lib/deploy/index.js +3 -0
- package/lib/index.d.ts +7 -0
- package/lib/lane/index.d.ts +24 -0
- package/lib/lane/index.js +157 -0
- package/lib/pack-app/index.d.ts +2 -0
- package/lib/pack-app/index.js +3 -0
- package/lib/pack-app/packApp.d.ts +22 -0
- package/lib/pack-app/packApp.js +558 -0
- package/lib/publish/FailedToPublishError.d.ts +22 -0
- package/lib/publish/FailedToPublishError.js +40 -0
- package/lib/publish/batchPublish.d.ts +20 -0
- package/lib/publish/batchPublish.js +201 -0
- package/lib/publish/displayError.d.ts +1 -0
- package/lib/publish/displayError.js +23 -0
- package/lib/publish/executeTokenHelper.d.ts +4 -0
- package/lib/publish/executeTokenHelper.js +17 -0
- package/lib/publish/extractManifestFromPacked.d.ts +19 -0
- package/lib/publish/extractManifestFromPacked.js +110 -0
- package/lib/publish/index.d.ts +3 -0
- package/lib/publish/index.js +4 -0
- package/lib/publish/oidc/authToken.d.ts +73 -0
- package/lib/publish/oidc/authToken.js +97 -0
- package/lib/publish/oidc/idToken.d.ts +76 -0
- package/lib/publish/oidc/idToken.js +90 -0
- package/lib/publish/oidc/provenance.d.ts +73 -0
- package/lib/publish/oidc/provenance.js +91 -0
- package/lib/publish/otp.d.ts +38 -0
- package/lib/publish/otp.js +41 -0
- package/lib/publish/otpEnv.d.ts +7 -0
- package/lib/publish/otpEnv.js +5 -0
- package/lib/publish/pack.d.ts +34 -0
- package/lib/publish/pack.js +408 -0
- package/lib/publish/previousChangelog.d.ts +25 -0
- package/lib/publish/previousChangelog.js +194 -0
- package/lib/publish/publish.d.ts +40 -0
- package/lib/publish/publish.js +256 -0
- package/lib/publish/publishPackedPkg.d.ts +79 -0
- package/lib/publish/publishPackedPkg.js +298 -0
- package/lib/publish/recursivePublish.d.ts +18 -0
- package/lib/publish/recursivePublish.js +132 -0
- package/lib/publish/registryConfigKeys.d.ts +30 -0
- package/lib/publish/registryConfigKeys.js +50 -0
- package/lib/publish/utils/shared-context.d.ts +7 -0
- package/lib/publish/utils/shared-context.js +20 -0
- package/lib/resolveUnpublishedDirs.d.ts +10 -0
- package/lib/resolveUnpublishedDirs.js +14 -0
- package/lib/stage/approve.d.ts +2 -0
- package/lib/stage/approve.js +14 -0
- package/lib/stage/context.d.ts +14 -0
- package/lib/stage/context.js +25 -0
- package/lib/stage/download.d.ts +2 -0
- package/lib/stage/download.js +32 -0
- package/lib/stage/errors.d.ts +15 -0
- package/lib/stage/errors.js +17 -0
- package/lib/stage/help.d.ts +1 -0
- package/lib/stage/help.js +84 -0
- package/lib/stage/index.d.ts +13 -0
- package/lib/stage/index.js +57 -0
- package/lib/stage/list.d.ts +2 -0
- package/lib/stage/list.js +49 -0
- package/lib/stage/parsing.d.ts +6 -0
- package/lib/stage/parsing.js +27 -0
- package/lib/stage/publish.d.ts +7 -0
- package/lib/stage/publish.js +38 -0
- package/lib/stage/reject.d.ts +2 -0
- package/lib/stage/reject.js +16 -0
- package/lib/stage/rendering.d.ts +11 -0
- package/lib/stage/rendering.js +50 -0
- package/lib/stage/request.d.ts +29 -0
- package/lib/stage/request.js +110 -0
- package/lib/stage/types.d.ts +38 -0
- package/lib/stage/types.js +3 -0
- package/lib/stage/view.d.ts +2 -0
- package/lib/stage/view.js +14 -0
- package/lib/tarball/index.d.ts +2 -0
- package/lib/tarball/index.js +3 -0
- package/lib/tarball/publishSummary.d.ts +47 -0
- package/lib/tarball/publishSummary.js +36 -0
- package/lib/tarball/safeTarballFilename.d.ts +8 -0
- package/lib/tarball/safeTarballFilename.js +21 -0
- package/lib/tarball/summarizeTarball.d.ts +12 -0
- package/lib/tarball/summarizeTarball.js +84 -0
- package/lib/version/index.d.ts +35 -0
- package/lib/version/index.js +360 -0
- package/package.json +44 -44
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { docsUrl } from '@pnpm/cli.utils';
|
|
6
|
+
import { getNodeMirror, parseNodeSpecifier, resolveNodeVersion, } from '@pnpm/engine.runtime.node-resolver';
|
|
7
|
+
import { PnpmError } from '@pnpm/error';
|
|
8
|
+
import { runPnpmCli } from '@pnpm/exec.pnpm-cli-runner';
|
|
9
|
+
import { createFetchFromRegistry } from '@pnpm/network.fetch';
|
|
10
|
+
import { familySync } from 'detect-libc';
|
|
11
|
+
import { safeExeca as execa } from 'execa';
|
|
12
|
+
import { renderHelp } from 'render-help';
|
|
13
|
+
/** Minimum Node.js version that supports `node --build-sea`. */
|
|
14
|
+
const MIN_BUILDER_VERSION = { major: 25, minor: 5 };
|
|
15
|
+
// Target OS names match `process.platform`. That keeps the CLI surface
|
|
16
|
+
// consistent with pnpm's own `--os` flag (which also takes platform constants)
|
|
17
|
+
// and with `supportedArchitectures.os` in pnpm-workspace.yaml.
|
|
18
|
+
const SUPPORTED_OS = ['linux', 'darwin', 'win32'];
|
|
19
|
+
const SUPPORTED_TARGETS = 'linux-x64, linux-x64-musl, linux-arm64, linux-arm64-musl, darwin-x64, darwin-arm64, win32-x64, win32-arm64';
|
|
20
|
+
export const commandNames = ['pack-app'];
|
|
21
|
+
export function rcOptionsTypes() {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
export function cliOptionsTypes() {
|
|
25
|
+
return {
|
|
26
|
+
entry: String,
|
|
27
|
+
target: [String, Array],
|
|
28
|
+
runtime: String,
|
|
29
|
+
'output-dir': String,
|
|
30
|
+
'output-name': String,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export const shorthands = {
|
|
34
|
+
t: '--target',
|
|
35
|
+
o: '--output-dir',
|
|
36
|
+
};
|
|
37
|
+
export function help() {
|
|
38
|
+
return renderHelp({
|
|
39
|
+
description: 'Pack a CommonJS entry file into a standalone executable for one or more target platforms.\n\n' +
|
|
40
|
+
'The executable embeds a Node.js binary via the Node.js Single Executable Applications API.\n' +
|
|
41
|
+
`Requires Node.js v${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}+ to perform ` +
|
|
42
|
+
'the injection. SEA blobs are not compatible across Node.js minor releases, so the ' +
|
|
43
|
+
'builder Node.js must match the embedded runtime version exactly. The running Node.js ' +
|
|
44
|
+
'is used when it already matches; otherwise a host-arch Node.js of the embedded runtime ' +
|
|
45
|
+
'version is downloaded automatically.\n\n' +
|
|
46
|
+
'Defaults for --entry, --target, --runtime, --output-dir, and --output-name can be ' +
|
|
47
|
+
'set in the package.json under "pnpm.app". CLI flags override the config; --target entirely ' +
|
|
48
|
+
'replaces the configured list so you can narrow it at invocation time.',
|
|
49
|
+
url: docsUrl('pack-app'),
|
|
50
|
+
usages: [
|
|
51
|
+
'pnpm pack-app --entry dist/index.cjs --target linux-x64 --target win32-x64',
|
|
52
|
+
`pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --runtime node@${MIN_BUILDER_VERSION.major}`,
|
|
53
|
+
],
|
|
54
|
+
descriptionLists: [
|
|
55
|
+
{
|
|
56
|
+
title: 'Options',
|
|
57
|
+
list: [
|
|
58
|
+
{
|
|
59
|
+
description: 'Path to the CJS entry file to embed in the executable',
|
|
60
|
+
name: '--entry',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
description: `Target to build for. May be specified multiple times. Supported: ${SUPPORTED_TARGETS}`,
|
|
64
|
+
name: '--target',
|
|
65
|
+
shortAlias: '-t',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
description: 'Runtime to embed in the output executables, as a "<name>@<version>" spec ' +
|
|
69
|
+
`(e.g. "node@${MIN_BUILDER_VERSION.major}", "node@${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0"). ` +
|
|
70
|
+
`Only "node" is supported today, and the version must be >= v${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor} (the minimum that supports --build-sea). ` +
|
|
71
|
+
'Defaults to the running Node.js version.',
|
|
72
|
+
name: '--runtime',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
description: 'Output directory for the built executables. Defaults to "dist-app".',
|
|
76
|
+
name: '--output-dir',
|
|
77
|
+
shortAlias: '-o',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
description: 'Name for the output executable (without extension). Defaults to the unscoped package name.',
|
|
81
|
+
name: '--output-name',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function handler(opts, params) {
|
|
89
|
+
// pnpm.app in package.json supplies defaults for every flag. CLI flags win,
|
|
90
|
+
// but `--target` entirely replaces the config list (additive merging would
|
|
91
|
+
// prevent narrowing from the CLI). See ProjectAppConfig below for the shape.
|
|
92
|
+
const project = await readProjectAppConfig(opts.dir);
|
|
93
|
+
const entryPath = opts.entry ?? params[0] ?? project.app?.entry;
|
|
94
|
+
if (!entryPath) {
|
|
95
|
+
throw new PnpmError('PACK_APP_MISSING_ENTRY', '"pnpm pack-app" requires a CJS entry file — pass --entry <path> or set "pnpm.app.entry" in package.json.');
|
|
96
|
+
}
|
|
97
|
+
// `entry` may come from a repo-controlled package.json, so reject absolute
|
|
98
|
+
// paths and `..` traversal before touching the filesystem: the entry's
|
|
99
|
+
// contents get embedded into the produced executable, so an escaping path
|
|
100
|
+
// could exfiltrate a host file (e.g. an SSH key) into a distributable binary.
|
|
101
|
+
if (pathEscapesProject(entryPath)) {
|
|
102
|
+
throw new PnpmError('PACK_APP_ENTRY_OUTSIDE_PROJECT', `The entry path "${entryPath}" resolves outside the project directory.`, { hint: 'The entry must be a relative path inside the project directory, not an absolute path or one that escapes via "..".' });
|
|
103
|
+
}
|
|
104
|
+
const resolvedEntry = path.resolve(opts.dir, entryPath);
|
|
105
|
+
let entryStat;
|
|
106
|
+
try {
|
|
107
|
+
entryStat = fs.statSync(resolvedEntry);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new PnpmError('PACK_APP_ENTRY_NOT_FOUND', `Entry file not found: ${resolvedEntry}`);
|
|
111
|
+
}
|
|
112
|
+
if (!entryStat.isFile()) {
|
|
113
|
+
throw new PnpmError('PACK_APP_ENTRY_NOT_FILE', `Entry path must be a regular file: ${resolvedEntry}`);
|
|
114
|
+
}
|
|
115
|
+
// Defense in depth against a same-name symlink that points out of the
|
|
116
|
+
// project: resolve symlinks and require the real path to stay within the
|
|
117
|
+
// (also symlink-resolved) project directory.
|
|
118
|
+
if (!isWithinDir(resolvedEntry, opts.dir)) {
|
|
119
|
+
throw new PnpmError('PACK_APP_ENTRY_OUTSIDE_PROJECT', `The entry path "${entryPath}" resolves outside the project directory.`, { hint: 'The entry must be a relative path inside the project directory, not an absolute path or one that escapes via "..".' });
|
|
120
|
+
}
|
|
121
|
+
const cliTargets = opts.target == null
|
|
122
|
+
? undefined
|
|
123
|
+
: Array.isArray(opts.target) ? opts.target : [opts.target];
|
|
124
|
+
const rawTargets = cliTargets ?? project.app?.targets ?? [];
|
|
125
|
+
if (rawTargets.length === 0) {
|
|
126
|
+
throw new PnpmError('PACK_APP_MISSING_TARGET', `"pnpm pack-app" requires at least one target — pass --target <triplet> or set "pnpm.app.targets" in package.json. Supported: ${SUPPORTED_TARGETS}`);
|
|
127
|
+
}
|
|
128
|
+
const targets = rawTargets.map(parseTarget);
|
|
129
|
+
// Parse the runtime before output-name derivation and any network work so
|
|
130
|
+
// that a malformed --runtime fails fast with a clear error instead of being
|
|
131
|
+
// masked by later problems (missing package.json name, registry lookup, etc.).
|
|
132
|
+
const runtimeSpec = opts.runtime ?? project.app?.runtime ?? `node@${process.version.slice(1)}`;
|
|
133
|
+
const { version: requestedNodeSpec } = parseRuntime(runtimeSpec);
|
|
134
|
+
// `outputDir` is likewise repo-controllable; reject absolute paths and `..`
|
|
135
|
+
// traversal so build artifacts cannot be written outside the project directory.
|
|
136
|
+
const outputDirRaw = opts.outputDir ?? project.app?.outputDir ?? 'dist-app';
|
|
137
|
+
if (pathEscapesProject(outputDirRaw)) {
|
|
138
|
+
throw new PnpmError('PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT', `The output directory "${outputDirRaw}" resolves outside the project directory.`, { hint: 'The output directory must be a relative path inside the project directory, not an absolute path or one that escapes via "..".' });
|
|
139
|
+
}
|
|
140
|
+
const outputDir = path.resolve(opts.dir, outputDirRaw);
|
|
141
|
+
await mkdir(outputDir, { recursive: true });
|
|
142
|
+
// Defense in depth against a symlinked output directory that points out of
|
|
143
|
+
// the project: the lexical check above can't see through a symlink, so
|
|
144
|
+
// re-check containment once the real path exists.
|
|
145
|
+
if (!isWithinDir(outputDir, opts.dir)) {
|
|
146
|
+
throw new PnpmError('PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT', `The output directory "${outputDirRaw}" resolves outside the project directory.`, { hint: 'The output directory must be a relative path inside the project directory, not an absolute path or one that escapes via "..".' });
|
|
147
|
+
}
|
|
148
|
+
const outputName = validateOutputName(opts.outputName ?? project.app?.outputName ?? deriveOutputNameFromPackage(project, opts.dir));
|
|
149
|
+
// Reject a pre-existing symlink (or any non-regular file) at any target's
|
|
150
|
+
// final output path before downloading anything: a repo could commit
|
|
151
|
+
// `dist-app/<target>/<name>` as a symlink pointing outside the project, and
|
|
152
|
+
// `node --build-sea` would follow it to overwrite an arbitrary file. The
|
|
153
|
+
// directory containment checks above do not cover the leaf file.
|
|
154
|
+
for (const target of targets) {
|
|
155
|
+
rejectNonRegularOutputFile(path.join(outputDir, target.raw, outputFileName(outputName, target.platform)));
|
|
156
|
+
}
|
|
157
|
+
const fetch = createFetchFromRegistry(opts);
|
|
158
|
+
const buildRoot = path.join(opts.pnpmHomeDir, 'pack-app');
|
|
159
|
+
// Resolve the embedded target version first so the builder can be pinned to
|
|
160
|
+
// the same version. SEA blobs carry no version header and the serialized
|
|
161
|
+
// format has changed across Node.js minor releases (e.g. v25.7 added a
|
|
162
|
+
// ModuleFormat byte for ESM entry points), so a blob produced by a builder
|
|
163
|
+
// of a different version than the embedded runtime will fail deserialization
|
|
164
|
+
// at startup with an opaque native assertion.
|
|
165
|
+
const resolvedTargetVersion = await resolveVersion(fetch, requestedNodeSpec, opts.nodeDownloadMirrors);
|
|
166
|
+
const builderBin = await resolveBuilderBinary({
|
|
167
|
+
buildRoot,
|
|
168
|
+
targetVersion: resolvedTargetVersion,
|
|
169
|
+
});
|
|
170
|
+
const results = [];
|
|
171
|
+
for (const target of targets) {
|
|
172
|
+
// eslint-disable-next-line no-await-in-loop
|
|
173
|
+
const embeddedNodeBin = await ensureNodeRuntime({
|
|
174
|
+
buildRoot,
|
|
175
|
+
version: resolvedTargetVersion,
|
|
176
|
+
platform: target.platform,
|
|
177
|
+
arch: target.arch,
|
|
178
|
+
libc: target.libc,
|
|
179
|
+
});
|
|
180
|
+
const targetOutputDir = path.join(outputDir, target.raw);
|
|
181
|
+
// eslint-disable-next-line no-await-in-loop
|
|
182
|
+
await mkdir(targetOutputDir, { recursive: true });
|
|
183
|
+
// A repo could symlink `dist-app/<target>` out of the project even when
|
|
184
|
+
// `dist-app` itself is contained; re-check the real path before any
|
|
185
|
+
// binary is written into it.
|
|
186
|
+
if (!isWithinDir(targetOutputDir, opts.dir)) {
|
|
187
|
+
throw new PnpmError('PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT', `The output directory "${targetOutputDir}" resolves outside the project directory.`, { hint: 'The output directory must be a relative path inside the project directory, not an absolute path or one that escapes via "..".' });
|
|
188
|
+
}
|
|
189
|
+
const outputFile = path.join(targetOutputDir, outputFileName(outputName, target.platform));
|
|
190
|
+
// Re-check the leaf path right before the build in case it became a
|
|
191
|
+
// symlink after the upfront pass.
|
|
192
|
+
rejectNonRegularOutputFile(outputFile);
|
|
193
|
+
const seaConfig = {
|
|
194
|
+
main: resolvedEntry,
|
|
195
|
+
output: outputFile,
|
|
196
|
+
executable: embeddedNodeBin,
|
|
197
|
+
disableExperimentalSEAWarning: true,
|
|
198
|
+
useCodeCache: false,
|
|
199
|
+
useSnapshot: false,
|
|
200
|
+
};
|
|
201
|
+
// Write the SEA config into a fresh, unpredictable temp directory (0700
|
|
202
|
+
// by default) rather than a predictable path under os.tmpdir(). Avoids
|
|
203
|
+
// TOCTOU/symlink attacks on multi-user systems.
|
|
204
|
+
// eslint-disable-next-line no-await-in-loop
|
|
205
|
+
const tmpConfigDir = await mkdtemp(path.join(os.tmpdir(), 'pnpm-pack-app-'));
|
|
206
|
+
const configPath = path.join(tmpConfigDir, 'sea-config.json');
|
|
207
|
+
// eslint-disable-next-line no-await-in-loop
|
|
208
|
+
await writeFile(configPath, JSON.stringify(seaConfig, null, 2), { flag: 'wx' });
|
|
209
|
+
try {
|
|
210
|
+
// eslint-disable-next-line no-await-in-loop
|
|
211
|
+
await execa(builderBin, ['--build-sea', configPath], { stdio: 'inherit' });
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
// eslint-disable-next-line no-await-in-loop
|
|
215
|
+
await rm(tmpConfigDir, { recursive: true, force: true }).catch(() => { });
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line no-await-in-loop
|
|
218
|
+
await adHocSignMacBinary(target, outputFile, opts.dir);
|
|
219
|
+
results.push(` ${target.raw}: ${outputFile} (Node.js ${resolvedTargetVersion})`);
|
|
220
|
+
}
|
|
221
|
+
return `Built ${targets.length} executable${targets.length === 1 ? '' : 's'}:\n${results.join('\n')}`;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Returns a Node.js binary that supports `--build-sea` AND produces a SEA
|
|
225
|
+
* blob the embedded runtime can deserialize. The second constraint forces the
|
|
226
|
+
* builder to match the target runtime version exactly: blobs are versioned by
|
|
227
|
+
* the writer's internal struct layout with no header, and Node bumps that
|
|
228
|
+
* layout in minor releases (e.g. v25.7 added a ModuleFormat byte for ESM
|
|
229
|
+
* entries), so a cross-version blob crashes at startup.
|
|
230
|
+
*
|
|
231
|
+
* Prefers the running interpreter when it already matches the target version;
|
|
232
|
+
* otherwise downloads the target version for the host platform.
|
|
233
|
+
*/
|
|
234
|
+
async function resolveBuilderBinary(ctx) {
|
|
235
|
+
if (runningNodeCanBuildSea() && process.version === `v${ctx.targetVersion}`) {
|
|
236
|
+
return process.execPath;
|
|
237
|
+
}
|
|
238
|
+
if (!builderVersionCanBuildSea(ctx.targetVersion)) {
|
|
239
|
+
throw new PnpmError('PACK_APP_RUNTIME_TOO_OLD', `The embedded runtime "node@${ctx.targetVersion}" is older than Node.js v${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}, which is the minimum version that supports --build-sea.`, { hint: `Pass --runtime node@${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0 (or newer) or set "pnpm.app.runtime" in package.json.` });
|
|
240
|
+
}
|
|
241
|
+
return ensureNodeRuntime({
|
|
242
|
+
buildRoot: ctx.buildRoot,
|
|
243
|
+
version: ctx.targetVersion,
|
|
244
|
+
platform: process.platform,
|
|
245
|
+
arch: process.arch,
|
|
246
|
+
// Pin libc to the host's. Otherwise a caller that had set
|
|
247
|
+
// supportedArchitectures.libc=musl in their config would cause the
|
|
248
|
+
// glibc host to download a musl Node that it cannot execute.
|
|
249
|
+
libc: hostLinuxLibc(),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function hostLinuxLibc() {
|
|
253
|
+
if (process.platform !== 'linux')
|
|
254
|
+
return undefined;
|
|
255
|
+
const family = familySync();
|
|
256
|
+
return family === 'musl' ? 'musl' : 'glibc';
|
|
257
|
+
}
|
|
258
|
+
function runningNodeCanBuildSea() {
|
|
259
|
+
return builderVersionCanBuildSea(process.version.slice(1));
|
|
260
|
+
}
|
|
261
|
+
function builderVersionCanBuildSea(version) {
|
|
262
|
+
const [majorStr, minorStr] = version.split('.');
|
|
263
|
+
const major = Number(majorStr);
|
|
264
|
+
const minor = Number(minorStr);
|
|
265
|
+
return (major > MIN_BUILDER_VERSION.major ||
|
|
266
|
+
(major === MIN_BUILDER_VERSION.major && minor >= MIN_BUILDER_VERSION.minor));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Fetches a Node.js runtime into a dedicated per-target directory under the
|
|
270
|
+
* pnpm home, reusing the cached binary if already present. Actual files are
|
|
271
|
+
* hardlinked from pnpm's content-addressable store, so repeated calls are
|
|
272
|
+
* cheap and `pnpm store prune` can reclaim them.
|
|
273
|
+
*/
|
|
274
|
+
async function ensureNodeRuntime(opts) {
|
|
275
|
+
// Linux variants always need a libc pin (glibc or musl) so that variant
|
|
276
|
+
// selection is deterministic and doesn't depend on the host's detected
|
|
277
|
+
// libc or the user's supportedArchitectures.libc config.
|
|
278
|
+
const libc = opts.platform === 'linux' ? opts.libc ?? 'glibc' : opts.libc;
|
|
279
|
+
const targetId = [opts.platform, opts.arch, libc].filter(Boolean).join('-');
|
|
280
|
+
const installDir = path.join(opts.buildRoot, `${targetId}-${opts.version}`);
|
|
281
|
+
const nodeDir = path.join(installDir, 'node_modules', 'node');
|
|
282
|
+
const binaryPath = nodeBinaryPath(nodeDir, opts.platform);
|
|
283
|
+
if (fs.existsSync(binaryPath))
|
|
284
|
+
return binaryPath;
|
|
285
|
+
await mkdir(installDir, { recursive: true });
|
|
286
|
+
await writeFile(path.join(installDir, 'package.json'), `${JSON.stringify({ name: `pnpm-pack-app-${targetId}`, private: true }, null, 2)}\n`);
|
|
287
|
+
// Flags that select the target variant must come before the positional
|
|
288
|
+
// package spec; otherwise `pnpm add` silently installs the host variant.
|
|
289
|
+
const args = [
|
|
290
|
+
'add',
|
|
291
|
+
'--ignore-scripts',
|
|
292
|
+
'--ignore-workspace',
|
|
293
|
+
`--os=${opts.platform}`,
|
|
294
|
+
`--cpu=${opts.arch}`,
|
|
295
|
+
];
|
|
296
|
+
if (libc != null) {
|
|
297
|
+
args.push(`--libc=${libc}`);
|
|
298
|
+
}
|
|
299
|
+
args.push(`node@runtime:${opts.version}`);
|
|
300
|
+
runPnpmCli(args, { cwd: installDir });
|
|
301
|
+
if (!fs.existsSync(binaryPath)) {
|
|
302
|
+
throw new PnpmError('PACK_APP_NODE_BINARY_MISSING', `Expected Node.js binary at ${binaryPath} after installing node@runtime:${opts.version}, but it was not found.`);
|
|
303
|
+
}
|
|
304
|
+
return binaryPath;
|
|
305
|
+
}
|
|
306
|
+
function nodeBinaryPath(nodeDir, platform) {
|
|
307
|
+
return platform === 'win32'
|
|
308
|
+
? path.join(nodeDir, 'node.exe')
|
|
309
|
+
: path.join(nodeDir, 'bin', 'node');
|
|
310
|
+
}
|
|
311
|
+
async function resolveVersion(fetch, specifier, nodeDownloadMirrors) {
|
|
312
|
+
const { releaseChannel, versionSpecifier } = parseNodeSpecifier(specifier);
|
|
313
|
+
const nodeMirrorBaseUrl = getNodeMirror(nodeDownloadMirrors, releaseChannel);
|
|
314
|
+
const version = await resolveNodeVersion(fetch, versionSpecifier, nodeMirrorBaseUrl);
|
|
315
|
+
if (!version) {
|
|
316
|
+
throw new PnpmError('PACK_APP_NODE_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies "${specifier}"`);
|
|
317
|
+
}
|
|
318
|
+
return version;
|
|
319
|
+
}
|
|
320
|
+
// Parsed triplet must match this shape exactly. We anchor and constrain each
|
|
321
|
+
// segment so that inputs like `linux-x64-musl-../../outside` are rejected
|
|
322
|
+
// outright — otherwise `target.raw` would later flow into path.join for the
|
|
323
|
+
// output directory and could escape it.
|
|
324
|
+
const TARGET_PATTERN = /^(linux|darwin|win32)-(x64|arm64)(?:-(musl))?$/;
|
|
325
|
+
function parseTarget(raw) {
|
|
326
|
+
const match = TARGET_PATTERN.exec(raw);
|
|
327
|
+
if (!match) {
|
|
328
|
+
throw new PnpmError('PACK_APP_INVALID_TARGET', `Invalid target: "${raw}". Expected format: <os>-<arch>[-<libc>] where <os> is ${SUPPORTED_OS.join('|')}, <arch> is x64|arm64, optional <libc> is musl (linux only).`);
|
|
329
|
+
}
|
|
330
|
+
const [, platform, arch, libc] = match;
|
|
331
|
+
if (libc === 'musl' && platform !== 'linux') {
|
|
332
|
+
throw new PnpmError('PACK_APP_INVALID_TARGET', `The "musl" libc suffix is only valid for linux targets (got "${raw}").`);
|
|
333
|
+
}
|
|
334
|
+
return { raw, platform, arch, libc: libc || undefined };
|
|
335
|
+
}
|
|
336
|
+
// Runtime spec is "<name>@<version>". Only "node" is supported today; the
|
|
337
|
+
// prefix is kept so future runtimes (bun, deno) can share the same flag
|
|
338
|
+
// without a breaking change. Reading the runtime name rather than a bare
|
|
339
|
+
// version also avoids shadowing pnpm's global `node-version` rc setting,
|
|
340
|
+
// whose value would otherwise leak into Config['nodeVersion'] and override
|
|
341
|
+
// `pnpm.app.runtime`.
|
|
342
|
+
const SUPPORTED_RUNTIMES = ['node'];
|
|
343
|
+
const RUNTIME_PATTERN = /^(node)@(.+)$/;
|
|
344
|
+
function parseRuntime(spec) {
|
|
345
|
+
const match = RUNTIME_PATTERN.exec(spec);
|
|
346
|
+
if (!match) {
|
|
347
|
+
throw new PnpmError('PACK_APP_INVALID_RUNTIME', `Invalid runtime "${spec}". Expected format: <name>@<version> (supported runtimes: ${SUPPORTED_RUNTIMES.join(', ')}; e.g. "node@${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0").`);
|
|
348
|
+
}
|
|
349
|
+
return { name: match[1], version: match[2] };
|
|
350
|
+
}
|
|
351
|
+
// Characters that Win32 rejects in filenames, plus NUL. Path separators are
|
|
352
|
+
// checked separately via `path.basename` so the message is crisp.
|
|
353
|
+
const INVALID_FILENAME_CHARS = /[<>:"|?*\0]/;
|
|
354
|
+
// Win32 reserved device names (case-insensitive, with or without an extension).
|
|
355
|
+
const RESERVED_WINDOWS_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
356
|
+
// Reject anything that would let the output escape its target directory, or
|
|
357
|
+
// that would fail filesystem-level validation on any supported host. This
|
|
358
|
+
// surfaces problems at `pack-app` invocation time instead of letting them
|
|
359
|
+
// blow up later in `writeFile(outputFile, …)`.
|
|
360
|
+
function validateOutputName(name) {
|
|
361
|
+
if (name !== path.basename(name) ||
|
|
362
|
+
name === '' || name === '.' || name === '..' ||
|
|
363
|
+
name.includes('/') || name.includes('\\') ||
|
|
364
|
+
INVALID_FILENAME_CHARS.test(name) ||
|
|
365
|
+
RESERVED_WINDOWS_NAME.test(name) ||
|
|
366
|
+
/[. ]$/.test(name)) {
|
|
367
|
+
throw new PnpmError('PACK_APP_INVALID_OUTPUT_NAME', `Invalid --output-name "${name}". The name must be a plain filename without path separators, Windows-reserved names (e.g. CON, NUL), characters like <>:"|?* or NUL, and must not end in a dot or space.`);
|
|
368
|
+
}
|
|
369
|
+
return name;
|
|
370
|
+
}
|
|
371
|
+
// A narrow reader just for this command. Using readProjectManifest from
|
|
372
|
+
// @pnpm/cli.utils would pull in the installable/engine checks, which are
|
|
373
|
+
// irrelevant here: pack-app doesn't need the current project to be installable
|
|
374
|
+
// under the running Node, just to have a package.json with optional settings.
|
|
375
|
+
async function readProjectAppConfig(dir) {
|
|
376
|
+
let raw;
|
|
377
|
+
try {
|
|
378
|
+
raw = await readFile(path.join(dir, 'package.json'), 'utf8');
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return {};
|
|
382
|
+
}
|
|
383
|
+
let manifest;
|
|
384
|
+
try {
|
|
385
|
+
manifest = JSON.parse(raw);
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
throw new PnpmError('PACK_APP_INVALID_PACKAGE_JSON', `Failed to parse ${path.join(dir, 'package.json')}: ${err.message}`);
|
|
389
|
+
}
|
|
390
|
+
if (!isObject(manifest))
|
|
391
|
+
return {};
|
|
392
|
+
const name = typeof manifest.name === 'string' && manifest.name !== '' ? manifest.name : undefined;
|
|
393
|
+
const pnpmField = isObject(manifest.pnpm) ? manifest.pnpm : undefined;
|
|
394
|
+
const appField = pnpmField && isObject(pnpmField.app) ? pnpmField.app : undefined;
|
|
395
|
+
if (!appField)
|
|
396
|
+
return { name };
|
|
397
|
+
return { name, app: validateAppConfig(appField) };
|
|
398
|
+
}
|
|
399
|
+
function validateAppConfig(raw) {
|
|
400
|
+
const known = new Set(['entry', 'targets', 'runtime', 'outputDir', 'outputName']);
|
|
401
|
+
for (const key of Object.keys(raw)) {
|
|
402
|
+
if (!known.has(key)) {
|
|
403
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', `Unknown "pnpm.app.${key}" setting in package.json. Allowed keys: ${Array.from(known).join(', ')}.`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const config = {};
|
|
407
|
+
if (raw.entry != null) {
|
|
408
|
+
if (typeof raw.entry !== 'string') {
|
|
409
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.entry" must be a string.');
|
|
410
|
+
}
|
|
411
|
+
config.entry = raw.entry;
|
|
412
|
+
}
|
|
413
|
+
if (raw.targets != null) {
|
|
414
|
+
if (!Array.isArray(raw.targets) || !raw.targets.every((t) => typeof t === 'string')) {
|
|
415
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.targets" must be an array of strings.');
|
|
416
|
+
}
|
|
417
|
+
config.targets = raw.targets;
|
|
418
|
+
}
|
|
419
|
+
if (raw.runtime != null) {
|
|
420
|
+
if (typeof raw.runtime !== 'string') {
|
|
421
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.runtime" must be a string.');
|
|
422
|
+
}
|
|
423
|
+
config.runtime = raw.runtime;
|
|
424
|
+
}
|
|
425
|
+
if (raw.outputDir != null) {
|
|
426
|
+
if (typeof raw.outputDir !== 'string') {
|
|
427
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.outputDir" must be a string.');
|
|
428
|
+
}
|
|
429
|
+
config.outputDir = raw.outputDir;
|
|
430
|
+
}
|
|
431
|
+
if (raw.outputName != null) {
|
|
432
|
+
if (typeof raw.outputName !== 'string') {
|
|
433
|
+
throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.outputName" must be a string.');
|
|
434
|
+
}
|
|
435
|
+
config.outputName = raw.outputName;
|
|
436
|
+
}
|
|
437
|
+
return config;
|
|
438
|
+
}
|
|
439
|
+
function deriveOutputNameFromPackage(project, dir) {
|
|
440
|
+
if (!project.name) {
|
|
441
|
+
throw new PnpmError('PACK_APP_NO_OUTPUT_NAME', `Could not determine the output name: package.json in ${dir} has no "name" field.`, { hint: 'Pass --output-name <name> or set "pnpm.app.outputName" in package.json.' });
|
|
442
|
+
}
|
|
443
|
+
// Strip @scope/ prefix from scoped packages so the binary name is a plain
|
|
444
|
+
// filename instead of "scope/name". The second validateOutputName() pass
|
|
445
|
+
// downstream rejects any leftover path separators.
|
|
446
|
+
return project.name.replace(/^@[^/]+\//, '');
|
|
447
|
+
}
|
|
448
|
+
function isObject(value) {
|
|
449
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
450
|
+
}
|
|
451
|
+
// `entry` and `outputDir` can come from a repo-controlled package.json, so a
|
|
452
|
+
// malicious repo must not be able to read/write outside the project via an
|
|
453
|
+
// absolute path or `..` traversal. An absolute path replaces the base on join;
|
|
454
|
+
// a `..` segment climbs out. Checked lexically before any filesystem access so
|
|
455
|
+
// the failure is fast and side-effect-free.
|
|
456
|
+
function pathEscapesProject(rawPath) {
|
|
457
|
+
// `path.parse().root` is non-empty for any host-rooted form: a POSIX
|
|
458
|
+
// absolute path (`/x`), and on Windows also the drive-relative (`C:x`) and
|
|
459
|
+
// root-relative (`\x`) forms that `path.isAbsolute()` reports as relative
|
|
460
|
+
// yet still resolve outside the project. Platform-specific, matching the
|
|
461
|
+
// pacquet port (which rejects `Component::RootDir` / `Component::Prefix`).
|
|
462
|
+
if (path.parse(rawPath).root !== '')
|
|
463
|
+
return true;
|
|
464
|
+
return rawPath.split(/[/\\]/).includes('..');
|
|
465
|
+
}
|
|
466
|
+
// Defense in depth beyond `pathEscapesProject`: resolve symlinks and require
|
|
467
|
+
// the real target to stay inside the (also symlink-resolved) project dir, so a
|
|
468
|
+
// same-name symlink pointing out of the project is caught even though the
|
|
469
|
+
// lexical join looked contained. Fails closed on any realpath error.
|
|
470
|
+
function isWithinDir(target, dir) {
|
|
471
|
+
let realDir;
|
|
472
|
+
let realTarget;
|
|
473
|
+
try {
|
|
474
|
+
realDir = fs.realpathSync(dir);
|
|
475
|
+
realTarget = fs.realpathSync(target);
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
const rel = path.relative(realDir, realTarget);
|
|
481
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
482
|
+
}
|
|
483
|
+
// The on-disk file name of the produced executable for a target: a bare name
|
|
484
|
+
// on POSIX, suffixed with `.exe` on Windows.
|
|
485
|
+
function outputFileName(outputName, platform) {
|
|
486
|
+
return platform === 'win32' ? `${outputName}.exe` : outputName;
|
|
487
|
+
}
|
|
488
|
+
// Refuse to write to `outputFile` when it already exists and is not a regular
|
|
489
|
+
// file — most importantly a symlink, which `node --build-sea` would follow to
|
|
490
|
+
// overwrite a file outside the project. `lstatSync` does not traverse the final
|
|
491
|
+
// component, so a symlink reports `isFile() === false`. A missing path is fine.
|
|
492
|
+
function rejectNonRegularOutputFile(outputFile) {
|
|
493
|
+
const existing = fs.lstatSync(outputFile, { throwIfNoEntry: false });
|
|
494
|
+
if (existing && !existing.isFile()) {
|
|
495
|
+
throw new PnpmError('PACK_APP_OUTPUT_FILE_NOT_REGULAR', `The output file "${outputFile}" already exists and is not a regular file (e.g. a symlink); refusing to write through it.`, { hint: 'Remove the existing path, or choose a different --output-name or --output-dir.' });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* SEA injection invalidates the existing code signature on macOS binaries, so
|
|
500
|
+
* the output must be re-signed. Native macOS hosts use `codesign`; Linux hosts
|
|
501
|
+
* cross-signing a darwin target use `ldid`. Windows hosts have no readily
|
|
502
|
+
* available ad-hoc signer, so we refuse to produce an unsigned output silently
|
|
503
|
+
* and tell the user to re-sign on macOS or Linux.
|
|
504
|
+
*/
|
|
505
|
+
async function adHocSignMacBinary(target, outputFile, dir) {
|
|
506
|
+
if (target.platform !== 'darwin')
|
|
507
|
+
return;
|
|
508
|
+
if (process.platform === 'darwin') {
|
|
509
|
+
// `codesign` is a macOS system tool; spawn it by absolute path so a
|
|
510
|
+
// repo-controlled `node_modules/.bin/codesign` on PATH can't be run in its
|
|
511
|
+
// place.
|
|
512
|
+
await execa('/usr/bin/codesign', ['--sign', '-', outputFile], { stdio: 'inherit' });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (process.platform === 'linux') {
|
|
516
|
+
const ldid = resolveTrustedSigner('ldid', dir, outputFile);
|
|
517
|
+
try {
|
|
518
|
+
await execa(ldid, ['-S', outputFile], { stdio: 'inherit' });
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
throw new PnpmError('PACK_APP_MACOS_SIGN_FAILED', `Cross-compiled macOS binary at ${outputFile} could not be ad-hoc signed with "ldid".`, { hint: 'Install ldid (https://github.com/ProcursusTeam/ldid) or re-sign the binary on macOS with "codesign --sign - <file>".' });
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
throw new PnpmError('PACK_APP_MACOS_SIGN_UNSUPPORTED_HOST', `Cannot ad-hoc sign the macOS binary at ${outputFile} on a ${process.platform} host.`, { hint: 'Build macOS targets on a macOS or Linux host, or re-sign the produced binary yourself with "codesign --sign -" on macOS.' });
|
|
526
|
+
}
|
|
527
|
+
// Resolve an external signer (`ldid`) to an absolute path via PATH, skipping
|
|
528
|
+
// any match that resolves inside the project directory — a repo could ship
|
|
529
|
+
// `node_modules/.bin/ldid` and, if that directory is on the developer's PATH,
|
|
530
|
+
// get an attacker-controlled binary executed when packaging a darwin target.
|
|
531
|
+
// Returns the first match outside the project, or throws PACK_APP_MACOS_SIGN_FAILED.
|
|
532
|
+
function resolveTrustedSigner(name, dir, outputFile) {
|
|
533
|
+
for (const entry of (process.env.PATH ?? '').split(path.delimiter)) {
|
|
534
|
+
if (entry === '')
|
|
535
|
+
continue;
|
|
536
|
+
const candidate = path.join(entry, name);
|
|
537
|
+
let stat;
|
|
538
|
+
try {
|
|
539
|
+
stat = fs.statSync(candidate);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!stat.isFile())
|
|
545
|
+
continue;
|
|
546
|
+
if (isWithinDir(candidate, dir))
|
|
547
|
+
continue;
|
|
548
|
+
try {
|
|
549
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
return candidate;
|
|
555
|
+
}
|
|
556
|
+
throw new PnpmError('PACK_APP_MACOS_SIGN_FAILED', `Cross-compiled macOS binary at ${outputFile} could not be ad-hoc signed with "ldid".`, { hint: 'Install ldid (https://github.com/ProcursusTeam/ldid) or re-sign the binary on macOS with "codesign --sign - <file>".' });
|
|
557
|
+
}
|
|
558
|
+
//# sourceMappingURL=packApp.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import type { PackResult } from './pack.js';
|
|
3
|
+
interface PublishErrorProperties<Pack> {
|
|
4
|
+
readonly pack: Pack;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly statusText: string;
|
|
7
|
+
readonly text: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class FailedToPublishError<Pack extends Pick<PackResult, 'publishedManifest'>> extends PnpmError implements PublishErrorProperties<Pack> {
|
|
10
|
+
readonly pack: Pack;
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly statusText: string;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
constructor(opts: PublishErrorProperties<Pack>);
|
|
15
|
+
}
|
|
16
|
+
export declare function createFailedToPublishError<Pack extends Pick<PackResult, 'publishedManifest'>>(pack: Pack, fetchResponse: FetchResponse): Promise<FailedToPublishError<Pack>>;
|
|
17
|
+
interface FetchResponse {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly statusText: string;
|
|
20
|
+
readonly text: (this: FetchResponse) => string | Promise<string>;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
export class FailedToPublishError extends PnpmError {
|
|
3
|
+
pack;
|
|
4
|
+
status;
|
|
5
|
+
statusText;
|
|
6
|
+
text;
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
const { pack, status, statusText, text } = opts;
|
|
9
|
+
const { name, version } = pack.publishedManifest;
|
|
10
|
+
const statusDisplay = statusText ? `${status} ${statusText}` : status;
|
|
11
|
+
const trimmedText = text.trim();
|
|
12
|
+
let message = `Failed to publish package ${name}@${version} (status ${statusDisplay})`;
|
|
13
|
+
if (trimmedText.includes('\n')) {
|
|
14
|
+
message += '\nDetails:\n';
|
|
15
|
+
for (const line of text.trimEnd().split('\n')) {
|
|
16
|
+
message += ` ${line}\n`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else if (trimmedText) {
|
|
20
|
+
message += `: ${trimmedText}`;
|
|
21
|
+
}
|
|
22
|
+
super('FAILED_TO_PUBLISH', message);
|
|
23
|
+
this.pack = pack;
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.statusText = statusText;
|
|
26
|
+
this.text = text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function createFailedToPublishError(pack, fetchResponse) {
|
|
30
|
+
const { status, statusText } = fetchResponse;
|
|
31
|
+
let text;
|
|
32
|
+
try {
|
|
33
|
+
text = await fetchResponse.text();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
text = '';
|
|
37
|
+
}
|
|
38
|
+
return new FailedToPublishError({ pack, status, statusText, text });
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=FailedToPublishError.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Config, ConfigContext } from '@pnpm/config.reader';
|
|
2
|
+
import type { Project } from '@pnpm/types';
|
|
3
|
+
import { type PublishSummary } from '../tarball/publishSummary.js';
|
|
4
|
+
import type { PublishRecursiveOpts } from './recursivePublish.js';
|
|
5
|
+
export declare const BATCH_PUBLISH_ENDPOINT = "/-/pnpm/v1/publish";
|
|
6
|
+
export type BatchPublishOptions = PublishRecursiveOpts & Pick<Config, 'embedReadme' | 'ignoreScripts' | 'nodeLinker' | 'packGzipLevel' | 'skipManifestObfuscation'> & Partial<Pick<ConfigContext, 'hooks'>>;
|
|
7
|
+
/**
|
|
8
|
+
* Publish all {@link pkgs} by sending a single `PUT /-/pnpm/v1/publish` request per target
|
|
9
|
+
* registry, instead of one request per package. The endpoint is not part of the standard npm
|
|
10
|
+
* registry API — the registry has to implement it explicitly (pnpr does), so this whole code
|
|
11
|
+
* path is opt-in via the `--batch` flag.
|
|
12
|
+
*
|
|
13
|
+
* Every package is packed (and its `prepublishOnly`/`prepublish` scripts run) before anything
|
|
14
|
+
* is sent, so a package that fails to pack aborts the publish before any package reaches the
|
|
15
|
+
* registry. The `publish`/`postpublish` scripts run only after the requests succeeded.
|
|
16
|
+
*
|
|
17
|
+
* @param pkgs packages to publish, already filtered (no private packages, no already-published
|
|
18
|
+
* versions) and in dependency order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function batchPublishPackages(pkgs: Project[], opts: BatchPublishOptions): Promise<PublishSummary[]>;
|