# Contributing ## Repository layout Source is authored in **TypeScript** and split into clear layers. `tsc` compiles `src/` to `dist/` (the published surface) — see [Dev workflow](#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 ```bash 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 docs ``` ::: tip Pre-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. ::: ::: tip 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 `.ts` and type-checked under `strict`. Consumers get `.d.ts` declarations for `mocg()` and every subpath. - **Enforced layering.** `eslint-plugin-boundaries` is wired onto this repo (`eslint.config.ts`) so the layers stay separated: `config ✗→ cli`, `cli ✗→ config`, and `core` stays a leaf (`✗→ cli, config`). - **The `STACKS` manifest is the single source of truth.** Plugin version ranges are read from this package's own `peerDependencies` — 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 `.npmrc` accepts those ranges in this dev environment. Published consumers resolve their own dependency trees. ## Adding or changing a rule 1. Edit the relevant ruleset module under `src/config/`. 2. 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. 3. Decide the version bump using the [versioning policy](/guide/versioning). A new error-level rule is a **minor**. 4. Update the [Rules & plugins reference](/reference/plugins) so the docs stay accurate. ## Adding a stack or add-on 1. Add a ruleset entry module (e.g. `src/config/svelte.eslint.ts`) and a subpath export in `package.json` (with its `types` + `import` conditions pointing at `dist/`). 2. Register it in `src/core/manifest.ts` with its `detect` markers and optional-peer `plugins`. 3. Wire it into `mocg()` in `src/index.ts` — add the lazy importer to `ENTRY_IMPORTERS` (or, for an add-on, it's picked up automatically from `EXTRAS`). 4. 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`) compose `mocg()` 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 under `examples/`, 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 React `zod-validation-error` and Vue directory-casing regressions. - **Pack check** (`npm run pack:check`) confirms the published tarball contains only `dist/` (with `.d.ts`) — no `src`, fixtures, tests, or docs.