@pnpm/bins.linker 1000.2.6
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.
- package/LICENSE +22 -0
- package/README.md +30 -0
- package/lib/getBinNodePaths.d.ts +11 -0
- package/lib/getBinNodePaths.js +55 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +314 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
|
|
4
|
+
Copyright (c) 2016-2026 Zoltan Kochan and other contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @pnpm/link-bins
|
|
2
|
+
|
|
3
|
+
> Link bins to node_modules/.bin
|
|
4
|
+
|
|
5
|
+
<!--@shields('npm')-->
|
|
6
|
+
[](https://www.npmjs.com/package/@pnpm/link-bins)
|
|
7
|
+
<!--/@-->
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @pnpm/link-bins
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import linkBins, {linkBinsOfPackages} from '@pnpm/link-bins'
|
|
19
|
+
|
|
20
|
+
function warn (msg) { console.warn(msg) }
|
|
21
|
+
|
|
22
|
+
await linkBins('node_modules', 'node_modules/.bin', {warn})
|
|
23
|
+
|
|
24
|
+
const packages = [{manifest: packageJson, location: pathToPackage}]
|
|
25
|
+
await linkBinsOfPackages(packages, 'node_modules/.bin', {warn})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## License
|
|
29
|
+
|
|
30
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the node_modules paths relevant to a binary in the virtual store layout.
|
|
3
|
+
* For a binary at `.pnpm/pkg@version/node_modules/pkg/bin/cli.js`, this returns:
|
|
4
|
+
* 1. `.pnpm/pkg@version/node_modules/pkg/node_modules` (bundled dependencies)
|
|
5
|
+
* 2. `.pnpm/pkg@version/node_modules` (sibling/regular dependencies)
|
|
6
|
+
*
|
|
7
|
+
* These directories must be in NODE_PATH so that tools like `import-local`
|
|
8
|
+
* (used by jest, eslint, etc.) which resolve from CWD can find the correct
|
|
9
|
+
* dependency versions.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getBinNodePaths(target: string): Promise<string[]>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the node_modules paths relevant to a binary in the virtual store layout.
|
|
5
|
+
* For a binary at `.pnpm/pkg@version/node_modules/pkg/bin/cli.js`, this returns:
|
|
6
|
+
* 1. `.pnpm/pkg@version/node_modules/pkg/node_modules` (bundled dependencies)
|
|
7
|
+
* 2. `.pnpm/pkg@version/node_modules` (sibling/regular dependencies)
|
|
8
|
+
*
|
|
9
|
+
* These directories must be in NODE_PATH so that tools like `import-local`
|
|
10
|
+
* (used by jest, eslint, etc.) which resolve from CWD can find the correct
|
|
11
|
+
* dependency versions.
|
|
12
|
+
*/
|
|
13
|
+
export async function getBinNodePaths(target) {
|
|
14
|
+
const targetDir = path.dirname(target);
|
|
15
|
+
let dir;
|
|
16
|
+
try {
|
|
17
|
+
dir = await fs.realpath(targetDir);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err.code !== 'ENOENT') {
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
dir = targetDir;
|
|
24
|
+
}
|
|
25
|
+
// Walk up from the resolved directory to find the first non-nested node_modules
|
|
26
|
+
let currentDir = dir;
|
|
27
|
+
while (true) {
|
|
28
|
+
if (path.basename(currentDir) === 'node_modules') {
|
|
29
|
+
// Skip nested node_modules (e.g., node_modules/node_modules)
|
|
30
|
+
if (path.basename(path.dirname(currentDir)) !== 'node_modules') {
|
|
31
|
+
const nodeModulesDir = currentDir;
|
|
32
|
+
const result = [];
|
|
33
|
+
// Determine the package directory from the relative path between
|
|
34
|
+
// node_modules and the resolved binary directory
|
|
35
|
+
const rel = path.relative(nodeModulesDir, dir);
|
|
36
|
+
if (rel) {
|
|
37
|
+
const relSegments = rel.split(path.sep);
|
|
38
|
+
// For scoped packages, the package dir is two levels deep: @scope/pkg
|
|
39
|
+
const pkgDir = relSegments[0].startsWith('@')
|
|
40
|
+
? path.join(nodeModulesDir, relSegments[0], relSegments[1])
|
|
41
|
+
: path.join(nodeModulesDir, relSegments[0]);
|
|
42
|
+
result.push(path.join(pkgDir, 'node_modules'));
|
|
43
|
+
}
|
|
44
|
+
result.push(nodeModulesDir);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const parent = path.dirname(currentDir);
|
|
49
|
+
if (parent === currentDir)
|
|
50
|
+
break;
|
|
51
|
+
currentDir = parent;
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=getBinNodePaths.js.map
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DependencyManifest, ProjectManifest } from '@pnpm/types';
|
|
2
|
+
export type WarningCode = 'BINARIES_CONFLICT' | 'EMPTY_BIN';
|
|
3
|
+
export type WarnFunction = (msg: string, code: WarningCode) => void;
|
|
4
|
+
export declare function linkBins(modulesDir: string, binsDir: string, opts: LinkBinOptions & {
|
|
5
|
+
allowExoticManifests?: boolean;
|
|
6
|
+
projectManifest?: ProjectManifest;
|
|
7
|
+
warn: WarnFunction;
|
|
8
|
+
}): Promise<string[]>;
|
|
9
|
+
export declare function linkBinsOfPkgsByAliases(depsAliases: string[], binsDir: string, opts: LinkBinOptions & {
|
|
10
|
+
modulesDir: string;
|
|
11
|
+
allowExoticManifests?: boolean;
|
|
12
|
+
projectManifest?: ProjectManifest;
|
|
13
|
+
warn: WarnFunction;
|
|
14
|
+
}): Promise<string[]>;
|
|
15
|
+
export declare function linkBinsOfPackages(pkgs: Array<{
|
|
16
|
+
manifest: DependencyManifest;
|
|
17
|
+
location: string;
|
|
18
|
+
}>, binsTarget: string, opts?: LinkBinOptions & {
|
|
19
|
+
excludeBins?: Set<string>;
|
|
20
|
+
}): Promise<string[]>;
|
|
21
|
+
export interface LinkBinOptions {
|
|
22
|
+
extraNodePaths?: string[];
|
|
23
|
+
preferSymlinkedExecutables?: boolean;
|
|
24
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getBinsFromPackageManifest } from '@pnpm/bins.resolver';
|
|
5
|
+
import { getBunBinLocationForCurrentOS, getDenoBinLocationForCurrentOS, getNodeBinLocationForCurrentOS } from '@pnpm/constants';
|
|
6
|
+
import { PnpmError } from '@pnpm/error';
|
|
7
|
+
import { readModulesDir } from '@pnpm/fs.read-modules-dir';
|
|
8
|
+
import { globalWarn, logger } from '@pnpm/logger';
|
|
9
|
+
import { readPackageJsonFromDir } from '@pnpm/pkg-manifest.reader';
|
|
10
|
+
import { getAllDependenciesFromManifest } from '@pnpm/pkg-manifest.utils';
|
|
11
|
+
import { safeReadProjectManifestOnly } from '@pnpm/workspace.project-manifest-reader';
|
|
12
|
+
import cmdShim from '@zkochan/cmd-shim';
|
|
13
|
+
import { rimraf } from '@zkochan/rimraf';
|
|
14
|
+
import fixBin from 'bin-links/lib/fix-bin.js';
|
|
15
|
+
import { isSubdir } from 'is-subdir';
|
|
16
|
+
import isWindows from 'is-windows';
|
|
17
|
+
import normalizePath from 'normalize-path';
|
|
18
|
+
import { groupBy, isEmpty, partition, unnest } from 'ramda';
|
|
19
|
+
import semver from 'semver';
|
|
20
|
+
import symlinkDir from 'symlink-dir';
|
|
21
|
+
import { getBinNodePaths } from './getBinNodePaths.js';
|
|
22
|
+
const binsConflictLogger = logger('bins-conflict');
|
|
23
|
+
const IS_WINDOWS = isWindows();
|
|
24
|
+
const EXECUTABLE_SHEBANG_SUPPORTED = !IS_WINDOWS;
|
|
25
|
+
const POWER_SHELL_IS_SUPPORTED = IS_WINDOWS;
|
|
26
|
+
export async function linkBins(modulesDir, binsDir, opts) {
|
|
27
|
+
const allDeps = await readModulesDir(modulesDir);
|
|
28
|
+
// If the modules dir does not exist, do nothing
|
|
29
|
+
if (allDeps === null)
|
|
30
|
+
return [];
|
|
31
|
+
return linkBinsOfPkgsByAliases(allDeps, binsDir, {
|
|
32
|
+
...opts,
|
|
33
|
+
modulesDir,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function linkBinsOfPkgsByAliases(depsAliases, binsDir, opts) {
|
|
37
|
+
const pkgBinOpts = {
|
|
38
|
+
allowExoticManifests: false,
|
|
39
|
+
...opts,
|
|
40
|
+
};
|
|
41
|
+
const directDependencies = opts.projectManifest == null
|
|
42
|
+
? undefined
|
|
43
|
+
: new Set(Object.keys(getAllDependenciesFromManifest(opts.projectManifest)));
|
|
44
|
+
const allCmds = unnest((await Promise.all(depsAliases
|
|
45
|
+
.map((alias) => ({
|
|
46
|
+
depDir: path.resolve(opts.modulesDir, alias),
|
|
47
|
+
isDirectDependency: directDependencies?.has(alias),
|
|
48
|
+
}))
|
|
49
|
+
.filter(({ depDir }) => !isSubdir(depDir, binsDir)) // Don't link own bins
|
|
50
|
+
.map(async ({ depDir, isDirectDependency }) => {
|
|
51
|
+
const target = normalizePath(depDir);
|
|
52
|
+
const cmds = await getPackageBins(pkgBinOpts, target);
|
|
53
|
+
return cmds.map((cmd) => ({ ...cmd, isDirectDependency }));
|
|
54
|
+
})))
|
|
55
|
+
.filter((cmds) => cmds.length));
|
|
56
|
+
const cmdsToLink = directDependencies != null ? preferDirectCmds(allCmds) : allCmds;
|
|
57
|
+
return _linkBins(cmdsToLink, binsDir, opts);
|
|
58
|
+
}
|
|
59
|
+
function preferDirectCmds(allCmds) {
|
|
60
|
+
const [directCmds, hoistedCmds] = partition((cmd) => cmd.isDirectDependency === true, allCmds);
|
|
61
|
+
const usedDirectCmds = new Set(directCmds.map((directCmd) => directCmd.name));
|
|
62
|
+
return [
|
|
63
|
+
...directCmds,
|
|
64
|
+
...hoistedCmds.filter(({ name }) => !usedDirectCmds.has(name)),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
export async function linkBinsOfPackages(pkgs, binsTarget, opts = {}) {
|
|
68
|
+
if (pkgs.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
let allCmds = unnest((await Promise.all(pkgs
|
|
71
|
+
.map(async (pkg) => getPackageBinsFromManifest(pkg.manifest, pkg.location))))
|
|
72
|
+
.filter((cmds) => cmds.length));
|
|
73
|
+
const excludeBins = opts.excludeBins;
|
|
74
|
+
if (excludeBins?.size) {
|
|
75
|
+
allCmds = allCmds.filter((cmd) => !excludeBins.has(cmd.name));
|
|
76
|
+
}
|
|
77
|
+
return _linkBins(allCmds, binsTarget, opts);
|
|
78
|
+
}
|
|
79
|
+
async function _linkBins(allCmds, binsDir, opts) {
|
|
80
|
+
if (allCmds.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
// deduplicate bin names to prevent race conditions (multiple writers for the same file)
|
|
83
|
+
allCmds = deduplicateCommands(allCmds, binsDir);
|
|
84
|
+
await fs.mkdir(binsDir, { recursive: true });
|
|
85
|
+
const results = await Promise.allSettled(allCmds.map(async cmd => linkBin(cmd, binsDir, opts)));
|
|
86
|
+
// We want to create all commands that we can create before throwing an exception
|
|
87
|
+
for (const result of results) {
|
|
88
|
+
if (result.status === 'rejected') {
|
|
89
|
+
throw result.reason;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return allCmds.map(cmd => cmd.pkgName);
|
|
93
|
+
}
|
|
94
|
+
function deduplicateCommands(commands, binsDir) {
|
|
95
|
+
const cmdGroups = groupBy(cmd => cmd.name, commands);
|
|
96
|
+
return Object.values(cmdGroups)
|
|
97
|
+
.filter((group) => group !== undefined && group.length !== 0)
|
|
98
|
+
.map(group => resolveCommandConflicts(group, binsDir));
|
|
99
|
+
}
|
|
100
|
+
function resolveCommandConflicts(group, binsDir) {
|
|
101
|
+
return group.reduce((a, b) => {
|
|
102
|
+
const [chosen, skipped] = compareCommandsInConflict(a, b) >= 0 ? [a, b] : [b, a];
|
|
103
|
+
logCommandConflict(chosen, skipped, binsDir);
|
|
104
|
+
return chosen;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function compareCommandsInConflict(a, b) {
|
|
108
|
+
if (a.ownName && !b.ownName)
|
|
109
|
+
return 1;
|
|
110
|
+
if (!a.ownName && b.ownName)
|
|
111
|
+
return -1;
|
|
112
|
+
if (a.pkgName !== b.pkgName)
|
|
113
|
+
return a.pkgName.localeCompare(b.pkgName); // it's pointless to compare versions of 2 different package
|
|
114
|
+
return semver.compare(a.pkgVersion, b.pkgVersion);
|
|
115
|
+
}
|
|
116
|
+
function logCommandConflict(chosen, skipped, binsDir) {
|
|
117
|
+
binsConflictLogger.debug({
|
|
118
|
+
binaryName: skipped.name,
|
|
119
|
+
binsDir,
|
|
120
|
+
linkedPkgName: chosen.pkgName,
|
|
121
|
+
linkedPkgVersion: chosen.pkgVersion,
|
|
122
|
+
skippedPkgName: skipped.pkgName,
|
|
123
|
+
skippedPkgVersion: skipped.pkgVersion,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function isFromModules(filename) {
|
|
127
|
+
const real = await fs.realpath(filename);
|
|
128
|
+
return normalizePath(real).includes('/node_modules/');
|
|
129
|
+
}
|
|
130
|
+
async function getPackageBins(opts, target) {
|
|
131
|
+
const manifest = opts.allowExoticManifests
|
|
132
|
+
? await safeReadProjectManifestOnly(target)
|
|
133
|
+
: await safeReadPkgJson(target);
|
|
134
|
+
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
|
+
// There's a directory in node_modules without package.json: ${target}.
|
|
167
|
+
// This used to be a warning but it didn't really cause any issues.
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
if (isEmpty(manifest.bin) && !await isFromModules(target)) {
|
|
171
|
+
opts.warn(`Package in ${target} must have a non-empty bin field to get bin linked.`, 'EMPTY_BIN');
|
|
172
|
+
}
|
|
173
|
+
if (typeof manifest.bin === 'string' && !manifest.name) {
|
|
174
|
+
throw new PnpmError('INVALID_PACKAGE_NAME', `Package in ${target} must have a name to get bin linked.`);
|
|
175
|
+
}
|
|
176
|
+
return getPackageBinsFromManifest(manifest, target);
|
|
177
|
+
}
|
|
178
|
+
async function getPackageBinsFromManifest(manifest, pkgDir) {
|
|
179
|
+
const cmds = await getBinsFromPackageManifest(manifest, pkgDir);
|
|
180
|
+
let nodeExecPath;
|
|
181
|
+
if (manifest.engines?.runtime && runtimeHasNodeDownloaded(manifest.engines.runtime)) {
|
|
182
|
+
const require = createRequire(import.meta.dirname);
|
|
183
|
+
// Using Node.js’ resolution algorithm is the most reliable way to find the Node.js
|
|
184
|
+
// package that comes from this CLI's dependencies, because the layout of node_modules can vary.
|
|
185
|
+
// In an isolated layout, it will be located in the same node_modules directory as the CLI.
|
|
186
|
+
// In a hoisted layout, it may be in one of the parent node_modules directories.
|
|
187
|
+
const nodeDir = path.dirname(require.resolve('node/CHANGELOG.md', { paths: [pkgDir] }));
|
|
188
|
+
if (nodeDir) {
|
|
189
|
+
nodeExecPath = path.join(nodeDir, IS_WINDOWS ? 'node.exe' : 'bin/node');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return cmds.map((cmd) => ({
|
|
193
|
+
...cmd,
|
|
194
|
+
ownName: cmd.name === manifest.name,
|
|
195
|
+
pkgName: manifest.name,
|
|
196
|
+
pkgVersion: manifest.version,
|
|
197
|
+
makePowerShellShim: POWER_SHELL_IS_SUPPORTED && manifest.name !== 'pnpm',
|
|
198
|
+
nodeExecPath,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
function runtimeHasNodeDownloaded(runtime) {
|
|
202
|
+
if (!Array.isArray(runtime)) {
|
|
203
|
+
return runtime.name === 'node' && runtime.onFail === 'download';
|
|
204
|
+
}
|
|
205
|
+
return runtime.find(({ name }) => name === 'node')?.onFail === 'download';
|
|
206
|
+
}
|
|
207
|
+
async function linkBin(cmd, binsDir, opts) {
|
|
208
|
+
const externalBinPath = path.join(binsDir, cmd.name);
|
|
209
|
+
if (IS_WINDOWS) {
|
|
210
|
+
const exePath = path.join(binsDir, `${cmd.name}${getExeExtension()}`);
|
|
211
|
+
if (existsSync(exePath)) {
|
|
212
|
+
globalWarn(`The target bin directory already contains an exe called ${cmd.name}, so removing ${exePath}`);
|
|
213
|
+
await rimraf(exePath);
|
|
214
|
+
}
|
|
215
|
+
// node.exe must exist as a real executable, not a cmd-shim wrapper.
|
|
216
|
+
// We could update our own cmd shims to support node.cmd, but we can't
|
|
217
|
+
// control npm's cmd shims, which break when node resolves to node.cmd.
|
|
218
|
+
// npm's cmd shims use `IF EXIST "%~dp0\node.exe"` to find the node binary.
|
|
219
|
+
if (cmd.name === 'node' && cmd.path.toLowerCase().endsWith('.exe')) {
|
|
220
|
+
try {
|
|
221
|
+
await fs.link(cmd.path, exePath);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
await fs.copyFile(cmd.path, exePath);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else if (cmd.name === 'node') {
|
|
230
|
+
// On non-Windows, node should be symlinked directly to the binary
|
|
231
|
+
// instead of wrapped in a shell shim.
|
|
232
|
+
// Use rimraf unconditionally instead of existsSync check, because
|
|
233
|
+
// existsSync follows symlinks and returns false for broken symlinks,
|
|
234
|
+
// causing EEXIST when the dangling symlink still exists on disk.
|
|
235
|
+
await rimraf(externalBinPath);
|
|
236
|
+
await fs.symlink(cmd.path, externalBinPath, 'file');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (opts?.preferSymlinkedExecutables && !IS_WINDOWS && cmd.nodeExecPath == null) {
|
|
240
|
+
try {
|
|
241
|
+
await symlinkDir(cmd.path, externalBinPath);
|
|
242
|
+
await fixBin(cmd.path, 0o755);
|
|
243
|
+
}
|
|
244
|
+
catch (err) { // eslint-disable-line
|
|
245
|
+
if (err.code !== 'ENOENT' && err.code !== 'EISDIR') {
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
let nodePath;
|
|
254
|
+
if (opts?.extraNodePaths?.length) {
|
|
255
|
+
const binNodePaths = await getBinNodePaths(cmd.path);
|
|
256
|
+
if (binNodePaths.length === 0) {
|
|
257
|
+
nodePath = opts.extraNodePaths;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
nodePath = [...binNodePaths];
|
|
261
|
+
for (const p of opts.extraNodePaths) {
|
|
262
|
+
if (!binNodePaths.includes(p)) {
|
|
263
|
+
nodePath.push(p);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
await cmdShim(cmd.path, externalBinPath, {
|
|
269
|
+
createPwshFile: cmd.makePowerShellShim,
|
|
270
|
+
nodePath,
|
|
271
|
+
nodeExecPath: cmd.nodeExecPath,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
catch (err) { // eslint-disable-line
|
|
275
|
+
if (err.code === 'ENOENT' || err.code === 'EISDIR') {
|
|
276
|
+
globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message}`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// On Windows, EPERM during bin creation can happen when another process
|
|
280
|
+
// (e.g. a parallel dlx call) is writing to the same shared bin directory.
|
|
281
|
+
// The other process will finish creating the bin, so we can safely skip.
|
|
282
|
+
if (IS_WINDOWS && err.code === 'EPERM') {
|
|
283
|
+
globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
// ensure that bin are executable and not containing
|
|
289
|
+
// windows line-endings(CRLF) on the hashbang line
|
|
290
|
+
if (EXECUTABLE_SHEBANG_SUPPORTED) {
|
|
291
|
+
await fixBin(cmd.path, 0o755);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function getExeExtension() {
|
|
295
|
+
let cmdExtension;
|
|
296
|
+
if (process.env.PATHEXT) {
|
|
297
|
+
cmdExtension = process.env.PATHEXT
|
|
298
|
+
.split(path.delimiter)
|
|
299
|
+
.find(ext => ext.toUpperCase() === '.EXE');
|
|
300
|
+
}
|
|
301
|
+
return cmdExtension ?? '.exe';
|
|
302
|
+
}
|
|
303
|
+
async function safeReadPkgJson(pkgDir) {
|
|
304
|
+
try {
|
|
305
|
+
return await readPackageJsonFromDir(pkgDir);
|
|
306
|
+
}
|
|
307
|
+
catch (err) { // eslint-disable-line
|
|
308
|
+
if (err.code === 'ENOENT') {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pnpm/bins.linker",
|
|
3
|
+
"version": "1000.2.6",
|
|
4
|
+
"description": "Link bins to node_modules/.bin",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pnpm",
|
|
7
|
+
"pnpm11",
|
|
8
|
+
"bin"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"funding": "https://opencollective.com/pnpm",
|
|
12
|
+
"repository": "https://github.com/pnpm/pnpm/tree/main/bins/linker",
|
|
13
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/bins/linker#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/pnpm/pnpm/issues"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "lib/index.js",
|
|
19
|
+
"types": "lib/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"!*.map"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@zkochan/cmd-shim": "^8.0.1",
|
|
29
|
+
"@zkochan/rimraf": "^4.0.0",
|
|
30
|
+
"bin-links": "^5.0.0",
|
|
31
|
+
"is-subdir": "^2.0.0",
|
|
32
|
+
"is-windows": "^1.0.2",
|
|
33
|
+
"normalize-path": "^3.0.0",
|
|
34
|
+
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
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"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@pnpm/logger": ">=1001.0.0 <1002.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@jest/globals": "30.0.5",
|
|
51
|
+
"@types/is-windows": "^1.0.2",
|
|
52
|
+
"@types/node": "^22.19.11",
|
|
53
|
+
"@types/normalize-path": "^3.0.2",
|
|
54
|
+
"@types/ramda": "0.29.12",
|
|
55
|
+
"@types/semver": "7.7.1",
|
|
56
|
+
"cmd-extension": "^2.0.0",
|
|
57
|
+
"tempy": "3.0.0",
|
|
58
|
+
"@pnpm/bins.linker": "1000.2.6",
|
|
59
|
+
"@pnpm/logger": "1001.0.1",
|
|
60
|
+
"@pnpm/test-fixtures": "1000.0.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=22.13"
|
|
64
|
+
},
|
|
65
|
+
"jest": {
|
|
66
|
+
"preset": "@pnpm/jest-config"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"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",
|
|
72
|
+
"fix": "tslint -c tslint.json --project . --fix",
|
|
73
|
+
"compile": "tsgo --build && pnpm run lint --fix"
|
|
74
|
+
}
|
|
75
|
+
}
|