mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-14 03:26:13 -04:00
fix: route <alias>:@scope/pkg to the named-registry resolver (#11598)
## Summary - The local resolver's path-shape match was claiming any specifier containing `/` as a local directory, so `pnpm add bit:@teambit/bit` (with `bit` configured under `namedRegistries`) installed a bogus link to `bit:@teambit/bit/` instead of resolving from the configured registry. - Split the local resolver into two exports: `resolveFromLocalScheme` (handles `file:`/`link:`/`workspace:`/`path:`) and `resolveFromLocalPath` (path-shape match — tarball extension, `path.sep`, `isFilespec`). `resolveFromLocal` is removed. - Re-order the default-resolver chain so the scheme pass runs *before* `resolveFromNamedRegistry` and the path pass runs *after*. Explicit local protocols still win even when a user configures a colliding `namedRegistries` alias; named-registry aliases reach their configured URL. Repro before the fix: ``` $ cat pnpm-workspace.yaml namedRegistries: bit: https://node-registry.bit.cloud/ $ pnpm add bit:@teambit/bit [WARN] Installing a dependency from a non-existent directory: /private/tmp/.../bit:@teambit/bit dependencies: + bit 0.0.0 <- bit:@teambit/bit ``` After the fix, the same command resolves `@teambit/bit 1.13.173` from `https://node-registry.bit.cloud/` and writes `"@teambit/bit": "bit:^1.13.173"` to `package.json`.
This commit is contained in:
6
.changeset/fix-named-registry-vs-local-resolver.md
Normal file
6
.changeset/fix-named-registry-vs-local-resolver.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolving.default-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed `pnpm add <alias>:@scope/pkg` for [named registries](https://github.com/pnpm/pnpm/pull/11324). The local resolver was claiming any specifier containing `/` as a local directory, so `pnpm add bit:@teambit/bit` (with `bit` configured under `namedRegistries`) installed a bogus link to `bit:@teambit/bit/` instead of resolving from the configured registry. The local resolver now runs after the named-registry resolver in the resolution chain.
|
||||
5
.changeset/split-local-resolver.md
Normal file
5
.changeset/split-local-resolver.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/resolving.local-resolver": major
|
||||
---
|
||||
|
||||
Replaced the `resolveFromLocal` export with two narrower exports: `resolveFromLocalScheme` (handles `file:`/`link:`/`workspace:`/`path:`) and `resolveFromLocalPath` (path-shape match by tarball extension or filesystem characters).
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -8422,6 +8422,15 @@ importers:
|
||||
'@pnpm/store.cafs-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/cafs-types
|
||||
'@pnpm/testing.mock-agent':
|
||||
specifier: workspace:*
|
||||
version: link:../../testing/mock-agent
|
||||
load-json-file:
|
||||
specifier: 'catalog:'
|
||||
version: 7.0.1
|
||||
tempy:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.0
|
||||
|
||||
resolving/git-resolver:
|
||||
dependencies:
|
||||
|
||||
@@ -52,7 +52,10 @@
|
||||
"@pnpm/fetching.tarball-fetcher": "workspace:*",
|
||||
"@pnpm/network.fetch": "workspace:*",
|
||||
"@pnpm/resolving.default-resolver": "workspace:*",
|
||||
"@pnpm/store.cafs-types": "workspace:*"
|
||||
"@pnpm/store.cafs-types": "workspace:*",
|
||||
"@pnpm/testing.mock-agent": "workspace:*",
|
||||
"load-json-file": "catalog:",
|
||||
"tempy": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.13"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PnpmError } from '@pnpm/error'
|
||||
import type { FetchFromRegistry, GetAuthHeader } from '@pnpm/fetching.types'
|
||||
import { checkCustomResolverCanResolve, type CustomResolver } from '@pnpm/hooks.types'
|
||||
import { createGitResolver, type GitResolveResult } from '@pnpm/resolving.git-resolver'
|
||||
import { type LocalResolveResult, resolveFromLocal } from '@pnpm/resolving.local-resolver'
|
||||
import { type LocalResolveResult, resolveFromLocalPath, resolveFromLocalScheme } from '@pnpm/resolving.local-resolver'
|
||||
import {
|
||||
createNpmResolver,
|
||||
type JsrResolveResult,
|
||||
@@ -95,9 +95,9 @@ export function createResolver (
|
||||
): { resolve: DefaultResolver, clearCache: () => void } {
|
||||
const { resolveFromNpm, resolveFromJsr, resolveFromNamedRegistry, clearCache } = createNpmResolver(fetchFromRegistry, getAuthHeader, pnpmOpts)
|
||||
const resolveFromGit = createGitResolver(pnpmOpts)
|
||||
const _resolveFromLocal = resolveFromLocal.bind(null, {
|
||||
preserveAbsolutePaths: pnpmOpts.preserveAbsolutePaths,
|
||||
})
|
||||
const localCtx = { preserveAbsolutePaths: pnpmOpts.preserveAbsolutePaths }
|
||||
const _resolveFromLocalScheme = resolveFromLocalScheme.bind(null, localCtx)
|
||||
const _resolveFromLocalPath = resolveFromLocalPath.bind(null, localCtx)
|
||||
const _resolveNodeRuntime = resolveNodeRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, nodeDownloadMirrors: pnpmOpts.nodeDownloadMirrors })
|
||||
const _resolveDenoRuntime = resolveDenoRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, resolveFromNpm })
|
||||
const _resolveBunRuntime = resolveBunRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, resolveFromNpm })
|
||||
@@ -112,16 +112,19 @@ export function createResolver (
|
||||
(wantedDependency.bareSpecifier && (
|
||||
await resolveFromGit(wantedDependency as { bareSpecifier: string }, opts) ??
|
||||
await resolveFromTarball(fetchFromRegistry, wantedDependency as { bareSpecifier: string }) ??
|
||||
await _resolveFromLocal(wantedDependency as { bareSpecifier: string }, opts)
|
||||
await _resolveFromLocalScheme(wantedDependency as { bareSpecifier: string }, opts)
|
||||
)) ??
|
||||
await _resolveNodeRuntime(wantedDependency, opts) ??
|
||||
await _resolveDenoRuntime(wantedDependency, opts) ??
|
||||
await _resolveBunRuntime(wantedDependency, opts) ??
|
||||
// Named-registry resolution runs last so that built-in schemes
|
||||
// (`npm:`, `jsr:`, `git:`/`github:`/`gitlab:`/…, `file:`, `link:`,
|
||||
// tarball URLs, etc.) are always claimed by their dedicated resolver
|
||||
// before a user-configured alias gets a chance to shadow them.
|
||||
await resolveFromNamedRegistry(wantedDependency, opts as ResolveFromNpmOptions)
|
||||
// Named-registry runs between the explicit local schemes above and the
|
||||
// path-shape match below, so `<alias>:@scope/pkg` reaches the configured
|
||||
// registry while a colliding `file:`/`link:`/`workspace:` alias cannot
|
||||
// hijack the built-in protocols.
|
||||
await resolveFromNamedRegistry(wantedDependency, opts as ResolveFromNpmOptions) ??
|
||||
(wantedDependency.bareSpecifier
|
||||
? await _resolveFromLocalPath(wantedDependency as { bareSpecifier: string }, opts)
|
||||
: null)
|
||||
if (!resolution) {
|
||||
let specifier = `${wantedDependency.alias ? wantedDependency.alias + '@' : ''}${wantedDependency.bareSpecifier ?? ''}`
|
||||
if (specifier !== '') {
|
||||
|
||||
108
resolving/default-resolver/test/namedRegistry.ts
Normal file
108
resolving/default-resolver/test/namedRegistry.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/// <reference path="../../../__typings__/index.d.ts"/>
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { afterEach, beforeEach, expect, test } from '@jest/globals'
|
||||
import { createFetchFromRegistry } from '@pnpm/network.fetch'
|
||||
import { createResolver } from '@pnpm/resolving.default-resolver'
|
||||
import { getMockAgent, setupMockAgent, teardownMockAgent } from '@pnpm/testing.mock-agent'
|
||||
import { loadJsonFileSync } from 'load-json-file'
|
||||
import { temporaryDirectory } from 'tempy'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const ghAcmePrivateMeta = loadJsonFileSync<any>(
|
||||
path.join(import.meta.dirname, '../../npm-resolver/test/fixtures/gh-acme-private.json')
|
||||
)
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
const GH_REGISTRY = 'https://npm.pkg.github.com/'
|
||||
const ENTERPRISE_REGISTRY = 'https://npm.enterprise.example.com/'
|
||||
|
||||
const registries = {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
}
|
||||
|
||||
const fetch = createFetchFromRegistry({})
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupMockAgent()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await teardownMockAgent()
|
||||
})
|
||||
|
||||
function interceptAcmePrivate (registry: string): void {
|
||||
const slash = '%2F'
|
||||
const pool = getMockAgent().get(registry.replace(/\/$/, ''))
|
||||
pool.intercept({ path: `/@acme${slash}private`, method: 'GET' }).reply(200, ghAcmePrivateMeta)
|
||||
}
|
||||
|
||||
test('createResolver() routes <alias>:@scope/pkg through the named-registry resolver instead of the local resolver', async () => {
|
||||
interceptAcmePrivate(GH_REGISTRY)
|
||||
|
||||
const { resolve } = createResolver(fetch, () => undefined, {
|
||||
cacheDir: temporaryDirectory(),
|
||||
storeDir: temporaryDirectory(),
|
||||
registries,
|
||||
})
|
||||
|
||||
const result = await resolve(
|
||||
{ bareSpecifier: 'gh:@acme/private' },
|
||||
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
|
||||
)
|
||||
|
||||
expect(result.resolvedVia).toBe('named-registry')
|
||||
expect(result.id).toBe('@acme/private@2.1.0')
|
||||
})
|
||||
|
||||
test('createResolver() routes a user-configured named registry alias through the named-registry resolver', async () => {
|
||||
interceptAcmePrivate(ENTERPRISE_REGISTRY)
|
||||
|
||||
const { resolve } = createResolver(fetch, () => undefined, {
|
||||
cacheDir: temporaryDirectory(),
|
||||
storeDir: temporaryDirectory(),
|
||||
registries,
|
||||
namedRegistries: {
|
||||
work: ENTERPRISE_REGISTRY,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await resolve(
|
||||
{ bareSpecifier: 'work:@acme/private' },
|
||||
{ lockfileDir: '/test', projectDir: '/test', preferredVersions: {} }
|
||||
)
|
||||
|
||||
expect(result.resolvedVia).toBe('named-registry')
|
||||
expect(result.id).toBe('@acme/private@2.1.0')
|
||||
})
|
||||
|
||||
test.each([
|
||||
['link:./pkg', 'link'],
|
||||
['workspace:./pkg', 'workspace'],
|
||||
['file:./pkg', 'file'],
|
||||
])('createResolver() lets the explicit local protocol %s win over a colliding named-registry alias', async (bareSpecifier, alias) => {
|
||||
const projectDir = temporaryDirectory()
|
||||
fs.mkdirSync(path.join(projectDir, 'pkg'))
|
||||
fs.writeFileSync(
|
||||
path.join(projectDir, 'pkg', 'package.json'),
|
||||
JSON.stringify({ name: 'pkg', version: '1.0.0' })
|
||||
)
|
||||
|
||||
const { resolve } = createResolver(fetch, () => undefined, {
|
||||
cacheDir: temporaryDirectory(),
|
||||
storeDir: temporaryDirectory(),
|
||||
registries,
|
||||
namedRegistries: {
|
||||
[alias]: ENTERPRISE_REGISTRY,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await resolve(
|
||||
{ bareSpecifier },
|
||||
{ lockfileDir: projectDir, projectDir, preferredVersions: {} }
|
||||
)
|
||||
|
||||
expect(result.resolvedVia).toBe('local-filesystem')
|
||||
})
|
||||
@@ -42,6 +42,9 @@
|
||||
{
|
||||
"path": "../../store/cafs-types"
|
||||
},
|
||||
{
|
||||
"path": "../../testing/mock-agent"
|
||||
},
|
||||
{
|
||||
"path": "../git-resolver"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { DirectoryResolution, Resolution, ResolveResult, TarballResolution
|
||||
import type { DependencyManifest, PkgResolutionId } from '@pnpm/types'
|
||||
import { readProjectManifestOnly } from '@pnpm/workspace.project-manifest-reader'
|
||||
|
||||
import { parseBareSpecifier, type WantedLocalDependency } from './parseBareSpecifier.js'
|
||||
import { type LocalPackageSpec, parseLocalPath, parseLocalScheme, type WantedLocalDependency } from './parseBareSpecifier.js'
|
||||
|
||||
export { type WantedLocalDependency }
|
||||
|
||||
@@ -19,26 +19,55 @@ export interface LocalResolveResult extends ResolveResult {
|
||||
resolvedVia: 'local-filesystem'
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a package hosted on the local filesystem
|
||||
*/
|
||||
export async function resolveFromLocal (
|
||||
ctx: {
|
||||
preserveAbsolutePaths?: boolean
|
||||
},
|
||||
wantedDependency: WantedLocalDependency,
|
||||
opts: {
|
||||
lockfileDir?: string
|
||||
projectDir: string
|
||||
currentPkg?: {
|
||||
id: PkgResolutionId
|
||||
resolution: DirectoryResolution | TarballResolution | Resolution
|
||||
}
|
||||
update?: false | 'compatible' | 'latest'
|
||||
export interface LocalResolverContext {
|
||||
preserveAbsolutePaths?: boolean
|
||||
}
|
||||
|
||||
export interface LocalResolverOptions {
|
||||
lockfileDir?: string
|
||||
projectDir: string
|
||||
currentPkg?: {
|
||||
id: PkgResolutionId
|
||||
resolution: DirectoryResolution | TarballResolution | Resolution
|
||||
}
|
||||
update?: false | 'compatible' | 'latest'
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dependency declared with an explicit local scheme:
|
||||
* `link:`, `workspace:`, `file:`, or (rejected) `path:`.
|
||||
*/
|
||||
export async function resolveFromLocalScheme (
|
||||
ctx: LocalResolverContext,
|
||||
wantedDependency: WantedLocalDependency,
|
||||
opts: LocalResolverOptions
|
||||
): Promise<LocalResolveResult | null> {
|
||||
const spec = parseLocalScheme(wantedDependency, opts.projectDir, opts.lockfileDir ?? opts.projectDir, {
|
||||
preserveAbsolutePaths: ctx.preserveAbsolutePaths ?? false,
|
||||
})
|
||||
return resolveSpec(spec, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a dependency by path shape — a relative/absolute path or a tarball
|
||||
* filename. Does not look at scheme prefixes; callers that want scheme support
|
||||
* should call {@link resolveFromLocalScheme} first.
|
||||
*/
|
||||
export async function resolveFromLocalPath (
|
||||
ctx: LocalResolverContext,
|
||||
wantedDependency: WantedLocalDependency,
|
||||
opts: LocalResolverOptions
|
||||
): Promise<LocalResolveResult | null> {
|
||||
const spec = parseLocalPath(wantedDependency, opts.projectDir, opts.lockfileDir ?? opts.projectDir, {
|
||||
preserveAbsolutePaths: ctx.preserveAbsolutePaths ?? false,
|
||||
})
|
||||
return resolveSpec(spec, opts)
|
||||
}
|
||||
|
||||
async function resolveSpec (
|
||||
spec: LocalPackageSpec | null,
|
||||
opts: LocalResolverOptions
|
||||
): Promise<LocalResolveResult | null> {
|
||||
const preserveAbsolutePaths = ctx.preserveAbsolutePaths ?? false
|
||||
const spec = parseBareSpecifier(wantedDependency, opts.projectDir, opts.lockfileDir ?? opts.projectDir, { preserveAbsolutePaths })
|
||||
if (spec == null) return null
|
||||
|
||||
if (spec.type === 'file') {
|
||||
|
||||
@@ -35,7 +35,7 @@ class PathIsUnsupportedProtocolError extends PnpmError {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseBareSpecifier (
|
||||
export function parseLocalScheme (
|
||||
wd: WantedLocalDependency,
|
||||
projectDir: string,
|
||||
lockfileDir: string,
|
||||
@@ -44,13 +44,7 @@ export function parseBareSpecifier (
|
||||
if (wd.bareSpecifier.startsWith('link:') || wd.bareSpecifier.startsWith('workspace:')) {
|
||||
return fromLocal(wd, projectDir, lockfileDir, 'directory', opts)
|
||||
}
|
||||
if (wd.bareSpecifier.endsWith('.tgz') ||
|
||||
wd.bareSpecifier.endsWith('.tar.gz') ||
|
||||
wd.bareSpecifier.endsWith('.tar') ||
|
||||
wd.bareSpecifier.includes(path.sep) ||
|
||||
wd.bareSpecifier.startsWith('file:') ||
|
||||
isFilespec.test(wd.bareSpecifier)
|
||||
) {
|
||||
if (wd.bareSpecifier.startsWith('file:')) {
|
||||
const type = isFilename.test(wd.bareSpecifier) ? 'file' : 'directory'
|
||||
return fromLocal(wd, projectDir, lockfileDir, type, opts)
|
||||
}
|
||||
@@ -60,6 +54,24 @@ export function parseBareSpecifier (
|
||||
return null
|
||||
}
|
||||
|
||||
export function parseLocalPath (
|
||||
wd: WantedLocalDependency,
|
||||
projectDir: string,
|
||||
lockfileDir: string,
|
||||
opts: { preserveAbsolutePaths: boolean }
|
||||
): LocalPackageSpec | null {
|
||||
if (wd.bareSpecifier.endsWith('.tgz') ||
|
||||
wd.bareSpecifier.endsWith('.tar.gz') ||
|
||||
wd.bareSpecifier.endsWith('.tar') ||
|
||||
wd.bareSpecifier.includes(path.sep) ||
|
||||
isFilespec.test(wd.bareSpecifier)
|
||||
) {
|
||||
const type = isFilename.test(wd.bareSpecifier) ? 'file' : 'directory'
|
||||
return fromLocal(wd, projectDir, lockfileDir, type, opts)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function fromLocal (
|
||||
{ bareSpecifier, injected }: WantedLocalDependency,
|
||||
projectDir: string,
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'node:path'
|
||||
|
||||
import { expect, jest, test } from '@jest/globals'
|
||||
import { logger } from '@pnpm/logger'
|
||||
import { resolveFromLocal } from '@pnpm/resolving.local-resolver'
|
||||
import { resolveFromLocalPath, resolveFromLocalScheme } from '@pnpm/resolving.local-resolver'
|
||||
import type { DirectoryResolution } from '@pnpm/resolving.resolver-base'
|
||||
import normalize from 'normalize-path'
|
||||
|
||||
@@ -12,7 +12,7 @@ const require = createRequire(import.meta.dirname)
|
||||
const TEST_DIR = path.dirname(require.resolve('@pnpm/tgz-fixtures/tgz/pnpm-local-resolver-0.1.1.tgz'))
|
||||
|
||||
test('resolve directory', async () => {
|
||||
const resolveResult = await resolveFromLocal({}, { bareSpecifier: '..' }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalPath({}, { bareSpecifier: '..' }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('link:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -23,7 +23,7 @@ test('resolve directory', async () => {
|
||||
test('resolve directory specified using absolute path', async () => {
|
||||
const linkedDir = path.join(import.meta.dirname, '..')
|
||||
const normalizedLinkedDir = normalize(linkedDir)
|
||||
const resolveResult = await resolveFromLocal({}, { bareSpecifier: `link:${linkedDir}` }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({}, { bareSpecifier: `link:${linkedDir}` }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('link:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe(`link:${normalizedLinkedDir}`)
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -34,7 +34,7 @@ test('resolve directory specified using absolute path', async () => {
|
||||
test('resolve directory specified using absolute path with preserveAbsolutePaths', async () => {
|
||||
const linkedDir = path.join(import.meta.dirname, '..')
|
||||
const normalizedLinkedDir = normalize(linkedDir)
|
||||
const resolveResult = await resolveFromLocal({ preserveAbsolutePaths: true }, { bareSpecifier: `link:${linkedDir}` }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({ preserveAbsolutePaths: true }, { bareSpecifier: `link:${linkedDir}` }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe(`link:${normalizedLinkedDir}`)
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe(`link:${normalizedLinkedDir}`)
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -45,7 +45,7 @@ test('resolve directory specified using absolute path with preserveAbsolutePaths
|
||||
test('resolve directory specified using absolute path with preserveAbsolutePaths and file: scheme', async () => {
|
||||
const linkedDir = path.join(import.meta.dirname, '..')
|
||||
const normalizedLinkedDir = normalize(linkedDir)
|
||||
const resolveResult = await resolveFromLocal(
|
||||
const resolveResult = await resolveFromLocalScheme(
|
||||
{ preserveAbsolutePaths: true },
|
||||
{ bareSpecifier: `file:${linkedDir}` },
|
||||
{ projectDir: import.meta.dirname }
|
||||
@@ -58,7 +58,7 @@ test('resolve directory specified using absolute path with preserveAbsolutePaths
|
||||
})
|
||||
|
||||
test('resolve injected directory', async () => {
|
||||
const resolveResult = await resolveFromLocal({}, { injected: true, bareSpecifier: '..' }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalPath({}, { injected: true, bareSpecifier: '..' }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('file:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe('file:..')
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -67,7 +67,7 @@ test('resolve injected directory', async () => {
|
||||
})
|
||||
|
||||
test('resolve workspace directory', async () => {
|
||||
const resolveResult = await resolveFromLocal({}, { bareSpecifier: 'workspace:..' }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({}, { bareSpecifier: 'workspace:..' }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('link:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -76,7 +76,7 @@ test('resolve workspace directory', async () => {
|
||||
})
|
||||
|
||||
test('resolve directory specified using the file: protocol', async () => {
|
||||
const resolveResult = await resolveFromLocal({}, { bareSpecifier: 'file:..' }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({}, { bareSpecifier: 'file:..' }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('file:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe('file:..')
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -85,7 +85,7 @@ test('resolve directory specified using the file: protocol', async () => {
|
||||
})
|
||||
|
||||
test('resolve directory specified using the link: protocol', async () => {
|
||||
const resolveResult = await resolveFromLocal({}, { bareSpecifier: 'link:..' }, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({}, { bareSpecifier: 'link:..' }, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult!.id).toBe('link:..')
|
||||
expect(resolveResult!.normalizedBareSpecifier).toBe('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toBe('@pnpm/resolving.local-resolver')
|
||||
@@ -95,7 +95,7 @@ test('resolve directory specified using the link: protocol', async () => {
|
||||
|
||||
test('resolve file', async () => {
|
||||
const wantedDependency = { bareSpecifier: './pnpm-local-resolver-0.1.1.tgz' }
|
||||
const resolveResult = await resolveFromLocal({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
const resolveResult = await resolveFromLocalPath({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
|
||||
expect(resolveResult).toEqual({
|
||||
id: 'file:pnpm-local-resolver-0.1.1.tgz',
|
||||
@@ -110,7 +110,7 @@ test('resolve file', async () => {
|
||||
|
||||
test("resolve file when lockfile directory differs from the package's dir", async () => {
|
||||
const wantedDependency = { bareSpecifier: './pnpm-local-resolver-0.1.1.tgz' }
|
||||
const resolveResult = await resolveFromLocal({}, wantedDependency, {
|
||||
const resolveResult = await resolveFromLocalPath({}, wantedDependency, {
|
||||
lockfileDir: path.join(TEST_DIR, '..'),
|
||||
projectDir: TEST_DIR,
|
||||
})
|
||||
@@ -128,7 +128,7 @@ test("resolve file when lockfile directory differs from the package's dir", asyn
|
||||
|
||||
test('resolve tarball specified with file: protocol', async () => {
|
||||
const wantedDependency = { bareSpecifier: 'file:./pnpm-local-resolver-0.1.1.tgz' }
|
||||
const resolveResult = await resolveFromLocal({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
const resolveResult = await resolveFromLocalScheme({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
|
||||
expect(resolveResult).toEqual({
|
||||
id: 'file:pnpm-local-resolver-0.1.1.tgz',
|
||||
@@ -143,7 +143,7 @@ test('resolve tarball specified with file: protocol', async () => {
|
||||
|
||||
test('resolve file with different integrity (forceFetch)', async () => {
|
||||
const wantedDependency = { bareSpecifier: 'file:./pnpm-local-resolver-0.1.1.tgz' }
|
||||
const resolveResult = await resolveFromLocal({}, wantedDependency, {
|
||||
const resolveResult = await resolveFromLocalScheme({}, wantedDependency, {
|
||||
projectDir: TEST_DIR,
|
||||
currentPkg: {
|
||||
id: 'file:pnpm-local-resolver-0.1.1.tgz' as any, // eslint-disable-line
|
||||
@@ -168,7 +168,7 @@ test('resolve file with different integrity (forceFetch)', async () => {
|
||||
test('fail when resolving tarball specified with the link: protocol', async () => {
|
||||
const wantedDependency = { bareSpecifier: 'link:./pnpm-local-resolver-0.1.1.tgz' }
|
||||
await expect(
|
||||
resolveFromLocal({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
resolveFromLocalScheme({}, wantedDependency, { projectDir: TEST_DIR })
|
||||
).rejects.toMatchObject({ code: 'ERR_PNPM_NOT_PACKAGE_DIRECTORY' })
|
||||
})
|
||||
|
||||
@@ -176,14 +176,14 @@ test('fail when resolving from not existing directory an injected dependency', a
|
||||
const wantedDependency = { bareSpecifier: 'file:./dir-does-not-exist' }
|
||||
const projectDir = import.meta.dirname
|
||||
await expect(
|
||||
resolveFromLocal({}, wantedDependency, { projectDir })
|
||||
resolveFromLocalScheme({}, wantedDependency, { projectDir })
|
||||
).rejects.toThrow(`Could not install from "${path.join(projectDir, 'dir-does-not-exist')}" as it does not exist.`)
|
||||
})
|
||||
|
||||
test('do not fail when resolving from not existing directory', async () => {
|
||||
jest.spyOn(logger, 'warn')
|
||||
const wantedDependency = { bareSpecifier: 'link:./dir-does-not-exist' }
|
||||
const resolveResult = await resolveFromLocal({}, wantedDependency, { projectDir: import.meta.dirname })
|
||||
const resolveResult = await resolveFromLocalScheme({}, wantedDependency, { projectDir: import.meta.dirname })
|
||||
expect(resolveResult?.manifest).toStrictEqual({
|
||||
name: 'dir-does-not-exist',
|
||||
version: '0.0.0',
|
||||
@@ -197,10 +197,18 @@ test('do not fail when resolving from not existing directory', async () => {
|
||||
|
||||
test('throw error when the path: protocol is used', async () => {
|
||||
await expect(
|
||||
resolveFromLocal({}, { bareSpecifier: 'path:..' }, { projectDir: import.meta.dirname })
|
||||
resolveFromLocalScheme({}, { bareSpecifier: 'path:..' }, { projectDir: import.meta.dirname })
|
||||
).rejects.toMatchObject({
|
||||
code: 'ERR_PNPM_PATH_IS_UNSUPPORTED_PROTOCOL',
|
||||
bareSpecifier: 'path:..',
|
||||
protocol: 'path:',
|
||||
})
|
||||
})
|
||||
|
||||
test('resolveFromLocalPath ignores explicit local schemes', async () => {
|
||||
await expect(resolveFromLocalScheme({}, { bareSpecifier: 'foo' }, { projectDir: import.meta.dirname })).resolves.toBeNull()
|
||||
await expect(resolveFromLocalPath({}, { bareSpecifier: 'link:..' }, { projectDir: import.meta.dirname })).resolves.toBeNull()
|
||||
await expect(resolveFromLocalPath({}, { bareSpecifier: 'workspace:..' }, { projectDir: import.meta.dirname })).resolves.toBeNull()
|
||||
await expect(resolveFromLocalPath({}, { bareSpecifier: 'file:..' }, { projectDir: import.meta.dirname })).resolves.toBeNull()
|
||||
await expect(resolveFromLocalPath({}, { bareSpecifier: 'path:..' }, { projectDir: import.meta.dirname })).resolves.toBeNull()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user