diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8bd9045..1092ce9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,9 +44,10 @@ updates: # error TS2322: Type 'Redis' is not assignable to type 'ConnectionOptions' # # on every site that hands a client to a Queue, Worker, QueueEvents or - # FlowProducer. Both #33 (ioredis alone) and #39 (bullmq alone) failed exactly - # that way, while the same two versions applied together are green. Grouping - # all update types keeps them moving as the pair they are. + # FlowProducer, while the very same versions applied together typecheck + # cleanly. Grouping all update types keeps them moving as the pair they are. + # The workspace `overrides` in pnpm-workspace.yaml enforce the other half of + # the invariant: one resolved copy across the root and every example. bullmq-stack: patterns: - 'bullmq' diff --git a/package.json b/package.json index 54fe2f6..363cfdc 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "release": "pnpm publish --provenance", "prepare": "husky" }, + "dependencies": {}, "peerDependencies": { "@nestjs/common": "^11.0.0", "@nestjs/core": "^11.0.0", diff --git a/scripts/dogfood-smoke-test.mjs b/scripts/dogfood-smoke-test.mjs index 2da5333..1c229af 100644 --- a/scripts/dogfood-smoke-test.mjs +++ b/scripts/dogfood-smoke-test.mjs @@ -7,9 +7,10 @@ * 2. ESM import resolves the expected named exports (server + shared) * 3. CJS require resolves the expected named exports (server + shared) * 4. Tarball contents (npm pack --dry-run) contain only dist/ + meta files - * 5. A minimal consumer (file: link in an OS temp dir) resolves both subpaths + * 5. The manifest still declares an explicit empty `dependencies` + * 6. A minimal consumer (file: link in an OS temp dir) resolves both subpaths * through the published `exports` map, in ESM *and* CJS - * 6. Behavioral smoke against the built artifact: forRoot wires a DynamicModule + * 7. Behavioral smoke against the built artifact: forRoot wires a DynamicModule * and the fail-fast validation path still throws * * Exit codes: 0 pass · 1 assertion failed · 2 build artifacts missing. @@ -143,6 +144,28 @@ try { fail(`npm pack --dry-run failed: ${err instanceof Error ? err.message : String(err)}`) } +// -- 5b. Zero-runtime-dependency contract ------------------------------------ +// The empty `dependencies` object is the contract, not decoration: it is what the +// README, CLAUDE.md and the Dependabot config all assert about this package. It is +// also fragile — `pnpm add` rewrites the manifest and drops an empty object without +// a word, so the key disappears on an ordinary dependency bump and nothing else in +// the pipeline notices. Assert the key EXISTS and is empty; a missing key and an +// empty one are different claims, and only one of them is the contract. +section('5b. Zero-runtime-dependency contract') +const manifest = req(resolve(ROOT, 'package.json')) +const declared = Object.hasOwn(manifest, 'dependencies') ? manifest.dependencies : undefined +if (declared === undefined) { + fail('package.json has no `dependencies` key — the zero-dependency contract is unstated') +} else if (declared === null || typeof declared !== 'object' || Array.isArray(declared)) { + // A guard that throws is worse than no guard: the run dies on an opaque + // TypeError instead of reporting which contract broke. + fail(`package.json \`dependencies\` is not an object (got ${JSON.stringify(declared)})`) +} else if (Object.keys(declared).length > 0) { + fail(`package.json declares runtime dependencies: ${Object.keys(declared).join(', ')}`) +} else { + pass('package.json declares an explicit empty `dependencies`') +} + // -- 6. Consumer file: link smoke -------------------------------------------- section('6. Consumer file: link smoke (resolution check, ESM + CJS)') try {