mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: automatically switch to the right pnpm version (#8363)
close #8360
This commit is contained in:
5
.changeset/fast-papayas-talk.md
Normal file
5
.changeset/fast-papayas-talk.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/cli-utils": major
|
||||
---
|
||||
|
||||
Removed packageManager field check.
|
||||
12
.changeset/proud-insects-pretend.md
Normal file
12
.changeset/proud-insects-pretend.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Added pnpm version management to pnpm. If the `manage-package-manager-versions` setting is set to `true`, pnpm will switch to the version specified in the `packageManager` field of `package.json` [#8363](https://github.com/pnpm/pnpm/pull/8363). This is the same field used by Corepack. Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"packageManager": "pnpm@9.3.0"
|
||||
}
|
||||
```
|
||||
5
.changeset/wet-tables-wave.md
Normal file
5
.changeset/wet-tables-wave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/env.path": major
|
||||
---
|
||||
|
||||
Initial release.
|
||||
5
.changeset/wicked-ants-return.md
Normal file
5
.changeset/wicked-ants-return.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-script-runners": patch
|
||||
---
|
||||
|
||||
Added usage of `@pnpm/env.path`.
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { isExecutedByCorepack, packageManager } from '@pnpm/cli-meta'
|
||||
import { logger, globalWarn } from '@pnpm/logger'
|
||||
import { packageManager } from '@pnpm/cli-meta'
|
||||
import { logger } from '@pnpm/logger'
|
||||
import { checkPackage, UnsupportedEngineError, type WantedEngine } from '@pnpm/package-is-installable'
|
||||
import { type SupportedArchitectures } from '@pnpm/types'
|
||||
|
||||
@@ -24,30 +23,6 @@ export function packageIsInstallable (
|
||||
const currentPnpmVersion = packageManager.name === 'pnpm'
|
||||
? packageManager.version
|
||||
: undefined
|
||||
if (pkg.packageManager && !isExecutedByCorepack()) {
|
||||
const [pmName, pmReference] = pkg.packageManager.split('@')
|
||||
if (pmName && pmName !== 'pnpm') {
|
||||
const msg = `This project is configured to use ${pmName}`
|
||||
if (opts.packageManagerStrict) {
|
||||
throw new PnpmError('OTHER_PM_EXPECTED', msg)
|
||||
} else {
|
||||
globalWarn(msg)
|
||||
}
|
||||
} else if (currentPnpmVersion && opts.packageManagerStrictVersion && !pmReference.includes(':')) {
|
||||
// pmReference is semantic versioning, not URL
|
||||
const [requiredPnpmVersion] = pmReference.split('+')
|
||||
if (requiredPnpmVersion && requiredPnpmVersion !== currentPnpmVersion) {
|
||||
const msg = `This project is configured to use v${requiredPnpmVersion} of pnpm. Your current pnpm is v${currentPnpmVersion}`
|
||||
if (opts.packageManagerStrict) {
|
||||
throw new PnpmError('BAD_PM_VERSION', msg, {
|
||||
hint: 'If you want to bypass this version check, you can set the "package-manager-strict" configuration to "false" or set the "COREPACK_ENABLE_STRICT" environment variable to "0"',
|
||||
})
|
||||
} else {
|
||||
globalWarn(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const err = checkPackage(pkgPath, pkg, {
|
||||
nodeVersion: opts.nodeVersion,
|
||||
pnpmVersion: currentPnpmVersion,
|
||||
|
||||
@@ -60,8 +60,22 @@ export async function parseCliArgs (
|
||||
commandName = opts.fallbackCommand!
|
||||
inputArgv.unshift(opts.fallbackCommand!)
|
||||
// The run command has special casing for --help and is handled further below.
|
||||
} else if (cmd !== 'run' && noptExploratoryResults['help']) {
|
||||
return getParsedArgsForHelp()
|
||||
} else if (cmd !== 'run') {
|
||||
if (noptExploratoryResults['help']) {
|
||||
return getParsedArgsForHelp()
|
||||
}
|
||||
if (noptExploratoryResults['version'] || noptExploratoryResults['v']) {
|
||||
return {
|
||||
argv: noptExploratoryResults.argv,
|
||||
cmd: null,
|
||||
options: {
|
||||
version: true,
|
||||
},
|
||||
params: noptExploratoryResults.argv.remain,
|
||||
unknownOptions: new Map(),
|
||||
fallbackCommandUsed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getParsedArgsForHelp (): ParsedCliArgs {
|
||||
|
||||
@@ -10,6 +10,11 @@ import type { Hooks } from '@pnpm/pnpmfile'
|
||||
|
||||
export type UniversalOptions = Pick<Config, 'color' | 'dir' | 'rawConfig' | 'rawLocalConfig'>
|
||||
|
||||
export interface WantedPackageManager {
|
||||
name: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
allProjects?: Project[]
|
||||
selectedProjectsGraph?: ProjectsGraph
|
||||
@@ -74,6 +79,7 @@ export interface Config {
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
wantedPackageManager?: WantedPackageManager
|
||||
preferOffline?: boolean
|
||||
sideEffectsCache?: boolean // for backward compatibility
|
||||
sideEffectsCacheReadonly?: boolean // for backward compatibility
|
||||
@@ -199,6 +205,7 @@ export interface Config {
|
||||
virtualStoreDirMaxLength: number
|
||||
peersSuffixMaxLength?: number
|
||||
strictStorePkgContentCheck: boolean
|
||||
managePackageManagerVersions: boolean
|
||||
}
|
||||
|
||||
export interface ConfigWithDeprecatedSettings extends Config {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type Config,
|
||||
type ConfigWithDeprecatedSettings,
|
||||
type UniversalOptions,
|
||||
type WantedPackageManager,
|
||||
} from './Config'
|
||||
import { getWorkspaceConcurrency } from './concurrency'
|
||||
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'
|
||||
@@ -35,7 +36,7 @@ export { types }
|
||||
export { getOptionsFromRootManifest, type OptionsFromRootManifest } from './getOptionsFromRootManifest'
|
||||
export * from './readLocalConfig'
|
||||
|
||||
export type { Config, UniversalOptions }
|
||||
export type { Config, UniversalOptions, WantedPackageManager }
|
||||
|
||||
type CamelToKebabCase<S extends string> = S extends `${infer T}${infer U}`
|
||||
? `${T extends Capitalize<T> ? '-' : ''}${Lowercase<T>}${CamelToKebabCase<U>}`
|
||||
@@ -147,6 +148,7 @@ export async function getConfig (opts: {
|
||||
'ignore-workspace-root-check': false,
|
||||
'link-workspace-packages': false,
|
||||
'lockfile-include-tarball-url': false,
|
||||
'manage-package-manager-versions': false,
|
||||
'modules-cache-max-age': 7 * 24 * 60, // 7 days
|
||||
'dlx-cache-max-age': 24 * 60, // 1 day
|
||||
'node-linker': 'isolated',
|
||||
@@ -483,8 +485,13 @@ export async function getConfig (opts: {
|
||||
}
|
||||
pnpmConfig.rootProjectManifestDir = pnpmConfig.lockfileDir ?? pnpmConfig.workspaceDir ?? pnpmConfig.dir
|
||||
pnpmConfig.rootProjectManifest = await safeReadProjectManifestOnly(pnpmConfig.rootProjectManifestDir) ?? undefined
|
||||
if (pnpmConfig.rootProjectManifest?.workspaces?.length && !pnpmConfig.workspaceDir) {
|
||||
warnings.push('The "workspaces" field in package.json is not supported by pnpm. Create a "pnpm-workspace.yaml" file instead.')
|
||||
if (pnpmConfig.rootProjectManifest != null) {
|
||||
if (pnpmConfig.rootProjectManifest.workspaces?.length && !pnpmConfig.workspaceDir) {
|
||||
warnings.push('The "workspaces" field in package.json is not supported by pnpm. Create a "pnpm-workspace.yaml" file instead.')
|
||||
}
|
||||
if (pnpmConfig.rootProjectManifest.packageManager) {
|
||||
pnpmConfig.wantedPackageManager = parsePackageManager(pnpmConfig.rootProjectManifest.packageManager)
|
||||
}
|
||||
}
|
||||
|
||||
if (pnpmConfig.workspaceDir != null) {
|
||||
@@ -504,3 +511,15 @@ function getProcessEnv (env: string): string | undefined {
|
||||
process.env[env.toUpperCase()] ??
|
||||
process.env[env.toLowerCase()]
|
||||
}
|
||||
|
||||
function parsePackageManager (packageManager: string): { name: string, version: string | undefined } {
|
||||
const [name, pmReference] = packageManager.split('@')
|
||||
// pmReference is semantic versioning, not URL
|
||||
if (pmReference.includes(':')) return { name, version: undefined }
|
||||
// Remove the integrity hash. Ex: "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
|
||||
const [version] = pmReference.split('+')
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export const types = Object.assign({
|
||||
'lockfile-include-tarball-url': Boolean,
|
||||
'lockfile-only': Boolean,
|
||||
loglevel: ['silent', 'error', 'warn', 'info', 'debug'],
|
||||
'manage-package-manager-versions': Boolean,
|
||||
maxsockets: Number,
|
||||
'modules-cache-max-age': Number,
|
||||
'dlx-cache-max-age': Number,
|
||||
|
||||
15
env/path/README.md
vendored
Normal file
15
env/path/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# @pnpm/env.path
|
||||
|
||||
> Functions for changing PATH env variable
|
||||
|
||||
[](https://www.npmjs.com/package/@pnpm/env.path)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @pnpm/env.path
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
3
env/path/jest.config.js
vendored
Normal file
3
env/path/jest.config.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require('../../jest.config.js')
|
||||
|
||||
module.exports = config
|
||||
42
env/path/package.json
vendored
Normal file
42
env/path/package.json
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@pnpm/env.path",
|
||||
"version": "0.0.0",
|
||||
"description": "Functions for changing PATH env variable",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/env/path",
|
||||
"keywords": [
|
||||
"pnpm9",
|
||||
"pnpm",
|
||||
"env"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/env/path#readme",
|
||||
"dependencies": {
|
||||
"path-name": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/env.path": "workspace:*"
|
||||
}
|
||||
}
|
||||
12
env/path/src/index.ts
vendored
Normal file
12
env/path/src/index.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import path from 'path'
|
||||
import PATH from 'path-name'
|
||||
|
||||
export function prependDirsToPath (prependDirs: string[], env = process.env): { name: string, value: string } {
|
||||
return {
|
||||
name: PATH,
|
||||
value: [
|
||||
...prependDirs,
|
||||
...(env[PATH] != null ? [env[PATH]] : []),
|
||||
].join(path.delimiter),
|
||||
}
|
||||
}
|
||||
14
env/path/test/index.ts
vendored
Normal file
14
env/path/test/index.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import path from 'path'
|
||||
import { prependDirsToPath } from '@pnpm/env.path'
|
||||
import PATH from 'path-name'
|
||||
|
||||
test('prependDirsToPath', () => {
|
||||
expect(prependDirsToPath(['foo'], {})).toStrictEqual({
|
||||
name: PATH,
|
||||
value: 'foo',
|
||||
})
|
||||
expect(prependDirsToPath(['foo'], { [PATH]: 'bar' })).toStrictEqual({
|
||||
name: PATH,
|
||||
value: `foo${path.delimiter}bar`,
|
||||
})
|
||||
})
|
||||
17
env/path/test/tsconfig.json
vendored
Normal file
17
env/path/test/tsconfig.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
||||
12
env/path/tsconfig.json
vendored
Normal file
12
env/path/tsconfig.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": []
|
||||
}
|
||||
8
env/path/tsconfig.lint.json
vendored
Normal file
8
env/path/tsconfig.lint.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/crypto.base32-hash": "workspace:*",
|
||||
"@pnpm/env.path": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/lifecycle": "workspace:*",
|
||||
"@pnpm/log.group": "catalog:",
|
||||
@@ -65,7 +66,6 @@
|
||||
"didyoumean2": "catalog:",
|
||||
"execa": "catalog:",
|
||||
"p-limit": "catalog:",
|
||||
"path-name": "catalog:",
|
||||
"ramda": "catalog:",
|
||||
"realpath-missing": "catalog:",
|
||||
"render-help": "catalog:",
|
||||
|
||||
@@ -9,7 +9,7 @@ import { sortPackages } from '@pnpm/sort-packages'
|
||||
import { type Project, type ProjectsGraph, type ProjectRootDir, type ProjectRootDirRealPath } from '@pnpm/types'
|
||||
import execa from 'execa'
|
||||
import pLimit from 'p-limit'
|
||||
import PATH from 'path-name'
|
||||
import { prependDirsToPath } from '@pnpm/env.path'
|
||||
import pick from 'ramda/src/pick'
|
||||
import renderHelp from 'render-help'
|
||||
import { existsInDir } from './existsInDir'
|
||||
@@ -355,11 +355,10 @@ function isErrorCommandNotFound (command: string, error: CommandError, prependPa
|
||||
|
||||
// Windows
|
||||
if (process.platform === 'win32') {
|
||||
const prepend = prependPaths.join(path.delimiter)
|
||||
const whichPath = process.env[PATH] ? `${prepend}${path.delimiter}${process.env[PATH] as string}` : prepend
|
||||
const { value: path } = prependDirsToPath(prependPaths)
|
||||
return !which.sync(command, {
|
||||
nothrow: true,
|
||||
path: whichPath,
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { prependDirsToPath } from '@pnpm/env.path'
|
||||
import path from 'path'
|
||||
import PATH from 'path-name'
|
||||
|
||||
export interface Env extends NodeJS.ProcessEnv {
|
||||
npm_config_user_agent: string
|
||||
@@ -22,13 +22,11 @@ export function makeEnv (
|
||||
throw new PnpmError('BAD_PATH_DIR', `Cannot add ${prependPath} to PATH because it contains the path delimiter character (${path.delimiter})`)
|
||||
}
|
||||
}
|
||||
const pathEnv = prependDirsToPath(opts.prependPaths)
|
||||
return {
|
||||
...process.env,
|
||||
...opts.extraEnv,
|
||||
npm_config_user_agent: opts.userAgent ?? 'pnpm',
|
||||
[PATH]: [
|
||||
...opts.prependPaths,
|
||||
process.env[PATH],
|
||||
].join(path.delimiter),
|
||||
[pathEnv.name]: pathEnv.value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
{
|
||||
"path": "../../config/config"
|
||||
},
|
||||
{
|
||||
"path": "../../env/path"
|
||||
},
|
||||
{
|
||||
"path": "../../env/plugin-commands-env"
|
||||
},
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -1700,6 +1700,16 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 7.5.3
|
||||
|
||||
env/path:
|
||||
dependencies:
|
||||
path-name:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.0
|
||||
devDependencies:
|
||||
'@pnpm/env.path':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
|
||||
env/plugin-commands-env:
|
||||
dependencies:
|
||||
'@pnpm/cli-utils':
|
||||
@@ -2094,6 +2104,9 @@ importers:
|
||||
'@pnpm/crypto.base32-hash':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/crypto.base32-hash
|
||||
'@pnpm/env.path':
|
||||
specifier: workspace:*
|
||||
version: link:../../env/path
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
@@ -2142,9 +2155,6 @@ importers:
|
||||
p-limit:
|
||||
specifier: 'catalog:'
|
||||
version: 3.1.0
|
||||
path-name:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.0
|
||||
ramda:
|
||||
specifier: 'catalog:'
|
||||
version: '@pnpm/ramda@0.28.1'
|
||||
@@ -5423,6 +5433,12 @@ importers:
|
||||
'@pnpm/dependency-path':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/dependency-path
|
||||
'@pnpm/env.path':
|
||||
specifier: workspace:*
|
||||
version: link:../env/path
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/error
|
||||
'@pnpm/filter-workspace-packages':
|
||||
specifier: workspace:*
|
||||
version: link:../workspace/filter-workspace-packages
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
"@pnpm/crypto.base32-hash": "workspace:*",
|
||||
"@pnpm/default-reporter": "workspace:*",
|
||||
"@pnpm/dependency-path": "workspace:*",
|
||||
"@pnpm/env.path": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/filter-workspace-packages": "workspace:*",
|
||||
"@pnpm/find-workspace-dir": "workspace:*",
|
||||
"@pnpm/lockfile.types": "workspace:*",
|
||||
|
||||
@@ -3,28 +3,28 @@ if (!global['pnpm__startedAt']) {
|
||||
global['pnpm__startedAt'] = Date.now()
|
||||
}
|
||||
import loudRejection from 'loud-rejection'
|
||||
import { packageManager } from '@pnpm/cli-meta'
|
||||
import { packageManager, isExecutedByCorepack } from '@pnpm/cli-meta'
|
||||
import { getConfig } from '@pnpm/cli-utils'
|
||||
import {
|
||||
type Config,
|
||||
} from '@pnpm/config'
|
||||
import { type Config, type WantedPackageManager } from '@pnpm/config'
|
||||
import { executionTimeLogger, scopeLogger } from '@pnpm/core-loggers'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { filterPackagesFromDir } from '@pnpm/filter-workspace-packages'
|
||||
import { globalWarn, logger } from '@pnpm/logger'
|
||||
import { type ParsedCliArgs } from '@pnpm/parse-cli-args'
|
||||
import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
|
||||
import { finishWorkers } from '@pnpm/worker'
|
||||
import chalk from 'chalk'
|
||||
import { checkForUpdates } from './checkForUpdates'
|
||||
import { pnpmCmds, rcOptionsTypes } from './cmd'
|
||||
import { formatUnknownOptionsError } from './formatError'
|
||||
import { parseCliArgs } from './parseCliArgs'
|
||||
import { initReporter, type ReporterType } from './reporter'
|
||||
import { isCI } from 'ci-info'
|
||||
import path from 'path'
|
||||
import isEmpty from 'ramda/src/isEmpty'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import which from 'which'
|
||||
import { checkForUpdates } from './checkForUpdates'
|
||||
import { pnpmCmds, rcOptionsTypes } from './cmd'
|
||||
import { formatUnknownOptionsError } from './formatError'
|
||||
import { parseCliArgs } from './parseCliArgs'
|
||||
import { initReporter, type ReporterType } from './reporter'
|
||||
import { switchCliVersion } from './switchCliVersion'
|
||||
|
||||
export const REPORTER_INITIALIZED = Symbol('reporterInitialized')
|
||||
|
||||
@@ -98,6 +98,13 @@ export async function main (inputArgv: string[]): Promise<void> {
|
||||
checkUnknownSetting: false,
|
||||
ignoreNonAuthSettingsFromLocal: isDlxCommand,
|
||||
}) as typeof config
|
||||
if (!isExecutedByCorepack() && config.wantedPackageManager != null) {
|
||||
if (config.managePackageManagerVersions) {
|
||||
await switchCliVersion(config)
|
||||
} else {
|
||||
checkPackageManager(config.wantedPackageManager, config)
|
||||
}
|
||||
}
|
||||
if (isDlxCommand) {
|
||||
config.useStderr = true
|
||||
}
|
||||
@@ -119,6 +126,10 @@ export async function main (inputArgv: string[]): Promise<void> {
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (cmd == null && cliOptions.version) {
|
||||
console.log(packageManager.version)
|
||||
return
|
||||
}
|
||||
|
||||
let write: (text: string) => void = process.stdout.write.bind(process.stdout)
|
||||
// chalk reads the FORCE_COLOR env variable
|
||||
@@ -325,3 +336,28 @@ function printError (message: string, hint?: string): void {
|
||||
console.log(hint)
|
||||
}
|
||||
}
|
||||
|
||||
function checkPackageManager (pm: WantedPackageManager, config: Config): void {
|
||||
if (!pm.name) return
|
||||
if (pm.name !== 'pnpm') {
|
||||
const msg = `This project is configured to use ${pm.name}`
|
||||
if (config.packageManagerStrict) {
|
||||
throw new PnpmError('OTHER_PM_EXPECTED', msg)
|
||||
}
|
||||
globalWarn(msg)
|
||||
} else {
|
||||
const currentPnpmVersion = packageManager.name === 'pnpm'
|
||||
? packageManager.version
|
||||
: undefined
|
||||
if (currentPnpmVersion && config.packageManagerStrictVersion && pm.version && pm.version !== currentPnpmVersion) {
|
||||
const msg = `This project is configured to use v${pm.version} of pnpm. Your current pnpm is v${currentPnpmVersion}`
|
||||
if (config.packageManagerStrict) {
|
||||
throw new PnpmError('BAD_PM_VERSION', msg, {
|
||||
hint: 'If you want to bypass this version check, you can set the "package-manager-strict" configuration to "false" or set the "COREPACK_ENABLE_STRICT" environment variable to "0"',
|
||||
})
|
||||
} else {
|
||||
globalWarn(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,6 @@ const argv = process.argv.slice(2)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
; (async () => {
|
||||
switch (argv[0]) {
|
||||
case '-v':
|
||||
case '--version': {
|
||||
const { version } = (await import('@pnpm/cli-meta')).packageManager
|
||||
console.log(version)
|
||||
break
|
||||
}
|
||||
// commands that are passed through to npm:
|
||||
case 'access':
|
||||
case 'adduser':
|
||||
|
||||
53
pnpm/src/switchCliVersion.ts
Normal file
53
pnpm/src/switchCliVersion.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { type Config } from '@pnpm/config'
|
||||
import { globalWarn } from '@pnpm/logger'
|
||||
import { detectIfCurrentPkgIsExecutable, packageManager } from '@pnpm/cli-meta'
|
||||
import { prependDirsToPath } from '@pnpm/env.path'
|
||||
import spawn from 'cross-spawn'
|
||||
import semver from 'semver'
|
||||
import { pnpmCmds } from './cmd'
|
||||
|
||||
export async function switchCliVersion (config: Config): Promise<void> {
|
||||
const pm = config.wantedPackageManager
|
||||
if (pm == null || pm.name !== 'pnpm' || pm.version == null || pm.version === packageManager.version) return
|
||||
if (!semver.valid(pm.version)) {
|
||||
globalWarn(`Cannot switch to pnpm@${pm.version}: "${pm.version}" is not a valid version`)
|
||||
return
|
||||
}
|
||||
const pkgName = detectIfCurrentPkgIsExecutable() ? getExePackageName() : 'pnpm'
|
||||
const dir = path.join(config.pnpmHomeDir, '.tools', pkgName.replaceAll('/', '+'), pm.version)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{}')
|
||||
await pnpmCmds.add(
|
||||
{
|
||||
...config,
|
||||
dir,
|
||||
lockfileDir: dir,
|
||||
bin: path.join(dir, 'bin'),
|
||||
},
|
||||
[`${pkgName}@${pm.version}`]
|
||||
)
|
||||
}
|
||||
const pnpmEnv = prependDirsToPath([path.join(dir, 'bin')])
|
||||
const { status } = spawn.sync('pnpm', process.argv.slice(2), {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
[pnpmEnv.name]: pnpmEnv.value,
|
||||
},
|
||||
})
|
||||
process.exit(status ?? 0)
|
||||
}
|
||||
|
||||
function getExePackageName () {
|
||||
const platform = process.platform === 'win32'
|
||||
? 'win'
|
||||
: process.platform === 'darwin'
|
||||
? 'macos'
|
||||
: process.platform
|
||||
const arch = platform === 'win' && process.arch === 'ia32' ? 'x86' : process.arch
|
||||
|
||||
return `@pnpm/${platform}-${arch}`
|
||||
}
|
||||
60
pnpm/test/switchingVersions.test.ts
Normal file
60
pnpm/test/switchingVersions.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { sync as writeJsonFile } from 'write-json-file'
|
||||
import { execPnpmSync } from './utils'
|
||||
|
||||
test('switch to the pnpm version specified in the packageManager field of package.json, when manager-package-manager=versions is true', async () => {
|
||||
prepare()
|
||||
const pnpmHome = path.resolve('pnpm')
|
||||
const env = { PNPM_HOME: pnpmHome }
|
||||
fs.writeFileSync('.npmrc', 'manage-package-manager-versions=true')
|
||||
writeJsonFile('package.json', {
|
||||
packageManager: 'pnpm@9.3.0',
|
||||
})
|
||||
|
||||
const { stdout } = execPnpmSync(['help'], { env })
|
||||
|
||||
expect(stdout.toString()).toContain('Version 9.3.0')
|
||||
})
|
||||
|
||||
test('do not switch to the pnpm version specified in the packageManager field of package.json', async () => {
|
||||
prepare()
|
||||
const pnpmHome = path.resolve('pnpm')
|
||||
const env = { PNPM_HOME: pnpmHome }
|
||||
writeJsonFile('package.json', {
|
||||
packageManager: 'pnpm@9.3.0',
|
||||
})
|
||||
|
||||
const { stdout } = execPnpmSync(['help'], { env })
|
||||
|
||||
expect(stdout.toString()).not.toContain('Version 9.3.0')
|
||||
})
|
||||
|
||||
test('do not switch to pnpm version that is specified not with a semver version', async () => {
|
||||
prepare()
|
||||
const pnpmHome = path.resolve('pnpm')
|
||||
const env = { PNPM_HOME: pnpmHome }
|
||||
fs.writeFileSync('.npmrc', 'manage-package-manager-versions=true')
|
||||
writeJsonFile('package.json', {
|
||||
packageManager: 'pnpm@kevva/is-positive',
|
||||
})
|
||||
|
||||
const { stdout } = execPnpmSync(['help'], { env })
|
||||
|
||||
expect(stdout.toString()).toContain('Cannot switch to pnpm@kevva/is-positive')
|
||||
})
|
||||
|
||||
test('do not switch to pnpm version when a range is specified', async () => {
|
||||
prepare()
|
||||
const pnpmHome = path.resolve('pnpm')
|
||||
const env = { PNPM_HOME: pnpmHome }
|
||||
fs.writeFileSync('.npmrc', 'manage-package-manager-versions=true')
|
||||
writeJsonFile('package.json', {
|
||||
packageManager: 'pnpm@^9.3.0',
|
||||
})
|
||||
|
||||
const { stdout } = execPnpmSync(['help'], { env })
|
||||
|
||||
expect(stdout.toString()).toContain('Cannot switch to pnpm@^9.3.0')
|
||||
})
|
||||
@@ -52,6 +52,9 @@
|
||||
{
|
||||
"path": "../../config/plugin-commands-config"
|
||||
},
|
||||
{
|
||||
"path": "../../env/path"
|
||||
},
|
||||
{
|
||||
"path": "../../env/plugin-commands-env"
|
||||
},
|
||||
@@ -82,6 +85,9 @@
|
||||
{
|
||||
"path": "../../packages/dependency-path"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/plugin-commands-doctor"
|
||||
},
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
{
|
||||
"path": "../config/plugin-commands-config"
|
||||
},
|
||||
{
|
||||
"path": "../env/path"
|
||||
},
|
||||
{
|
||||
"path": "../env/plugin-commands-env"
|
||||
},
|
||||
@@ -85,6 +88,9 @@
|
||||
{
|
||||
"path": "../packages/dependency-path"
|
||||
},
|
||||
{
|
||||
"path": "../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../packages/plugin-commands-doctor"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user