@pnpm/releasing.commands 1100.1.0 → 1100.2.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.
@@ -7,7 +7,7 @@ export declare function help(): string;
7
7
  export type PackAppOptions = Pick<Config, 'dir' | 'pnpmHomeDir'> & Partial<Pick<Config, 'ca' | 'cert' | 'configByUri' | 'httpProxy' | 'httpsProxy' | 'key' | 'localAddress' | 'nodeDownloadMirrors' | 'noProxy' | 'strictSsl' | 'userAgent'>> & {
8
8
  entry?: string;
9
9
  target?: string | string[];
10
- nodeVersion?: string;
10
+ runtime?: string;
11
11
  outputDir?: string;
12
12
  outputName?: string;
13
13
  };
@@ -16,7 +16,7 @@ export declare function handler(opts: PackAppOptions, params: string[]): Promise
16
16
  export interface ProjectAppConfig {
17
17
  entry?: string;
18
18
  targets?: string[];
19
- nodeVersion?: string;
19
+ runtime?: string;
20
20
  outputDir?: string;
21
21
  outputName?: string;
22
22
  }
@@ -12,11 +12,6 @@ import { safeExeca as execa } from 'execa';
12
12
  import { renderHelp } from 'render-help';
13
13
  /** Minimum Node.js version that supports `node --build-sea`. */
14
14
  const MIN_BUILDER_VERSION = { major: 25, minor: 5 };
15
- // Range to download when the running Node is too old. Constrained to the
16
- // current major so we don't silently jump majors across releases, and pinned
17
- // above MIN_BUILDER_VERSION.minor so older point releases (e.g. 25.0.x) that
18
- // don't support `--build-sea` aren't picked.
19
- const DEFAULT_BUILDER_SPEC = `>=${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}.0 <${MIN_BUILDER_VERSION.major + 1}.0.0`;
20
15
  // Target OS names match `process.platform`. That keeps the CLI surface
21
16
  // consistent with pnpm's own `--os` flag (which also takes platform constants)
22
17
  // and with `supportedArchitectures.os` in pnpm-workspace.yaml.
