@pnpm/deps.compliance.sbom 1100.4.0 → 1101.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @pnpm/deps.compliance.sbom
2
2
 
3
+ ## 1101.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - The three registry lookups are now named for what they are keyed by, so that none of them is called `registries` — a name the `registries` setting itself has taken:
8
+
9
+ | before | after |
10
+ |---|---|
11
+ | `Config.registries` | `Config.registriesByScope` |
12
+ | `Config.namedRegistries` | `Config.registriesByPrefix` |
13
+ | `Config.registryOptions` | `Config.registryOptionsByUrl` |
14
+
15
+ The same rename applies to the `RegistryContext` fields, the `Registries` and `NamedRegistries` types (now `RegistriesByScope` and `RegistriesByPrefix`), `normalizeRegistries` / `normalizeNamedRegistries` (now `normalizeRegistriesByScope` / `normalizeRegistriesByPrefix`), and the `BUILTIN_NAMED_REGISTRIES` constant (now `BUILTIN_REGISTRIES_BY_PREFIX`).
16
+
17
+ This is an internal rename: no setting, error code, lockfile field, or `.pnpmfile.cjs` hook field changes. A `preResolution` hook still reads `ctx.registries`, which is the name pacquet passes as well. The `registries` and `namedRegistries` settings are read under the names users write them.
18
+
19
+ The pnpr resolve request sends `registriesByPrefix` where it sent `namedRegistries`. A pnpr server and its clients must be on matching versions, which is already the case for an experimental server.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies:
24
+ - @pnpm/config.normalize-registries@1101.0.0
25
+ - @pnpm/config.package-is-installable@1100.1.4
26
+ - @pnpm/constants@1102.0.0
27
+ - @pnpm/error@1100.1.3
28
+ - @pnpm/lockfile.detect-dep-types@1100.0.20
29
+ - @pnpm/lockfile.types@1100.0.20
30
+ - @pnpm/lockfile.utils@1102.0.0
31
+ - @pnpm/lockfile.walker@1100.0.20
32
+ - @pnpm/pkg-manifest.reader@1100.0.17
33
+ - @pnpm/resolving.resolver-base@1101.1.1
34
+ - @pnpm/store.index@1100.2.5
35
+ - @pnpm/store.pkg-finder@1100.0.30
36
+ - @pnpm/types@1102.0.0
37
+
38
+ ## 1100.4.1
39
+
40
+ ### Patch Changes
41
+
42
+ - An `integrity` recorded on a git dependency's resolution (`resolution: {type: git, repo, commit, integrity: sha512-…}`) is no longer treated as a checksum. pnpm never verifies a git checkout against such a hash — the commit pins the content — so it is now dropped when the lockfile is rewritten, and `pnpm sbom` no longer republishes it as a CycloneDX/SPDX checksum. Lockfiles carrying one also load again instead of failing with `ERR_PNPM_BROKEN_LOCKFILE` [#13042](https://github.com/pnpm/pnpm/issues/13042).
43
+
44
+ `pnpm sbom` now also publishes the checksum of a `type: binary` runtime archive, which pnpm does verify.
45
+
46
+ - `pnpm sbom` no longer emits components for optional platform-specific dependencies that cannot be installed on the current platform (for example, the native `@rolldown/binding-*` variants for other operating systems). Such packages are present in the lockfile but are never downloaded, so their license (and other metadata) could not be resolved and they appeared in the SBOM without one. `pnpm sbom --lockfile-only` still describes the whole lockfile graph, which is platform-independent by design.
47
+
48
+ - Updated dependencies:
49
+ - @pnpm/config.package-is-installable@1100.1.3
50
+ - @pnpm/error@1100.1.2
51
+ - @pnpm/lockfile.utils@1101.1.0
52
+ - @pnpm/pkg-manifest.reader@1100.0.16
53
+ - @pnpm/store.index@1100.2.4
54
+ - @pnpm/store.pkg-finder@1100.0.29
55
+
3
56
  ## 1100.4.0
4
57
 
5
58
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  import type { LockfileObject } from '@pnpm/lockfile.types';
2
2
  import type { Resolution } from '@pnpm/resolving.resolver-base';
3
- import type { DependenciesField, ProjectId, Registries } from '@pnpm/types';
3
+ import type { DependenciesField, ProjectId, RegistriesByScope, SupportedArchitectures } from '@pnpm/types';
4
4
  import type { SbomComponentType, SbomResult } from './types.js';
