twenty/nx.json
Charles Bochet 9d57bc39e5
Migrate from ESLint to OxLint (#18443)
## Summary

Fully replaces ESLint with OxLint across the entire monorepo:

- **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint
configs (`.oxlintrc.json`) for every package: `twenty-front`,
`twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`,
`twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`,
`twenty-apps/*`, `create-twenty-app`
- **Migrated custom lint rules** from ESLint plugin format to OxLint JS
plugin system (`@oxlint/plugins`), including
`styled-components-prefixed-with-styled`, `no-hardcoded-colors`,
`sort-css-properties-alphabetically`,
`graphql-resolvers-should-be-guarded`,
`rest-api-methods-should-be-guarded`, `max-consts-per-file`, and
Jotai-related rules
- **Migrated custom rule tests** from ESLint `RuleTester` + Jest to
`oxlint/plugins-dev` `RuleTester` + Vitest
- **Removed all ESLint dependencies** from `package.json` files and
regenerated lockfiles
- **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in
`nx.json` and per-project `project.json` to use `oxlint` commands with
proper `dependsOn` for plugin builds
- **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more
ESLint executor
- **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with
`oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and
format-on-save with Prettier
- **Replaced all `eslint-disable` comments** with `oxlint-disable`
equivalents across the codebase
- **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint
- **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules`

### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`)

| Rule | Package | Violations | Auto-fixable |
|------|---------|-----------|-------------|
| `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes
|
| `typescript/consistent-type-imports` | twenty-server | 3814 | Yes |
| `twenty/max-consts-per-file` | twenty-server | 94 | No |

### Dropped plugins (no OxLint equivalent)

`eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`,
`import/order`, `prefer-arrow/prefer-arrow-functions`,
`eslint-plugin-mdx`, `@next/eslint-plugin-next`,
`eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial
coverage for `jsx-a11y` and `unused-imports`.

### Additional fixes (pre-existing issues exposed by merge)

- Fixed `EmailThreadPreview.tsx` broken import from main rename
(`useOpenEmailThreadInSidePanel`)
- Restored truthiness guard in `getActivityTargetObjectRecords.ts`
- Fixed `AgentTurnResolver` return types to match entity (virtual
`fileMediaType`/`fileUrl` are resolved via `@ResolveField()`)

## Test plan

- [x] `npx nx lint twenty-front` passes
- [x] `npx nx lint twenty-server` passes
- [x] `npx nx lint twenty-docs` passes
- [x] Custom oxlint rules validated with Vitest: `npx nx test
twenty-oxlint-rules`
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx typecheck twenty-server` passes
- [x] CI workflows trigger correctly with `dependsOn:
["twenty-oxlint-rules:build"]`
- [x] IDE linting works with `oxc.oxc-vscode` extension
2026-03-06 01:03:50 +01:00

297 lines
8 KiB
JSON

{
"workspaceLayout": {
"appsDir": "packages",
"libsDir": "packages"
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"excludeStories": [
"default",
"!{projectRoot}/.storybook/*",
"!{projectRoot}/**/tsconfig.storybook.json",
"!{projectRoot}/**/*.stories.(ts|tsx)",
"!{projectRoot}/**/__stories__/*"
],
"excludeTests": [
"default",
"!{projectRoot}/**/jest.config.(js|ts)",
"!{projectRoot}/**/tsconfig.spec.json",
"!{projectRoot}/**/*.test.(ts|tsx)",
"!{projectRoot}/**/*.spec.(ts|tsx)",
"!{projectRoot}/**/*.integration-spec.ts",
"!{projectRoot}/**/__tests__/*"
],
"production": [
"default",
"excludeStories",
"excludeTests",
"!{projectRoot}/**/__mocks__/*",
"!{projectRoot}/**/testing/*"
]
},
"targetDefaults": {
"build": {
"cache": true,
"inputs": ["^production", "production"],
"dependsOn": ["^build"]
},
"start": {
"cache": false,
"dependsOn": ["^build"]
},
"lint": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "npx oxlint -c .oxlintrc.json ."
},
"configurations": {
"ci": {},
"fix": {
"command": "npx oxlint --fix -c .oxlintrc.json ."
}
},
"dependsOn": ["^build", "twenty-oxlint-rules:build"]
},
"lint:diff-with-main": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint -c {projectRoot}/.oxlintrc.json $FILES",
"pattern": "\\.(ts|tsx|js|jsx)$"
},
"configurations": {
"fix": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES"
}
}
},
"fmt": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "prettier {args.files} --check --cache {args.cache} --cache-location {args.cacheLocation} --write {args.write} --cache-strategy {args.cacheStrategy}",
"cache": true,
"cacheLocation": "../../.cache/prettier/{projectRoot}",
"cacheStrategy": "metadata",
"write": false
},
"configurations": {
"ci": {
"cacheStrategy": "content"
},
"fix": {
"write": true
}
},
"dependsOn": ["^build"]
},
"typecheck": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "tsgo -p tsconfig.json"
},
"configurations": {
"watch": {
"command": "tsgo -p tsconfig.json --watch"
}
},
"dependsOn": ["^build"]
},
"test": {
"executor": "@nx/jest:jest",
"cache": true,
"dependsOn": ["^build"],
"inputs": [
"^default",
"excludeStories",
"{workspaceRoot}/jest.preset.js"
],
"outputs": ["{projectRoot}/coverage"],
"options": {
"jestConfig": "{projectRoot}/jest.config.mjs",
"silent": true,
"coverage": true,
"coverageReporters": ["text-summary"],
"cacheDirectory": "../../.cache/jest/{projectRoot}"
},
"configurations": {
"ci": {
"ci": true,
"maxWorkers": 1
},
"coverage": {
"coverageReporters": ["lcov", "text"]
},
"watch": {
"watch": true
}
}
},
"test:e2e": {
"cache": true,
"dependsOn": ["^build"]
},
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "NODE_OPTIONS='--max-old-space-size=10240' storybook build --test",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
"dependsOn": ["^build"]
},
"storybook:serve:dev": {
"executor": "nx:run-commands",
"cache": true,
"dependsOn": ["^build"],
"options": {
"cwd": "{projectRoot}",
"command": "storybook dev",
"config-dir": ".storybook"
}
},
"storybook:serve:static": {
"executor": "nx:run-commands",
"dependsOn": ["storybook:build"],
"options": {
"cwd": "{projectRoot}",
"command": "npx http-server {args.staticDir} -a={args.host} --port={args.port} --silent={args.silent}",
"staticDir": "storybook-static",
"host": "localhost",
"port": 6006,
"silent": true
}
},
"storybook:test": {
"executor": "nx:run-commands",
"cache": true,
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/coverage/storybook"],
"options": {
"cwd": "{projectRoot}",
"command": "vitest run --coverage --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:test:no-coverage": {
"executor": "nx:run-commands",
"inputs": ["^default", "excludeTests"],
"options": {
"cwd": "{projectRoot}",
"command": "vitest run --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:coverage": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"^default",
"excludeTests",
"{projectRoot}/coverage/storybook/coverage-storybook.json"
],
"outputs": [
"{projectRoot}/coverage/storybook",
"!{projectRoot}/coverage/storybook/coverage-storybook.json"
],
"options": {
"command": "npx nyc report --reporter={args.reporter} --reporter=text-summary -t {args.coverageDir} --report-dir {args.coverageDir} --check-coverage={args.checkCoverage} --cwd={projectRoot}",
"coverageDir": "coverage/storybook",
"reporter": "lcov",
"checkCoverage": true
},
"configurations": {
"text": {
"reporter": "text"
}
}
},
"chromatic": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": [
{
"command": "nx storybook:build {projectName}",
"forwardAllArgs": false
},
"chromatic --storybook-build-dir=storybook-static {args.ci}"
],
"parallel": false
},
"configurations": {
"ci": {
"ci": "--exit-zero-on-changes"
}
}
},
"@nx/jest:jest": {
"cache": true,
"inputs": [
"^default",
"excludeStories",
"{workspaceRoot}/jest.preset.js"
],
"options": {
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"@nx/vite:build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
},
"@nx/vitest:test": {
"cache": true,
"inputs": ["default", "^default"]
}
},
"generators": {
"@nx/react": {
"application": {
"style": "@linaria/react",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
"projectNameAndRootFormat": "derived"
},
"library": {
"style": "@linaria/react",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
"projectNameAndRootFormat": "derived"
},
"component": {
"style": "@linaria/react"
}
}
},
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["storybook:build"]
}
}
},
"useInferencePlugins": false,
"defaultBase": "main",
"tui": {
"enabled": false
}
}