@pnpm/deps.compliance.sbom 1100.3.2 → 1100.3.4
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/CHANGELOG.md +21 -0
- package/LICENSE +22 -0
- package/package.json +11 -11
- package/lib/collectComponents.d.ts +0 -50
- package/lib/collectComponents.js +0 -209
- package/lib/getPkgMetadata.d.ts +0 -19
- package/lib/getPkgMetadata.js +0 -109
- package/lib/index.d.ts +0 -7
- package/lib/integrity.d.ts +0 -9
- package/lib/integrity.js +0 -38
- package/lib/license.d.ts +0 -11
- package/lib/license.js +0 -23
- package/lib/purl.d.ts +0 -14
- package/lib/purl.js +0 -23
- package/lib/serializeCycloneDx.d.ts +0 -10
- package/lib/serializeCycloneDx.js +0 -174
- package/lib/serializeSpdx.d.ts +0 -5
- package/lib/serializeSpdx.js +0 -145
- package/lib/types.d.ts +0 -36
- package/lib/types.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @pnpm/deps.compliance.sbom
|
|
2
2
|
|
|
3
|
+
## 1100.3.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies:
|
|
8
|
+
- @pnpm/store.pkg-finder@1100.0.21
|
|
9
|
+
|
|
10
|
+
## 1100.3.3
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies:
|
|
15
|
+
- @pnpm/lockfile.detect-dep-types@1100.0.14
|
|
16
|
+
- @pnpm/lockfile.types@1100.0.14
|
|
17
|
+
- @pnpm/lockfile.utils@1100.1.3
|
|
18
|
+
- @pnpm/lockfile.walker@1100.0.14
|
|
19
|
+
- @pnpm/pkg-manifest.reader@1100.0.10
|
|
20
|
+
- @pnpm/resolving.resolver-base@1100.5.2
|
|
21
|
+
- @pnpm/store.pkg-finder@1100.0.20
|
|
22
|
+
- @pnpm/types@1101.4.0
|
|
23
|
+
|
|
3
24
|
## 1100.3.2
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
|
|
4
|
+
Copyright (c) 2016-2026 Zoltan Kochan and other contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/deps.compliance.sbom",
|
|
3
|
-
"version": "1100.3.
|
|
3
|
+
"version": "1100.3.4",
|
|
4
4
|
"description": "Generate SBOM from pnpm lockfile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@cyclonedx/cyclonedx-library": "10.1.0",
|
|
34
34
|
"@pnpm/deps.compliance.license-resolver": "1100.0.0",
|
|
35
|
-
"@pnpm/lockfile.detect-dep-types": "1100.0.
|
|
36
|
-
"@pnpm/lockfile.types": "1100.0.
|
|
37
|
-
"@pnpm/lockfile.utils": "1100.1.
|
|
38
|
-
"@pnpm/lockfile.walker": "1100.0.
|
|
39
|
-
"@pnpm/pkg-manifest.reader": "1100.0.
|
|
40
|
-
"@pnpm/resolving.resolver-base": "1100.5.
|
|
35
|
+
"@pnpm/lockfile.detect-dep-types": "1100.0.14",
|
|
36
|
+
"@pnpm/lockfile.types": "1100.0.14",
|
|
37
|
+
"@pnpm/lockfile.utils": "1100.1.3",
|
|
38
|
+
"@pnpm/lockfile.walker": "1100.0.14",
|
|
39
|
+
"@pnpm/pkg-manifest.reader": "1100.0.10",
|
|
40
|
+
"@pnpm/resolving.resolver-base": "1100.5.2",
|
|
41
41
|
"@pnpm/store.index": "1100.2.1",
|
|
42
|
-
"@pnpm/store.pkg-finder": "1100.0.
|
|
43
|
-
"@pnpm/types": "1101.
|
|
42
|
+
"@pnpm/store.pkg-finder": "1100.0.21",
|
|
43
|
+
"@pnpm/types": "1101.4.0",
|
|
44
44
|
"p-limit": "^7.3.0",
|
|
45
45
|
"ssri": "13.0.1"
|
|
46
46
|
},
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@pnpm/deps.compliance.sbom": "1100.3.
|
|
52
|
+
"@pnpm/deps.compliance.sbom": "1100.3.4",
|
|
53
53
|
"@pnpm/logger": "1100.0.0",
|
|
54
|
-
"@pnpm/store.cafs": "1100.1.
|
|
54
|
+
"@pnpm/store.cafs": "1100.1.13",
|
|
55
55
|
"@types/ssri": "^7.1.5"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { LockfileObject } from '@pnpm/lockfile.types';
|
|
2
|
-
import type { Resolution } from '@pnpm/resolving.resolver-base';
|
|
3
|
-
import type { DependenciesField, ProjectId, Registries } from '@pnpm/types';
|
|
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
|
-
}
|
|
13
|
-
export interface CollectSbomComponentsOptions {
|
|
14
|
-
lockfile: LockfileObject;
|
|
15
|
-
rootName: string;
|
|
16
|
-
rootVersion: string;
|
|
17
|
-
rootLicense?: string;
|
|
18
|
-
rootDescription?: string;
|
|
19
|
-
rootAuthor?: string;
|
|
20
|
-
rootRepository?: string;
|
|
21
|
-
rootBugsUrl?: string;
|
|
22
|
-
sbomType?: SbomComponentType;
|
|
23
|
-
include?: {
|
|
24
|
-
[dependenciesField in DependenciesField]: boolean;
|
|
25
|
-
};
|
|
26
|
-
registries: Registries;
|
|
27
|
-
lockfileDir: string;
|
|
28
|
-
includedImporterIds?: ProjectId[];
|
|
29
|
-
lockfileOnly?: boolean;
|
|
30
|
-
storeDir?: string;
|
|
31
|
-
virtualStoreDirMaxLength?: number;
|
|
32
|
-
workspacePackages?: Record<ProjectId, WorkspacePackageInfo>;
|
|
33
|
-
resolvedWorkspaceDeps?: ReturnType<typeof resolveWorkspaceDeps>;
|
|
34
|
-
excludePeerNamesByImporter?: Map<string, Set<string>>;
|
|
35
|
-
}
|
|
36
|
-
export declare function collectSbomComponents(opts: CollectSbomComponentsOptions): Promise<SbomResult>;
|
|
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
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { DepType, detectDepTypes } from '@pnpm/lockfile.detect-dep-types';
|
|
3
|
-
import { nameVerFromPkgSnapshot, pkgSnapshotToResolution } from '@pnpm/lockfile.utils';
|
|
4
|
-
import { lockfileWalkerGroupImporterSteps, } from '@pnpm/lockfile.walker';
|
|
5
|
-
import { StoreIndex } from '@pnpm/store.index';
|
|
6
|
-
import pLimit from 'p-limit';
|
|
7
|
-
import { getPkgMetadata } from './getPkgMetadata.js';
|
|
8
|
-
import { buildPurl, encodePurlName } from './purl.js';
|
|
9
|
-
const IMPORTER_WALK_CONCURRENCY = 8;
|
|
10
|
-
export async function collectSbomComponents(opts) {
|
|
11
|
-
const depTypes = detectDepTypes(opts.lockfile);
|
|
12
|
-
const importerIds = opts.includedImporterIds ?? Object.keys(opts.lockfile.importers);
|
|
13
|
-
const componentsMap = new Map();
|
|
14
|
-
const relationships = [];
|
|
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
|
-
}
|
|
73
|
-
const storeIndex = (!opts.lockfileOnly && opts.storeDir)
|
|
74
|
-
? new StoreIndex(opts.storeDir)
|
|
75
|
-
: undefined;
|
|
76
|
-
const metadataOpts = (storeIndex && opts.storeDir)
|
|
77
|
-
? {
|
|
78
|
-
storeDir: opts.storeDir,
|
|
79
|
-
storeIndex,
|
|
80
|
-
lockfileDir: opts.lockfileDir,
|
|
81
|
-
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength ?? 120,
|
|
82
|
-
}
|
|
83
|
-
: undefined;
|
|
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
|
-
})));
|
|
111
|
-
storeIndex?.close();
|
|
112
|
-
return {
|
|
113
|
-
rootComponent: {
|
|
114
|
-
name: opts.rootName,
|
|
115
|
-
version: opts.rootVersion,
|
|
116
|
-
type: opts.sbomType ?? 'library',
|
|
117
|
-
license: opts.rootLicense,
|
|
118
|
-
description: opts.rootDescription,
|
|
119
|
-
author: opts.rootAuthor,
|
|
120
|
-
repository: opts.rootRepository,
|
|
121
|
-
bugsUrl: opts.rootBugsUrl,
|
|
122
|
-
},
|
|
123
|
-
components: Array.from(componentsMap.values()),
|
|
124
|
-
relationships,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
async function walkStep(step, parentPurl, depTypes, componentsMap, relationships, opts, metadataOpts) {
|
|
128
|
-
await Promise.all(step.dependencies.map(async (dep) => {
|
|
129
|
-
const { depPath, pkgSnapshot, next } = dep;
|
|
130
|
-
const { name, version, nonSemverVersion } = nameVerFromPkgSnapshot(depPath, pkgSnapshot);
|
|
131
|
-
if (!name || !version)
|
|
132
|
-
return;
|
|
133
|
-
const purl = buildPurl({ name, version, nonSemverVersion: nonSemverVersion ?? undefined });
|
|
134
|
-
relationships.push({ from: parentPurl, to: purl });
|
|
135
|
-
if (componentsMap.has(purl))
|
|
136
|
-
return;
|
|
137
|
-
const integrity = pkgSnapshot.resolution.integrity;
|
|
138
|
-
const resolution = pkgSnapshotToResolution(depPath, pkgSnapshot, opts.registries);
|
|
139
|
-
const tarballUrl = resolution.tarball ?? gitDownloadUrl(resolution);
|
|
140
|
-
let metadata = {};
|
|
141
|
-
if (metadataOpts) {
|
|
142
|
-
metadata = await getPkgMetadata(depPath, pkgSnapshot, opts.registries, metadataOpts);
|
|
143
|
-
}
|
|
144
|
-
const component = {
|
|
145
|
-
name,
|
|
146
|
-
version,
|
|
147
|
-
purl,
|
|
148
|
-
depPath,
|
|
149
|
-
depType: depTypes[depPath] ?? DepType.ProdOnly,
|
|
150
|
-
integrity,
|
|
151
|
-
tarballUrl,
|
|
152
|
-
...metadata,
|
|
153
|
-
};
|
|
154
|
-
componentsMap.set(purl, component);
|
|
155
|
-
const subStep = next();
|
|
156
|
-
await walkStep(subStep, purl, depTypes, componentsMap, relationships, opts, metadataOpts);
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
export function gitDownloadUrl(resolution) {
|
|
160
|
-
if (resolution.type !== 'git')
|
|
161
|
-
return undefined;
|
|
162
|
-
const needsGitPlusPrefix = resolution.repo.includes('://') && !resolution.repo.startsWith('git+');
|
|
163
|
-
const prefix = needsGitPlusPrefix ? 'git+' : '';
|
|
164
|
-
return `${prefix}${resolution.repo}#${resolution.commit}`;
|
|
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
|
-
}
|
|
209
|
-
//# sourceMappingURL=collectComponents.js.map
|
package/lib/getPkgMetadata.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type PackageSnapshot } from '@pnpm/lockfile.utils';
|
|
2
|
-
import type { StoreIndex } from '@pnpm/store.index';
|
|
3
|
-
import type { DepPath, Registries } from '@pnpm/types';
|
|
4
|
-
export interface PkgMetadata {
|
|
5
|
-
license?: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
author?: string;
|
|
8
|
-
homepage?: string;
|
|
9
|
-
repository?: string;
|
|
10
|
-
bugsUrl?: string;
|
|
11
|
-
}
|
|
12
|
-
export interface GetPkgMetadataOptions {
|
|
13
|
-
storeDir: string;
|
|
14
|
-
storeIndex: StoreIndex;
|
|
15
|
-
lockfileDir: string;
|
|
16
|
-
virtualStoreDirMaxLength: number;
|
|
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
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { isSpdxLicenseExpression, resolveLicense } from '@pnpm/deps.compliance.license-resolver';
|
|
2
|
-
import { packageIdFromSnapshot, pkgSnapshotToResolution } from '@pnpm/lockfile.utils';
|
|
3
|
-
import { readPackageJson } from '@pnpm/pkg-manifest.reader';
|
|
4
|
-
import { readPackageFileMap } from '@pnpm/store.pkg-finder';
|
|
5
|
-
import pLimit from 'p-limit';
|
|
6
|
-
const limitMetadataReads = pLimit(4);
|
|
7
|
-
export async function getPkgMetadata(depPath, snapshot, registries, opts) {
|
|
8
|
-
return limitMetadataReads(() => getPkgMetadataUnclamped(depPath, snapshot, registries, opts));
|
|
9
|
-
}
|
|
10
|
-
async function getPkgMetadataUnclamped(depPath, snapshot, registries, opts) {
|
|
11
|
-
const id = packageIdFromSnapshot(depPath, snapshot);
|
|
12
|
-
const resolution = pkgSnapshotToResolution(depPath, snapshot, registries);
|
|
13
|
-
let files;
|
|
14
|
-
try {
|
|
15
|
-
const result = await readPackageFileMap(resolution, id, opts);
|
|
16
|
-
if (!result)
|
|
17
|
-
return {};
|
|
18
|
-
files = result;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return {};
|
|
22
|
-
}
|
|
23
|
-
const manifestPath = files.get('package.json');
|
|
24
|
-
if (!manifestPath)
|
|
25
|
-
return {};
|
|
26
|
-
const manifest = await readPackageJson(manifestPath);
|
|
27
|
-
return extractMetadata(manifest, files);
|
|
28
|
-
}
|
|
29
|
-
async function extractMetadata(manifest, files) {
|
|
30
|
-
const license = await resolveLicense({ manifest, files });
|
|
31
|
-
return {
|
|
32
|
-
license: serializableLicense(license),
|
|
33
|
-
description: manifest.description,
|
|
34
|
-
author: parseAuthorField(manifest.author),
|
|
35
|
-
homepage: manifest.homepage,
|
|
36
|
-
repository: parseRepositoryField(manifest.repository),
|
|
37
|
-
bugsUrl: bugsUrlFromField(manifest.bugs),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Drop:
|
|
41
|
-
// - missing / "Unknown" — so serializers emit NOASSERTION / absence.
|
|
42
|
-
// - LICENSE-file-detected values that aren't SPDX-valid (e.g. "Eclipse Public
|
|
43
|
-
// License 1.0") — would produce non-compliant SPDX output. Manifest-declared
|
|
44
|
-
// licenses are trusted as-is; authors use SPDX expressions there.
|
|
45
|
-
function serializableLicense(license) {
|
|
46
|
-
if (!license || license.name === 'Unknown')
|
|
47
|
-
return undefined;
|
|
48
|
-
if (license.licenseFile && !isSpdxLicenseExpression(license.name))
|
|
49
|
-
return undefined;
|
|
50
|
-
return license.name;
|
|
51
|
-
}
|
|
52
|
-
function parseAuthorField(field) {
|
|
53
|
-
if (!field)
|
|
54
|
-
return undefined;
|
|
55
|
-
if (typeof field === 'string')
|
|
56
|
-
return field;
|
|
57
|
-
if (typeof field === 'object' && 'name' in field) {
|
|
58
|
-
return field.name;
|
|
59
|
-
}
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
function parseRepositoryField(field) {
|
|
63
|
-
if (!field)
|
|
64
|
-
return undefined;
|
|
65
|
-
if (typeof field === 'string')
|
|
66
|
-
return field;
|
|
67
|
-
if (typeof field === 'object' && 'url' in field) {
|
|
68
|
-
return field.url;
|
|
69
|
-
}
|
|
70
|
-
return undefined;
|
|
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
|
-
}
|
|
109
|
-
//# sourceMappingURL=getPkgMetadata.js.map
|
package/lib/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { collectSbomComponents, type CollectSbomComponentsOptions, gitDownloadUrl, resolveWorkspaceDeps, type WorkspacePackageInfo } from './collectComponents.js';
|
|
2
|
-
export { bugsUrlFromField } from './getPkgMetadata.js';
|
|
3
|
-
export { integrityToHashes } from './integrity.js';
|
|
4
|
-
export { buildPurl, encodePurlName } from './purl.js';
|
|
5
|
-
export { type CycloneDxOptions, serializeCycloneDx } from './serializeCycloneDx.js';
|
|
6
|
-
export { serializeSpdx } from './serializeSpdx.js';
|
|
7
|
-
export type { SbomComponent, SbomComponentType, SbomFormat, SbomRelationship, SbomResult } from './types.js';
|
package/lib/integrity.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface HashDigest {
|
|
2
|
-
algorithm: string;
|
|
3
|
-
digest: string;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Convert an SRI integrity string to a list of algorithm + hex digest pairs.
|
|
7
|
-
* e.g. "sha512-abc123..." → [{ algorithm: "SHA-512", digest: "..." }]
|
|
8
|
-
*/
|
|
9
|
-
export declare function integrityToHashes(integrity: string | undefined): HashDigest[];
|
package/lib/integrity.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import ssri from 'ssri';
|
|
2
|
-
/**
|
|
3
|
-
* Convert an SRI integrity string to a list of algorithm + hex digest pairs.
|
|
4
|
-
* e.g. "sha512-abc123..." → [{ algorithm: "SHA-512", digest: "..." }]
|
|
5
|
-
*/
|
|
6
|
-
export function integrityToHashes(integrity) {
|
|
7
|
-
if (!integrity)
|
|
8
|
-
return [];
|
|
9
|
-
const parsed = ssri.parse(integrity);
|
|
10
|
-
const hashes = [];
|
|
11
|
-
for (const [algo, entries] of Object.entries(parsed)) {
|
|
12
|
-
if (!entries?.length)
|
|
13
|
-
continue;
|
|
14
|
-
for (const entry of entries) {
|
|
15
|
-
const hexDigest = Buffer.from(entry.digest, 'base64').toString('hex');
|
|
16
|
-
hashes.push({
|
|
17
|
-
algorithm: normalizeShaAlgorithm(algo),
|
|
18
|
-
digest: hexDigest,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return hashes;
|
|
23
|
-
}
|
|
24
|
-
function normalizeShaAlgorithm(algo) {
|
|
25
|
-
switch (algo) {
|
|
26
|
-
case 'sha1':
|
|
27
|
-
return 'SHA-1';
|
|
28
|
-
case 'sha256':
|
|
29
|
-
return 'SHA-256';
|
|
30
|
-
case 'sha384':
|
|
31
|
-
return 'SHA-384';
|
|
32
|
-
case 'sha512':
|
|
33
|
-
return 'SHA-512';
|
|
34
|
-
default:
|
|
35
|
-
return algo.toUpperCase();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=integrity.js.map
|
package/lib/license.d.ts
DELETED
package/lib/license.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Sub-path import to pull only the SPDX module — avoids dragging in the
|
|
2
|
-
// validation/serialize layers with optional native deps that break esbuild bundling.
|
|
3
|
-
import { fixupSpdxId, isSupportedSpdxId } from '@cyclonedx/cyclonedx-library/SPDX';
|
|
4
|
-
// Classifies a license string into the appropriate CycloneDX representation.
|
|
5
|
-
// Uses the CycloneDX library's own SPDX list rather than spdx-license-ids,
|
|
6
|
-
// since CycloneDX maintains its own subset of recognized IDs.
|
|
7
|
-
// Order matters: check ID first because "MIT" matches both isSupportedSpdxId
|
|
8
|
-
// and isSpdxExpression, but we prefer the more specific license.id form.
|
|
9
|
-
export function classifyLicense(license) {
|
|
10
|
-
const fixedId = fixupSpdxId(license);
|
|
11
|
-
if (fixedId != null && isSupportedSpdxId(fixedId)) {
|
|
12
|
-
return { license: { id: fixedId } };
|
|
13
|
-
}
|
|
14
|
-
if (isSpdxExpression(license)) {
|
|
15
|
-
return { expression: license };
|
|
16
|
-
}
|
|
17
|
-
return { license: { name: license } };
|
|
18
|
-
}
|
|
19
|
-
// Checks if a string looks like an SPDX license expression (compound with operators).
|
|
20
|
-
function isSpdxExpression(license) {
|
|
21
|
-
return /\b(?:AND|OR|WITH)\b/.test(license);
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=license.js.map
|
package/lib/purl.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encode a package name for use in a PURL.
|
|
3
|
-
* Scoped packages: @scope/name → %40scope/name
|
|
4
|
-
*/
|
|
5
|
-
export declare function encodePurlName(name: string): string;
|
|
6
|
-
/**
|
|
7
|
-
* Build a Package URL (PURL) for a given package.
|
|
8
|
-
* Spec: https://github.com/package-url/purl-spec
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildPurl(opts: {
|
|
11
|
-
name: string;
|
|
12
|
-
version: string;
|
|
13
|
-
nonSemverVersion?: string;
|
|
14
|
-
}): string;
|
package/lib/purl.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encode a package name for use in a PURL.
|
|
3
|
-
* Scoped packages: @scope/name → %40scope/name
|
|
4
|
-
*/
|
|
5
|
-
export function encodePurlName(name) {
|
|
6
|
-
if (name.startsWith('@')) {
|
|
7
|
-
return `%40${name.slice(1)}`;
|
|
8
|
-
}
|
|
9
|
-
return name;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Build a Package URL (PURL) for a given package.
|
|
13
|
-
* Spec: https://github.com/package-url/purl-spec
|
|
14
|
-
*/
|
|
15
|
-
export function buildPurl(opts) {
|
|
16
|
-
if (opts.nonSemverVersion) {
|
|
17
|
-
// Git-hosted or tarball dep — encode the raw version as a qualifier
|
|
18
|
-
const encodedUrl = encodeURIComponent(opts.nonSemverVersion);
|
|
19
|
-
return `pkg:npm/${encodePurlName(opts.name)}@${encodeURIComponent(opts.version)}?vcs_url=${encodedUrl}`;
|
|
20
|
-
}
|
|
21
|
-
return `pkg:npm/${encodePurlName(opts.name)}@${opts.version}`;
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=purl.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { SbomResult } from './types.js';
|
|
2
|
-
export interface CycloneDxOptions {
|
|
3
|
-
pnpmVersion?: string;
|
|
4
|
-
lockfileOnly?: boolean;
|
|
5
|
-
sbomAuthors?: string[];
|
|
6
|
-
sbomSupplier?: string;
|
|
7
|
-
specVersion?: string;
|
|
8
|
-
compact?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export declare function serializeCycloneDx(result: SbomResult, opts?: CycloneDxOptions): string;
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { DepType } from '@pnpm/lockfile.detect-dep-types';
|
|
3
|
-
import { integrityToHashes } from './integrity.js';
|
|
4
|
-
import { classifyLicense } from './license.js';
|
|
5
|
-
import { encodePurlName } from './purl.js';
|
|
6
|
-
export function serializeCycloneDx(result, opts) {
|
|
7
|
-
const { rootComponent, components, relationships } = result;
|
|
8
|
-
const rootBomRef = `pkg:npm/${encodePurlName(rootComponent.name)}@${rootComponent.version}`;
|
|
9
|
-
const bomComponents = components.map((comp) => {
|
|
10
|
-
const { group, name } = splitScopedName(comp.name);
|
|
11
|
-
const cdxComp = {
|
|
12
|
-
type: 'library',
|
|
13
|
-
name,
|
|
14
|
-
version: comp.version,
|
|
15
|
-
purl: comp.purl,
|
|
16
|
-
'bom-ref': comp.purl,
|
|
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
|
-
}
|
|
31
|
-
if (group) {
|
|
32
|
-
cdxComp.group = group;
|
|
33
|
-
}
|
|
34
|
-
if (comp.description) {
|
|
35
|
-
cdxComp.description = comp.description;
|
|
36
|
-
}
|
|
37
|
-
// CycloneDX supplier is the registry/distributor, not the package author
|
|
38
|
-
if (comp.author) {
|
|
39
|
-
cdxComp.authors = [{ name: comp.author }];
|
|
40
|
-
}
|
|
41
|
-
if (comp.license) {
|
|
42
|
-
cdxComp.licenses = [classifyLicense(comp.license)];
|
|
43
|
-
}
|
|
44
|
-
const externalRefs = [];
|
|
45
|
-
// Lockfile integrity is a tarball hash, not a source hash — belongs on the
|
|
46
|
-
// distribution reference, not component.hashes
|
|
47
|
-
if (comp.tarballUrl) {
|
|
48
|
-
const hashes = integrityToHashes(comp.integrity);
|
|
49
|
-
const distRef = {
|
|
50
|
-
type: 'distribution',
|
|
51
|
-
url: comp.tarballUrl,
|
|
52
|
-
};
|
|
53
|
-
if (hashes.length > 0) {
|
|
54
|
-
distRef.hashes = hashes.map((h) => ({
|
|
55
|
-
alg: h.algorithm,
|
|
56
|
-
content: h.digest,
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
externalRefs.push(distRef);
|
|
60
|
-
}
|
|
61
|
-
if (comp.homepage) {
|
|
62
|
-
externalRefs.push({
|
|
63
|
-
type: 'website',
|
|
64
|
-
url: comp.homepage,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
if (comp.repository) {
|
|
68
|
-
externalRefs.push({
|
|
69
|
-
type: 'vcs',
|
|
70
|
-
url: comp.repository,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
if (comp.bugsUrl) {
|
|
74
|
-
externalRefs.push({
|
|
75
|
-
type: 'issue-tracker',
|
|
76
|
-
url: comp.bugsUrl,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (externalRefs.length > 0) {
|
|
80
|
-
cdxComp.externalReferences = externalRefs;
|
|
81
|
-
}
|
|
82
|
-
return cdxComp;
|
|
83
|
-
});
|
|
84
|
-
// Group relationships by source
|
|
85
|
-
const depMap = new Map();
|
|
86
|
-
depMap.set(rootBomRef, []);
|
|
87
|
-
for (const comp of components) {
|
|
88
|
-
depMap.set(comp.purl, []);
|
|
89
|
-
}
|
|
90
|
-
for (const rel of relationships) {
|
|
91
|
-
const deps = depMap.get(rel.from);
|
|
92
|
-
if (deps) {
|
|
93
|
-
deps.push(rel.to);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const bomDependencies = Array.from(depMap.entries()).map(([ref, dependsOn]) => ({
|
|
97
|
-
ref,
|
|
98
|
-
dependsOn: [...new Set(dependsOn)],
|
|
99
|
-
}));
|
|
100
|
-
const { group: rootGroup, name: rootName } = splitScopedName(rootComponent.name);
|
|
101
|
-
const rootCdxComponent = {
|
|
102
|
-
type: rootComponent.type,
|
|
103
|
-
name: rootName,
|
|
104
|
-
version: rootComponent.version,
|
|
105
|
-
purl: rootBomRef,
|
|
106
|
-
'bom-ref': rootBomRef,
|
|
107
|
-
};
|
|
108
|
-
if (rootGroup) {
|
|
109
|
-
rootCdxComponent.group = rootGroup;
|
|
110
|
-
}
|
|
111
|
-
if (rootComponent.author) {
|
|
112
|
-
rootCdxComponent.authors = [{ name: rootComponent.author }];
|
|
113
|
-
}
|
|
114
|
-
if (rootComponent.license) {
|
|
115
|
-
rootCdxComponent.licenses = [classifyLicense(rootComponent.license)];
|
|
116
|
-
}
|
|
117
|
-
if (rootComponent.description) {
|
|
118
|
-
rootCdxComponent.description = rootComponent.description;
|
|
119
|
-
}
|
|
120
|
-
const rootExternalRefs = [];
|
|
121
|
-
if (rootComponent.repository) {
|
|
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;
|
|
129
|
-
}
|
|
130
|
-
const toolComponents = [];
|
|
131
|
-
if (opts?.pnpmVersion) {
|
|
132
|
-
toolComponents.push({
|
|
133
|
-
type: 'application',
|
|
134
|
-
name: 'pnpm',
|
|
135
|
-
version: opts.pnpmVersion,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
const metadata = {
|
|
139
|
-
timestamp: new Date().toISOString(),
|
|
140
|
-
lifecycles: [{ phase: opts?.lockfileOnly ? 'pre-build' : 'build' }],
|
|
141
|
-
tools: { components: toolComponents },
|
|
142
|
-
component: rootCdxComponent,
|
|
143
|
-
};
|
|
144
|
-
// authors/supplier describe who authored/supplies the BOM document,
|
|
145
|
-
// not the tool — opt-in via --sbom-authors and --sbom-supplier
|
|
146
|
-
if (opts?.sbomAuthors?.length) {
|
|
147
|
-
metadata.authors = opts.sbomAuthors.map((name) => ({ name }));
|
|
148
|
-
}
|
|
149
|
-
if (opts?.sbomSupplier) {
|
|
150
|
-
metadata.supplier = { name: opts.sbomSupplier };
|
|
151
|
-
}
|
|
152
|
-
const version = opts?.specVersion || '1.7';
|
|
153
|
-
const bom = {
|
|
154
|
-
$schema: `http://cyclonedx.org/schema/bom-${version}.schema.json`,
|
|
155
|
-
bomFormat: 'CycloneDX',
|
|
156
|
-
specVersion: version,
|
|
157
|
-
serialNumber: `urn:uuid:${crypto.randomUUID()}`,
|
|
158
|
-
version: 1,
|
|
159
|
-
metadata,
|
|
160
|
-
components: bomComponents,
|
|
161
|
-
dependencies: bomDependencies,
|
|
162
|
-
};
|
|
163
|
-
return JSON.stringify(bom, null, opts?.compact ? undefined : 2);
|
|
164
|
-
}
|
|
165
|
-
function splitScopedName(fullName) {
|
|
166
|
-
if (fullName.startsWith('@')) {
|
|
167
|
-
const slashIdx = fullName.indexOf('/');
|
|
168
|
-
if (slashIdx > 0) {
|
|
169
|
-
return { group: fullName.slice(0, slashIdx), name: fullName.slice(slashIdx + 1) };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return { group: undefined, name: fullName };
|
|
173
|
-
}
|
|
174
|
-
//# sourceMappingURL=serializeCycloneDx.js.map
|
package/lib/serializeSpdx.d.ts
DELETED
package/lib/serializeSpdx.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { integrityToHashes } from './integrity.js';
|
|
3
|
-
import { encodePurlName } from './purl.js';
|
|
4
|
-
export function serializeSpdx(result, opts) {
|
|
5
|
-
const { rootComponent, components, relationships } = result;
|
|
6
|
-
const rootSpdxId = 'SPDXRef-RootPackage';
|
|
7
|
-
const documentNamespace = `https://spdx.org/spdxdocs/${sanitizeSpdxId(rootComponent.name)}-${rootComponent.version}-${crypto.randomUUID()}`;
|
|
8
|
-
const rootPurl = `pkg:npm/${encodePurlName(rootComponent.name)}@${rootComponent.version}`;
|
|
9
|
-
const rootPackage = {
|
|
10
|
-
SPDXID: rootSpdxId,
|
|
11
|
-
name: rootComponent.name,
|
|
12
|
-
versionInfo: rootComponent.version,
|
|
13
|
-
downloadLocation: 'NOASSERTION',
|
|
14
|
-
filesAnalyzed: false,
|
|
15
|
-
primaryPackagePurpose: rootComponent.type === 'application' ? 'APPLICATION' : 'LIBRARY',
|
|
16
|
-
externalRefs: [
|
|
17
|
-
{
|
|
18
|
-
referenceCategory: 'PACKAGE-MANAGER',
|
|
19
|
-
referenceType: 'purl',
|
|
20
|
-
referenceLocator: rootPurl,
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
};
|
|
24
|
-
if (rootComponent.license) {
|
|
25
|
-
rootPackage.licenseConcluded = rootComponent.license;
|
|
26
|
-
rootPackage.licenseDeclared = rootComponent.license;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
rootPackage.licenseConcluded = 'NOASSERTION';
|
|
30
|
-
rootPackage.licenseDeclared = 'NOASSERTION';
|
|
31
|
-
}
|
|
32
|
-
rootPackage.copyrightText = 'NOASSERTION';
|
|
33
|
-
if (rootComponent.description) {
|
|
34
|
-
rootPackage.description = rootComponent.description;
|
|
35
|
-
}
|
|
36
|
-
if (rootComponent.author) {
|
|
37
|
-
rootPackage.supplier = `Person: ${rootComponent.author}`;
|
|
38
|
-
}
|
|
39
|
-
if (rootComponent.repository) {
|
|
40
|
-
rootPackage.homepage = rootComponent.repository;
|
|
41
|
-
}
|
|
42
|
-
const purlToSpdxId = new Map();
|
|
43
|
-
purlToSpdxId.set(rootPurl, rootSpdxId);
|
|
44
|
-
const spdxPackages = components.map((comp, idx) => {
|
|
45
|
-
const spdxId = `SPDXRef-Package-${sanitizeSpdxId(comp.name)}-${sanitizeSpdxId(comp.version)}-${idx}`;
|
|
46
|
-
purlToSpdxId.set(comp.purl, spdxId);
|
|
47
|
-
const pkg = {
|
|
48
|
-
SPDXID: spdxId,
|
|
49
|
-
name: comp.name,
|
|
50
|
-
versionInfo: comp.version,
|
|
51
|
-
downloadLocation: comp.tarballUrl ?? 'NOASSERTION',
|
|
52
|
-
filesAnalyzed: false,
|
|
53
|
-
externalRefs: [
|
|
54
|
-
{
|
|
55
|
-
referenceCategory: 'PACKAGE-MANAGER',
|
|
56
|
-
referenceType: 'purl',
|
|
57
|
-
referenceLocator: comp.purl,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
};
|
|
61
|
-
if (comp.license) {
|
|
62
|
-
pkg.licenseConcluded = comp.license;
|
|
63
|
-
pkg.licenseDeclared = comp.license;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
pkg.licenseConcluded = 'NOASSERTION';
|
|
67
|
-
pkg.licenseDeclared = 'NOASSERTION';
|
|
68
|
-
}
|
|
69
|
-
pkg.copyrightText = 'NOASSERTION';
|
|
70
|
-
if (comp.description) {
|
|
71
|
-
pkg.description = comp.description;
|
|
72
|
-
}
|
|
73
|
-
if (comp.homepage) {
|
|
74
|
-
pkg.homepage = comp.homepage;
|
|
75
|
-
}
|
|
76
|
-
if (comp.author) {
|
|
77
|
-
pkg.supplier = `Person: ${comp.author}`;
|
|
78
|
-
}
|
|
79
|
-
const hashes = integrityToHashes(comp.integrity);
|
|
80
|
-
if (hashes.length > 0) {
|
|
81
|
-
pkg.checksums = hashes.map((h) => ({
|
|
82
|
-
algorithm: spdxHashAlgorithm(h.algorithm),
|
|
83
|
-
checksumValue: h.digest,
|
|
84
|
-
}));
|
|
85
|
-
}
|
|
86
|
-
return pkg;
|
|
87
|
-
});
|
|
88
|
-
const spdxRelationships = [
|
|
89
|
-
{
|
|
90
|
-
spdxElementId: 'SPDXRef-DOCUMENT',
|
|
91
|
-
relatedSpdxElement: rootSpdxId,
|
|
92
|
-
relationshipType: 'DESCRIBES',
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
|
-
const seenRelationships = new Set();
|
|
96
|
-
for (const rel of relationships) {
|
|
97
|
-
const fromId = purlToSpdxId.get(rel.from);
|
|
98
|
-
const toId = purlToSpdxId.get(rel.to);
|
|
99
|
-
if (fromId && toId) {
|
|
100
|
-
const key = `${fromId}|${toId}`;
|
|
101
|
-
if (seenRelationships.has(key))
|
|
102
|
-
continue;
|
|
103
|
-
seenRelationships.add(key);
|
|
104
|
-
spdxRelationships.push({
|
|
105
|
-
spdxElementId: fromId,
|
|
106
|
-
relatedSpdxElement: toId,
|
|
107
|
-
relationshipType: 'DEPENDS_ON',
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const doc = {
|
|
112
|
-
spdxVersion: 'SPDX-2.3',
|
|
113
|
-
dataLicense: 'CC0-1.0',
|
|
114
|
-
SPDXID: 'SPDXRef-DOCUMENT',
|
|
115
|
-
name: rootComponent.name,
|
|
116
|
-
documentNamespace,
|
|
117
|
-
creationInfo: {
|
|
118
|
-
created: new Date().toISOString(),
|
|
119
|
-
creators: [
|
|
120
|
-
'Tool: pnpm',
|
|
121
|
-
],
|
|
122
|
-
},
|
|
123
|
-
packages: [rootPackage, ...spdxPackages],
|
|
124
|
-
relationships: spdxRelationships,
|
|
125
|
-
};
|
|
126
|
-
return JSON.stringify(doc, null, opts?.compact ? undefined : 2);
|
|
127
|
-
}
|
|
128
|
-
function sanitizeSpdxId(value) {
|
|
129
|
-
return value.replace(/[^a-z0-9.-]/gi, '-');
|
|
130
|
-
}
|
|
131
|
-
function spdxHashAlgorithm(algo) {
|
|
132
|
-
switch (algo) {
|
|
133
|
-
case 'SHA-1':
|
|
134
|
-
return 'SHA1';
|
|
135
|
-
case 'SHA-256':
|
|
136
|
-
return 'SHA256';
|
|
137
|
-
case 'SHA-384':
|
|
138
|
-
return 'SHA384';
|
|
139
|
-
case 'SHA-512':
|
|
140
|
-
return 'SHA512';
|
|
141
|
-
default:
|
|
142
|
-
return algo;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
//# sourceMappingURL=serializeSpdx.js.map
|
package/lib/types.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { DepType } from '@pnpm/lockfile.detect-dep-types';
|
|
2
|
-
export interface SbomComponent {
|
|
3
|
-
name: string;
|
|
4
|
-
version: string;
|
|
5
|
-
purl: string;
|
|
6
|
-
depPath: string;
|
|
7
|
-
depType: DepType;
|
|
8
|
-
integrity?: string;
|
|
9
|
-
tarballUrl?: string;
|
|
10
|
-
license?: string;
|
|
11
|
-
description?: string;
|
|
12
|
-
author?: string;
|
|
13
|
-
homepage?: string;
|
|
14
|
-
repository?: string;
|
|
15
|
-
bugsUrl?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface SbomRelationship {
|
|
18
|
-
from: string;
|
|
19
|
-
to: string;
|
|
20
|
-
}
|
|
21
|
-
export interface SbomResult {
|
|
22
|
-
rootComponent: {
|
|
23
|
-
name: string;
|
|
24
|
-
version: string;
|
|
25
|
-
type: 'library' | 'application';
|
|
26
|
-
license?: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
author?: string;
|
|
29
|
-
repository?: string;
|
|
30
|
-
bugsUrl?: string;
|
|
31
|
-
};
|
|
32
|
-
components: SbomComponent[];
|
|
33
|
-
relationships: SbomRelationship[];
|
|
34
|
-
}
|
|
35
|
-
export type SbomFormat = 'cyclonedx' | 'spdx';
|
|
36
|
-
export type SbomComponentType = 'library' | 'application';
|
package/lib/types.js
DELETED