5
5
  export interface WorkspacePackageInfo {
6
6
  name: string;
@@ -23,10 +23,11 @@ export interface CollectSbomComponentsOptions {
23
23
  include?: {
24
24
  [dependenciesField in DependenciesField]: boolean;
25
25
  };
26
- registries: Registries;
27
- namedRegistries?: Record<string, string>;
26
+ registriesByScope: RegistriesByScope;
27
+ registriesByPrefix?: Record<string, string>;
28
28
  lockfileDir: string;
29
29
  includedImporterIds?: ProjectId[];
30
+ supportedArchitectures?: SupportedArchitectures;
30
31
  lockfileOnly?: boolean;
31
32
  storeDir?: string;
32
33
  virtualStoreDirMaxLength?: number;
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
- import { normalizeNamedRegistries } from '@pnpm/config.normalize-registries';
2
+ import { normalizeRegistriesByPrefix } from '@pnpm/config.normalize-registries';
3
+ import { checkPackageInstallability } from '@pnpm/config.package-is-installable';
3
4
  import { PnpmError } from '@pnpm/error';
4
5
  import { DepType, detectDepTypes } from '@pnpm/lockfile.detect-dep-types';
5
6
  import { nameVerFromPkgSnapshot, pkgSnapshotToResolution } from '@pnpm/lockfile.utils';
@@ -132,6 +133,23 @@ async function walkStep(step, parentPurl, depTypes, componentsMap, relationships
132
133
  const { name, version, nonSemverVersion, registryName } = nameVerFromPkgSnapshot(depPath, pkgSnapshot);
133
134
  if (!name || !version)
134
135
  return;
136
+ // An optional dependency for another platform is in the lockfile but was
137
+ // never fetched, so the store holds no metadata to describe it with.
138
+ // `checkPackageInstallability`, not `packageIsInstallable`: the latter
139
+ // reports every skip through the install loggers, which reading a
140
+ // lockfile must not do.
141
+ if (!opts.lockfileOnly && pkgSnapshot.optional === true && checkPackageInstallability(pkgSnapshot.id ?? depPath, {
142
+ name,
143
+ version,
144
+ cpu: pkgSnapshot.cpu,
145
+ os: pkgSnapshot.os,
146
+ libc: pkgSnapshot.libc,
147
+ }, {
148
+ optional: true,
149
+ supportedArchitectures: opts.supportedArchitectures,
150
+ }) != null) {
151
+ return;
152
+ }
135
153
  // Resolve the alias before the purl is built. An unknown alias would
136
154
  // otherwise yield an unqualified purl that collides with the same
137
155
  // package from the default registry, and the `componentsMap.has(purl)`
@@ -140,9 +158,9 @@ async function walkStep(step, parentPurl, depTypes, componentsMap, relationships
140
158
  // artifact from a compliance document.
141
159
  const registryUrl = registryName == null
142
160
  ? undefined
143
- : normalizeNamedRegistries(opts.namedRegistries)[registryName];
161
+ : normalizeRegistriesByPrefix(opts.registriesByPrefix)[registryName];
144
162
  if (registryName != null && registryUrl == null) {
145
- throw new PnpmError('MISSING_NAMED_REGISTRY', `Cannot describe package "${depPath}": it was resolved from the named registry '${registryName}:', which is not present in the namedRegistries setting.`, { hint: `Add '${registryName}' to the namedRegistries setting in pnpm-workspace.yaml.` });
163
+ throw new PnpmError('MISSING_NAMED_REGISTRY', `Cannot describe package "${depPath}": it was resolved from the named registry '${registryName}:', which is not present in the registriesByPrefix setting.`, { hint: `Add '${registryName}' to the registriesByPrefix setting in pnpm-workspace.yaml.` });
146
164
  }
147
165
  const purl = buildPurl({
148
166
  name,
@@ -153,12 +171,12 @@ async function walkStep(step, parentPurl, depTypes, componentsMap, relationships
153
171
  relationships.push({ from: parentPurl, to: purl });
154
172
  if (componentsMap.has(purl))
155
173
  return;
156
- const integrity = pkgSnapshot.resolution.integrity;
157
- const resolution = pkgSnapshotToResolution(depPath, pkgSnapshot, { registries: opts.registries, namedRegistries: opts.namedRegistries });
174
+ const integrity = verifiedIntegrity(pkgSnapshot.resolution);
175
+ const resolution = pkgSnapshotToResolution(depPath, pkgSnapshot, { registriesByScope: opts.registriesByScope, registriesByPrefix: opts.registriesByPrefix });
158
176
  const tarballUrl = resolution.tarball ?? gitDownloadUrl(resolution);
159
177
  let metadata = {};
160
178
  if (metadataOpts) {
161
- metadata = await getPkgMetadata(depPath, pkgSnapshot, { registries: opts.registries, namedRegistries: opts.namedRegistries }, metadataOpts);
179
+ metadata = await getPkgMetadata(depPath, pkgSnapshot, { registriesByScope: opts.registriesByScope, registriesByPrefix: opts.registriesByPrefix }, metadataOpts);
162
180
  }
163
181
  const component = {
164
182
  name,
@@ -225,4 +243,22 @@ export function resolveWorkspaceDeps(lockfile, importerIds, include) {
225
243
  }
226
244
  return { links, additionalImporterIds };
227
245
  }
246
+ /**
247
+ * The resolution's integrity, but only where pnpm verifies the downloaded
248
+ * bytes against it: the tarball/registry hash and a `type: binary` runtime
249
+ * archive's. Nothing checks a git checkout against a hash, so an `integrity`
250
+ * recorded on one is not a checksum and is never published as one.
251
+ *
252
+ * Read from an untyped lockfile, so the shape is probed rather than trusted:
253
+ * reading a checksum out of a malformed resolution yields nothing instead of
254
+ * throwing, and a non-string integrity never reaches `ssri.parse` downstream.
255
+ * (A malformed resolution still fails the walk further along, in
256
+ * `pkgSnapshotToResolution` — this only keeps the checksum lookup total.)
257
+ */
258
+ function verifiedIntegrity(resolution) {
259
+ const { type, integrity } = (resolution ?? {});
260
+ if (typeof integrity !== 'string')
261
+ return undefined;
262
+ return (type === undefined || type === 'binary') ? integrity : undefined;
263
+ }
228
264
  //# sourceMappingURL=collectComponents.js.map
package/lib/purl.js CHANGED
@@ -28,7 +28,7 @@ export function buildPurl(opts) {
28
28
  * Reduce a registry URL to the parts that identify the registry, dropping
29
29
  * anything that could carry a secret.
30
30
  *
31
- * An SBOM is meant to be published, and a `namedRegistries` entry may
31
+ * An SBOM is meant to be published, and a `registriesByPrefix` entry may
32
32
  * legitimately embed credentials — as userinfo
33
33
  * (`https://user:token@npm.example.com/`) or in a query string
34
34
  * (`?api_key=…`). Origin and path are kept because two registries can
@@ -37,7 +37,7 @@ export function buildPurl(opts) {
37
37
  * qualifier exists to prevent.
38
38
  *
39
39
  * A URL that doesn't parse is returned unchanged: it has no userinfo to
40
- * strip, and `namedRegistries` values are validated as http(s) URLs when
40
+ * strip, and `registriesByPrefix` values are validated as http(s) URLs when
41
41
  * the resolver is constructed, so this is unreachable in practice.
42
42
  */
43
43
  function sanitizeRegistryUrl(url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.compliance.sbom",
3
- "version": "1100.4.0",
3
+ "version": "1101.0.0",
4
4
  "description": "Generate SBOM from pnpm lockfile",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -30,20 +30,21 @@
30
30
  "!*.map"
31
31
  ],
32
32
  "dependencies": {
33
- "@cyclonedx/cyclonedx-library": "10.1.0",
34
- "@pnpm/config.normalize-registries": "1100.1.0",
35
- "@pnpm/constants": "1101.0.0",
33
+ "@cyclonedx/cyclonedx-library": "10.2.0",
34
+ "@pnpm/config.normalize-registries": "1101.0.0",
35
+ "@pnpm/config.package-is-installable": "1100.1.4",
36
+ "@pnpm/constants": "1102.0.0",
36
37
  "@pnpm/deps.compliance.license-resolver": "1100.0.1",
37
- "@pnpm/error": "1100.1.1",
38
- "@pnpm/lockfile.detect-dep-types": "1100.0.19",
39
- "@pnpm/lockfile.types": "1100.0.19",
40
- "@pnpm/lockfile.utils": "1101.0.0",
41
- "@pnpm/lockfile.walker": "1100.0.19",
42
- "@pnpm/pkg-manifest.reader": "1100.0.15",
43
- "@pnpm/resolving.resolver-base": "1101.1.0",
44
- "@pnpm/store.index": "1100.2.3",
45
- "@pnpm/store.pkg-finder": "1100.0.28",
46
- "@pnpm/types": "1101.9.0",
38
+ "@pnpm/error": "1100.1.3",
39
+ "@pnpm/lockfile.detect-dep-types": "1100.0.20",
40
+ "@pnpm/lockfile.types": "1100.0.20",
41
+ "@pnpm/lockfile.utils": "1102.0.0",
42
+ "@pnpm/lockfile.walker": "1100.0.20",
43
+ "@pnpm/pkg-manifest.reader": "1100.0.17",
44
+ "@pnpm/resolving.resolver-base": "1101.1.1",
45
+ "@pnpm/store.index": "1100.2.5",
46
+ "@pnpm/store.pkg-finder": "1100.0.30",
47
+ "@pnpm/types": "1102.0.0",
47
48
  "p-limit": "^7.3.1",
48
49
  "ssri": "13.0.1"
49
50
  },
@@ -52,9 +53,9 @@
52
53
  },
53
54
  "devDependencies": {
54
55
  "@jest/globals": "30.4.1",
55
- "@pnpm/deps.compliance.sbom": "1100.4.0",
56
+ "@pnpm/deps.compliance.sbom": "1101.0.0",
56
57
  "@pnpm/logger": "1100.0.0",
57
- "@pnpm/store.cafs": "1100.1.18",
58
+ "@pnpm/store.cafs": "1100.2.0",
58
59
  "@types/ssri": "^7.1.5"
59
60
  },
60
61
  "engines": {