From 49b15ac2e341f2075a9a09e3c197d8102bfbc83b Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 16 Apr 2023 20:57:14 +0300 Subject: [PATCH] fix(env): use hard links instead of symlinks on Windows (#6405) close #4315 --- .changeset/late-laws-approve.md | 6 ++++++ env/plugin-commands-env/package.json | 3 +++ env/plugin-commands-env/src/envUse.ts | 16 ++++++++++++++-- env/plugin-commands-env/src/utils.ts | 11 +++++++---- pnpm-lock.yaml | 22 +++++++++++++++------- 5 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 .changeset/late-laws-approve.md diff --git a/.changeset/late-laws-approve.md b/.changeset/late-laws-approve.md new file mode 100644 index 00000000000..bc0bb99dacb --- /dev/null +++ b/.changeset/late-laws-approve.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-env": patch +"pnpm": patch +--- + +Use hard links to link the node executable on Windows machines [#4315](https://github.com/pnpm/pnpm/issues/4315). diff --git a/env/plugin-commands-env/package.json b/env/plugin-commands-env/package.json index e9880e7ce2d..371c07deeff 100644 --- a/env/plugin-commands-env/package.json +++ b/env/plugin-commands-env/package.json @@ -41,9 +41,11 @@ "@pnpm/store-path": "workspace:*", "@zkochan/cmd-shim": "^6.0.0", "@zkochan/rimraf": "^2.1.2", + "is-windows": "^1.0.2", "load-json-file": "^6.2.0", "render-help": "^1.0.3", "semver": "^7.4.0", + "symlink-dir": "^5.1.1", "write-json-file": "^4.3.0" }, "funding": "https://opencollective.com/pnpm", @@ -51,6 +53,7 @@ "@pnpm/plugin-commands-env": "workspace:*", "@pnpm/prepare": "workspace:*", "@types/adm-zip": "^0.5.0", + "@types/is-windows": "^1.0.0", "@types/semver": "7.3.13", "adm-zip": "^0.5.10", "execa": "npm:safe-execa@0.1.2", diff --git a/env/plugin-commands-env/src/envUse.ts b/env/plugin-commands-env/src/envUse.ts index b5c147722b9..2a685a04d61 100644 --- a/env/plugin-commands-env/src/envUse.ts +++ b/env/plugin-commands-env/src/envUse.ts @@ -4,10 +4,12 @@ import { PnpmError } from '@pnpm/error' import { createFetchFromRegistry } from '@pnpm/fetch' import { resolveNodeVersion } from '@pnpm/node.resolver' import cmdShim from '@zkochan/cmd-shim' +import isWindows from 'is-windows' +import symlinkDir from 'symlink-dir' import { getNodeDir, type NvmNodeCommandOptions } from './node' import { getNodeMirror } from './getNodeMirror' import { parseNodeEditionSpecifier } from './parseNodeEditionSpecifier' -import { getNodeExecPathInBinDir, getNodeExecPathInNodeDir } from './utils' +import { CURRENT_NODE_DIRNAME, getNodeExecPathInBinDir, getNodeExecPathInNodeDir } from './utils' export async function envUse (opts: NvmNodeCommandOptions, params: string[]) { if (!opts.global) { @@ -27,10 +29,11 @@ export async function envUse (opts: NvmNodeCommandOptions, params: string[]) { }) const src = getNodeExecPathInNodeDir(nodeDir) const dest = getNodeExecPathInBinDir(opts.bin) + await symlinkDir(nodeDir, path.join(opts.pnpmHomeDir, CURRENT_NODE_DIRNAME)) try { await fs.unlink(dest) } catch (err) {} - await fs.symlink(src, dest, 'file') + await symlinkOrHardLink(src, dest) try { let npmDir = nodeDir if (process.platform !== 'win32') { @@ -52,3 +55,12 @@ export async function envUse (opts: NvmNodeCommandOptions, params: string[]) { return `Node.js ${nodeVersion as string} is activated ${dest} -> ${src}` } + +// On Windows, symlinks only work with developer mode enabled +// or with admin permissions. So it is better to use hard links on Windows. +async function symlinkOrHardLink (existingPath: string, newPath: string) { + if (isWindows()) { + return fs.link(existingPath, newPath) + } + return fs.symlink(existingPath, newPath, 'file') +} diff --git a/env/plugin-commands-env/src/utils.ts b/env/plugin-commands-env/src/utils.ts index b945e458e5d..9ae133c8102 100644 --- a/env/plugin-commands-env/src/utils.ts +++ b/env/plugin-commands-env/src/utils.ts @@ -1,15 +1,18 @@ import { promises as fs } from 'fs' import path from 'path' +export const CURRENT_NODE_DIRNAME = 'nodejs_current' + export async function getNodeExecPathAndTargetDir (pnpmHomeDir: string) { const nodePath = getNodeExecPathInBinDir(pnpmHomeDir) - let nodeLink: string | undefined + const nodeCurrentDirLink = path.join(pnpmHomeDir, CURRENT_NODE_DIRNAME) + let nodeCurrentDir: string | undefined try { - nodeLink = await fs.readlink(nodePath) + nodeCurrentDir = await fs.readlink(nodeCurrentDirLink) } catch (err) { - nodeLink = undefined + nodeCurrentDir = undefined } - return { nodePath, nodeLink } + return { nodePath, nodeLink: nodeCurrentDir ? getNodeExecPathInNodeDir(nodeCurrentDir) : undefined } } export function getNodeExecPathInBinDir (pnpmHomeDir: string) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2552aaa970..288ad877993 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -855,6 +855,9 @@ importers: '@zkochan/rimraf': specifier: ^2.1.2 version: 2.1.2 + is-windows: + specifier: ^1.0.2 + version: 1.0.2 load-json-file: specifier: ^6.2.0 version: 6.2.0 @@ -864,6 +867,9 @@ importers: semver: specifier: ^7.4.0 version: 7.4.0 + symlink-dir: + specifier: ^5.1.1 + version: 5.1.1 write-json-file: specifier: ^4.3.0 version: 4.3.0 @@ -877,6 +883,9 @@ importers: '@types/adm-zip': specifier: ^0.5.0 version: 0.5.0 + '@types/is-windows': + specifier: ^1.0.0 + version: 1.0.0 '@types/semver': specifier: 7.3.13 version: 7.3.13 @@ -8804,7 +8813,7 @@ packages: /@types/byline@4.2.33: resolution: {integrity: sha512-LJYez7wrWcJQQDknqZtrZuExMGP0IXmPl1rOOGDqLbu+H7UNNRfKNuSxCBcQMLH1EfjeWidLedC/hCc5dDfBog==} dependencies: - '@types/node': 18.15.11 + '@types/node': 14.18.42 dev: true /@types/cacheable-request@6.0.3: @@ -8812,7 +8821,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 18.15.11 + '@types/node': 14.18.42 '@types/responselike': 1.0.0 /@types/concat-stream@2.0.0: @@ -8840,7 +8849,7 @@ packages: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.15.11 + '@types/node': 14.18.42 dev: true /@types/graceful-fs@4.1.6: @@ -8914,7 +8923,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.15.11 + '@types/node': 14.18.42 /@types/lodash@4.14.181: resolution: {integrity: sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==} @@ -8957,7 +8966,6 @@ packages: /@types/node@14.18.42: resolution: {integrity: sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==} - dev: true /@types/node@18.15.11: resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} @@ -8994,7 +9002,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 14.18.42 /@types/retry@0.12.2: resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -13501,7 +13509,7 @@ packages: jws: 3.2.2 lodash: 4.17.21 ms: 2.1.3 - semver: 7.3.8 + semver: 7.4.0 /jsprim@1.4.2: resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}