Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"release": "pnpm publish --provenance",
"prepare": "husky"
},
"dependencies": {},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
"@nestjs/core": "^11.0.0",
Expand Down
27 changes: 25 additions & 2 deletions scripts/dogfood-smoke-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
Loading