@santux/eslint-config 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,45 +1,85 @@
1
1
  # @santux/eslint-config
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Shared ESLint, Prettier and commitlint config for TypeScript projects.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Entries
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ | Entry | Tool | Export |
8
+ | --------------------------------------- | ---------- | ----------------- |
9
+ | `@santux/eslint-config/lint` | ESLint 9 | Flat config array |
10
+ | `@santux/eslint-config/next` | ESLint 9 | Flat config array |
11
+ | `@santux/eslint-config/react-vite` | ESLint 9 | Flat config array |
12
+ | `@santux/eslint-config/tanstack-start` | ESLint 9 | Flat config array |
13
+ | `@santux/eslint-config/astro` | ESLint 9 | Flat config array |
14
+ | `@santux/eslint-config/astro-react` | ESLint 9 | Flat config array |
15
+ | `@santux/eslint-config/tailwind` | ESLint 9 | Flat config array |
16
+ | `@santux/eslint-config/format` | Prettier | Config object |
17
+ | `@santux/eslint-config/format-astro` | Prettier | Config object |
18
+ | `@santux/eslint-config/format-tailwind` | Prettier | Config object |
19
+ | `@santux/eslint-config/commitlint` | commitlint | Config object |
8
20
 
9
- ## Purpose
21
+ The lint entry uses typescript-eslint `strictTypeChecked` + `stylisticTypeChecked`, `eslint-plugin-import-x` (with the TypeScript import resolver, so `no-cycle`, `no-self-import` and `no-extraneous-dependencies` work on `.ts`/`.tsx`), unicorn and check-file naming.
10
22
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@santux/eslint-config`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
23
+ The `next` entry builds on the lint entry with react, react-hooks, jsx-a11y and Next.js rules (`react.version` is `19`). Compose it after the base: `defineConfig([santux, next])`. Its plugins (`@next/eslint-plugin-next`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies, needed only when you use it.
15
24
 
16
- ## What is OIDC Trusted Publishing?
25
+ The `react-vite` entry builds on the lint entry the same way, with react, react-hooks, jsx-a11y and react-refresh rules. Its plugins (`eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react-refresh`, `globals`) are optional peer dependencies.
17
26
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
27
+ The `tanstack-start` entry builds on the lint entry the same way, with react, react-hooks and jsx-a11y rules and browser plus Node globals. Its plugins (`eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies.
19
28
 
