mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-30 19:05:23 -04:00
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2
- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
`it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
instead of relying on `@types/jest` ambient declarations. Under the
new tsgo build, `import { jest } from '@jest/globals'` shadows the
ambient `jest` namespace, breaking `@types/jest`'s `declare var
describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
now import from it, and add `@types/node` to packages that need it
but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
no longer globally available.
* chore: fix remaining tsgo type-strictness errors
- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
toStrictEqual / toEqual; @jest/globals rejects the typed objects
(which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.
* chore: address Copilot review on #11332
- Move misplaced `@jest/globals` imports to the top import block in
checkEngine, run.ts, and workspace/root-finder tests where the
script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
in bins/linker, lockfile/fs, and resolving/local-resolver tests with
`await expect(x()).rejects.toMatchObject({...})`. The old pattern
swallowed an unrelated `throw` if the under-test call silently
succeeded, which would fail on the catch-block assertion with a
misleading message.
249 lines
9.2 KiB
TypeScript
249 lines
9.2 KiB
TypeScript
import path from 'node:path'
|
|
|
|
import { expect, test } from '@jest/globals'
|
|
import { resolveAndInstallConfigDeps } from '@pnpm/installing.env-installer'
|
|
import { createEnvLockfile, readEnvLockfile, writeEnvLockfile } from '@pnpm/lockfile.fs'
|
|
import { prepareEmpty } from '@pnpm/prepare'
|
|
import { getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
|
import { createTempStore } from '@pnpm/testing.temp-store'
|
|
import { loadJsonFileSync } from 'load-json-file'
|
|
|
|
const registry = `http://localhost:${REGISTRY_MOCK_PORT}/`
|
|
|
|
function createOpts () {
|
|
const { storeController, storeDir } = createTempStore()
|
|
return {
|
|
registries: { default: registry },
|
|
rootDir: process.cwd(),
|
|
cacheDir: path.resolve('cache'),
|
|
userConfig: {},
|
|
store: storeController,
|
|
storeDir,
|
|
}
|
|
}
|
|
|
|
test('resolves and installs config dep when no env lockfile exists', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// Simulate a config dep manually added to pnpm-workspace.yaml (clean specifier, no lockfile)
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
}, opts)
|
|
|
|
// Package should be installed
|
|
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(manifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(manifest.version).toBe('100.0.0')
|
|
|
|
// Env lockfile should be created with resolved info
|
|
const envLockfile = await readEnvLockfile(process.cwd())
|
|
expect(envLockfile).not.toBeNull()
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toStrictEqual({
|
|
specifier: '100.0.0',
|
|
version: '100.0.0',
|
|
})
|
|
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toStrictEqual({
|
|
resolution: {
|
|
integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0'),
|
|
},
|
|
})
|
|
})
|
|
|
|
test('resolves newly added config dep when env lockfile already has other deps', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// Pre-create env lockfile with one dep
|
|
const existingLockfile = createEnvLockfile()
|
|
existingLockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
|
|
specifier: '100.0.0',
|
|
version: '100.0.0',
|
|
}
|
|
existingLockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
|
|
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
|
|
}
|
|
existingLockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
|
|
await writeEnvLockfile(process.cwd(), existingLockfile)
|
|
|
|
// Now install with an additional dep
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
'@pnpm.e2e/bar': '100.0.0',
|
|
}, opts)
|
|
|
|
// Both packages should be installed
|
|
const fooManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(fooManifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(fooManifest.version).toBe('100.0.0')
|
|
|
|
const barManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/bar/package.json')
|
|
expect(barManifest.name).toBe('@pnpm.e2e/bar')
|
|
expect(barManifest.version).toBe('100.0.0')
|
|
|
|
// Env lockfile should have both deps
|
|
const envLockfile = await readEnvLockfile(process.cwd())
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toBeDefined()
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/bar']).toBeDefined()
|
|
})
|
|
|
|
test('skips resolution when all deps are already in env lockfile', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// Pre-create complete env lockfile
|
|
const lockfile = createEnvLockfile()
|
|
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
|
|
specifier: '100.0.0',
|
|
version: '100.0.0',
|
|
}
|
|
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
|
|
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
|
|
}
|
|
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
|
|
await writeEnvLockfile(process.cwd(), lockfile)
|
|
|
|
// Install should work without network (using lockfile data)
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
}, opts)
|
|
|
|
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(manifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(manifest.version).toBe('100.0.0')
|
|
})
|
|
|
|
test('re-resolves and reinstalls when config dep version changes in pnpm-workspace.yaml', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// Pre-create env lockfile with foo@100.0.0
|
|
const lockfile = createEnvLockfile()
|
|
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
|
|
specifier: '100.0.0',
|
|
version: '100.0.0',
|
|
}
|
|
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
|
|
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
|
|
}
|
|
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
|
|
await writeEnvLockfile(process.cwd(), lockfile)
|
|
|
|
// Install first with the old version
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
}, opts)
|
|
|
|
// Now simulate user changing the version in pnpm-workspace.yaml
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.1.0',
|
|
}, opts)
|
|
|
|
// The new version should be installed
|
|
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(manifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(manifest.version).toBe('100.1.0')
|
|
|
|
// Env lockfile should be updated with the new version
|
|
const envLockfile = await readEnvLockfile(process.cwd())
|
|
expect(envLockfile).not.toBeNull()
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toStrictEqual({
|
|
specifier: '100.1.0',
|
|
version: '100.1.0',
|
|
})
|
|
expect(envLockfile!.packages['@pnpm.e2e/foo@100.1.0']).toStrictEqual({
|
|
resolution: {
|
|
integrity: getIntegrity('@pnpm.e2e/foo', '100.1.0'),
|
|
},
|
|
})
|
|
// Old version should be cleaned up from the lockfile
|
|
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toBeUndefined()
|
|
})
|
|
|
|
test('handles old format config deps via migration path', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
|
|
}, opts)
|
|
|
|
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(manifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(manifest.version).toBe('100.0.0')
|
|
})
|
|
|
|
test('handles mixed old-format and new-format config deps together', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// One dep in old inline-integrity format, another as a clean specifier
|
|
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
|
|
'@pnpm.e2e/bar': '100.0.0',
|
|
}, opts)
|
|
|
|
// Both packages should be installed
|
|
const fooManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(fooManifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(fooManifest.version).toBe('100.0.0')
|
|
|
|
const barManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/bar/package.json')
|
|
expect(barManifest.name).toBe('@pnpm.e2e/bar')
|
|
expect(barManifest.version).toBe('100.0.0')
|
|
|
|
// Env lockfile should have both deps
|
|
const envLockfile = await readEnvLockfile(process.cwd())
|
|
expect(envLockfile).not.toBeNull()
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toBeDefined()
|
|
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/bar']).toBeDefined()
|
|
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toBeDefined()
|
|
expect(envLockfile!.packages['@pnpm.e2e/bar@100.0.0']).toBeDefined()
|
|
})
|
|
|
|
test('fails with frozenLockfile when old-format deps need migration', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
|
|
await expect(resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
|
|
}, { ...opts, frozenLockfile: true })).rejects.toThrow('Cannot update configDependencies with "frozen-lockfile"')
|
|
})
|
|
|
|
test('fails with frozenLockfile when new-format deps need resolution', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
await expect(resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
}, { ...opts, frozenLockfile: true })).rejects.toThrow('Cannot update configDependencies with "frozen-lockfile"')
|
|
})
|
|
|
|
test('succeeds with frozenLockfile when env lockfile is up-to-date', async () => {
|
|
prepareEmpty()
|
|
const opts = createOpts()
|
|
|
|
// Pre-create complete env lockfile
|
|
const lockfile = createEnvLockfile()
|
|
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
|
|
specifier: '100.0.0',
|
|
version: '100.0.0',
|
|
}
|
|
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
|
|
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
|
|
}
|
|
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
|
|
await writeEnvLockfile(process.cwd(), lockfile)
|
|
|
|
await resolveAndInstallConfigDeps({
|
|
'@pnpm.e2e/foo': '100.0.0',
|
|
}, { ...opts, frozenLockfile: true })
|
|
|
|
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
|
expect(manifest.name).toBe('@pnpm.e2e/foo')
|
|
expect(manifest.version).toBe('100.0.0')
|
|
})
|