@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 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
+ [![npm version](https://img.shields.io/npm/v/@pnpm/link-bins.svg)](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
+ }