mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-21 05:02:30 -04:00
Merge remote-tracking branch 'origin/main' into claude/speed-up-pnpm-compile-UZl1m
This commit is contained in:
6
.changeset/plenty-jokes-wave.md
Normal file
6
.changeset/plenty-jokes-wave.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/installing.deps-resolver": patch
|
||||
pnpm: patch
|
||||
---
|
||||
|
||||
Improved handling of non-string version selectors in an internal function (e.g. `hoistPeers`).
|
||||
5
.changeset/wide-hotels-study.md
Normal file
5
.changeset/wide-hotels-study.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/core": patch
|
||||
---
|
||||
|
||||
The `resolutionMode` option for `mutateModules` now defaults to `highest` to match the default in `@pnpm/config`. It previously defaulted to `lowest-direct`.
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -48,6 +48,8 @@ RELEASE.md
|
||||
.turbo
|
||||
.eslintcache
|
||||
.claude
|
||||
.local-settings
|
||||
.pnpm-typecheck.json
|
||||
|
||||
## custom modules-dir fixture
|
||||
__fixtures__/custom-modules-dir/**/fake_modules/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { findWorkspaceProjects } from '@pnpm/workspace.projects-reader'
|
||||
@@ -54,8 +55,14 @@ async function main (): Promise<void> {
|
||||
JSON.stringify(typeCheckTSConfig, undefined, 2)
|
||||
)
|
||||
|
||||
console.log('Running tsgo --build...')
|
||||
execa('tsgo', ['--build', '--singleThreaded', typeCheckDir], {
|
||||
const singleThreaded = resolveThreadingMode(repoRoot)
|
||||
const args = ['--build']
|
||||
if (singleThreaded) {
|
||||
args.push('--singleThreaded')
|
||||
}
|
||||
args.push(typeCheckDir)
|
||||
console.log(`Running tsgo --build${singleThreaded ? ' --singleThreaded' : ''}...`)
|
||||
execa('tsgo', args, {
|
||||
// The INIT_CWD variable is populated by package managers and points towards
|
||||
// the user's original working directory. It's more useful to run TypeScript
|
||||
// from the user's actual working directory so any type checking errors can
|
||||
@@ -72,6 +79,47 @@ async function main (): Promise<void> {
|
||||
console.log('Running tsgo build done')
|
||||
}
|
||||
|
||||
const AUTO_SINGLE_THREAD_MEMORY_THRESHOLD_GB = 8
|
||||
|
||||
function resolveThreadingMode (repoRoot: string): boolean {
|
||||
const { mode, source } = readThreadingMode(repoRoot)
|
||||
switch (mode) {
|
||||
case 'single-threaded':
|
||||
return true
|
||||
case 'multi-threaded':
|
||||
return false
|
||||
case 'auto':
|
||||
return os.totalmem() / (1024 ** 3) < AUTO_SINGLE_THREAD_MEMORY_THRESHOLD_GB
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid threading mode "${mode}" from ${source}. ` +
|
||||
'Valid values: auto, single-threaded, multi-threaded.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function readThreadingMode (repoRoot: string): { mode: string, source: string } {
|
||||
const envValue = process.env.PNPM_TYPECHECK_THREADING?.trim().toLowerCase()
|
||||
if (envValue) {
|
||||
return { mode: envValue, source: 'PNPM_TYPECHECK_THREADING env var' }
|
||||
}
|
||||
|
||||
for (const configPath of [
|
||||
path.join(repoRoot, '.local-settings', 'pnpm-typecheck.json'),
|
||||
path.join(repoRoot, '.pnpm-typecheck.json'),
|
||||
]) {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
const threading = typeof config.threading === 'string' ? config.threading.trim().toLowerCase() : ''
|
||||
if (threading) {
|
||||
return { mode: threading, source: configPath }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { mode: 'auto', source: 'default' }
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
if (error && typeof error === 'object' && 'exitCode' in error && 'shortMessage' in error) {
|
||||
process.exit(error.exitCode as number)
|
||||
|
||||
@@ -69,15 +69,17 @@ if (/^\d+$/.test(arg)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Symlink .claude into the new worktree, pointing at the bare repo's git common
|
||||
// dir so all worktrees share the same Claude Code settings and approved commands.
|
||||
// Symlink shared directories into the new worktree, pointing at the bare repo's
|
||||
// git common dir so all worktrees share the same settings without duplication.
|
||||
const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8', cwd: repoRoot }).trim()
|
||||
const sharedClaudeDir = path.resolve(repoRoot, gitCommonDir, '.claude')
|
||||
const newClaudeDir = path.join(worktreePath, '.claude')
|
||||
fs.mkdirSync(sharedClaudeDir, { recursive: true })
|
||||
if (!fs.existsSync(newClaudeDir)) {
|
||||
// 'junction' works without elevated privileges on Windows; ignored on Unix
|
||||
fs.symlinkSync(sharedClaudeDir, newClaudeDir, 'junction')
|
||||
for (const dir of ['.claude', '.local-settings']) {
|
||||
const sharedDir = path.resolve(repoRoot, gitCommonDir, dir)
|
||||
const newDir = path.join(worktreePath, dir)
|
||||
fs.mkdirSync(sharedDir, { recursive: true })
|
||||
if (!fs.existsSync(newDir)) {
|
||||
// 'junction' works without elevated privileges on Windows; ignored on Unix
|
||||
fs.symlinkSync(sharedDir, newDir, 'junction')
|
||||
}
|
||||
}
|
||||
|
||||
// Print path to stdout — allows: cd $(pnpm worktree:new <arg>)
|
||||
|
||||
@@ -165,6 +165,12 @@ export async function installDeps (
|
||||
ignoreFilteredInstallCache: true,
|
||||
})
|
||||
if (upToDate) {
|
||||
if (opts.hooks?.customResolvers?.some(r => r.shouldRefreshResolution)) {
|
||||
logger.warn({
|
||||
message: 'shouldRefreshResolution hooks were skipped because optimisticRepeatInstall is enabled.',
|
||||
prefix: opts.dir,
|
||||
})
|
||||
}
|
||||
globalInfo('Already up to date')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ const defaults = (opts: InstallOptions): StrictInstallOptions => {
|
||||
pruneStore: false,
|
||||
rawConfig: {},
|
||||
registries: DEFAULT_REGISTRIES,
|
||||
resolutionMode: 'lowest-direct',
|
||||
resolutionMode: 'highest',
|
||||
saveWorkspaceProtocol: 'rolling',
|
||||
scriptsPrependNodePath: false,
|
||||
shamefullyHoist: false,
|
||||
|
||||
@@ -178,13 +178,13 @@ test('lockfile contains catalog snapshots', async () => {
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'is-positive': 'catalog:',
|
||||
'@pnpm.e2e/bar': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
dependencies: {
|
||||
'is-negative': 'catalog:',
|
||||
'@pnpm.e2e/foo': 'catalog:',
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -194,8 +194,8 @@ test('lockfile contains catalog snapshots', async () => {
|
||||
lockfileOnly: true,
|
||||
catalogs: {
|
||||
default: {
|
||||
'is-positive': '^1.0.0',
|
||||
'is-negative': '^1.0.0',
|
||||
'@pnpm.e2e/bar': '^100.0.0',
|
||||
'@pnpm.e2e/foo': '^100.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -203,8 +203,8 @@ test('lockfile contains catalog snapshots', async () => {
|
||||
const lockfile = readLockfile()
|
||||
expect(lockfile.catalogs).toStrictEqual({
|
||||
default: {
|
||||
'is-positive': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'is-negative': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'@pnpm.e2e/bar': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
'@pnpm.e2e/foo': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -306,13 +306,13 @@ test('lockfile catalog snapshots retain existing entries on --filter', async ()
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'is-negative': 'catalog:',
|
||||
'@pnpm.e2e/bar': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
dependencies: {
|
||||
'is-positive': 'catalog:',
|
||||
'@pnpm.e2e/foo': 'catalog:',
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -322,16 +322,16 @@ test('lockfile catalog snapshots retain existing entries on --filter', async ()
|
||||
lockfileOnly: true,
|
||||
catalogs: {
|
||||
default: {
|
||||
'is-positive': '^1.0.0',
|
||||
'is-negative': '^1.0.0',
|
||||
'@pnpm.e2e/bar': '^100.0.0',
|
||||
'@pnpm.e2e/foo': '^1.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(readLockfile().catalogs).toStrictEqual({
|
||||
default: {
|
||||
'is-negative': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'is-positive': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'@pnpm.e2e/bar': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
'@pnpm.e2e/foo': { specifier: '^1.0.0', version: '1.3.0' },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -341,19 +341,19 @@ test('lockfile catalog snapshots retain existing entries on --filter', async ()
|
||||
lockfileOnly: true,
|
||||
catalogs: {
|
||||
default: {
|
||||
'is-positive': '=3.1.0',
|
||||
'is-negative': '^1.0.0',
|
||||
'@pnpm.e2e/bar': '^100.0.0',
|
||||
'@pnpm.e2e/foo': '=100.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(readLockfile().catalogs).toStrictEqual({
|
||||
default: {
|
||||
// The is-negative snapshot should be carried from the previous install,
|
||||
// The @pnpm.e2e/bar snapshot should be carried from the previous install,
|
||||
// despite the current filtered install not using it.
|
||||
'is-negative': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'@pnpm.e2e/bar': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
|
||||
'is-positive': { specifier: '=3.1.0', version: '3.1.0' },
|
||||
'@pnpm.e2e/foo': { specifier: '=100.0.0', version: '100.0.0' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -513,28 +513,28 @@ test('--fix-lockfile with --filter does not erase catalog snapshots', async () =
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'is-negative': 'catalog:',
|
||||
'@pnpm.e2e/bar': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
dependencies: {
|
||||
'is-positive': 'catalog:',
|
||||
'@pnpm.e2e/foo': 'catalog:',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const catalogs = {
|
||||
default: {
|
||||
'is-positive': '^1.0.0',
|
||||
'is-negative': '^1.0.0',
|
||||
'@pnpm.e2e/bar': '^100.0.0',
|
||||
'@pnpm.e2e/foo': '^100.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
const expectedCatalogsSnapshot: CatalogSnapshots = {
|
||||
default: {
|
||||
'is-negative': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'is-positive': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'@pnpm.e2e/bar': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
'@pnpm.e2e/foo': { specifier: '^100.0.0', version: '100.1.0' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,7 @@ describe('add', () => {
|
||||
])
|
||||
|
||||
const catalogs = {
|
||||
default: { '@pnpm.e2e/foo': '^100.0.0' },
|
||||
default: { '@pnpm.e2e/foo': '^100.1.0' },
|
||||
}
|
||||
|
||||
await mutateModules(installProjects(projects), {
|
||||
@@ -1238,12 +1238,12 @@ describe('add', () => {
|
||||
|
||||
// Sanity check that the rest of the lockfile has expected contents.
|
||||
expect(readLockfile()).toMatchObject({
|
||||
catalogs: { default: { '@pnpm.e2e/foo': { specifier: '^100.0.0', version: '100.0.0' } } },
|
||||
catalogs: { default: { '@pnpm.e2e/foo': { specifier: '^100.1.0', version: '100.1.0' } } },
|
||||
importers: {
|
||||
project1: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.0.0' } } },
|
||||
project2: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.0.0' } } },
|
||||
project1: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.1.0' } } },
|
||||
project2: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.1.0' } } },
|
||||
},
|
||||
packages: { '@pnpm.e2e/foo@100.0.0': expect.any(Object) },
|
||||
packages: { '@pnpm.e2e/foo@100.1.0': expect.any(Object) },
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ test('fix broken lockfile with --fix-lockfile', async () => {
|
||||
writeYamlFileSync(WANTED_LOCKFILE, {
|
||||
dependencies: {
|
||||
'@types/semver': {
|
||||
specifier: '^5.3.31',
|
||||
specifier: '5.3.31',
|
||||
version: '5.3.31',
|
||||
},
|
||||
},
|
||||
devDependencies: {
|
||||
fsevents: {
|
||||
specifier: '^2.3.2',
|
||||
specifier: '2.3.2',
|
||||
version: '2.3.2',
|
||||
},
|
||||
},
|
||||
@@ -44,10 +44,10 @@ test('fix broken lockfile with --fix-lockfile', async () => {
|
||||
|
||||
await install({
|
||||
dependencies: {
|
||||
'@types/semver': '^5.3.31',
|
||||
'@types/semver': '5.3.31',
|
||||
},
|
||||
devDependencies: {
|
||||
'core-js-pure': '^3.16.2',
|
||||
'core-js-pure': '3.16.2',
|
||||
},
|
||||
}, testDefaults({ fixLockfile: true }))
|
||||
|
||||
|
||||
@@ -1802,7 +1802,7 @@ test('injected local packages are deduped', async () => {
|
||||
'project-1': 'workspace:1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'is-positive': '2.0.0',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
'project-1': {
|
||||
@@ -1954,9 +1954,9 @@ test('injected local packages are deduped', async () => {
|
||||
},
|
||||
})
|
||||
expect(lockfile.packages['project-1@file:project-1(is-positive@1.0.0)']).toBeFalsy()
|
||||
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
|
||||
expect(lockfile.snapshots['project-2@file:project-2(is-positive@1.0.0)']).toEqual({
|
||||
dependencies: {
|
||||
'project-1': 'file:project-1(is-positive@2.0.0)',
|
||||
'project-1': 'file:project-1(is-positive@1.0.0)',
|
||||
},
|
||||
transitivePeerDependencies: ['is-positive'],
|
||||
})
|
||||
|
||||
@@ -1244,6 +1244,7 @@ packages:
|
||||
|
||||
test('a lockfile v6 with merge conflicts is autofixed', async () => {
|
||||
const project = prepareEmpty()
|
||||
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
|
||||
|
||||
fs.writeFileSync(WANTED_LOCKFILE, `\
|
||||
lockfileVersion: '${LOCKFILE_VERSION}'
|
||||
|
||||
@@ -29,7 +29,8 @@ export function hoistPeers (
|
||||
if (opts.allPreferredVersions![peerName]) {
|
||||
const versions: string[] = []
|
||||
const nonVersions: string[] = []
|
||||
for (const [spec, specType] of Object.entries(opts.allPreferredVersions![peerName])) {
|
||||
for (const [spec, selector] of Object.entries(opts.allPreferredVersions![peerName])) {
|
||||
const specType = typeof selector === 'string' ? selector : selector.selectorType
|
||||
if (specType === 'version') {
|
||||
versions.push(spec)
|
||||
} else {
|
||||
|
||||
@@ -96,6 +96,21 @@ test('hoistPeers handles workspace: protocol range without throwing', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/pnpm/pnpm/pull/11048
|
||||
test('hoistPeers handles version selector with weight', () => {
|
||||
expect(hoistPeers({
|
||||
autoInstallPeers: true,
|
||||
allPreferredVersions: {
|
||||
foo: {
|
||||
'1.0.0': { selectorType: 'version', weight: 1 },
|
||||
},
|
||||
},
|
||||
workspaceRootDeps: [],
|
||||
}, [['foo', { range: '1' }]])).toStrictEqual({
|
||||
foo: '1.0.0',
|
||||
})
|
||||
})
|
||||
|
||||
test('getHoistableOptionalPeers only picks a version that satisfies all optional ranges', () => {
|
||||
expect(getHoistableOptionalPeers({
|
||||
foo: ['2', '2.1'],
|
||||
|
||||
@@ -899,7 +899,7 @@ test('deploy with a shared lockfile that has peer dependencies suffix in workspa
|
||||
'project-2': 'workspace:*',
|
||||
},
|
||||
peerDependencies: {
|
||||
'is-negative': '>=1.0.0',
|
||||
'is-negative': '1.0.0',
|
||||
'project-2': '*',
|
||||
},
|
||||
},
|
||||
@@ -907,7 +907,7 @@ test('deploy with a shared lockfile that has peer dependencies suffix in workspa
|
||||
name: 'project-2',
|
||||
version: '0.0.0',
|
||||
peerDependencies: {
|
||||
'is-positive': '>=1.0.0',
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user