@pnpm/bins.linker 1000.2.6 → 1100.0.0

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 +30 -38
  2. package/package.json +20 -21
package/lib/index.js CHANGED
@@ -1,15 +1,14 @@
1
1
  import { existsSync, promises as fs } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import path from 'node:path';
4
- import { getBinsFromPackageManifest } from '@pnpm/bins.resolver';
5
- import { getBunBinLocationForCurrentOS, getDenoBinLocationForCurrentOS, getNodeBinLocationForCurrentOS } from '@pnpm/constants';
4
+ import { getBinsFromPackageManifest, pkgOwnsBin } from '@pnpm/bins.resolver';
6
5
  import { PnpmError } from '@pnpm/error';
7
6
  import { readModulesDir } from '@pnpm/fs.read-modules-dir';
8
7
  import { globalWarn, logger } from '@pnpm/logger';
9
8
  import { readPackageJsonFromDir } from '@pnpm/pkg-manifest.reader';
10
9
  import { getAllDependenciesFromManifest } from '@pnpm/pkg-manifest.utils';
11
10
  import { safeReadProjectManifestOnly } from '@pnpm/workspace.project-manifest-reader';
12
- import cmdShim from '@zkochan/cmd-shim';
11
+ import { cmdShim, isShimPointingAt } from '@zkochan/cmd-shim';
13
12
  import { rimraf } from '@zkochan/rimraf';
14
13
  import fixBin from 'bin-links/lib/fix-bin.js';
15
14
  import { isSubdir } from 'is-subdir';
@@ -17,12 +16,14 @@ import isWindows from 'is-windows';
17
16
  import normalizePath from 'normalize-path';
18
17
  import { groupBy, isEmpty, partition, unnest } from 'ramda';
19
18
  import semver from 'semver';
20
- import symlinkDir from 'symlink-dir';
19
+ import { symlinkDir } from 'symlink-dir';
21
20
  import { getBinNodePaths } from './getBinNodePaths.js';
22
21
  const binsConflictLogger = logger('bins-conflict');
23
22
  const IS_WINDOWS = isWindows();
24
23
  const EXECUTABLE_SHEBANG_SUPPORTED = !IS_WINDOWS;
25
24
  const POWER_SHELL_IS_SUPPORTED = IS_WINDOWS;
25
+ // A cmd-shim is a small shell script. Anything larger is a binary and should not be read.
26
+ const CMD_SHIM_MAX_SIZE = 4 * 1024;
26
27
  export async function linkBins(modulesDir, binsDir, opts) {
27
28
  const allDeps = await readModulesDir(modulesDir);
28
29
  // If the modules dir does not exist, do nothing
@@ -105,9 +106,12 @@ function resolveCommandConflicts(group, binsDir) {
105
106
  });
106
107
  }
