@pnpm/deps.compliance.commands 1101.3.4 → 1101.4.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.
@@ -401,6 +401,11 @@ async function interactiveAuditFix(auditReport) {
401
401
  flatChoices.push({
402
402
  name: choice.message,
403
403
  value: choice.value,
404
+ // Same shape as the update prompt: `name` is the rendered table
405
+ // row, but the post-submission line uses `short` per choice.
406
+ // Without this, every selected row's full table dump is comma-
407
+ // joined back to stdout.
408
+ short: choice.value,
404
409
  });
405
410
  }
406
411
  }
@@ -6,11 +6,14 @@ export type SbomCommandOptions = {
6
6
  lockfileOnly?: boolean;
7
7
  sbomAuthors?: string;
8
8
  sbomSupplier?: string;
9
- } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'virtualStoreDirMaxLength'> & Pick<ConfigContext, 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir'> & Partial<Pick<Config, 'userConfig'>>;
9
+ out?: string;
10
+ split?: boolean;
11
+ } & Pick<Config, 'dev' | 'dir' | 'lockfileDir' | 'registries' | 'optional' | 'production' | 'storeDir' | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' | 'virtualStoreDirMaxLength'> & Pick<ConfigContext, 'allProjectsGraph' | 'selectedProjectsGraph' | 'rootProjectManifest' | 'rootProjectManifestDir'> & Partial<Pick<Config, 'userConfig'>>;
10
12
  export declare function rcOptionsTypes(): Record<string, unknown>;
11
13
  export declare const cliOptionsTypes: () => Record<string, unknown>;
12
14
  export declare const shorthands: Record<string, string>;
13
15
  export declare const commandNames: string[];
16
+ export declare const recursiveByDefault = true;
14
17
  export declare function help(): string;
15
18
  export declare function handler(opts: SbomCommandOptions, _params?: string[]): Promise<{
16
19
  output: string;
package/lib/sbom/sbom.js CHANGED
@@ -1,13 +1,16 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
1
3
  import { FILTERING } from '@pnpm/cli.common-cli-options-help';
2
4
  import { packageManager } from '@pnpm/cli.meta';
3
5
  import { docsUrl, readProjectManifestOnly } from '@pnpm/cli.utils';
4
6
  import { types as allTypes } from '@pnpm/config.reader';
5
7
  import { WANTED_LOCKFILE } from '@pnpm/constants';
6
8
  import { isSpdxLicenseExpression, resolveLicenseFromDir } from '@pnpm/deps.compliance.license-resolver';
7
- import { collectSbomComponents, serializeCycloneDx, serializeSpdx, } from '@pnpm/deps.compliance.sbom';
9
+ import { collectSbomComponents, resolveWorkspaceDeps, serializeCycloneDx, serializeSpdx, } from '@pnpm/deps.compliance.sbom';
8
10
  import { PnpmError } from '@pnpm/error';
9
11
  import { getLockfileImporterId, readWantedLockfile } from '@pnpm/lockfile.fs';
10
12
  import { getStorePath } from '@pnpm/store.path';
13
+ import pLimit from 'p-limit';
11
14
  import { pick } from 'ramda';
12
15
  import { renderHelp } from 'render-help';
13
16
  export function rcOptionsTypes() {
@@ -22,12 +25,15 @@ export const cliOptionsTypes = () => ({
22
25
  'sbom-authors': String,
23
26
  'sbom-supplier': String,
24
27
  'lockfile-only': Boolean,
28
+ out: String,
29
+ split: Boolean,
25
30
  });
26
31
  export const shorthands = {
27
32
  D: '--dev',
28
33
  P: '--production',
29
34
  };
30
35
  export const commandNames = ['sbom'];
36
+ export const recursiveByDefault = true;
31
37
  export function help() {
32
38
  return renderHelp({
33
39
  description: 'Generate a Software Bill of Materials (SBOM) for the project.',
@@ -73,6 +79,14 @@ export function help() {
73
79
  description: 'Don\'t include "optionalDependencies"',
74
80
  name: '--no-optional',
75
81
  },
82
+ {
83
+ description: 'Write SBOM to a file instead of stdout. Use %s for the package name and %v for the version.',
84
+ name: '--out <path>',
85
+ },
86
+ {
87
+ description: 'Generate a separate SBOM for each matched workspace package. Outputs NDJSON to stdout, or files when combined with --out.',
88
+ name: '--split',
89
+ },
76
90
  ],
77
91
  },
78
92
  FILTERING,
@@ -83,6 +97,9 @@ export function help() {
83
97
  'pnpm sbom --sbom-format spdx',
84
98
  'pnpm sbom --sbom-format cyclonedx --lockfile-only',
85
99
  'pnpm sbom --sbom-format spdx --prod',
100
+ 'pnpm sbom --sbom-format cyclonedx --filter ./apps/my-app',
101
+ 'pnpm sbom --sbom-format cyclonedx --out out/%s.cdx.json',
102
+ 'pnpm sbom --sbom-format cyclonedx --split',
86
103
  ],
87
104
  });
88
105
  }
