From ce41f959e69e56acd375c8e347e502d0b8e25b49 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Thu, 30 Jul 2026 13:12:31 +0200 Subject: [PATCH 1/3] Make Oxlint the primary linter --- .github/workflows/tests-main.yml | 4 +-- .github/workflows/tests-pr.yml | 4 +-- docs/cli/get-started.md | 4 +-- eslint.codegen.config.js | 4 +++ eslint.config.js | 44 ++++++++++++++++++++++++++++- package.json | 13 +++++---- packages/app/project.json | 14 ++++----- packages/cli-kit/project.json | 4 +-- packages/organizations/project.json | 4 +-- packages/store/project.json | 4 +-- 10 files changed, 74 insertions(+), 25 deletions(-) create mode 100644 eslint.codegen.config.js diff --git a/.github/workflows/tests-main.yml b/.github/workflows/tests-main.yml index c631c9d8bfc..6677ce00044 100644 --- a/.github/workflows/tests-main.yml +++ b/.github/workflows/tests-main.yml @@ -51,8 +51,8 @@ jobs: - name: Oxlint run: pnpm lint:oxlint if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '26.1.0' }} - - name: Lint - run: pnpm nx run-many --all --target=lint --output-style=stream + - name: ESLint compatibility + run: pnpm lint:eslint --output-style=stream if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '26.1.0' }} - name: Type-check run: pnpm nx run-many --all --target=type-check --output-style=stream diff --git a/.github/workflows/tests-pr.yml b/.github/workflows/tests-pr.yml index 2b3aaf8a2cf..19523a560e5 100644 --- a/.github/workflows/tests-pr.yml +++ b/.github/workflows/tests-pr.yml @@ -55,8 +55,8 @@ jobs: run: pnpm prettier - name: Oxlint run: pnpm lint:oxlint - - name: Lint - run: pnpm nx run-many --all --target=lint --output-style=stream + - name: ESLint compatibility + run: pnpm lint:eslint --output-style=stream bundle: name: 'Bundle' diff --git a/docs/cli/get-started.md b/docs/cli/get-started.md index d64b8919374..505a90e82bc 100644 --- a/docs/cli/get-started.md +++ b/docs/cli/get-started.md @@ -73,8 +73,8 @@ Besides the scripts for building and running the CLIs, there are others that mig - `pnpm test`: Runs the tests of all the packages. - `pnpm prettier`: Checks formatting for all linted source files. - `pnpm prettier:fix`: Formats all linted source files. -- `pnpm lint`: Runs Prettier, Oxlint, and ESLint checks for all the packages. -- `pnpm lint:fix`: Runs Prettier and ESLint for all the packages and fixes the fixable issues. +- `pnpm lint`: Runs Prettier, Oxlint, and ESLint compatibility checks for all the packages. +- `pnpm lint:fix`: Runs Prettier, Oxlint, and ESLint compatibility checks and fixes supported issues. - `pnpm type-check`: Type-checks all the packagesusing the Typescript `tsc` tool. - `pnpm clean`: Removes the `dist` directory from all the packages. diff --git a/eslint.codegen.config.js b/eslint.codegen.config.js new file mode 100644 index 00000000000..e637f330600 --- /dev/null +++ b/eslint.codegen.config.js @@ -0,0 +1,4 @@ +import {eslintBaseConfig} from './eslint.config.js' + +// Generated GraphQL output relies on the full ESLint autofix behavior to remain deterministic. +export default eslintBaseConfig diff --git a/eslint.config.js b/eslint.config.js index ca4372edc03..77db6b6a91e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,9 +1,38 @@ import nxPlugin from '@nx/eslint-plugin' import cliPlugin from '@shopify/eslint-plugin-cli' import jsdocPlugin from 'eslint-plugin-jsdoc' +import {readFileSync} from 'node:fs' + +const oxlintConfig = JSON.parse(readFileSync(new URL('./oxlint.json', import.meta.url), 'utf8')) + +const eslintPluginNames = { + import: 'import-x', + node: 'n', + typescript: '@typescript-eslint', +} + +const eslintRuleName = (oxlintRuleName) => { + const [pluginName, ...ruleNameParts] = oxlintRuleName.split('/') + const eslintPluginName = eslintPluginNames[pluginName] + return eslintPluginName ? `${eslintPluginName}/${ruleNameParts.join('/')}` : oxlintRuleName +} + +const isEnabled = (ruleConfiguration) => { + const severity = Array.isArray(ruleConfiguration) ? ruleConfiguration[0] : ruleConfiguration + return severity !== 'off' && severity !== 0 +} + +const eslintCompatibilityConfig = ({files, rules}) => ({ + ...(files ? {files} : {}), + rules: Object.fromEntries( + Object.entries(rules) + .filter(([, ruleConfiguration]) => isEnabled(ruleConfiguration)) + .map(([ruleName]) => [eslintRuleName(ruleName), 'off']), + ), +}) // Spread the CLI plugin's base config which includes all necessary plugins -const config = [ +export const eslintBaseConfig = [ // Base config from @shopify/eslint-plugin-cli (includes shopify, typescript, prettier, etc.) ...cliPlugin.configs.config, @@ -243,4 +272,17 @@ const config = [ }, ] +const config = [ + ...eslintBaseConfig, + // ESLint remains as a compatibility layer for rules that Oxlint does not support yet. + // A directive can target an Oxlint-owned rule, so ESLint alone cannot determine whether it is unused. + { + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, + }, + eslintCompatibilityConfig(oxlintConfig), + ...oxlintConfig.overrides.map((override) => eslintCompatibilityConfig(override)), +] + export default config diff --git a/package.json b/package.json index d65766764fb..aaf6aa72792 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "graphql-codegen:get-graphql-schemas": "bin/get-graphql-schemas.js", "graphql-codegen": "nx run-many --target=graphql-codegen --all", "knip": "knip", - "lint:affected": "pnpm prettier && pnpm lint:oxlint && nx affected --target=lint", - "lint:fix:affected": "nx affected --target=lint:fix && pnpm prettier:fix", - "lint:fix": "nx run-many --target=lint:fix --all --skip-nx-cache && pnpm prettier:fix", - "lint": "pnpm prettier && pnpm lint:oxlint && nx run-many --target=lint --all", + "lint:affected": "pnpm prettier && pnpm lint:oxlint && pnpm lint:eslint:affected", + "lint:eslint:affected": "nx affected --target=lint", + "lint:fix:affected": "pnpm lint:oxlint --fix && nx affected --target=lint:fix && pnpm prettier:fix", + "lint:fix": "pnpm lint:oxlint --fix && nx run-many --target=lint:fix --all --skip-nx-cache && pnpm prettier:fix", + "lint": "pnpm prettier && pnpm lint:oxlint && pnpm lint:eslint", + "lint:eslint": "nx run-many --target=lint --all", "lint:oxlint": "oxlint --config oxlint.json --deny-warnings packages/app/src packages/cli-kit/src packages/cli/src packages/create-app/src packages/organizations/src packages/plugin-cloudflare/src packages/plugin-did-you-mean/src packages/store/src packages/theme/src packages/ui-extensions-server-kit/src packages/ui-extensions-test-utils/src packages/cli/bin packages/e2e", "prettier": "prettier --check \"packages/{app,cli-kit,cli,create-app,organizations,plugin-cloudflare,plugin-did-you-mean,store,theme,ui-extensions-server-kit,ui-extensions-test-utils}/src/**/*.{js,ts,tsx}\" \"packages/cli/bin/*.js\" \"packages/e2e/{setup,helpers,tests}/**/*.ts\" \"packages/e2e/*.ts\"", "prettier:fix": "prettier --write \"packages/{app,cli-kit,cli,create-app,organizations,plugin-cloudflare,plugin-did-you-mean,store,theme,ui-extensions-server-kit,ui-extensions-test-utils}/src/**/*.{js,ts,tsx}\" \"packages/cli/bin/*.js\" \"packages/e2e/{setup,helpers,tests}/**/*.ts\" \"packages/e2e/*.ts\"", @@ -191,7 +193,8 @@ ".": { "entry": [ "bin/**/*.js", - "configurations/vite.config.ts" + "configurations/vite.config.ts", + "eslint.codegen.config.js" ], "ignoreBinaries": [ "bin/*", diff --git a/packages/app/project.json b/packages/app/project.json index 357117cda8e..2e85a847346 100644 --- a/packages/app/project.json +++ b/packages/app/project.json @@ -74,13 +74,13 @@ ], "options": { "commands": [ - "pnpm eslint 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/app-dev/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/app-dev/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/app-management/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/app-management/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/webhooks/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/webhooks/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/functions/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/functions/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}'" + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/app-dev/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/app-dev/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/app-management/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/app-management/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/webhooks/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/webhooks/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/functions/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/functions/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}'" ], "cwd": "packages/app" } diff --git a/packages/cli-kit/project.json b/packages/cli-kit/project.json index 33381599c2f..e079d7c7e7e 100644 --- a/packages/cli-kit/project.json +++ b/packages/cli-kit/project.json @@ -109,8 +109,8 @@ ], "options": { "commands": [ - "pnpm eslint 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/bulk-operations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/bulk-operations/generated/**/*.{ts,tsx}'" + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/admin/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/bulk-operations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/bulk-operations/generated/**/*.{ts,tsx}'" ], "cwd": "packages/cli-kit" } diff --git a/packages/organizations/project.json b/packages/organizations/project.json index 0fbdd920d71..18b9c654419 100644 --- a/packages/organizations/project.json +++ b/packages/organizations/project.json @@ -53,8 +53,8 @@ ], "options": { "commands": [ - "pnpm eslint 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'" + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'" ], "cwd": "packages/organizations" } diff --git a/packages/store/project.json b/packages/store/project.json index aa887bdc79c..f34c0144f2f 100644 --- a/packages/store/project.json +++ b/packages/store/project.json @@ -54,8 +54,8 @@ ], "options": { "commands": [ - "pnpm eslint 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", - "pnpm eslint 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'" + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-destinations/generated/**/*.{ts,tsx}'", + "pnpm eslint --config ../../eslint.codegen.config.js 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}' --fix && pnpm exec prettier --write --ignore-path ../../.prettierignore 'src/cli/api/graphql/business-platform-organizations/generated/**/*.{ts,tsx}'" ], "cwd": "packages/store" } From e5c8cf151bd3e9fd0d1845d8b42c8ba9ecb59647 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Tue, 6 Oct 2026 10:12:46 +0200 Subject: [PATCH 2/3] Remove transitional lint commentary --- eslint.codegen.config.js | 1 - eslint.config.js | 1 - 2 files changed, 2 deletions(-) diff --git a/eslint.codegen.config.js b/eslint.codegen.config.js index e637f330600..e778ead89c3 100644 --- a/eslint.codegen.config.js +++ b/eslint.codegen.config.js @@ -1,4 +1,3 @@ import {eslintBaseConfig} from './eslint.config.js' -// Generated GraphQL output relies on the full ESLint autofix behavior to remain deterministic. export default eslintBaseConfig diff --git a/eslint.config.js b/eslint.config.js index 77db6b6a91e..419322f5d46 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -274,7 +274,6 @@ export const eslintBaseConfig = [ const config = [ ...eslintBaseConfig, - // ESLint remains as a compatibility layer for rules that Oxlint does not support yet. // A directive can target an Oxlint-owned rule, so ESLint alone cannot determine whether it is unused. { linterOptions: { From afc296193e0192879face4c9be2c078d9d2b0027 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 9 Oct 2026 13:49:30 +0200 Subject: [PATCH 3/3] Preserve compatibility lint coverage and track config cache inputs --- eslint.config.js | 5 +++-- nx.json | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 419322f5d46..50cb466f3c6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,8 +22,9 @@ const isEnabled = (ruleConfiguration) => { return severity !== 'off' && severity !== 0 } -const eslintCompatibilityConfig = ({files, rules}) => ({ - ...(files ? {files} : {}), +const eslintCompatibilityConfig = ({files, rules, excludeFiles = []}) => ({ + files: files ?? ['packages/*/src/**/*.{js,mjs,ts,tsx}', 'packages/cli/bin/*.js', 'packages/e2e/**/*.ts'], + ignores: [...oxlintConfig.ignorePatterns, 'packages/eslint-plugin-cli/**', ...excludeFiles], rules: Object.fromEntries( Object.entries(rules) .filter(([, ruleConfiguration]) => isEnabled(ruleConfiguration)) diff --git a/nx.json b/nx.json index 782e76079c2..9d7caaafeab 100644 --- a/nx.json +++ b/nx.json @@ -80,6 +80,8 @@ "{workspaceRoot}/configurations/tsconfig.json", "{workspaceRoot}/configurations/vite.config.ts", "{workspaceRoot}/eslint.config.js", + "{workspaceRoot}/eslint.codegen.config.js", + "{workspaceRoot}/oxlint.json", "{workspaceRoot}/graphql.config.ts", "{workspaceRoot}/package.json", "{workspaceRoot}/packages/eslint-plugin-cli/**/*",