20
- ## Setup Instructions
29
+ The `astro` entry builds on the lint entry with the `eslint-plugin-astro` recommended rules and its jsx-a11y rules for `.astro` files, plus browser and Node globals. It parses `.astro` files with TypeScript and runs the type-aware rules there too, allows the `Props` interface Astro reads component props from, ignores `.astro/` and turns off the file and folder name rules under `pages`. Its plugins (`eslint-plugin-astro`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies. `eslint-plugin-astro` stays on 1.7.0, the last version that supports ESLint 9, until this package moves to ESLint 10.
21
30
 
22
- To properly configure OIDC trusted publishing for this package:
31
+ The `astro-react` entry adds the react, react-hooks and jsx-a11y rules to the `.jsx` and `.tsx` files of an Astro project with React islands (`@astrojs/react`), without react-refresh. Compose it after the `astro` entry: `defineConfig([santux, astro, astroReact])`. It needs `eslint-plugin-react` and `eslint-plugin-react-hooks`, optional peer dependencies.
23
32
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
33
+ The `tailwind` entry adds [`@shadcn/lint`](https://github.com/shadcn-ui/lint) (an optional peer dependency) with every rule except `require-static-classes`: `no-unknown-classes`, `no-raw-colors`, `no-arbitrary-values`, `no-inline-styles` and `no-restyle`. It needs no shadcn/ui: it finds the stylesheet that imports Tailwind, and it reads `components.json` when there is one. `no-restyle` only knows your design system components through `components.json` or `settings.shadcn.componentImports`. Compose it after the Framework entry: `defineConfig([santux, next, tailwind])`.
28
34
 
29
- ## DO NOT USE THIS PACKAGE
35
+ The `format-tailwind` entry is a Prettier config fragment that sorts Tailwind classes with `prettier-plugin-tailwindcss` (an optional peer dependency). Spread it after the format entry: `export default { ...santux, ...formatTailwind }`. For Tailwind v4 projects with a custom stylesheet, also set `tailwindStylesheet` in your Prettier config.
30
36
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
37
+ The `format-astro` entry formats `.astro` files with `prettier-plugin-astro` (an optional peer dependency), pinned to 0.14.1 because `prettier-plugin-tailwindcss` does not sort classes in `.astro` files with its 1.x. Spreading two entries keeps only the last `plugins`, so merge them with the Tailwind plugin last:
36
38
 
37
- ## More Information
39
+ ```js
40
+ export default {
41
+ ...santux,
42
+ ...formatAstro,
43
+ ...formatTailwind,
44
+ plugins: [...formatAstro.plugins, ...formatTailwind.plugins],
45
+ }
46
+ ```
38
47
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
48
+ ## Usage
42
49
 
43
- ---
50
+ Every tool is an exact-version peer dependency and must be installed in your project:
44
51
 
45
- **Maintained for OIDC setup purposes only**
52
+ ```sh
53
+ pnpm add -D @santux/eslint-config @commitlint/cli @commitlint/config-conventional @eslint/js @stylistic/eslint-plugin eslint eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-check-file eslint-plugin-import-x eslint-plugin-unicorn husky prettier typescript typescript-eslint
54
+ ```
55
+
56
+ ```js
57
+ // eslint.config.js
58
+ import santux from '@santux/eslint-config/lint'
59
+ import { defineConfig } from 'eslint/config'
60
+
61
+ export default defineConfig([
62
+ santux,
63
+ // Override, disable or add rules locally
64
+ { rules: { 'no-console': 'off', 'unicorn/prefer-node-protocol': 'error' } },
65
+ { files: ['**/*.test.ts'], rules: { '@typescript-eslint/no-non-null-assertion': 'off' } },
66
+ ])
67
+ ```
68
+
69
+ ```js
70
+ // prettier.config.js
71
+ import santux from '@santux/eslint-config/format'
72
+
73
+ export default { ...santux, printWidth: 120 }
74
+ ```
75
+
76
+ ```js
77
+ // commitlint.config.js
78
+ export { default } from '@santux/eslint-config/commitlint'
79
+ ```
80
+
81
+ Requires Node `>=22.18`.
82
+
83
+ ## Versioning
84
+
85
+ This package follows strict semver: **any change that can make previously passing code fail is a major release**. That includes a new error-level rule, a rule moving from warning to error, and a newer required tool or Node version. Projects can stay on an older major for as long as they want.
package/package.json CHANGED
@@ -1,10 +1,172 @@
1
1
  {
2
2
  "name": "@santux/eslint-config",
3
- "version": "0.0.0",
4
- "description": "OIDC trusted publishing setup package for @santux/eslint-config",
3
+ "version": "1.0.0",
4
+ "description": "Shared ESLint, Prettier and commitlint config for TypeScript projects.",
5
5
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
6
+ "commitlint",
7
+ "eslint",
8
+ "eslint-config",
9
+ "prettier"
10
+ ],
11
+ "license": "ISC",
12
+ "author": {
13
+ "name": "Nicolás Santuccio",
14
+ "email": "nico.santuccio@gmail.com"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/nicosantux/santux.git",
19
+ "directory": "packages/eslint-config"
20
+ },
21
+ "files": [
22
+ "src",
23
+ "!src/test",
24
+ "README.md"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ "./lint": {
29
+ "types": "./src/lint.d.ts",
30
+ "default": "./src/lint.js"
31
+ },
32
+ "./format": {
33
+ "types": "./src/format.d.ts",
34
+ "default": "./src/format.js"
35
+ },
36
+ "./format-astro": {
37
+ "types": "./src/format-astro.d.ts",
38
+ "default": "./src/format-astro.js"
39
+ },
40
+ "./format-tailwind": {
41
+ "types": "./src/format-tailwind.d.ts",
42
+ "default": "./src/format-tailwind.js"
43
+ },
44
+ "./commitlint": {
45
+ "types": "./src/commitlint.d.ts",
46
+ "default": "./src/commitlint.js"
47
+ },
48
+ "./astro": {
49
+ "types": "./src/astro.d.ts",
50
+ "default": "./src/astro.js"
51
+ },
52
+ "./astro-react": {
53
+ "types": "./src/astro-react.d.ts",
54
+ "default": "./src/astro-react.js"
55
+ },
56
+ "./next": {
57
+ "types": "./src/next.d.ts",
58
+ "default": "./src/next.js"
59
+ },
60
+ "./react-vite": {
61
+ "types": "./src/react-vite.d.ts",
62
+ "default": "./src/react-vite.js"
63
+ },
64
+ "./tanstack-start": {
65
+ "types": "./src/tanstack-start.d.ts",
66
+ "default": "./src/tanstack-start.js"
67
+ },
68
+ "./tailwind": {
69
+ "types": "./src/tailwind.d.ts",
70
+ "default": "./src/tailwind.js"
71
+ }
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ },
76
+ "scripts": {
77
+ "test": "vitest run",
78
+ "typecheck": "tsc --noEmit"
79
+ },
80
+ "devDependencies": {
81
+ "@commitlint/cli": "21.2.4",
82
+ "@commitlint/config-conventional": "21.2.4",
83
+ "@eslint/js": "9.39.5",
84
+ "@next/eslint-plugin-next": "16.4.6",
85
+ "@shadcn/lint": "0.2.5",
86
+ "@stylistic/eslint-plugin": "5.10.0",
87
+ "eslint": "9.39.5",
88
+ "eslint-config-prettier": "10.1.8",
89
+ "eslint-import-resolver-typescript": "4.4.5",
90
+ "eslint-plugin-astro": "1.7.0",
91
+ "eslint-plugin-check-file": "3.3.2",
92
+ "eslint-plugin-import-x": "4.18.1",
93
+ "eslint-plugin-jsx-a11y": "6.10.2",
94
+ "eslint-plugin-react": "7.37.5",
95
+ "eslint-plugin-react-hooks": "7.1.1",
96
+ "eslint-plugin-react-refresh": "0.5.7",
97
+ "eslint-plugin-unicorn": "65.0.1",
98
+ "globals": "17.14.0",
99
+ "husky": "9.1.7",
100
+ "prettier": "3.10.1",
101
+ "prettier-plugin-astro": "0.14.1",
102
+ "prettier-plugin-tailwindcss": "0.8.1",
103
+ "tailwindcss": "4.3.5",
104
+ "typescript": "6.0.3",
105
+ "typescript-eslint": "8.72.2",
106
+ "vitest": "5.1.0"
107
+ },
108
+ "peerDependencies": {
109
+ "@commitlint/cli": "21.2.4",
110
+ "@commitlint/config-conventional": "21.2.4",
111
+ "@eslint/js": "9.39.5",
112
+ "@next/eslint-plugin-next": "16.4.6",
113
+ "@shadcn/lint": "0.2.5",
114
+ "@stylistic/eslint-plugin": "5.10.0",
115
+ "@typescript/typescript6": "6.0.2",
116
+ "eslint": "9.39.5",
117
+ "eslint-config-prettier": "10.1.8",
118
+ "eslint-import-resolver-typescript": "4.4.5",
119
+ "eslint-plugin-astro": "1.7.0",
120
+ "eslint-plugin-check-file": "3.3.2",
121
+ "eslint-plugin-import-x": "4.18.1",
122
+ "eslint-plugin-jsx-a11y": "6.10.2",
123
+ "eslint-plugin-react": "7.37.5",
124
+ "eslint-plugin-react-hooks": "7.1.1",
125
+ "eslint-plugin-react-refresh": "0.5.7",
126
+ "eslint-plugin-unicorn": "65.0.1",
127
+ "globals": "17.14.0",
128
+ "husky": "9.1.7",
129
+ "prettier": "3.10.1",
130
+ "prettier-plugin-astro": "0.14.1",
131
+ "prettier-plugin-tailwindcss": "0.8.1",
132
+ "typescript-eslint": "8.72.2"
133
+ },
134
+ "peerDependenciesMeta": {
135
+ "@next/eslint-plugin-next": {
136
+ "optional": true
137
+ },
138
+ "@shadcn/lint": {
139
+ "optional": true
140
+ },
141
+ "@typescript/typescript6": {
142
+ "optional": true
143
+ },
144
+ "eslint-plugin-astro": {
145
+ "optional": true
146
+ },
147
+ "eslint-plugin-jsx-a11y": {
148
+ "optional": true
149
+ },
150
+ "eslint-plugin-react": {
151
+ "optional": true
152
+ },
153
+ "eslint-plugin-react-hooks": {
154
+ "optional": true
155
+ },
156
+ "eslint-plugin-react-refresh": {
157
+ "optional": true
158
+ },
159
+ "globals": {
160
+ "optional": true
161
+ },
162
+ "prettier-plugin-astro": {
163
+ "optional": true
164
+ },
165
+ "prettier-plugin-tailwindcss": {
166
+ "optional": true
167
+ }
168
+ },
169
+ "engines": {
170
+ "node": ">=22.18"
171
+ }
10
172
  }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import globals from 'globals'
3
+
4
+ import { reactConfig } from './react.js'
5
+
6
+ export default defineConfig([{ ...reactConfig(globals.browser), files: ['**/*.{jsx,tsx}'] }])
package/src/astro.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/astro.js ADDED
@@ -0,0 +1,68 @@
1
+ import { createRequire } from 'node:module'
2
+
3
+ import astroPlugin from 'eslint-plugin-astro'
4
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+ import globals from 'globals'
7
+ import tsEslint from 'typescript-eslint'
8
+
9
+ // The rules of eslint-plugin-astro's `jsx-a11y-recommended` config, limited to .astro files. The
10
+ // config itself registers its own copy of the `jsx-a11y` plugin, which clashes with the React rules
11
+ const a11yRules = Object.fromEntries(
12
+ Object.entries(jsxA11yPlugin.flatConfigs.recommended.rules).map(([name, entry]) => [
13
+ `astro/${name}`,
14
+ entry,
15
+ ]),
16
+ )
17
+
18
+ // import-x parses the modules an .astro file imports with the .astro parser unless told otherwise
19
+ const tsParserPath = createRequire(import.meta.resolve('typescript-eslint')).resolve(
20
+ '@typescript-eslint/parser',
21
+ )
22
+
23
+ export default defineConfig([
24
+ globalIgnores(['**/.astro/**']),
25
+ astroPlugin.configs['flat/recommended'],
26
+ {
27
+ files: ['**/*.astro'],
28
+ // eslint-plugin-astro only picks the TS parser and the TS <script> processor when it finds the
29
+ // parser hoisted to the project root, and astro-eslint-parser does not support `projectService`
30
+ processor: 'astro/client-side-ts',
31
+ languageOptions: {
32
+ parserOptions: {
33
+ parser: tsEslint.parser,
34
+ extraFileExtensions: ['.astro'],
35
+ project: true,
36
+ projectService: false,
37
+ },
38
+ },
39
+ settings: {
40
+ 'import-x/parsers': {
41
+ [tsParserPath]: ['.js', '.cjs', '.mjs', '.jsx', '.ts', '.cts', '.mts', '.tsx'],
42
+ },
43
+ },
44
+ rules: {
45
+ ...a11yRules,
46
+ // Astro types a component's props through an interface named `Props`
47
+ '@typescript-eslint/naming-convention': [
48
+ 'error',
49
+ { format: ['PascalCase'], selector: ['typeLike', 'enumMember'] },
50
+ {
51
+ custom: { match: false, regex: '^I[A-Z]|^(Interface|State)$' },
52
+ format: ['PascalCase'],
53
+ selector: 'interface',
54
+ },
55
+ ],
56
+ },
57
+ },
58
+ // The <script> blocks are virtual files that no tsconfig includes, so they get no type information
59
+ { files: ['**/*.astro/*.ts'], ...tsEslint.configs.disableTypeChecked },
60
+ {
61
+ files: ['**/pages/**'],
62
+ rules: {
63
+ 'check-file/filename-naming-convention': 'off',
64
+ 'check-file/folder-naming-convention': 'off',
65
+ },
66
+ },
67
+ { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
68
+ ])
@@ -0,0 +1,2 @@
1
+ declare const config: { extends: string[]; rules: Record<string, [number, string, unknown]> }
2
+ export default config
@@ -0,0 +1,8 @@
1
+ /** @type {import('@commitlint/types').UserConfig} */
2
+ export default {
3
+ extends: ['@commitlint/config-conventional'],
4
+ rules: {
5
+ 'subject-case': [2, 'always', 'lower-case'],
6
+ 'body-max-line-length': [2, 'always', 72],
7
+ },
8
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
@@ -0,0 +1,5 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ plugins: ['prettier-plugin-astro'],
4
+ overrides: [{ files: '*.astro', options: { parser: 'astro' } }],
5
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
@@ -0,0 +1,4 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ plugins: ['prettier-plugin-tailwindcss'],
4
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
package/src/format.js ADDED
@@ -0,0 +1,15 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ arrowParens: 'always',
4
+ bracketSameLine: false,
5
+ bracketSpacing: true,
6
+ endOfLine: 'auto',
7
+ jsxSingleQuote: true,
8
+ printWidth: 100,
9
+ quoteProps: 'as-needed',
10
+ semi: false,
11
+ singleQuote: true,
12
+ tabWidth: 2,
13
+ trailingComma: 'all',
14
+ useTabs: false,
15
+ }
@@ -0,0 +1 @@
1
+ export declare function importOrder(earlyPackages: string[]): [string, object]
@@ -0,0 +1,17 @@
1
+ /** `import-x/order` options. Packages in `earlyPackages` are grouped before the other builtins. */
2
+ export function importOrder(earlyPackages) {
3
+ return [
4
+ 'warn',
5
+ {
6
+ alphabetize: { order: 'asc', caseInsensitive: true },
7
+ groups: ['type', 'builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
8
+ 'newlines-between': 'always',
9
+ pathGroups: [
10
+ { pattern: '@/**', group: 'internal', position: 'after' },
11
+ { pattern: `{${earlyPackages.join(',')}}`, group: 'builtin', position: 'before' },
12
+ ],
13
+ pathGroupsExcludedImportTypes: ['builtin', 'internal'],
14
+ warnOnUnassignedImports: true,
15
+ },
16
+ ]
17
+ }
package/src/lint.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/lint.js ADDED
@@ -0,0 +1,169 @@
1
+ import eslint from '@eslint/js'
2
+ import stylistic from '@stylistic/eslint-plugin'
3
+ import eslintConfigPrettier from 'eslint-config-prettier/flat'
4
+ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
5
+ import checkFilePlugin from 'eslint-plugin-check-file'
6
+ import { importX } from 'eslint-plugin-import-x'
7
+ import unicornPlugin from 'eslint-plugin-unicorn'
8
+ import { defineConfig, globalIgnores } from 'eslint/config'
9
+ import tsEslint from 'typescript-eslint'
10
+
11
+ import { importOrder } from './import-order.js'
12
+
13
+ export default defineConfig([
14
+ globalIgnores(['**/node_modules', '**/build', '**/dist']),
15
+ // Vanilla ESLint
16
+ {
17
+ extends: [eslint.configs.recommended],
18
+ rules: {
19
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
20
+ 'no-await-in-loop': 'error',
21
+ 'no-console': 'error',
22
+ 'no-else-return': 'warn',
23
+ 'no-implicit-coercion': 'error',
24
+ 'no-param-reassign': 'error',
25
+ 'no-self-compare': 'error',
26
+ 'no-undef-init': 'warn',
27
+ 'no-unneeded-ternary': 'error',
28
+ 'no-var': 'error',
29
+ 'object-shorthand': 'warn',
30
+ 'prefer-object-spread': 'warn',
31
+ 'prefer-rest-params': 'error',
32
+ 'prefer-spread': 'error',
33
+ 'prefer-template': 'warn',
34
+ },
35
+ },
36
+ // Check files
37
+ {
38
+ plugins: { 'check-file': checkFilePlugin },
39
+ rules: {
40
+ 'check-file/filename-naming-convention': [
41
+ 'error',
42
+ { '**/*.{js,jsx,ts,tsx}': 'KEBAB_CASE' },
43
+ { ignoreMiddleExtensions: true },
44
+ ],
45
+ 'check-file/folder-naming-convention': ['error', { '**/src/**/': 'KEBAB_CASE' }],
46
+ },
47
+ },
48
+ // Import order
49
+ {
50
+ plugins: { 'import-x': importX },
51
+ settings: {
52
+ 'import-x/extensions': ['.js', '.jsx', '.ts', '.tsx'],
53
+ 'import-x/resolver-next': [createTypeScriptImportResolver()],
54
+ },
55
+ rules: {
56
+ 'import-x/first': 'error',
57
+ 'import-x/newline-after-import': 'warn',
58
+ 'import-x/no-duplicates': ['warn', { 'prefer-inline': true }],
59
+ 'import-x/no-cycle': 'error',
60
+ 'import-x/no-extraneous-dependencies': ['error', { includeTypes: true }],
61
+ 'import-x/no-mutable-exports': 'error',
62
+ 'import-x/no-self-import': 'error',
63
+ 'import-x/no-useless-path-segments': 'error',
64
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
65
+ },
66
+ },
67
+ // Stylistic
68
+ {
69
+ plugins: { '@stylistic': stylistic },
70
+ rules: {
71
+ '@stylistic/padding-line-between-statements': [
72
+ 'warn',
73
+ { blankLine: 'always', prev: 'directive', next: '*' },
74
+ { blankLine: 'always', prev: '*', next: 'block-like' },
75
+ { blankLine: 'always', prev: 'block-like', next: '*' },
76
+ { blankLine: 'always', prev: '*', next: 'return' },
77
+ { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
78
+ {
79
+ blankLine: 'any',
80
+ prev: ['singleline-const', 'singleline-let', 'singleline-var'],
81
+ next: ['singleline-const', 'singleline-let', 'singleline-var'],
82
+ },
83
+ ],
84
+ },
85
+ },
86
+ // TypeScript
87
+ {
88
+ extends: [tsEslint.configs.strictTypeChecked, tsEslint.configs.stylisticTypeChecked],
89
+ languageOptions: { parserOptions: { projectService: true } },
90
+ rules: {
91
+ '@typescript-eslint/consistent-type-exports': [
92
+ 'warn',
93
+ { fixMixedExportsWithInlineTypeSpecifier: true },
94
+ ],
95
+ '@typescript-eslint/consistent-type-imports': ['warn', { fixStyle: 'inline-type-imports' }],
96
+ '@typescript-eslint/default-param-last': 'error',
97
+ '@typescript-eslint/member-ordering': [
98
+ 'error',
99
+ { interfaces: { order: 'natural-case-insensitive' } },
100
+ ],
101
+ '@typescript-eslint/method-signature-style': 'warn',
102
+ '@typescript-eslint/naming-convention': [
103
+ 'error',
104
+ { format: ['PascalCase'], selector: ['typeLike', 'enumMember'] },
105
+ {
106
+ custom: {
107
+ match: false,
108
+ regex: '^I[A-Z]|^(Interface|Props|State)$',
109
+ },
110
+ format: ['PascalCase'],
111
+ selector: 'interface',
112
+ },
113
+ ],
114
+ '@typescript-eslint/no-import-type-side-effects': 'warn',
115
+ '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
116
+ '@typescript-eslint/no-loop-func': 'error',
117
+ '@typescript-eslint/no-redundant-type-constituents': 'warn',
118
+ '@typescript-eslint/no-unnecessary-qualifier': 'warn',
119
+ '@typescript-eslint/no-unused-vars': [
120
+ 'error',
121
+ {
122
+ args: 'all',
123
+ argsIgnorePattern: '^_',
124
+ caughtErrors: 'all',
125
+ caughtErrorsIgnorePattern: '^_',
126
+ destructuredArrayIgnorePattern: '^_',
127
+ varsIgnorePattern: '^_',
128
+ ignoreRestSiblings: true,
129
+ },
130
+ ],
131
+ '@typescript-eslint/prefer-nullish-coalescing': [
132
+ 'error',
133
+ {
134
+ ignorePrimitives: {
135
+ bigint: true,
136
+ boolean: true,
137
+ number: true,
138
+ string: true,
139
+ },
140
+ },
141
+ ],
142
+ '@typescript-eslint/require-array-sort-compare': 'error',
143
+ '@typescript-eslint/switch-exhaustiveness-check': 'error',
144
+ '@typescript-eslint/triple-slash-reference': 'off',
145
+ // After the typescript-eslint presets, which set it to error in TypeScript files
146
+ 'prefer-const': 'warn',
147
+ },
148
+ },
149
+ // Disable type checked in .js files
150
+ {
151
+ files: ['**/*.{js,cjs,mjs}'],
152
+ ...tsEslint.configs.disableTypeChecked,
153
+ },
154
+ // Unicorn
155
+ {
156
+ plugins: { unicorn: unicornPlugin },
157
+ rules: {
158
+ 'unicorn/error-message': 'error',
159
+ 'unicorn/no-empty-file': 'error',
160
+ 'unicorn/no-instanceof-builtins': 'error',
161
+ 'unicorn/no-useless-spread': 'warn',
162
+ 'unicorn/prefer-array-flat-map': 'warn',
163
+ 'unicorn/prefer-node-protocol': 'warn',
164
+ 'unicorn/throw-new-error': 'error',
165
+ },
166
+ },
167
+ // Prettier
168
+ eslintConfigPrettier,
169
+ ])
package/src/next.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/next.js ADDED
@@ -0,0 +1,18 @@
1
+ import nextPlugin from '@next/eslint-plugin-next'
2
+ import { defineConfig, globalIgnores } from 'eslint/config'
3
+ import globals from 'globals'
4
+
5
+ import { importOrder } from './import-order.js'
6
+ import { reactConfig } from './react.js'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['**/.next/**', '**/out/**', '**/next-env.d.ts']),
10
+ {
11
+ rules: {
12
+ 'check-file/folder-naming-convention': ['error', { '**/src/**/': 'NEXT_JS_APP_ROUTER_CASE' }],
13
+ 'import-x/order': importOrder(['next', 'next/**', 'react', 'react-dom', 'react-dom/**']),
14
+ },
15
+ },
16
+ nextPlugin.configs['core-web-vitals'],
17
+ reactConfig({ ...globals.browser, ...globals.node }),
18
+ ])
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,15 @@
1
+ import reactRefreshPlugin from 'eslint-plugin-react-refresh'
2
+ import { defineConfig } from 'eslint/config'
3
+ import globals from 'globals'
4
+
5
+ import { importOrder } from './import-order.js'
6
+ import { reactConfig } from './react.js'
7
+
8
+ export default defineConfig([
9
+ {
10
+ rules: {
11
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
12
+ },
13
+ },
14
+ reactConfig(globals.browser, [reactRefreshPlugin.configs.vite]),
15
+ ])
package/src/react.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ export declare function reactConfig(
4
+ globals: Record<string, boolean | 'readonly' | 'writable' | 'off'>,
5
+ extraExtends?: Config[],
6
+ ): Config
package/src/react.js ADDED
@@ -0,0 +1,37 @@
1
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'
2
+ import reactPlugin from 'eslint-plugin-react'
3
+ import reactHooksPlugin from 'eslint-plugin-react-hooks'
4
+
5
+ /** React config block shared by the Framework entries. `extraExtends` go after the React presets. */
6
+ export function reactConfig(globals, extraExtends = []) {
7
+ return {
8
+ files: ['**/*.{js,mjs,cjs,jsx,ts,tsx}'],
9
+ extends: [
10
+ reactPlugin.configs.flat.recommended,
11
+ reactPlugin.configs.flat['jsx-runtime'],
12
+ reactHooksPlugin.configs.flat['recommended-latest'],
13
+ jsxA11yPlugin.flatConfigs.recommended,
14
+ ...extraExtends,
15
+ ],
16
+ languageOptions: {
17
+ globals,
18
+ parserOptions: { ecmaFeatures: { jsx: true } },
19
+ },
20
+ rules: {
21
+ 'react/function-component-definition': 'warn',
22
+ 'react/hook-use-state': 'error',
23
+ 'react/jsx-boolean-value': 'warn',
24
+ 'react/jsx-curly-brace-presence': 'warn',
25
+ 'react/jsx-fragments': 'warn',
26
+ 'react/jsx-no-leaked-render': 'warn',
27
+ 'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
28
+ 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
29
+ 'react/jsx-sort-props': ['warn', { noSortAlphabetically: false }],
30
+ 'react/no-array-index-key': 'warn',
31
+ 'react/no-unstable-nested-components': 'error',
32
+ 'react/prop-types': 'off',
33
+ 'react/self-closing-comp': 'warn',
34
+ },
35
+ settings: { react: { version: '19' } },
36
+ }
37
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,16 @@
1
+ import { plugin as shadcnPlugin } from '@shadcn/lint'
2
+ import { defineConfig } from 'eslint/config'
3
+
4
+ export default defineConfig([
5
+ {
6
+ files: ['**/*.{js,mjs,cjs,jsx,ts,tsx}'],
7
+ plugins: { shadcn: shadcnPlugin },
8
+ rules: {
9
+ 'shadcn/no-arbitrary-values': 'error',
10
+ 'shadcn/no-inline-styles': 'error',
11
+ 'shadcn/no-raw-colors': 'error',
12
+ 'shadcn/no-restyle': 'error',
13
+ 'shadcn/no-unknown-classes': 'error',
14
+ },
15
+ },
16
+ ])
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,22 @@
1
+ import { defineConfig, globalIgnores } from 'eslint/config'
2
+ import globals from 'globals'
3
+
4
+ import { importOrder } from './import-order.js'
5
+ import { reactConfig } from './react.js'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['**/.output/**', '**/.tanstack/**', '**/routeTree.gen.ts']),
9
+ {
10
+ rules: {
11
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
12
+ },
13
+ },
14
+ {
15
+ files: ['**/routes/**'],
16
+ rules: {
17
+ 'check-file/filename-naming-convention': 'off',
18
+ 'check-file/folder-naming-convention': 'off',
19
+ },
20
+ },
21
+ reactConfig({ ...globals.browser, ...globals.node }),
22
+ ])