@forthtilliath/eslint-config 0.8.0 → 0.10.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
@@ -24,10 +24,52 @@ Or, from within this monorepo, as a workspace dependency:
24
24
  ```
25
25
 
26
26
  `eslint` and `typescript` are peer dependencies — install them yourself (any
27
- recent flat-config-era ESLint 9+/10+, and TypeScript 5+). Every ESLint plugin
28
- each variant actually uses (`typescript-eslint`, `eslint-plugin-react`,
29
- `angular-eslint`, etc.) ships as a regular dependency of this package, so
30
- there's nothing else to install per variant.
27
+ recent flat-config-era ESLint 9+/10+, and TypeScript 5+). Only what the base
28
+ config needs (`typescript-eslint`, `@eslint/js`, `globals`, import sorting,
29
+ Prettier conflict-resolution) ships as a regular dependency: every other
30
+ plugin is an **optional peer dependency**, installed by the project only for
31
+ the variant and options it actually uses. A React Native app doesn't get
32
+ `angular-eslint` (and the Angular CLI it pulls in), nor a TypeScript library
33
+ every React plugin.
34
+
35
+ | Variant / option | Also install |
36
+ | ---------------------- | --------------------------------------------------------------------------------------------------------- |
37
+ | `.` (base) | — |
38
+ | `/react` | `@eslint-react/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh` |
39
+ | `/react-native` | the `/react` list + `eslint-plugin-react-native` |
40
+ | `/nextjs` | the `/react` list + `@next/eslint-plugin-next` |
41
+ | `/storybook` | the `/react` list + `eslint-plugin-storybook` |
42
+ | `/angular` | `angular-eslint` |
43
+ | `turbo: true`\*\* | `eslint-plugin-turbo` — \*\*default inside a Turborepo (a `turbo.json` in the working directory or above) |
44
+ | `a11y: true`\* | `eslint-plugin-jsx-a11y` — \*default for `/react`, `/nextjs`, `/storybook`; pass `a11y: false` to skip it |
45
+ | `i18n: true` | `eslint-plugin-i18next` |
46
+ | `testingLibrary: true` | `eslint-plugin-testing-library eslint-plugin-jest-dom` |
47
+ | `expo: true` | `eslint-plugin-expo` — `/react-native` only |
48
+
49
+ ```bash
50
+ # e.g. a standalone React Native / Expo app, with createReactNativeConfig({ expo: true })
51
+ npm install --save-dev @forthtilliath/eslint-config eslint typescript \
52
+ @eslint-react/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks \
53
+ eslint-plugin-react-refresh eslint-plugin-react-native eslint-plugin-expo
54
+ ```
55
+
56
+ A missing plugin fails with an explicit error naming the package to install
57
+ (or the option to turn off) as soon as ESLint loads the config.
58
+
59
+ **ESLint 10:** `eslint-plugin-react` and `eslint-plugin-react-native` still
60
+ declare `eslint` up to 9 only, so npm refuses to install them next to ESLint
61
+ 10 (`ERESOLVE`). They work under ESLint 10 (see the `react-native` rules
62
+ below for the exceptions), so tell npm to accept it in the app's
63
+ `package.json`:
64
+
65
+ ```json
66
+ {
67
+ "overrides": {
68
+ "eslint-plugin-react": { "eslint": "$eslint" },
69
+ "eslint-plugin-react-native": { "eslint": "$eslint" }
70
+ }
71
+ }
72
+ ```
31
73
 
32
74
  ## Usage
33
75
 
@@ -82,9 +124,21 @@ export default reactNativeConfig;
82
124
 
83
125
  All six are **named** exports — a default import (`import config from "..."`)
84
126
  resolves to the whole module namespace object instead of the config array and
85
- crashes ESLint's flat-config loader outright. Two of the five variants had
127
+ crashes ESLint's flat-config loader outright. Two of the variants had
86
128
  exactly this bug at one point; use the named-import form above.
87
129
 
130
+ The config loads from a CommonJS `eslint.config.js` too (the one Expo's
131
+ templates generate), through `require()` — no top-level `await` anywhere in
132
+ the package. That needs Node 20.19+ or 22.12+ (`require()` of an ES module):
133
+
134
+ ```js
135
+ // eslint.config.js (CommonJS)
136
+ const {
137
+ createReactNativeConfig,
138
+ } = require("@forthtilliath/eslint-config/react-native");
139
+ module.exports = createReactNativeConfig({ expo: true });
140
+ ```
141
+
88
142
  Each variant is additive: `reactConfig`/`angularConfig` extend `baseConfig`,
89
143
  `nextJsConfig`/`storybookConfig`/`reactNativeConfig` extend `reactConfig`.
90
144
 
@@ -105,20 +159,21 @@ export default createReactConfig({ prettier: false });
105
159
  ```ts
106
160
  // Fall back to typescript-eslint's `recommended` preset instead of
107
161
  // `strictTypeChecked`/`stylisticTypeChecked` (e.g. while migrating an
108
- // existing codebase), and skip the Turborepo-only rule outside a Turborepo.
162
+ // existing codebase).
109
163
  import { createBaseConfig } from "@forthtilliath/eslint-config";