@@ -30,7 +25,7 @@ export function cliOptionsTypes() {
30
25
  return {
31
26
  entry: String,
32
27
  target: [String, Array],
33
- 'node-version': String,
28
+ runtime: String,
34
29
  'output-dir': String,
35
30
  'output-name': String,
36
31
  };
@@ -44,16 +39,17 @@ export function help() {
44
39
  description: 'Pack a CommonJS entry file into a standalone executable for one or more target platforms.\n\n' +
45
40
  'The executable embeds a Node.js binary via the Node.js Single Executable Applications API.\n' +
46
41
  `Requires Node.js v${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}+ to perform ` +
47
- 'the injection. The running Node.js is used when it is new enough; otherwise, the ' +
48
- `latest Node.js v${MIN_BUILDER_VERSION.major}.${MIN_BUILDER_VERSION.minor}+ in the ` +
49
- `v${MIN_BUILDER_VERSION.major}.x line is downloaded automatically.\n\n` +
50
- 'Defaults for --entry, --target, --node-version, --output-dir, and --output-name can be ' +
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 ' +
51
47
  'set in the package.json under "pnpm.app". CLI flags override the config; --target entirely ' +
52
48
  'replaces the configured list so you can narrow it at invocation time.',
53
49
  url: docsUrl('pack-app'),
54
50
  usages: [
55
51
  'pnpm pack-app --entry dist/index.cjs --target linux-x64 --target win32-x64',
56
- 'pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --node-version 22',
52
+ `pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --runtime node@${MIN_BUILDER_VERSION.major}`,
57
53
  ],
58
54
  descriptionLists: [
59
55
  {
@@ -69,9 +65,11 @@ export function help() {
69
65
  shortAlias: '-t',
70
66
  },
71
67
  {
72
- description: 'Node.js version to embed in the output executables (e.g. "22", "22.0.0", "lts"). ' +
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). ` +
73
71
  'Defaults to the running Node.js version.',
74
- name: '--node-version',
72
+ name: '--runtime',
75
73
  },
76
74
  {
77
75
  description: 'Output directory for the built executables. Defaults to "dist-app".',
@@ -115,14 +113,27 @@ export async function handler(opts, params) {
115
113
  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}`);
116
114
  }
117
115
  const targets = rawTargets.map(parseTarget);
116
+ // Parse the runtime before output-name derivation and any network work so
117
+ // that a malformed --runtime fails fast with a clear error instead of being
118
+ // masked by later problems (missing package.json name, registry lookup, etc.).
119
+ const runtimeSpec = opts.runtime ?? project.app?.runtime ?? `node@${process.version.slice(1)}`;
120
+ const { version: requestedNodeSpec } = parseRuntime(runtimeSpec);
118
121
  const outputDir = path.resolve(opts.dir, opts.outputDir ?? project.app?.outputDir ?? 'dist-app');
119
122
  await mkdir(outputDir, { recursive: true });
120
123
  const outputName = validateOutputName(opts.outputName ?? project.app?.outputName ?? deriveOutputNameFromPackage(project, opts.dir));
121
- const requestedNodeSpec = opts.nodeVersion ?? project.app?.nodeVersion ?? process.version.slice(1);
122
124
  const fetch = createFetchFromRegistry(opts);
123
125
  const buildRoot = path.join(opts.pnpmHomeDir, 'pack-app');
124
- const builderBin = await resolveBuilderBinary({ fetch, nodeDownloadMirrors: opts.nodeDownloadMirrors, buildRoot });
126
+ // Resolve the embedded target version first so the builder can be pinned to
127
+ // the same version. SEA blobs carry no version header and the serialized
128
+ // format has changed across Node.js minor releases (e.g. v25.7 added a
129
+ // ModuleFormat byte for ESM entry points), so a blob produced by a builder
130
+ // of a different version than the embedded runtime will fail deserialization
131
+ // at startup with an opaque native assertion.
125
132
  const resolvedTargetVersion = await resolveVersion(fetch, requestedNodeSpec, opts.nodeDownloadMirrors);
133
+ const builderBin = await resolveBuilderBinary({
134
+ buildRoot,
135
+ targetVersion: resolvedTargetVersion,
136
+ });
126
137
  const results = [];
127
138
  for (const target of targets) {
128
139
  // eslint-disable-next-line no-await-in-loop
@@ -170,17 +181,26 @@ export async function handler(opts, params) {
170
181
  return `Built ${targets.length} executable${targets.length === 1 ? '' : 's'}:\n${results.join('\n')}`;
171
182
  }
172
183
  /**
173
- * Returns a Node.js binary that supports `--build-sea`. Prefers the running
174
- * interpreter to avoid a download; falls back to downloading Node.js v25.
184
+ * Returns a Node.js binary that supports `--build-sea` AND produces a SEA
185
+ * blob the embedded runtime can deserialize. The second constraint forces the
186
+ * builder to match the target runtime version exactly: blobs are versioned by
187
+ * the writer's internal struct layout with no header, and Node bumps that
188
+ * layout in minor releases (e.g. v25.7 added a ModuleFormat byte for ESM
189
+ * entries), so a cross-version blob crashes at startup.
190
+ *
191
+ * Prefers the running interpreter when it already matches the target version;
192
+ * otherwise downloads the target version for the host platform.
175
193
  */
176
194
  async function resolveBuilderBinary(ctx) {
177
- if (runningNodeCanBuildSea()) {
195
+ if (runningNodeCanBuildSea() && process.version === `v${ctx.targetVersion}`) {
178
196
  return process.execPath;
179
197
  }
180
- const version = await resolveVersion(ctx.fetch, DEFAULT_BUILDER_SPEC, ctx.nodeDownloadMirrors);
198
+ if (!builderVersionCanBuildSea(ctx.targetVersion)) {
199
+ 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.` });
200
+ }
181
201
  return ensureNodeRuntime({
182
202
  buildRoot: ctx.buildRoot,
183
- version,
203
+ version: ctx.targetVersion,
184
204
  platform: process.platform,
185
205
  arch: process.arch,
186
206
  // Pin libc to the host's. Otherwise a caller that had set
@@ -196,7 +216,10 @@ function hostLinuxLibc() {
196
216
  return family === 'musl' ? 'musl' : 'glibc';
197
217
  }
198
218
  function runningNodeCanBuildSea() {
199
- const [majorStr, minorStr] = process.version.slice(1).split('.');
219
+ return builderVersionCanBuildSea(process.version.slice(1));
220
+ }
221
+ function builderVersionCanBuildSea(version) {
222
+ const [majorStr, minorStr] = version.split('.');
200
223
  const major = Number(majorStr);
201
224
  const minor = Number(minorStr);
202
225
  return (major > MIN_BUILDER_VERSION.major ||
@@ -270,6 +293,21 @@ function parseTarget(raw) {
270
293
  }
271
294
  return { raw, platform, arch, libc: libc || undefined };
272
295
  }
296
+ // Runtime spec is "<name>@<version>". Only "node" is supported today; the
297
+ // prefix is kept so future runtimes (bun, deno) can share the same flag
298
+ // without a breaking change. Reading the runtime name rather than a bare
299
+ // version also avoids shadowing pnpm's global `node-version` rc setting,
300
+ // whose value would otherwise leak into Config['nodeVersion'] and override
301
+ // `pnpm.app.runtime`.
302
+ const SUPPORTED_RUNTIMES = ['node'];
303
+ const RUNTIME_PATTERN = /^(node)@(.+)$/;
304
+ function parseRuntime(spec) {
305
+ const match = RUNTIME_PATTERN.exec(spec);
306
+ if (!match) {
307
+ 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").`);
308
+ }
309
+ return { name: match[1], version: match[2] };
310
+ }
273
311
  // Characters that Win32 rejects in filenames, plus NUL. Path separators are
274
312
  // checked separately via `path.basename` so the message is crisp.
275
313
  const INVALID_FILENAME_CHARS = /[<>:"|?*\0]/;
@@ -319,7 +357,7 @@ async function readProjectAppConfig(dir) {
319
357
  return { name, app: validateAppConfig(appField) };
320
358
  }
321
359
  function validateAppConfig(raw) {
322
- const known = new Set(['entry', 'targets', 'nodeVersion', 'outputDir', 'outputName']);
360
+ const known = new Set(['entry', 'targets', 'runtime', 'outputDir', 'outputName']);
323
361
  for (const key of Object.keys(raw)) {
324
362
  if (!known.has(key)) {
325
363
  throw new PnpmError('PACK_APP_INVALID_CONFIG', `Unknown "pnpm.app.${key}" setting in package.json. Allowed keys: ${Array.from(known).join(', ')}.`);
@@ -338,11 +376,11 @@ function validateAppConfig(raw) {
338
376
  }
339
377
  config.targets = raw.targets;
340
378
  }
341
- if (raw.nodeVersion != null) {
342
- if (typeof raw.nodeVersion !== 'string') {
343
- throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.nodeVersion" must be a string.');
379
+ if (raw.runtime != null) {
380
+ if (typeof raw.runtime !== 'string') {
381
+ throw new PnpmError('PACK_APP_INVALID_CONFIG', '"pnpm.app.runtime" must be a string.');
344
382
  }
345
- config.nodeVersion = raw.nodeVersion;
383
+ config.runtime = raw.runtime;
346
384
  }
347
385
  if (raw.outputDir != null) {
348
386
  if (typeof raw.outputDir !== 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/releasing.commands",
3
- "version": "1100.1.0",
3
+ "version": "1100.2.1",
4
4
  "description": "Commands for deploy, pack, and publish",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -47,35 +47,35 @@
47
47
  "validate-npm-package-name": "7.0.2",
48
48
  "write-json-file": "^7.0.0",
49
49
  "write-yaml-file": "^6.0.0",
50
+ "@pnpm/catalogs.types": "1100.0.0",
51
+ "@pnpm/cli.common-cli-options-help": "1100.0.0",
50
52
  "@pnpm/bins.resolver": "1100.0.1",
51
53
  "@pnpm/cli.utils": "1101.0.0",
52
- "@pnpm/cli.common-cli-options-help": "1100.0.0",
53
54
  "@pnpm/config.pick-registry-for-package": "1100.0.1",
54
55
  "@pnpm/constants": "1100.0.0",
55
- "@pnpm/engine.runtime.node-resolver": "1100.0.3",
56
+ "@pnpm/deps.path": "1100.0.1",
57
+ "@pnpm/engine.runtime.node-resolver": "1101.0.0",
58
+ "@pnpm/engine.runtime.commands": "1100.0.4",
56
59
  "@pnpm/error": "1100.0.0",
57
- "@pnpm/fetching.directory-fetcher": "1100.0.3",
60
+ "@pnpm/config.reader": "1101.1.1",
61
+ "@pnpm/exec.lifecycle": "1100.0.4",
58
62
  "@pnpm/exec.pnpm-cli-runner": "1100.0.0",
59
- "@pnpm/exec.lifecycle": "1100.0.3",
60
- "@pnpm/deps.path": "1100.0.1",
61
- "@pnpm/installing.client": "1100.0.3",
63
+ "@pnpm/fetching.directory-fetcher": "1100.0.4",
62
64
  "@pnpm/fs.is-empty-dir-or-nothing": "1100.0.0",
65
+ "@pnpm/fs.indexed-pkg-importer": "1100.0.3",
63
66
  "@pnpm/fs.packlist": "1100.0.0",
64
- "@pnpm/catalogs.types": "1100.0.0",
65
- "@pnpm/fs.indexed-pkg-importer": "1100.0.2",
66
- "@pnpm/engine.runtime.commands": "1100.0.3",
67
+ "@pnpm/installing.client": "1100.0.4",
68
+ "@pnpm/installing.commands": "1100.1.2",
69
+ "@pnpm/lockfile.fs": "1100.0.3",
67
70
  "@pnpm/lockfile.types": "1100.0.2",
68
- "@pnpm/network.web-auth": "1101.0.0",
69
71
  "@pnpm/network.git-utils": "1100.0.0",
72
+ "@pnpm/network.web-auth": "1101.0.0",
70
73
  "@pnpm/releasing.exportable-manifest": "1100.0.2",
71
- "@pnpm/types": "1101.0.0",
72
74
  "@pnpm/resolving.resolver-base": "1100.1.0",
73
- "@pnpm/lockfile.fs": "1100.0.2",
74
- "@pnpm/workspace.projects-filter": "1100.0.3",
75
- "@pnpm/workspace.projects-sorter": "1100.0.1",
75
+ "@pnpm/types": "1101.0.0",
76
76
  "@pnpm/network.fetch": "1100.0.1",
77
- "@pnpm/config.reader": "1101.1.0",
78
- "@pnpm/installing.commands": "1100.1.1"
77
+ "@pnpm/workspace.projects-filter": "1100.0.4",
78
+ "@pnpm/workspace.projects-sorter": "1100.0.1"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -98,14 +98,14 @@
98
98
  "load-json-file": "^7.0.1",
99
99
  "tar": "^7.5.10",
100
100
  "write-yaml-file": "^6.0.0",
101
- "@pnpm/hooks.pnpmfile": "1100.0.2",
102
- "@pnpm/prepare": "1100.0.2",
103
- "@pnpm/assert-project": "1100.0.2",
104
- "@pnpm/releasing.commands": "1100.1.0",
101
+ "@pnpm/catalogs.config": "1100.0.0",
102
+ "@pnpm/hooks.pnpmfile": "1100.0.3",
103
+ "@pnpm/prepare": "1100.0.3",
104
+ "@pnpm/assert-project": "1100.0.3",
105
105
  "@pnpm/logger": "1100.0.0",
106
+ "@pnpm/releasing.commands": "1100.2.1",
106
107
  "@pnpm/test-fixtures": "1100.0.0",
107
108
  "@pnpm/testing.command-defaults": "1100.0.1",
108
- "@pnpm/catalogs.config": "1100.0.0",
109
109
  "@pnpm/test-ipc-server": "1100.0.0"
110
110
  },
111
111
  "engines": {