mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-27 09:08:13 -05:00
feat: pin Node.js to global package (#3780)
This commit is contained in:
5
.changeset/gold-ties-hear.md
Normal file
5
.changeset/gold-ties-hear.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/manifest-utils": minor
|
||||
---
|
||||
|
||||
The path to Node.js executable is added to `dependenciesMeta` when `nodeExecPath` is specified in the`PackageSpecObject`.
|
||||
5
.changeset/serious-foxes-fold.md
Normal file
5
.changeset/serious-foxes-fold.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/link-bins": minor
|
||||
---
|
||||
|
||||
Allow to specify the path to Node.js executable that should be called from the command shim.
|
||||
5
.changeset/strange-camels-wave.md
Normal file
5
.changeset/strange-camels-wave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
---
|
||||
|
||||
Globally installed packages should always use the active version of Node.js. So if webpack is installed while Node.js 16 is active, webpack will be executed using Node.js 16 even if the active Node.js version is switched using `pnpm env`.
|
||||
@@ -38,7 +38,7 @@
|
||||
"@pnpm/read-package-json": "workspace:5.0.4",
|
||||
"@pnpm/read-project-manifest": "workspace:2.0.5",
|
||||
"@pnpm/types": "workspace:7.4.0",
|
||||
"@zkochan/cmd-shim": "^5.1.3",
|
||||
"@zkochan/cmd-shim": "^5.2.0",
|
||||
"is-subdir": "^1.1.1",
|
||||
"is-windows": "^1.0.2",
|
||||
"normalize-path": "^3.0.0",
|
||||
|
||||
@@ -32,6 +32,7 @@ export default async (
|
||||
binsDir: string,
|
||||
opts: {
|
||||
allowExoticManifests?: boolean
|
||||
nodeExecPathByAlias?: Record<string, string>
|
||||
warn: WarnFunction
|
||||
}
|
||||
): Promise<string[]> => {
|
||||
@@ -45,10 +46,15 @@ export default async (
|
||||
const allCmds = unnest(
|
||||
(await Promise.all(
|
||||
allDeps
|
||||
.map((depName) => path.resolve(modulesDir, depName))
|
||||
.filter((depDir) => !isSubdir(depDir, binsDir)) // Don't link own bins
|
||||
.map((depDir) => normalizePath(depDir))
|
||||
.map(getPackageBins.bind(null, pkgBinOpts))
|
||||
.map((alias) => ({
|
||||
depDir: path.resolve(modulesDir, alias),
|
||||
nodeExecPath: opts.nodeExecPathByAlias?.[alias],
|
||||
}))
|
||||
.filter(({ depDir }) => !isSubdir(depDir, binsDir)) // Don't link own bins
|
||||
.map(({ depDir, nodeExecPath }) => {
|
||||
const target = normalizePath(depDir)
|
||||
return getPackageBins(pkgBinOpts, target, nodeExecPath)
|
||||
})
|
||||
))
|
||||
.filter((cmds: Command[]) => cmds.length)
|
||||
)
|
||||
@@ -59,6 +65,7 @@ export default async (
|
||||
export async function linkBinsOfPackages (
|
||||
pkgs: Array<{
|
||||
manifest: DependencyManifest
|
||||
nodeExecPath?: string
|
||||
location: string
|
||||
}>,
|
||||
binsTarget: string,
|
||||
@@ -71,7 +78,7 @@ export async function linkBinsOfPackages (
|
||||
const allCmds = unnest(
|
||||
(await Promise.all(
|
||||
pkgs
|
||||
.map(async (pkg) => getPackageBinsFromManifest(pkg.manifest, pkg.location))
|
||||
.map(async (pkg) => getPackageBinsFromManifest(pkg.manifest, pkg.location, pkg.nodeExecPath))
|
||||
))
|
||||
.filter((cmds: Command[]) => cmds.length)
|
||||
)
|
||||
@@ -83,6 +90,7 @@ type CommandInfo = Command & {
|
||||
ownName: boolean
|
||||
pkgName: string
|
||||
makePowerShellShim: boolean
|
||||
nodeExecPath?: string
|
||||
}
|
||||
|
||||
async function linkBins (
|
||||
@@ -130,7 +138,8 @@ async function getPackageBins (
|
||||
allowExoticManifests: boolean
|
||||
warn: WarnFunction
|
||||
},
|
||||
target: string
|
||||
target: string,
|
||||
nodeExecPath?: string
|
||||
): Promise<CommandInfo[]> {
|
||||
const manifest = opts.allowExoticManifests
|
||||
? (await safeReadProjectManifestOnly(target) as DependencyManifest)
|
||||
@@ -150,16 +159,17 @@ async function getPackageBins (
|
||||
throw new PnpmError('INVALID_PACKAGE_NAME', `Package in ${target} must have a name to get bin linked.`)
|
||||
}
|
||||
|
||||
return getPackageBinsFromManifest(manifest, target)
|
||||
return getPackageBinsFromManifest(manifest, target, nodeExecPath)
|
||||
}
|
||||
|
||||
async function getPackageBinsFromManifest (manifest: DependencyManifest, pkgDir: string): Promise<CommandInfo[]> {
|
||||
async function getPackageBinsFromManifest (manifest: DependencyManifest, pkgDir: string, nodeExecPath?: string): Promise<CommandInfo[]> {
|
||||
const cmds = await binify(manifest, pkgDir)
|
||||
return cmds.map((cmd) => ({
|
||||
...cmd,
|
||||
ownName: cmd.name === manifest.name,
|
||||
pkgName: manifest.name,
|
||||
makePowerShellShim: POWER_SHELL_IS_SUPPORTED && manifest.name !== 'pnpm',
|
||||
nodeExecPath,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -177,6 +187,7 @@ async function linkBin (cmd: CommandInfo, binsDir: string) {
|
||||
return cmdShim(cmd.path, externalBinPath, {
|
||||
createPwshFile: cmd.makePowerShellShim,
|
||||
nodePath,
|
||||
nodeExecPath: cmd.nodeExecPath,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
|
||||
export interface PackageSpecObject {
|
||||
alias: string
|
||||
nodeExecPath?: string
|
||||
peer?: boolean
|
||||
pref?: string
|
||||
saveType?: DependenciesField
|
||||
@@ -38,6 +39,12 @@ export async function updateProjectManifestObject (
|
||||
packageManifest[usedDepType] = packageManifest[usedDepType] ?? {}
|
||||
packageManifest[usedDepType]![packageSpec.alias] = packageSpec.pref
|
||||
}
|
||||
if (packageSpec.nodeExecPath) {
|
||||
if (packageManifest.dependenciesMeta == null) {
|
||||
packageManifest.dependenciesMeta = {}
|
||||
}
|
||||
packageManifest.dependenciesMeta[packageSpec.alias] = { node: packageSpec.nodeExecPath }
|
||||
}
|
||||
})
|
||||
|
||||
packageManifestLogger.debug({
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"@pnpm/package-store": "workspace:12.0.15",
|
||||
"@pnpm/store-path": "^5.0.0",
|
||||
"@pnpm/tarball-fetcher": "workspace:9.3.6",
|
||||
"@zkochan/cmd-shim": "^5.1.3",
|
||||
"@zkochan/cmd-shim": "^5.2.0",
|
||||
"adm-zip": "^0.5.5",
|
||||
"load-json-file": "^6.2.0",
|
||||
"rename-overwrite": "^4.0.0",
|
||||
|
||||
@@ -177,6 +177,9 @@ when running add/update with the --workspace option')
|
||||
if (!opts.ignorePnpmfile) {
|
||||
installOpts['hooks'] = requireHooks(opts.lockfileDir ?? dir, opts)
|
||||
}
|
||||
if (opts.global) {
|
||||
installOpts['nodeExecPath'] = process.env.NODE ?? process.execPath
|
||||
}
|
||||
|
||||
let { manifest, writeProjectManifest } = await tryReadProjectManifest(opts.dir, opts)
|
||||
if (manifest === null) {
|
||||
|
||||
53
packages/plugin-commands-installation/test/global.ts
Normal file
53
packages/plugin-commands-installation/test/global.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { add } from '@pnpm/plugin-commands-installation'
|
||||
import prepare from '@pnpm/prepare'
|
||||
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
||||
import tempy from 'tempy'
|
||||
|
||||
const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`
|
||||
const tmp = tempy.directory()
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
argv: {
|
||||
original: [],
|
||||
},
|
||||
bail: false,
|
||||
bin: 'node_modules/.bin',
|
||||
cacheDir: path.join(tmp, 'cache'),
|
||||
cliOptions: {},
|
||||
include: {
|
||||
dependencies: true,
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
},
|
||||
lock: true,
|
||||
pnpmfile: '.pnpmfile.cjs',
|
||||
rawConfig: { registry: REGISTRY_URL },
|
||||
rawLocalConfig: { registry: REGISTRY_URL },
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
sort: true,
|
||||
storeDir: path.join(tmp, 'store'),
|
||||
workspaceConcurrency: 1,
|
||||
}
|
||||
|
||||
test('globally installed package is linked with active version of Node.js', async () => {
|
||||
prepare()
|
||||
await add.handler({
|
||||
...DEFAULT_OPTIONS,
|
||||
dir: process.cwd(),
|
||||
global: true,
|
||||
linkWorkspacePackages: false,
|
||||
}, ['hello-world-js-bin'])
|
||||
|
||||
const manifest = (await import(path.resolve('package.json')))
|
||||
|
||||
expect(
|
||||
manifest.dependenciesMeta['hello-world-js-bin']?.node
|
||||
).toBeTruthy()
|
||||
|
||||
const shimContent = await fs.readFile('node_modules/.bin/hello-world-js-bin', 'utf-8')
|
||||
expect(shimContent).toContain(process.env.NODE)
|
||||
})
|
||||
@@ -57,6 +57,7 @@ interface ProjectToLink {
|
||||
|
||||
export type ImporterToResolve = Importer<{
|
||||
isNew?: boolean
|
||||
nodeExecPath?: string
|
||||
pinnedVersion?: PinnedVersion
|
||||
raw: string
|
||||
updateSpec?: boolean
|
||||
|
||||
@@ -25,6 +25,7 @@ export default async function updateProjectManifest (
|
||||
.map((rdd, index) => {
|
||||
const wantedDep = importer.wantedDependencies[index]!
|
||||
return resolvedDirectDepToSpecObject({ ...rdd, isNew: wantedDep.isNew, specRaw: wantedDep.raw }, importer, {
|
||||
nodeExecPath: wantedDep.nodeExecPath,
|
||||
pinnedVersion: wantedDep.pinnedVersion ?? importer['pinnedVersion'] ?? 'major',
|
||||
preserveWorkspaceProtocol: opts.preserveWorkspaceProtocol,
|
||||
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
|
||||
@@ -34,6 +35,7 @@ export default async function updateProjectManifest (
|
||||
if (pkgToInstall.updateSpec && pkgToInstall.alias && !specsToUpsert.some(({ alias }) => alias === pkgToInstall.alias)) {
|
||||
specsToUpsert.push({
|
||||
alias: pkgToInstall.alias,
|
||||
nodeExecPath: pkgToInstall.nodeExecPath,
|
||||
peer: importer['peer'],
|
||||
saveType: importer['targetDependenciesField'],
|
||||
})
|
||||
@@ -66,6 +68,7 @@ function resolvedDirectDepToSpecObject (
|
||||
}: ResolvedDirectDependency & { isNew?: Boolean, specRaw: string },
|
||||
importer: ImporterToResolve,
|
||||
opts: {
|
||||
nodeExecPath?: string
|
||||
pinnedVersion: PinnedVersion
|
||||
preserveWorkspaceProtocol: boolean
|
||||
saveWorkspaceProtocol: boolean
|
||||
@@ -105,6 +108,7 @@ function resolvedDirectDepToSpecObject (
|
||||
}
|
||||
return {
|
||||
alias,
|
||||
nodeExecPath: opts.nodeExecPath,
|
||||
peer: importer['peer'],
|
||||
pref,
|
||||
saveType: (isNew === true) ? importer['targetDependenciesField'] : undefined,
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface StrictInstallOptions {
|
||||
rawConfig: object
|
||||
verifyStoreIntegrity: boolean
|
||||
engineStrict: boolean
|
||||
nodeExecPath?: string
|
||||
nodeVersion: string
|
||||
packageManager: {
|
||||
name: string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { filterDependenciesByType } from '@pnpm/manifest-utils'
|
||||
import {
|
||||
Dependencies,
|
||||
DependenciesMeta,
|
||||
IncludedDependencies,
|
||||
ProjectManifest,
|
||||
} from '@pnpm/types'
|
||||
@@ -18,9 +19,10 @@ export interface WantedDependency {
|
||||
}
|
||||
|
||||
export default function getWantedDependencies (
|
||||
pkg: Pick<ProjectManifest, 'devDependencies' | 'dependencies' | 'optionalDependencies'>,
|
||||
pkg: Pick<ProjectManifest, 'devDependencies' | 'dependencies' | 'optionalDependencies' | 'dependenciesMeta'>,
|
||||
opts?: {
|
||||
includeDirect?: IncludedDependencies
|
||||
nodeExecPath?: string
|
||||
updateWorkspaceDependencies?: boolean
|
||||
}
|
||||
): WantedDependency[] {
|
||||
@@ -34,6 +36,7 @@ export default function getWantedDependencies (
|
||||
dependencies: pkg.dependencies ?? {},
|
||||
devDependencies: pkg.devDependencies ?? {},
|
||||
optionalDependencies: pkg.optionalDependencies ?? {},
|
||||
dependenciesMeta: pkg.dependenciesMeta ?? {},
|
||||
updatePref: opts?.updateWorkspaceDependencies === true
|
||||
? updateWorkspacePref
|
||||
: (pref) => pref,
|
||||
@@ -50,6 +53,8 @@ function getWantedDependenciesFromGivenSet (
|
||||
dependencies: Dependencies
|
||||
devDependencies: Dependencies
|
||||
optionalDependencies: Dependencies
|
||||
dependenciesMeta: DependenciesMeta
|
||||
nodeExecPath?: string
|
||||
updatePref: (pref: string) => string
|
||||
}
|
||||
): WantedDependency[] {
|
||||
@@ -64,6 +69,7 @@ function getWantedDependenciesFromGivenSet (
|
||||
alias,
|
||||
dev: depType === 'dev',
|
||||
optional: depType === 'optional',
|
||||
nodeExecPath: opts.nodeExecPath ?? opts.dependenciesMeta[alias]?.node,
|
||||
pinnedVersion: guessPinnedVersionFromExistingSpec(deps[alias]),
|
||||
pref,
|
||||
raw: `${alias}@${pref}`,
|
||||
|
||||
@@ -410,6 +410,7 @@ export async function mutateModules (
|
||||
const wantedDependencies = getWantedDependencies(project.manifest, {
|
||||
includeDirect: opts.includeDirect,
|
||||
updateWorkspaceDependencies: opts.update,
|
||||
nodeExecPath: opts.nodeExecPath,
|
||||
})
|
||||
.map((wantedDependency) => ({ ...wantedDependency, updateSpec: true }))
|
||||
|
||||
@@ -453,7 +454,7 @@ export async function mutateModules (
|
||||
projectsToInstall.push({
|
||||
pruneDirectDependencies: false,
|
||||
...project,
|
||||
wantedDependencies: wantedDeps.map(wantedDep => ({ ...wantedDep, isNew: true, updateSpec: true })),
|
||||
wantedDependencies: wantedDeps.map(wantedDep => ({ ...wantedDep, isNew: true, updateSpec: true, nodeExecPath: opts.nodeExecPath })),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -891,8 +892,16 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
await Promise.all(projectsToResolve.map(async (project, index) => {
|
||||
let linkedPackages!: string[]
|
||||
if (ctx.publicHoistPattern?.length && path.relative(project.rootDir, opts.lockfileDir) === '') {
|
||||
const nodeExecPathByAlias = Object.entries(project.manifest.dependenciesMeta ?? {})
|
||||
.reduce((prev, [alias, { node }]) => {
|
||||
if (node) {
|
||||
prev[alias] = node
|
||||
}
|
||||
return prev
|
||||
}, {})
|
||||
linkedPackages = await linkBins(project.modulesDir, project.binsDir, {
|
||||
allowExoticManifests: true,
|
||||
nodeExecPathByAlias,
|
||||
warn: binWarn.bind(null, project.rootDir),
|
||||
})
|
||||
} else {
|
||||
@@ -909,10 +918,14 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
linkedPackages = await linkBinsOfPackages(
|
||||
(
|
||||
await Promise.all(
|
||||
directPkgs.map(async (dep) => ({
|
||||
location: dep.dir,
|
||||
manifest: await dep.fetchingBundledManifest?.() ?? await safeReadProjectManifestOnly(dep.dir),
|
||||
}))
|
||||
directPkgs.map(async (dep) => {
|
||||
const manifest = await dep.fetchingBundledManifest?.() ?? await safeReadProjectManifestOnly(dep.dir)
|
||||
return {
|
||||
location: dep.dir,
|
||||
manifest,
|
||||
nodeExecPath: project.manifest.dependenciesMeta?.[manifest!.name!]?.node,
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>,
|
||||
|
||||
@@ -33,6 +33,11 @@ export default async function (
|
||||
delete packageManifest.peerDependencies[removedDependency]
|
||||
}
|
||||
}
|
||||
if (packageManifest.dependenciesMeta != null) {
|
||||
for (const removedDependency of removedPackages) {
|
||||
delete packageManifest.dependenciesMeta[removedDependency]
|
||||
}
|
||||
}
|
||||
|
||||
packageManifestLogger.debug({
|
||||
prefix: opts.prefix,
|
||||
|
||||
@@ -46,6 +46,12 @@ export interface PeerDependenciesMeta {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DependenciesMeta {
|
||||
[dependencyName: string]: {
|
||||
node?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface PublishConfig extends Record<string, unknown> {
|
||||
directory?: string
|
||||
executableFiles?: string[]
|
||||
@@ -64,6 +70,7 @@ interface BaseManifest {
|
||||
optionalDependencies?: Dependencies
|
||||
peerDependencies?: Dependencies
|
||||
peerDependenciesMeta?: PeerDependenciesMeta
|
||||
dependenciesMeta?: DependenciesMeta
|
||||
bundleDependencies?: string[]
|
||||
bundledDependencies?: string[]
|
||||
homepage?: string
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -980,7 +980,7 @@ importers:
|
||||
'@types/node': ^14.14.33
|
||||
'@types/normalize-path': ^3.0.0
|
||||
'@types/ramda': 0.27.39
|
||||
'@zkochan/cmd-shim': ^5.1.3
|
||||
'@zkochan/cmd-shim': ^5.2.0
|
||||
is-subdir: ^1.1.1
|
||||
is-windows: ^1.0.2
|
||||
ncp: ^2.0.0
|
||||
@@ -996,7 +996,7 @@ importers:
|
||||
'@pnpm/read-package-json': link:../read-package-json
|
||||
'@pnpm/read-project-manifest': link:../read-project-manifest
|
||||
'@pnpm/types': link:../types
|
||||
'@zkochan/cmd-shim': 5.1.3
|
||||
'@zkochan/cmd-shim': 5.2.0
|
||||
is-subdir: 1.2.0
|
||||
is-windows: 1.0.2
|
||||
normalize-path: 3.0.0
|
||||
@@ -1786,7 +1786,7 @@ importers:
|
||||
'@pnpm/store-path': ^5.0.0
|
||||
'@pnpm/tarball-fetcher': workspace:9.3.6
|
||||
'@types/adm-zip': ^0.4.34
|
||||
'@zkochan/cmd-shim': ^5.1.3
|
||||
'@zkochan/cmd-shim': ^5.2.0
|
||||
adm-zip: ^0.5.5
|
||||
execa: npm:safe-execa@^0.1.1
|
||||
load-json-file: ^6.2.0
|
||||
@@ -1805,7 +1805,7 @@ importers:
|
||||
'@pnpm/package-store': link:../package-store
|
||||
'@pnpm/store-path': 5.0.0
|
||||
'@pnpm/tarball-fetcher': link:../tarball-fetcher
|
||||
'@zkochan/cmd-shim': 5.1.3
|
||||
'@zkochan/cmd-shim': 5.2.0
|
||||
adm-zip: 0.5.5
|
||||
load-json-file: 6.2.0
|
||||
rename-overwrite: 4.0.0
|
||||
@@ -5311,8 +5311,8 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: false
|
||||
|
||||
/@zkochan/cmd-shim/5.1.3:
|
||||
resolution: {integrity: sha512-XCy+ZwXoFKswHmJBFbhPIs+NBxYJpitzQ+kSvlhu2upIt74k0/OJsiOJnwJS4Usuydh+ipmcIjwQ55vIJOyKJg==}
|
||||
/@zkochan/cmd-shim/5.2.0:
|
||||
resolution: {integrity: sha512-lY0gYPCG09RcrOjvQtJpABF8YCjsaLjoYMOjAxZhNK1vKKlr0/UPvHsp66z/Gsj96+L1DRHL1oqwaPTXf368jg==}
|
||||
engines: {node: '>=10.13'}
|
||||
dependencies:
|
||||
is-windows: 1.0.2
|
||||
|
||||
Reference in New Issue
Block a user