feat: customize supportedArchitectures using CLI (#9745)

close #7510
This commit is contained in:
Khải
2025-07-16 05:56:00 +07:00
committed by GitHub
parent 2e85f29e3d
commit 6f7ac0f48b
21 changed files with 596 additions and 62 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": patch
"pnpm": patch
---
Fix a bug in which `pnpm add` downloads packages whose `libc` differ from `pnpm.supportedArchitectures.libc`.

View File

@@ -0,0 +1,8 @@
---
"@pnpm/plugin-commands-script-runners": major
"@pnpm/plugin-commands-installation": minor
"@pnpm/config": minor
"pnpm": minor
---
Add `--cpu`, `--libc`, and `--os` to `pnpm install`, `pnpm add`, and `pnpm dlx` to customize `supportedArchitectures` via the CLI [#7510](https://github.com/pnpm/pnpm/issues/7510).

View File

@@ -36,6 +36,10 @@ import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'
import { types } from './types'
import { getOptionsFromPnpmSettings, getOptionsFromRootManifest } from './getOptionsFromRootManifest'
import {
type CliOptions as SupportedArchitecturesCliOptions,
overrideSupportedArchitecturesWithCLI,
} from './overrideSupportedArchitecturesWithCLI'
export { types }
export { getOptionsFromRootManifest, getOptionsFromPnpmSettings, type OptionsFromRootManifest } from './getOptionsFromRootManifest'
@@ -54,7 +58,7 @@ type KebabCaseConfig = {
const npmDefaults = loadNpmConf.defaults
export type CliOptions = Record<string, unknown> & { dir?: string, json?: boolean }
export type CliOptions = Record<string, unknown> & SupportedArchitecturesCliOptions & { dir?: string, json?: boolean }
export async function getConfig (opts: {
globalDirShouldAllowWrite?: boolean
@@ -379,6 +383,9 @@ export async function getConfig (opts: {
}
}
}
overrideSupportedArchitecturesWithCLI(pnpmConfig, cliOptions)
if (opts.cliOptions['global']) {
extractAndRemoveDependencyBuildOptions(pnpmConfig)
Object.assign(pnpmConfig, globalDepsBuildConfig)

View File

@@ -0,0 +1,23 @@
import { type Config } from './Config'
import { type types } from './types'
const CLI_OPTION_NAMES = ['cpu', 'libc', 'os'] as const satisfies Array<keyof typeof types>
type CliOptionName = typeof CLI_OPTION_NAMES[number]
export type CliOptions = Partial<Record<CliOptionName, string | string[]>>
export type TargetConfig = Pick<Config, 'supportedArchitectures'>
/**
* If `--cpu`, `--libc`, or `--os` was provided from the command line, override `supportedArchitectures` with them.
* @param targetConfig - The config object whose `supportedArchitectures` would be overridden.
* @param cliOptions - The object that contains object
*/
export function overrideSupportedArchitecturesWithCLI (targetConfig: TargetConfig, cliOptions: CliOptions): void {
for (const key of CLI_OPTION_NAMES) {
const values = cliOptions[key]
if (values != null) {
targetConfig.supportedArchitectures ??= {}
targetConfig.supportedArchitectures[key] = typeof values === 'string' ? [values] : values
}
}
}

View File

@@ -129,4 +129,7 @@ export const types = Object.assign({
'registry-supports-time-field': Boolean,
'fail-if-no-match': Boolean,
'sync-injected-deps-after-scripts': Array,
cpu: [String, Array],
libc: [String, Array],
os: [String, Array],
}, npmTypes.types)

View File

@@ -0,0 +1,93 @@
import { type SupportedArchitectures } from '@pnpm/types'
import { type CliOptions, type TargetConfig, overrideSupportedArchitecturesWithCLI } from '../src/overrideSupportedArchitecturesWithCLI'
function getOverriddenSupportedArchitectures (
supportedArchitectures: SupportedArchitectures | undefined,
cliOptions: CliOptions
): SupportedArchitectures | undefined {
const config: TargetConfig = { supportedArchitectures }
overrideSupportedArchitecturesWithCLI(config, cliOptions)
return config.supportedArchitectures
}
test('no flags, no overrides', () => {
expect(getOverriddenSupportedArchitectures(undefined, {})).toBeUndefined()
expect(getOverriddenSupportedArchitectures({}, {})).toStrictEqual({})
expect(getOverriddenSupportedArchitectures({
os: ['linux'],
}, {})).toStrictEqual({
os: ['linux'],
} as SupportedArchitectures)
expect(getOverriddenSupportedArchitectures({
cpu: ['x64'],
os: ['linux'],
}, {})).toStrictEqual({
cpu: ['x64'],
os: ['linux'],
} as SupportedArchitectures)
expect(getOverriddenSupportedArchitectures({
cpu: ['x64'],
libc: ['glibc'],
os: ['linux'],
}, {})).toStrictEqual({
cpu: ['x64'],
libc: ['glibc'],
os: ['linux'],
} as SupportedArchitectures)
})
test('overrides', () => {
expect(getOverriddenSupportedArchitectures(undefined, {
cpu: ['arm64'],
os: ['darwin'],
})).toStrictEqual({
cpu: ['arm64'],
os: ['darwin'],
})
expect(getOverriddenSupportedArchitectures({}, {
cpu: ['arm64'],
os: ['darwin'],
})).toStrictEqual({
cpu: ['arm64'],
os: ['darwin'],
})
expect(getOverriddenSupportedArchitectures({
os: ['linux'],
}, {
cpu: ['arm64'],
os: ['darwin'],
})).toStrictEqual({
cpu: ['arm64'],
os: ['darwin'],
} as SupportedArchitectures)
expect(getOverriddenSupportedArchitectures({
cpu: ['x64'],
os: ['linux'],
}, {
cpu: ['arm64'],
os: ['darwin'],
})).toStrictEqual({
cpu: ['arm64'],
os: ['darwin'],
} as SupportedArchitectures)
expect(getOverriddenSupportedArchitectures({
cpu: ['x64'],
libc: ['glibc'],
os: ['linux'],
}, {
cpu: ['arm64'],
os: ['darwin'],
})).toStrictEqual({
cpu: ['arm64'],
libc: ['glibc'],
os: ['darwin'],
} as SupportedArchitectures)
})

View File

@@ -54,6 +54,7 @@
"@pnpm/sort-packages": "workspace:*",
"@pnpm/store-path": "workspace:*",
"@pnpm/types": "workspace:*",
"@pnpm/util.lex-comparator": "catalog:",
"@pnpm/workspace.injected-deps-syncer": "workspace:*",
"@zkochan/rimraf": "catalog:",
"didyoumean2": "catalog:",

View File

@@ -11,7 +11,8 @@ import { PnpmError } from '@pnpm/error'
import { add } from '@pnpm/plugin-commands-installation'
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
import { getBinsFromPackageManifest } from '@pnpm/package-bins'
import { type PnpmSettings } from '@pnpm/types'
import { type PnpmSettings, type SupportedArchitectures } from '@pnpm/types'
import { lexCompare } from '@pnpm/util.lex-comparator'
import execa from 'execa'
import pick from 'ramda/src/pick'
import renderHelp from 'render-help'
@@ -29,6 +30,9 @@ export const shorthands: Record<string, string> = {
export function rcOptionsTypes (): Record<string, unknown> {
return {
...pick([
'cpu',
'libc',
'os',
'use-node-version',
], types),
'shell-mode': Boolean,
@@ -97,11 +101,13 @@ export async function handler (
})
return resolved.id
}))
const { cacheLink, cacheExists, cachedDir } = findCache(resolvedPkgs, {
const { cacheLink, cacheExists, cachedDir } = findCache({
packages: resolvedPkgs,
dlxCacheMaxAge: opts.dlxCacheMaxAge,
cacheDir: opts.cacheDir,
registries: opts.registries,
allowBuild: opts.allowBuild ?? [],
allowBuild: opts.allowBuild,
supportedArchitectures: opts.supportedArchitectures,
})
if (!cacheExists) {
fs.mkdirSync(cachedDir, { recursive: true })
@@ -196,13 +202,15 @@ function scopeless (pkgName: string): string {
return pkgName
}
function findCache (pkgs: string[], opts: {
function findCache (opts: {
packages: string[]
cacheDir: string
dlxCacheMaxAge: number
registries: Record<string, string>
allowBuild: string[]
allowBuild?: string[]
supportedArchitectures?: SupportedArchitectures
}): { cacheLink: string, cacheExists: boolean, cachedDir: string } {
const dlxCommandCacheDir = createDlxCommandCacheDir(pkgs, opts)
const dlxCommandCacheDir = createDlxCommandCacheDir(opts)
const cacheLink = path.join(dlxCommandCacheDir, 'pkg')
const cachedDir = getValidCacheDir(cacheLink, opts.dlxCacheMaxAge)
return {
@@ -213,26 +221,44 @@ function findCache (pkgs: string[], opts: {
}
function createDlxCommandCacheDir (
pkgs: string[],
opts: {
packages: string[]
registries: Record<string, string>
cacheDir: string
allowBuild: string[]
allowBuild?: string[]
supportedArchitectures?: SupportedArchitectures
}
): string {
const dlxCacheDir = path.resolve(opts.cacheDir, 'dlx')
const cacheKey = createCacheKey(pkgs, opts.registries, opts.allowBuild)
const cacheKey = createCacheKey(opts)
const cachePath = path.join(dlxCacheDir, cacheKey)
fs.mkdirSync(cachePath, { recursive: true })
return cachePath
}
export function createCacheKey (pkgs: string[], registries: Record<string, string>, allowBuild?: string[]): string {
const sortedPkgs = [...pkgs].sort((a, b) => a.localeCompare(b))
const sortedRegistries = Object.entries(registries).sort(([k1], [k2]) => k1.localeCompare(k2))
export function createCacheKey (opts: {
packages: string[]
registries: Record<string, string>
allowBuild?: string[]
supportedArchitectures?: SupportedArchitectures
}): string {
const sortedPkgs = [...opts.packages].sort((a, b) => a.localeCompare(b))
const sortedRegistries = Object.entries(opts.registries).sort(([k1], [k2]) => k1.localeCompare(k2))
const args: unknown[] = [sortedPkgs, sortedRegistries]
if (allowBuild?.length) {
args.push({ allowBuild: allowBuild.sort((pkg1, pkg2) => pkg1.localeCompare(pkg2)) })
if (opts.allowBuild?.length) {
args.push({ allowBuild: opts.allowBuild.sort(lexCompare) })
}
if (opts.supportedArchitectures) {
const supportedArchitecturesKeys = ['cpu', 'libc', 'os'] as const satisfies Array<keyof SupportedArchitectures>
for (const key of supportedArchitecturesKeys) {
const value = opts.supportedArchitectures[key]
if (!value?.length) continue
args.push({
supportedArchitectures: {
[key]: [...new Set(value)].sort(lexCompare),
},
})
}
}
const hashStr = JSON.stringify(args)
return createHexHash(hashStr)

View File

@@ -2,9 +2,12 @@ import { createHexHash } from '@pnpm/crypto.hash'
import { createCacheKey } from '../src/dlx'
test('creates a hash', () => {
const received = createCacheKey(['shx', '@foo/bar'], {
default: 'https://registry.npmjs.com/',
'@foo': 'https://example.com/npm-registry/foo/',
const received = createCacheKey({
packages: ['shx', '@foo/bar'],
registries: {
default: 'https://registry.npmjs.com/',
'@foo': 'https://example.com/npm-registry/foo/',
},
})
const expected = createHexHash(JSON.stringify([['@foo/bar', 'shx'], [
['@foo', 'https://example.com/npm-registry/foo/'],
@@ -15,16 +18,43 @@ test('creates a hash', () => {
test('is agnostic to package order', () => {
const registries = { default: 'https://registry.npmjs.com/' }
expect(createCacheKey(['a', 'c', 'b'], registries)).toBe(createCacheKey(['a', 'b', 'c'], registries))
expect(createCacheKey(['b', 'a', 'c'], registries)).toBe(createCacheKey(['a', 'b', 'c'], registries))
expect(createCacheKey(['b', 'c', 'a'], registries)).toBe(createCacheKey(['a', 'b', 'c'], registries))
expect(createCacheKey(['c', 'a', 'b'], registries)).toBe(createCacheKey(['a', 'b', 'c'], registries))
expect(createCacheKey(['c', 'b', 'a'], registries)).toBe(createCacheKey(['a', 'b', 'c'], registries))
const makeOpts = (packages: string[]) => ({ packages, registries })
expect(createCacheKey(makeOpts(['a', 'c', 'b']))).toBe(createCacheKey(makeOpts(['a', 'b', 'c'])))
expect(createCacheKey(makeOpts(['b', 'a', 'c']))).toBe(createCacheKey(makeOpts(['a', 'b', 'c'])))
expect(createCacheKey(makeOpts(['b', 'c', 'a']))).toBe(createCacheKey(makeOpts(['a', 'b', 'c'])))
expect(createCacheKey(makeOpts(['c', 'a', 'b']))).toBe(createCacheKey(makeOpts(['a', 'b', 'c'])))
expect(createCacheKey(makeOpts(['c', 'b', 'a']))).toBe(createCacheKey(makeOpts(['a', 'b', 'c'])))
})
test('is agnostic to registry key order', () => {
const packages = ['a', 'b', 'c']
const foo = 'https://example.com/foo/'
const bar = 'https://example.com/bar/'
expect(createCacheKey(packages, { '@foo': foo, '@bar': bar })).toBe(createCacheKey(packages, { '@bar': bar, '@foo': foo }))
expect(createCacheKey({
packages,
registries: { '@foo': foo, '@bar': bar },
})).toBe(createCacheKey({
packages,
registries: { '@bar': bar, '@foo': foo },
}))
})
test('is agnostic to supportedArchitectures values order', () => {
const packages = ['a', 'b', 'c']
const registries = { default: 'https://registry.npmjs.com/' }
expect(createCacheKey({
packages,
registries,
supportedArchitectures: {
os: ['win32', 'linux', 'darwin'],
cpu: ['x86_64', 'armv7', 'i686'],
},
})).toBe(createCacheKey({
packages,
registries,
supportedArchitectures: {
cpu: ['armv7', 'i686', 'x86_64'],
os: ['darwin', 'linux', 'win32'],
},
}))
})

View File

@@ -20,7 +20,11 @@ function sanitizeDlxCacheComponent (cacheName: string): string {
return '***********-*****'
}
const createCacheKey = (...pkgs: string[]): string => dlx.createCacheKey(pkgs, DEFAULT_OPTS.registries)
const createCacheKey = (...packages: string[]): string => dlx.createCacheKey({
packages,
registries: DEFAULT_OPTS.registries,
supportedArchitectures: DEFAULT_OPTS.supportedArchitectures,
})
function verifyDlxCache (cacheName: string): void {
expect(
@@ -335,7 +339,12 @@ test('dlx builds the packages passed via --allow-build', async () => {
dlxCacheMaxAge: Infinity,
}, ['@pnpm.e2e/has-bin-and-needs-build'])
const dlxCacheDir = path.resolve('cache', 'dlx', dlx.createCacheKey(['@pnpm.e2e/has-bin-and-needs-build@1.0.0'], DEFAULT_OPTS.registries, allowBuild), 'pkg')
const dlxCacheDir = path.resolve('cache', 'dlx', dlx.createCacheKey({
packages: ['@pnpm.e2e/has-bin-and-needs-build@1.0.0'],
allowBuild,
registries: DEFAULT_OPTS.registries,
supportedArchitectures: DEFAULT_OPTS.supportedArchitectures,
}), 'pkg')
const builtPkg1Path = path.join(dlxCacheDir, 'node_modules/.pnpm/@pnpm.e2e+pre-and-postinstall-scripts-example@1.0.0/node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example')
expect(fs.existsSync(path.join(builtPkg1Path, 'package.json'))).toBeTruthy()
expect(fs.existsSync(path.join(builtPkg1Path, 'generated-by-preinstall.js'))).toBeFalsy()

View File

@@ -7,6 +7,7 @@ import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
import { createOrConnectStoreController } from '@pnpm/store-connection-manager'
import pick from 'ramda/src/pick'
import renderHelp from 'render-help'
import { getFetchFullMetadata } from './getFetchFullMetadata'
import { type InstallCommandOptions } from './install'
import { installDeps } from './installDeps'
import { writeSettings } from '@pnpm/config.config-writer'
@@ -18,6 +19,7 @@ export const shorthands: Record<string, string> = {
export function rcOptionsTypes (): Record<string, unknown> {
return pick([
'cache-dir',
'cpu',
'child-concurrency',
'dangerously-allow-all-builds',
'engine-strict',
@@ -37,6 +39,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
'ignore-pnpmfile',
'ignore-scripts',
'ignore-workspace-root-check',
'libc',
'link-workspace-packages',
'lockfile-dir',
'lockfile-directory',
@@ -47,6 +50,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
'node-linker',
'noproxy',
'npm-path',
'os',
'package-import-method',
'pnpmfile',
'prefer-offline',
@@ -278,6 +282,7 @@ export async function handler (
}
return installDeps({
...opts,
fetchFullMetadata: getFetchFullMetadata(opts),
include,
includeDirect: include,
prepareExecutionEnv: prepareExecutionEnv.bind(null, opts),

View File

@@ -0,0 +1,13 @@
import { type InstallCommandOptions } from './install'
export type GetFetchFullMetadataOptions = Pick<InstallCommandOptions, 'supportedArchitectures' | 'rootProjectManifest'>
/**
* This function is a workaround for the fact that npm registry's abbreviated metadata currently does not contain `libc`.
*
* See <https://github.com/pnpm/pnpm/issues/7362#issuecomment-1971964689>.
*/
export const getFetchFullMetadata = (opts: GetFetchFullMetadataOptions): true | undefined => (
opts.supportedArchitectures?.libc ??
opts.rootProjectManifest?.pnpm?.supportedArchitectures?.libc
) && true

View File

@@ -6,12 +6,14 @@ import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
import { type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
import pick from 'ramda/src/pick'
import renderHelp from 'render-help'
import { getFetchFullMetadata } from './getFetchFullMetadata'
import { installDeps, type InstallDepsOptions } from './installDeps'
export function rcOptionsTypes (): Record<string, unknown> {
return pick([
'cache-dir',
'child-concurrency',
'cpu',
'dangerously-allow-all-builds',
'dev',
'engine-strict',
@@ -30,6 +32,8 @@ export function rcOptionsTypes (): Record<string, unknown> {
'ignore-pnpmfile',
'ignore-scripts',
'optimistic-repeat-install',
'os',
'libc',
'link-workspace-packages',
'lockfile-dir',
'lockfile-directory',
@@ -317,6 +321,7 @@ export type InstallCommandOptions = Pick<Config,
| 'disallowWorkspaceCycles'
| 'updateConfig'
| 'overrides'
| 'supportedArchitectures'
> & CreateStoreControllerOptions & {
argv: {
original: string[]
@@ -342,9 +347,6 @@ export async function handler (opts: InstallCommandOptions): Promise<void> {
devDependencies: opts.dev !== false,
optionalDependencies: opts.optional !== false,
}
// npm registry's abbreviated metadata currently does not contain libc
// see <https://github.com/pnpm/pnpm/issues/7362#issuecomment-1971964689>
const fetchFullMetadata: true | undefined = opts.rootProjectManifest?.pnpm?.supportedArchitectures?.libc && true
const installDepsOptions: InstallDepsOptions = {
...opts,
frozenLockfileIfExists: opts.frozenLockfileIfExists ?? (
@@ -355,7 +357,7 @@ export async function handler (opts: InstallCommandOptions): Promise<void> {
include,
includeDirect: include,
prepareExecutionEnv: prepareExecutionEnv.bind(null, opts),
fetchFullMetadata,
fetchFullMetadata: getFetchFullMetadata(opts),
}
if (opts.resolutionOnly) {
installDepsOptions.lockfileOnly = true

View File

@@ -1,8 +1,10 @@
import fs from 'fs'
import path from 'path'
import { type PnpmError } from '@pnpm/error'
import { add, remove } from '@pnpm/plugin-commands-installation'
import { prepare, prepareEmpty, preparePackages } from '@pnpm/prepare'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { type ProjectManifest } from '@pnpm/types'
import loadJsonFile from 'load-json-file'
import tempy from 'tempy'
@@ -42,6 +44,8 @@ const DEFAULT_OPTIONS = {
virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120,
}
const describeOnLinuxOnly = process.platform === 'linux' ? describe : describe.skip
test('installing with "workspace:" should work even if link-workspace-packages is off', async () => {
const projects = preparePackages([
{
@@ -395,3 +399,34 @@ test('add: fail trying to install @pnpm/exe', async () => {
}
expect(err.code).toBe('ERR_PNPM_GLOBAL_PNPM_INSTALL')
})
describeOnLinuxOnly('filters optional dependencies based on pnpm.supportedArchitectures.libc', () => {
test.each([
['glibc', '@pnpm.e2e+only-linux-x64-glibc@1.0.0', '@pnpm.e2e+only-linux-x64-musl@1.0.0'],
['musl', '@pnpm.e2e+only-linux-x64-musl@1.0.0', '@pnpm.e2e+only-linux-x64-glibc@1.0.0'],
])('%p → installs %p, does not install %p', async (libc, found, notFound) => {
const rootProjectManifest: ProjectManifest = {
pnpm: {
supportedArchitectures: {
os: ['linux'],
cpu: ['x64'],
libc: [libc],
},
},
}
prepare(rootProjectManifest)
await add.handler({
...DEFAULT_OPTIONS,
rootProjectManifest,
dir: process.cwd(),
linkWorkspacePackages: true,
}, ['@pnpm.e2e/support-different-architectures'])
const pkgDirs = fs.readdirSync(path.resolve('node_modules', '.pnpm'))
expect(pkgDirs).toContain('@pnpm.e2e+support-different-architectures@1.0.0')
expect(pkgDirs).toContain(found)
expect(pkgDirs).not.toContain(notFound)
})
})

View File

@@ -58,7 +58,7 @@ test('install with no store integrity validation', async () => {
})
// Covers https://github.com/pnpm/pnpm/issues/7362
describeOnLinuxOnly('filters optional dependencies based on libc', () => {
describeOnLinuxOnly('filters optional dependencies based on pnpm.supportedArchitectures.libc', () => {
test.each([
['glibc', '@pnpm.e2e+only-linux-x64-glibc@1.0.0', '@pnpm.e2e+only-linux-x64-musl@1.0.0'],
['musl', '@pnpm.e2e+only-linux-x64-musl@1.0.0', '@pnpm.e2e+only-linux-x64-glibc@1.0.0'],
@@ -90,3 +90,32 @@ describeOnLinuxOnly('filters optional dependencies based on libc', () => {
expect(pkgDirs).not.toContain(notFound)
})
})
describeOnLinuxOnly('filters optional dependencies based on --libc', () => {
test.each([
['glibc', '@pnpm.e2e+only-linux-x64-glibc@1.0.0', '@pnpm.e2e+only-linux-x64-musl@1.0.0'],
['musl', '@pnpm.e2e+only-linux-x64-musl@1.0.0', '@pnpm.e2e+only-linux-x64-glibc@1.0.0'],
])('%p → installs %p, does not install %p', async (libc, found, notFound) => {
const rootProjectManifest = {
dependencies: {
'@pnpm.e2e/support-different-architectures': '1.0.0',
},
}
prepare(rootProjectManifest)
await install.handler({
...DEFAULT_OPTS,
rootProjectManifest,
dir: process.cwd(),
supportedArchitectures: {
libc: [libc],
},
})
const pkgDirs = fs.readdirSync(path.resolve('node_modules', '.pnpm'))
expect(pkgDirs).toContain('@pnpm.e2e+support-different-architectures@1.0.0')
expect(pkgDirs).toContain(found)
expect(pkgDirs).not.toContain(notFound)
})
})

56
pnpm-lock.yaml generated
View File

@@ -61,8 +61,8 @@ catalogs:
specifier: 0.0.1
version: 0.0.1
'@pnpm/registry-mock':
specifier: 4.6.0
version: 4.6.0
specifier: 4.7.1
version: 4.7.1
'@pnpm/semver-diff':
specifier: ^1.1.0
version: 1.1.0
@@ -871,7 +871,7 @@ importers:
version: link:../../pkg-manager/modules-yaml
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
@@ -905,7 +905,7 @@ importers:
dependencies:
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/store.cafs':
specifier: workspace:*
version: link:../../store/cafs
@@ -980,7 +980,7 @@ importers:
dependencies:
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/worker':
specifier: workspace:*
version: link:../../worker
@@ -1166,7 +1166,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@types/ramda':
specifier: 'catalog:'
version: 0.29.12
@@ -1657,7 +1657,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/testing.temp-store':
specifier: workspace:*
version: link:../../testing/temp-store
@@ -2299,7 +2299,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
@@ -2585,7 +2585,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -2682,6 +2682,9 @@ importers:
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
'@pnpm/util.lex-comparator':
specifier: 'catalog:'
version: 3.0.2
'@pnpm/workspace.injected-deps-syncer':
specifier: workspace:*
version: link:../../workspace/injected-deps-syncer
@@ -2733,7 +2736,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-ipc-server':
specifier: workspace:*
version: link:../../__utils__/test-ipc-server
@@ -4419,7 +4422,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -4706,7 +4709,7 @@ importers:
version: link:../../pkg-manifest/read-package-json
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/store-path':
specifier: workspace:*
version: link:../../store/store-path
@@ -4979,7 +4982,7 @@ importers:
version: link:../read-projects-context
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/store-path':
specifier: workspace:*
version: link:../../store/store-path
@@ -5330,7 +5333,7 @@ importers:
version: 'link:'
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -5556,7 +5559,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -6160,7 +6163,7 @@ importers:
version: link:../pkg-manifest/read-project-manifest
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/run-npm':
specifier: workspace:*
version: link:../exec/run-npm
@@ -6473,7 +6476,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -6600,7 +6603,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-ipc-server':
specifier: workspace:*
version: link:../../__utils__/test-ipc-server
@@ -7209,7 +7212,7 @@ importers:
version: link:../../pkg-manifest/read-package-json
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -7273,7 +7276,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/workspace.filter-packages-from-dir':
specifier: workspace:*
version: link:../../workspace/filter-packages-from-dir
@@ -7358,7 +7361,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -7712,7 +7715,7 @@ importers:
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@types/archy':
specifier: 'catalog:'
version: 0.0.33
@@ -7966,7 +7969,7 @@ importers:
version: link:../../store/package-store
'@pnpm/registry-mock':
specifier: 'catalog:'
version: 4.6.0(encoding@0.1.13)(typanion@3.14.0)
version: 4.7.1(encoding@0.1.13)(typanion@3.14.0)
'@pnpm/store-controller-types':
specifier: workspace:*
version: link:../../store/store-controller-types
@@ -9812,8 +9815,8 @@ packages:
resolution: {integrity: sha512-UY5ZFl8jTgWpPMp3qwVt1z455gDLGh4aAna7ufqsJP9qhI6lr9scFpnEamjpA51Y3MJMBtnML8KATmH6RY+NHQ==}
engines: {node: '>=18.12'}
'@pnpm/registry-mock@4.6.0':
resolution: {integrity: sha512-VX2paPLc1wmv3mDNZiWVF+xqs47M7iLdemKkkvD50eG1kxjXyTjQMy6sfQjPCn/iFrxea1hpyi6FJ5AtROE7sw==}
'@pnpm/registry-mock@4.7.1':
resolution: {integrity: sha512-R9ApAYW+4QORO/4R60zwjkm01Ub4LQN6Zt6Ad+OGPw95C0ge4tWgvvQqtRR/3fjPMgw3BZ6SmK21686zqtURrA==}
engines: {node: '>=18.12'}
hasBin: true
@@ -15177,7 +15180,6 @@ packages:
verdaccio@5.20.1:
resolution: {integrity: sha512-zKQXYubQOfl2w09gO9BR7U9ZZkFPPby8tvV+na86/2vGZnY79kNSVnSbK8CM1bpJHTCQ80AGsmIGovg2FgXhdQ==}
engines: {node: '>=12.18'}
deprecated: this version is deprecated, please migrate to 6.x versions
hasBin: true
verror@1.10.0:
@@ -17471,7 +17473,7 @@ snapshots:
read-yaml-file: 2.1.0
strip-bom: 4.0.0
'@pnpm/registry-mock@4.6.0(encoding@0.1.13)(typanion@3.14.0)':
'@pnpm/registry-mock@4.7.1(encoding@0.1.13)(typanion@3.14.0)':
dependencies:
anonymous-npm-registry-client: 0.3.2
execa: 5.1.1

View File

@@ -61,7 +61,7 @@ catalog:
'@pnpm/npm-package-arg': ^1.0.0
'@pnpm/os.env.path-extender': ^2.0.3
'@pnpm/patch-package': 0.0.1
'@pnpm/registry-mock': 4.6.0
'@pnpm/registry-mock': 4.7.1
'@pnpm/semver-diff': ^1.1.0
'@pnpm/tabtab': ^0.5.4
'@pnpm/util.lex-comparator': ^3.0.2

View File

@@ -6,6 +6,7 @@ import { prepare, prepareEmpty } from '@pnpm/prepare'
import { readModulesManifest } from '@pnpm/modules-yaml'
import { addUser, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { dlx } from '@pnpm/plugin-commands-script-runners'
import { type BaseManifest } from '@pnpm/types'
import { execPnpm, execPnpmSync } from './utils'
let registries: Record<string, string>
@@ -16,7 +17,9 @@ beforeAll(async () => {
registries.default = `http://localhost:${REGISTRY_MOCK_PORT}/`
})
const createCacheKey = (...pkgs: string[]): string => dlx.createCacheKey(pkgs, registries)
const createCacheKey = (...packages: string[]): string => dlx.createCacheKey({ packages, registries })
const describeOnLinuxOnly = process.platform === 'linux' ? describe : describe.skip
test('dlx parses options between "dlx" and the command name', async () => {
prepareEmpty()
@@ -298,3 +301,97 @@ test('dlx uses the node version specified by --use-node-version', async () => {
: path.join(pnpmHome, 'nodejs', '20.0.0', 'bin', 'node'),
})
})
describeOnLinuxOnly('dlx with supportedArchitectures CLI options', () => {
type CPU = 'arm64' | 'x64'
type LibC = 'glibc' | 'musl'
type OS = 'darwin' | 'linux' | 'win32'
type CLIOption = `--cpu=${CPU}` | `--libc=${LibC}` | `--os=${OS}`
type Installed = string[]
type NotInstalled = string[]
type Case = [CLIOption[], Installed, NotInstalled]
test.each([
[['--cpu=arm64', '--os=win32'], ['@pnpm.e2e/only-win32-arm64'], [
'@pnpm.e2e/only-darwin-arm64',
'@pnpm.e2e/only-darwin-x64',
'@pnpm.e2e/only-linux-arm64-glibc',
'@pnpm.e2e/only-linux-arm64-musl',
'@pnpm.e2e/only-linux-x64-glibc',
'@pnpm.e2e/only-linux-x64-musl',
'@pnpm.e2e/only-win32-x64',
]],
[['--cpu=arm64', '--os=darwin'], ['@pnpm.e2e/only-darwin-arm64'], [
'@pnpm.e2e/only-darwin-x64',
'@pnpm.e2e/only-linux-arm64-glibc',
'@pnpm.e2e/only-linux-arm64-musl',
'@pnpm.e2e/only-linux-x64-glibc',
'@pnpm.e2e/only-linux-x64-musl',
'@pnpm.e2e/only-win32-arm64',
'@pnpm.e2e/only-win32-x64',
]],
[['--cpu=x64', '--os=linux', '--libc=musl'], [
'@pnpm.e2e/only-linux-x64-musl',
], [
'@pnpm.e2e/only-darwin-arm64',
'@pnpm.e2e/only-darwin-x64',
'@pnpm.e2e/only-linux-arm64-glibc',
'@pnpm.e2e/only-linux-arm64-musl',
'@pnpm.e2e/only-linux-x64-glibc',
'@pnpm.e2e/only-win32-arm64',
'@pnpm.e2e/only-win32-x64',
]],
[[
'--cpu=arm64',
'--cpu=x64',
'--os=darwin',
'--os=linux',
'--os=win32',
], [
'@pnpm.e2e/only-darwin-arm64',
'@pnpm.e2e/only-darwin-x64',
'@pnpm.e2e/only-linux-arm64-glibc',
'@pnpm.e2e/only-linux-arm64-musl',
'@pnpm.e2e/only-linux-x64-glibc',
'@pnpm.e2e/only-linux-x64-musl',
'@pnpm.e2e/only-win32-arm64',
'@pnpm.e2e/only-win32-x64',
], []],
] as Case[])('%p', async (cliOpts, installed, notInstalled) => {
prepareEmpty()
const execResult = execPnpmSync([
`--config.store-dir=${path.resolve('store')}`,
`--config.cache-dir=${path.resolve('cache')}`,
'--package=@pnpm.e2e/support-different-architectures',
...cliOpts,
'dlx',
'get-optional-dependencies',
], {
stdio: [null, 'pipe', 'inherit'],
expectSuccess: true,
})
interface OptionalDepsInfo {
installed: Record<string, BaseManifest>
notInstalled: string[]
}
let optionalDepsInfo: OptionalDepsInfo
try {
optionalDepsInfo = JSON.parse(execResult.stdout.toString())
} catch (err) {
console.error(execResult.stdout.toString())
console.error(execResult.stderr.toString())
throw err
}
expect(optionalDepsInfo).toStrictEqual({
installed: Object.fromEntries(installed.map(name => [name, expect.objectContaining({ name })])),
notInstalled,
} as OptionalDepsInfo)
})
})

View File

@@ -0,0 +1,139 @@
import fs from 'fs'
import { prepare, prepareEmpty } from '@pnpm/prepare'
import { readModulesManifest } from '@pnpm/modules-yaml'
import { type WorkspaceManifest } from '@pnpm/workspace.read-manifest'
import { sync as writeYamlFile } from 'write-yaml-file'
import { execPnpm } from '../utils'
const describeOnLinuxOnly = process.platform === 'linux' ? describe : describe.skip
type CPU = 'arm64' | 'x64'
type LibC = 'glibc' | 'musl'
type OS = 'darwin' | 'linux' | 'win32'
type CLIOption = `--cpu=${CPU}` | `--libc=${LibC}` | `--os=${OS}`
interface WorkspaceConfig {
cpu?: CPU[]
libc?: LibC[]
os?: OS[]
}
type Installed = string[]
type Skipped = string[]
type Case = [
CLIOption[],
WorkspaceConfig | undefined,
Installed,
Skipped
]
const TEST_CASES: Case[] = [
[[], undefined, [
'only-linux-x64-glibc',
'only-linux-x64-musl',
], [
'only-darwin-arm64',
'only-darwin-x64',
'only-linux-arm64-glibc',
'only-linux-arm64-musl',
'only-win32-arm64',
'only-win32-x64',
]],
[[], {
os: ['win32'],
cpu: ['arm64', 'x64'],
}, [
'only-win32-arm64',
'only-win32-x64',
], [
'only-darwin-arm64',
'only-darwin-x64',
'only-linux-arm64-glibc',
'only-linux-arm64-musl',
'only-linux-x64-glibc',
'only-linux-x64-musl',
]],
[[
'--os=darwin',
'--cpu=arm64',
'--cpu=x64',
], undefined, [
'only-darwin-arm64',
'only-darwin-x64',
], [
'only-linux-arm64-glibc',
'only-linux-arm64-musl',
'only-linux-x64-glibc',
'only-linux-x64-musl',
'only-win32-arm64',
'only-win32-x64',
]],
[[
'--os=darwin',
'--cpu=arm64',
'--cpu=x64',
], {
os: ['win32'],
cpu: ['arm64', 'x64'],
}, [
'only-darwin-arm64',
'only-darwin-x64',
], [
'only-linux-arm64-glibc',
'only-linux-arm64-musl',
'only-linux-x64-glibc',
'only-linux-x64-musl',
'only-win32-arm64',
'only-win32-x64',
]],
]
describeOnLinuxOnly('install with supportedArchitectures from CLI options and manifest.pnpm', () => {
test.each(TEST_CASES)('%j on %j', async (cliOpts, workspaceConfig, installed, skipped) => {
prepare({
dependencies: {
'@pnpm.e2e/support-different-architectures': '1.0.0',
},
})
writeYamlFile('pnpm-workspace.yaml', {
supportedArchitectures: workspaceConfig,
} as WorkspaceManifest)
await execPnpm([
'install',
'--reporter=append-only',
...cliOpts,
])
const modulesManifest = await readModulesManifest('node_modules')
expect(Object.keys(modulesManifest?.hoistedDependencies ?? {}).sort()).toStrictEqual(installed.map(name => `@pnpm.e2e/${name}@1.0.0`))
expect(modulesManifest?.skipped.sort()).toStrictEqual(skipped.map(name => `@pnpm.e2e/${name}@1.0.0`))
expect(fs.readdirSync('node_modules/.pnpm/node_modules/@pnpm.e2e/')).toStrictEqual(installed)
})
})
describeOnLinuxOnly('add with supportedArchitectures from CLI options and manifest.pnpm', () => {
test.each(TEST_CASES)('%j on %j', async (cliOpts, workspaceConfig, installed, skipped) => {
prepareEmpty()
writeYamlFile('pnpm-workspace.yaml', {
supportedArchitectures: workspaceConfig,
} as WorkspaceManifest)
await execPnpm([
'add',
'--reporter=append-only',
...cliOpts,
'@pnpm.e2e/support-different-architectures',
])
const modulesManifest = await readModulesManifest('node_modules')
expect(Object.keys(modulesManifest?.hoistedDependencies ?? {}).sort()).toStrictEqual(installed.map(name => `@pnpm.e2e/${name}@1.0.0`))
expect(modulesManifest?.skipped.sort()).toStrictEqual(skipped.map(name => `@pnpm.e2e/${name}@1.0.0`))
expect(fs.readdirSync('node_modules/.pnpm/node_modules/@pnpm.e2e/')).toStrictEqual(installed)
})
})

View File

@@ -4,7 +4,10 @@ import { dlx } from '@pnpm/plugin-commands-script-runners'
import { prepareEmpty } from '@pnpm/prepare'
import { cleanExpiredDlxCache, cleanOrphans } from './cleanExpiredDlxCache'
const createCacheKey = (...pkgs: string[]): string => dlx.createCacheKey(pkgs, { default: 'https://registry.npmjs.com/' })
const createCacheKey = (...packages: string[]): string => dlx.createCacheKey({
packages,
registries: { default: 'https://registry.npmjs.com/' },
})
function createSampleDlxCacheLinkTarget (dirPath: string): void {
fs.mkdirSync(path.join(dirPath, 'node_modules', '.pnpm'), { recursive: true })

View File

@@ -12,7 +12,10 @@ import execa from 'execa'
const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}/`
const pnpmBin = path.join(__dirname, '../../../pnpm/bin/pnpm.cjs')
const createCacheKey = (...pkgs: string[]): string => dlx.createCacheKey(pkgs, { default: REGISTRY })
const createCacheKey = (...packages: string[]): string => dlx.createCacheKey({
packages,
registries: { default: REGISTRY },
})
test('remove unreferenced packages', async () => {
const project = prepare()