@@ -96,73 +113,179 @@ export async function handler(opts, _params = []) {
96
113
  }
97
114
  const sbomType = validateSbomType(opts.sbomType);
98
115
  const sbomSpecVersion = validateSbomSpecVersion(opts.sbomSpecVersion, format);
116
+ const ctx = await buildSharedContext(opts);
117
+ const serialOpts = { format, sbomType, sbomSpecVersion };
118
+ // `%s` in --out only implies per-package output inside a workspace; in a
119
+ // single-project repo it is interpolated from the root component on the
120
+ // single-output path below.
121
+ const hasWorkspaceGraph = opts.selectedProjectsGraph != null || opts.allProjectsGraph != null;
122
+ const shouldSplit = opts.split || (opts.out != null && opts.out.includes('%s') && hasWorkspaceGraph);
123
+ if (shouldSplit) {
124
+ return handleSplit(opts, serialOpts, ctx);
125
+ }
126
+ const { output, rootName, rootVersion } = await generateSbomForProject(opts, serialOpts, ctx);
127
+ if (opts.out) {
128
+ const filePath = opts.out
129
+ .replaceAll('%s', sanitizePathSegment(sanitizePackageName(rootName)))
130
+ .replaceAll('%v', sanitizePathSegment(rootVersion));
131
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
132
+ fs.writeFileSync(filePath, output);
133
+ return { output: filePath, exitCode: 0 };
134
+ }
135
+ return { output, exitCode: 0 };
136
+ }
137
+ async function handleSplit(opts, serialOpts, ctx) {
138
+ const projectsGraph = opts.selectedProjectsGraph ?? opts.allProjectsGraph;
139
+ if (!projectsGraph) {
140
+ throw new PnpmError('SBOM_NO_PROJECTS', 'No workspace projects found. --split requires a workspace.');
141
+ }
142
+ if (opts.out && !opts.out.includes('%s')) {
143
+ throw new PnpmError('SBOM_OUT_MISSING_PLACEHOLDER', 'When using --split with --out, the path must contain %s as a placeholder for the package name.');
144
+ }
145
+ const entries = Object.entries(projectsGraph);
146
+ const ndjsonLines = [];
147
+ const files = [];
148
+ const writtenPaths = new Set();
149
+ const compact = !opts.out;
150
+ const createdDirs = new Set();
151
+ for (const [dir, entry] of entries) {
152
+ const manifest = entry.package.manifest;
153
+ if (!manifest.name)
154
+ continue;
155
+ const singleProjectGraph = { [dir]: entry };
156
+ // eslint-disable-next-line no-await-in-loop
157
+ const { output } = await generateSbomForProject({ ...opts, selectedProjectsGraph: singleProjectGraph, allProjectsGraph: undefined, split: false, out: undefined }, serialOpts, ctx, compact);
158
+ if (opts.out) {
159
+ const filePath = opts.out
160
+ .replaceAll('%s', sanitizePathSegment(sanitizePackageName(manifest.name)))
161
+ .replaceAll('%v', sanitizePathSegment(manifest.version ?? '0.0.0'));
162
+ // Sanitizing package names is lossy (e.g. "@a/b" and "a-b" both map to "a-b"),
163
+ // so two packages can resolve to the same path. Fail loudly instead of letting
164
+ // one SBOM silently overwrite another.
165
+ if (writtenPaths.has(filePath)) {
166
+ throw new PnpmError('SBOM_OUT_PATH_COLLISION', `Multiple workspace packages resolve to the same output path "${filePath}". Include %v in the --out pattern to disambiguate.`);
167
+ }
168
+ writtenPaths.add(filePath);
169
+ const fileDir = path.dirname(filePath);
170
+ if (!createdDirs.has(fileDir)) {
171
+ fs.mkdirSync(fileDir, { recursive: true });
172
+ createdDirs.add(fileDir);
173
+ }
174
+ fs.writeFileSync(filePath, output);
175
+ files.push(filePath);
176
+ }
177
+ else {
178
+ ndjsonLines.push(output);
179
+ }
180
+ }
181
+ if (opts.out) {
182
+ return {
183
+ output: `Generated ${files.length} SBOMs:\n${files.map((f) => ` ${f}`).join('\n')}`,
184
+ exitCode: 0,
185
+ };
186
+ }
187
+ return { output: ndjsonLines.join('\n'), exitCode: 0 };
188
+ }
189
+ async function buildSharedContext(opts) {
99
190
  const lockfile = await readWantedLockfile(opts.lockfileDir ?? opts.dir, {
100
191
  ignoreIncompatible: true,
101
192
  });
102
193
  if (lockfile == null) {
103
194
  throw new PnpmError('SBOM_NO_LOCKFILE', `No ${WANTED_LOCKFILE} found: Cannot generate SBOM without a lockfile`);
104
195
  }
196
+ const rootManifestDir = opts.rootProjectManifestDir ?? opts.dir;
197
+ const rootManifest = opts.rootProjectManifest ?? await readProjectManifestOnly(rootManifestDir);
198
+ let storeDir;
199
+ if (!opts.lockfileOnly) {
200
+ storeDir = await getStorePath({
201
+ pkgRoot: opts.dir,
202
+ storePath: opts.storeDir,
203
+ pnpmHomeDir: opts.pnpmHomeDir,
204
+ });
205
+ }
206
+ const lockfileDir = opts.lockfileDir ?? opts.dir;
207
+ const workspaceManifestsByImporterId = new Map();
208
+ for (const graph of [opts.allProjectsGraph, opts.selectedProjectsGraph]) {
209
+ if (!graph)
210
+ continue;
211
+ for (const [dir, entry] of Object.entries(graph)) {
212
+ workspaceManifestsByImporterId.set(getLockfileImporterId(lockfileDir, dir), entry.package.manifest);
213
+ }
214
+ }
215
+ const rootLicense = await resolveRootLicense(rootManifest, rootManifestDir);
216
+ return { lockfile, rootManifest, rootManifestDir, rootLicense, storeDir, workspaceManifestsByImporterId };
217
+ }
218
+ async function generateSbomForProject(opts, serialOpts, ctx, compact) {
219
+ const { lockfile, rootManifest, rootManifestDir, rootLicense: cachedRootLicense } = ctx;
105
220
  const include = {
106
221
  dependencies: opts.production !== false,
107
222
  devDependencies: opts.dev !== false,
108
223
  optionalDependencies: opts.optional !== false,
109
224
  };
110
- const manifest = await readProjectManifestOnly(opts.dir);
225
+ const selectedEntries = opts.selectedProjectsGraph
226
+ ? Object.entries(opts.selectedProjectsGraph)
227
+ : undefined;
228
+ const singleProject = selectedEntries?.length === 1
229
+ ? selectedEntries[0]
230
+ : undefined;
231
+ const manifest = singleProject
232
+ ? singleProject[1].package.manifest
233
+ : rootManifest;
234
+ const projectDir = singleProject
235
+ ? singleProject[0]
236
+ : rootManifestDir;
111
237
  const rootName = manifest.name ?? 'unknown';
112
238
  const rootVersion = manifest.version ?? '0.0.0';
113
- // Keep the root in sync with transitive deps: consult manifest `license` /
114
- // legacy `licenses` first, then fall back to an on-disk LICENSE file. Drop
115
- // file-scanned values that aren't SPDX-valid to avoid non-compliant output.
116
- const rootLicenseInfo = await resolveLicenseFromDir({ manifest, dir: opts.dir });
117
- const rootLicense = rootLicenseInfo && rootLicenseInfo.name !== 'Unknown' &&
118
- (!rootLicenseInfo.licenseFile || isSpdxLicenseExpression(rootLicenseInfo.name))
119
- ? rootLicenseInfo.name
120
- : undefined;
121
- const rootAuthor = typeof manifest.author === 'string'
122
- ? manifest.author
123
- : manifest.author?.name;
124
- const rootRepository = typeof manifest.repository === 'string'
125
- ? manifest.repository
126
- : manifest.repository?.url;
239
+ const rootLicense = singleProject
240
+ ? (await resolveRootLicense(manifest, projectDir) ?? cachedRootLicense)
241
+ : cachedRootLicense;
242
+ const rootAuthor = extractAuthor(manifest)
243
+ ?? (singleProject ? extractAuthor(rootManifest) : undefined);
244
+ const rootRepository = extractRepository(manifest)
245
+ ?? (singleProject ? extractRepository(rootManifest) : undefined);
246
+ const rootDescription = manifest.description
247
+ ?? (singleProject ? rootManifest.description : undefined);
248
+ const lockfileDir = opts.lockfileDir ?? opts.dir;
127
249
  const includedImporterIds = opts.selectedProjectsGraph
128
250
  ? Object.keys(opts.selectedProjectsGraph)
129
- .map((p) => getLockfileImporterId(opts.lockfileDir ?? opts.dir, p))
251
+ .map((p) => getLockfileImporterId(lockfileDir, p))
252
+ : undefined;
253
+ const resolvedWorkspaceDeps = opts.lockfileOnly
254
+ ? undefined
255
+ : resolveWorkspaceDeps(lockfile, includedImporterIds ?? Object.keys(lockfile.importers), include);
256
+ const workspacePackages = resolvedWorkspaceDeps
257
+ ? await buildWorkspacePackagesMap(resolvedWorkspaceDeps.additionalImporterIds, lockfileDir, ctx.workspaceManifestsByImporterId)
130
258
  : undefined;
131
- let storeDir;
132
- if (!opts.lockfileOnly) {
133
- storeDir = await getStorePath({
134
- pkgRoot: opts.dir,
135
- storePath: opts.storeDir,
136
- pnpmHomeDir: opts.pnpmHomeDir,
137
- });
138
- }
139
259
  const result = await collectSbomComponents({
140
260
  lockfile,
141
261
  rootName,
142
262
  rootVersion,
143
263
  rootLicense,
144
- rootDescription: manifest.description,
264
+ rootDescription,
145
265
  rootAuthor,
146
266
  rootRepository,
147
- sbomType,
267
+ sbomType: serialOpts.sbomType,
148
268
  include,
149
269
  registries: opts.registries,
150
- lockfileDir: opts.lockfileDir ?? opts.dir,
270
+ lockfileDir,
151
271
  includedImporterIds,
152
272
  lockfileOnly: opts.lockfileOnly,
153
- storeDir,
273
+ storeDir: ctx.storeDir,
154
274
  virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
275
+ workspacePackages,
276
+ resolvedWorkspaceDeps,
155
277
  });
156
- const output = format === 'cyclonedx'
278
+ const output = serialOpts.format === 'cyclonedx'
157
279
  ? serializeCycloneDx(result, {
158
280
  pnpmVersion: packageManager.version,
159
281
  lockfileOnly: opts.lockfileOnly,
160
282
  sbomAuthors: opts.sbomAuthors?.split(',').map((s) => s.trim()).filter(Boolean),
161
283
  sbomSupplier: opts.sbomSupplier,
162
- specVersion: sbomSpecVersion,
284
+ specVersion: serialOpts.sbomSpecVersion,
285
+ compact,
163
286
  })
164
- : serializeSpdx(result);
165
- return { output, exitCode: 0 };
287
+ : serializeSpdx(result, { compact });
288
+ return { output, exitCode: 0, rootName, rootVersion };
166
289
  }
