@pnpm/bins.linker 1100.0.13 → 1100.0.14

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 (2) hide show
  1. package/lib/index.js +46 -2
  2. package/package.json +11 -11
package/lib/index.js CHANGED
@@ -231,7 +231,7 @@ async function linkBin(cmd, binsDir, opts) {
231
231
  if (opts?.preferSymlinkedExecutables && !IS_WINDOWS && cmd.nodeExecPath == null) {
232
232
  try {
233
233
  await symlinkDir(cmd.path, externalBinPath);
234
- await fixBin(cmd.path, 0o755);
234
+ await ensureExecutable(cmd.path, 0o755);
235
235
  }
236
236
  catch (err) { // eslint-disable-line
237
237
  if (err.code !== 'ENOENT' && err.code !== 'EISDIR') {
@@ -280,7 +280,51 @@ async function linkBin(cmd, binsDir, opts) {
280
280
  // ensure that bin are executable and not containing
281
281
  // windows line-endings(CRLF) on the hashbang line
282
282
  if (EXECUTABLE_SHEBANG_SUPPORTED) {
283
- await fixBin(cmd.path, 0o755);
283
+ await ensureExecutable(cmd.path, 0o755);
284
+ }
285
+ }
286
+ // `fixBin` chmods the bin's source file (which lives inside the store) to make
287
+ // it executable and rewrites a Windows CRLF shebang to LF. Under the global
288
+ // virtual store that source is `{storeDir}/links/...`, so on a read-only store
289
+ // (e.g. `frozenStore`) the chmod is refused — with EPERM/EACCES when the file is
290
+ // owned but permissions forbid it, or EROFS on a genuinely read-only filesystem
291
+ // (Nix store, RO bind mount, OCI layer). A complete seed already ships its bins
292
+ // executable and shebang-normalized by the writable seed-build, so that work is
293
+ // redundant: treat an already-correct target as a no-op, keeping bin-linking
294
+ // write-free (see building/during-install: "Bin-linking reuses existing symlinks
295
+ // write-free"). A non-executable bin — or one still carrying a CRLF shebang that
296
+ // `fixBin` could not rewrite here — still throws, because that means the seed is
297
+ // broken and the bin would not run.
298
+ async function ensureExecutable(file, mode) {
299
+ try {
300
+ await fixBin(file, mode);
301
+ }
302
+ catch (err) { // eslint-disable-line
303
+ if (err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EROFS') {
304
+ const stat = await fs.stat(file).catch(() => undefined);
305
+ if (stat != null && (stat.mode & 0o111) !== 0 && !(await hasWindowsShebang(file)))
306
+ return;
307
+ }
308
+ throw err;
309
+ }
310
+ }
311
+ // Detects a `#!`-shebang line terminated by CRLF, which fails to execute on
312
+ // POSIX. Mirrors bin-links' own fix-bin detection so a chmod failure on a
313
+ // read-only store is only swallowed when the bin is genuinely already correct.
314
+ async function hasWindowsShebang(file) {
315
+ const fh = await fs.open(file, 'r').catch(() => undefined);
316
+ if (fh == null)
317
+ return false;
318
+ try {
319
+ const buf = Buffer.alloc(2048);
320
+ await fh.read(buf, 0, 2048, 0);
321
+ return buf[0] === 0x23 /* # */ && buf[1] === 0x21 /* ! */ && /^#![^\n]+\r\n/.test(buf.toString());
322
+ }
323
+ catch {
324
+ return false;
325
+ }
326
+ finally {
327
+ await fh.close().catch(() => { });
284
328
  }
285
329
  }
286
330
  function getExeExtension() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/bins.linker",
3
- "version": "1100.0.13",
3
+ "version": "1100.0.14",
4
4
  "description": "Link bins to node_modules/.bin",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -28,28 +28,28 @@
28
28
  "!*.map"
29
29
  ],
30
30
  "dependencies": {
31
- "@zkochan/cmd-shim": "^9.0.3",
31
+ "@zkochan/cmd-shim": "^9.0.6",
32
32
  "@zkochan/rimraf": "^4.0.0",
33
33
  "bin-links": "^6.0.2",
34
34
  "is-subdir": "^2.0.0",
35
35
  "is-windows": "^1.0.2",
36
36
  "normalize-path": "^3.0.0",
37
37
  "ramda": "npm:@pnpm/ramda@0.28.1",
38
- "semver": "^7.8.1",
38
+ "semver": "^7.8.4",
39
39
  "symlink-dir": "^10.0.1",
40
- "@pnpm/bins.resolver": "1100.0.7",
41
- "@pnpm/pkg-manifest.reader": "1100.0.7",
40
+ "@pnpm/bins.resolver": "1100.0.8",
41
+ "@pnpm/pkg-manifest.utils": "1100.2.5",
42
42
  "@pnpm/error": "1100.0.0",
43
43
  "@pnpm/fs.read-modules-dir": "1100.0.1",
44
- "@pnpm/pkg-manifest.utils": "1100.2.4",
45
- "@pnpm/types": "1101.3.1",
46
- "@pnpm/workspace.project-manifest-reader": "1100.0.12"
44
+ "@pnpm/types": "1101.3.2",
45
+ "@pnpm/pkg-manifest.reader": "1100.0.8",
46
+ "@pnpm/workspace.project-manifest-reader": "1100.0.13"
47
47
  },
48
48
  "peerDependencies": {
49
- "@pnpm/logger": "^1001.0.1"
49
+ "@pnpm/logger": "^1100.0.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@jest/globals": "30.3.0",
52
+ "@jest/globals": "30.4.1",
53
53
  "@types/is-windows": "^1.0.2",
54
54
  "@types/node": "^22.19.19",
55
55
  "@types/normalize-path": "^3.0.2",
@@ -57,7 +57,7 @@
57
57
  "@types/semver": "7.7.1",
58
58
  "cmd-extension": "^2.0.0",
59
59
  "tempy": "3.0.0",
60
- "@pnpm/bins.linker": "1100.0.13",
60
+ "@pnpm/bins.linker": "1100.0.14",
61
61
  "@pnpm/logger": "1100.0.0",
62
62
  "@pnpm/test-fixtures": "1100.0.0"
63
63
  },