107
108
  function compareCommandsInConflict(a, b) {
108
- if (a.ownName && !b.ownName)
109
+ // Check ownership: a package that owns the bin name gets priority
110
+ const aOwns = pkgOwnsBin(a.name, a.pkgName);
111
+ const bOwns = pkgOwnsBin(b.name, b.pkgName);
112
+ if (aOwns && !bOwns)
109
113
  return 1;
110
- if (!a.ownName && b.ownName)
114
+ if (!aOwns && bOwns)
111
115
  return -1;
112
116
  if (a.pkgName !== b.pkgName)
113
117
  return a.pkgName.localeCompare(b.pkgName); // it's pointless to compare versions of 2 different package
@@ -132,37 +136,6 @@ async function getPackageBins(opts, target) {
132
136
  ? await safeReadProjectManifestOnly(target)
133
137
  : await safeReadPkgJson(target);
134
138
  if (manifest == null) {
135
- // There is a probably a better way to do this.
136
- // It isn't good to have these hardcoded here.
137
- switch (path.basename(target)) {
138
- case 'node':
139
- return [{
140
- name: 'node',
141
- path: path.join(target, getNodeBinLocationForCurrentOS()),
142
- ownName: true,
143
- pkgName: '',
144
- pkgVersion: '',
145
- makePowerShellShim: false,
146
- }];
147
- case 'deno':
148
- return [{
149
- name: 'deno',
150
- path: path.join(target, getDenoBinLocationForCurrentOS()),
151
- ownName: true,
152
- pkgName: '',
153
- pkgVersion: '',
154
- makePowerShellShim: false,
155
- }];
156
- case 'bun':
157
- return [{
158
- name: 'bun',
159
- path: path.join(target, getBunBinLocationForCurrentOS()),
160
- ownName: true,
161
- pkgName: '',
162
- pkgVersion: '',
163
- makePowerShellShim: false,
164
- }];
165
- }
166
139
  // There's a directory in node_modules without package.json: ${target}.
167
140
  // This used to be a warning but it didn't really cause any issues.
168
141
  return [];
@@ -191,7 +164,6 @@ async function getPackageBinsFromManifest(manifest, pkgDir) {
191
164
  }
192
165
  return cmds.map((cmd) => ({
193
166
  ...cmd,
194
- ownName: cmd.name === manifest.name,
195
167
  pkgName: manifest.name,
196
168
  pkgVersion: manifest.version,
197
169
  makePowerShellShim: POWER_SHELL_IS_SUPPORTED && manifest.name !== 'pnpm',
@@ -206,6 +178,26 @@ function runtimeHasNodeDownloaded(runtime) {
206
178
  }
207
179
  async function linkBin(cmd, binsDir, opts) {
208
180
  const externalBinPath = path.join(binsDir, cmd.name);
181
+ // Skip if the existing bin already references the correct target.
182
+ // This avoids redundant I/O on warm installs and EACCES on read-only stores.
183
+ // We verify the target path — not just existence — so that conflict resolution
184
+ // changes or provider swaps still get the bin rewritten.
185
+ try {
186
+ const stat = await fs.lstat(externalBinPath);
187
+ if (stat.isSymbolicLink()) {
188
+ const target = await fs.readlink(externalBinPath);
189
+ if (target === cmd.path || path.resolve(binsDir, target) === path.resolve(cmd.path)) {
190
+ return;
191
+ }
192
+ }
193
+ else if (stat.isFile() && stat.size < CMD_SHIM_MAX_SIZE) {
194
+ const content = await fs.readFile(externalBinPath, 'utf8');
195
+ if (isShimPointingAt(content, cmd.path)) {
196
+ return;
197
+ }
198
+ }
199
+ }
200
+ catch { }
209
201
  if (IS_WINDOWS) {
210
202
  const exePath = path.join(binsDir, `${cmd.name}${getExeExtension()}`);
211
203
  if (existsSync(exePath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/bins.linker",
3
- "version": "1000.2.6",
3
+ "version": "1100.0.0",
4
4
  "description": "Link bins to node_modules/.bin",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -25,39 +25,38 @@
25
25
  "!*.map"
26
26
  ],
27
27
  "dependencies": {
28
- "@zkochan/cmd-shim": "^8.0.1",
28
+ "@zkochan/cmd-shim": "^9.0.0",
29
29
  "@zkochan/rimraf": "^4.0.0",
30
- "bin-links": "^5.0.0",
30
+ "bin-links": "^6.0.0",
31
31
  "is-subdir": "^2.0.0",
32
32
  "is-windows": "^1.0.2",
33
33
  "normalize-path": "^3.0.0",
34
34
  "ramda": "npm:@pnpm/ramda@0.28.1",
35
35
  "semver": "^7.7.2",
36
- "symlink-dir": "^7.0.0",
37
- "@pnpm/error": "1000.0.5",
38
- "@pnpm/bins.resolver": "1000.0.11",
39
- "@pnpm/constants": "1001.3.1",
40
- "@pnpm/fs.read-modules-dir": "1000.0.0",
41
- "@pnpm/pkg-manifest.utils": "1001.0.6",
42
- "@pnpm/pkg-manifest.reader": "1000.1.2",
43
- "@pnpm/workspace.project-manifest-reader": "1001.1.4",
44
- "@pnpm/types": "1000.9.0"
36
+ "symlink-dir": "^10.0.1",
37
+ "@pnpm/bins.resolver": "1100.0.0",
38
+ "@pnpm/error": "1100.0.0",
39
+ "@pnpm/fs.read-modules-dir": "1100.0.0",
40
+ "@pnpm/types": "1100.0.0",
41
+ "@pnpm/pkg-manifest.reader": "1100.0.0",
42
+ "@pnpm/pkg-manifest.utils": "1100.0.0",
43
+ "@pnpm/workspace.project-manifest-reader": "1100.0.0"
45
44
  },
46
45
  "peerDependencies": {
47
46
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
48
47
  },
49
48
  "devDependencies": {
50
- "@jest/globals": "30.0.5",
49
+ "@jest/globals": "30.3.0",
51
50
  "@types/is-windows": "^1.0.2",
52
- "@types/node": "^22.19.11",
51
+ "@types/node": "^22.19.15",
53
52
  "@types/normalize-path": "^3.0.2",
54
- "@types/ramda": "0.29.12",
53
+ "@types/ramda": "0.31.1",
55
54
  "@types/semver": "7.7.1",
56
55
  "cmd-extension": "^2.0.0",
57
56
  "tempy": "3.0.0",
58
- "@pnpm/bins.linker": "1000.2.6",
59
- "@pnpm/logger": "1001.0.1",
60
- "@pnpm/test-fixtures": "1000.0.0"
57
+ "@pnpm/bins.linker": "1100.0.0",
58
+ "@pnpm/logger": "1100.0.0",
59
+ "@pnpm/test-fixtures": "1100.0.0"
61
60
  },
62
61
  "engines": {
63
62
  "node": ">=22.13"
@@ -67,9 +66,9 @@
67
66
  },
68
67
  "scripts": {
69
68
  "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
70
- "_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest",
71
- "test": "pnpm run compile && pnpm run _test",
69
+ "test": "pn compile && pn .test",
72
70
  "fix": "tslint -c tslint.json --project . --fix",
73
- "compile": "tsgo --build && pnpm run lint --fix"
71
+ "compile": "tsgo --build && pn lint --fix",
72
+ ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
74
73
  }
75
74
  }