From 1fc58160148e2c3ffea779eced1d0e1f6df732b8 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:14:07 +0000 Subject: [PATCH 01/11] feat: fail when the root and registry manifests disagree on a dependency --- package.json | 1 + scripts/check-registry-deps.mjs | 117 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 scripts/check-registry-deps.mjs diff --git a/package.json b/package.json index 9d58499..71084a8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "setup": "bash scripts/fetch-dep.sh", "clean": "rm -rf daml/canton-token-forge/.daml daml/canton-token-forge-test/.daml consumer-smoke/consumer/.daml consumer-smoke/consumer/vendor registry/dist", "smoke": "bash scripts/consumer-smoke.sh", + "check:deps": "node scripts/check-registry-deps.mjs", "build": "cd daml/canton-token-forge && LANG=C.UTF-8 dpm build && cd ../canton-token-forge-test && LANG=C.UTF-8 dpm build", "build:canton-token-forge": "cd daml/canton-token-forge && LANG=C.UTF-8 dpm build", "sandbox": "bash scripts/sandbox.sh", diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs new file mode 100644 index 0000000..c5472ac --- /dev/null +++ b/scripts/check-registry-deps.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node +// +// check-registry-deps.mjs - the root package.json ships registry/dist as its +// bin, so a consumer install resolves the service's imports against the root +// dependency list, while every test suite that vetted that code ran against +// registry/package.json's list. The two are deliberate duplicates: this +// guard fails when they drift apart, in either the declared ranges or the +// versions each lockfile actually resolved. + +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +const readJson = (path) => JSON.parse(readFileSync(path, 'utf8')) + +const rootManifest = readJson(resolve(repoRoot, 'package.json')) +const registryManifest = readJson(resolve(repoRoot, 'registry/package.json')) +const rootLock = readJson(resolve(repoRoot, 'package-lock.json')) +const registryLock = readJson(resolve(repoRoot, 'registry/package-lock.json')) + +const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] + +// A package can be a runtime dep on one side and a devDependency on the +// other, so ranges are compared regardless of section; the section is kept +// only to name where each range was found in a message. +function rangesBySection(manifest) { + const bySection = new Map() + for (const section of SECTIONS) { + for (const [name, range] of Object.entries(manifest[section] ?? {})) { + bySection.set(name, { section, range }) + } + } + return bySection +} + +const rootRanges = rangesBySection(rootManifest) +const registryRanges = rangesBySection(registryManifest) + +const failures = [] + +for (const name of Object.keys(rootManifest.dependencies ?? {})) { + if (!(name in (registryManifest.dependencies ?? {}))) { + failures.push( + `${name} is a runtime dependency of package.json but is not in registry/package.json's "dependencies"; no suite installs it. Add it to registry/package.json.`, + ) + } +} + +for (const name of Object.keys(registryManifest.dependencies ?? {})) { + if (!(name in (rootManifest.dependencies ?? {}))) { + failures.push( + `${name} is a runtime dependency of registry/package.json but is not in the root "dependencies"; a consumer install would not resolve it. Add it to package.json.`, + ) + } +} + +const sharedNames = [...rootRanges.keys()].filter((name) => registryRanges.has(name)).sort() + +for (const name of sharedNames) { + const root = rootRanges.get(name) + const registry = registryRanges.get(name) + if (root.range !== registry.range) { + failures.push( + `${name} is "${root.range}" in package.json ("${root.section}") and "${registry.range}" in registry/package.json ("${registry.section}"); make the two ranges identical.`, + ) + } +} + +for (const field of ['node', 'type']) { + const rootValue = field === 'node' ? rootManifest.engines?.node : rootManifest.type + const registryValue = field === 'node' ? registryManifest.engines?.node : registryManifest.type + const label = field === 'node' ? 'engines.node' : 'type' + if (rootValue !== registryValue) { + failures.push( + `${label} is ${JSON.stringify(rootValue)} in package.json and ${JSON.stringify(registryValue)} in registry/package.json; make the two identical.`, + ) + } +} + +let resolvedMatches = 0 +for (const name of sharedNames) { + const rootEntry = rootLock.packages?.[`node_modules/${name}`] + const registryEntry = registryLock.packages?.[`node_modules/${name}`] + if (!rootEntry) { + failures.push( + `${name} is declared in both manifests but has no "node_modules/${name}" entry in package-lock.json; run npm install to refresh it.`, + ) + continue + } + if (!registryEntry) { + failures.push( + `${name} is declared in both manifests but has no "node_modules/${name}" entry in registry/package-lock.json; run npm install to refresh it.`, + ) + continue + } + if (rootEntry.version !== registryEntry.version) { + failures.push( + `${name} resolves to ${rootEntry.version} in package-lock.json and ${registryEntry.version} in registry/package-lock.json; regenerate one lockfile so both trees run the same code.`, + ) + continue + } + resolvedMatches += 1 +} + +if (failures.length > 0) { + console.error('the root and registry/ packages disagree:') + for (const failure of failures) { + console.error(` - ${failure}`) + } + process.exit(1) +} + +console.log( + `manifests agree: ${sharedNames.length} packages named in both carry identical ranges, ${resolvedMatches} resolve to the same version in both lockfiles, engines.node and type match`, +) From f28172676bfcc16f6cf0b8524b75b1b544222b50 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:40:51 +0000 Subject: [PATCH 02/11] fix: compare every section a package is declared in, not the last one seen --- scripts/check-registry-deps.mjs | 41 +++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index c5472ac..1582dbd 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -23,13 +23,17 @@ const registryLock = readJson(resolve(repoRoot, 'registry/package-lock.json')) const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] // A package can be a runtime dep on one side and a devDependency on the -// other, so ranges are compared regardless of section; the section is kept -// only to name where each range was found in a message. +// other, so ranges are compared regardless of section. Every occurrence is +// kept rather than the last one seen: a name declared in two sections of one +// manifest would otherwise shadow itself, and the surviving entry can agree +// across the two files while the shadowed one drifts. function rangesBySection(manifest) { const bySection = new Map() for (const section of SECTIONS) { for (const [name, range] of Object.entries(manifest[section] ?? {})) { - bySection.set(name, { section, range }) + const occurrences = bySection.get(name) ?? [] + occurrences.push({ section, range }) + bySection.set(name, occurrences) } } return bySection @@ -58,13 +62,32 @@ for (const name of Object.keys(registryManifest.dependencies ?? {})) { const sharedNames = [...rootRanges.keys()].filter((name) => registryRanges.has(name)).sort() +// A manifest naming one package at two ranges is incoherent on its own, and +// reporting that as a cross-manifest disagreement would point at the wrong file. +for (const [file, ranges] of [ + ['package.json', rootRanges], + ['registry/package.json', registryRanges], +]) { + for (const [name, [first, ...rest]] of ranges) { + for (const other of rest) { + if (other.range !== first.range) { + failures.push( + `${name} is "${first.range}" in ${file}'s "${first.section}" and "${other.range}" in its "${other.section}"; a manifest cannot name one package at two ranges.`, + ) + } + } + } +} + for (const name of sharedNames) { - const root = rootRanges.get(name) - const registry = registryRanges.get(name) - if (root.range !== registry.range) { - failures.push( - `${name} is "${root.range}" in package.json ("${root.section}") and "${registry.range}" in registry/package.json ("${registry.section}"); make the two ranges identical.`, - ) + for (const root of rootRanges.get(name)) { + for (const registry of registryRanges.get(name)) { + if (root.range !== registry.range) { + failures.push( + `${name} is "${root.range}" in package.json ("${root.section}") and "${registry.range}" in registry/package.json ("${registry.section}"); make the two ranges identical.`, + ) + } + } } } From 3d3eb6ac5b9e3b30ece62b974eede3dd1bcb77f8 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:41:07 +0000 Subject: [PATCH 03/11] fix: fail when engines.node or type is absent from both manifests --- scripts/check-registry-deps.mjs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 1582dbd..38d70ce 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -91,14 +91,24 @@ for (const name of sharedNames) { } } -for (const field of ['node', 'type']) { - const rootValue = field === 'node' ? rootManifest.engines?.node : rootManifest.type - const registryValue = field === 'node' ? registryManifest.engines?.node : registryManifest.type - const label = field === 'node' ? 'engines.node' : 'type' +// Absence on both sides is a disagreement with npm's defaults rather than +// between the two files: an omitted "type" means commonjs, which the compiled +// ESM bin cannot be loaded under, and an omitted floor lets a consumer install +// on a runtime the closure does not support. +const FIELDS = [ + { label: 'engines.node', read: (manifest) => manifest.engines?.node }, + { label: 'type', read: (manifest) => manifest.type }, +] + +for (const { label, read } of FIELDS) { + const rootValue = read(rootManifest) + const registryValue = read(registryManifest) if (rootValue !== registryValue) { failures.push( `${label} is ${JSON.stringify(rootValue)} in package.json and ${JSON.stringify(registryValue)} in registry/package.json; make the two identical.`, ) + } else if (rootValue === undefined) { + failures.push(`${label} is missing from both package.json and registry/package.json; set it in both.`) } } From d2cadd454175bcea1e75d7ad8d534be07488ff39 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:42:20 +0000 Subject: [PATCH 04/11] fix: name the operation that actually converges two lockfiles --- scripts/check-registry-deps.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 38d70ce..9ae1cb1 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -129,8 +129,13 @@ for (const name of sharedNames) { continue } if (rootEntry.version !== registryEntry.version) { + // A plain npm install keeps any locked resolution that still satisfies the + // range, so it leaves this untouched; rebuilding the lockfile from scratch + // is what moves it. Naming the version instead (npm install pkg@version) + // also rewrites the manifest range, and when neither tree holds the newest + // version the ranges allow, both lockfiles have to be rebuilt. failures.push( - `${name} resolves to ${rootEntry.version} in package-lock.json and ${registryEntry.version} in registry/package-lock.json; regenerate one lockfile so both trees run the same code.`, + `${name} resolves to ${rootEntry.version} in package-lock.json and ${registryEntry.version} in registry/package-lock.json; npm install keeps a resolution that still satisfies the range, so delete the lockfile you are correcting and reinstall.`, ) continue } From 31273a25ccafc9391bc1e9417db93ed539604027 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:42:29 +0000 Subject: [PATCH 05/11] fix: report both lockfiles when neither records a shared package --- scripts/check-registry-deps.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 9ae1cb1..0b465f9 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -120,14 +120,13 @@ for (const name of sharedNames) { failures.push( `${name} is declared in both manifests but has no "node_modules/${name}" entry in package-lock.json; run npm install to refresh it.`, ) - continue } if (!registryEntry) { failures.push( `${name} is declared in both manifests but has no "node_modules/${name}" entry in registry/package-lock.json; run npm install to refresh it.`, ) - continue } + if (!rootEntry || !registryEntry) continue if (rootEntry.version !== registryEntry.version) { // A plain npm install keeps any locked resolution that still satisfies the // range, so it leaves this untouched; rebuilding the lockfile from scratch From c423634f0f3829d7be989b511ff3d6170e2ed516 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:43:00 +0000 Subject: [PATCH 06/11] feat: fail when a lockfile no longer records the manifest beside it --- scripts/check-registry-deps.mjs | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 0b465f9..2724157 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -141,6 +141,47 @@ for (const name of sharedNames) { resolvedMatches += 1 } +// Making two ranges identical satisfies the rule above while leaving each +// lockfile recording the range it was generated from, and npm ci refuses a tree +// in that state. Comparing the root entry npm writes into every lockfile against +// the manifest beside it is the cheap half of what npm ci validates. +const TREES = [ + { + manifest: rootManifest, + manifestFile: 'package.json', + lock: rootLock, + lockFile: 'package-lock.json', + }, + { + manifest: registryManifest, + manifestFile: 'registry/package.json', + lock: registryLock, + lockFile: 'registry/package-lock.json', + }, +] + +for (const { manifest, manifestFile, lock, lockFile } of TREES) { + const recorded = lock.packages?.[''] ?? {} + for (const section of SECTIONS) { + const declared = manifest[section] ?? {} + const locked = recorded[section] ?? {} + for (const [name, range] of Object.entries(declared)) { + if (locked[name] !== range) { + failures.push( + `${name} is "${range}" in ${manifestFile}'s "${section}" but ${lockFile} records ${JSON.stringify(locked[name])}; run npm install to bring the lockfile up to date.`, + ) + } + } + for (const name of Object.keys(locked)) { + if (!(name in declared)) { + failures.push( + `${lockFile} still records ${name} in "${section}" but ${manifestFile} no longer declares it; run npm install to bring the lockfile up to date.`, + ) + } + } + } +} + if (failures.length > 0) { console.error('the root and registry/ packages disagree:') for (const failure of failures) { @@ -150,5 +191,5 @@ if (failures.length > 0) { } console.log( - `manifests agree: ${sharedNames.length} packages named in both carry identical ranges, ${resolvedMatches} resolve to the same version in both lockfiles, engines.node and type match`, + `manifests agree: ${sharedNames.length} packages named in both carry identical ranges, ${resolvedMatches} resolve to the same version in both lockfiles, engines.node and type match, and each lockfile records the manifest beside it`, ) From 7724fd9e98d12a4ad34f451b72b0df6ad996dc5b Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:43:24 +0000 Subject: [PATCH 07/11] fix: say a lockfile does not record a package instead of printing undefined --- scripts/check-registry-deps.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 2724157..cb8fe9c 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -167,8 +167,10 @@ for (const { manifest, manifestFile, lock, lockFile } of TREES) { const locked = recorded[section] ?? {} for (const [name, range] of Object.entries(declared)) { if (locked[name] !== range) { + const recordedRange = + locked[name] === undefined ? 'does not record it' : `records "${locked[name]}"` failures.push( - `${name} is "${range}" in ${manifestFile}'s "${section}" but ${lockFile} records ${JSON.stringify(locked[name])}; run npm install to bring the lockfile up to date.`, + `${name} is "${range}" in ${manifestFile}'s "${section}" but ${lockFile} ${recordedRange}; run npm install to bring the lockfile up to date.`, ) } } From 0a61363d5641d4b6ae1ebf092b9abb1d9de66f6f Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Wed, 2 Sep 2026 21:48:42 +0000 Subject: [PATCH 08/11] docs: describe every rule the guard enforces in its header comment --- scripts/check-registry-deps.mjs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index cb8fe9c..97c73fd 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -3,9 +3,12 @@ // check-registry-deps.mjs - the root package.json ships registry/dist as its // bin, so a consumer install resolves the service's imports against the root // dependency list, while every test suite that vetted that code ran against -// registry/package.json's list. The two are deliberate duplicates: this -// guard fails when they drift apart, in either the declared ranges or the -// versions each lockfile actually resolved. +// registry/package.json's list. The two are deliberate duplicates, and this +// guard fails on four ways they come apart: a runtime dependency declared on +// one side only, a shared package at two ranges, a mismatched engines.node or +// type, and a package the two lockfiles resolve differently. It also fails +// when a lockfile stops recording the manifest beside it, which is the state +// an edit to one of the ranges leaves behind. import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' @@ -72,7 +75,7 @@ for (const [file, ranges] of [ for (const other of rest) { if (other.range !== first.range) { failures.push( - `${name} is "${first.range}" in ${file}'s "${first.section}" and "${other.range}" in its "${other.section}"; a manifest cannot name one package at two ranges.`, + `${name} is "${first.range}" in ${file}'s "${first.section}" and "${other.range}" in its "${other.section}"; a manifest cannot name one package at two ranges, so drop one of them.`, ) } } @@ -141,7 +144,7 @@ for (const name of sharedNames) { resolvedMatches += 1 } -// Making two ranges identical satisfies the rule above while leaving each +// Making two ranges identical satisfies the range rule while leaving each // lockfile recording the range it was generated from, and npm ci refuses a tree // in that state. Comparing the root entry npm writes into every lockfile against // the manifest beside it is the cheap half of what npm ci validates. From e2e28ff395cd7cbec6e2f15e6f8166997ab35ced Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Thu, 3 Sep 2026 11:47:02 +0000 Subject: [PATCH 09/11] fix: name the directory each lockfile is refreshed from --- scripts/check-registry-deps.mjs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 97c73fd..598649e 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -115,18 +115,24 @@ for (const { label, read } of FIELDS) { } } +// registry/ is not a workspace of the root package: each tree is installed from +// its own directory, so a root npm install leaves registry/package-lock.json +// exactly as it found it. +const ROOT_INSTALL = 'npm install' +const REGISTRY_INSTALL = 'npm install in registry/' + let resolvedMatches = 0 for (const name of sharedNames) { const rootEntry = rootLock.packages?.[`node_modules/${name}`] const registryEntry = registryLock.packages?.[`node_modules/${name}`] if (!rootEntry) { failures.push( - `${name} is declared in both manifests but has no "node_modules/${name}" entry in package-lock.json; run npm install to refresh it.`, + `${name} is declared in both manifests but has no "node_modules/${name}" entry in package-lock.json; run ${ROOT_INSTALL} to refresh it.`, ) } if (!registryEntry) { failures.push( - `${name} is declared in both manifests but has no "node_modules/${name}" entry in registry/package-lock.json; run npm install to refresh it.`, + `${name} is declared in both manifests but has no "node_modules/${name}" entry in registry/package-lock.json; run ${REGISTRY_INSTALL} to refresh it.`, ) } if (!rootEntry || !registryEntry) continue @@ -154,16 +160,18 @@ const TREES = [ manifestFile: 'package.json', lock: rootLock, lockFile: 'package-lock.json', + install: ROOT_INSTALL, }, { manifest: registryManifest, manifestFile: 'registry/package.json', lock: registryLock, lockFile: 'registry/package-lock.json', + install: REGISTRY_INSTALL, }, ] -for (const { manifest, manifestFile, lock, lockFile } of TREES) { +for (const { manifest, manifestFile, lock, lockFile, install } of TREES) { const recorded = lock.packages?.[''] ?? {} for (const section of SECTIONS) { const declared = manifest[section] ?? {} @@ -173,14 +181,14 @@ for (const { manifest, manifestFile, lock, lockFile } of TREES) { const recordedRange = locked[name] === undefined ? 'does not record it' : `records "${locked[name]}"` failures.push( - `${name} is "${range}" in ${manifestFile}'s "${section}" but ${lockFile} ${recordedRange}; run npm install to bring the lockfile up to date.`, + `${name} is "${range}" in ${manifestFile}'s "${section}" but ${lockFile} ${recordedRange}; run ${install} to bring the lockfile up to date.`, ) } } for (const name of Object.keys(locked)) { if (!(name in declared)) { failures.push( - `${lockFile} still records ${name} in "${section}" but ${manifestFile} no longer declares it; run npm install to bring the lockfile up to date.`, + `${lockFile} still records ${name} in "${section}" but ${manifestFile} no longer declares it; run ${install} to bring the lockfile up to date.`, ) } } From a04d0617e06acba788538423d665d8a83eaf8500 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Thu, 3 Sep 2026 11:48:09 +0000 Subject: [PATCH 10/11] fix: rebuild both lockfiles, since deleting one does not converge --- scripts/check-registry-deps.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index 598649e..a8d9d53 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -138,12 +138,12 @@ for (const name of sharedNames) { if (!rootEntry || !registryEntry) continue if (rootEntry.version !== registryEntry.version) { // A plain npm install keeps any locked resolution that still satisfies the - // range, so it leaves this untouched; rebuilding the lockfile from scratch - // is what moves it. Naming the version instead (npm install pkg@version) - // also rewrites the manifest range, and when neither tree holds the newest - // version the ranges allow, both lockfiles have to be rebuilt. + // range, so it leaves this untouched. Deleting one lockfile does not settle + // it either: that tree re-resolves to the newest version its range allows, + // which matches the other tree only by luck. Naming the version instead + // (npm install pkg@version) converges but rewrites the manifest range. failures.push( - `${name} resolves to ${rootEntry.version} in package-lock.json and ${registryEntry.version} in registry/package-lock.json; npm install keeps a resolution that still satisfies the range, so delete the lockfile you are correcting and reinstall.`, + `${name} resolves to ${rootEntry.version} in package-lock.json and ${registryEntry.version} in registry/package-lock.json; npm install keeps a resolution that still satisfies the range, so delete both lockfiles and rebuild them together: ${ROOT_INSTALL}, then ${REGISTRY_INSTALL}.`, ) continue } From 87ed925a6bfdb66be0dd9eea150fa39c7a9136b4 Mon Sep 17 00:00:00 2001 From: Lisandro Corbalan Date: Thu, 3 Sep 2026 11:48:26 +0000 Subject: [PATCH 11/11] docs: add the three rules the header comment left out --- scripts/check-registry-deps.mjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/check-registry-deps.mjs b/scripts/check-registry-deps.mjs index a8d9d53..2e194fd 100644 --- a/scripts/check-registry-deps.mjs +++ b/scripts/check-registry-deps.mjs @@ -4,11 +4,13 @@ // bin, so a consumer install resolves the service's imports against the root // dependency list, while every test suite that vetted that code ran against // registry/package.json's list. The two are deliberate duplicates, and this -// guard fails on four ways they come apart: a runtime dependency declared on -// one side only, a shared package at two ranges, a mismatched engines.node or -// type, and a package the two lockfiles resolve differently. It also fails -// when a lockfile stops recording the manifest beside it, which is the state -// an edit to one of the ranges leaves behind. +// guard fails on every way they come apart: a runtime dependency declared on +// one side only, one manifest naming a package at two ranges, a shared package +// at two ranges across the two, an engines.node or type that differs or is +// absent from both, a shared package the two lockfiles resolve differently or +// that one lockfile does not record at all, and a lockfile that no longer +// records the manifest beside it, which is the state an edit to a range leaves +// behind. import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path'