@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.
- package/lib/audit/audit.js +5 -0
- package/lib/sbom/sbom.d.ts +4 -1
- package/lib/sbom/sbom.js +228 -35
- package/package.json +36 -36
package/lib/audit/audit.js
CHANGED
|
@@ -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
|
}
|
package/lib/sbom/sbom.d.ts
CHANGED
|
@@ -6,11 +6,14 @@ export type SbomCommandOptions = {
|
|
|
6
6
|
lockfileOnly?: boolean;
|
|
7
7
|
sbomAuthors?: string;
|
|
8
8
|
sbomSupplier?: string;
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
?
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
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
|
|
264
|
+
rootDescription,
|
|
145
265
|
rootAuthor,
|
|
146
266
|
rootRepository,
|
|
147
|
-
sbomType,
|
|
267
|
+
sbomType: serialOpts.sbomType,
|
|
148
268
|
include,
|
|
149
269
|
registries: opts.registries,
|
|
150
|
-
lockfileDir
|
|
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
|
+
"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": "^
|
|
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.
|
|
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.
|
|
45
|
-
"@pnpm/config.
|
|
46
|
-
"@pnpm/
|
|
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/
|
|
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": "^
|
|
67
|
+
"@pnpm/logger": "^1100.0.0"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
|
-
"@jest/globals": "30.
|
|
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.
|
|
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/
|
|
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"
|