fix(npm): preserve executable bit on platform binaries

Per-platform packages were published with the binary at mode 0644, so
pacquet's JS shim's spawnSync failed with EACCES on install. The
chmodSync(0o755) in generate-packages.mjs ran on the publish runner,
but npm/pnpm pack normalizes file modes for deterministic tarballs:
files referenced in the manifest's bin field become 0755 and everything
else 0644.

publishConfig.executableFiles is the npm-native way to keep additional
files at 0755 in the published tarball without overloading bin's "this
is a CLI entry point" semantics. Both npm and pnpm honor it.
This commit is contained in:
Zoltan Kochan
2026-04-27 01:26:06 +02:00
parent da1cc43090
commit 2e7e5f7b5c

View File

@@ -29,6 +29,11 @@ function generateNativePackage(platform, arch) {
// Generate the package.json manifest
const { version } = rootManifest;
// publishConfig.executableFiles tells pnpm pack to keep mode 0755
// on the binary in the published tarball. Without it, pack normalizes
// every non-bin file to 0644 and the JS shim's spawnSync fails with
// EACCES on install.
const ext = platform === "win32" ? ".exe" : "";
const manifest = JSON.stringify({
name: packageName,
version,
@@ -38,6 +43,9 @@ function generateNativePackage(platform, arch) {
type: "git",
url: "https://github.com/pnpm/pacquet",
},
publishConfig: {
executableFiles: [`./${BIN_NAME}${ext}`],
},
}, null, 2);
const manifestPath = resolve(packageRoot, "package.json");
@@ -45,7 +53,6 @@ function generateNativePackage(platform, arch) {
fs.writeFileSync(manifestPath, manifest);
// Copy the binary
const ext = platform === "win32" ? ".exe" : "";
const binarySource = resolve(REPO_ROOT, `${BIN_NAME}-${platform}-${arch}${ext}`);
const binaryTarget = resolve(packageRoot, `${BIN_NAME}${ext}`);