Files
pnpm/installing/env-installer/test/installConfigDeps.ts
Marvin Hagemeister 49e6074644 test: replace @pnpm/registry-mock with an in-repo in-process registry (#11927)
Replace the external `@pnpm/registry-mock` (Verdaccio) test dependency with an in-repo, in-process registry that serves package fixtures to **both** the pacquet Rust tests and the pnpm CLI (Jest) tests. No separately managed registry process is needed.

### How it works

- **Fixtures** live at `registry/.fixtures/packages/<name>/<version>/…`, moved verbatim from [`pnpm/registry-mock`](https://github.com/pnpm/registry-mock) (keyed by each `package.json`'s `name`+`version`).
- **`pnpm-registry-fixtures`** builds verdaccio-shaped storage from those fixtures; the in-tree **`pnpm-registry`** crate serves it.
  - Files whose names differ only by case (`@pnpm.e2e/with-same-file-in-different-cases`) and `bundleDependencies` trees are composed **in memory** by the builder, since neither can be committed to the working tree.
- **pacquet**: `pacquet-testing-utils`' `TestRegistry` starts the server lazily (once per process) in proxy mode, serving `@pnpm.e2e` fixtures locally and falling through to the npm uplink for real packages (`is-positive`, `is-negative`, …) — matching how registry-mock behaved.
- **pnpm CLI**: the `with-registry` Jest `globalSetup` builds storage from the fixtures via the new `pnpm-registry-prepare` binary (built from source in the Test CI job) and serves it with `pnpm-registry`. `REGISTRY_MOCK_PORT` / `REGISTRY_MOCK_CREDENTIALS` / `getIntegrity` now come from `@pnpm/testing.registry-mock`.

### Result

`@pnpm/registry-mock` is removed from every manifest, the catalog, and `packageExtensions`; `cargo test` / `cargo nextest run` / `just test` and the pnpm CLI Jest suites all run registry-backed tests without launching Verdaccio.
2026-05-29 14:35:45 +02:00

381 lines
14 KiB
TypeScript

import fs from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { expect, test } from '@jest/globals'
import { installConfigDeps } from '@pnpm/installing.env-installer'
import { createEnvLockfile, type EnvLockfile, readEnvLockfile } from '@pnpm/lockfile.fs'
import { prepareEmpty } from '@pnpm/prepare'
import { getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/testing.registry-mock'
import { createTempStore } from '@pnpm/testing.temp-store'
import { rimraf } from '@zkochan/rimraf'
import { loadJsonFileSync } from 'load-json-file'
const registry = `http://localhost:${REGISTRY_MOCK_PORT}/`
function makeEnvLockfile (deps: Record<string, { version: string, integrity: string }>): EnvLockfile {
const lockfile = createEnvLockfile()
for (const [name, { version, integrity }] of Object.entries(deps)) {
const pkgKey = `${name}@${version}`
lockfile.importers['.'].configDependencies[name] = { specifier: version, version }
lockfile.packages[pkgKey] = { resolution: { integrity } }
lockfile.snapshots[pkgKey] = {}
}
return lockfile
}
test('configuration dependency is installed from env lockfile', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const lockfile = makeEnvLockfile({
'@pnpm.e2e/foo': { version: '100.0.0', integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
})
await installConfigDeps(lockfile, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
{
const configDepManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
expect(configDepManifest.version).toBe('100.0.0')
// The local path should be a symlink to the global virtual store
expect(fs.lstatSync('node_modules/.pnpm-config/@pnpm.e2e/foo').isSymbolicLink()).toBe(true)
}
// Dependency is updated
const lockfile2 = makeEnvLockfile({
'@pnpm.e2e/foo': { version: '100.1.0', integrity: getIntegrity('@pnpm.e2e/foo', '100.1.0') },
})
await installConfigDeps(lockfile2, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
{
const configDepManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
expect(configDepManifest.version).toBe('100.1.0')
}
// Dependency is removed
const lockfile3 = createEnvLockfile()
await installConfigDeps(lockfile3, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
expect(fs.existsSync('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')).toBeFalsy()
})
test('optional subdep matching the current platform is installed and symlinked next to parent', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const parentName = '@pnpm.e2e/foo'
const parentVersion = '100.0.0'
const subdepName = '@pnpm.e2e/bar'
const subdepVersion = '100.0.0'
const lockfile = createEnvLockfile()
const parentKey = `${parentName}@${parentVersion}`
lockfile.importers['.'].configDependencies[parentName] = { specifier: parentVersion, version: parentVersion }
lockfile.packages[parentKey] = { resolution: { integrity: getIntegrity(parentName, parentVersion) } }
lockfile.snapshots[parentKey] = {
optionalDependencies: { [subdepName]: subdepVersion },
}
lockfile.packages[`${subdepName}@${subdepVersion}`] = {
resolution: { integrity: getIntegrity(subdepName, subdepVersion) },
os: [process.platform],
cpu: [process.arch],
}
await installConfigDeps(lockfile, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
expect(fs.existsSync(`node_modules/.pnpm-config/${parentName}/package.json`)).toBe(true)
// Node-style resolution from inside the parent must find the sibling subdep.
const parentRealPath = fs.realpathSync(`node_modules/.pnpm-config/${parentName}`)
const requireFromParent = createRequire(path.join(parentRealPath, 'package.json'))
const siblingPkgJsonPath = requireFromParent.resolve(`${subdepName}/package.json`)
const siblingManifest = loadJsonFileSync<{ name: string, version: string }>(siblingPkgJsonPath)
expect(siblingManifest.name).toBe(subdepName)
expect(siblingManifest.version).toBe(subdepVersion)
})
test('changing only an optional subdep version re-installs and re-symlinks the parent', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const parentName = '@pnpm.e2e/foo'
const parentVersion = '100.0.0'
const subdepName = '@pnpm.e2e/bar'
function buildLockfile (subdepVersion: string): EnvLockfile {
const lockfile = createEnvLockfile()
const parentKey = `${parentName}@${parentVersion}`
lockfile.importers['.'].configDependencies[parentName] = { specifier: parentVersion, version: parentVersion }
lockfile.packages[parentKey] = { resolution: { integrity: getIntegrity(parentName, parentVersion) } }
lockfile.snapshots[parentKey] = { optionalDependencies: { [subdepName]: subdepVersion } }
lockfile.packages[`${subdepName}@${subdepVersion}`] = {
resolution: { integrity: getIntegrity(subdepName, subdepVersion) },
os: [process.platform],
cpu: [process.arch],
}
return lockfile
}
const installOpts = {
registries: { default: registry },
rootDir: process.cwd(),
store: storeController,
storeDir,
}
await installConfigDeps(buildLockfile('100.0.0'), installOpts)
const requireBefore = createRequire(path.join(fs.realpathSync(`node_modules/.pnpm-config/${parentName}`), 'package.json'))
expect(loadJsonFileSync<{ version: string }>(requireBefore.resolve(`${subdepName}/package.json`)).version).toBe('100.0.0')
await installConfigDeps(buildLockfile('100.1.0'), installOpts)
const requireAfter = createRequire(path.join(fs.realpathSync(`node_modules/.pnpm-config/${parentName}`), 'package.json'))
expect(loadJsonFileSync<{ version: string }>(requireAfter.resolve(`${subdepName}/package.json`)).version).toBe('100.1.0')
})
test('optional subdep that does not match the current platform is skipped', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const parentName = '@pnpm.e2e/foo'
const parentVersion = '100.0.0'
const subdepName = '@pnpm.e2e/bar'
const subdepVersion = '100.0.0'
const lockfile = createEnvLockfile()
const parentKey = `${parentName}@${parentVersion}`
lockfile.importers['.'].configDependencies[parentName] = { specifier: parentVersion, version: parentVersion }
lockfile.packages[parentKey] = { resolution: { integrity: getIntegrity(parentName, parentVersion) } }
lockfile.snapshots[parentKey] = {
optionalDependencies: { [subdepName]: subdepVersion },
}
lockfile.packages[`${subdepName}@${subdepVersion}`] = {
resolution: { integrity: getIntegrity(subdepName, subdepVersion) },
os: ['this-os-does-not-exist'],
}
await installConfigDeps(lockfile, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
const parentRealPath = fs.realpathSync(`node_modules/.pnpm-config/${parentName}`)
const requireFromParent = createRequire(path.join(parentRealPath, 'package.json'))
expect(() => requireFromParent.resolve(`${subdepName}/package.json`)).toThrow(/Cannot find/)
})
test('re-installs sibling symlinks even when the parent symlink is already correct', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const parentName = '@pnpm.e2e/foo'
const parentVersion = '100.0.0'
const subdepName = '@pnpm.e2e/bar'
const subdepVersion = '100.0.0'
const lockfile = createEnvLockfile()
const parentKey = `${parentName}@${parentVersion}`
lockfile.importers['.'].configDependencies[parentName] = { specifier: parentVersion, version: parentVersion }
lockfile.packages[parentKey] = { resolution: { integrity: getIntegrity(parentName, parentVersion) } }
lockfile.snapshots[parentKey] = { optionalDependencies: { [subdepName]: subdepVersion } }
lockfile.packages[`${subdepName}@${subdepVersion}`] = {
resolution: { integrity: getIntegrity(subdepName, subdepVersion) },
os: [process.platform],
cpu: [process.arch],
}
const installOpts = {
registries: { default: registry },
rootDir: process.cwd(),
store: storeController,
storeDir,
}
// First install — parent + subdep symlink land in the GVS leaf.
await installConfigDeps(lockfile, installOpts)
const parentRealPath = fs.realpathSync(`node_modules/.pnpm-config/${parentName}`)
const subdepSiblingPath = path.join(path.dirname(path.dirname(parentRealPath)), subdepName)
expect(fs.existsSync(`${subdepSiblingPath}/package.json`)).toBe(true)
// Simulate stale state: remove the subdep sibling symlink. The parent's
// .pnpm-config symlink still points at the expected leaf, so the realpath
// skip-check passes. installOptionalSubdeps must still run to repair.
await rimraf(subdepSiblingPath)
expect(fs.existsSync(subdepSiblingPath)).toBe(false)
// Second install with the same lockfile.
await installConfigDeps(lockfile, installOpts)
expect(fs.existsSync(`${subdepSiblingPath}/package.json`)).toBe(true)
})
test('optional subdep that does not match the current cpu is skipped', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const parentName = '@pnpm.e2e/foo'
const parentVersion = '100.0.0'
const subdepName = '@pnpm.e2e/bar'
const subdepVersion = '100.0.0'
const lockfile = createEnvLockfile()
const parentKey = `${parentName}@${parentVersion}`
lockfile.importers['.'].configDependencies[parentName] = { specifier: parentVersion, version: parentVersion }
lockfile.packages[parentKey] = { resolution: { integrity: getIntegrity(parentName, parentVersion) } }
lockfile.snapshots[parentKey] = {
optionalDependencies: { [subdepName]: subdepVersion },
}
lockfile.packages[`${subdepName}@${subdepVersion}`] = {
resolution: { integrity: getIntegrity(subdepName, subdepVersion) },
cpu: ['this-cpu-does-not-exist'],
}
await installConfigDeps(lockfile, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
const parentRealPath = fs.realpathSync(`node_modules/.pnpm-config/${parentName}`)
const requireFromParent = createRequire(path.join(parentRealPath, 'package.json'))
expect(() => requireFromParent.resolve(`${subdepName}/package.json`)).toThrow(/Cannot find/)
})
test('installation fails if the checksum of the config dependency is invalid', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore({
clientOptions: {
retry: {
retries: 0,
},
},
})
const lockfile = makeEnvLockfile({
'@pnpm.e2e/foo': {
version: '100.0.0',
integrity: 'sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==',
},
})
await expect(installConfigDeps(lockfile, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})).rejects.toThrow('Got unexpected checksum for')
})
test('migration: installs from old inline integrity format and creates env lockfile', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
// Old format: ConfigDependencies with inline integrity
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
const configDeps: Record<string, string> = {
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
}
await installConfigDeps(configDeps, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})
{
const configDepManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
expect(configDepManifest.version).toBe('100.0.0')
}
// Verify env lockfile was created with expected content in pnpm-lock.yaml
const envLockfile = (await readEnvLockfile(process.cwd()))!
expect(envLockfile.lockfileVersion).toBeDefined()
expect(envLockfile.importers['.'].configDependencies['@pnpm.e2e/foo']).toEqual({
specifier: '100.0.0',
version: '100.0.0',
})
expect((envLockfile.packages['@pnpm.e2e/foo@100.0.0'].resolution as { integrity: string }).integrity).toBe(integrity)
})
test('migration fails with frozenLockfile when no env lockfile exists', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore()
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
const configDeps: Record<string, string> = {
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
}
await expect(installConfigDeps(configDeps, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
frozenLockfile: true,
})).rejects.toThrow('Cannot migrate configDependencies with "frozen-lockfile"')
})
test('installation fails if the config dependency does not have a checksum (old format)', async () => {
prepareEmpty()
const { storeController, storeDir } = createTempStore({
clientOptions: {
retry: {
retries: 0,
},
},
})
const configDeps: Record<string, string> = {
'@pnpm.e2e/foo': '100.0.0',
}
await expect(installConfigDeps(configDeps, {
registries: {
default: registry,
},
rootDir: process.cwd(),
store: storeController,
storeDir,
})).rejects.toThrow('already in clean-specifier form')
})