Files
pnpm/pnpm11/resolving/default-resolver/test/customResolver.ts
Zoltan Kochan 4edeef9176 style(ts): port the perfectionist lint rules to ESLint (#16415)
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.

The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.

The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.

Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
  the injected-deps syncer, and were dropped from writeWantedLockfile's
  returned lockfile

The CI chunk runner learns to run the node:test suite of the ESLint
config package.
2026-09-30 15:10:08 +02:00

488 lines
16 KiB
TypeScript

/// <reference path="../../../__typings__/index.d.ts"/>
import { expect, jest, test } from '@jest/globals'
import type { CustomResolver, WantedDependency } from '@pnpm/hooks.types'
import { createResolver } from '@pnpm/resolving.default-resolver'
test('custom resolver intercepts matching packages', async () => {
const customResolver: CustomResolver = {
canResolve: (wantedDependency: WantedDependency) => {
return wantedDependency.alias === 'test-package'
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the test resolver ignores its options
resolve: async (wantedDependency: WantedDependency, _opts: any) => {
return {
id: `custom:${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: {
type: 'directory',
directory: '/test/path',
},
}
},
}
const fetchFromRegistry = async (): Promise<Response> => new Response('')
const getAuthHeader = () => undefined
const { resolve } = createResolver(fetchFromRegistry, getAuthHeader, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
const result = await resolve(
{ alias: 'test-package', bareSpecifier: '1.0.0' },
{
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: {},
}
)
expect(result.id).toBe('custom:test-package@1.0.0')
expect(result.resolvedVia).toBe('custom-resolver')
})
test('custom resolver with synchronous methods', async () => {
const customResolver: CustomResolver = {
// Synchronous support check
canResolve: (wantedDependency: WantedDependency) => {
return wantedDependency.alias!.startsWith('@sync/')
},
// Synchronous resolution
resolve: (wantedDependency: WantedDependency) => {
return {
id: `sync:${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: {
tarball: `file://${wantedDependency.alias}-${wantedDependency.bareSpecifier}.tgz`,
integrity: 'sha512-test',
},
}
},
}
const fetchFromRegistry = async (): Promise<Response> => new Response('')
const getAuthHeader = () => undefined
const { resolve } = createResolver(fetchFromRegistry, getAuthHeader, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
const result = await resolve(
{ alias: '@sync/test', bareSpecifier: '2.0.0' },
{
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: {},
}
)
expect(result.id).toBe('sync:@sync/test@2.0.0')
expect(result.resolvedVia).toBe('custom-resolver')
})
test('multiple custom resolvers - first matching wins', async () => {
const resolver1: CustomResolver = {
canResolve: (wantedDependency) => wantedDependency.alias === 'shared-package',
resolve: () => ({
id: 'resolver-1:shared-package',
resolution: { tarball: 'file://resolver1.tgz', integrity: 'sha512-1' },
}),
}
const resolver2: CustomResolver = {
canResolve: (wantedDependency) => wantedDependency.alias === 'shared-package',
resolve: () => ({
id: 'resolver-2:shared-package',
resolution: { tarball: 'file://resolver2.tgz', integrity: 'sha512-2' },
}),
}
const fetchFromRegistry = async (): Promise<Response> => new Response('')
const getAuthHeader = () => undefined
const { resolve } = createResolver(fetchFromRegistry, getAuthHeader, {
customResolvers: [resolver1, resolver2], // Order matters
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
const result = await resolve(
{ alias: 'shared-package', bareSpecifier: '1.0.0' },
{
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: {},
}
)
// First custom resolver should win
expect(result.id).toBe('resolver-1:shared-package')
expect(result.resolvedVia).toBe('custom-resolver')
})
test('custom resolver error handling', async () => {
const customResolver: CustomResolver = {
canResolve: () => true,
resolve: () => {
throw new Error('Custom resolver failed')
},
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
await expect(resolve({ alias: 'any', bareSpecifier: '1.0.0' }, { lockfileDir: '/test', projectDir: '/test', preferredVersions: {} })).rejects.toThrow('Custom resolver failed')
})
test('preferredVersions are passed to custom resolver', async () => {
const resolve = jest.fn<NonNullable<CustomResolver['resolve']>>(() => ({
id: 'test@1.0.0',
resolution: { tarball: 'file://test.tgz', integrity: 'sha512-test' },
}))
const customResolver: CustomResolver = {
canResolve: () => true,
resolve,
}
const { resolve: resolvePackage } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
await resolvePackage(
{ alias: 'any', bareSpecifier: '1.0.0' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: { any: { '1.0.0': 'version' } } as unknown as Record<string, Record<string, 'version' | 'range' | 'tag'>> }
)
expect(resolve).toHaveBeenCalledWith(
{ alias: 'any', bareSpecifier: '1.0.0' },
expect.objectContaining({
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: { any: { '1.0.0': 'version' } },
})
)
})
test('custom resolver can intercept any protocol', async () => {
const customResolver: CustomResolver = {
canResolve: (wantedDependency: WantedDependency) => {
return wantedDependency.alias!.startsWith('custom-')
},
resolve: (wantedDependency: WantedDependency) => ({
id: `custom-handled:${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: {
type: 'custom:test',
directory: `/custom/${wantedDependency.alias}`,
},
}),
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
const result = await resolve(
{ alias: 'custom-package', bareSpecifier: 'file:../some-path' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
expect(result.resolvedVia).toBe('custom-resolver')
expect(result.id).toBe('custom-handled:custom-package@file:../some-path')
})
test('custom resolver falls through when not supported', async () => {
const customResolver: CustomResolver = {
canResolve: (wantedDependency: WantedDependency) => {
return wantedDependency.alias!.startsWith('custom-')
},
resolve: (wantedDependency: WantedDependency) => ({
id: `custom:${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: { type: 'custom:test', directory: '/custom' },
}),
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
await expect(
resolve(
{ alias: 'regular-package', bareSpecifier: 'file:../nonexistent' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
).rejects.toThrow()
})
test('custom resolver can override npm registry resolution', async () => {
const npmStyleResolver: CustomResolver = {
canResolve: (wantedDependency) => {
return !wantedDependency.bareSpecifier!.includes(':')
},
resolve: (wantedDependency) => ({
id: `custom-registry:${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: {
tarball: `https://custom-registry.com/${wantedDependency.alias}/-/${wantedDependency.alias}-${wantedDependency.bareSpecifier}.tgz`,
integrity: 'sha512-custom',
},
}),
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [npmStyleResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
storeDir: '.store',
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
const result = await resolve(
{ alias: 'express', bareSpecifier: '^4.0.0' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
expect(result.resolvedVia).toBe('custom-resolver')
expect('tarball' in result.resolution && result.resolution.tarball).toContain('custom-registry.com')
})
// Fetch phase custom fetcher tests - showing complete fetcher replacements
test('custom custom fetcher: reuse local tarball fetcher', async () => {
// This demonstrates how a custom resolver can reuse pnpm's local tarball fetcher
// for a custom protocol like "company-local:package-name"
const localTarballResolver: CustomResolver = {
canResolve: (wantedDependency) => wantedDependency.alias!.startsWith('company-local:'),
resolve: (wantedDependency) => {
const actualName = wantedDependency.alias!.replace('company-local:', '')
return {
id: wantedDependency.alias!,
resolution: {
type: 'custom:local',
localPath: `/company/tarballs/${actualName}-${wantedDependency.bareSpecifier}.tgz`,
},
}
},
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [localTarballResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
registriesByScope: { default: 'https://registry.npmjs.org/' },
storeDir: '.store',
})
const result = await resolve(
{ alias: 'company-local:my-package', bareSpecifier: '1.0.0' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
expect(result.resolvedVia).toBe('custom-resolver')
expect(result.resolution).toHaveProperty('type', 'custom:local')
})
test('custom custom fetcher: reuse remote tarball downloader', async () => {
// This demonstrates fetching from a custom CDN using pnpm's download utilities
// for a custom protocol like "cdn:package-name"
const cdnResolver: CustomResolver = {
canResolve: (wantedDependency) => wantedDependency.alias!.startsWith('cdn:'),
resolve: (wantedDependency) => {
const actualName = wantedDependency.alias!.replace('cdn:', '')
return {
id: wantedDependency.alias!,
resolution: {
type: 'custom:cdn',
cdnUrl: `https://cdn.example.com/packages/${actualName}/${wantedDependency.bareSpecifier}/${actualName}-${wantedDependency.bareSpecifier}.tgz`,
},
}
},
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [cdnResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
registriesByScope: { default: 'https://registry.npmjs.org/' },
storeDir: '.store',
})
const result = await resolve(
{ alias: 'cdn:awesome-lib', bareSpecifier: '2.0.0' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
expect(result.resolvedVia).toBe('custom-resolver')
expect(result.resolution).toHaveProperty('type', 'custom:cdn')
})
test('custom custom fetcher: wrap npm registry with custom logic', async () => {
// This demonstrates wrapping/enhancing standard npm registry resolution and fetching
// for a protocol like "private-npm:package-name" that uses private registry
const privateNpmResolver: CustomResolver = {
canResolve: (wantedDependency) => wantedDependency.alias!.startsWith('private-npm:'),
resolve: async (wantedDependency, _opts) => {
const actualName = wantedDependency.alias!.replace('private-npm:', '')
// In a real implementation, you'd fetch from your private registry here
// For this test, we mock the registry response
return {
id: `private-npm:${actualName}@${wantedDependency.bareSpecifier}`,
resolution: {
tarball: `https://private-registry.company.com/${actualName}/-/${actualName}-${wantedDependency.bareSpecifier}.tgz`,
integrity: 'sha512-mock-integrity',
registry: 'https://private-registry.company.com/',
},
}
},
}
const { resolve } = createResolver(async () => new Response(''), () => undefined, {
customResolvers: [privateNpmResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
registriesByScope: { default: 'https://registry.npmjs.org/' },
storeDir: '.store',
})
const result = await resolve(
{ alias: 'private-npm:company-utils', bareSpecifier: '3.0.0' },
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
)
expect(result.resolvedVia).toBe('custom-resolver')
expect('tarball' in result.resolution && result.resolution.tarball).toContain('private-registry.company.com')
})
test('custom resolver receives currentPkg when provided', async () => {
let receivedCurrentPkg: unknown = null
const customResolver: CustomResolver = {
canResolve: (wantedDependency: WantedDependency) => {
return wantedDependency.alias === 'test-package'
},
resolve: async (wantedDependency: WantedDependency, opts) => {
receivedCurrentPkg = opts.currentPkg
// If currentPkg is provided, return existing resolution
if (opts.currentPkg) {
return {
id: opts.currentPkg.id,
resolution: opts.currentPkg.resolution,
}
}
return {
id: `custom:${wantedDependency.alias}@1.0.0`,
resolution: {
type: 'directory',
directory: '/test/path',
},
}
},
}
const fetchFromRegistry = async (): Promise<Response> => new Response('')
const getAuthHeader = () => undefined
const { resolve } = createResolver(fetchFromRegistry, getAuthHeader, {
customResolvers: [customResolver],
cacheDir: '/tmp/test-cache',
offline: false,
preferOffline: false,
retry: {},
timeout: 60000,
registriesByScope: { default: 'https://registry.npmjs.org/' },
})
// First call without currentPkg
const result1 = await resolve(
{ alias: 'test-package', bareSpecifier: '1.0.0' },
{
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: {},
}
)
expect(result1.id).toBe('custom:test-package@1.0.0')
expect(receivedCurrentPkg).toBeUndefined()
// Second call with currentPkg
const existingResolution = {
type: 'directory' as const,
directory: '/existing/path',
}
const result2 = await resolve(
{ alias: 'test-package', bareSpecifier: '1.0.0' },
{
lockfileDir: '/test',
projectDir: '/test',
preferredVersions: {},
currentPkg: {
id: 'existing:test-package@1.0.0' as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- a plain string stands in for the branded package id
resolution: existingResolution,
},
}
)
expect(receivedCurrentPkg).toBeTruthy()
expect((receivedCurrentPkg as any).id).toBe('existing:test-package@1.0.0') // eslint-disable-line @typescript-eslint/no-explicit-any -- the captured value is untyped test state
expect(result2.id).toBe('existing:test-package@1.0.0')
expect(result2.resolution).toBe(existingResolution)
})