Skip to content

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

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

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.

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. A new error-level rule is a minor.
  4. Update the Rules & plugins reference 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.

Internal tooling — Master of Code Global