110
- export default createBaseConfig({ strict: false, turbo: false });
164
+ export default createBaseConfig({ strict: false });
111
165
  ```
112
166
 
113
167
  | Option | Default | Effect |
114
168
  | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
169
  | `prettier` | `true` | Append `eslint-config-prettier` at the end. |
116
170
  | `strict` | `true` | Use `strictTypeChecked`/`stylisticTypeChecked` instead of plain `recommended`. |
117
- | `turbo` | `true` | Enable `eslint-plugin-turbo`'s `no-undeclared-env-vars` rule. |
171
+ | `turbo` | auto | Enable `eslint-plugin-turbo`'s `no-undeclared-env-vars` rule. Defaults to `true` when a `turbo.json` sits in ESLint's working directory or one of its parents, `false` otherwise. |
118
172
  | `snakeCase` | `false` | Also accept `snake_case` variable names in `@typescript-eslint/naming-convention` (e.g. rows destructured straight from a Supabase/SQL database). |
119
173
  | `a11y` | `true`\* | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-jsx-a11y`'s recommended rules. \*Defaults to `false` in `createReactNativeConfig` (jsx-a11y targets DOM semantics, which RN doesn't have). |
120
174
  | `i18n` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-i18next`'s `no-literal-string` rule. |
121
175
  | `testingLibrary` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-testing-library` + `eslint-plugin-jest-dom` on test files. |
176
+ | `expo` | `false` | _(react-native)_ Enable the `eslint-plugin-expo` rules `eslint-config-expo` turns on: `use-dom-exports`, `no-env-var-destructuring`, `no-dynamic-env-var`. |
122
177
 
123
178
  Options are forwarded down the chain, so `createNextJsConfig({ prettier: false })`
124
179
  also disables Prettier in the `reactConfig`/`baseConfig` layers it builds on.
@@ -173,6 +228,10 @@ consumer here yet, so `src/angular.test.js` lints small fixture files under
173
228
  for that. Extend it (or add a sibling `<variant>.test.js`) whenever a variant
174
229
  changes without a real consumer to catch a regression.
175
230
 
231
+ `src/package.test.js` guards the dependency layout: any plugin imported
232
+ outside the base config must stay an optional peer dependency, never a
233
+ regular one.
234
+
176
235
  ## Scripts
177
236
 
