vouchington-tooling 0.19.1 → 0.19.2

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/README.md CHANGED
@@ -328,7 +328,10 @@ or derive a configuration or documentation package spec from that manifest inste
328
328
 
329
329
  `dependency-license-policy` keeps legal policy in the consumer. `collectPnpmLicenseReport` creates
330
330
  an isolated, script-free temporary workspace and store, expands pnpm's supported architectures to
331
- every `os`, `cpu`, and `libc` selector represented in the lockfile, and validates the JSON report.
331
+ every `os`, `cpu`, and `libc` selector represented in the lockfile, drops every `engines` constraint
332
+ from the audit copy of the lockfile, and validates the JSON report. Dropping `engines` keeps
333
+ `pnpm fetch` from skipping optional packages that exclude the running Node.js; pnpm 12's `fetch`
334
+ ignores `force`, so their licenses would otherwise report as Unknown.
332
335
  Pass explicit denied SPDX IDs and prefixes, exact aliases, and justified allowlist scopes to
333
336
  `evaluatePnpmLicenseReport`. Unknown, malformed, and custom SPDX references fail closed. Allowlist
334
337
  scopes are either intentionally global or an exact package-name set; the library returns structured
@@ -26,7 +26,10 @@ export function collectPnpmLicenseReport(repoRoot, options = {}) {
26
26
  const auditWorkspace = prepareWorkspace(repoRoot, readFile(lockfilePath, 'utf8'), readFile(workspacePath, 'utf8'));
27
27
  try {
28
28
  const storeConfig = `--config.store-dir=${join(auditWorkspace.cwd, '.pnpm-store')}`;
29
- const fetchResult = execute('pnpm', [storeConfig, '--config.force=true', 'fetch', '--ignore-scripts'], { cwd: auditWorkspace.cwd, encoding: 'utf8' });
29
+ const fetchResult = execute('pnpm', [storeConfig, 'fetch', '--ignore-scripts'], {
30
+ cwd: auditWorkspace.cwd,
31
+ encoding: 'utf8',
32
+ });
30
33
  assertCommandSucceeded('pnpm fetch', fetchResult);
31
34
  const result = execute('pnpm', [storeConfig, 'licenses', 'list', '--json'], {
32
35
  cwd: auditWorkspace.cwd,
@@ -2,8 +2,21 @@ export interface PnpmLicenseAuditWorkspace {
2
2
  readonly cleanup: () => void;
3
3
  readonly cwd: string;
4
4
  }
5
- export declare function renderPnpmLicenseAuditWorkspace(lockfileSource: string, workspaceSource: string, paths: {
5
+ /** The rendered `pnpm-lock.yaml` and `pnpm-workspace.yaml` of a license audit workspace. */
6
+ export interface PnpmLicenseAuditFiles {
7
+ readonly lockfile: string;
8
+ readonly workspace: string;
9
+ }
10
+ /**
11
+ * Renders the audit copies of `pnpm-lock.yaml` and `pnpm-workspace.yaml` so `pnpm fetch` downloads
12
+ * every package in the lockfile, including optional ones that don't match the host.
13
+ *
14
+ * The workspace supports every `os`, `cpu`, and `libc` in the lockfile. The lockfile drops every
15
+ * `engines` constraint because pnpm 12's `fetch` ignores `force` and skips an optional package whose
16
+ * `engines` exclude the running Node.js, which would leave that package's license Unknown.
17
+ */
18
+ export declare function renderPnpmLicenseAuditFiles(lockfileSource: string, workspaceSource: string, paths: {
6
19
  lockfile: string;
7
20
  workspace: string;
8
- }): string;
21
+ }): PnpmLicenseAuditFiles;
9
22
  export declare function preparePnpmLicenseAuditWorkspace(repoRoot: string, lockfileSource: string, workspaceSource: string): PnpmLicenseAuditWorkspace;
@@ -1,20 +1,20 @@
1
1
  import { copyFileSync, existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
- import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
4
+ import { isMap, parseDocument, stringify as stringifyYaml } from 'yaml';
5
5
  const PLATFORM_KEYS = ['os', 'cpu', 'libc'];
6
- function parseYamlObject(source, path) {
7
- let parsed;
8
- try {
9
- parsed = parseYaml(source);
10
- }
11
- catch (error) {
6
+ function parseYamlDocument(source, path) {
7
+ const document = parseDocument(source);
8
+ const [error] = document.errors;
9
+ if (error)
12
10
  throw new Error(`failed to parse ${path}: ${String(error)}`, { cause: error });
13
- }
14
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
11
+ return document;
12
+ }
13
+ function assertYamlObject(value, path) {
14
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15
15
  throw new Error(`expected ${path} to contain a YAML object`);
16
16
  }
17
- return parsed;
17
+ return value;
18
18
  }
19
19
  function getStringList(value, path) {
20
20
  if (typeof value === 'string')
@@ -24,13 +24,7 @@ function getStringList(value, path) {
24
24
  }
25
25
  return value;
26
26
  }
27
- export function renderPnpmLicenseAuditWorkspace(lockfileSource, workspaceSource, paths) {
28
- const lockfile = parseYamlObject(lockfileSource, paths.lockfile);
29
- const workspace = parseYamlObject(workspaceSource, paths.workspace);
30
- const packages = lockfile.packages;
31
- if (typeof packages !== 'object' || packages === null || Array.isArray(packages)) {
32
- throw new Error(`expected ${paths.lockfile} to contain a packages object`);
33
- }
27
+ function collectSupportedArchitectures(packages, lockfilePath) {
34
28
  const supportedArchitectures = {
35
29
  cpu: ['current'],
36
30
  libc: ['current'],
@@ -44,27 +38,52 @@ export function renderPnpmLicenseAuditWorkspace(lockfileSource, workspaceSource,
44
38
  const value = snapshot[key];
45
39
  if (value === undefined)
46
40
  continue;
47
- for (const platform of getStringList(value, `${paths.lockfile} packages.*.${key}`)) {
41
+ for (const platform of getStringList(value, `${lockfilePath} packages.*.${key}`)) {
48
42
  values.add(platform);
49
43
  }
50
44
  }
51
45
  supportedArchitectures[key].push(...[...values].filter((value) => value !== 'current').sort());
52
46
  }
53
- return stringifyYaml({ ...workspace, packages: [], supportedArchitectures });
47
+ return supportedArchitectures;
48
+ }
49
+ /**
50
+ * Renders the audit copies of `pnpm-lock.yaml` and `pnpm-workspace.yaml` so `pnpm fetch` downloads
51
+ * every package in the lockfile, including optional ones that don't match the host.
52
+ *
53
+ * The workspace supports every `os`, `cpu`, and `libc` in the lockfile. The lockfile drops every
54
+ * `engines` constraint because pnpm 12's `fetch` ignores `force` and skips an optional package whose
55
+ * `engines` exclude the running Node.js, which would leave that package's license Unknown.
56
+ */
57
+ export function renderPnpmLicenseAuditFiles(lockfileSource, workspaceSource, paths) {
58
+ const lockfileDocument = parseYamlDocument(lockfileSource, paths.lockfile);
59
+ const lockfile = assertYamlObject(lockfileDocument.toJS(), paths.lockfile);
60
+ const workspace = assertYamlObject(parseYamlDocument(workspaceSource, paths.workspace).toJS(), paths.workspace);
61
+ const packageNodes = lockfileDocument.get('packages');
62
+ if (!isMap(packageNodes)) {
63
+ throw new Error(`expected ${paths.lockfile} to contain a packages object`);
64
+ }
65
+ const supportedArchitectures = collectSupportedArchitectures(lockfile.packages, paths.lockfile);
66
+ for (const { value } of packageNodes.items)
67
+ if (isMap(value))
68
+ value.delete('engines');
69
+ return {
70
+ lockfile: lockfileDocument.toString({ lineWidth: 0 }),
71
+ workspace: stringifyYaml({ ...workspace, packages: [], supportedArchitectures }),
72
+ };
54
73
  }
55
74
  export function preparePnpmLicenseAuditWorkspace(repoRoot, lockfileSource, workspaceSource) {
56
75
  const auditRoot = mkdtempSync(join(tmpdir(), 'dependency-license-audit-'));
57
76
  try {
58
- for (const filename of ['package.json', 'pnpm-lock.yaml']) {
59
- copyFileSync(join(repoRoot, filename), join(auditRoot, filename));
60
- }
77
+ copyFileSync(join(repoRoot, 'package.json'), join(auditRoot, 'package.json'));
61
78
  const npmrc = join(repoRoot, '.npmrc');
62
79
  if (existsSync(npmrc))
63
80
  copyFileSync(npmrc, join(auditRoot, '.npmrc'));
64
- writeFileSync(join(auditRoot, 'pnpm-workspace.yaml'), renderPnpmLicenseAuditWorkspace(lockfileSource, workspaceSource, {
81
+ const files = renderPnpmLicenseAuditFiles(lockfileSource, workspaceSource, {
65
82
  lockfile: join(repoRoot, 'pnpm-lock.yaml'),
66
83
  workspace: join(repoRoot, 'pnpm-workspace.yaml'),
67
- }), 'utf8');
84
+ });
85
+ writeFileSync(join(auditRoot, 'pnpm-lock.yaml'), files.lockfile, 'utf8');
86
+ writeFileSync(join(auditRoot, 'pnpm-workspace.yaml'), files.workspace, 'utf8');
68
87
  }
69
88
  catch (error) {
70
89
  rmSync(auditRoot, { force: true, recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Vouchington CLI and extractable tooling libraries.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
6
6
  "bugs": {