Changed admin React apps to ESLint 9 / flat config#28803
Conversation
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin-x-settings:test:acceptance |
✅ Succeeded | 10m 1s | View ↗ |
nx build @tryghost/activitypub |
✅ Succeeded | 3s | View ↗ |
nx run ghost:test:ci:integration |
✅ Succeeded | 2m 21s | View ↗ |
nx build @tryghost/portal |
✅ Succeeded | 1s | View ↗ |
nx build @tryghost/announcement-bar |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/admin-toolbar |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/signup-form |
✅ Succeeded | <1s | View ↗ |
nx build @tryghost/comments-ui |
✅ Succeeded | <1s | View ↗ |
Additional runs (15) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-22 20:51:14 UTC
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (38)
💤 Files with no reviewable changes (11)
✅ Files skipped from review due to trivial changes (12)
🚧 Files skipped from review as they are similar to previous changes (12)
WalkthroughFour apps ( Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
77bc878 to
4d4f71d
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/admin-x-settings/eslint.config.js`:
- Line 21: In the eslint.config.js file, locate the 'no-shadow': 'error' rule
within the ghostRules section and disable it by setting it to 'off'. Then add
'`@typescript-eslint/no-shadow`': 'error' to the src rules block (around line 77)
to properly handle TypeScript-specific scoping for enums, namespaces, and type
parameters, which the base ESLint rule does not understand.
In `@apps/posts/eslint.config.js`:
- Line 21: In the eslint.config.js file, replace the base ESLint rule
`'no-shadow': 'error'` in the ghostRules object with the TypeScript-specific
equivalent `'`@typescript-eslint/no-shadow`': 'error'`. This ensures proper
handling of TypeScript-specific scoping for enums, namespaces, and type
parameters. Remove the base no-shadow rule entry from ghostRules and add the
`@typescript-eslint/no-shadow` rule in the appropriate configuration section for
TypeScript files.
In `@apps/stats/eslint.config.js`:
- Line 21: The ghostRules object currently includes the base ESLint rule
'no-shadow' set to 'error', but TypeScript projects should use the
TypeScript-aware variant '`@typescript-eslint/no-shadow`' instead, as the base
rule doesn't understand TypeScript-specific scoping for enums, namespaces, and
type parameters. Remove the 'no-shadow': 'error' entry from the ghostRules
object and add '`@typescript-eslint/no-shadow`': 'error' to properly handle
TypeScript-specific scoping rules.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 20c302ee-ee9e-4f69-9d23-e78435771848
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (32)
apps/activitypub/.eslintignoreapps/activitypub/.eslintrc.cjsapps/activitypub/eslint.config.jsapps/activitypub/package.jsonapps/activitypub/src/components/layout/onboarding/step-3.tsxapps/activitypub/src/views/preferences/components/profile.tsxapps/activitypub/src/views/profile/profile.tsxapps/activitypub/test/.eslintrc.cjsapps/activitypub/test/acceptance/dom-validation.test.tsapps/activitypub/test/acceptance/feed.test.tsapps/activitypub/test/acceptance/inbox.test.tsapps/activitypub/test/acceptance/my-profile.test.tsapps/activitypub/test/utils/initial-api-requests.tsapps/admin-x-settings/.eslintignoreapps/admin-x-settings/.eslintrc.cjsapps/admin-x-settings/eslint.config.jsapps/admin-x-settings/package.jsonapps/admin-x-settings/src/components/settings/general/about.tsxapps/admin-x-settings/src/components/settings/general/invite-user-modal.tsxapps/admin-x-settings/src/components/settings/growth/offers/add-offer-modal.tsxapps/admin-x-settings/src/components/settings/growth/recommendations/recommendation-description-form.tsxapps/admin-x-settings/src/hooks/use-setting-group.tsxapps/posts/.eslintrc.cjsapps/posts/eslint.config.jsapps/posts/package.jsonapps/posts/src/views/filters/filter-codecs.tsapps/posts/test/.eslintrc.cjsapps/stats/.eslintignoreapps/stats/.eslintrc.cjsapps/stats/eslint.config.jsapps/stats/package.jsonapps/stats/test/.eslintrc.cjs
💤 Files with no reviewable changes (11)
- apps/posts/.eslintrc.cjs
- apps/admin-x-settings/.eslintrc.cjs
- apps/activitypub/.eslintrc.cjs
- apps/activitypub/test/.eslintrc.cjs
- apps/activitypub/.eslintignore
- apps/stats/.eslintrc.cjs
- apps/stats/.eslintignore
- apps/admin-x-settings/.eslintignore
- apps/admin-x-settings/src/hooks/use-setting-group.tsx
- apps/stats/test/.eslintrc.cjs
- apps/posts/test/.eslintrc.cjs
| 'no-eval': 'error', | ||
| 'no-useless-call': 'error', | ||
| 'no-console': 'error', | ||
| 'no-shadow': 'error', |
There was a problem hiding this comment.
Enable @typescript-eslint/no-shadow and disable the base no-shadow rule.
The ghostRules includes 'no-shadow': 'error' (line 21), but TypeScript projects should use @typescript-eslint/no-shadow instead. The base ESLint no-shadow rule doesn't understand TypeScript-specific scoping for enums, namespaces, and type parameters.
Compare with activitypub/eslint.config.js which correctly handles this by disabling the base rule and enabling the TypeScript variant.
🔧 Proposed fix
const ghostRules = {
curly: 'error',
camelcase: ['error', {properties: 'never'}],
'dot-notation': 'error',
eqeqeq: ['error', 'always'],
'no-plusplus': ['error', {allowForLoopAfterthoughts: true}],
'no-eval': 'error',
'no-useless-call': 'error',
'no-console': 'error',
- 'no-shadow': 'error',
'array-callback-return': 'error',
'no-constructor-return': 'error',
'no-promise-executor-return': 'error',
'no-unused-vars': 'off',
'`@typescript-eslint/no-unused-vars`': ['error', {
args: 'after-used',
argsIgnorePattern: '^_',
caughtErrors: 'none'
}],
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false]
};Then add to the src rules block after line 77:
// TS handles these — disable the base ESLint variants
'no-undef': 'off',
'no-redeclare': 'off',
'no-unexpected-multiline': 'off',
+ 'no-shadow': 'off',
+ '`@typescript-eslint/no-shadow`': 'error',
'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', {Also applies to: 74-77
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/admin-x-settings/eslint.config.js` at line 21, In the eslint.config.js
file, locate the 'no-shadow': 'error' rule within the ghostRules section and
disable it by setting it to 'off'. Then add '`@typescript-eslint/no-shadow`':
'error' to the src rules block (around line 77) to properly handle
TypeScript-specific scoping for enums, namespaces, and type parameters, which
the base ESLint rule does not understand.
| 'no-eval': 'error', | ||
| 'no-useless-call': 'error', | ||
| 'no-console': 'error', | ||
| 'no-shadow': 'error', |
There was a problem hiding this comment.
Enable @typescript-eslint/no-shadow and disable the base no-shadow rule.
The ghostRules includes 'no-shadow': 'error' (line 21), but TypeScript projects should use @typescript-eslint/no-shadow instead. The base ESLint no-shadow rule doesn't understand TypeScript-specific scoping for enums, namespaces, and type parameters.
See activitypub/eslint.config.js for the correct implementation.
🔧 Proposed fix
Remove 'no-shadow': 'error' from ghostRules:
const ghostRules = {
curly: 'error',
camelcase: ['error', {properties: 'never'}],
'dot-notation': 'error',
eqeqeq: ['error', 'always'],
'no-plusplus': ['error', {allowForLoopAfterthoughts: true}],
'no-eval': 'error',
'no-useless-call': 'error',
'no-console': 'error',
- 'no-shadow': 'error',
'array-callback-return': 'error',
'no-constructor-return': 'error',
'no-promise-executor-return': 'error',
'no-unused-vars': 'off',
'`@typescript-eslint/no-unused-vars`': ['error', {
args: 'after-used',
argsIgnorePattern: '^_',
caughtErrors: 'none'
}],
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false]
};Add after line 76:
'no-undef': 'off',
'no-redeclare': 'off',
'no-unexpected-multiline': 'off',
+ 'no-shadow': 'off',
+ '`@typescript-eslint/no-shadow`': 'error',
'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', {Also applies to: 74-76
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/posts/eslint.config.js` at line 21, In the eslint.config.js file,
replace the base ESLint rule `'no-shadow': 'error'` in the ghostRules object
with the TypeScript-specific equivalent `'`@typescript-eslint/no-shadow`':
'error'`. This ensures proper handling of TypeScript-specific scoping for enums,
namespaces, and type parameters. Remove the base no-shadow rule entry from
ghostRules and add the `@typescript-eslint/no-shadow` rule in the appropriate
configuration section for TypeScript files.
There was a problem hiding this comment.
Will do in a follow up.
There was a problem hiding this comment.
@9larsons, sounds good! Would you like me to open a GitHub issue to track replacing 'no-shadow': 'error' with @typescript-eslint/no-shadow (and disabling the base rule) across the migrated ESLint flat configs in apps/posts, apps/stats, and apps/activitypub?
🧠 Learnings used
Learnt from: JohnONolan
Repo: TryGhost/Ghost PR: 28368
File: apps/admin-x-settings/src/components/settings/site/navigation/navigation-edit-form.tsx:32-32
Timestamp: 2026-06-04T15:15:20.265Z
Learning: In this TryGhost/Ghost codebase (Tailwind CSS v4), use/accept the v4 suffix form of the important modifier in class names (e.g., `opacity-100!`, `flex!`). Do not flag these as incorrect or inconsistent with the older v3 prefix form (`!opacity-100`), since the suffix form is the established convention and aligns with the generated CSS.
Learnt from: sagzy
Repo: TryGhost/Ghost PR: 28779
File: ghost/core/core/frontend/web/middleware/error-handler.js:0-0
Timestamp: 2026-06-22T14:36:35.803Z
Learning: When using Express.js view engines, Express stores engine handler functions in `app.engines` with keys that include a leading dot (e.g., `app.engines['.hbs']` and `app.engines['.ejs']`). Therefore, checking `app.engines.hbs` (no dot) will be `undefined`; to test whether an engine is already registered, use bracket notation with the dot prefix: `app.engines['.hbs'] !== undefined` (or equivalently `Object.prototype.hasOwnProperty.call(app.engines, '.hbs')`).
| 'no-eval': 'error', | ||
| 'no-useless-call': 'error', | ||
| 'no-console': 'error', | ||
| 'no-shadow': 'error', |
There was a problem hiding this comment.
Enable @typescript-eslint/no-shadow and disable the base no-shadow rule.
The ghostRules includes 'no-shadow': 'error' (line 21), but TypeScript projects should use @typescript-eslint/no-shadow instead. The base ESLint no-shadow rule doesn't understand TypeScript-specific scoping for enums, namespaces, and type parameters.
See activitypub/eslint.config.js for the correct implementation.
🔧 Proposed fix
Remove 'no-shadow': 'error' from ghostRules:
const ghostRules = {
curly: 'error',
camelcase: ['error', {properties: 'never'}],
'dot-notation': 'error',
eqeqeq: ['error', 'always'],
'no-plusplus': ['error', {allowForLoopAfterthoughts: true}],
'no-eval': 'error',
'no-useless-call': 'error',
'no-console': 'error',
- 'no-shadow': 'error',
'array-callback-return': 'error',
'no-constructor-return': 'error',
'no-promise-executor-return': 'error',
'no-unused-vars': 'off',
'`@typescript-eslint/no-unused-vars`': ['error', {
args: 'after-used',
argsIgnorePattern: '^_',
caughtErrors: 'none'
}],
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false]
};Add after line 76:
'no-undef': 'off',
'no-redeclare': 'off',
'no-unexpected-multiline': 'off',
+ 'no-shadow': 'off',
+ '`@typescript-eslint/no-shadow`': 'error',
'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', {Also applies to: 74-76
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/stats/eslint.config.js` at line 21, The ghostRules object currently
includes the base ESLint rule 'no-shadow' set to 'error', but TypeScript
projects should use the TypeScript-aware variant '`@typescript-eslint/no-shadow`'
instead, as the base rule doesn't understand TypeScript-specific scoping for
enums, namespaces, and type parameters. Remove the 'no-shadow': 'error' entry
from the ghostRules object and add '`@typescript-eslint/no-shadow`': 'error' to
properly handle TypeScript-specific scoping rules.
There was a problem hiding this comment.
Will do in a follow up.
There was a problem hiding this comment.
@9larsons, sounds good! Would you like me to open a GitHub issue to track replacing no-shadow with @typescript-eslint/no-shadow across the ESLint flat configs (apps/stats, apps/posts, apps/activitypub, apps/admin-x-settings) so it doesn't get lost?
🧠 Learnings used
Learnt from: JohnONolan
Repo: TryGhost/Ghost PR: 28368
File: apps/admin-x-settings/src/components/settings/site/navigation/navigation-edit-form.tsx:32-32
Timestamp: 2026-06-04T15:15:20.265Z
Learning: In this TryGhost/Ghost codebase (Tailwind CSS v4), use/accept the v4 suffix form of the important modifier in class names (e.g., `opacity-100!`, `flex!`). Do not flag these as incorrect or inconsistent with the older v3 prefix form (`!opacity-100`), since the suffix form is the established convention and aligns with the generated CSS.
Learnt from: sagzy
Repo: TryGhost/Ghost PR: 28779
File: ghost/core/core/frontend/web/middleware/error-handler.js:0-0
Timestamp: 2026-06-22T14:36:35.803Z
Learning: When using Express.js view engines, Express stores engine handler functions in `app.engines` with keys that include a leading dot (e.g., `app.engines['.hbs']` and `app.engines['.ejs']`). Therefore, checking `app.engines.hbs` (no dot) will be `undefined`; to test whether an engine is already registered, use bracket notation with the dot prefix: `app.engines['.hbs'] !== undefined` (or equivalently `Object.prototype.hasOwnProperty.call(app.engines, '.hbs')`).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #28803 +/- ##
==========================================
+ Coverage 74.06% 74.09% +0.02%
==========================================
Files 1559 1559
Lines 134729 134729
Branches 16296 16298 +2
==========================================
+ Hits 99784 99823 +39
+ Misses 33966 33896 -70
- Partials 979 1010 +31
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
4d4f71d to
99aa5f1
Compare
ref https://linear.app/tryghost/ - migrated apps/admin-x-settings, apps/posts, apps/stats, apps/activitypub - each had .eslintrc.cjs + test/.eslintrc.cjs split; collapsed to single eslint.config.js per workspace - dropped admin-x-settings's enormous inline copy of the legacy ghost/browser formatting rules (all removed from ESLint 9 core anyway) - moved .eslintignore patterns into flat-config 'ignores' (flat config does not honor .eslintignore files) - fixed a small batch of pre-existing real lint failures uncovered when the cache was rebuilt: - empty-pattern destructurings -> regular signatures - !!x boolean cast in an if-test -> bare condition - useless backslashes inside regex character classes - case-without-braces for a const declaration - empty else block - one no-async-promise-executor disabled inline (intentional pattern) - regex newlines via NBSP -> regular space - '@typescript-eslint/no-explicit-any' kept 'warn' in src and 'off' in tests - modernized all four to ESM ('type': 'module'); each already runs ESM at runtime under Vite, just makes declaration match
99aa5f1 to
0a5927b
Compare

Summary
Migrates
apps/admin-x-settings,apps/posts,apps/stats, andapps/activitypub..eslintrc.cjs+test/.eslintrc.cjssplit; collapsed each into a singleeslint.config.js.eslint: catalog:tocatalog:eslint9and added@eslint/js,globals,eslint-plugin-react,eslint-plugin-ghost,typescript-eslint(unified) as direct deps.eslint-plugin-tailwindcssstays atcatalog:..eslintignorepatterns into flat-configignores(flat config doesn't honor.eslintignorefiles); deleted the legacy ignore files.'@typescript-eslint/no-explicit-any'keptwarnin src andoffin tests, matching the legacy posture.--extCLI flag;lint:code/lint:testscripts now pass directory args.Forced source fixes
ESLint 9's stricter defaults + a freshly-rebuilt cache exposed a few pre-existing real lint failures that had been hiding. Mechanical fixes:
apps/admin-x-settings/src/components/settings/general/about.tsx— empty destructuring({}) => ...→() => ...apps/admin-x-settings/src/components/settings/general/invite-user-modal.tsx—if (!!errors.email)→if (errors.email)apps/admin-x-settings/src/components/settings/growth/offers/add-offer-modal.tsx— useless backslash-dash in regex character classapps/admin-x-settings/src/components/settings/growth/recommendations/recommendation-description-form.tsx— case-declaration without bracesapps/admin-x-settings/src/hooks/use-setting-group.tsx— emptyelse {}removedapps/activitypub/src/components/layout/onboarding/step-3.tsx— non-breaking-space chars (U+00A0) → regular spaces (caught byno-irregular-whitespace)apps/activitypub/src/views/preferences/components/profile.tsx—no-async-promise-executordisabled inline (intentional pattern)apps/activitypub/src/views/profile/profile.tsx— empty destructuring fixapps/posts/src/views/filters/filter-codecs.ts— useless backslash in regex character classNo version bumps
None of these four are CDN-published.
check-app-version-bump.jsdoes not monitor them.Test plan
pnpm --filter <pkg> lintare green locally