Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Enforce package.json exports match source modules with a test
Add a test asserting every top-level source module has a matching package.json exports entry, so new modules can't be forgotten.
Bad example
| 1 | // package.json — exports maintained by hand, no test enforcing coverage |
| 2 | { |
| 3 | "exports": { |
| 4 | "./button": { |
| 5 | "types": "./dist/button.d.ts", |
| 6 | "import": "./dist/button.js", |
| 7 | "default": "./dist/button.cjs" |
| 8 | }, |
| 9 | "./modal": { |
| 10 | "types": "./dist/modal.d.ts", |
| 11 | "import": "./dist/modal.js", |
| 12 | "default": "./dist/modal.cjs" |
| 13 | } |
| 14 | // A new "date-picker" module was added under src/, but nobody |
| 15 | // remembered to add a "./date-picker" entry here — it silently |
| 16 | // isn't importable by consumers until someone notices and patches it. |
| 17 | } |
| 18 | } |
Explanation (EN)
The exports map is maintained purely by hand and by memory. Nothing fails when a new source module is added without a matching export entry, so the gap is only discovered later, by a consumer hitting a missing-export error or by a reviewer catching it in a follow-up PR.
Objašnjenje (HR)
Mapa exports održava se ručno, na temelju sjećanja. Ništa ne pukne kad se doda novi modul u izvornom kodu bez pripadajućeg exports zapisa, pa se propust otkrije tek kasnije, kad korisnik naleti na grešku 'missing export' ili kad to netko primijeti u sljedećem PR-u.
Good example
| 1 | // exports.test.ts |
| 2 | import { readdirSync } from 'node:fs'; |
| 3 | import { describe, expect, it } from 'vitest'; |
| 4 | import packageJson from '../package.json'; |
| 5 |
|
| 6 | describe('package exports', () => { |
| 7 | it('exposes every top-level module in src as a subpath export', () => { |
| 8 | const sourceModules = readdirSync('src', { withFileTypes: true }) |
| 9 | .filter((entry) => entry.isFile()) |
| 10 | .map((entry) => `./${entry.name.replace(/\.[^.]+$/, '')}`); |
| 11 |
|
| 12 | const exportedPaths = Object.keys(packageJson.exports).filter( |
| 13 | (key) => key !== '.' && key !== './package.json', |
| 14 | ); |
| 15 |
|
| 16 | sourceModules.forEach((modulePath) => { |
| 17 | expect(exportedPaths).toContain(modulePath); |
| 18 | }); |
| 19 | }); |
| 20 | }); |
Explanation (EN)
A test derives the expected export list directly from the source directory and asserts package.json's exports field covers it. Adding a module under src without wiring up its export now fails CI immediately instead of being caught by chance.
Objašnjenje (HR)
Test izvodi očekivani popis exporta izravno iz src direktorija i provjerava pokriva li exports polje u package.json sve to. Ako se doda modul u src bez pripadajućeg exporta, CI odmah pukne, umjesto da se propust otkrije slučajno.