@pnpm/resolving.npm-resolver 1102.1.7 → 1102.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ import type { PackageMeta } from '@pnpm/resolving.registry.types';
2
+ import type { FetchMetadataNotModifiedResult, FetchMetadataOptions, FetchMetadataResult } from './fetch.js';
3
+ export type FetchMetadata = (pkgName: string, opts: FetchMetadataOptions) => Promise<FetchMetadataResult | FetchMetadataNotModifiedResult>;
4
+ export interface MemoizedFetchMetadata {
5
+ fetch: FetchMetadata;
6
+ clear: () => void;
7
+ }
8
+ export interface MemoizeFetchMetadataOptions {
9
+ /**
10
+ * Applied to a settled result's `meta` before the entry is retained for the
11
+ * rest of the resolution phase, so a full document doesn't stay pinned here
12
+ * at full size. Callers awaiting the in-flight request still receive the
13
+ * original document.
14
+ */
15
+ condenseSettledMeta?: (meta: PackageMeta) => PackageMeta;
16
+ }
17
+ /**
18
+ * Memoizes metadata fetches for the whole resolution phase (cleared via
19
+ * `clear`, see `clearResolutionCache`), deduplicating concurrent and repeat
20
+ * requests for the same package.
21
+ *
22
+ * Unlike plain memoization, the entry is swapped for a body-less clone once
23
+ * the request settles. `jsonText` — the raw registry response body, up to tens
24
+ * of MB for a popular package — reaches every caller sharing the in-flight
25
+ * request, so a package resolved by many workspace projects at once mirrors
26
+ * that one body to disk instead of each project separately re-serializing
27
+ * `meta`. Retaining bodies past settlement would pin hundreds of MB on large
28
+ * cold-cache graphs, so a later cache-hit caller that writes the mirror falls
29
+ * back to `JSON.stringify(meta)` in `prepareJsonForDisk`, which is equivalent
30
+ * on read: `loadMeta` re-derives `etag` from the headers line.
31
+ *
32
+ * Because that swap lands a turn after the request settles, both settlement
33
+ * paths write back only while the entry is still their own promise — a `clear`
34
+ * (or a retry that already replaced the entry) must not be undone by a request
35
+ * that was in flight when it happened.
36
+ *
37
+ * A rejected fetch is evicted so a transient network failure is retried by
38
+ * the next request instead of being cached for the rest of the phase.
39
+ */
40
+ export declare function memoizeFetchMetadata(fetch: FetchMetadata, memoOpts?: MemoizeFetchMetadataOptions): MemoizedFetchMetadata;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Memoizes metadata fetches for the whole resolution phase (cleared via
3
+ * `clear`, see `clearResolutionCache`), deduplicating concurrent and repeat
4
+ * requests for the same package.
5
+ *
6
+ * Unlike plain memoization, the entry is swapped for a body-less clone once
7
+ * the request settles. `jsonText` — the raw registry response body, up to tens
8
+ * of MB for a popular package — reaches every caller sharing the in-flight
9
+ * request, so a package resolved by many workspace projects at once mirrors
10
+ * that one body to disk instead of each project separately re-serializing
11
+ * `meta`. Retaining bodies past settlement would pin hundreds of MB on large
12
+ * cold-cache graphs, so a later cache-hit caller that writes the mirror falls
13
+ * back to `JSON.stringify(meta)` in `prepareJsonForDisk`, which is equivalent
14
+ * on read: `loadMeta` re-derives `etag` from the headers line.
15
+ *
16
+ * Because that swap lands a turn after the request settles, both settlement
17
+ * paths write back only while the entry is still their own promise — a `clear`
18
+ * (or a retry that already replaced the entry) must not be undone by a request
19
+ * that was in flight when it happened.
20
+ *
21
+ * A rejected fetch is evicted so a transient network failure is retried by
22
+ * the next request instead of being cached for the rest of the phase.
23
+ */
24
+ export function memoizeFetchMetadata(fetch, memoOpts) {
25
+ const condense = memoOpts?.condenseSettledMeta;
26
+ const cache = new Map();
27
+ return {
28
+ fetch: (pkgName, opts) => {
29
+ const key = JSON.stringify([pkgName, opts]);
30
+ const cached = cache.get(key);
31
+ if (cached != null)
32
+ return cached;
33
+ const pending = fetch(pkgName, opts);
34
+ cache.set(key, pending);
35
+ void pending.then((result) => {
36
+ if (cache.get(key) !== pending)
37
+ return;
38
+ cache.set(key, Promise.resolve(settledEntry(result)));
39
+ }, () => {
40
+ if (cache.get(key) === pending)
41
+ cache.delete(key);
42
+ });
43
+ return pending;
44
+ },
45
+ clear: () => {
46
+ cache.clear();
47
+ },
48
+ };
49
+ // Runs inside a fire-and-forget then-callback where a throw would become an
50
+ // unhandled rejection, so a failed condense falls back to the uncondensed
51
+ // meta; the resolution path condenses the same document with proper error
52
+ // propagation.
53
+ function settledEntry(result) {
54
+ if (result.notModified)
55
+ return result;
56
+ let meta = result.meta;
57
+ if (condense != null) {
58
+ try {
59
+ meta = condense(result.meta);
60
+ }
61
+ catch { }
62
+ }
63
+ return { ...result, jsonText: undefined, meta };
64
+ }
65
+ }
66
+ //# sourceMappingURL=memoizeFetchMetadata.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Remove default ports (80 for HTTP, 443 for HTTPS) to ensure consistency
3
+ */
4
+ export declare function normalizeRegistryUrl(urlString: string): string;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Remove default ports (80 for HTTP, 443 for HTTPS) to ensure consistency
3
+ */
4
+ export function normalizeRegistryUrl(urlString) {
5
+ try {
6
+ return new URL(urlString).toString();
7
+ }
8
+ catch {
9
+ return urlString;
10
+ }
11
+ }
12
+ //# sourceMappingURL=normalizeRegistryUrl.js.map
@@ -0,0 +1,16 @@
1
+ export interface RegistryPackageSpec {
2
+ type: 'tag' | 'version' | 'range';
3
+ name: string;
4
+ fetchSpec: string;
5
+ normalizedBareSpecifier?: string;
6
+ }
7
+ export declare function parseBareSpecifier(bareSpecifier: string, alias: string | undefined, defaultTag: string, registry: string): RegistryPackageSpec | null;
8
+ export interface JsrRegistryPackageSpec extends RegistryPackageSpec {
9
+ jsrPkgName: string;
10
+ }
11
+ export declare function parseJsrSpecifierToRegistryPackageSpec(rawSpecifier: string, alias: string | undefined, defaultTag: string): JsrRegistryPackageSpec | null;
12
+ export declare const BUILTIN_NAMED_REGISTRIES: Readonly<Record<string, string>>;
13
+ export interface NamedRegistryPackageSpec extends RegistryPackageSpec {
14
+ registryName: string;
15
+ }
16
+ export declare function parseNamedRegistrySpecifierToRegistryPackageSpec(rawSpecifier: string, knownRegistryNames: ReadonlySet<string>, packageAlias: string | undefined, defaultTag: string): NamedRegistryPackageSpec | null;
@@ -0,0 +1,143 @@
1
+ import { PnpmError } from '@pnpm/error';
2
+ import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser';
3
+ import { parseNpmTarballUrl } from 'parse-npm-tarball-url';
4
+ import semver from 'semver';
5
+ import validateNpmPackageName from 'validate-npm-package-name';
6
+ import getVersionSelectorType from 'version-selector-type';
7
+ export function parseBareSpecifier(bareSpecifier, alias, defaultTag, registry) {
8
+ let name = alias;
9
+ if (bareSpecifier.startsWith('npm:')) {
10
+ bareSpecifier = bareSpecifier.slice(4);
11
+ // `npm:<version_selector>` — fall back to the outer dependency alias as
12
+ // the package name, mirroring the named-registry shape (e.g. `gh:^1.0.0`).
13
+ // Restricted to semver ranges/versions so unscoped package names like
14
+ // `npm:is-positive` keep their npm package-aliasing meaning.
15
+ if (alias && semver.validRange(bareSpecifier) != null) {
16
+ name = alias;
17
+ }
18
+ else {
19
+ const index = bareSpecifier.lastIndexOf('@');
20
+ if (index < 1) {
21
+ name = bareSpecifier;
22
+ bareSpecifier = defaultTag;
23
+ }
24
+ else {
25
+ name = bareSpecifier.slice(0, index);
26
+ bareSpecifier = bareSpecifier.slice(index + 1);
27
+ }
28
+ }
29
+ }
30
+ if (name) {
31
+ const selector = getVersionSelectorType(bareSpecifier);
32
+ if (selector != null) {
33
+ return {
34
+ fetchSpec: selector.normalized,
35
+ name,
36
+ type: selector.type,
37
+ };
38
+ }
39
+ }
40
+ if (bareSpecifier.startsWith(registry)) {
41
+ const pkg = parseNpmTarballUrl(bareSpecifier);
42
+ if (pkg != null) {
43
+ return {
44
+ fetchSpec: pkg.version,
45
+ name: pkg.name,
46
+ normalizedBareSpecifier: bareSpecifier,
47
+ type: 'version',
48
+ };
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ export function parseJsrSpecifierToRegistryPackageSpec(rawSpecifier, alias, defaultTag) {
54
+ const spec = parseJsrSpecifier(rawSpecifier, alias);
55
+ if (!spec?.npmPkgName)
56
+ return null;
57
+ const selector = getVersionSelectorType(spec.versionSelector ?? defaultTag);
58
+ if (selector == null)
59
+ return null;
60
+ return {
61
+ fetchSpec: selector.normalized,
62
+ name: spec.npmPkgName,
63
+ type: selector.type,
64
+ jsrPkgName: spec.jsrPkgName,
65
+ };
66
+ }
67
+ export const BUILTIN_NAMED_REGISTRIES = Object.freeze({
68
+ gh: 'https://npm.pkg.github.com/',
69
+ });
70
+ // Parses a named-registry specifier of the shape `<alias>:<body>` into a
71
+ // RegistryPackageSpec. Returns `null` when the specifier does not use one of
72
+ // the configured aliases, so the caller can fall through to other resolvers.
73
+ // Throws INVALID_NAMED_REGISTRY_PACKAGE_NAME when the alias matches but the
74
+ // package name is malformed (missing or empty scope/name segments, path
75
+ // separators inside the name).
76
+ // Supported shapes:
77
+ // - `<alias>:[@<owner>/]<name>[@<version_selector>]`
78
+ // - `<alias>:<version_selector>` paired with a package alias
79
+ export function parseNamedRegistrySpecifierToRegistryPackageSpec(rawSpecifier, knownRegistryNames, packageAlias, defaultTag) {
80
+ const colon = rawSpecifier.indexOf(':');
81
+ if (colon <= 0)
82
+ return null;
83
+ const registryName = rawSpecifier.substring(0, colon);
84
+ if (!knownRegistryNames.has(registryName))
85
+ return null;
86
+ const body = rawSpecifier.substring(colon + 1);
87
+ let pkgName;
88
+ let versionSelector;
89
+ if (semver.validRange(body) != null) {
90
+ // `<alias>:<version_selector>` — fall back to the dependency alias as
91
+ // the package name. Unresolvable without one.
92
+ if (!packageAlias)
93
+ return null;
94
+ pkgName = packageAlias;
95
+ versionSelector = body;
96
+ }
97
+ else if (body[0] === '@') {
98
+ // `<alias>:@<owner>/<name>[@<version_selector>]` — scoped package.
99
+ const index = body.lastIndexOf('@');
100
+ if (index === 0) {
101
+ pkgName = body;
102
+ }
103
+ else {
104
+ pkgName = body.substring(0, index);
105
+ versionSelector = body.substring(index + '@'.length);
106
+ }
107
+ }
108
+ else if (packageAlias?.startsWith('@')) {
109
+ // `<alias>:<tag>` paired with a scoped alias — body is a version
110
+ // selector (tag/dist-tag). Mirrors GitHub Packages, where the package
111
+ // is always scoped and a bare body is a tag.
112
+ pkgName = packageAlias;
113
+ versionSelector = body;
114
+ }
115
+ else {
116
+ // `<alias>:<name>[@<version_selector>]` — unscoped package in body.
117
+ const index = body.lastIndexOf('@');
118
+ if (index < 1) {
119
+ pkgName = body;
120
+ }
121
+ else {
122
+ pkgName = body.substring(0, index);
123
+ versionSelector = body.substring(index + '@'.length);
124
+ }
125
+ if (!pkgName)
126
+ return null;
127
+ }
128
+ // The name is used in registry URLs and metadata cache file paths, so
129
+ // anything that is not a valid npm package name must never make it through.
130
+ if (!validateNpmPackageName(pkgName).validForOldPackages) {
131
+ throw new PnpmError('INVALID_NAMED_REGISTRY_PACKAGE_NAME', `The package name '${pkgName}' in named registry '${registryName}:' is invalid`);
132
+ }
133
+ const selector = getVersionSelectorType(versionSelector ?? defaultTag);
134
+ if (selector == null)
135
+ return null;
136
+ return {
137
+ fetchSpec: selector.normalized,
138
+ name: pkgName,
139
+ type: selector.type,
140
+ registryName,
141
+ };
142
+ }
143
+ //# sourceMappingURL=parseBareSpecifier.js.map
@@ -0,0 +1,113 @@
1
+ import type { PackageInRegistry, PackageMeta } from '@pnpm/resolving.registry.types';
2
+ import { type FetchMetadataNotModifiedResult, type FetchMetadataResult } from './fetch.js';
3
+ import type { RegistryPackageSpec } from './parseBareSpecifier.js';
4
+ import { type PickPackageFromMetaOptions } from './pickPackageFromMeta.js';
5
+ export interface PackageMetaCache {
6
+ /**
7
+ * Must return the same object reference that `set` stored for the key: the
8
+ * resolver tracks whether a cached packument was validated against the
9
+ * registry by object identity (see `unverifiedDiskPackuments`). In a cache
10
+ * that clones or deserializes on read, that provenance is lost and recovery
11
+ * degrades — a stale disk-promoted entry that can't satisfy a spec fails
12
+ * the pick instead of falling through to the registry.
13
+ */
14
+ get: (key: string) => PackageMeta | undefined;
15
+ set: (key: string, meta: PackageMeta) => void;
16
+ has: (key: string) => boolean;
17
+ }
18
+ export interface PickPackageOptions extends PickPackageFromMetaOptions {
19
+ authHeaderValue?: string;
20
+ pickLowestVersion?: boolean;
21
+ registry: string;
22
+ dryRun: boolean;
23
+ includeLatestTag?: boolean;
24
+ optional?: boolean;
25
+ /**
26
+ * When true, force a conditional registry request so a stale on-disk
27
+ * packument can't satisfy the call: the on-disk exact-version fast
28
+ * path is skipped, and the in-memory cache is bypassed too. The fast
29
+ * path now promotes disk-loaded packuments into the in-memory cache,
30
+ * so an entry there can no longer be assumed to come from this
31
+ * install's own fresh network fetch — on a shared or long-lived
32
+ * resolver it might be disk-sourced, which would short-circuit the
33
+ * revalidation updateChecksums exists to force.
34
+ */
35
+ updateChecksums?: boolean;
36
+ }
37
+ export declare function pickPackage(ctx: {
38
+ fetch: (pkgName: string, opts: {
39
+ registry: string;
40
+ authHeaderValue?: string;
41
+ cacheBypass?: boolean;
42
+ fullMetadata?: boolean;
43
+ etag?: string;
44
+ modified?: string;
45
+ }) => Promise<FetchMetadataResult | FetchMetadataNotModifiedResult>;
46
+ fullMetadata?: boolean;
47
+ metaCache: PackageMetaCache;
48
+ cacheDir: string;
49
+ offline?: boolean;
50
+ preferOffline?: boolean;
51
+ filterMetadata?: boolean;
52
+ ignoreMissingTimeField?: boolean;
53
+ }, spec: RegistryPackageSpec, opts: PickPackageOptions): Promise<{
54
+ meta: PackageMeta;
55
+ pickedPackage: PackageInRegistry | null;
56
+ }>;
57
+ export declare function encodePkgName(pkgName: string): string;
58
+ /**
59
+ * Key for the in-memory `metaCache` holding a package's registry metadata. The
60
+ * registry is part of the key so that a package of the same name served by two
61
+ * registries in one install can't collide on a single slot (which would resolve
62
+ * the wrong tarball/integrity). `fullMetadata` and `filterMetadata` keep the
63
+ * abbreviated, full, and filtered-full documents in distinct slots, mirroring
64
+ * the on-disk `metaDir` split: a `filterMetadata` resolver stores a `clearMeta`-
65
+ * stripped packument, so it must not share a slot with an unfiltered full one
66
+ * (reachable only when a `metaCache` is shared across resolvers with different
67
+ * settings). `filterMetadata` only narrows the full slot — abbreviated metadata
68
+ * shares one on-disk mirror regardless, so its key carries no filtered variant.
69
+ * `\x00` can't appear in a registry URL or a package name, so it's an
70
+ * unambiguous separator. The verifier reads this same cache and must build the
71
+ * key with this function.
72
+ *
73
+ * The registry is canonicalized to its origin plus a trailing-slashed path, so
74
+ * the resolver (which may pass a configured named-registry URL verbatim) and
75
+ * the verifier (which routes through trailing-slashed prefixes) converge on one
76
+ * key for the same logical registry instead of creating duplicate slots. Origin
77
+ * and path are preserved, so two registries that genuinely differ never collapse.
78
+ */
79
+ export declare function getPkgMetaCacheKey(registry: string, pkgName: string, fullMetadata: boolean, filterMetadata: boolean): string;
80
+ /**
81
+ * Path of the on-disk JSONL document where pnpm mirrors a package's registry
82
+ * metadata. `metaDir` selects between abbreviated and full caches.
83
+ */
84
+ export declare function getPkgMirrorPath(cacheDir: string, metaDir: string, registry: string, pkgName: string): string;
85
+ /**
86
+ * Formats metadata for disk storage as two-line NDJSON:
87
+ * Line 1: cache headers (etag, modified) — small, fast to read
88
+ * Line 2: the registry metadata JSON
89
+ *
90
+ * The etag lives only in the headers line (`loadMeta` re-attaches it from
91
+ * there), so a `meta` that carries one is serialized without it.
92
+ */
93
+ export declare function prepareJsonForDisk(meta: PackageMeta, etag: string | undefined, jsonText?: string): string;
94
+ export declare function warnMissingTimeFieldOnce(pkgName: string): void;
95
+ interface MetaHeaders {
96
+ etag?: string;
97
+ modified?: string;
98
+ }
99
+ /**
100
+ * Reads only the first line of the cached NDJSON metadata file to extract
101
+ * the cache headers (etag, modified). This avoids reading and
102
+ * parsing the full metadata (which can be megabytes for popular packages)
103
+ * when we only need conditional-request headers.
104
+ */
105
+ export declare function loadMetaHeaders(pkgMirror: string): Promise<MetaHeaders | null>;
106
+ /**
107
+ * Reads the full metadata from the cached NDJSON file.
108
+ * Line 1: cache headers (etag, modified)
109
+ * Line 2: registry metadata JSON
110
+ */
111
+ export declare function loadMeta(pkgMirror: string): Promise<PackageMeta | null>;
112
+ export declare function saveMeta(pkgMirror: string, json: string): Promise<void>;
113
+ export {};