@pnpm/deps.inspection.outdated 1100.0.16 → 1100.1.0

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/outdated.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Catalogs } from '@pnpm/catalogs.types';
2
+ import type { ResolveLatestDispatcher } from '@pnpm/installing.client';
2
3
  import { type LockfileObject } from '@pnpm/lockfile.fs';
3
- import { type DependenciesField, type IncludedDependencies, type PackageManifest, type ProjectManifest, type Registries } from '@pnpm/types';
4
+ import { type DependenciesField, type IncludedDependencies, type PackageManifest, type PackageVersionPolicy, type ProjectManifest } from '@pnpm/types';
4
5
  export * from './createManifestGetter.js';
5
- export type GetLatestManifestFunction = (packageName: string, rangeOrTag: string) => Promise<PackageManifest | null>;
6
6
  export interface OutdatedPackage {
7
7
  alias: string;
8
8
  belongsTo: DependenciesField;
@@ -16,7 +16,7 @@ export declare function outdated(opts: {
16
16
  catalogs?: Catalogs;
17
17
  compatible?: boolean;
18
18
  currentLockfile: LockfileObject | null;
19
- getLatestManifest: GetLatestManifestFunction;
19
+ resolveLatest: ResolveLatestDispatcher;
20
20
  ignoreDependencies?: string[];
21
21
  include?: IncludedDependencies;
22
22
  lockfileDir: string;
@@ -25,6 +25,7 @@ export declare function outdated(opts: {
25
25
  minimumReleaseAge?: number;
26
26
  minimumReleaseAgeExclude?: string[];
27
27
  prefix: string;
28
- registries: Registries;
28
+ publishedBy?: Date;
29
+ publishedByExclude?: PackageVersionPolicy;
29
30
  wantedLockfile: LockfileObject | null;
30
31
  }): Promise<OutdatedPackage[]>;
package/lib/outdated.js CHANGED
@@ -1,15 +1,12 @@
1
1
  import { matchCatalogResolveResult, resolveFromCatalog, } from '@pnpm/catalogs.resolver';
2
2
  import { createMatcher } from '@pnpm/config.matcher';
3
3
  import { parseOverrides } from '@pnpm/config.parse-overrides';
4
- import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
5
- import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants';
4
+ import { LOCKFILE_VERSION } from '@pnpm/constants';
6
5
  import * as dp from '@pnpm/deps.path';
7
6
  import { PnpmError } from '@pnpm/error';
8
7
  import { createReadPackageHook } from '@pnpm/hooks.read-package-hook';
9
8
  import { getLockfileImporterId, } from '@pnpm/lockfile.fs';
10
- import { nameVerFromPkgSnapshot } from '@pnpm/lockfile.utils';
11
9
  import { getAllDependenciesFromManifest } from '@pnpm/pkg-manifest.utils';
12
- import { parseBareSpecifier } from '@pnpm/resolving.npm-resolver';
13
10
  import { DEPENDENCIES_FIELDS, } from '@pnpm/types';
14
11
  import semver from 'semver';
15
12
  export * from './createManifestGetter.js';
@@ -37,6 +34,13 @@ export async function outdated(opts) {
37
34
  const currentLockfile = opts.currentLockfile ?? { lockfileVersion: LOCKFILE_VERSION, importers: { [importerId]: { specifiers: {} } } };
38
35
  const outdated = [];
39
36
  const ignoreDependenciesMatcher = opts.ignoreDependencies?.length ? createMatcher(opts.ignoreDependencies) : undefined;
37
+ const resolveOpts = {
38
+ lockfileDir: opts.lockfileDir,
39
+ preferredVersions: {},
40
+ projectDir: opts.prefix,
41
+ publishedBy: opts.publishedBy,
42
+ publishedByExclude: opts.publishedByExclude,
43
+ };
40
44
  await Promise.all(DEPENDENCIES_FIELDS.map(async (depType) => {
41
45
  if (opts.include?.[depType] === false ||
42
46
  (opts.wantedLockfile.importers[importerId][depType] == null))
@@ -49,31 +53,31 @@ export async function outdated(opts) {
49
53
  await Promise.all(pkgs.map(async (alias) => {
50
54
  if (!allDeps[alias])
51
55
  return;
52
- const ref = opts.wantedLockfile.importers[importerId][depType][alias];
53
- if (ref.startsWith('file:') || // ignoring linked packages. (For backward compatibility)
54
- ignoreDependenciesMatcher?.(alias)) {
56
+ const wantedRef = opts.wantedLockfile.importers[importerId][depType][alias];
57
+ if (ignoreDependenciesMatcher?.(alias))
55
58
  return;
56
- }
57
- const relativeDepPath = dp.refToRelative(ref, alias);
58
- // ignoring linked packages
59
- if (relativeDepPath === null)
60
- return;
61
- const pkgSnapshot = opts.wantedLockfile.packages?.[relativeDepPath];
62
- if (pkgSnapshot == null) {
63
- throw new Error(`Invalid ${WANTED_LOCKFILE} file. ${relativeDepPath} not found in packages field`);
64
- }
65
59
  const currentRef = currentLockfile.importers[importerId]?.[depType]?.[alias];
66
- const currentRelative = currentRef && dp.refToRelative(currentRef, alias);
67
- const current = (currentRelative && dp.parse(currentRelative).version) ?? currentRef;
68
- const wanted = dp.parse(relativeDepPath).version ?? ref;
69
- const { name: packageName } = nameVerFromPkgSnapshot(relativeDepPath, pkgSnapshot);
70
- const name = dp.parse(relativeDepPath).name ?? packageName;
60
+ const wantedRelative = dp.refToRelative(wantedRef, alias);
61
+ const currentRelative = currentRef ? dp.refToRelative(currentRef, alias) : null;
62
+ const wantedSnapshot = wantedRelative != null ? opts.wantedLockfile.packages?.[wantedRelative] : undefined;
63
+ const currentSnapshot = currentRelative != null ? currentLockfile.packages?.[currentRelative] : undefined;
64
+ // Aliased npm deps lock under their real name (e.g. `positive: is-positive@3.1.0`);
65
+ // pull the name off the depPath so the report shows the real package.
66
+ const packageName = (wantedRelative != null ? dp.parse(wantedRelative).name : undefined) ?? alias;
71
67
  const bareSpecifier = _replaceCatalogProtocolIfNecessary({ alias, bareSpecifier: allDeps[alias] });
72
- // If the npm resolve parser cannot parse the spec of the dependency,
73
- // it means that the package is not from a npm-compatible registry.
74
- // In that case, we can't check whether the package is up-to-date
75
- if (parseBareSpecifier(bareSpecifier, alias, 'latest', pickRegistryForPackage(opts.registries, name)) == null) {
76
- if (current !== wanted) {
68
+ const info = await opts.resolveLatest({ wantedDependency: { alias, bareSpecifier }, compatible: opts.compatible }, resolveOpts);
69
+ if (info == null)
70
+ return; // resolver doesn't claim this dep skip silently
71
+ const wanted = displayVersion(wantedRef, wantedRelative, wantedSnapshot?.version);
72
+ const current = currentRef ? displayVersion(currentRef, currentRelative, currentSnapshot?.version) : undefined;
73
+ const { latestManifest } = info;
74
+ // Compare the parsed `wanted` / `current` rather than raw refs.
75
+ // For npm-style deps that means peer-graph-only changes (same
76
+ // semver, different `(peer-hash)`) don't surface as fake
77
+ // "outdated" entries; for URL/git refs the display values *are*
78
+ // the refs, so a commit/path change still fires correctly.
79
+ if (latestManifest == null) {
80
+ if (wanted !== current) {
77
81
  outdated.push({
78
82
  alias,
79
83
  belongsTo: depType,
@@ -86,9 +90,6 @@ export async function outdated(opts) {
86
90
  }
87
91
  return;
88
92
  }
89
- const latestManifest = await opts.getLatestManifest(name, opts.compatible ? (bareSpecifier ?? 'latest') : 'latest');
90
- if (latestManifest == null)
91
- return;
92
93
  if (!current) {
93
94
  outdated.push({
94
95
  alias,
@@ -100,7 +101,7 @@ export async function outdated(opts) {
100
101
  });
101
102
  return;
102
103
  }
103
- if (current !== wanted || semver.lt(current, latestManifest.version) || latestManifest.deprecated) {
104
+ if (wanted !== current || isLowerVersion(wanted, latestManifest.version) || latestManifest.deprecated) {
104
105
  outdated.push({
105
106
  alias,
106
107
  belongsTo: depType,
@@ -123,6 +124,34 @@ function packageHasNoDeps(manifest) {
123
124
  function isEmpty(obj) {
124
125
  return Object.keys(obj).length === 0;
125
126
  }
127
+ // Pick a clean display string for a lockfile ref.
128
+ //
129
+ // - If the dep-path parses to a semver, that's the value (handles
130
+ // `pkg@1.0.0(peer-hash)` and aliased `positive: is-positive@3.1.0`).
131
+ // - If the dep-path's non-semver version contains a `/`, it's a
132
+ // URL/git-shape (`https://`, `git+ssh://`, scheme-less `github.com/…/sha`,
133
+ // `link:../foo`, etc.) — return the raw ref so a commit/path change is
134
+ // visible to the user.
135
+ // - Otherwise prefer `snapshot.version` (clean semver for `runtime:`-style
136
+ // refs); fall back to the raw ref when the snapshot didn't record one.
137
+ function displayVersion(ref, relativeDepPath, snapshotVersion) {
138
+ if (relativeDepPath != null) {
139
+ const parsed = dp.parse(relativeDepPath);
140
+ if (parsed.version != null)
141
+ return parsed.version;
142
+ if (parsed.nonSemverVersion?.includes('/'))
143
+ return ref;
144
+ }
145
+ return snapshotVersion ?? ref;
146
+ }
147
+ // semver.lt throws on non-semver strings (e.g. URL refs from git/tarball).
148
+ // Treat those as "not lower" so a ref change still gets surfaced via the
149
+ // `wantedRef !== currentRef` check above.
150
+ function isLowerVersion(current, latest) {
151
+ if (!semver.valid(current) || !semver.valid(latest))
152
+ return false;
153
+ return semver.lt(current, latest);
154
+ }
126
155
  function replaceCatalogProtocolIfNecessary(catalogs, wantedDependency) {
127
156
  return matchCatalogResolveResult(resolveFromCatalog(catalogs, wantedDependency), {
128
157
  unused: () => wantedDependency.bareSpecifier,
@@ -1,15 +1,23 @@
1
1
  import type { Catalogs } from '@pnpm/catalogs.types';
2
- import type { IncludedDependencies, ProjectManifest, ProjectRootDir } from '@pnpm/types';
3
- import { type ManifestGetterOptions } from './createManifestGetter.js';
2
+ import { type ClientOptions } from '@pnpm/installing.client';
3
+ import type { IncludedDependencies, ProjectManifest, ProjectRootDir, RegistryConfig } from '@pnpm/types';
4
4
  import { type OutdatedPackage } from './outdated.js';
5
+ export type OutdatedDepsOfProjectsOptions = Omit<ClientOptions, 'configByUri' | 'minimumReleaseAgeExclude' | 'storeIndex'> & {
6
+ dir: string;
7
+ lockfileDir?: string;
8
+ configByUri: Record<string, RegistryConfig>;
9
+ fullMetadata?: boolean;
10
+ minimumReleaseAge?: number;
11
+ minimumReleaseAgeExclude?: string[];
12
+ minimumReleaseAgeIgnoreMissingTime?: boolean;
13
+ minimumReleaseAgeStrict?: boolean;
14
+ };
5
15
  export declare function outdatedDepsOfProjects(pkgs: Array<{
6
16
  rootDir: ProjectRootDir;
7
17
  manifest: ProjectManifest;
8
- }>, args: string[], opts: Omit<ManifestGetterOptions, 'fullMetadata' | 'lockfileDir'> & {
18
+ }>, args: string[], opts: OutdatedDepsOfProjectsOptions & {
9
19
  catalogs?: Catalogs;
10
20
  compatible?: boolean;
11
21
  ignoreDependencies?: string[];
12
22
  include: IncludedDependencies;
13
- minimumReleaseAge?: number;
14
- minimumReleaseAgeExclude?: string[];
15
- } & Partial<Pick<ManifestGetterOptions, 'fullMetadata' | 'lockfileDir'>>): Promise<OutdatedPackage[][]>;
23
+ }): Promise<OutdatedPackage[][]>;
@@ -1,8 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import { createMatcher } from '@pnpm/config.matcher';
3
+ import { getPublishedByPolicy } from '@pnpm/config.version-policy';
4
+ import { createResolver } from '@pnpm/installing.client';
3
5
  import { readCurrentLockfile, readWantedLockfile, } from '@pnpm/lockfile.fs';
4
6
  import { unnest } from 'ramda';
5
- import { createManifestGetter } from './createManifestGetter.js';
6
7
  import { outdated } from './outdated.js';
7
8
  export async function outdatedDepsOfProjects(pkgs, args, opts) {
8
9
  if (!opts.lockfileDir) {
@@ -12,12 +13,13 @@ export async function outdatedDepsOfProjects(pkgs, args, opts) {
12
13
  const internalPnpmDir = path.join(path.join(lockfileDir, 'node_modules/.pnpm'));
13
14
  const currentLockfile = await readCurrentLockfile(internalPnpmDir, { ignoreIncompatible: false });
14
15
  const wantedLockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: false }) ?? currentLockfile;
15
- const getLatestManifest = createManifestGetter({
16
+ const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts);
17
+ const { resolveLatest } = createResolver({
16
18
  ...opts,
19
+ configByUri: opts.configByUri,
20
+ filterMetadata: false,
17
21
  fullMetadata: opts.fullMetadata === true || Boolean(opts.minimumReleaseAge),
18
- lockfileDir,
19
- minimumReleaseAge: opts.minimumReleaseAge,
20
- minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
22
+ ignoreMissingTimeField: opts.minimumReleaseAgeIgnoreMissingTime,
21
23
  });
22
24
  return Promise.all(pkgs.map(async ({ rootDir, manifest }) => {
23
25
  const match = (args.length > 0) && createMatcher(args) || undefined;
@@ -25,7 +27,7 @@ export async function outdatedDepsOfProjects(pkgs, args, opts) {
25
27
  catalogs: opts.catalogs,
26
28
  compatible: opts.compatible,
27
29
  currentLockfile,
28
- getLatestManifest,
30
+ resolveLatest,
29
31
  ignoreDependencies: opts.ignoreDependencies,
30
32
  include: opts.include,
31
33
  lockfileDir,
@@ -34,7 +36,8 @@ export async function outdatedDepsOfProjects(pkgs, args, opts) {
34
36
  minimumReleaseAge: opts.minimumReleaseAge,
35
37
  minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
36
38
  prefix: rootDir,
37
- registries: opts.registries,
39
+ publishedBy,
40
+ publishedByExclude,
38
41
  wantedLockfile,
39
42
  });
40
43
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.inspection.outdated",
3
- "version": "1100.0.16",
3
+ "version": "1100.1.0",
4
4
  "description": "Check for outdated packages",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -27,22 +27,21 @@
27
27
  "dependencies": {
28
28
  "ramda": "npm:@pnpm/ramda@0.28.1",
29
29
  "semver": "^7.7.2",
30
- "@pnpm/catalogs.types": "1100.0.0",
30
+ "@pnpm/catalogs.resolver": "1100.0.0",
31
31
  "@pnpm/config.matcher": "1100.0.1",
32
- "@pnpm/config.parse-overrides": "1100.0.1",
33
- "@pnpm/config.version-policy": "1100.1.0",
32
+ "@pnpm/deps.path": "1100.0.4",
33
+ "@pnpm/catalogs.types": "1100.0.0",
34
34
  "@pnpm/constants": "1100.0.0",
35
- "@pnpm/deps.path": "1100.0.3",
35
+ "@pnpm/config.parse-overrides": "1100.0.1",
36
36
  "@pnpm/error": "1100.0.0",
37
- "@pnpm/hooks.read-package-hook": "1100.0.3",
38
- "@pnpm/installing.client": "1100.1.0",
39
- "@pnpm/lockfile.fs": "1100.1.0",
40
- "@pnpm/resolving.npm-resolver": "1101.2.0",
41
- "@pnpm/catalogs.resolver": "1100.0.0",
42
- "@pnpm/types": "1101.1.0",
43
- "@pnpm/pkg-manifest.utils": "1100.1.4",
44
- "@pnpm/config.pick-registry-for-package": "1100.0.3",
45
- "@pnpm/lockfile.utils": "1100.0.8"
37
+ "@pnpm/lockfile.utils": "1100.0.9",
38
+ "@pnpm/pkg-manifest.utils": "1100.2.0",
39
+ "@pnpm/hooks.read-package-hook": "1100.0.4",
40
+ "@pnpm/types": "1101.1.1",
41
+ "@pnpm/installing.client": "1100.2.0",
42
+ "@pnpm/resolving.npm-resolver": "1101.3.0",
43
+ "@pnpm/config.version-policy": "1100.1.1",
44
+ "@pnpm/lockfile.fs": "1100.1.1"
46
45
  },
47
46
  "peerDependencies": {
48
47
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -51,9 +50,9 @@
51
50
  "@jest/globals": "30.3.0",
52
51
  "@types/ramda": "0.31.1",
53
52
  "@types/semver": "7.7.1",
54
- "@pnpm/deps.inspection.outdated": "1100.0.16",
53
+ "@pnpm/deps.inspection.outdated": "1100.1.0",
55
54
  "@pnpm/logger": "1100.0.0",
56
- "@pnpm/resolving.resolver-base": "1100.2.0"
55
+ "@pnpm/resolving.resolver-base": "1100.3.0"
57
56
  },
58
57
  "engines": {
59
58
  "node": ">=22.13"