@pnpm/deps.compliance.commands 1000.0.0 → 1100.0.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.
@@ -1,4 +1,4 @@
1
- import { type Config, type UniversalOptions } from '@pnpm/config.reader';
1
+ import { type Config, type ConfigContext, type UniversalOptions } from '@pnpm/config.reader';
2
2
  import { type AuditReport } from '@pnpm/deps.compliance.audit';
3
3
  import { type InstallCommandOptions } from '@pnpm/installing.commands';
4
4
  import type { Registries } from '@pnpm/types';
@@ -7,6 +7,7 @@ export declare function rcOptionsTypes(): Record<string, unknown>;
7
7
  export declare function cliOptionsTypes(): Record<string, unknown>;
8
8
  export declare const shorthands: Record<string, string>;
9
9
  export declare const commandNames: string[];
10
+ export declare const recursiveByDefault = true;
10
11
  export declare function help(): string;
11
12
  export type AuditOptions = Pick<UniversalOptions, 'dir'> & {
12
13
  fix?: boolean | 'override' | 'update';
@@ -16,7 +17,7 @@ export type AuditOptions = Pick<UniversalOptions, 'dir'> & {
16
17
  registries: Registries;
17
18
  ignore?: string[];
18
19
  ignoreUnfixable?: boolean;
19
- } & Pick<Config, 'auditConfig' | 'auditLevel' | 'ca' | 'cert' | 'httpProxy' | 'httpsProxy' | 'key' | 'localAddress' | 'maxSockets' | 'noProxy' | 'strictSsl' | 'fetchRetries' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchRetryFactor' | 'fetchTimeout' | 'production' | 'dev' | 'overrides' | 'optional' | 'userConfig' | 'rawConfig' | 'rootProjectManifest' | 'rootProjectManifestDir' | 'virtualStoreDirMaxLength' | 'workspaceDir'> & InstallCommandOptions;
20
+ } & Pick<Config, 'auditConfig' | 'auditLevel' | 'minimumReleaseAge' | 'ca' | 'cert' | 'httpProxy' | 'httpsProxy' | 'key' | 'localAddress' | 'maxSockets' | 'noProxy' | 'strictSsl' | 'fetchRetries' | 'fetchRetryMaxtimeout' | 'fetchRetryMintimeout' | 'fetchRetryFactor' | 'fetchTimeout' | 'production' | 'dev' | 'overrides' | 'optional' | 'configByUri' | 'virtualStoreDirMaxLength' | 'workspaceDir'> & Pick<ConfigContext, 'rootProjectManifest' | 'rootProjectManifestDir'> & InstallCommandOptions;
20
21
  export declare function handler(opts: AuditOptions): Promise<{
21
22
  exitCode: number;
22
23
  output: string;
@@ -69,6 +69,7 @@ export const shorthands = {
69
69
  P: '--production',
70
70
  };
71
71
  export const commandNames = ['audit'];
72
+ export const recursiveByDefault = true;
72
73
  export function help() {
73
74
  return renderHelp({
74
75
  description: 'Checks for known security issues with the installed packages.',
@@ -135,10 +136,10 @@ export async function handler(opts) {
135
136
  optionalDependencies: opts.optional !== false,
136
137
  };
137
138
  let auditReport;
138
- const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.rawConfig, userSettings: opts.userConfig });
139
+ const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri, opts.registries?.default);
139
140
  try {
140
141
  auditReport = await audit(lockfile, getAuthHeader, {
141
- agentOptions: {
142
+ dispatcherOptions: {
142
143
  ca: opts.ca,
143
144
  cert: opts.cert,
144
145
  httpProxy: opts.httpProxy,
@@ -188,26 +189,34 @@ export async function handler(opts) {
188
189
  }
189
190
  if (fixMethod === 'update') {
190
191
  const result = await fixWithUpdate(auditReport, { ...opts, include });
192
+ let output = formatFixWithUpdateOutput(result, auditReport);
193
+ if (result.addedAgeExcludes.length > 0) {
194
+ output += `\n${result.addedAgeExcludes.length} entries were added to minimumReleaseAgeExclude to allow installing the patched versions:\n${result.addedAgeExcludes.join('\n')}\n`;
195
+ }
191
196
  return {
192
197
  exitCode: result.remaining.length > 0 ? 1 : 0,
193
- output: formatFixWithUpdateOutput(result, auditReport),
198
+ output,
194
199
  };
195
200
  }
196
201
  if (fixMethod === 'override') {
197
- const newOverrides = await fix(auditReport, opts);
198
- if (Object.values(newOverrides).length === 0) {
202
+ const { vulnOverrides, addedAgeExcludes } = await fix(auditReport, opts);
203
+ if (Object.values(vulnOverrides).length === 0) {
199
204
  return {
200
205
  exitCode: 0,
201
206
  output: 'No fixes were made',
202
207
  };
203
208
  }
204
- return {
205
- exitCode: 0,
206
- output: `${Object.values(newOverrides).length} overrides were added to package.json to fix vulnerabilities.
209
+ let output = `${Object.values(vulnOverrides).length} overrides were added to pnpm-workspace.yaml to fix vulnerabilities.
207
210
  Run "pnpm install" to apply the fixes.
208
211
 
209
212
  The added overrides:
210
- ${JSON.stringify(newOverrides, null, 2)}`,
213
+ ${JSON.stringify(vulnOverrides, null, 2)}`;
214
+ if (addedAgeExcludes.length > 0) {
215
+ output += `\n\n${addedAgeExcludes.length} entries were added to minimumReleaseAgeExclude to allow installing the patched versions:\n${addedAgeExcludes.join('\n')}`;
216
+ }
217
+ return {
218
+ exitCode: 0,
219
+ output,
211
220
  };
212
221
  }
213
222
  if (opts.ignore !== undefined || opts.ignoreUnfixable) {
@@ -1,3 +1,8 @@
1
- import type { AuditReport } from '@pnpm/deps.compliance.audit';
1
+ import type { AuditAdvisory, AuditReport } from '@pnpm/deps.compliance.audit';
2
2
  import type { AuditOptions } from './audit.js';
3
- export declare function fix(auditReport: AuditReport, opts: AuditOptions): Promise<Record<string, string>>;
3
+ export interface FixResult {
4
+ vulnOverrides: Record<string, string>;
5
+ addedAgeExcludes: string[];
6
+ }
7
+ export declare function fix(auditReport: AuditReport, opts: AuditOptions): Promise<FixResult>;
8
+ export declare function createMinimumReleaseAgeExcludes(advisories: AuditAdvisory[]): string[];
package/lib/audit/fix.js CHANGED
@@ -1,26 +1,46 @@
1
1
  import { writeSettings } from '@pnpm/config.writer';
2
2
  import { difference } from 'ramda';
3
+ import semver from 'semver';
3
4
  export async function fix(auditReport, opts) {
4
- const vulnOverrides = createOverrides(Object.values(auditReport.advisories), opts.auditConfig?.ignoreCves);
5
+ const fixableAdvisories = getFixableAdvisories(Object.values(auditReport.advisories), opts.auditConfig?.ignoreCves);
6
+ const vulnOverrides = createOverrides(fixableAdvisories);
5
7
  if (Object.values(vulnOverrides).length === 0)
6
- return vulnOverrides;
8
+ return { vulnOverrides, addedAgeExcludes: [] };
9
+ const addedAgeExcludes = opts.minimumReleaseAge ? createMinimumReleaseAgeExcludes(fixableAdvisories) : [];
7
10
  await writeSettings({
8
11
  updatedOverrides: vulnOverrides,
12
+ addedMinimumReleaseAgeExcludes: addedAgeExcludes.length > 0 ? addedAgeExcludes : undefined,
9
13
  rootProjectManifest: opts.rootProjectManifest,
10
14
  rootProjectManifestDir: opts.rootProjectManifestDir,
11
15
  workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
12
16
  });
13
- return vulnOverrides;
17
+ return { vulnOverrides, addedAgeExcludes };
14
18
  }
15
- function createOverrides(advisories, ignoreCves) {
19
+ function getFixableAdvisories(advisories, ignoreCves) {
16
20
  if (ignoreCves) {
17
21
  advisories = advisories.filter(({ cves }) => difference(cves, ignoreCves).length > 0);
18
22
  }
19
- return Object.fromEntries(advisories
20
- .filter(({ vulnerable_versions: vulnerableVersions, patched_versions: patchedVersions }) => vulnerableVersions !== '>=0.0.0' && patchedVersions !== '<0.0.0')
21
- .map((advisory) => [
23
+ return advisories
24
+ .filter(({ vulnerable_versions: vulnerableVersions, patched_versions: patchedVersions }) => vulnerableVersions !== '>=0.0.0' && patchedVersions !== '<0.0.0');
25
+ }
26
+ function createOverrides(advisories) {
27
+ return Object.fromEntries(advisories.map((advisory) => [
22
28
  `${advisory.module_name}@${advisory.vulnerable_versions}`,
23
29
  advisory.patched_versions,
24
30
  ]));
25
31
  }
32
+ export function createMinimumReleaseAgeExcludes(advisories) {
33
+ const excludes = new Set();
34
+ for (const advisory of advisories) {
35
+ if (advisory.patched_versions === '<0.0.0')
36
+ continue;
37
+ if (advisory.vulnerable_versions === '>=0.0.0' || advisory.vulnerable_versions === '*')
38
+ continue;
39
+ const minVersion = semver.minVersion(advisory.patched_versions);
40
+ if (minVersion) {
41
+ excludes.add(`${advisory.module_name}@${minVersion.version}`);
42
+ }
43
+ }
44
+ return Array.from(excludes);
45
+ }
26
46
  //# sourceMappingURL=fix.js.map
@@ -4,6 +4,7 @@ import type { AuditOptions } from './audit.js';
4
4
  export interface FixWithUpdateResult {
5
5
  fixed: number[];
6
6
  remaining: number[];
7
+ addedAgeExcludes: string[];
7
8
  }
8
9
  export type FixWithUpdateOptions = AuditOptions & {
9
10
  include?: {
@@ -1,8 +1,10 @@
1
+ import { writeSettings } from '@pnpm/config.writer';
1
2
  import { WANTED_LOCKFILE } from '@pnpm/constants';
2
3
  import { PnpmError } from '@pnpm/error';
3
4
  import { update } from '@pnpm/installing.commands';
4
5
  import { readWantedLockfile } from '@pnpm/lockfile.fs';
5
6
  import semver from 'semver';
7
+ import { createMinimumReleaseAgeExcludes } from './fix.js';
6
8
  import { lockfileToPackages } from './lockfileToPackages.js';
7
9
  export async function fixWithUpdate(auditReport, opts) {
8
10
  const vulnerabilitiesByPackage = new Map();
@@ -58,8 +60,22 @@ export async function fixWithUpdate(auditReport, opts) {
58
60
  return allVulnerabilities;
59
61
  },
60
62
  };
63
+ // Add minimum patched versions to minimumReleaseAgeExclude so the resolver
64
+ // can install them even when minimumReleaseAge would otherwise block them.
65
+ const addedAgeExcludes = opts.minimumReleaseAge ? createMinimumReleaseAgeExcludes(Object.values(auditReport.advisories)) : [];
66
+ const updateOpts = { ...opts };
67
+ if (addedAgeExcludes.length > 0) {
68
+ const existing = updateOpts.minimumReleaseAgeExclude ?? [];
69
+ updateOpts.minimumReleaseAgeExclude = [...existing, ...addedAgeExcludes];
70
+ await writeSettings({
71
+ addedMinimumReleaseAgeExcludes: addedAgeExcludes,
72
+ rootProjectManifest: opts.rootProjectManifest,
73
+ rootProjectManifestDir: opts.rootProjectManifestDir,
74
+ workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
75
+ });
76
+ }
61
77
  await update.handler({
62
- ...opts,
78
+ ...updateOpts,
63
79
  packageVulnerabilityAudit,
64
80
  }, []);
65
81
  const lockfileDir = opts.lockfileDir ?? opts.dir;
@@ -105,6 +121,6 @@ export async function fixWithUpdate(auditReport, opts) {
105
121
  fixed.push(...unfixableIds);
106
122
  }
107
123
  }
108
- return { fixed, remaining };
124
+ return { fixed, remaining, addedAgeExcludes };
109
125
  }
110
126
  //# sourceMappingURL=fixWithUpdate.js.map
@@ -1,9 +1,9 @@
1
- import type { Config } from '@pnpm/config.reader';
1
+ import type { Config, ConfigContext } from '@pnpm/config.reader';
2
2
  import type { LicensesCommandResult } from './LicensesCommandResult.js';
3
3
  export type LicensesCommandOptions = {
4
4
  compatible?: boolean;
5
5
  long?: boolean;
6
6
  recursive?: boolean;
7
7
  json?: boolean;
8
- } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir' | 'supportedArchitectures' | 'virtualStoreDirMaxLength'> & Partial<Pick<Config, 'userConfig'>>;
8
+ } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'supportedArchitectures' | 'virtualStoreDirMaxLength'> & Pick<ConfigContext, 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir'> & Partial<Pick<Config, 'userConfig'>>;
9
9
  export declare function licensesList(opts: LicensesCommandOptions): Promise<LicensesCommandResult>;
@@ -1,11 +1,11 @@
1
- import { type Config } from '@pnpm/config.reader';
1
+ import { type Config, type ConfigContext } from '@pnpm/config.reader';
2
2
  export type SbomCommandOptions = {
3
3
  sbomFormat?: string;
4
4
  sbomType?: string;
5
5
  lockfileOnly?: boolean;
6
6
  sbomAuthors?: string;
7
7
  sbomSupplier?: string;
8
- } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir' | 'virtualStoreDirMaxLength'> & Partial<Pick<Config, 'userConfig'>>;
8
+ } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'virtualStoreDirMaxLength'> & Pick<ConfigContext, 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir'> & Partial<Pick<Config, 'userConfig'>>;
9
9
  export declare function rcOptionsTypes(): Record<string, unknown>;
10
10
  export declare const cliOptionsTypes: () => Record<string, unknown>;
11
11
  export declare const shorthands: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.compliance.commands",
3
- "version": "1000.0.0",
3
+ "version": "1100.0.0",
4
4
  "description": "pnpm commands for audit, licenses, and sbom",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -33,41 +33,43 @@
33
33
  "ramda": "npm:@pnpm/ramda@0.28.1",
34
34
  "render-help": "^2.0.0",
35
35
  "semver": "^7.7.2",
36
- "@pnpm/cli.command": "1000.0.0",
37
- "@pnpm/cli.common-cli-options-help": "1000.0.1",
38
- "@pnpm/cli.utils": "1001.2.8",
39
- "@pnpm/config.reader": "1004.4.2",
40
- "@pnpm/cli.meta": "1000.0.11",
41
- "@pnpm/constants": "1001.3.1",
42
- "@pnpm/deps.compliance.audit": "1002.0.14",
43
- "@pnpm/deps.compliance.license-scanner": "1001.0.27",
44
- "@pnpm/deps.compliance.sbom": "1000.0.0-0",
45
- "@pnpm/error": "1000.0.5",
46
- "@pnpm/installing.commands": "1004.6.10",
47
- "@pnpm/lockfile.fs": "1001.1.21",
48
- "@pnpm/config.writer": "1000.0.14",
49
- "@pnpm/lockfile.utils": "1003.0.3",
50
- "@pnpm/lockfile.types": "1002.0.2",
51
- "@pnpm/store.path": "1000.0.5",
52
- "@pnpm/network.auth-header": "1000.0.6",
53
- "@pnpm/types": "1000.9.0",
54
- "@pnpm/lockfile.walker": "1001.0.16",
55
- "@pnpm/workspace.project-manifest-reader": "1001.1.4"
36
+ "@pnpm/cli.command": "1100.0.0",
37
+ "@pnpm/cli.common-cli-options-help": "1100.0.0",
38
+ "@pnpm/cli.meta": "1100.0.0",
39
+ "@pnpm/config.writer": "1100.0.0",
40
+ "@pnpm/constants": "1100.0.0",
41
+ "@pnpm/deps.compliance.audit": "1100.0.0",
42
+ "@pnpm/deps.compliance.license-scanner": "1100.0.0",
43
+ "@pnpm/config.reader": "1100.0.0",
44
+ "@pnpm/deps.compliance.sbom": "1100.0.0",
45
+ "@pnpm/error": "1100.0.0",
46
+ "@pnpm/installing.commands": "1100.0.0",
47
+ "@pnpm/lockfile.fs": "1100.0.0",
48
+ "@pnpm/lockfile.types": "1100.0.0",
49
+ "@pnpm/lockfile.utils": "1100.0.0",
50
+ "@pnpm/lockfile.walker": "1100.0.0",
51
+ "@pnpm/cli.utils": "1100.0.0",
52
+ "@pnpm/store.path": "1100.0.0",
53
+ "@pnpm/network.auth-header": "1100.0.0",
54
+ "@pnpm/types": "1100.0.0",
55
+ "@pnpm/workspace.project-manifest-reader": "1100.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@pnpm/registry-mock": "5.2.4",
59
- "@types/ramda": "0.29.12",
58
+ "@pnpm/registry-mock": "6.0.0",
59
+ "@types/ramda": "0.31.1",
60
60
  "@types/semver": "7.7.1",
61
61
  "@types/zkochan__table": "npm:@types/table@6.0.0",
62
62
  "load-json-file": "^7.0.1",
63
63
  "nock": "13.3.4",
64
64
  "read-yaml-file": "^3.0.0",
65
65
  "tempy": "3.0.0",
66
- "@pnpm/deps.compliance.commands": "1000.0.0",
67
- "@pnpm/pkg-manifest.reader": "1000.1.2",
68
- "@pnpm/workspace.projects-filter": "1000.0.43",
69
- "@pnpm/test-fixtures": "1000.0.0",
70
- "@pnpm/prepare": "1000.0.4"
66
+ "@pnpm/deps.compliance.commands": "1100.0.0",
67
+ "@pnpm/prepare": "1100.0.0",
68
+ "@pnpm/pkg-manifest.reader": "1100.0.0",
69
+ "@pnpm/testing.command-defaults": "1100.0.0",
70
+ "@pnpm/testing.mock-agent": "1100.0.0",
71
+ "@pnpm/workspace.projects-filter": "1100.0.0",
72
+ "@pnpm/test-fixtures": "1100.0.0"
71
73
  },
72
74
  "engines": {
73
75
  "node": ">=22.13"
@@ -77,9 +79,9 @@
77
79
  },
78
80
  "scripts": {
79
81
  "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
80
- "_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest",
81
- "test": "pnpm run compile && pnpm run _test",
82
- "compile": "tsgo --build && pnpm run lint --fix",
83
- "update-responses": "node test/audit/utils/responses/update.ts"
82
+ "test": "pn compile && pn .test",
83
+ "compile": "tsgo --build && pn lint --fix",
84
+ "update-responses": "node test/audit/utils/responses/update.ts",
85
+ ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
84
86
  }
85
87
  }