@ttt-productions/repo-guards 0.1.14 → 0.1.15

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.
@@ -2,7 +2,7 @@ import type { GenericGuardDefinition } from '../../node/suite/config.js';
2
2
  export interface DependencyOverride {
3
3
  /** The manifest holding the parent-scoped `overrides` entry, repo-relative. */
4
4
  manifest: string;
5
- /** The folder whose `node_modules` holds the installed parent ('' for the repository root). */
5
+ /** The folder whose `package-lock.json` records the installed parent ('' for the repository root). */
6
6
  installedUnder: string;
7
7
  parent: string;
8
8
  child: string;
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-overrides.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/dependency-overrides.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAIzE,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC1C;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAC7B,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC,EAC5D,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,IAAI,CAYf;AAED,eAAO,MAAM,uBAAuB,EAAE,sBAAsB,CAAC,uBAAuB,CAmEnF,CAAC"}
1
+ {"version":3,"file":"dependency-overrides.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/dependency-overrides.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAIzE,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,sGAAsG;IACtG,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC1C;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAC7B,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC,EAC5D,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,IAAI,CAYf;AAED,eAAO,MAAM,uBAAuB,EAAE,sBAAsB,CAAC,uBAAuB,CA8EnF,CAAC"}
@@ -52,15 +52,26 @@ export const dependencyOverrideGuard = {
52
52
  const findings = [];
53
53
  for (const o of facts.overrides) {
54
54
  const manifest = JSON.parse(context.file(o.manifest).text);
55
- const parentPath = `${o.installedUnder ? `${o.installedUnder}/` : ''}node_modules/${o.parent}/package.json`;
56
- if (!context.reader.exists(parentPath)) {
55
+ // The parent's declared range is read from the committed lockfile, never `node_modules`:
56
+ // every checkout has the lockfile, a CI job that never installs this folder included, and
57
+ // `npm ci` installs exactly what it records.
58
+ const lockPath = `${o.installedUnder ? `${o.installedUnder}/` : ''}package-lock.json`;
59
+ if (!context.reader.exists(lockPath)) {
57
60
  findings.push({
58
61
  file: o.manifest,
59
- detail: `${o.parent} is not installed under ${o.installedUnder || 'the root'}, so the override cannot be judged`,
62
+ detail: `${lockPath} is missing, so the override cannot be judged`,
63
+ });
64
+ continue;
65
+ }
66
+ const lock = JSON.parse(context.file(lockPath).text);
67
+ const parent = lock.packages?.[`node_modules/${o.parent}`];
68
+ if (!parent) {
69
+ findings.push({
70
+ file: o.manifest,
71
+ detail: `${lockPath} records no installed ${o.parent}, so the override cannot be judged`,
60
72
  });
61
73
  continue;
62
74
  }
63
- const parent = JSON.parse(context.file(parentPath).text);
64
75
  const scoped = manifest.overrides?.[o.parent];
65
76
  const pinned = scoped !== null && typeof scoped === 'object'
66
77
  ? scoped[o.child]
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-overrides.js","sourceRoot":"","sources":["../../../src/modules/repo/dependency-overrides.ts"],"names":[],"mappings":"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","sourcesContent":["// A dependency override exists only while it is needed: while the parent's declared range for the\n// child can still resolve to an affected version. Once the parent's range can no longer reach one,\n// the override is redundant and is removed; while it can, the override is present and its own range\n// admits no affected version.\n\nimport semver from 'semver';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding } from '../../guard.js';\nimport { isText, reviewedProblems, textProblem } from '../../support/facts.js';\n\nexport interface DependencyOverride {\n /** The manifest holding the parent-scoped `overrides` entry, repo-relative. */\n manifest: string;\n /** The folder whose `node_modules` holds the installed parent ('' for the repository root). */\n installedUnder: string;\n parent: string;\n child: string;\n /** The child's affected versions, as a semver range. */\n affected: string;\n why: string;\n}\n\nexport interface DependencyOverrideFacts {\n overrides: readonly DependencyOverride[];\n}\n\n/** Why the override is wrong for these ranges, or null when it is exactly as it must be. */\nexport function overrideProblem(\n o: Pick<DependencyOverride, 'parent' | 'child' | 'affected'>,\n parentRange: string | undefined,\n overrideRange: string | undefined,\n): string | null {\n if (parentRange === undefined)\n return `${o.parent} no longer declares ${o.child}: remove the override`;\n if (semver.validRange(parentRange) !== null && !semver.intersects(parentRange, o.affected)) {\n return `${o.parent} now declares ${o.child} ${parentRange}, which reaches no affected version: remove the override`;\n }\n if (overrideRange === undefined)\n return `${o.parent} still declares ${o.child} ${parentRange}, which can reach an affected version, and no override pins it`;\n if (semver.validRange(overrideRange) === null || semver.intersects(overrideRange, o.affected)) {\n return `the override pins ${o.child} to ${overrideRange}, which admits an affected version (${o.affected})`;\n }\n return null;\n}\n\nexport const dependencyOverrideGuard: GenericGuardDefinition<DependencyOverrideFacts> = {\n id: 'dependencyOverrides',\n absentWhen: 'the app carries no dependency override',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.overrides, 'overrides', problems, {\n key: 'parent',\n // One override per manifest: each manifest that installs the parent carries its own copy.\n id: (o) =>\n isText(o.child) && isText(o.manifest)\n ? `${o.manifest}: ${o.parent} → ${o.child}`\n : undefined,\n idNames: 'manifest or child',\n nonEmpty: true,\n extra(o, label) {\n textProblem(o.manifest, `${label}'s manifest`, problems);\n if (typeof o.installedUnder !== 'string')\n problems.push(`${label}'s installedUnder is missing ('' for the repository root)`);\n if (!isText(o.affected) || semver.validRange(o.affected) === null)\n problems.push(`${label}'s affected is not a semver range`);\n },\n });\n return problems;\n },\n build(facts) {\n return [\n {\n title:\n 'every dependency override exists exactly while its parent can still reach an affected version',\n description:\n \"An override pins a child dependency under one parent because the parent's own range can resolve to an affected \" +\n \"version. Once it cannot, the override is redundant and is removed; while it can, the override's range admits no \" +\n 'affected version.',\n run(context) {\n const findings: Finding[] = [];\n for (const o of facts.overrides) {\n const manifest = JSON.parse(context.file(o.manifest).text) as {\n overrides?: Record<string, unknown>;\n };\n const parentPath = `${o.installedUnder ? `${o.installedUnder}/` : ''}node_modules/${o.parent}/package.json`;\n if (!context.reader.exists(parentPath)) {\n findings.push({\n file: o.manifest,\n detail: `${o.parent} is not installed under ${o.installedUnder || 'the root'}, so the override cannot be judged`,\n });\n continue;\n }\n const parent = JSON.parse(context.file(parentPath).text) as {\n dependencies?: Record<string, string>;\n };\n const scoped = manifest.overrides?.[o.parent];\n const pinned =\n scoped !== null && typeof scoped === 'object'\n ? (scoped as Record<string, unknown>)[o.child]\n : undefined;\n const problem = overrideProblem(\n o,\n parent.dependencies?.[o.child],\n typeof pinned === 'string' ? pinned : undefined,\n );\n if (problem) findings.push({ file: o.manifest, detail: problem });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
1
+ {"version":3,"file":"dependency-overrides.js","sourceRoot":"","sources":["../../../src/modules/repo/dependency-overrides.ts"],"names":[],"mappings":"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","sourcesContent":["// A dependency override exists only while it is needed: while the parent's declared range for the\n// child can still resolve to an affected version. Once the parent's range can no longer reach one,\n// the override is redundant and is removed; while it can, the override is present and its own range\n// admits no affected version.\n\nimport semver from 'semver';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding } from '../../guard.js';\nimport { isText, reviewedProblems, textProblem } from '../../support/facts.js';\n\nexport interface DependencyOverride {\n /** The manifest holding the parent-scoped `overrides` entry, repo-relative. */\n manifest: string;\n /** The folder whose `package-lock.json` records the installed parent ('' for the repository root). */\n installedUnder: string;\n parent: string;\n child: string;\n /** The child's affected versions, as a semver range. */\n affected: string;\n why: string;\n}\n\nexport interface DependencyOverrideFacts {\n overrides: readonly DependencyOverride[];\n}\n\n/** Why the override is wrong for these ranges, or null when it is exactly as it must be. */\nexport function overrideProblem(\n o: Pick<DependencyOverride, 'parent' | 'child' | 'affected'>,\n parentRange: string | undefined,\n overrideRange: string | undefined,\n): string | null {\n if (parentRange === undefined)\n return `${o.parent} no longer declares ${o.child}: remove the override`;\n if (semver.validRange(parentRange) !== null && !semver.intersects(parentRange, o.affected)) {\n return `${o.parent} now declares ${o.child} ${parentRange}, which reaches no affected version: remove the override`;\n }\n if (overrideRange === undefined)\n return `${o.parent} still declares ${o.child} ${parentRange}, which can reach an affected version, and no override pins it`;\n if (semver.validRange(overrideRange) === null || semver.intersects(overrideRange, o.affected)) {\n return `the override pins ${o.child} to ${overrideRange}, which admits an affected version (${o.affected})`;\n }\n return null;\n}\n\nexport const dependencyOverrideGuard: GenericGuardDefinition<DependencyOverrideFacts> = {\n id: 'dependencyOverrides',\n absentWhen: 'the app carries no dependency override',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.overrides, 'overrides', problems, {\n key: 'parent',\n // One override per manifest: each manifest that installs the parent carries its own copy.\n id: (o) =>\n isText(o.child) && isText(o.manifest)\n ? `${o.manifest}: ${o.parent} → ${o.child}`\n : undefined,\n idNames: 'manifest or child',\n nonEmpty: true,\n extra(o, label) {\n textProblem(o.manifest, `${label}'s manifest`, problems);\n if (typeof o.installedUnder !== 'string')\n problems.push(`${label}'s installedUnder is missing ('' for the repository root)`);\n if (!isText(o.affected) || semver.validRange(o.affected) === null)\n problems.push(`${label}'s affected is not a semver range`);\n },\n });\n return problems;\n },\n build(facts) {\n return [\n {\n title:\n 'every dependency override exists exactly while its parent can still reach an affected version',\n description:\n \"An override pins a child dependency under one parent because the parent's own range can resolve to an affected \" +\n \"version. Once it cannot, the override is redundant and is removed; while it can, the override's range admits no \" +\n 'affected version.',\n run(context) {\n const findings: Finding[] = [];\n for (const o of facts.overrides) {\n const manifest = JSON.parse(context.file(o.manifest).text) as {\n overrides?: Record<string, unknown>;\n };\n // The parent's declared range is read from the committed lockfile, never `node_modules`:\n // every checkout has the lockfile, a CI job that never installs this folder included, and\n // `npm ci` installs exactly what it records.\n const lockPath = `${o.installedUnder ? `${o.installedUnder}/` : ''}package-lock.json`;\n if (!context.reader.exists(lockPath)) {\n findings.push({\n file: o.manifest,\n detail: `${lockPath} is missing, so the override cannot be judged`,\n });\n continue;\n }\n const lock = JSON.parse(context.file(lockPath).text) as {\n packages?: Record<string, { dependencies?: Record<string, string> } | undefined>;\n };\n const parent = lock.packages?.[`node_modules/${o.parent}`];\n if (!parent) {\n findings.push({\n file: o.manifest,\n detail: `${lockPath} records no installed ${o.parent}, so the override cannot be judged`,\n });\n continue;\n }\n const scoped = manifest.overrides?.[o.parent];\n const pinned =\n scoped !== null && typeof scoped === 'object'\n ? (scoped as Record<string, unknown>)[o.child]\n : undefined;\n const problem = overrideProblem(\n o,\n parent.dependencies?.[o.child],\n typeof pinned === 'string' ? pinned : undefined,\n );\n if (problem) findings.push({ file: o.manifest, detail: problem });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/repo-guards",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",