internal/planning/2214-unify-lint-configs.md
Design spec for issue #2214: "Finish merging linting between Core and Pro."
Eliminate the duplicate Prettier and ESLint configurations between the Core and Pro
packages, and remove .github/workflows/pro-lint.yml. After this change, there is a
single ESLint config and a single Prettier config covering the whole monorepo, and
CI runs JS lint and format checks exactly once per change.
Configs (4 files):
.prettierrc (root) — printWidth 110, semi, singleQuote, trailingComma all, plus
CSS/SCSS, JSON, and .*rc → yaml overrides
.prettierignore (root) — excludes react_on_rails_pro/ so root prettier never
touches Pro
react_on_rails_pro/.prettierrc — functionally identical Prettier options for
JS/TS/JSON/CSS; explicit parser: css / parser: json (defaults, no effect); no
.*rc → yaml override
react_on_rails_pro/.prettierignore — Pro-scoped ignores
eslint.config.ts (root) — flat config, globalIgnores(['react_on_rails_pro/', ...]).
Already contains override blocks for packages/react-on-rails-pro/**,
packages/react-on-rails-pro-node-renderer/**, and react_on_rails_pro/spec/dummy/**.
react_on_rails_pro/eslint.config.mjs — parallel flat config. Pro-only rules:
lines-between-class-members enforce, no-mixed-operators: off,
no-restricted-syntax: off, import/extensions: off, import/prefer-default-export: off,
@typescript-eslint/no-floating-promises with FastifyReply known-safe,
no-restricted-imports for integrations, e2e overrides
(no-empty-pattern { allowObjectPatternsAsParameters: true },
react-hooks/rules-of-hooks: off).
Pre-commit hooks (lefthook):
bin/lefthook/eslint-lint — splits changed files into root vs react_on_rails_pro/
vs packages/react-on-rails-pro/, runs ESLint twice (once from repo root, once from
inside react_on_rails_pro/).bin/lefthook/prettier-format — same split, runs Prettier twice.CI workflows (relevant):
.github/workflows/lint-js-and-ruby.yml — root ESLint, root Prettier check, root
rubocop, RBS validate, type-check, stylelint, attw/publint. paths-ignore excludes
react_on_rails_pro/**..github/workflows/pro-lint.yml — sets up Pro Ruby gems, Pro dummy gems, runs
generate_packs, builds the react-on-rails-pro package, then runs Pro rubocop
(--ignore-parent-exclusion), Pro rake rbs:validate, Pro ESLint, Pro Prettier
check, Pro pnpm run nps check-typescript..github/workflows/pro-test-package-and-gem.yml — Pro gem and JS package tests
(already sets up Pro Ruby + pnpm).Approach 1: Full merge into root (chosen).
Delete Pro's parallel ESLint and Prettier configs. Promote Pro-only rules to scoped
override blocks in root eslint.config.ts. Remove the react_on_rails_pro/ ignore
line from root .prettierignore. Simplify lefthook scripts to a single invocation
each. Move Pro Ruby + RBS + TypeScript checks out of the deleted pro-lint.yml into
a new pro-lint job inside pro-test-package-and-gem.yml. Update CI triggers and
documentation accordingly.
Rejected alternatives:
pro-lint.yml cleanly.Root eslint.config.ts:
'react_on_rails_pro/' from globalIgnores(...) (currently line 26).react_on_rails_pro/spec/dummy/** override block to also
include react_on_rails_pro/spec/execjs-compatible-dummy/**. Both disable
import/no-unresolved (dummy app deps may not be installed during lint).packages/react-on-rails-pro-node-renderer/src/integrations/**
(ignoring …/integrations/api.ts) — no-restricted-imports
{ patterns: ['../*'] } to keep integrations on the public API only.react_on_rails_pro/spec/dummy/e2e-tests/**/* —
no-empty-pattern { allowObjectPatternsAsParameters: true } and
react-hooks/rules-of-hooks: 'off' (Playwright fixtures + Playwright test
function false-positives).packages/react-on-rails-pro-node-renderer/**/*.ts —
@typescript-eslint/no-floating-promises with FastifyReply allowed as a
known-safe promise.no-restricted-syntax: off,
lines-between-class-members enforce, no-mixed-operators: off,
import/extensions: off, import/prefer-default-export: off). Triage step 6
below handles surfaced violations.react_on_rails_pro/eslint.config.mjs.Root .prettierrc: no changes. Pro's .prettierrc is a functional subset.
Root .prettierignore:
react_on_rails_pro/ (currently line 6, including its comment).**/.node-renderer-bundles (Pro-only path).**/tmp, **/public, **/.yalc/**, **/generated,
**/vendor, **/package.json, .rubocop.yml) are already covered by existing
root patterns or the *.yml global ignore.Deletions:
react_on_rails_pro/.prettierrcreact_on_rails_pro/.prettierignoreAfter deletion, run pnpm exec prettier --write . from the repo root and commit any
formatting diffs as a separate prep commit (reviewable independently). The two
configs produced identical Prettier output for JS/TS/JSON, so diffs should be
minimal — likely limited to files that the Pro config processed but root's pattern
matchers had not seen before.
.github/workflows/lint-js-and-ruby.yml:
react_on_rails_pro/** from paths-ignore so Pro changes trigger this
workflow.pnpm run eslint --report-unused-disable-directives
and pnpm start format.listDifferent cover Pro automatically after the global
ignore is removed.import/no-unresolved for packages/react-on-rails-pro/**,
packages/react-on-rails-pro-node-renderer/**, and the Pro dummy paths, so the
build/generate steps from pro-lint.yml should be unnecessary. If a rule does
need them, prefer scope-disabling that rule over re-adding the build step..github/workflows/pro-test-package-and-gem.yml:
Add a new pro-lint job (parallel to existing test jobs, gated by the same
detect-changes outputs). Steps:
pnpm install --frozen-lockfile.bundle exec rake react_on_rails:generate_packs in spec/dummy (needed for the
TypeScript check to see the generated entrypoints).pnpm --filter react-on-rails-pro build (needed for the TypeScript check).cd react_on_rails_pro && bundle exec rubocop --ignore-parent-exclusioncd react_on_rails_pro && bundle exec rake rbs:validatecd react_on_rails_pro && pnpm run nps check-typescriptOne job rather than three jobs: all three checks share the heaviest setup (Ruby gem install, pnpm install, dummy gem install) and splitting would triple setup time for negligible parallelism benefit.
.github/workflows/pro-lint.yml:
Delete the file.
script/ci-changes-detector and any related GitHub Actions:
Audit for references to run_pro_lint and pro-lint. JS/Prettier triggers fold
into existing run_lint; Ruby/RBS/TS triggers fold into existing run_pro_tests
(which already gates pro-test-package-and-gem.yml).
bin/lefthook/eslint-lint:
Replace the directory-splitting body with a single pnpm exec eslint $files --fix
invocation. Drop the cd react_on_rails_pro && pnpm exec eslint branch. Preserve
the early-exit on no matching files and the CONTEXT echo lines.
bin/lefthook/prettier-format:
Same simplification — single pnpm exec prettier --write $files for all files.
Drop the cd react_on_rails_pro && pnpm exec prettier branch.
.lefthook.yml: no changes.
react_on_rails_pro/CLAUDE.md:
pro-lint.yml entry.react_on_rails_pro/package-scripts.yml: keep as-is. The eslint, format, and
lint nps scripts will resolve the root unified configs via cosmiconfig, so they
keep working unchanged.
Root AGENTS.md / CLAUDE.md / docs index: grep for pro-lint.yml and "Pro has
its own" mentions and update.
pnpm install && pnpm run eslint --report-unused-disable-directives
from root. Expect clean, or a finite triage list (apply step 6 from Section 1).pnpm exec prettier --check . from root. If diffs,
commit pnpm exec prettier --write . output as a prep commit.react_on_rails_pro/ and run
bundle exec lefthook run pre-commit to confirm hooks lint and format Pro
files using the unified config without the directory split.lint-js-and-ruby.yml runs and passes (now exercising Pro paths).pro-test-package-and-gem.yml runs the new pro-lint job and passes.pro-lint.yml workflow does not appear in the checks list.generate_packs or the built Pro package to lint Pro source, the root lint job
would need a setup expansion. Verification step 1 will catch this. Fallback is
to scope-disable the offending rule for Pro paths rather than bloat the root
lint job.paths-ignore removal on lint-js-and-ruby.yml means
Pro changes now trigger root lint. This is the desired outcome; it ensures Pro
lint regressions are caught even when Pro test jobs are skipped by docs-only
paths.pro-test-package-and-gem.yml.