Contributing
Repository layout
Source is authored in TypeScript and split into clear layers. tsc compiles src/ to dist/ (the published surface) — see Dev workflow.
src/
index.ts # the mocg() umbrella + public re-exports (orchestrator)
core/ # shared machinery (no ESLint rules)
detect.ts # dependency-based stack auto-detection
logger.ts # namespaced debug logger
tsconfig-utils.ts # tsconfig discovery + path/reference merging
manifest.ts # the STACKS / EXTRAS manifest (single source of truth)
cli/ # the installer
index.ts # the bin entry (init / doctor / help)
init.ts # the installer wizard
doctor.ts # the diagnostics command
project.ts # PM detection, config generation, script patching
ui.ts # zero-dependency terminal output helpers
config/ # the ESLint rule product
node.eslint.ts # createNodeConfig — the base
react.eslint.ts # createReactConfig
vue.eslint.ts # createVueConfig / createVueTsConfig
nest.eslint.ts # createNestConfig (Node + Nest)
vitest|jest|zod|i18n|tailwind.eslint.ts # add-on entries
node/ # the individual core rulesets (one file per plugin)
typescript/ # type-aware TS rulesets
react/ vue/ nest/ … # framework-specific rulesets
boundaries/ # per-stack architectural-boundary configs (consumer templates)
types/ # ambient declarations for untyped plugins
fixtures/ # integration-test sample projects (bad code + per-stack dogfood fixtures)
examples/ # runnable consumer projects (typescript/react/nest/vue-app) — also a CI gate
scripts/ # dev/CI scripts (verify-examples.sh)
tests/ # vitest suites
docs/ # this VitePress site
.husky/ # git hooks (pre-push runs the full check suite locally)
.github/workflows/ # CI workflow — kept but disabled (manual-only) for now
dist/ # build output (git-ignored, generated by `npm run build`)The public import surface is the mocg() umbrella plus every subpath export (/node, /react, …); the subpath names are unchanged and only the resolved paths moved.
Dev workflow
npm install # legacy-peer-deps (see below); `prepare` builds dist/ + installs husky hooks
npm run lint # the package lints its own .ts source with its own config
npm run typecheck # tsc --noEmit over the TypeScript source
npm run test:run # vitest (includes the framework-stack dogfood tests)
npm run build # tsc -p tsconfig.build.json → dist/*.js + *.d.ts
npm run verify:examples # pack the config, install it into examples/*, lint + typecheck each
npm run docs:dev # preview these docsPre-push hook (checks run locally)
All checks run locally via a husky pre-push hook (.husky/pre-push) before every push: lint, typecheck, test:run, pack:check, and verify:examples. test:run includes the React/Nest/Vue dogfood tests (tests/*.test.mjs), which compose mocg() for each stack and lint a fixture — so a rule or plugin that crashes on load, or flags a standard project layout, fails before it leaves your machine; and verify:examples (real consumer installs of the packed tarball) also catches peer/transitive-resolution breakage an in-repo test cannot see. It does ~4 npm installs, so use git push --no-verify to skip it for a one-off push.
GitHub Actions CI (.github/workflows/ci.yml) is kept on hand but disabled for now (manual-only workflow_dispatch); re-enable it by uncommenting the push/pull_request triggers.
Build step
The package is authored in TypeScript and must be built before it runs: Node refuses to type-strip .ts files inside node_modules, so a compile step is required. npm run build emits dist/*.js plus .d.ts declarations 1:1 from src/; prepare runs it automatically on install (including git+ssh installs). Keep the build a pure tsc transpile (erasableSyntaxOnly is on) — don't add a bundler, so the 1:1 file output keeps mapping cleanly onto the subpath exports.
Conventions this repo holds itself to
The package eats its own dog food: npm run lint loads eslint.config.ts via ESLint's jiti loader and runs mocg() against src/, so the config's own TypeScript source must pass the full company standard (including type-aware rules).
- Full TypeScript. Every source file is
.tsand type-checked understrict. Consumers get.d.tsdeclarations formocg()and every subpath. - Enforced layering.
eslint-plugin-boundariesis wired onto this repo (eslint.config.ts) so the layers stay separated:config ✗→ cli,cli ✗→ config, andcorestays a leaf (✗→ cli, config). - The
STACKSmanifest is the single source of truth. Plugin version ranges are read from this package's ownpeerDependencies— never hard-coded. When you bump a peer version, the manifest, the installer,doctor, and the error messages all update together. A test enforces this. legacy-peer-deps. Some ESLint plugins haven't widened their peer ranges to declare ESLint 10 yet (they work with it). The.npmrcaccepts those ranges in this dev environment. Published consumers resolve their own dependency trees.
Adding or changing a rule
- Edit the relevant ruleset module under
src/config/. - Run
npm run test:run. The fixture-based integration tests lint real sample code and assert which rule IDs fire — update or add a fixture if you're introducing new behavior. - Decide the version bump using the versioning policy. A new error-level rule is a minor.
- Update the Rules & plugins reference so the docs stay accurate.
Adding a stack or add-on
- Add a ruleset entry module (e.g.
src/config/svelte.eslint.ts) and a subpath export inpackage.json(with itstypes+importconditions pointing atdist/). - Register it in
src/core/manifest.tswith itsdetectmarkers and optional-peerplugins. - Wire it into
mocg()insrc/index.ts— add the lazy importer toENTRY_IMPORTERS(or, for an add-on, it's picked up automatically fromEXTRAS). - Add its peers to
package.json(peerDependencies+peerDependenciesMeta.optional), and a fixture + tests.
Testing strategy
- Unit tests cover the manifest, detection, and CLI helpers.
- Integration tests build the real
mocg()config and run the ESLint API against fixture projects, asserting that specific rules fire on bad code and that clean code passes. - Exports smoke test imports the root and every subpath module and asserts each resolves.
- Framework-stack dogfood tests (
tests/react-stack.test.mjs,tests/framework-stacks.test.mjs) composemocg()for React, NestJS, and Vue and run the ESLint API over a fixture, asserting the stack's config loaded and that ESLint does not crash or flag a standard layout. - Example verification (
npm run verify:examples) packs the config and installs it into each project underexamples/, then runs that project's own lint + typecheck. This is the only check that exercises a real consumer install (peer resolution, framework-version detection), so it runs in the pre-push hook; it caught the Reactzod-validation-errorand Vue directory-casing regressions. - Pack check (
npm run pack:check) confirms the published tarball contains onlydist/(with.d.ts) — nosrc, fixtures, tests, or docs.