exadev-eslint-config 2.16.1 → 2.17.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
@@ -160,6 +160,18 @@ export default tseslint.config(
160
160
 
161
161
  Trailing arguments are arbitrary flat-config objects, appended in order after everything else -- `exadevConfig({}, { rules: { 'no-console': 'warn' } })` is equivalent to spreading the default export plus one more config object.
162
162
 
163
+ ## Gitignore-derived ignores
164
+
165
+ `exadevConfig()`'s default output includes an `ignores` block derived directly from your project's own `.gitignore` (via [`@eslint/config-helpers`](https://www.npmjs.com/package/@eslint/config-helpers)'s `includeIgnoreFile`), so a generated directory your `.gitignore` already knows about (`dist/`, `coverage/`, a tool's own report output) is never linted, without hand-duplicating that list in `eslint.config.ts` too. This closes a real gap: a `.gitignore`d directory that nothing previously linted broadly enough to reach could still get linted the moment a wide-reaching rule (this package's own bundled RFC 8785 JSON canonicalization, say) started matching every file its glob covers.
166
+
167
+ | Value | `options.gitignore` |
168
+ | --- | --- |
169
+ | `true` | Force on -- throws if no `.gitignore` exists |
170
+ | `false` | Force off -- always `[]`, no resolution attempted |
171
+ | `undefined` / omitted | Auto-detect (the default): on if the project has a `.gitignore`, silently off if it doesn't (nothing to read from a project with no version control set up yet) |
172
+
173
+ Needs no peer to install -- `@eslint/config-helpers` is bundled into this package's own build.
174
+
163
175
  ## RFC 8785 canonical JSON formatting
164
176
 
165
177
  Every JSON file is linted against [`eslint-plugin-json-canonical`](https://github.com/ExaDev/eslint-plugin-json-canonical) v2 -- plain UTF-16 code-unit key ordering, canonical number formatting, canonical string escaping, and (as of that plugin's own v2) pretty-printed layout (2-space indentation, one member/element per line, a trailing newline), per [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785). This is bundled unconditionally, the same way jsdoc/tsdoc support is: `eslint-plugin-json-canonical` is a plain dependency of this package, so every consumer already has it. There is no option to turn it off.
package/dist/index.cjs CHANGED
@@ -24,6 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
+ let node_fs = require("node:fs");
28
+ node_fs = __toESM(node_fs, 1);
29
+ let node_path = require("node:path");
30
+ node_path = __toESM(node_path, 1);
27
31
  let eslint_plugin_jsdoc = require("eslint-plugin-jsdoc");
28
32
  eslint_plugin_jsdoc = __toESM(eslint_plugin_jsdoc, 1);
29
33
  let eslint_plugin_tsdoc = require("eslint-plugin-tsdoc");
@@ -31,8 +35,6 @@ eslint_plugin_tsdoc = __toESM(eslint_plugin_tsdoc, 1);
31
35
  let eslint_plugin_json_canonical = require("eslint-plugin-json-canonical");
32
36
  eslint_plugin_json_canonical = __toESM(eslint_plugin_json_canonical, 1);
33
37
  let node_module = require("node:module");
34
- let node_fs = require("node:fs");
35
- let node_path = require("node:path");
36
38
  let _typescript_eslint_utils = require("@typescript-eslint/utils");
37
39
  let typescript = require("typescript");
38
40
  typescript = __toESM(typescript, 1);
@@ -41,6 +43,143 @@ let _eslint_js = require("@eslint/js");
41
43
  _eslint_js = __toESM(_eslint_js, 1);
42
44
  let typescript_eslint = require("typescript-eslint");
43
45
  typescript_eslint = __toESM(typescript_eslint, 1);
46
+ //#region node_modules/.pnpm/@eslint+config-helpers@0.7.0/node_modules/@eslint/config-helpers/dist/esm/index.js
47
+ /**
48
+ * @fileoverview Ignore file utilities for the config-helpers package.
49
+ * This file was forked from the source code for the compat package.
50
+ *
51
+ * @author Nicholas C. Zakas
52
+ * @author Kirk Waiblinger
53
+ */
54
+ /**
55
+ * @typedef {object} IncludeIgnoreFileOptionsObject
56
+ * @property {boolean} [gitignoreResolution] Whether to interpret the contents of an ignore file relative to the config file or the ignore file.
57
+ * - gitignoreResolution: false (default): Interprets ignore patterns relative to the config file
58
+ * - gitignoreResolution: true: Interprets the ignore patterns in a file relative to the ignore file
59
+ * @property {string} [name] The name to give the output config object(s).
60
+ */
61
+ /**
62
+ * Options for `includeIgnoreFile()`. May be provided as an object or, for
63
+ * legacy compatibility with `@eslint/compat`, as a string which is treated as
64
+ * the `name` option.
65
+ * @typedef {IncludeIgnoreFileOptionsObject | string} IncludeIgnoreFileOptions
66
+ */
67
+ /**
68
+ * Converts an ESLint ignore pattern to a minimatch pattern.
69
+ * @param {string} pattern The .eslintignore or .gitignore pattern to convert.
70
+ * @returns {string} The converted pattern.
71
+ */
72
+ function convertIgnorePatternToMinimatch(pattern) {
73
+ const isNegated = pattern.startsWith("!");
74
+ const negatedPrefix = isNegated ? "!" : "";
75
+ const patternToTest = (isNegated ? pattern.slice(1) : pattern).trimEnd();
76
+ if ([
77
+ "",
78
+ "**",
79
+ "/**",
80
+ "**/"
81
+ ].includes(patternToTest)) return `${negatedPrefix}${patternToTest}`;
82
+ const firstIndexOfSlash = patternToTest.indexOf("/");
83
+ return `${negatedPrefix}${firstIndexOfSlash < 0 || firstIndexOfSlash === patternToTest.length - 1 ? "**/" : ""}${(firstIndexOfSlash === 0 ? patternToTest.slice(1) : patternToTest).replaceAll(/(?=((?:\\.|[^{(])*))\1([{(])/guy, "$1\\$2")}${patternToTest.endsWith("/**") ? "/*" : ""}`;
84
+ }
85
+ /**
86
+ * @param {string} ignoreFilePath
87
+ * @returns {string[]}
88
+ */
89
+ function ignoreFilePathToPatterns(ignoreFilePath) {
90
+ return node_fs.default.readFileSync(ignoreFilePath, "utf8").split(/\r?\n/u).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map(convertIgnorePatternToMinimatch);
91
+ }
92
+ /**
93
+ * Helper to parse and validate the options to `includeIgnoreFile()`
94
+ *
95
+ * @param {string | { gitignoreResolution?: unknown, name?: unknown } | undefined} options
96
+ * @returns {{ gitignoreResolution: boolean, name: string }}
97
+ */
98
+ function parseOptions(options) {
99
+ if (typeof options === "string") return {
100
+ gitignoreResolution: false,
101
+ name: options
102
+ };
103
+ const optionsObject = options ?? {};
104
+ if (typeof optionsObject !== "object" || Array.isArray(optionsObject)) throw new TypeError("The options argument to `includeIgnoreFile()` should be an object or a string.");
105
+ const gitignoreResolution = optionsObject.gitignoreResolution ?? false;
106
+ if (typeof gitignoreResolution !== "boolean") throw new TypeError("The `gitignoreResolution` option must be specified a boolean or omitted");
107
+ const name = optionsObject.name ?? `Imported .gitignore patterns`;
108
+ if (typeof name !== "string") throw new TypeError("The `name` option must be specified as a string or omitted.");
109
+ return {
110
+ gitignoreResolution,
111
+ name
112
+ };
113
+ }
114
+ /**
115
+ * @overload
116
+ *
117
+ * Reads ignore files and returns objects with the ignore patterns.
118
+ *
119
+ * @param {string[]} ignoreFilePathArg The paths of ignore files to include.
120
+ * @param {IncludeIgnoreFileOptions} [options]
121
+ * @returns {ConfigObject[]}
122
+ */
123
+ /**
124
+ * @overload
125
+ *
126
+ * Reads an ignore file and returns an object with the ignore patterns.
127
+ *
128
+ * @param {string} ignoreFilePathArg The path of the ignore file to include.
129
+ * @param {IncludeIgnoreFileOptions} [options]
130
+ * @returns {ConfigObject}
131
+ */
132
+ /**
133
+ * @overload
134
+ *
135
+ * Reads an ignore file(s) and returns an object(s) with the ignore patterns.
136
+ *
137
+ * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include.
138
+ * @param {IncludeIgnoreFileOptions} [options]
139
+ * @returns {ConfigObject[] | ConfigObject}
140
+ */
141
+ /**
142
+ * Reads an ignore file(s) and returns an object(s) with the ignore patterns.
143
+ *
144
+ * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include.
145
+ * @param {IncludeIgnoreFileOptions} [options]
146
+ * @returns {ConfigObject[] | ConfigObject}
147
+ */
148
+ function includeIgnoreFile(ignoreFilePathArg, options) {
149
+ const returnSingleObject = !Array.isArray(ignoreFilePathArg);
150
+ const ignoreFilePaths = Array.isArray(ignoreFilePathArg) ? ignoreFilePathArg : [ignoreFilePathArg];
151
+ for (const ignorePath of ignoreFilePaths) {
152
+ if (typeof ignorePath !== "string") throw new TypeError("The first argument to `includeIgnoreFile()` should be a string or array of strings");
153
+ if (!node_path.default.isAbsolute(ignorePath)) throw new Error(`The ignore file location must be an absolute path. Received ${ignorePath}`);
154
+ }
155
+ const { gitignoreResolution, name } = parseOptions(options);
156
+ if (returnSingleObject) return {
157
+ name,
158
+ ignores: ignoreFilePathToPatterns(ignoreFilePathArg),
159
+ ...gitignoreResolution ? { basePath: node_path.default.dirname(ignoreFilePathArg) } : {}
160
+ };
161
+ return ignoreFilePaths.map((ignoreFilePath, i) => ({
162
+ name: `${name} (${i})`,
163
+ ignores: ignoreFilePathToPatterns(ignoreFilePath),
164
+ ...gitignoreResolution ? { basePath: node_path.default.dirname(ignoreFilePath) } : {}
165
+ }));
166
+ }
167
+ //#endregion
168
+ //#region src/gitignore.ts
169
+ /**
170
+ * A generated directory that's `.gitignore`d but not in this array's own `ignores` block was, until this feature existed, a real, confirmed gap: harmless while nothing linted broadly enough to reach it, then a real problem the moment a wide-reaching rule (this package's own bundled RFC 8785 JSON canonicalization, say) started matching every file its own glob covers, including a leftover build/report artifact nobody meant to lint at all. Deriving `ignores` from `.gitignore` itself, rather than a hand-maintained list every consumer would otherwise have to keep in sync by hand, means a project's own existing single source of truth for "what isn't source" is the one ESLint uses too.
171
+ */
172
+ function buildGitignoreConfig(options = {}) {
173
+ const cwd = options.cwd ?? process.cwd();
174
+ if (options.enabled === false) return [];
175
+ const gitignorePath = (0, node_path.join)(cwd, ".gitignore");
176
+ if (!(0, node_fs.existsSync)(gitignorePath)) {
177
+ if (options.enabled === true) throw new Error(`@exadev/eslint-config: gitignore-based ignores were explicitly requested but no .gitignore was found at ${gitignorePath}`);
178
+ return [];
179
+ }
180
+ return [includeIgnoreFile(gitignorePath)];
181
+ }
182
+ //#endregion
44
183
  //#region src/jsdoc.ts
45
184
  const JS_TS_FILE_PATTERNS$1 = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
46
185
  const jsdocConfig = eslint_plugin_jsdoc.default.configs["flat/recommended-tsdoc-error"];
@@ -160,7 +299,7 @@ function buildNextjsConfig(options = {}) {
160
299
  }
161
300
  //#endregion
162
301
  //#region package.json
163
- var version = "2.16.1";
302
+ var version = "2.17.0";
164
303
  //#endregion
165
304
  //#region src/react.ts
166
305
  const JSX_FILE_PATTERNS = ["**/*.jsx", "**/*.tsx"];
@@ -1872,6 +2011,7 @@ function toPublicConfigArray(built) {
1872
2011
  //#region src/create-config.ts
1873
2012
  function exadevConfig(options = {}, ...userConfigs) {
1874
2013
  return toPublicConfigArray([
2014
+ ...buildGitignoreConfig({ enabled: options.gitignore }),
1875
2015
  ...recommendedTypeChecked,
1876
2016
  ...jsdocAndTsdoc,
1877
2017
  ...jsonCanonicalConfig,
package/dist/index.d.cts CHANGED
@@ -8,6 +8,7 @@ interface ExadevConfigOptions {
8
8
  readonly react?: boolean;
9
9
  readonly nextjs?: boolean;
10
10
  readonly packageJsonKeyOrder?: boolean;
11
+ readonly gitignore?: boolean;
11
12
  }
12
13
  declare function exadevConfig(options?: ExadevConfigOptions, ...userConfigs: readonly TSESLint.FlatConfig.Config[]): PublicConfigArray;
13
14
  declare const defaultConfig: PublicConfigArray;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ interface ExadevConfigOptions {
8
8
  readonly react?: boolean;
9
9
  readonly nextjs?: boolean;
10
10
  readonly packageJsonKeyOrder?: boolean;
11
+ readonly gitignore?: boolean;
11
12
  }
12
13
  declare function exadevConfig(options?: ExadevConfigOptions, ...userConfigs: readonly TSESLint.FlatConfig.Config[]): PublicConfigArray;
13
14
  declare const defaultConfig: PublicConfigArray;
package/dist/index.js CHANGED
@@ -1,14 +1,151 @@
1
+ import fs, { existsSync, readFileSync } from "node:fs";
2
+ import path, { join, posix } from "node:path";
1
3
  import jsdoc from "eslint-plugin-jsdoc";
2
4
  import tsdoc from "eslint-plugin-tsdoc";
3
5
  import jsonCanonical from "eslint-plugin-json-canonical";
4
6
  import { createRequire } from "node:module";
5
- import { existsSync, readFileSync } from "node:fs";
6
- import { join, posix } from "node:path";
7
7
  import { AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
8
8
  import * as ts from "typescript";
9
9
  import { isPropertyReadonlyInType, isTypeReference } from "ts-api-utils";
10
10
  import js from "@eslint/js";
11
11
  import tseslint from "typescript-eslint";
12
+ //#region node_modules/.pnpm/@eslint+config-helpers@0.7.0/node_modules/@eslint/config-helpers/dist/esm/index.js
13
+ /**
14
+ * @fileoverview Ignore file utilities for the config-helpers package.
15
+ * This file was forked from the source code for the compat package.
16
+ *
17
+ * @author Nicholas C. Zakas
18
+ * @author Kirk Waiblinger
19
+ */
20
+ /**
21
+ * @typedef {object} IncludeIgnoreFileOptionsObject
22
+ * @property {boolean} [gitignoreResolution] Whether to interpret the contents of an ignore file relative to the config file or the ignore file.
23
+ * - gitignoreResolution: false (default): Interprets ignore patterns relative to the config file
24
+ * - gitignoreResolution: true: Interprets the ignore patterns in a file relative to the ignore file
25
+ * @property {string} [name] The name to give the output config object(s).
26
+ */
27
+ /**
28
+ * Options for `includeIgnoreFile()`. May be provided as an object or, for
29
+ * legacy compatibility with `@eslint/compat`, as a string which is treated as
30
+ * the `name` option.
31
+ * @typedef {IncludeIgnoreFileOptionsObject | string} IncludeIgnoreFileOptions
32
+ */
33
+ /**
34
+ * Converts an ESLint ignore pattern to a minimatch pattern.
35
+ * @param {string} pattern The .eslintignore or .gitignore pattern to convert.
36
+ * @returns {string} The converted pattern.
37
+ */
38
+ function convertIgnorePatternToMinimatch(pattern) {
39
+ const isNegated = pattern.startsWith("!");
40
+ const negatedPrefix = isNegated ? "!" : "";
41
+ const patternToTest = (isNegated ? pattern.slice(1) : pattern).trimEnd();
42
+ if ([
43
+ "",
44
+ "**",
45
+ "/**",
46
+ "**/"
47
+ ].includes(patternToTest)) return `${negatedPrefix}${patternToTest}`;
48
+ const firstIndexOfSlash = patternToTest.indexOf("/");
49
+ return `${negatedPrefix}${firstIndexOfSlash < 0 || firstIndexOfSlash === patternToTest.length - 1 ? "**/" : ""}${(firstIndexOfSlash === 0 ? patternToTest.slice(1) : patternToTest).replaceAll(/(?=((?:\\.|[^{(])*))\1([{(])/guy, "$1\\$2")}${patternToTest.endsWith("/**") ? "/*" : ""}`;
50
+ }
51
+ /**
52
+ * @param {string} ignoreFilePath
53
+ * @returns {string[]}
54
+ */
55
+ function ignoreFilePathToPatterns(ignoreFilePath) {
56
+ return fs.readFileSync(ignoreFilePath, "utf8").split(/\r?\n/u).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map(convertIgnorePatternToMinimatch);
57
+ }
58
+ /**
59
+ * Helper to parse and validate the options to `includeIgnoreFile()`
60
+ *
61
+ * @param {string | { gitignoreResolution?: unknown, name?: unknown } | undefined} options
62
+ * @returns {{ gitignoreResolution: boolean, name: string }}
63
+ */
64
+ function parseOptions(options) {
65
+ if (typeof options === "string") return {
66
+ gitignoreResolution: false,
67
+ name: options
68
+ };
69
+ const optionsObject = options ?? {};
70
+ if (typeof optionsObject !== "object" || Array.isArray(optionsObject)) throw new TypeError("The options argument to `includeIgnoreFile()` should be an object or a string.");
71
+ const gitignoreResolution = optionsObject.gitignoreResolution ?? false;
72
+ if (typeof gitignoreResolution !== "boolean") throw new TypeError("The `gitignoreResolution` option must be specified a boolean or omitted");
73
+ const name = optionsObject.name ?? `Imported .gitignore patterns`;
74
+ if (typeof name !== "string") throw new TypeError("The `name` option must be specified as a string or omitted.");
75
+ return {
76
+ gitignoreResolution,
77
+ name
78
+ };
79
+ }
80
+ /**
81
+ * @overload
82
+ *
83
+ * Reads ignore files and returns objects with the ignore patterns.
84
+ *
85
+ * @param {string[]} ignoreFilePathArg The paths of ignore files to include.
86
+ * @param {IncludeIgnoreFileOptions} [options]
87
+ * @returns {ConfigObject[]}
88
+ */
89
+ /**
90
+ * @overload
91
+ *
92
+ * Reads an ignore file and returns an object with the ignore patterns.
93
+ *
94
+ * @param {string} ignoreFilePathArg The path of the ignore file to include.
95
+ * @param {IncludeIgnoreFileOptions} [options]
96
+ * @returns {ConfigObject}
97
+ */
98
+ /**
99
+ * @overload
100
+ *
101
+ * Reads an ignore file(s) and returns an object(s) with the ignore patterns.
102
+ *
103
+ * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include.
104
+ * @param {IncludeIgnoreFileOptions} [options]
105
+ * @returns {ConfigObject[] | ConfigObject}
106
+ */
107
+ /**
108
+ * Reads an ignore file(s) and returns an object(s) with the ignore patterns.
109
+ *
110
+ * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include.
111
+ * @param {IncludeIgnoreFileOptions} [options]
112
+ * @returns {ConfigObject[] | ConfigObject}
113
+ */
114
+ function includeIgnoreFile(ignoreFilePathArg, options) {
115
+ const returnSingleObject = !Array.isArray(ignoreFilePathArg);
116
+ const ignoreFilePaths = Array.isArray(ignoreFilePathArg) ? ignoreFilePathArg : [ignoreFilePathArg];
117
+ for (const ignorePath of ignoreFilePaths) {
118
+ if (typeof ignorePath !== "string") throw new TypeError("The first argument to `includeIgnoreFile()` should be a string or array of strings");
119
+ if (!path.isAbsolute(ignorePath)) throw new Error(`The ignore file location must be an absolute path. Received ${ignorePath}`);
120
+ }
121
+ const { gitignoreResolution, name } = parseOptions(options);
122
+ if (returnSingleObject) return {
123
+ name,
124
+ ignores: ignoreFilePathToPatterns(ignoreFilePathArg),
125
+ ...gitignoreResolution ? { basePath: path.dirname(ignoreFilePathArg) } : {}
126
+ };
127
+ return ignoreFilePaths.map((ignoreFilePath, i) => ({
128
+ name: `${name} (${i})`,
129
+ ignores: ignoreFilePathToPatterns(ignoreFilePath),
130
+ ...gitignoreResolution ? { basePath: path.dirname(ignoreFilePath) } : {}
131
+ }));
132
+ }
133
+ //#endregion
134
+ //#region src/gitignore.ts
135
+ /**
136
+ * A generated directory that's `.gitignore`d but not in this array's own `ignores` block was, until this feature existed, a real, confirmed gap: harmless while nothing linted broadly enough to reach it, then a real problem the moment a wide-reaching rule (this package's own bundled RFC 8785 JSON canonicalization, say) started matching every file its own glob covers, including a leftover build/report artifact nobody meant to lint at all. Deriving `ignores` from `.gitignore` itself, rather than a hand-maintained list every consumer would otherwise have to keep in sync by hand, means a project's own existing single source of truth for "what isn't source" is the one ESLint uses too.
137
+ */
138
+ function buildGitignoreConfig(options = {}) {
139
+ const cwd = options.cwd ?? process.cwd();
140
+ if (options.enabled === false) return [];
141
+ const gitignorePath = join(cwd, ".gitignore");
142
+ if (!existsSync(gitignorePath)) {
143
+ if (options.enabled === true) throw new Error(`@exadev/eslint-config: gitignore-based ignores were explicitly requested but no .gitignore was found at ${gitignorePath}`);
144
+ return [];
145
+ }
146
+ return [includeIgnoreFile(gitignorePath)];
147
+ }
148
+ //#endregion
12
149
  //#region src/jsdoc.ts
13
150
  const JS_TS_FILE_PATTERNS$1 = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
14
151
  const jsdocConfig = jsdoc.configs["flat/recommended-tsdoc-error"];
@@ -128,7 +265,7 @@ function buildNextjsConfig(options = {}) {
128
265
  }
129
266
  //#endregion
130
267
  //#region package.json
131
- var version = "2.16.1";
268
+ var version = "2.17.0";
132
269
  //#endregion
133
270
  //#region src/react.ts
134
271
  const JSX_FILE_PATTERNS = ["**/*.jsx", "**/*.tsx"];
@@ -1840,6 +1977,7 @@ function toPublicConfigArray(built) {
1840
1977
  //#region src/create-config.ts
1841
1978
  function exadevConfig(options = {}, ...userConfigs) {
1842
1979
  return toPublicConfigArray([
1980
+ ...buildGitignoreConfig({ enabled: options.gitignore }),
1843
1981
  ...recommendedTypeChecked,
1844
1982
  ...jsdocAndTsdoc,
1845
1983
  ...jsonCanonicalConfig,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "exadev-eslint-config",
3
3
  "description": "Shared custom ESLint rules and plugin for ExaDev projects",
4
- "version": "2.16.1",
4
+ "version": "2.17.0",
5
5
  "dependencies": {
6
6
  "@eslint/js": "^10.0.1",
7
7
  "@typescript-eslint/utils": "8.67.0",
@@ -14,6 +14,7 @@
14
14
  "@arethetypeswrong/cli": "^0.18.5",
15
15
  "@commitlint/cli": "^21.2.1",
16
16
  "@commitlint/config-conventional": "^21.2.0",
17
+ "@eslint/config-helpers": "0.7.0",
17
18
  "@eslint/json": "2.1.0",
18
19
  "@humanwhocodes/momoa": "^3.3.13",
19
20
  "@next/eslint-plugin-next": "^16.3.2",