@pnpm/resolving.npm-resolver 1101.2.0 → 1101.3.1

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.
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PnpmError } from '@pnpm/error';
2
2
  import type { FetchFromRegistry, GetAuthHeader, RetryTimeoutOptions } from '@pnpm/fetching.types';
3
3
  import type { PackageMeta } from '@pnpm/resolving.registry.types';
4
- import type { DirectoryResolution, PkgResolutionId, PreferredVersions, ResolveResult, TarballResolution, WantedDependency, WorkspacePackages } from '@pnpm/resolving.resolver-base';
4
+ import type { DirectoryResolution, LatestInfo, LatestQuery, PkgResolutionId, PreferredVersions, ResolveOptions, ResolveResult, TarballResolution, WantedDependency, WorkspacePackages } from '@pnpm/resolving.resolver-base';
5
5
  import type { DependencyManifest, PackageVersionPolicy, PinnedVersion, Registries, TrustPolicy } from '@pnpm/types';
6
6
  import { fetchMetadataFromFromRegistry, type FetchMetadataFromFromRegistryOptions, RegistryResponseError } from './fetch.js';
7
7
  import { BUILTIN_NAMED_REGISTRIES, parseBareSpecifier, type RegistryPackageSpec } from './parseBareSpecifier.js';
@@ -70,10 +70,14 @@ export interface WorkspaceResolveResult extends ResolveResult {
70
70
  export type NpmResolver = (wantedDependency: WantedDependency & {
71
71
  optional?: boolean;
72
72
  }, opts: ResolveFromNpmOptions) => Promise<NpmResolveResult | JsrResolveResult | NamedRegistryResolveResult | WorkspaceResolveResult | null>;
73
+ export type ResolveLatestFromNpmStyle = (query: LatestQuery, opts: ResolveOptions) => Promise<LatestInfo | undefined>;
73
74
  export declare function createNpmResolver(fetchFromRegistry: FetchFromRegistry, getAuthHeader: GetAuthHeader, opts: ResolverFactoryOptions): {
74
75
  resolveFromNpm: NpmResolver;
75
76
  resolveFromJsr: NpmResolver;
76
77
  resolveFromNamedRegistry: NpmResolver;
78
+ resolveLatestFromNpm: ResolveLatestFromNpmStyle;
79
+ resolveLatestFromJsr: ResolveLatestFromNpmStyle;
80
+ resolveLatestFromNamedRegistry: ResolveLatestFromNpmStyle;
77
81
  clearCache: () => void;
78
82
  };
79
83
  export interface ResolveFromNpmContext {
package/lib/index.js CHANGED
@@ -125,10 +125,17 @@ export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
125
125
  saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
126
126
  peekManifestFromStore,
127
127
  };
128
+ const boundResolveFromNpm = resolveNpm.bind(null, ctx);
129
+ const boundResolveFromJsr = resolveJsr.bind(null, ctx);
130
+ const boundResolveFromNamedRegistry = resolveFromNamedRegistry.bind(null, ctx);
131
+ const defaultRegistry = opts.registries.default;
128
132
  return {
129
- resolveFromNpm: resolveNpm.bind(null, ctx),
130
- resolveFromJsr: resolveJsr.bind(null, ctx),
131
- resolveFromNamedRegistry: resolveFromNamedRegistry.bind(null, ctx),
133
+ resolveFromNpm: boundResolveFromNpm,
134
+ resolveFromJsr: boundResolveFromJsr,
135
+ resolveFromNamedRegistry: boundResolveFromNamedRegistry,
136
+ resolveLatestFromNpm: createResolveLatest(boundResolveFromNpm, (query) => isNpmSpec(query, defaultRegistry)),
137
+ resolveLatestFromJsr: createResolveLatest(boundResolveFromJsr, isJsrSpec),
138
+ resolveLatestFromNamedRegistry: createResolveLatest(boundResolveFromNamedRegistry, (query) => isNamedRegistrySpec(query, ctx.namedRegistryNames)),
132
139
  clearCache: () => {
133
140
  if ('clear' in metaCache && typeof metaCache.clear === 'function') {
134
141
  metaCache.clear();
@@ -137,6 +144,54 @@ export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
137
144
  },
138
145
  };
139
146
  }
147
+ function isNpmSpec(query, defaultRegistry) {
148
+ const { alias, bareSpecifier } = query.wantedDependency;
149
+ if (!bareSpecifier)
150
+ return alias != null;
151
+ return parseBareSpecifier(bareSpecifier, alias, 'latest', defaultRegistry) != null;
152
+ }
153
+ function isJsrSpec(query) {
154
+ if (!query.wantedDependency.bareSpecifier?.startsWith('jsr:'))
155
+ return false;
156
+ return parseJsrSpecifierToRegistryPackageSpec(query.wantedDependency.bareSpecifier, query.wantedDependency.alias, 'latest') != null;
157
+ }
158
+ function isNamedRegistrySpec(query, knownRegistryNames) {
159
+ if (!query.wantedDependency.bareSpecifier)
160
+ return false;
161
+ try {
162
+ return parseNamedRegistrySpecifierToRegistryPackageSpec(query.wantedDependency.bareSpecifier, knownRegistryNames, query.wantedDependency.alias, 'latest') != null;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
168
+ function createResolveLatest(resolve, matches) {
169
+ return async (query, opts) => {
170
+ if (!matches(query))
171
+ return undefined;
172
+ // Always pass the manifest's bareSpecifier so protocol-prefixed specs
173
+ // (`jsr:@scope/pkg@^1.0.0`, `gh:owner/repo@^1.0.0`) still match their
174
+ // resolver. In --compatible mode that range drives the pick; otherwise
175
+ // `update: 'latest'` tells the resolver to ignore the range and take
176
+ // the absolute newest.
177
+ const bareSpecifier = query.wantedDependency.bareSpecifier ?? 'latest';
178
+ const resolveOpts = query.compatible ? opts : { ...opts, update: 'latest' };
179
+ try {
180
+ const result = await resolve({ alias: query.wantedDependency.alias, bareSpecifier }, resolveOpts);
181
+ // Policy-blocked: handled but no latest to surface.
182
+ if (result?.policyViolation?.code === MINIMUM_RELEASE_AGE_VIOLATION_CODE) {
183
+ return {};
184
+ }
185
+ return { latestManifest: result?.manifest };
186
+ }
187
+ catch (err) {
188
+ if (opts.publishedBy && err.code === 'ERR_PNPM_NO_MATCHING_VERSION') {
189
+ return {};
190
+ }
191
+ throw err;
192
+ }
193
+ };
194
+ }
140
195
  async function resolveNpm(ctx, wantedDependency, opts) {
141
196
  const defaultTag = opts.defaultTag ?? 'latest';
142
197
  const registry = wantedDependency.alias
@@ -277,7 +277,12 @@ export async function pickPackage(ctx, spec, opts) {
277
277
  opts.publishedByExclude?.(spec.name) !== true) {
278
278
  const modifiedDate = meta.modified ? new Date(meta.modified) : null;
279
279
  const isModifiedValid = modifiedDate != null && !Number.isNaN(modifiedDate.getTime());
280
- if (!isModifiedValid || modifiedDate >= opts.publishedBy) {
280
+ // Strict `>` (not `>=`) so the boundary case `modified == publishedBy`
281
+ // takes the abbreviated fast path: `modified` is an upper bound on
282
+ // every version's publish time, so when it equals the cutoff every
283
+ // version passes the per-version `<=` filter in
284
+ // `filterPkgMetadataByPublishDate` and a full re-fetch isn't needed.
285
+ if (!isModifiedValid || modifiedDate > opts.publishedBy) {
281
286
  // Save the abbreviated metadata to the abbreviated cache before re-fetching full.
282
287
  if (!opts.dryRun) {
283
288
  const abbreviatedJson = prepareJsonForDisk(fetchResult.meta, fetchResult.etag, fetchResult.jsonText);
@@ -361,9 +366,12 @@ async function maybeUpgradeAbbreviatedMetaForReleaseAge(ctx, spec, opts, meta) {
361
366
  }
362
367
  const modifiedDate = meta.modified ? new Date(meta.modified) : null;
363
368
  const isModifiedValid = modifiedDate != null && !Number.isNaN(modifiedDate.getTime());
364
- if (isModifiedValid && modifiedDate < opts.publishedBy) {
365
- // The package was last modified before the maturity cutoff. No individual
366
- // version can be newer than the cutoff, so the abbreviated form is fine.
369
+ if (isModifiedValid && modifiedDate <= opts.publishedBy) {
370
+ // The package was last modified at or before the maturity cutoff. Since
371
+ // `modified` is an upper bound on every version's publish time, no version
372
+ // can be newer than the cutoff, so the abbreviated form is fine.
373
+ // Inclusive at the boundary on purpose: matches the per-version `<=` filter
374
+ // in `filterPkgMetadataByPublishDate`.
367
375
  return { meta };
368
376
  }
369
377
  // When `modified` is missing or malformed we fall through to the upgrade
@@ -14,13 +14,17 @@ export function pickPackageFromMeta(pickVersionByVersionRangeFn, { preferredVers
14
14
  }
15
15
  else {
16
16
  const modifiedDate = parseModifiedDate(meta.modified);
17
- if (modifiedDate == null || modifiedDate >= publishedBy) {
17
+ if (modifiedDate == null || modifiedDate > publishedBy) {
18
18
  // Abbreviated metadata without per-version timestamps, and the package
19
19
  // was recently modified (or has no/invalid modified field). We cannot determine
20
20
  // which individual versions are mature enough — need full metadata.
21
21
  assertMetaHasTime(meta);
22
22
  }
23
- // else: meta.modified < publishedBy — all versions are old enough, no filtering needed
23
+ // else: meta.modified <= publishedBy — every version was published at or
24
+ // before the cutoff (modified is an upper bound on per-version time), so
25
+ // they all pass the per-version `<=` maturity filter and no filtering is
26
+ // needed. Inclusive at the boundary on purpose so this branch matches the
27
+ // per-version filter in `filterPkgMetadataByPublishDate`.
24
28
  }
25
29
  }
26
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/resolving.npm-resolver",
3
- "version": "1101.2.0",
3
+ "version": "1101.3.1",
4
4
  "description": "Resolver for npm-hosted packages",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -40,27 +40,27 @@
40
40
  "semver-utils": "^1.1.4",
41
41
  "ssri": "13.0.1",
42
42
  "version-selector-type": "^3.0.0",
43
- "@pnpm/config.pick-registry-for-package": "1100.0.3",
44
- "@pnpm/config.version-policy": "1100.1.0",
43
+ "@pnpm/config.version-policy": "1100.1.1",
44
+ "@pnpm/config.pick-registry-for-package": "1100.0.5",
45
45
  "@pnpm/constants": "1100.0.0",
46
- "@pnpm/core-loggers": "1100.1.0",
46
+ "@pnpm/core-loggers": "1100.1.1",
47
47
  "@pnpm/crypto.hash": "1100.0.1",
48
48
  "@pnpm/error": "1100.0.0",
49
49
  "@pnpm/fetching.types": "1100.0.1",
50
- "@pnpm/fs.graceful-fs": "1100.1.0",
51
50
  "@pnpm/resolving.jsr-specifier-parser": "1100.0.0",
52
- "@pnpm/resolving.registry.pkg-metadata-filter": "1100.0.3",
53
- "@pnpm/resolving.registry.types": "1100.0.3",
54
- "@pnpm/resolving.resolver-base": "1100.2.0",
55
- "@pnpm/store.cafs": "1100.1.5",
51
+ "@pnpm/fs.graceful-fs": "1100.1.0",
52
+ "@pnpm/resolving.registry.types": "1100.0.4",
53
+ "@pnpm/resolving.registry.pkg-metadata-filter": "1100.0.4",
54
+ "@pnpm/resolving.resolver-base": "1100.3.0",
56
55
  "@pnpm/store.index": "1100.1.0",
57
- "@pnpm/types": "1101.1.0",
56
+ "@pnpm/types": "1101.1.1",
58
57
  "@pnpm/workspace.range-resolver": "1100.0.1",
58
+ "@pnpm/store.cafs": "1100.1.6",
59
59
  "@pnpm/workspace.spec-parser": "1100.0.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@pnpm/logger": ">=1001.0.0 <1002.0.0",
63
- "@pnpm/worker": "^1100.1.6"
63
+ "@pnpm/worker": "^1100.1.7"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@jest/globals": "30.3.0",
@@ -70,11 +70,11 @@
70
70
  "@types/ssri": "^7.1.5",
71
71
  "load-json-file": "^7.0.1",
72
72
  "tempy": "3.0.0",
73
+ "@pnpm/network.fetch": "1100.0.6",
73
74
  "@pnpm/logger": "1100.0.0",
74
- "@pnpm/resolving.npm-resolver": "1101.2.0",
75
- "@pnpm/network.fetch": "1100.0.5",
76
- "@pnpm/test-fixtures": "1100.0.0",
77
- "@pnpm/testing.mock-agent": "1100.0.5"
75
+ "@pnpm/resolving.npm-resolver": "1101.3.1",
76
+ "@pnpm/testing.mock-agent": "1100.0.6",
77
+ "@pnpm/test-fixtures": "1100.0.0"
78
78
  },
79
79
  "engines": {
80
80
  "node": ">=22.13"