@pnpm/releasing.commands 1100.6.0 → 1100.6.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +22 -22
  3. package/lib/change/index.d.ts +0 -41
  4. package/lib/change/index.js +0 -245
  5. package/lib/deploy/createDeployFiles.d.ts +0 -26
  6. package/lib/deploy/createDeployFiles.js +0 -255
  7. package/lib/deploy/deploy.d.ts +0 -12
  8. package/lib/deploy/deploy.js +0 -398
  9. package/lib/deploy/deployHook.d.ts +0 -2
  10. package/lib/deploy/deployHook.js +0 -15
  11. package/lib/deploy/index.d.ts +0 -2
  12. package/lib/deploy/index.js +0 -3
  13. package/lib/index.d.ts +0 -7
  14. package/lib/lane/index.d.ts +0 -24
  15. package/lib/lane/index.js +0 -157
  16. package/lib/pack-app/index.d.ts +0 -2
  17. package/lib/pack-app/index.js +0 -3
  18. package/lib/pack-app/packApp.d.ts +0 -22
  19. package/lib/pack-app/packApp.js +0 -558
  20. package/lib/publish/FailedToPublishError.d.ts +0 -22
  21. package/lib/publish/FailedToPublishError.js +0 -40
  22. package/lib/publish/batchPublish.d.ts +0 -20
  23. package/lib/publish/batchPublish.js +0 -201
  24. package/lib/publish/displayError.d.ts +0 -1
  25. package/lib/publish/displayError.js +0 -23
  26. package/lib/publish/executeTokenHelper.d.ts +0 -4
  27. package/lib/publish/executeTokenHelper.js +0 -17
  28. package/lib/publish/extractManifestFromPacked.d.ts +0 -19
  29. package/lib/publish/extractManifestFromPacked.js +0 -110
  30. package/lib/publish/index.d.ts +0 -3
  31. package/lib/publish/index.js +0 -4
  32. package/lib/publish/oidc/authToken.d.ts +0 -73
  33. package/lib/publish/oidc/authToken.js +0 -97
  34. package/lib/publish/oidc/idToken.d.ts +0 -76
  35. package/lib/publish/oidc/idToken.js +0 -90
  36. package/lib/publish/oidc/provenance.d.ts +0 -73
  37. package/lib/publish/oidc/provenance.js +0 -91
  38. package/lib/publish/otp.d.ts +0 -38
  39. package/lib/publish/otp.js +0 -41
  40. package/lib/publish/otpEnv.d.ts +0 -7
  41. package/lib/publish/otpEnv.js +0 -5
  42. package/lib/publish/pack.d.ts +0 -34
  43. package/lib/publish/pack.js +0 -408
  44. package/lib/publish/previousChangelog.d.ts +0 -19
  45. package/lib/publish/previousChangelog.js +0 -182
  46. package/lib/publish/publish.d.ts +0 -40
  47. package/lib/publish/publish.js +0 -256
  48. package/lib/publish/publishPackedPkg.d.ts +0 -79
  49. package/lib/publish/publishPackedPkg.js +0 -298
  50. package/lib/publish/recursivePublish.d.ts +0 -18
  51. package/lib/publish/recursivePublish.js +0 -132
  52. package/lib/publish/registryConfigKeys.d.ts +0 -30
  53. package/lib/publish/registryConfigKeys.js +0 -50
  54. package/lib/publish/utils/shared-context.d.ts +0 -7
  55. package/lib/publish/utils/shared-context.js +0 -20
  56. package/lib/stage/approve.d.ts +0 -2
  57. package/lib/stage/approve.js +0 -14
  58. package/lib/stage/context.d.ts +0 -14
  59. package/lib/stage/context.js +0 -25
  60. package/lib/stage/download.d.ts +0 -2
  61. package/lib/stage/download.js +0 -32
  62. package/lib/stage/errors.d.ts +0 -15
  63. package/lib/stage/errors.js +0 -17
  64. package/lib/stage/help.d.ts +0 -1
  65. package/lib/stage/help.js +0 -84
  66. package/lib/stage/index.d.ts +0 -13
  67. package/lib/stage/index.js +0 -57
  68. package/lib/stage/list.d.ts +0 -2
  69. package/lib/stage/list.js +0 -49
  70. package/lib/stage/parsing.d.ts +0 -6
  71. package/lib/stage/parsing.js +0 -27
  72. package/lib/stage/publish.d.ts +0 -7
  73. package/lib/stage/publish.js +0 -38
  74. package/lib/stage/reject.d.ts +0 -2
  75. package/lib/stage/reject.js +0 -16
  76. package/lib/stage/rendering.d.ts +0 -11
  77. package/lib/stage/rendering.js +0 -50
  78. package/lib/stage/request.d.ts +0 -29
  79. package/lib/stage/request.js +0 -110
  80. package/lib/stage/types.d.ts +0 -38
  81. package/lib/stage/types.js +0 -3
  82. package/lib/stage/view.d.ts +0 -2
  83. package/lib/stage/view.js +0 -14
  84. package/lib/tarball/index.d.ts +0 -2
  85. package/lib/tarball/index.js +0 -3
  86. package/lib/tarball/publishSummary.d.ts +0 -47
  87. package/lib/tarball/publishSummary.js +0 -36
  88. package/lib/tarball/safeTarballFilename.d.ts +0 -8
  89. package/lib/tarball/safeTarballFilename.js +0 -21
  90. package/lib/tarball/summarizeTarball.d.ts +0 -12
  91. package/lib/tarball/summarizeTarball.js +0 -84
  92. package/lib/version/index.d.ts +0 -33
  93. package/lib/version/index.js +0 -337
@@ -1,558 +0,0 @@
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
@@ -1,22 +0,0 @@
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 {};
@@ -1,40 +0,0 @@
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
@@ -1,20 +0,0 @@
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[]>;