@pnpm/deps.compliance.sbom 1100.2.0 → 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.
@@ -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;
@@ -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
- const importerWalkers = lockfileWalkerGroupImporterSteps(opts.lockfile, allImporterIds, { include: opts.include });
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
- await walkStep(step, parentPurl, depTypes, componentsMap, relationships, opts, metadataOpts);
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,
@@ -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;
@@ -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
- rootCdxComponent.externalReferences = [{
116
- type: 'vcs',
117
- url: rootComponent.repository,
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.2.0",
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
  },
@@ -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/resolving.resolver-base": "1100.4.2",
41
- "@pnpm/store.index": "1100.2.0",
42
- "@pnpm/lockfile.types": "1100.0.11",
43
- "@pnpm/types": "1101.3.2",
44
- "@pnpm/store.pkg-finder": "1100.0.17",
45
- "@pnpm/pkg-manifest.reader": "1100.0.8"
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
+ "@pnpm/types": "1101.3.2"
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/store.cafs": "1100.1.10",
54
- "@pnpm/deps.compliance.sbom": "1100.2.0",
55
- "@pnpm/logger": "1100.0.0"
53
+ "@pnpm/logger": "1100.0.0",
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"