167
290
  function validateSbomType(value) {
168
291
  if (!value || value === 'library')
@@ -171,8 +294,6 @@ function validateSbomType(value) {
171
294
  return 'application';
172
295
  throw new PnpmError('SBOM_INVALID_TYPE', `Invalid SBOM type "${value}". Use "library" or "application".`);
173
296
  }
174
- // Versions whose schema is fully covered by what we currently emit
175
- // (e.g. metadata.lifecycles requires CycloneDX 1.5+).
176
297
  const SUPPORTED_CYCLONEDX_SPEC_VERSIONS = ['1.5', '1.6', '1.7'];
177
298
  function validateSbomSpecVersion(value, format) {
178
299
  if (value == null)
@@ -186,4 +307,76 @@ function validateSbomSpecVersion(value, format) {
186
307
  }
187
308
  return normalized;
188
309
  }
310
+ async function resolveRootLicense(manifest, dir) {
311
+ // Skip the on-disk LICENSE probing when the manifest already declares a usable
312
+ // SPDX license; resolveLicenseFromDir would return the same value after the scan.
313
+ if (typeof manifest.license === 'string' && isSpdxLicenseExpression(manifest.license)) {
314
+ return manifest.license;
315
+ }
316
+ const info = await resolveLicenseFromDir({ manifest, dir });
317
+ if (info && info.name !== 'Unknown' && (!info.licenseFile || isSpdxLicenseExpression(info.name))) {
318
+ return info.name;
319
+ }
320
+ return undefined;
321
+ }
322
+ function extractAuthor(manifest) {
323
+ if (typeof manifest.author === 'string')
324
+ return manifest.author;
325
+ return manifest.author?.name;
326
+ }
327
+ function extractRepository(manifest) {
328
+ if (typeof manifest.repository === 'string')
329
+ return manifest.repository;
330
+ return manifest.repository?.url;
331
+ }
332
+ const WORKSPACE_MANIFEST_READ_CONCURRENCY = 8;
333
+ async function buildWorkspacePackagesMap(reachableImporterIds, lockfileDir, manifestsByImporterId) {
334
+ if (reachableImporterIds.length === 0)
335
+ return {};
336
+ const readManifest = pLimit(WORKSPACE_MANIFEST_READ_CONCURRENCY);
337
+ const entries = await Promise.all(reachableImporterIds.map((importerId) => readManifest(async () => {
338
+ const known = manifestsByImporterId.get(importerId);
339
+ const manifest = known
340
+ ? known
341
+ : isInsideDir(lockfileDir, importerId)
342
+ ? await readManifestSafe(path.join(lockfileDir, importerId))
343
+ : undefined;
344
+ if (!manifest?.name)
345
+ return null;
346
+ return [importerId, {
347
+ name: manifest.name,
348
+ version: manifest.version ?? '0.0.0',
349
+ license: typeof manifest.license === 'string' ? manifest.license : undefined,
350
+ description: manifest.description,
351
+ author: extractAuthor(manifest),
352
+ repository: extractRepository(manifest),
353
+ }];
354
+ })));
355
+ return Object.fromEntries(entries.filter((e) => e !== null));
356
+ }
357
+ async function readManifestSafe(dir) {
358
+ try {
359
+ return await readProjectManifestOnly(dir);
360
+ }
361
+ catch {
362
+ return undefined;
363
+ }
364
+ }
365
+ function sanitizePackageName(name) {
366
+ return name.replace(/^@/, '').replace(/\//g, '-');
367
+ }
368
+ function sanitizePathSegment(value) {
369
+ // Control characters (e.g. newlines) would produce confusing filenames and could
370
+ // inject extra lines into the printed `--split --out` summary; filesystem
371
+ // metacharacters are replaced so the value stays a single path segment.
372
+ // eslint-disable-next-line no-control-regex
373
+ const sanitized = value.replace(/[/\\:*?"<>|\x00-\x1F\x7F]/g, '-');
374
+ // `.`, `..`, or a blank value would let a crafted name/version escape or replace
375
+ // the intended output directory once interpolated into an `--out` template.
376
+ return sanitized === '.' || sanitized === '..' || sanitized.trim() === '' ? '-' : sanitized;
377
+ }
378
+ function isInsideDir(parentDir, childRelativePath) {
379
+ const rel = path.relative(parentDir, path.resolve(parentDir, childRelativePath));
380
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
381
+ }
189
382
  //# sourceMappingURL=sbom.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.compliance.commands",
3
- "version": "1101.3.4",
3
+ "version": "1101.4.0",
4
4
  "description": "pnpm commands for audit, licenses, and sbom",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -33,56 +33,56 @@
33
33
  "@inquirer/prompts": "^8.4.3",
34
34
  "@zkochan/table": "^2.0.1",
35
35
  "chalk": "^5.6.2",
36
- "memoize": "^10.2.0",
36
+ "memoize": "^11.0.0",
37
+ "p-limit": "^7.3.0",
37
38
  "ramda": "npm:@pnpm/ramda@0.28.1",
38
39
  "render-help": "^2.0.0",
39
- "semver": "^7.8.1",
40
+ "semver": "^7.8.4",
40
41
  "@pnpm/cli.common-cli-options-help": "1100.0.2",
41
- "@pnpm/config.reader": "1101.8.0",
42
- "@pnpm/cli.utils": "1101.0.11",
43
42
  "@pnpm/cli.command": "1100.0.1",
44
- "@pnpm/cli.meta": "1100.0.7",
45
- "@pnpm/config.writer": "1100.0.12",
46
- "@pnpm/deps.compliance.audit": "1101.0.15",
47
- "@pnpm/deps.security.signatures": "1101.2.1",
48
- "@pnpm/installing.commands": "1100.8.0",
49
- "@pnpm/config.pick-registry-for-package": "1100.0.8",
50
- "@pnpm/error": "1100.0.0",
51
- "@pnpm/lockfile.fs": "1100.1.4",
52
- "@pnpm/lockfile.types": "1100.0.10",
53
- "@pnpm/deps.compliance.license-scanner": "1100.0.18",
54
- "@pnpm/lockfile.walker": "1100.0.10",
55
- "@pnpm/constants": "1100.0.0",
56
- "@pnpm/lockfile.utils": "1100.0.12",
57
- "@pnpm/network.auth-header": "1101.1.1",
58
- "@pnpm/object.key-sorting": "1100.0.0",
59
- "@pnpm/store.path": "1100.0.1",
60
- "@pnpm/types": "1101.3.1",
61
- "@pnpm/workspace.project-manifest-reader": "1100.0.12",
43
+ "@pnpm/cli.meta": "1100.0.8",
44
+ "@pnpm/config.reader": "1101.10.0",
45
+ "@pnpm/config.writer": "1100.0.13",
62
46
  "@pnpm/deps.compliance.license-resolver": "1100.0.0",
63
- "@pnpm/deps.compliance.sbom": "1100.1.8"
47
+ "@pnpm/constants": "1100.0.0",
48
+ "@pnpm/deps.compliance.audit": "1101.0.17",
49
+ "@pnpm/cli.utils": "1101.0.12",
50
+ "@pnpm/deps.compliance.license-scanner": "1100.0.20",
51
+ "@pnpm/deps.security.signatures": "1101.2.2",
52
+ "@pnpm/error": "1100.0.0",
53
+ "@pnpm/deps.compliance.sbom": "1100.2.0",
54
+ "@pnpm/installing.commands": "1100.10.0",
55
+ "@pnpm/lockfile.fs": "1100.1.6",
56
+ "@pnpm/config.pick-registry-for-package": "1100.0.9",
57
+ "@pnpm/lockfile.types": "1100.0.11",
58
+ "@pnpm/lockfile.walker": "1100.0.11",
59
+ "@pnpm/network.auth-header": "1101.1.2",
60
+ "@pnpm/object.key-sorting": "1100.0.1",
61
+ "@pnpm/types": "1101.3.2",
62
+ "@pnpm/lockfile.utils": "1100.0.13",
63
+ "@pnpm/workspace.project-manifest-reader": "1100.0.13",
64
+ "@pnpm/store.path": "1100.0.1"
64
65
  },
65
66
  "peerDependencies": {
66
- "@pnpm/logger": "^1001.0.1"
67
+ "@pnpm/logger": "^1100.0.0"
67
68
  },
68
69
  "devDependencies": {
69
- "@jest/globals": "30.3.0",
70
+ "@jest/globals": "30.4.1",
70
71
  "@types/ramda": "0.31.1",
71
72
  "@types/semver": "7.7.1",
72
- "@types/zkochan__table": "npm:@types/table@6.0.0",
73
+ "@types/zkochan__table": "npm:@types/table@6.3.2",
73
74
  "load-json-file": "^7.0.1",
74
- "nock": "13.3.4",
75
75
  "read-yaml-file": "^3.0.0",
76
76
  "tempy": "3.0.0",
77
- "@pnpm/deps.compliance.commands": "1101.3.4",
78
- "@pnpm/prepare": "1100.0.15",
79
- "@pnpm/test-fixtures": "1100.0.0",
80
- "@pnpm/testing.registry-mock": "1100.0.5",
81
- "@pnpm/testing.command-defaults": "1100.0.5",
82
- "@pnpm/workspace.projects-filter": "1100.0.20",
83
- "@pnpm/pkg-manifest.reader": "1100.0.7",
84
77
  "@pnpm/logger": "1100.0.0",
85
- "@pnpm/testing.mock-agent": "1101.0.2"
78
+ "@pnpm/pkg-manifest.reader": "1100.0.8",
79
+ "@pnpm/deps.compliance.commands": "1101.4.0",
80
+ "@pnpm/prepare": "1100.0.16",
81
+ "@pnpm/testing.command-defaults": "1100.0.6",
82
+ "@pnpm/testing.registry-mock": "1100.0.6",
83
+ "@pnpm/test-fixtures": "1100.0.0",
84
+ "@pnpm/workspace.projects-filter": "1100.0.22",
85
+ "@pnpm/testing.mock-agent": "1101.0.3"
86
86
  },
87
87
  "engines": {
88
88
  "node": ">=22.13"