@pnpm/deps.compliance.sbom 1100.2.0 → 1100.3.1
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 +2 -0
- package/lib/collectComponents.js +20 -2
- package/lib/getPkgMetadata.d.ts +2 -0
- package/lib/getPkgMetadata.js +38 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/serializeCycloneDx.js +14 -4
- package/lib/types.d.ts +2 -0
- package/package.json +14 -14
|
@@ -18,6 +18,7 @@ export interface CollectSbomComponentsOptions {
|
|
|
18
18
|
rootDescription?: string;
|
|
19
19
|
rootAuthor?: string;
|
|
20
20
|
rootRepository?: string;
|
|
21
|
+
rootBugsUrl?: string;
|
|
21
22
|
sbomType?: SbomComponentType;
|
|
22
23
|
include?: {
|
|
23
24
|
[dependenciesField in DependenciesField]: boolean;
|
|
@@ -30,6 +31,7 @@ export interface CollectSbomComponentsOptions {
|
|
|
30
31
|
virtualStoreDirMaxLength?: number;
|
|
31
32
|
workspacePackages?: Record<ProjectId, WorkspacePackageInfo>;
|
|
32
33
|
resolvedWorkspaceDeps?: ReturnType<typeof resolveWorkspaceDeps>;
|
|
34
|
+
excludePeerNamesByImporter?: Map<string, Set<string>>;
|
|
33
35
|
}
|
|
34
36
|
export declare function collectSbomComponents(opts: CollectSbomComponentsOptions): Promise<SbomResult>;
|
|
35
37
|
export declare function gitDownloadUrl(resolution: Resolution): string | undefined;
|
package/lib/collectComponents.js
CHANGED
|
@@ -18,7 +18,11 @@ export async function collectSbomComponents(opts) {
|
|
|
18
18
|
? { links: [], additionalImporterIds: [] }
|
|
19
19
|
: resolveWorkspaceDeps(opts.lockfile, importerIds, opts.include));
|
|
20
20
|
const allImporterIds = [...importerIds, ...workspaceDeps.additionalImporterIds];
|
|
21
|
-
|
|
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 });
|
|
22
26
|
const importerIdSet = new Set(importerIds);
|
|
23
27
|
if (opts.workspacePackages) {
|
|
24
28
|
const workspaceDepTypes = new Map();
|
|
@@ -89,7 +93,20 @@ export async function collectSbomComponents(opts) {
|
|
|
89
93
|
return;
|
|
90
94
|
parentPurl = buildPurl({ name: info.name, version: info.version });
|
|
91
95
|
}
|
|
92
|
-
|
|
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);
|
|
93
110
|
})));
|
|
94
111
|
storeIndex?.close();
|
|
95
112
|
return {
|
|
@@ -101,6 +118,7 @@ export async function collectSbomComponents(opts) {
|
|
|
101
118
|
description: opts.rootDescription,
|
|
102
119
|
author: opts.rootAuthor,
|
|
103
120
|
repository: opts.rootRepository,
|
|
121
|
+
bugsUrl: opts.rootBugsUrl,
|
|
104
122
|
},
|
|
105
123
|
components: Array.from(componentsMap.values()),
|
|
106
124
|
relationships,
|
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
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
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';
|
|
@@ -70,6 +70,12 @@ export function serializeCycloneDx(result, opts) {
|
|
|
70
70
|
url: comp.repository,
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
|
+
if (comp.bugsUrl) {
|
|
74
|
+
externalRefs.push({
|
|
75
|
+
type: 'issue-tracker',
|
|
76
|
+
url: comp.bugsUrl,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
73
79
|
if (externalRefs.length > 0) {
|
|
74
80
|
cdxComp.externalReferences = externalRefs;
|
|
75
81
|
}
|
|
@@ -111,11 +117,15 @@ export function serializeCycloneDx(result, opts) {
|
|
|
111
117
|
if (rootComponent.description) {
|
|
112
118
|
rootCdxComponent.description = rootComponent.description;
|
|
113
119
|
}
|
|
120
|
+
const rootExternalRefs = [];
|
|
114
121
|
if (rootComponent.repository) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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;
|
|
119
129
|
}
|
|
120
130
|
const toolComponents = [];
|
|
121
131
|
if (opts?.pnpmVersion) {
|
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.1",
|
|
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
|
},
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
"@cyclonedx/cyclonedx-library": "10.1.0",
|
|
34
34
|
"p-limit": "^7.3.0",
|
|
35
35
|
"ssri": "13.0.1",
|
|
36
|
-
"@pnpm/lockfile.detect-dep-types": "1100.0.11",
|
|
37
|
-
"@pnpm/lockfile.walker": "1100.0.11",
|
|
38
|
-
"@pnpm/lockfile.utils": "1100.0.13",
|
|
39
36
|
"@pnpm/deps.compliance.license-resolver": "1100.0.0",
|
|
40
|
-
"@pnpm/
|
|
41
|
-
"@pnpm/
|
|
42
|
-
"@pnpm/lockfile.
|
|
37
|
+
"@pnpm/lockfile.types": "1100.0.13",
|
|
38
|
+
"@pnpm/lockfile.detect-dep-types": "1100.0.13",
|
|
39
|
+
"@pnpm/lockfile.walker": "1100.0.13",
|
|
40
|
+
"@pnpm/lockfile.utils": "1100.1.1",
|
|
41
|
+
"@pnpm/pkg-manifest.reader": "1100.0.9",
|
|
42
|
+
"@pnpm/resolving.resolver-base": "1100.5.1",
|
|
43
|
+
"@pnpm/store.pkg-finder": "1100.0.19",
|
|
43
44
|
"@pnpm/types": "1101.3.2",
|
|
44
|
-
"@pnpm/store.
|
|
45
|
-
"@pnpm/pkg-manifest.reader": "1100.0.8"
|
|
45
|
+
"@pnpm/store.index": "1100.2.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@pnpm/logger": "^1100.0.0"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
52
|
"@types/ssri": "^7.1.5",
|
|
53
|
-
"@pnpm/
|
|
54
|
-
"@pnpm/
|
|
55
|
-
"@pnpm/
|
|
53
|
+
"@pnpm/logger": "1100.0.0",
|
|
54
|
+
"@pnpm/store.cafs": "1100.1.12",
|
|
55
|
+
"@pnpm/deps.compliance.sbom": "1100.3.1"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=22.13"
|