178
237
  ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"angular.d.ts","sourceRoot":"","sources":["../src/angular.js"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,8CAHW,OAAO,WAAW,EAAE,iBAAiB,GACnC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA+D5C;AAED;;;;8CAI8C;AAC9C,4BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACS"}
1
+ {"version":3,"file":"angular.d.ts","sourceRoot":"","sources":["../src/angular.js"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AACH,8CAHW,OAAO,WAAW,EAAE,iBAAiB,GACnC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA+D5C;AAED;;;;8CAI8C;AAC9C,4BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAC2B"}
package/dist/angular.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import angular from "angular-eslint";
2
2
  import { defineConfig } from "eslint/config";
3
3
  import tseslint from "typescript-eslint";
4
+ import { lazyConfig } from "./internal/lazy-config.js";
4
5
  import { createBaseConfig, NODE_BUILTIN_IMPORT_GROUP } from "./base.js";
5
6
  /**
6
7
  * A custom ESLint configuration for Angular applications/libraries.
@@ -80,4 +81,4 @@ export function createAngularConfig(options) {
80
81
  * default options. Use `createAngularConfig(options)` instead to customize it.
81
82
  *
82
83
  * @type {import("eslint").Linter.Config[]} */
83
- export const angularConfig = createAngularConfig();
84
+ export const angularConfig = lazyConfig(() => createAngularConfig());
package/dist/base.d.ts CHANGED
@@ -8,9 +8,11 @@
8
8
  * `strictTypeChecked` + `stylisticTypeChecked` presets. Set to `false` to
9
9
  * fall back to the plain `recommended` preset instead (useful while
10
10
  * migrating an existing codebase that isn't ready for the strict rules).
11
- * @property {boolean} [turbo=true] - Enable eslint-plugin-turbo's
12
- * `no-undeclared-env-vars` rule. Only relevant inside a Turborepo; set to
13
- * `false` for a standalone project.
11
+ * @property {boolean} [turbo] - Enable eslint-plugin-turbo's
12
+ * `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
13
+ * Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
14
+ * sits in ESLint's working directory or one of its parents, `false`
15
+ * otherwise.
14
16
  * @property {boolean} [snakeCase=false] - Also accept `snake_case` variable
15
17
  * names in `@typescript-eslint/naming-convention` — for code that
16
18
  * destructures rows straight from a SQL database (e.g. Supabase), whose
@@ -70,8 +72,10 @@ export type BaseConfigOptions = {
70
72
  strict?: boolean | undefined;
71
73
  /**
72
74
  * - Enable eslint-plugin-turbo's
73
- * `no-undeclared-env-vars` rule. Only relevant inside a Turborepo; set to
74
- * `false` for a standalone project.
75
+ * `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
76
+ * Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
77
+ * sits in ESLint's working directory or one of its parents, `false`
78
+ * otherwise.
75
79
  */
76
80
  turbo?: boolean | undefined;
77
81
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.js"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;GAUG;AACH,mEALG;IAA0B,SAAS;IACT,KAAK;CAE/B,GAAU,OAAO,QAAQ,EAAE,MAAM,CAAC,SAAS,CA+B7C;AAED;;;;;GAKG;AACH,2EAHW,iBAAiB,GACf,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiH5C;AAjMD;;;;;;GAMG;AACH,wCAFU,MAAM,EAAE,CAQhB;AAsLF;;;;;KAKK;AACL,yBAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEG"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;GAUG;AACH,mEALG;IAA0B,SAAS;IACT,KAAK;CAE/B,GAAU,OAAO,QAAQ,EAAE,MAAM,CAAC,SAAS,CA+B7C;AAED;;;;;GAKG;AACH,2EAHW,iBAAiB,GACf,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAmH5C;AArMD;;;;;;GAMG;AACH,wCAFU,MAAM,EAAE,CAQhB;AA0LF;;;;;KAKK;AACL,yBAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEqB"}
package/dist/base.js CHANGED
@@ -2,9 +2,11 @@ import eslint from "@eslint/js";
2
2
  import { defineConfig } from "eslint/config";
3
3
  import eslintConfigPrettier from "eslint-config-prettier";
4
4
  import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
5
- import turboPlugin from "eslint-plugin-turbo";
6
5
  import globals from "globals";
7
6
  import tseslint from "typescript-eslint";
7
+ import { lazyConfig } from "./internal/lazy-config.js";
8
+ import { requireOptional } from "./internal/optional-plugin.js";
9
+ import { isInsideTurborepo } from "./internal/turborepo.js";
8
10
  /**
9
11
  * Node.js builtins without the `node:` prefix, as the first
10
12
  * `simple-import-sort/imports` group. Exported so other variants (e.g.
@@ -29,9 +31,11 @@ export const NODE_BUILTIN_IMPORT_GROUP = [
29
31
  * `strictTypeChecked` + `stylisticTypeChecked` presets. Set to `false` to
30
32
  * fall back to the plain `recommended` preset instead (useful while
31
33
  * migrating an existing codebase that isn't ready for the strict rules).
32
- * @property {boolean} [turbo=true] - Enable eslint-plugin-turbo's
33
- * `no-undeclared-env-vars` rule. Only relevant inside a Turborepo; set to
34
- * `false` for a standalone project.
34
+ * @property {boolean} [turbo] - Enable eslint-plugin-turbo's
35
+ * `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
36
+ * Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
37
+ * sits in ESLint's working directory or one of its parents, `false`
38
+ * otherwise.
35
39
  * @property {boolean} [snakeCase=false] - Also accept `snake_case` variable
36
40
  * names in `@typescript-eslint/naming-convention` — for code that
37
41
  * destructures rows straight from a SQL database (e.g. Supabase), whose
@@ -81,7 +85,7 @@ export function createNamingConventionRule({ snakeCase = false, react = false, }
81
85
  * @param {BaseConfigOptions} [options]
82
86
  * @returns {import("eslint").Linter.Config[]}
83
87
  */
84
- export function createBaseConfig({ prettier = true, strict = true, turbo = true, snakeCase = false, } = {}) {
88
+ export function createBaseConfig({ prettier = true, strict = true, turbo = isInsideTurborepo(), snakeCase = false, } = {}) {
85
89
  return defineConfig([
86
90
  eslint.configs.recommended,
87
91
  strict ? tseslint.configs.strictTypeChecked : tseslint.configs.recommended,
@@ -152,7 +156,7 @@ export function createBaseConfig({ prettier = true, strict = true, turbo = true,
152
156
  turbo
153
157
  ? {
154
158
  plugins: {
155
- turbo: turboPlugin,
159
+ turbo: /** @type {typeof import("eslint-plugin-turbo").default} */ (requireOptional("eslint-plugin-turbo", "turbo")),
156
160
  },
157
161
  rules: {
158
162
  "turbo/no-undeclared-env-vars": "error",
@@ -194,4 +198,4 @@ export function createBaseConfig({ prettier = true, strict = true, turbo = true,
194
198
  *
195
199
  * @type {import("eslint").Linter.Config[]}
196
200
  * */
197
- export const baseConfig = createBaseConfig();
201
+ export const baseConfig = lazyConfig(() => createBaseConfig());
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Wraps a config factory call so it only runs the first time the resulting
3
+ * array is actually read (spread, iterated, indexed...).
4
+ *
5
+ * Every variant module exports a ready-made default (`reactConfig`,
6
+ * `nextJsConfig`...) next to its factory. Built eagerly, that default would
7
+ * load every optional plugin its default options enable (`a11y`, `turbo`) as
8
+ * soon as the module is imported — so a project calling
9
+ * `createReactNativeConfig({ turbo: false })` would still crash on a missing
10
+ * eslint-plugin-turbo, just because `reactConfig` was evaluated on the way.
11
+ *
12
+ * @param {() => import("eslint").Linter.Config[]} create
13
+ * @returns {import("eslint").Linter.Config[]}
14
+ */
15
+ export function lazyConfig(create: () => import("eslint").Linter.Config[]): import("eslint").Linter.Config[];
16
+ //# sourceMappingURL=lazy-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-config.d.ts","sourceRoot":"","sources":["../../src/internal/lazy-config.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,mCAHW,MAAM,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GACpC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAe5C"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Wraps a config factory call so it only runs the first time the resulting
3
+ * array is actually read (spread, iterated, indexed...).
4
+ *
5
+ * Every variant module exports a ready-made default (`reactConfig`,
6
+ * `nextJsConfig`...) next to its factory. Built eagerly, that default would
7
+ * load every optional plugin its default options enable (`a11y`, `turbo`) as
8
+ * soon as the module is imported — so a project calling
9
+ * `createReactNativeConfig({ turbo: false })` would still crash on a missing
10
+ * eslint-plugin-turbo, just because `reactConfig` was evaluated on the way.
11
+ *
12
+ * @param {() => import("eslint").Linter.Config[]} create
13
+ * @returns {import("eslint").Linter.Config[]}
14
+ */
15
+ export function lazyConfig(create) {
16
+ /** @type {import("eslint").Linter.Config[] | undefined} */
17
+ let config;
18
+ const resolve = () => (config ??= create());
19
+ // The target is a real (empty) array so `Array.isArray()` stays true —
20
+ // ESLint's `defineConfig()` relies on it to flatten nested configs.
21
+ return new Proxy(/** @type {import("eslint").Linter.Config[]} */ ([]), {
22
+ get: (_target, key) => Reflect.get(resolve(), key),
23
+ has: (_target, key) => Reflect.has(resolve(), key),
24
+ ownKeys: () => Reflect.ownKeys(resolve()),
25
+ getOwnPropertyDescriptor: (_target, key) => Reflect.getOwnPropertyDescriptor(resolve(), key),
26
+ });
27
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Loads an optional plugin, or throws an error explaining which package to
3
+ * install (or which option to turn off) when it isn't installed. Any other
4
+ * failure (a broken install, a missing transitive dependency of the plugin
5
+ * itself) is rethrown rather than hidden.
6
+ *
7
+ * Callers cast the result to the package's own type (its `import()`
8
+ * default export) to keep the plugin's type, which `require()` loses.
9
+ *
10
+ * @param {string} name - The package name.
11
+ * @param {string} option - The option that needs it, e.g. `a11y`.
12
+ * @param {(name: string) => unknown} [load] - Stands in for `require` in tests.
13
+ * @returns {unknown}
14
+ */
15
+ export function requireOptional(name: string, option: string, load?: (name: string) => unknown): unknown;
16
+ //# sourceMappingURL=optional-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optional-plugin.d.ts","sourceRoot":"","sources":["../../src/internal/optional-plugin.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;GAaG;AACH,sCALW,MAAM,UACN,MAAM,SACN,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACvB,OAAO,CAmBnB"}
@@ -0,0 +1,76 @@
1
+ import { createRequire } from "node:module";
2
+ /**
3
+ * Plugins that only some variants or options use are optional peer
4
+ * dependencies (see package.json's `peerDependenciesMeta`): a React Native app
5
+ * must not end up installing angular-eslint (and the whole Angular CLI it
6
+ * pulls in), nor a plain TypeScript library every React plugin. Plugins a
7
+ * variant always needs are imported statically by that variant's module;
8
+ * plugins behind an option (`a11y`, `expo`, `i18n`, `testingLibrary`,
9
+ * `turbo`) go through {@link requireOptional} instead, only when the option is
10
+ * on, so a project that leaves it off doesn't have to install them.
11
+ *
12
+ * Loaded with `require()` rather than `import()`: an `await import()` at the
13
+ * top of a variant module would make the whole package an async module, which
14
+ * Node refuses to `require()` (`ERR_REQUIRE_ASYNC_MODULE`) — and the CommonJS
15
+ * `eslint.config.js` generated by Expo's templates does exactly that.
16
+ */
17
+ const require = createRequire(import.meta.url);
18
+ /**
19
+ * Loads an optional plugin, or throws an error explaining which package to
20
+ * install (or which option to turn off) when it isn't installed. Any other
21
+ * failure (a broken install, a missing transitive dependency of the plugin
22
+ * itself) is rethrown rather than hidden.
23
+ *
24
+ * Callers cast the result to the package's own type (its `import()`
25
+ * default export) to keep the plugin's type, which `require()` loses.
26
+ *
27
+ * @param {string} name - The package name.
28
+ * @param {string} option - The option that needs it, e.g. `a11y`.
29
+ * @param {(name: string) => unknown} [load] - Stands in for `require` in tests.
30
+ * @returns {unknown}
31
+ */
32
+ export function requireOptional(name, option, load = require) {
33
+ /** @type {unknown} */
34
+ let loaded;
35
+ try {
36
+ loaded = load(name);
37
+ }
38
+ catch (error) {
39
+ if (isPackageNotFound(error, name)) {
40
+ throw new Error(`@forthtilliath/eslint-config: the \`${option}\` option needs ${name}, ` +
41
+ `which isn't installed. Install it as a devDependency ` +
42
+ `(npm install --save-dev ${name}), or pass \`${option}: false\`.`, { cause: error });
43
+ }
44
+ throw error;
45
+ }
46
+ return isModuleNamespace(loaded) ? loaded.default : loaded;
47
+ }
48
+ /**
49
+ * `require()` of an ES module returns its namespace object, where `import()`'s
50
+ * `.default` is the plugin; a CommonJS module is returned as `module.exports`,
51
+ * which is already what `import()` puts in `.default`.
52
+ *
53
+ * @param {unknown} value
54
+ * @returns {value is { default: unknown }}
55
+ */
56
+ function isModuleNamespace(value) {
57
+ return (typeof value === "object" &&
58
+ value !== null &&
59
+ Object.prototype.toString.call(value) === "[object Module]");
60
+ }
61
+ /**
62
+ * @param {unknown} error
63
+ * @param {string} name
64
+ * @returns {boolean}
65
+ */
66
+ function isPackageNotFound(error, name) {
67
+ return (error instanceof Error &&
68
+ "code" in error &&
69
+ (error.code === "MODULE_NOT_FOUND" ||
70
+ error.code === "ERR_MODULE_NOT_FOUND") &&
71
+ // Node names the package it couldn't find on the first line (the
72
+ // require stack follows): checking it tells "the plugin isn't installed"
73
+ // apart from "the plugin is installed but one of its own dependencies is
74
+ // missing", which must not be swallowed.
75
+ (error.message.split("\n")[0] ?? "").includes(`'${name}'`));
76
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Whether `dir` or one of its parents holds a `turbo.json` — i.e. ESLint runs
3
+ * inside a Turborepo, where eslint-plugin-turbo's rule makes sense. Used as
4
+ * the default of the `turbo` option, so a standalone app doesn't have to pass
5
+ * `turbo: false` (nor install the plugin) to load the config.
6
+ *
7
+ * @param {string} [dir] - Where to start looking; ESLint's working directory
8
+ * by default.
9
+ * @returns {boolean}
10
+ */
11
+ export function isInsideTurborepo(dir?: string): boolean;
12
+ //# sourceMappingURL=turborepo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turborepo.d.ts","sourceRoot":"","sources":["../../src/internal/turborepo.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,wCAJW,MAAM,GAEJ,OAAO,CAOnB"}
@@ -0,0 +1,20 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ /**
4
+ * Whether `dir` or one of its parents holds a `turbo.json` — i.e. ESLint runs
5
+ * inside a Turborepo, where eslint-plugin-turbo's rule makes sense. Used as
6
+ * the default of the `turbo` option, so a standalone app doesn't have to pass
7
+ * `turbo: false` (nor install the plugin) to load the config.
8
+ *
9
+ * @param {string} [dir] - Where to start looking; ESLint's working directory
10
+ * by default.
11
+ * @returns {boolean}
12
+ */
13
+ export function isInsideTurborepo(dir = process.cwd()) {
14
+ for (let current = dir;; current = dirname(current)) {
15
+ if (existsSync(join(current, "turbo.json")))
16
+ return true;
17
+ if (dirname(current) === current)
18
+ return false;
19
+ }
20
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.js"],"names":[],"mappings":"AAKA;;;;;;;KAOK;AACL,6CAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAsD5C;AAED;;;;;KAKK;AACL,2BAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEO"}
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.js"],"names":[],"mappings":"AAMA;;;;;;;KAOK;AACL,6CAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAsD5C;AAED;;;;;KAKK;AACL,2BAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEyB"}
package/dist/nextjs.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import pluginNext from "@next/eslint-plugin-next";
2
2
  import { defineConfig } from "eslint/config";
3
+ import { lazyConfig } from "./internal/lazy-config.js";
3
4
  import { createReactConfig } from "./react.js";
4
5
  /**
5
6
  * A custom ESLint configuration for Next.js applications, built on top of
@@ -68,4 +69,4 @@ export function createNextJsConfig(options) {
68
69
  *
69
70
  * @type {import("eslint").Linter.Config[]}
70
71
  * */
71
- export const nextJsConfig = createNextJsConfig();
72
+ export const nextJsConfig = lazyConfig(() => createNextJsConfig());
@@ -4,12 +4,20 @@
4
4
  * hand here instead of spreading a ready-made flat config like storybook.js
5
5
  * does.
6
6
  */
7
+ /**
8
+ * @typedef {object} ReactNativeConfigOptions
9
+ * @property {boolean} [expo=false] - Enable the eslint-plugin-expo rules that
10
+ * eslint-config-expo turns on (`use-dom-exports`,
11
+ * `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
12
+ * migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
13
+ * installed).
14
+ */
7
15
  /**
8
16
  * A custom ESLint configuration for React Native / Expo apps.
9
17
  *
10
- * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
18
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions} [options]
11
19
  * @returns {import("eslint").Linter.Config[]} */
12
- export function createReactNativeConfig({ a11y, ...options }?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions): import("eslint").Linter.Config[];
20
+ export function createReactNativeConfig({ a11y, expo, ...options }?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions): import("eslint").Linter.Config[];
13
21
  /**
14
22
  * A custom ESLint configuration for React Native / Expo apps, with the
15
23
  * default options. Use `createReactNativeConfig(options)` instead to
@@ -17,4 +25,14 @@ export function createReactNativeConfig({ a11y, ...options }?: import("./base.js
17
25
  *
18
26
  * @type {import("eslint").Linter.Config[]} */
19
27
  export const reactNativeConfig: import("eslint").Linter.Config[];
28
+ export type ReactNativeConfigOptions = {
29
+ /**
30
+ * - Enable the eslint-plugin-expo rules that
31
+ * eslint-config-expo turns on (`use-dom-exports`,
32
+ * `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
33
+ * migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
34
+ * installed).
35
+ */
36
+ expo?: boolean | undefined;
37
+ };
20
38
  //# sourceMappingURL=react-native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../src/react-native.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH;;;;iDAIiD;AACjD,+DAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAwC5C;AAED;;;;;8CAK8C;AAC9C,gCADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACiB"}
1
+ {"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../src/react-native.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH;;;;iDAIiD;AACjD,qEAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAAG,wBAAwB,GACxG,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA6D5C;AAED;;;;;8CAK8C;AAC9C,gCADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACmC"}
@@ -1,5 +1,7 @@
1
1
  import { defineConfig } from "eslint/config";
2
2
  import pluginReactNative from "eslint-plugin-react-native";
3
+ import { lazyConfig } from "./internal/lazy-config.js";
4
+ import { requireOptional } from "./internal/optional-plugin.js";
3
5
  import { createReactConfig } from "./react.js";
4
6
  /**
5
7
  * eslint-plugin-react-native only ships a legacy (eslintrc) `configs.all`
@@ -7,14 +9,37 @@ import { createReactConfig } from "./react.js";
7
9
  * hand here instead of spreading a ready-made flat config like storybook.js
8
10
  * does.
9
11
  */
12
+ /**
13
+ * @typedef {object} ReactNativeConfigOptions
14
+ * @property {boolean} [expo=false] - Enable the eslint-plugin-expo rules that
15
+ * eslint-config-expo turns on (`use-dom-exports`,
16
+ * `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
17
+ * migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
18
+ * installed).
19
+ */
10
20
  /**
11
21
  * A custom ESLint configuration for React Native / Expo apps.
12
22
  *
13
- * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
23
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions} [options]
14
24
  * @returns {import("eslint").Linter.Config[]} */
15
- export function createReactNativeConfig({ a11y = false, ...options } = {}) {
25
+ export function createReactNativeConfig({ a11y = false, expo = false, ...options } = {}) {
16
26
  return defineConfig([
17
27
  ...createReactConfig({ a11y, ...options }),
28
+ expo
29
+ ? {
30
+ plugins: {
31
+ expo: /** @type {typeof import("eslint-plugin-expo")} */ (requireOptional("eslint-plugin-expo", "expo")),
32
+ },
33
+ rules: {
34
+ // Every `EXPO_PUBLIC_*` variable is inlined at build time by
35
+ // reading `process.env.EXPO_PUBLIC_X` literally: a destructured or
36
+ // computed access stays `undefined` in the bundle.
37
+ "expo/no-env-var-destructuring": "error",
38
+ "expo/no-dynamic-env-var": "error",
39
+ "expo/use-dom-exports": "error",
40
+ },
41
+ }
42
+ : [],
18
43
  {
19
44
  languageOptions: {
20
45
  globals: {
@@ -58,4 +83,4 @@ export function createReactNativeConfig({ a11y = false, ...options } = {}) {
58
83
  * customize it.
59
84
  *
60
85
  * @type {import("eslint").Linter.Config[]} */
61
- export const reactNativeConfig = createReactNativeConfig();
86
+ export const reactNativeConfig = lazyConfig(() => createReactNativeConfig());
package/dist/react.d.ts CHANGED
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * @typedef {object} ReactConfigOptions
3
3
  * @property {boolean} [a11y=true] - Enable eslint-plugin-jsx-a11y's
4
- * recommended rules.
4
+ * recommended rules (needs `eslint-plugin-jsx-a11y` installed).
5
5
  * @property {boolean} [i18n=false] - Enable eslint-plugin-i18next's
6
- * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
7
- * makes sense for a project fully committed to i18n (e.g. driven by
8
- * next-intl) — off by default since it flags every literal otherwise.
6
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX (needs
7
+ * `eslint-plugin-i18next` installed). Only makes sense for a project fully
8
+ * committed to i18n (e.g. driven by next-intl) — off by default since it
9
+ * flags every literal otherwise.
9
10
  * @property {boolean} [testingLibrary=false] - Enable
10
11
  * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
11
- * recommended rules, scoped to test files only. Off by default:
12
+ * recommended rules, scoped to test files only (needs both installed). Off
13
+ * by default:
12
14
  * `testing-library/no-manual-cleanup` in particular assumes
13
15
  * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
14
16
  * only happens when `afterEach` is a real global (vitest's `test.globals:
@@ -31,20 +33,22 @@ export const reactConfig: import("eslint").Linter.Config[];
31
33
  export type ReactConfigOptions = {
32
34
  /**
33
35
  * - Enable eslint-plugin-jsx-a11y's
34
- * recommended rules.
36
+ * recommended rules (needs `eslint-plugin-jsx-a11y` installed).
35
37
  */
36
38
  a11y?: boolean | undefined;
37
39
  /**
38
40
  * - Enable eslint-plugin-i18next's
39
- * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
40
- * makes sense for a project fully committed to i18n (e.g. driven by
41
- * next-intl) — off by default since it flags every literal otherwise.
41
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX (needs
42
+ * `eslint-plugin-i18next` installed). Only makes sense for a project fully
43
+ * committed to i18n (e.g. driven by next-intl) — off by default since it
44
+ * flags every literal otherwise.
42
45
  */
43
46
  i18n?: boolean | undefined;
44
47
  /**
45
48
  * - Enable
46
49
  * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
47
- * recommended rules, scoped to test files only. Off by default:
50
+ * recommended rules, scoped to test files only (needs both installed). Off
51
+ * by default:
48
52
  * `testing-library/no-manual-cleanup` in particular assumes
49
53
  * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
50
54
  * only happens when `afterEach` is a real global (vitest's `test.globals:
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;GAKG;AACH,0FAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,kBAAkB,GACxD,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAwF5C;AAED;;;;8CAI8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACK"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH,0FAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,kBAAkB,GACxD,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAqG5C;AAED;;;;8CAI8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACuB"}
package/dist/react.js CHANGED
@@ -1,13 +1,11 @@
1
1
  import pluginEslintReact from "@eslint-react/eslint-plugin";
2
2
  import { defineConfig } from "eslint/config";
3
- import pluginI18next from "eslint-plugin-i18next";
4
- import pluginJestDom from "eslint-plugin-jest-dom";
5
- import pluginJsxA11y from "eslint-plugin-jsx-a11y";
6
3
  import pluginReact from "eslint-plugin-react";
7
4
  import pluginReactHooks from "eslint-plugin-react-hooks";
8
5
  import pluginReactRefresh from "eslint-plugin-react-refresh";
9
- import pluginTestingLibrary from "eslint-plugin-testing-library";
10
6
  import globals from "globals";
7
+ import { lazyConfig } from "./internal/lazy-config.js";
8
+ import { requireOptional } from "./internal/optional-plugin.js";
11
9
  import { createBaseConfig, createNamingConventionRule } from "./base.js";
12
10
  /**
13
11
  * Any file matching this is a test file, whatever runner or naming
@@ -36,14 +34,16 @@ const RULES_COVERED_BY_ESLINT_REACT = [
36
34
  /**
37
35
  * @typedef {object} ReactConfigOptions
38
36
  * @property {boolean} [a11y=true] - Enable eslint-plugin-jsx-a11y's
39
- * recommended rules.
37
+ * recommended rules (needs `eslint-plugin-jsx-a11y` installed).
40
38
  * @property {boolean} [i18n=false] - Enable eslint-plugin-i18next's
41
- * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
42
- * makes sense for a project fully committed to i18n (e.g. driven by
43
- * next-intl) — off by default since it flags every literal otherwise.
39
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX (needs
40
+ * `eslint-plugin-i18next` installed). Only makes sense for a project fully
41
+ * committed to i18n (e.g. driven by next-intl) — off by default since it
42
+ * flags every literal otherwise.
44
43
  * @property {boolean} [testingLibrary=false] - Enable
45
44
  * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
46
- * recommended rules, scoped to test files only. Off by default:
45
+ * recommended rules, scoped to test files only (needs both installed). Off
46
+ * by default:
47
47
  * `testing-library/no-manual-cleanup` in particular assumes
48
48
  * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
49
49
  * only happens when `afterEach` is a real global (vitest's `test.globals:
@@ -59,21 +59,26 @@ const RULES_COVERED_BY_ESLINT_REACT = [
59
59
  export function createReactConfig({ a11y = true, i18n = false, testingLibrary = false, snakeCase = false, ...options } = {}) {
60
60
  return defineConfig([
61
61
  ...createBaseConfig({ snakeCase, ...options }),
62
- a11y ? pluginJsxA11y.flatConfigs.recommended : [],
62
+ a11y
63
+ ? /** @type {typeof import("eslint-plugin-jsx-a11y").default} */ (requireOptional("eslint-plugin-jsx-a11y", "a11y")).flatConfigs.recommended
64
+ : [],
63
65
  i18n
64
66
  ? {
65
- plugins: { i18next: pluginI18next },
67
+ plugins: {
68
+ i18next:
69
+ /** @type {typeof import("eslint-plugin-i18next").default} */ (requireOptional("eslint-plugin-i18next", "i18n")),
70
+ },
66
71
  rules: { "i18next/no-literal-string": "error" },
67
72
  }
68
73
  : [],
69
74
  testingLibrary
70
75
  ? [
71
76
  {
72
- ...pluginTestingLibrary.configs["flat/react"],
77
+ ... /** @type {typeof import("eslint-plugin-testing-library").default} */(requireOptional("eslint-plugin-testing-library", "testingLibrary")).configs["flat/react"],
73
78
  files: TEST_FILE_GLOBS,
74
79
  },
75
80
  {
76
- ...pluginJestDom.configs["flat/recommended"],
81
+ ... /** @type {typeof import("eslint-plugin-jest-dom").default} */(requireOptional("eslint-plugin-jest-dom", "testingLibrary")).configs["flat/recommended"],
77
82
  files: TEST_FILE_GLOBS,
78
83
  },
79
84
  ]
@@ -140,4 +145,4 @@ export function createReactConfig({ a11y = true, i18n = false, testingLibrary =
140
145
  * default options. Use `createReactConfig(options)` instead to customize it.
141
146
  *
142
147
  * @type {import("eslint").Linter.Config[]} */
143
- export const reactConfig = createReactConfig();
148
+ export const reactConfig = lazyConfig(() => createReactConfig());
@@ -1 +1 @@
1
- {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.js"],"names":[],"mappings":"AAKA;;;;iDAIiD;AACjD,gDAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiB5C;AAED;;;;;8CAK8C;AAC9C,8BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACa"}
1
+ {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.js"],"names":[],"mappings":"AAMA;;;;iDAIiD;AACjD,gDAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiB5C;AAED;;;;;8CAK8C;AAC9C,8BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAC+B"}
package/dist/storybook.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineConfig } from "eslint/config";
2
2
  import storybook from "eslint-plugin-storybook";
3
+ import { lazyConfig } from "./internal/lazy-config.js";
3
4
  import { createReactConfig } from "./react.js";
4
5
  /**
5
6
  * A custom ESLint configuration for libraries that use React and Storybook.
@@ -29,4 +30,4 @@ export function createStorybookConfig(options) {
29
30
  * customize it.
30
31
  *
31
32
  * @type {import("eslint").Linter.Config[]} */
32
- export const storybookConfig = createStorybookConfig();
33
+ export const storybookConfig = lazyConfig(() => createStorybookConfig());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/eslint-config",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -18,14 +18,22 @@
18
18
  ],
19
19
  "exports": {
20
20
  ".": "./dist/base.js",
21
+ "./internal/*": null,
21
22
  "./*": "./dist/*.js"
22
23
  },
23
24
  "dependencies": {
24
- "@eslint-react/eslint-plugin": "^5.18.3",
25
25
  "@eslint/js": "^9.39.5",
26
+ "eslint-config-prettier": "^10.1.8",
27
+ "eslint-plugin-simple-import-sort": "^13.0.0",
28
+ "globals": "^17.9.0",
29
+ "typescript-eslint": "^8.67.0"
30
+ },
31
+ "peerDependencies": {
32
+ "@eslint-react/eslint-plugin": "^5.18.3",
26
33
  "@next/eslint-plugin-next": "^16.3.0",
27
34
  "angular-eslint": "^21.4.0",
28
- "eslint-config-prettier": "^10.1.8",
35
+ "eslint": "^9.0.0 || ^10.0.0",
36
+ "eslint-plugin-expo": "^1.2.1",
29
37
  "eslint-plugin-i18next": "^6.1.5",
30
38
  "eslint-plugin-jest-dom": "^5.10.1",
31
39
  "eslint-plugin-jsx-a11y": "^6.10.2",
@@ -33,23 +41,75 @@
33
41
  "eslint-plugin-react-hooks": "^7.1.1",
34
42
  "eslint-plugin-react-native": "^5.0.0",
35
43
  "eslint-plugin-react-refresh": "^0.5.4",
36
- "eslint-plugin-simple-import-sort": "^13.0.0",
37
44
  "eslint-plugin-storybook": "^10.6.1",
38
45
  "eslint-plugin-testing-library": "^7.16.2",
39
46
  "eslint-plugin-turbo": "^2.10.9",
40
- "globals": "^17.9.0",
41
- "typescript-eslint": "^8.67.0"
42
- },
43
- "peerDependencies": {
44
- "eslint": "^9.0.0 || ^10.0.0",
45
47
  "typescript": ">=5.0.0"
46
48
  },
49
+ "peerDependenciesMeta": {
50
+ "@eslint-react/eslint-plugin": {
51
+ "optional": true
52
+ },
53
+ "@next/eslint-plugin-next": {
54
+ "optional": true
55
+ },
56
+ "angular-eslint": {
57
+ "optional": true
58
+ },
59
+ "eslint-plugin-expo": {
60
+ "optional": true
61
+ },
62
+ "eslint-plugin-i18next": {
63
+ "optional": true
64
+ },
65
+ "eslint-plugin-jest-dom": {
66
+ "optional": true
67
+ },
68
+ "eslint-plugin-jsx-a11y": {
69
+ "optional": true
70
+ },
71
+ "eslint-plugin-react": {
72
+ "optional": true
73
+ },
74
+ "eslint-plugin-react-hooks": {
75
+ "optional": true
76
+ },
77
+ "eslint-plugin-react-native": {
78
+ "optional": true
79
+ },
80
+ "eslint-plugin-react-refresh": {
81
+ "optional": true
82
+ },
83
+ "eslint-plugin-storybook": {
84
+ "optional": true
85
+ },
86
+ "eslint-plugin-testing-library": {
87
+ "optional": true
88
+ },
89
+ "eslint-plugin-turbo": {
90
+ "optional": true
91
+ }
92
+ },
47
93
  "devDependencies": {
48
- "@angular/core": "^21.0.0",
94
+ "@angular/core": "^21.2.25",
95
+ "@eslint-react/eslint-plugin": "^5.18.3",
96
+ "@next/eslint-plugin-next": "^16.3.0",
97
+ "angular-eslint": "^21.4.0",
49
98
  "eslint": "10.7.0",
99
+ "eslint-plugin-expo": "^1.2.1",
100
+ "eslint-plugin-i18next": "^6.1.5",
101
+ "eslint-plugin-jest-dom": "^5.10.1",
102
+ "eslint-plugin-jsx-a11y": "^6.10.2",
103
+ "eslint-plugin-react": "^7.37.5",
104
+ "eslint-plugin-react-hooks": "^7.1.1",
105
+ "eslint-plugin-react-native": "^5.0.0",
106
+ "eslint-plugin-react-refresh": "^0.5.4",
107
+ "eslint-plugin-storybook": "^10.6.1",
108
+ "eslint-plugin-testing-library": "^7.16.2",
109
+ "eslint-plugin-turbo": "^2.10.9",
50
110
  "rxjs": "^7.8.0",
51
111
  "typescript": "^5.9.3",
52
- "vitest": "^5.0.2",
112
+ "vitest": "^5.0.5",
53
113
  "@forthtilliath/typescript-config": "0.1.1"
54
114
  },
55
115
  "scripts": {