@pnpm/deps.compliance.sbom 1100.1.9 → 1100.3.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/collectComponents.d.ts +25 -0
- package/lib/collectComponents.js +131 -4
- package/lib/getPkgMetadata.d.ts +2 -0
- package/lib/getPkgMetadata.js +38 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/serializeCycloneDx.d.ts +1 -0
- package/lib/serializeCycloneDx.js +29 -5
- package/lib/serializeSpdx.d.ts +4 -1
- package/lib/serializeSpdx.js +2 -2
- package/lib/types.d.ts +2 -0
- package/package.json +13 -13
|
@@ -2,6 +2,14 @@ import type { LockfileObject } from '@pnpm/lockfile.types';
|
|
|
2
2
|
import type { Resolution } from '@pnpm/resolving.resolver-base';
|
|
3
3
|
import type { DependenciesField, ProjectId, Registries } from '@pnpm/types';
|
|
4
4
|
import type { SbomComponentType, SbomResult } from './types.js';
|
|
5
|
+
export interface WorkspacePackageInfo {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
license?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
author?: string;
|
|
11
|
+
repository?: string;
|
|
12
|
+
}
|
|
5
13
|
export interface CollectSbomComponentsOptions {
|
|
6
14
|
lockfile: LockfileObject;
|
|
7
15
|
rootName: string;
|
|
@@ -10,6 +18,7 @@ export interface CollectSbomComponentsOptions {
|
|
|
10
18
|
rootDescription?: string;
|
|
11
19
|
rootAuthor?: string;
|
|
12
20
|
rootRepository?: string;
|
|
21
|
+
rootBugsUrl?: string;
|
|
13
22
|
sbomType?: SbomComponentType;
|
|
14
23
|
include?: {
|
|
15
24
|
[dependenciesField in DependenciesField]: boolean;
|
|
@@ -20,6 +29,22 @@ export interface CollectSbomComponentsOptions {
|
|
|
20
29
|
lockfileOnly?: boolean;
|
|
21
30
|
storeDir?: string;
|
|
22
31
|
virtualStoreDirMaxLength?: number;
|
|
32
|
+
workspacePackages?: Record<ProjectId, WorkspacePackageInfo>;
|
|
33
|
+
resolvedWorkspaceDeps?: ReturnType<typeof resolveWorkspaceDeps>;
|
|
34
|
+
excludePeerNamesByImporter?: Map<string, Set<string>>;
|
|
23
35
|
}
|
|
24
36
|
export declare function collectSbomComponents(opts: CollectSbomComponentsOptions): Promise<SbomResult>;
|
|
25
37
|
export declare function gitDownloadUrl(resolution: Resolution): string | undefined;
|
|
38
|
+
interface WorkspaceLink {
|
|
39
|
+
sourceImporterId: ProjectId;
|
|
40
|
+
targetImporterId: ProjectId;
|
|
41
|
+
depName: string;
|
|
42
|
+
devOnly: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function resolveWorkspaceDeps(lockfile: LockfileObject, importerIds: ProjectId[], include?: {
|
|
45
|
+
[dependenciesField in DependenciesField]: boolean;
|
|
46
|
+
}): {
|
|
47
|
+
links: WorkspaceLink[];
|
|
48
|
+
additionalImporterIds: ProjectId[];
|
|
49
|
+
};
|
|
50
|
+
export {};
|
package/lib/collectComponents.js
CHANGED
|
@@ -1,16 +1,75 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { DepType, detectDepTypes } from '@pnpm/lockfile.detect-dep-types';
|
|
2
3
|
import { nameVerFromPkgSnapshot, pkgSnapshotToResolution } from '@pnpm/lockfile.utils';
|
|
3
4
|
import { lockfileWalkerGroupImporterSteps, } from '@pnpm/lockfile.walker';
|
|
4
5
|
import { StoreIndex } from '@pnpm/store.index';
|
|
6
|
+
import pLimit from 'p-limit';
|
|
5
7
|
import { getPkgMetadata } from './getPkgMetadata.js';
|
|
6
8
|
import { buildPurl, encodePurlName } from './purl.js';
|
|
9
|
+
const IMPORTER_WALK_CONCURRENCY = 8;
|
|
7
10
|
export async function collectSbomComponents(opts) {
|
|
8
11
|
const depTypes = detectDepTypes(opts.lockfile);
|
|
9
12
|
const importerIds = opts.includedImporterIds ?? Object.keys(opts.lockfile.importers);
|
|
10
|
-
const importerWalkers = lockfileWalkerGroupImporterSteps(opts.lockfile, importerIds, { include: opts.include });
|
|
11
13
|
const componentsMap = new Map();
|
|
12
14
|
const relationships = [];
|
|
13
15
|
const rootPurl = `pkg:npm/${encodePurlName(opts.rootName)}@${opts.rootVersion}`;
|
|
16
|
+
const workspaceDeps = opts.resolvedWorkspaceDeps
|
|
17
|
+
?? (opts.lockfileOnly
|
|
18
|
+
? { links: [], additionalImporterIds: [] }
|
|
19
|
+
: resolveWorkspaceDeps(opts.lockfile, importerIds, opts.include));
|
|
20
|
+
const allImporterIds = [...importerIds, ...workspaceDeps.additionalImporterIds];
|
|
21
|
+
// When excluding peers, walk each importer with its own `walked` set so one
|
|
22
|
+
// importer's peer can't suppress another's real dependency.
|
|
23
|
+
const importerWalkers = opts.excludePeerNamesByImporter
|
|
24
|
+
? allImporterIds.flatMap((importerId) => lockfileWalkerGroupImporterSteps(opts.lockfile, [importerId], { include: opts.include }))
|
|
25
|
+
: lockfileWalkerGroupImporterSteps(opts.lockfile, allImporterIds, { include: opts.include });
|
|
26
|
+
const importerIdSet = new Set(importerIds);
|
|
27
|
+
if (opts.workspacePackages) {
|
|
28
|
+
const workspaceDepTypes = new Map();
|
|
29
|
+
for (const dep of workspaceDeps.links) {
|
|
30
|
+
const info = opts.workspacePackages[dep.targetImporterId];
|
|
31
|
+
if (!info)
|
|
32
|
+
continue;
|
|
33
|
+
const purl = buildPurl({ name: info.name, version: info.version });
|
|
34
|
+
const current = workspaceDepTypes.get(purl);
|
|
35
|
+
if (!dep.devOnly) {
|
|
36
|
+
workspaceDepTypes.set(purl, DepType.ProdOnly);
|
|
37
|
+
}
|
|
38
|
+
else if (current === undefined) {
|
|
39
|
+
workspaceDepTypes.set(purl, DepType.DevOnly);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const dep of workspaceDeps.links) {
|
|
43
|
+
const info = opts.workspacePackages[dep.targetImporterId];
|
|
44
|
+
if (!info)
|
|
45
|
+
continue;
|
|
46
|
+
const purl = buildPurl({ name: info.name, version: info.version });
|
|
47
|
+
let parentPurl;
|
|
48
|
+
if (importerIdSet.has(dep.sourceImporterId)) {
|
|
49
|
+
parentPurl = rootPurl;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const sourceInfo = opts.workspacePackages[dep.sourceImporterId];
|
|
53
|
+
parentPurl = sourceInfo
|
|
54
|
+
? buildPurl({ name: sourceInfo.name, version: sourceInfo.version })
|
|
55
|
+
: rootPurl;
|
|
56
|
+
}
|
|
57
|
+
relationships.push({ from: parentPurl, to: purl });
|
|
58
|
+
if (!componentsMap.has(purl)) {
|
|
59
|
+
componentsMap.set(purl, {
|
|
60
|
+
name: info.name,
|
|
61
|
+
version: info.version,
|
|
62
|
+
purl,
|
|
63
|
+
depPath: `link:${dep.targetImporterId}`,
|
|
64
|
+
depType: workspaceDepTypes.get(purl) ?? DepType.ProdOnly,
|
|
65
|
+
license: info.license,
|
|
66
|
+
description: info.description,
|
|
67
|
+
author: info.author,
|
|
68
|
+
repository: info.repository,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
14
73
|
const storeIndex = (!opts.lockfileOnly && opts.storeDir)
|
|
15
74
|
? new StoreIndex(opts.storeDir)
|
|
16
75
|
: undefined;
|
|
@@ -22,9 +81,33 @@ export async function collectSbomComponents(opts) {
|
|
|
22
81
|
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength ?? 120,
|
|
23
82
|
}
|
|
24
83
|
: undefined;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
84
|
+
const walkImporter = pLimit(IMPORTER_WALK_CONCURRENCY);
|
|
85
|
+
await Promise.all(importerWalkers.map(({ importerId, step }) => walkImporter(async () => {
|
|
86
|
+
let parentPurl = rootPurl;
|
|
87
|
+
if (!importerIdSet.has(importerId)) {
|
|
88
|
+
const info = opts.workspacePackages?.[importerId];
|
|
89
|
+
// A reachable workspace importer with no resolved package info (e.g. its
|
|
90
|
+
// manifest could not be read) is skipped entirely; walking it would
|
|
91
|
+
// misattribute its dependencies to the root component.
|
|
92
|
+
if (!info)
|
|
93
|
+
return;
|
|
94
|
+
parentPurl = buildPurl({ name: info.name, version: info.version });
|
|
95
|
+
}
|
|
96
|
+
// Drop this importer's peer entries before walking. With the per-importer
|
|
97
|
+
// walk above, this prunes a peer's exclusive subtree without hiding a
|
|
98
|
+
// package that is also a real dependency here or in another importer.
|
|
99
|
+
const peerNames = opts.excludePeerNamesByImporter?.get(importerId);
|
|
100
|
+
const filteredStep = (peerNames?.size)
|
|
101
|
+
? {
|
|
102
|
+
...step,
|
|
103
|
+
dependencies: step.dependencies.filter((dep) => {
|
|
104
|
+
const { name } = nameVerFromPkgSnapshot(dep.depPath, dep.pkgSnapshot);
|
|
105
|
+
return !name || !peerNames.has(name);
|
|
106
|
+
}),
|
|
107
|
+
}
|
|
108
|
+
: step;
|
|
109
|
+
await walkStep(filteredStep, parentPurl, depTypes, componentsMap, relationships, opts, metadataOpts);
|
|
110
|
+
})));
|
|
28
111
|
storeIndex?.close();
|
|
29
112
|
return {
|
|
30
113
|
rootComponent: {
|
|
@@ -35,6 +118,7 @@ export async function collectSbomComponents(opts) {
|
|
|
35
118
|
description: opts.rootDescription,
|
|
36
119
|
author: opts.rootAuthor,
|
|
37
120
|
repository: opts.rootRepository,
|
|
121
|
+
bugsUrl: opts.rootBugsUrl,
|
|
38
122
|
},
|
|
39
123
|
components: Array.from(componentsMap.values()),
|
|
40
124
|
relationships,
|
|
@@ -79,4 +163,47 @@ export function gitDownloadUrl(resolution) {
|
|
|
79
163
|
const prefix = needsGitPlusPrefix ? 'git+' : '';
|
|
80
164
|
return `${prefix}${resolution.repo}#${resolution.commit}`;
|
|
81
165
|
}
|
|
166
|
+
export function resolveWorkspaceDeps(lockfile, importerIds, include) {
|
|
167
|
+
const links = [];
|
|
168
|
+
const visited = new Set(importerIds);
|
|
169
|
+
const queue = [...importerIds];
|
|
170
|
+
const additionalImporterIds = [];
|
|
171
|
+
for (let head = 0; head < queue.length; head++) {
|
|
172
|
+
const importerId = queue[head];
|
|
173
|
+
const snapshot = lockfile.importers[importerId];
|
|
174
|
+
if (!snapshot)
|
|
175
|
+
continue;
|
|
176
|
+
const devDepNames = new Set(Object.keys(snapshot.devDependencies ?? {}));
|
|
177
|
+
const prodDeps = {
|
|
178
|
+
...(include?.dependencies !== false ? snapshot.dependencies : {}),
|
|
179
|
+
...(include?.optionalDependencies !== false ? snapshot.optionalDependencies : {}),
|
|
180
|
+
};
|
|
181
|
+
const allDeps = {
|
|
182
|
+
...prodDeps,
|
|
183
|
+
...(include?.devDependencies !== false ? snapshot.devDependencies : {}),
|
|
184
|
+
};
|
|
185
|
+
for (const [depName, reference] of Object.entries(allDeps)) {
|
|
186
|
+
if (!reference.startsWith('link:'))
|
|
187
|
+
continue;
|
|
188
|
+
const linkPath = reference.slice(5);
|
|
189
|
+
const targetId = path.posix.normalize(importerId === '.' ? linkPath : path.posix.join(importerId, linkPath));
|
|
190
|
+
// A crafted lockfile can point a `link:` target outside the workspace root;
|
|
191
|
+
// such importer IDs must never be followed, as they later become filesystem reads.
|
|
192
|
+
if (path.posix.isAbsolute(targetId) || targetId === '..' || targetId.startsWith('../'))
|
|
193
|
+
continue;
|
|
194
|
+
// `in` would also match inherited keys (e.g. "toString"); a crafted lockfile
|
|
195
|
+
// must not be able to enqueue importer IDs that are not actually present.
|
|
196
|
+
if (!Object.prototype.hasOwnProperty.call(lockfile.importers, targetId))
|
|
197
|
+
continue;
|
|
198
|
+
const devOnly = devDepNames.has(depName) && !(depName in prodDeps);
|
|
199
|
+
links.push({ sourceImporterId: importerId, targetImporterId: targetId, depName, devOnly });
|
|
200
|
+
if (!visited.has(targetId)) {
|
|
201
|
+
visited.add(targetId);
|
|
202
|
+
additionalImporterIds.push(targetId);
|
|
203
|
+
queue.push(targetId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { links, additionalImporterIds };
|
|
208
|
+
}
|
|
82
209
|
//# sourceMappingURL=collectComponents.js.map
|
package/lib/getPkgMetadata.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface PkgMetadata {
|
|
|
7
7
|
author?: string;
|
|
8
8
|
homepage?: string;
|
|
9
9
|
repository?: string;
|
|
10
|
+
bugsUrl?: string;
|
|
10
11
|
}
|
|
11
12
|
export interface GetPkgMetadataOptions {
|
|
12
13
|
storeDir: string;
|
|
@@ -15,3 +16,4 @@ export interface GetPkgMetadataOptions {
|
|
|
15
16
|
virtualStoreDirMaxLength: number;
|
|
16
17
|
}
|
|
17
18
|
export declare function getPkgMetadata(depPath: DepPath, snapshot: PackageSnapshot, registries: Registries, opts: GetPkgMetadataOptions): Promise<PkgMetadata>;
|
|
19
|
+
export declare function bugsUrlFromField(field: unknown): string | undefined;
|
package/lib/getPkgMetadata.js
CHANGED
|
@@ -34,6 +34,7 @@ async function extractMetadata(manifest, files) {
|
|
|
34
34
|
author: parseAuthorField(manifest.author),
|
|
35
35
|
homepage: manifest.homepage,
|
|
36
36
|
repository: parseRepositoryField(manifest.repository),
|
|
37
|
+
bugsUrl: bugsUrlFromField(manifest.bugs),
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
// Drop:
|
|
@@ -68,4 +69,41 @@ function parseRepositoryField(field) {
|
|
|
68
69
|
}
|
|
69
70
|
return undefined;
|
|
70
71
|
}
|
|
72
|
+
// `bugs` may be a URL string, a bare email, or `{ url, email }`. The CycloneDX
|
|
73
|
+
// issue-tracker reference expects a URL, so parse the candidate and keep it only
|
|
74
|
+
// when it is a well-formed http(s) URL — dropping email-only bug contacts and
|
|
75
|
+
// malformed values like "https://". Exported so the command's root-package
|
|
76
|
+
// handling uses the same rule.
|
|
77
|
+
export function bugsUrlFromField(field) {
|
|
78
|
+
let candidate;
|
|
79
|
+
if (typeof field === 'string') {
|
|
80
|
+
candidate = field.trim();
|
|
81
|
+
}
|
|
82
|
+
else if (field && typeof field === 'object' && 'url' in field) {
|
|
83
|
+
const value = field.url;
|
|
84
|
+
if (typeof value === 'string')
|
|
85
|
+
candidate = value.trim();
|
|
86
|
+
}
|
|
87
|
+
if (!candidate)
|
|
88
|
+
return undefined;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = new URL(candidate);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
97
|
+
return undefined;
|
|
98
|
+
// Drop any embedded credentials: an SBOM is a shareable/published artifact,
|
|
99
|
+
// so a `bugs` URL like `https://user:token@tracker/...` must not leak the
|
|
100
|
+
// secret into externalReferences[].url. The tracker URL itself is still useful.
|
|
101
|
+
parsed.username = '';
|
|
102
|
+
parsed.password = '';
|
|
103
|
+
// Emit the normalized URL, not the raw input: `new URL` strips CR/LF/tab and
|
|
104
|
+
// percent-encodes spaces and control characters, so a crafted `bugs` value
|
|
105
|
+
// can't push raw whitespace or control chars into the CycloneDX
|
|
106
|
+
// `externalReferences[].url` (whose format is an `iri-reference`).
|
|
107
|
+
return parsed.href;
|
|
108
|
+
}
|
|
71
109
|
//# sourceMappingURL=getPkgMetadata.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { collectSbomComponents, type CollectSbomComponentsOptions, gitDownloadUrl } from './collectComponents.js';
|
|
1
|
+
export { collectSbomComponents, type CollectSbomComponentsOptions, gitDownloadUrl, resolveWorkspaceDeps, type WorkspacePackageInfo } from './collectComponents.js';
|
|
2
|
+
export { bugsUrlFromField } from './getPkgMetadata.js';
|
|
2
3
|
export { integrityToHashes } from './integrity.js';
|
|
3
4
|
export { buildPurl, encodePurlName } from './purl.js';
|
|
4
5
|
export { type CycloneDxOptions, serializeCycloneDx } from './serializeCycloneDx.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { collectSbomComponents, gitDownloadUrl } from './collectComponents.js';
|
|
1
|
+
export { collectSbomComponents, gitDownloadUrl, resolveWorkspaceDeps } from './collectComponents.js';
|
|
2
|
+
export { bugsUrlFromField } from './getPkgMetadata.js';
|
|
2
3
|
export { integrityToHashes } from './integrity.js';
|
|
3
4
|
export { buildPurl, encodePurlName } from './purl.js';
|
|
4
5
|
export { serializeCycloneDx } from './serializeCycloneDx.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import { DepType } from '@pnpm/lockfile.detect-dep-types';
|
|
2
3
|
import { integrityToHashes } from './integrity.js';
|
|
3
4
|
import { classifyLicense } from './license.js';
|
|
4
5
|
import { encodePurlName } from './purl.js';
|
|
@@ -14,6 +15,19 @@ export function serializeCycloneDx(result, opts) {
|
|
|
14
15
|
purl: comp.purl,
|
|
15
16
|
'bom-ref': comp.purl,
|
|
16
17
|
};
|
|
18
|
+
// CycloneDX `excluded` scope (valid in every exported spec version):
|
|
19
|
+
// "component usage for test and other non-runtime purposes", which is the
|
|
20
|
+
// semantics of a devDependency.
|
|
21
|
+
// Components reachable through prod (ProdOnly/DevAndProd) omit scope and
|
|
22
|
+
// default to `required`. Installed optionalDependencies are runtime-reachable,
|
|
23
|
+
// so they stay `required` too, not `optional`.
|
|
24
|
+
if (comp.depType === DepType.DevOnly) {
|
|
25
|
+
cdxComp.scope = 'excluded';
|
|
26
|
+
// Also emit the CycloneDX npm-taxonomy marker. `scope` is the modern
|
|
27
|
+
// signal; `cdx:npm:package:development` is what @cyclonedx/cyclonedx-npm
|
|
28
|
+
// emits and what older consumers read, so we provide both.
|
|
29
|
+
cdxComp.properties = [{ name: 'cdx:npm:package:development', value: 'true' }];
|
|
30
|
+
}
|
|
17
31
|
if (group) {
|
|
18
32
|
cdxComp.group = group;
|
|
19
33
|
}
|
|
@@ -56,6 +70,12 @@ export function serializeCycloneDx(result, opts) {
|
|
|
56
70
|
url: comp.repository,
|
|
57
71
|
});
|
|
58
72
|
}
|
|
73
|
+
if (comp.bugsUrl) {
|
|
74
|
+
externalRefs.push({
|
|
75
|
+
type: 'issue-tracker',
|
|
76
|
+
url: comp.bugsUrl,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
59
79
|
if (externalRefs.length > 0) {
|
|
60
80
|
cdxComp.externalReferences = externalRefs;
|
|
61
81
|
}
|
|
@@ -97,11 +117,15 @@ export function serializeCycloneDx(result, opts) {
|
|
|
97
117
|
if (rootComponent.description) {
|
|
98
118
|
rootCdxComponent.description = rootComponent.description;
|
|
99
119
|
}
|
|
120
|
+
const rootExternalRefs = [];
|
|
100
121
|
if (rootComponent.repository) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
rootExternalRefs.push({ type: 'vcs', url: rootComponent.repository });
|
|
123
|
+
}
|
|
124
|
+
if (rootComponent.bugsUrl) {
|
|
125
|
+
rootExternalRefs.push({ type: 'issue-tracker', url: rootComponent.bugsUrl });
|
|
126
|
+
}
|
|
127
|
+
if (rootExternalRefs.length > 0) {
|
|
128
|
+
rootCdxComponent.externalReferences = rootExternalRefs;
|
|
105
129
|
}
|
|
106
130
|
const toolComponents = [];
|
|
107
131
|
if (opts?.pnpmVersion) {
|
|
@@ -136,7 +160,7 @@ export function serializeCycloneDx(result, opts) {
|
|
|
136
160
|
components: bomComponents,
|
|
137
161
|
dependencies: bomDependencies,
|
|
138
162
|
};
|
|
139
|
-
return JSON.stringify(bom, null, 2);
|
|
163
|
+
return JSON.stringify(bom, null, opts?.compact ? undefined : 2);
|
|
140
164
|
}
|
|
141
165
|
function splitScopedName(fullName) {
|
|
142
166
|
if (fullName.startsWith('@')) {
|
package/lib/serializeSpdx.d.ts
CHANGED
package/lib/serializeSpdx.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { integrityToHashes } from './integrity.js';
|
|
3
3
|
import { encodePurlName } from './purl.js';
|
|
4
|
-
export function serializeSpdx(result) {
|
|
4
|
+
export function serializeSpdx(result, opts) {
|
|
5
5
|
const { rootComponent, components, relationships } = result;
|
|
6
6
|
const rootSpdxId = 'SPDXRef-RootPackage';
|
|
7
7
|
const documentNamespace = `https://spdx.org/spdxdocs/${sanitizeSpdxId(rootComponent.name)}-${rootComponent.version}-${crypto.randomUUID()}`;
|
|
@@ -123,7 +123,7 @@ export function serializeSpdx(result) {
|
|
|
123
123
|
packages: [rootPackage, ...spdxPackages],
|
|
124
124
|
relationships: spdxRelationships,
|
|
125
125
|
};
|
|
126
|
-
return JSON.stringify(doc, null, 2);
|
|
126
|
+
return JSON.stringify(doc, null, opts?.compact ? undefined : 2);
|
|
127
127
|
}
|
|
128
128
|
function sanitizeSpdxId(value) {
|
|
129
129
|
return value.replace(/[^a-z0-9.-]/gi, '-');
|
package/lib/types.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface SbomComponent {
|
|
|
12
12
|
author?: string;
|
|
13
13
|
homepage?: string;
|
|
14
14
|
repository?: string;
|
|
15
|
+
bugsUrl?: string;
|
|
15
16
|
}
|
|
16
17
|
export interface SbomRelationship {
|
|
17
18
|
from: string;
|
|
@@ -26,6 +27,7 @@ export interface SbomResult {
|
|
|
26
27
|
description?: string;
|
|
27
28
|
author?: string;
|
|
28
29
|
repository?: string;
|
|
30
|
+
bugsUrl?: string;
|
|
29
31
|
};
|
|
30
32
|
components: SbomComponent[];
|
|
31
33
|
relationships: SbomRelationship[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/deps.compliance.sbom",
|
|
3
|
-
"version": "1100.
|
|
3
|
+
"version": "1100.3.0",
|
|
4
4
|
"description": "Generate SBOM from pnpm lockfile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"funding": "https://opencollective.com/pnpm",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/pnpm/pnpm/tree/main/deps/compliance/sbom"
|
|
16
|
+
"url": "https://github.com/pnpm/pnpm/tree/main/pnpm11/deps/compliance/sbom"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://github.com/pnpm/pnpm/tree/main/deps/compliance/sbom#readme",
|
|
18
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/pnpm11/deps/compliance/sbom#readme",
|
|
19
19
|
"bugs": {
|
|
20
20
|
"url": "https://github.com/pnpm/pnpm/issues"
|
|
21
21
|
},
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"p-limit": "^7.3.0",
|
|
35
35
|
"ssri": "13.0.1",
|
|
36
36
|
"@pnpm/deps.compliance.license-resolver": "1100.0.0",
|
|
37
|
-
"@pnpm/lockfile.detect-dep-types": "1100.0.
|
|
38
|
-
"@pnpm/lockfile.types": "1100.0.
|
|
39
|
-
"@pnpm/lockfile.utils": "1100.0
|
|
40
|
-
"@pnpm/lockfile.walker": "1100.0.
|
|
41
|
-
"@pnpm/pkg-manifest.reader": "1100.0.
|
|
42
|
-
"@pnpm/resolving.resolver-base": "1100.
|
|
43
|
-
"@pnpm/store.index": "1100.2.
|
|
44
|
-
"@pnpm/store.pkg-finder": "1100.0.
|
|
37
|
+
"@pnpm/lockfile.detect-dep-types": "1100.0.12",
|
|
38
|
+
"@pnpm/lockfile.types": "1100.0.12",
|
|
39
|
+
"@pnpm/lockfile.utils": "1100.1.0",
|
|
40
|
+
"@pnpm/lockfile.walker": "1100.0.12",
|
|
41
|
+
"@pnpm/pkg-manifest.reader": "1100.0.9",
|
|
42
|
+
"@pnpm/resolving.resolver-base": "1100.5.0",
|
|
43
|
+
"@pnpm/store.index": "1100.2.1",
|
|
44
|
+
"@pnpm/store.pkg-finder": "1100.0.18",
|
|
45
45
|
"@pnpm/types": "1101.3.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
52
|
"@types/ssri": "^7.1.5",
|
|
53
|
-
"@pnpm/deps.compliance.sbom": "1100.1.9",
|
|
54
53
|
"@pnpm/logger": "1100.0.0",
|
|
55
|
-
"@pnpm/store.cafs": "1100.1.
|
|
54
|
+
"@pnpm/store.cafs": "1100.1.11",
|
|
55
|
+
"@pnpm/deps.compliance.sbom": "1100.3.0"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=22.13"
|