@fabdeh/eslint-config 0.10.0 → 0.11.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.
Files changed (4) hide show
  1. package/README.md +201 -15
  2. package/dist/index.d.mts +15442 -13862
  3. package/dist/index.mjs +413 -178
  4. package/package.json +7 -5
package/dist/index.mjs CHANGED
@@ -1,21 +1,60 @@
1
- import tseslint from "typescript-eslint";
2
- import { statSync } from "node:fs";
3
- import { readFile } from "node:fs/promises";
1
+ import { readFileSync, statSync } from "node:fs";
4
2
  import { dirname, join, normalize, resolve } from "node:path";
5
3
  import process from "node:process";
6
- import { glob } from "glob";
4
+ import { globSync } from "glob";
7
5
  import { isPackageExists } from "local-pkg";
8
6
  import eslintComments from "@eslint-community/eslint-plugin-eslint-comments";
9
7
  import * as importX from "eslint-plugin-import-x";
10
8
  import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions";
11
9
  import unusedImports from "eslint-plugin-unused-imports";
12
10
  import globals from "globals";
11
+ import tseslint from "typescript-eslint";
13
12
  import jsdocPlugin from "eslint-plugin-jsdoc";
14
13
  import { mergeProcessors, processorPassThrough } from "eslint-merge-processors";
15
14
  import nodePlugin from "eslint-plugin-n";
16
15
  import perfectionistPlugin from "eslint-plugin-perfectionist";
16
+ import { readFile } from "node:fs/promises";
17
+ import { findUp, findUpSync } from "find-up-simple";
17
18
  import { configs } from "eslint-plugin-regexp";
18
19
  import unicornPlugin from "eslint-plugin-unicorn";
20
+ import { FlatConfigComposer } from "eslint-flat-config-utils";
21
+ //#region src/config-presets.ts
22
+ const CONFIG_PRESET_FULL_ON = {
23
+ angular: true,
24
+ gitignore: true,
25
+ jsdoc: true,
26
+ jsonc: true,
27
+ markdown: true,
28
+ ngrx: true,
29
+ playwright: true,
30
+ pnpm: true,
31
+ regexp: true,
32
+ stylistic: { experimental: true },
33
+ tailwindcss: true,
34
+ toml: true,
35
+ typescript: { enableErasableSyntaxOnly: true },
36
+ unicorn: true,
37
+ vitest: true,
38
+ yaml: true
39
+ };
40
+ const CONFIG_PRESET_FULL_OFF = {
41
+ angular: false,
42
+ gitignore: false,
43
+ jsdoc: false,
44
+ jsonc: false,
45
+ markdown: false,
46
+ ngrx: false,
47
+ playwright: false,
48
+ pnpm: false,
49
+ regexp: false,
50
+ stylistic: false,
51
+ tailwindcss: false,
52
+ toml: false,
53
+ typescript: false,
54
+ unicorn: false,
55
+ vitest: false,
56
+ yaml: false
57
+ };
19
58
  /**
20
59
  * A glob pattern that matches JavaScript and TypeScript source files (including JSX/TSX).
21
60
  */
@@ -167,7 +206,7 @@ function isDirectory(path) {
167
206
  * @returns The root directory of the workspace.
168
207
  */
169
208
  function getWorkspaceRoot(dir, candidateRoot) {
170
- if (process.env.NX_WORKSPACE_ROOT) return process.env.NX_WORKSPACE_ROOT;
209
+ if (process.env["NX_WORKSPACE_ROOT"]) return process.env["NX_WORKSPACE_ROOT"];
171
210
  if (dirname(dir) === dir) return candidateRoot;
172
211
  if ([
173
212
  join(dir, "nx.json"),
@@ -211,12 +250,38 @@ function isPackageInScope(name) {
211
250
  * @returns A promise that resolves when the operation is complete.
212
251
  */
213
252
  async function ensurePackages(packages) {
214
- if (process.env.CI || !process.stdout.isTTY || !IS_CWD_IN_SCOPE) return;
253
+ if (process.env["CI"] || !process.stdout.isTTY || !IS_CWD_IN_SCOPE) return;
215
254
  const nonExistingPackages = packages.filter((i) => i && !isPackageInScope(i));
216
255
  if (nonExistingPackages.length === 0) return;
217
256
  if (await (await import("@clack/prompts")).confirm({ message: `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?` })) await import("@antfu/install-pkg").then((i) => i.installPackage(nonExistingPackages, { dev: true }));
218
257
  }
219
258
  /**
259
+ * Detects whether the current process is running in an interactive editor environment.
260
+ *
261
+ * The check returns `false` in CI and Git hook\/`lint-staged` contexts to avoid
262
+ * editor-specific behavior in non-interactive executions.
263
+ *
264
+ * Editor detection is based on common environment variables from VS Code,
265
+ * JetBrains IDEs, Vim\/Neovim, and Zed.
266
+ *
267
+ * @returns `true` when execution appears to be inside a supported editor; otherwise `false`.
268
+ */
269
+ function isInEditorEnv() {
270
+ if (process.env["CI"]) return false;
271
+ if (isInGitHooksOrLintStaged()) return false;
272
+ return !!(process.env["VSCODE_PID"] || process.env["VSCODE_CWD"] || process.env["JETBRAINS_IDE"] || process.env["VIM"] || process.env["NVIM"] || process.env["ZED_ENVIRONMENT"] && !process.env["ZED_TERM"]);
273
+ }
274
+ /**
275
+ * Detects whether the current process is running from a Git hook or `lint\-staged`.
276
+ *
277
+ * It checks common environment variables set by Git tooling and npm lifecycle scripts.
278
+ *
279
+ * @returns `true` when execution appears to be in a Git hook or `lint\-staged` context.
280
+ */
281
+ function isInGitHooksOrLintStaged() {
282
+ return !!(process.env["GIT_PARAMS"] || process.env["VSCODE_GIT_COMMAND"] || process.env["npm_lifecycle_script"]?.startsWith("lint-staged") || process.env["npm_lifecycle_script"]?.startsWith("nano-staged"));
283
+ }
284
+ /**
220
285
  * Returns the object representation of the configuration or an empty object if it is a boolean.
221
286
  *
222
287
  * @param options - The {@link DefineConfigOptions} object to extract the sub-options from.
@@ -277,10 +342,10 @@ function pathToGlobPattern(options) {
277
342
  *
278
343
  * @returns Absolute path to the first existing Playwright test directory, or `undefined` if none is found.
279
344
  */
280
- async function getPlaywrightDirectory() {
281
- const playwrightConfigs = await glob("**/playwright.config.ts", { ignore: ["**/node_modules/**"] });
345
+ function getPlaywrightDirectory() {
346
+ const playwrightConfigs = globSync("**/playwright.config.ts", { ignore: ["**/node_modules/**"] });
282
347
  for (const config of playwrightConfigs) try {
283
- const configContent = await readFile(config, "utf-8");
348
+ const configContent = readFileSync(config, "utf-8");
284
349
  const match = /testDir:\s*["'](.+)["'],?/.exec(configContent);
285
350
  if (match) {
286
351
  const testDir = match[1];
@@ -296,15 +361,17 @@ async function getPlaywrightDirectory() {
296
361
  *
297
362
  * @param options - Configuration options for Angular ESLint.
298
363
  * @param options.enableAccessibilityRules - Whether to enable accessibility rules for HTML templates.
299
- * @param options.tsOverrides - Additional TypeScript rule overrides.
300
- * @param options.htmlOverrides - Additional HTML rule overrides.
301
364
  * @param options.prefix - The prefix to use for Angular component and directive selectors.
302
365
  * @returns A promise that resolves to an array of ESLint configurations.
303
366
  */
304
367
  async function angular(options = {}) {
305
368
  const angularEslint = await interopDefault(import("angular-eslint"));
306
- const { enableAccessibilityRules = true, tsOverrides = {}, htmlOverrides = {}, prefix = "app", ignoreClassNamePatternForInjectableProvidedIn: ignoreClassNamePattern, componentStylesMode = "string", preferOnPushOnly = true, banExperimentalApi = true, banDeveloperPreviewApi = true, inlineTemplateAndStyles = false } = options;
307
- return tseslint.config({
369
+ const { enableAccessibilityRules = true, overrides = {}, prefix = "app", ignoreClassNamePatternForInjectableProvidedIn: ignoreClassNamePattern, componentStylesMode = "string", preferOnPushOnly = true, banExperimentalApi = true, banDeveloperPreviewApi = true, inlineTemplateAndStyles = false } = options;
370
+ const tsOverrides = {};
371
+ const htmlOverrides = {};
372
+ for (const [key, value] of Object.entries(overrides)) if (key.startsWith("@angular-eslint/template/")) htmlOverrides[key] = value;
373
+ else if (key.startsWith("@angular-eslint/")) tsOverrides[key] = value;
374
+ return [{
308
375
  name: "fabdeh/angular/rules",
309
376
  plugins: { "@angular-eslint": angularEslint.tsPlugin },
310
377
  processor: angularEslint.processInlineTemplates,
@@ -392,7 +459,7 @@ async function angular(options = {}) {
392
459
  ...enableAccessibilityRules ? angularEslint.configs.templateAccessibility.find((c) => c.name === "angular-eslint/template-accessibility")?.rules : {},
393
460
  ...htmlOverrides
394
461
  }
395
- });
462
+ }];
396
463
  }
397
464
  //#endregion
398
465
  //#region src/configs/comments.ts
@@ -406,7 +473,7 @@ async function angular(options = {}) {
406
473
  * @returns The configuration array for ESLint comments rules.
407
474
  */
408
475
  function comments() {
409
- return tseslint.config({
476
+ return [{
410
477
  name: "fabdeh/comments/rules",
411
478
  files: [GLOB_SRC],
412
479
  plugins: { "@eslint-community/eslint-comments": eslintComments },
@@ -418,7 +485,7 @@ function comments() {
418
485
  "@eslint-community/eslint-comments/no-unused-disable": "error",
419
486
  "@eslint-community/eslint-comments/no-unused-enable": "error"
420
487
  }
421
- });
488
+ }];
422
489
  }
423
490
  //#endregion
424
491
  //#region src/configs/ignore.ts
@@ -427,13 +494,13 @@ function comments() {
427
494
  *
428
495
  * @param userIgnores - An array of user-defined glob patterns to ignore. Defaults to an empty array.
429
496
  * @param fromFactory - An optional suffix to add to the config name.
430
- * @returns A TypedConfigArray object containing the combined "ignore" patterns.
497
+ * @returns A TypedConfig[] object containing the combined "ignore" patterns.
431
498
  */
432
499
  function ignores(userIgnores = [], fromFactory) {
433
- return tseslint.config({
500
+ return [{
434
501
  name: `fabdeh/ignores${fromFactory ? `/${fromFactory}` : ""}`,
435
502
  ignores: [...!fromFactory || fromFactory === "workspace" ? GLOB_EXCLUDE : [], ...userIgnores]
436
- });
503
+ }];
437
504
  }
438
505
  //#endregion
439
506
  //#region src/configs/imports.ts
@@ -442,11 +509,11 @@ function ignores(userIgnores = [], fromFactory) {
442
509
  *
443
510
  * @param options - An object containing stylistic options.
444
511
  * @param options.stylistic - A boolean indicating whether to include stylistic rules. Defaults to true.
445
- * @returns A TypedConfigArray object with the specified import rules.
512
+ * @returns A TypedConfig[] object with the specified import rules.
446
513
  */
447
514
  function imports(options = {}) {
448
515
  const { stylistic = true } = options;
449
- return tseslint.config({
516
+ return [{
450
517
  name: "fabdeh/imports/rules",
451
518
  files: [GLOB_SRC],
452
519
  plugins: { "import-x": importX },
@@ -480,7 +547,7 @@ function imports(options = {}) {
480
547
  "import-x/named": "off",
481
548
  "import-x/no-deprecated": "off"
482
549
  }
483
- });
550
+ }];
484
551
  }
485
552
  //#endregion
486
553
  //#region src/configs/javascript.ts
@@ -500,8 +567,8 @@ function imports(options = {}) {
500
567
  * ```
501
568
  */
502
569
  function javascript(options = {}) {
503
- const { overrides = {} } = options;
504
- return tseslint.config({
570
+ const { isInEditor = false, overrides = {} } = options;
571
+ return [{
505
572
  name: "fabdeh/javascript/setup",
506
573
  languageOptions: {
507
574
  ecmaVersion: "latest",
@@ -716,7 +783,10 @@ function javascript(options = {}) {
716
783
  singleReturnOnly: true,
717
784
  allowNamedFunctions: true
718
785
  }],
719
- "prefer-const": "error",
786
+ "prefer-const": [isInEditor ? "warn" : "error", {
787
+ destructuring: "all",
788
+ ignoreReadBeforeAssign: true
789
+ }],
720
790
  "prefer-exponentiation-operator": "error",
721
791
  "prefer-object-spread": "error",
722
792
  "prefer-promise-reject-errors": "error",
@@ -729,7 +799,7 @@ function javascript(options = {}) {
729
799
  "symbol-description": "error",
730
800
  "unicode-bom": ["error", "never"],
731
801
  "use-isnan": ["error", { enforceForIndexOf: true }],
732
- "unused-imports/no-unused-imports": "error",
802
+ "unused-imports/no-unused-imports": isInEditor ? "warn" : "error",
733
803
  "unused-imports/no-unused-vars": ["error", {
734
804
  args: "after-used",
735
805
  argsIgnorePattern: "^_",
@@ -752,7 +822,7 @@ function javascript(options = {}) {
752
822
  "@typescript-eslint/no-this-alias": "error",
753
823
  ...overrides
754
824
  }
755
- });
825
+ }];
756
826
  }
757
827
  //#endregion
758
828
  //#region src/configs/jsdoc.ts
@@ -828,7 +898,7 @@ function getJsDocRules(level, stylistic, mode) {
828
898
  */
829
899
  function jsdoc(options = {}) {
830
900
  const { stylistic = true } = options;
831
- return tseslint.config({
901
+ return [{
832
902
  name: "fabdeh/jsdoc/rules",
833
903
  files: [GLOB_SRC],
834
904
  plugins: { jsdoc: jsdocPlugin },
@@ -837,7 +907,7 @@ function jsdoc(options = {}) {
837
907
  name: "fabdeh/jsdoc/ts-only/rules",
838
908
  files: [GLOB_TS],
839
909
  rules: getJsDocRules("warn", !!stylistic, "tsOnly")
840
- });
910
+ }];
841
911
  }
842
912
  //#endregion
843
913
  //#region src/configs/jsonc.ts
@@ -848,7 +918,7 @@ function jsdoc(options = {}) {
848
918
  * @param options.files - An array of glob patterns to specify the JSONC files to lint. Defaults to `[GLOB_JSON, GLOB_JSON5, GLOB_JSONC]`.
849
919
  * @param options.overrides - Additional rules or configurations to override the default settings.
850
920
  * @param options.stylistic - A boolean or object to enable or configure stylistic rules. Defaults to `true`.
851
- * @returns A promise that resolves to a `TypedConfigArray` containing the ESLint configuration.
921
+ * @returns A promise that resolves to a `TypedConfig[]` containing the ESLint configuration.
852
922
  * @example
853
923
  * ```typescript
854
924
  * const config = await jsonc({
@@ -867,7 +937,7 @@ async function jsonc(options = {}) {
867
937
  ], overrides = {}, stylistic = true } = options;
868
938
  const { indent = 2 } = typeof stylistic === "object" ? stylistic : {};
869
939
  const [jsoncPlugin, jsoncParser] = await Promise.all([interopDefault(import("eslint-plugin-jsonc")), interopDefault(import("jsonc-eslint-parser"))]);
870
- return tseslint.config({
940
+ return [{
871
941
  name: "fabdeh/jsonc/setup",
872
942
  plugins: { jsonc: jsoncPlugin }
873
943
  }, {
@@ -921,7 +991,7 @@ async function jsonc(options = {}) {
921
991
  } : {},
922
992
  ...overrides
923
993
  }
924
- });
994
+ }];
925
995
  }
926
996
  //#endregion
927
997
  //#region src/configs/markdown.ts
@@ -934,7 +1004,7 @@ async function jsonc(options = {}) {
934
1004
  * @param options - An object containing file and override options.
935
1005
  * @param options.files - An array of glob patterns to specify the Markdown files to lint.
936
1006
  * @param options.overrides - An object containing rule overrides.
937
- * @returns A promise that resolves to a TypedConfigArray containing the ESLint configurations.
1007
+ * @returns A promise that resolves to a TypedConfig[] containing the ESLint configurations.
938
1008
  * @example
939
1009
  * ```typescript
940
1010
  * const config = await markdown({
@@ -948,75 +1018,81 @@ async function jsonc(options = {}) {
948
1018
  async function markdown(options = {}) {
949
1019
  const { files = [GLOB_MARKDOWN], overrides = {} } = options;
950
1020
  const markdownPlugin = await interopDefault(import("@eslint/markdown"));
951
- return tseslint.config({
952
- name: "fabdeh/markdown/setup",
953
- plugins: { markdown: markdownPlugin }
954
- }, {
955
- name: "fabdeh/markdown/processor",
956
- files,
957
- ignores: [GLOB_MARKDOWN_IN_MARKDOWN],
958
- processor: mergeProcessors([markdownPlugin.processors.markdown, processorPassThrough])
959
- }, {
960
- name: "fabdeh/markdown/parser",
961
- files,
962
- languageOptions: { parser: {
963
- meta: { name: "parser-plain" },
964
- parseForESLint: (code) => ({
965
- ast: {
966
- body: [],
967
- comments: [],
968
- loc: {
969
- end: code.length,
970
- start: 0
1021
+ return [
1022
+ {
1023
+ name: "fabdeh/markdown/setup",
1024
+ plugins: { markdown: markdownPlugin }
1025
+ },
1026
+ {
1027
+ name: "fabdeh/markdown/processor",
1028
+ files,
1029
+ ignores: [GLOB_MARKDOWN_IN_MARKDOWN],
1030
+ processor: mergeProcessors([markdownPlugin.processors.markdown, processorPassThrough])
1031
+ },
1032
+ {
1033
+ name: "fabdeh/markdown/parser",
1034
+ files,
1035
+ languageOptions: { parser: {
1036
+ meta: { name: "parser-plain" },
1037
+ parseForESLint: (code) => ({
1038
+ ast: {
1039
+ body: [],
1040
+ comments: [],
1041
+ loc: {
1042
+ end: code.length,
1043
+ start: 0
1044
+ },
1045
+ range: [0, code.length],
1046
+ tokens: [],
1047
+ type: "Program"
971
1048
  },
972
- range: [0, code.length],
973
- tokens: [],
974
- type: "Program"
975
- },
976
- scopeManager: null,
977
- services: { isPlain: true },
978
- visitorKeys: { Program: [] }
979
- })
980
- } }
981
- }, {
982
- name: "fabdeh/markdown/disables",
983
- languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
984
- files: [GLOB_MARKDOWN_CODE],
985
- rules: {
986
- ...tseslint.configs.disableTypeChecked.rules,
987
- "import-x/newline-after-import": "off",
988
- "no-alert": "off",
989
- "no-console": "off",
990
- "no-labels": "off",
991
- "no-lone-blocks": "off",
992
- "no-restricted-syntax": "off",
993
- "no-undef": "off",
994
- "no-unused-expressions": "off",
995
- "no-unused-labels": "off",
996
- "no-unused-vars": "off",
997
- "@stylistic/comma-dangle": "off",
998
- "@stylistic/eol-last": "off",
999
- "@typescript-eslint/consistent-type-imports": "off",
1000
- "@typescript-eslint/explicit-function-return-type": "off",
1001
- "@typescript-eslint/no-namespace": "off",
1002
- "@typescript-eslint/no-redeclare": "off",
1003
- "@typescript-eslint/no-require-imports": "off",
1004
- "@typescript-eslint/no-unused-expressions": "off",
1005
- "@typescript-eslint/no-unused-vars": "off",
1006
- "@typescript-eslint/no-use-before-define": "off",
1007
- "unicode-bom": "off",
1008
- "unused-imports/no-unused-imports": "off",
1009
- "unused-imports/no-unused-vars": "off",
1010
- ...overrides
1011
- }
1012
- }, {
1013
- name: "fabdeh/markdown/rules",
1014
- files,
1015
- rules: {
1016
- ...markdownPlugin.configs.recommended.at(0)?.rules,
1017
- "markdown/no-duplicate-headings": "error"
1049
+ scopeManager: null,
1050
+ services: { isPlain: true },
1051
+ visitorKeys: { Program: [] }
1052
+ })
1053
+ } }
1054
+ },
1055
+ {
1056
+ name: "fabdeh/markdown/disables",
1057
+ languageOptions: { parserOptions: { ecmaFeatures: { impliedStrict: true } } },
1058
+ files: [GLOB_MARKDOWN_CODE],
1059
+ rules: {
1060
+ ...tseslint.configs.disableTypeChecked.rules,
1061
+ "import-x/newline-after-import": "off",
1062
+ "no-alert": "off",
1063
+ "no-console": "off",
1064
+ "no-labels": "off",
1065
+ "no-lone-blocks": "off",
1066
+ "no-restricted-syntax": "off",
1067
+ "no-undef": "off",
1068
+ "no-unused-expressions": "off",
1069
+ "no-unused-labels": "off",
1070
+ "no-unused-vars": "off",
1071
+ "@stylistic/comma-dangle": "off",
1072
+ "@stylistic/eol-last": "off",
1073
+ "@typescript-eslint/consistent-type-imports": "off",
1074
+ "@typescript-eslint/explicit-function-return-type": "off",
1075
+ "@typescript-eslint/no-namespace": "off",
1076
+ "@typescript-eslint/no-redeclare": "off",
1077
+ "@typescript-eslint/no-require-imports": "off",
1078
+ "@typescript-eslint/no-unused-expressions": "off",
1079
+ "@typescript-eslint/no-unused-vars": "off",
1080
+ "@typescript-eslint/no-use-before-define": "off",
1081
+ "unicode-bom": "off",
1082
+ "unused-imports/no-unused-imports": "off",
1083
+ "unused-imports/no-unused-vars": "off",
1084
+ ...overrides
1085
+ }
1086
+ },
1087
+ {
1088
+ name: "fabdeh/markdown/rules",
1089
+ files,
1090
+ rules: {
1091
+ ...markdownPlugin.configs.recommended.at(0)?.rules,
1092
+ "markdown/no-duplicate-headings": "error"
1093
+ }
1018
1094
  }
1019
- });
1095
+ ];
1020
1096
  }
1021
1097
  //#endregion
1022
1098
  //#region src/configs/rules-configs/naming-convention.ts
@@ -1127,15 +1203,16 @@ async function ngrx(options = {}) {
1127
1203
  const { store = isPackageExists("@ngrx/store"), effects = isPackageExists("@ngrx/effects"), signals = isPackageExists("@ngrx/signals"), useRelaxedNamingConventionForCamelAndPascalCases = false } = options;
1128
1204
  const configs = [];
1129
1205
  let addOperatorsRules = false;
1206
+ const registerPluginFiles = [];
1130
1207
  const ngrxOperatorsFiles = [];
1131
1208
  if (store) {
1132
1209
  const { files = DEFAULT_STORE_GLOB, enforceOperatorsRules = isPackageExists("@ngrx/operators"), overrides = {} } = typeof store === "object" ? store : {};
1133
1210
  addOperatorsRules ||= enforceOperatorsRules;
1134
1211
  ngrxOperatorsFiles.push(files);
1212
+ registerPluginFiles.push(files);
1135
1213
  configs.push({
1136
1214
  name: "fabdeh/ngrx-store/rules",
1137
1215
  files,
1138
- plugins: { "@ngrx": ngrxPlugin },
1139
1216
  rules: {
1140
1217
  ...ngrxPlugin.configs.store.find((c) => c.name === "ngrx/store")?.rules,
1141
1218
  "@typescript-eslint/naming-convention": [
@@ -1169,10 +1246,10 @@ async function ngrx(options = {}) {
1169
1246
  const { files = DEFAULT_EFFECTS_GLOB, enforceOperatorsRules = isPackageExists("@ngrx/operators"), overrides = {} } = typeof effects === "object" ? effects : {};
1170
1247
  addOperatorsRules ||= enforceOperatorsRules;
1171
1248
  ngrxOperatorsFiles.push(files);
1249
+ registerPluginFiles.push(files);
1172
1250
  configs.push({
1173
1251
  name: "fabdeh/ngrx-effects/rules",
1174
1252
  files,
1175
- plugins: { "@ngrx": ngrxPlugin },
1176
1253
  rules: {
1177
1254
  ...ngrxPlugin.configs.effects.find((c) => c.name === "ngrx/effects")?.rules,
1178
1255
  "@typescript-eslint/naming-convention": [
@@ -1199,10 +1276,10 @@ async function ngrx(options = {}) {
1199
1276
  const { files = DEFAULT_SIGNALS_GLOB, enforceOperatorsRules = isPackageExists("@ngrx/operators"), overrides = {} } = typeof signals === "object" ? signals : {};
1200
1277
  addOperatorsRules ||= enforceOperatorsRules;
1201
1278
  ngrxOperatorsFiles.push(files);
1279
+ registerPluginFiles.push(files);
1202
1280
  configs.push({
1203
1281
  name: "fabdeh/ngrx-signals/rules",
1204
1282
  files,
1205
- plugins: { "@ngrx": ngrxPlugin },
1206
1283
  rules: {
1207
1284
  ...ngrxPlugin.configs.signals.find((c) => c.name === "ngrx/signals")?.rules,
1208
1285
  "@typescript-eslint/naming-convention": [
@@ -1233,10 +1310,14 @@ async function ngrx(options = {}) {
1233
1310
  if (addOperatorsRules) configs.push({
1234
1311
  name: "fabdeh/ngrx-operators/rules",
1235
1312
  files: [...ngrxOperatorsFiles],
1236
- plugins: { "@ngrx": ngrxPlugin },
1237
1313
  rules: { ...ngrxPlugin.configs.operators.find((c) => c.name === "ngrx/operators")?.rules }
1238
1314
  });
1239
- return tseslint.config(configs);
1315
+ if (registerPluginFiles.length > 0) configs.unshift({
1316
+ name: "fabdeh/ngrx/setup",
1317
+ files: [...registerPluginFiles],
1318
+ plugins: { "@ngrx": ngrxPlugin.configs.store.find((c) => c.name === "ngrx/base")?.plugins?.["@ngrx"] ?? {} }
1319
+ });
1320
+ return configs;
1240
1321
  }
1241
1322
  //#endregion
1242
1323
  //#region src/configs/node.ts
@@ -1246,7 +1327,7 @@ async function ngrx(options = {}) {
1246
1327
  * @returns An array defining Node.js rules, plugins, and their configurations.
1247
1328
  */
1248
1329
  function node() {
1249
- return tseslint.config({
1330
+ return [{
1250
1331
  name: "fabdeh/node/rules",
1251
1332
  files: [GLOB_SRC],
1252
1333
  plugins: { n: nodePlugin },
@@ -1263,7 +1344,7 @@ function node() {
1263
1344
  "n/prefer-promises/fs": "error",
1264
1345
  "n/process-exit-as-throw": "error"
1265
1346
  }
1266
- });
1347
+ }];
1267
1348
  }
1268
1349
  //#endregion
1269
1350
  //#region src/configs/rules-configs/perfectionist-groups.ts
@@ -1325,7 +1406,7 @@ const SORT_UNION_OR_INTERSECTION_GROUPS = [
1325
1406
  */
1326
1407
  function perfectionist() {
1327
1408
  const rootDir = getWorkspaceRoot(process.cwd(), process.cwd());
1328
- return tseslint.config({
1409
+ return [{
1329
1410
  name: "fabdeh/perfectionist/rules",
1330
1411
  files: [GLOB_SRC],
1331
1412
  plugins: { perfectionist: perfectionistPlugin },
@@ -1367,7 +1448,7 @@ function perfectionist() {
1367
1448
  type: "natural"
1368
1449
  }]
1369
1450
  }
1370
- });
1451
+ }];
1371
1452
  }
1372
1453
  //#endregion
1373
1454
  //#region src/configs/playwright.ts
@@ -1430,36 +1511,141 @@ async function playwright(options = {}) {
1430
1511
  //#endregion
1431
1512
  //#region src/configs/pnpm.ts
1432
1513
  /**
1514
+ * Detects whether the current PNPM workspace uses catalog definitions.
1515
+ *
1516
+ * Searches for a `pnpm-workspace.yaml` file and checks whether it contains
1517
+ * either the `catalog:` or `catalogs:` keys, which are used to define shared
1518
+ * dependency version catalogs in PNPM workspaces.
1519
+ *
1520
+ * @returns `true` when catalog configuration is present; otherwise `false`.
1521
+ */
1522
+ async function detectCatalogUsage() {
1523
+ const workspaceFile = await findUp("pnpm-workspace.yaml");
1524
+ if (!workspaceFile) return false;
1525
+ const yaml = await readFile(workspaceFile, "utf-8");
1526
+ return yaml.includes("catalog:") || yaml.includes("catalogs:");
1527
+ }
1528
+ /**
1433
1529
  * Generates an array containing configuration settings for PNPM workspace-specific rules and plugins.
1434
1530
  *
1531
+ * @param options - The options
1435
1532
  * @returns An array defining PNPM workspace rules, plugins, and their configurations.
1436
1533
  */
1437
- async function pnpm() {
1438
- const [pluginPnpm, yamlParser, jsoncParser] = await Promise.all([
1534
+ async function pnpm(options = {}) {
1535
+ const [pluginPnpm, pluginYaml, yamlParser] = await Promise.all([
1439
1536
  interopDefault(import("eslint-plugin-pnpm")),
1440
- interopDefault(import("yaml-eslint-parser")),
1441
- interopDefault(import("jsonc-eslint-parser"))
1537
+ interopDefault(import("eslint-plugin-yml")),
1538
+ interopDefault(import("yaml-eslint-parser"))
1442
1539
  ]);
1443
- return tseslint.config({
1540
+ const { catalogs = await detectCatalogUsage(), isInEditor = false, json = true, sort = true, yaml = true } = options;
1541
+ const configs = [];
1542
+ if (json) configs.push({
1444
1543
  name: "fabdeh/pnpm/package-json",
1445
1544
  files: ["package.json", "**/package.json"],
1446
- languageOptions: { parser: jsoncParser },
1545
+ language: "jsonc/x",
1447
1546
  plugins: { pnpm: pluginPnpm },
1448
1547
  rules: {
1449
- "pnpm/json-enforce-catalog": "error",
1450
- "pnpm/json-prefer-workspace-settings": "error",
1451
- "pnpm/json-valid-catalog": "error"
1548
+ ...catalogs ? { "pnpm/json-enforce-catalog": ["error", {
1549
+ autofix: !isInEditor,
1550
+ ignores: ["@types/vscode"]
1551
+ }] } : {},
1552
+ "pnpm/json-prefer-workspace-settings": ["error", { autofix: !isInEditor }],
1553
+ "pnpm/json-valid-catalog": ["error", { autofix: !isInEditor }]
1452
1554
  }
1453
- }, {
1555
+ });
1556
+ if (yaml) configs.push({
1454
1557
  name: "fabdeh/pnpm/pnpm-workspace-yaml",
1455
1558
  files: ["pnpm-workspace.yaml"],
1456
1559
  languageOptions: { parser: yamlParser },
1457
1560
  plugins: { pnpm: pluginPnpm },
1458
1561
  rules: {
1562
+ "pnpm/yaml-enforce-settings": ["error", { settings: {
1563
+ shellEmulator: true,
1564
+ trustPolicy: "no-downgrade"
1565
+ } }],
1459
1566
  "pnpm/yaml-no-duplicate-catalog-item": "error",
1460
1567
  "pnpm/yaml-no-unused-catalog-item": "error"
1461
1568
  }
1462
1569
  });
1570
+ if (sort) configs.push({
1571
+ files: ["pnpm-workspace.yaml"],
1572
+ languageOptions: { parser: yamlParser },
1573
+ name: "antfu/pnpm/pnpm-workspace-yaml-sort",
1574
+ plugins: { yaml: pluginYaml },
1575
+ rules: { "yaml/sort-keys": [
1576
+ "error",
1577
+ {
1578
+ order: [
1579
+ ...[
1580
+ "cacheDir",
1581
+ "catalogMode",
1582
+ "cleanupUnusedCatalogs",
1583
+ "dedupeDirectDeps",
1584
+ "deployAllFiles",
1585
+ "enablePrePostScripts",
1586
+ "engineStrict",
1587
+ "extendNodePath",
1588
+ "hoist",
1589
+ "hoistPattern",
1590
+ "hoistWorkspacePackages",
1591
+ "ignoreCompatibilityDb",
1592
+ "ignoreDepScripts",
1593
+ "ignoreScripts",
1594
+ "ignoreWorkspaceRootCheck",
1595
+ "managePackageManagerVersions",
1596
+ "minimumReleaseAge",
1597
+ "minimumReleaseAgeExclude",
1598
+ "modulesDir",
1599
+ "nodeLinker",
1600
+ "nodeVersion",
1601
+ "optimisticRepeatInstall",
1602
+ "packageManagerStrict",
1603
+ "packageManagerStrictVersion",
1604
+ "preferSymlinkedExecutables",
1605
+ "preferWorkspacePackages",
1606
+ "publicHoistPattern",
1607
+ "registrySupportsTimeField",
1608
+ "requiredScripts",
1609
+ "resolutionMode",
1610
+ "savePrefix",
1611
+ "scriptShell",
1612
+ "shamefullyHoist",
1613
+ "shellEmulator",
1614
+ "stateDir",
1615
+ "supportedArchitectures",
1616
+ "symlink",
1617
+ "tag",
1618
+ "trustPolicy",
1619
+ "trustPolicyExclude",
1620
+ "updateNotifier"
1621
+ ],
1622
+ "packages",
1623
+ "overrides",
1624
+ "patchedDependencies",
1625
+ "catalog",
1626
+ "catalogs",
1627
+ ...[
1628
+ "allowedDeprecatedVersions",
1629
+ "allowNonAppliedPatches",
1630
+ "configDependencies",
1631
+ "ignoredBuiltDependencies",
1632
+ "ignoredOptionalDependencies",
1633
+ "neverBuiltDependencies",
1634
+ "onlyBuiltDependencies",
1635
+ "onlyBuiltDependenciesFile",
1636
+ "packageExtensions",
1637
+ "peerDependencyRules"
1638
+ ]
1639
+ ],
1640
+ pathPattern: "^$"
1641
+ },
1642
+ {
1643
+ order: { type: "asc" },
1644
+ pathPattern: ".*"
1645
+ }
1646
+ ] }
1647
+ });
1648
+ return configs;
1463
1649
  }
1464
1650
  //#endregion
1465
1651
  //#region src/configs/regexp.ts
@@ -1473,7 +1659,7 @@ function regexp(options = {}) {
1473
1659
  const { level, overrides } = options;
1474
1660
  const config = configs["flat/recommended"];
1475
1661
  const rules = Object.fromEntries(Object.entries(config.rules).map(([ruleName, ruleLevel]) => [ruleName, level === "warn" ? level : ruleLevel]));
1476
- return tseslint.config({
1662
+ return [{
1477
1663
  name: "fabdeh/regexp/rules",
1478
1664
  files: [GLOB_SRC],
1479
1665
  plugins: { ...config.plugins },
@@ -1481,7 +1667,7 @@ function regexp(options = {}) {
1481
1667
  ...rules,
1482
1668
  ...overrides
1483
1669
  }
1484
- });
1670
+ }];
1485
1671
  }
1486
1672
  //#endregion
1487
1673
  //#region src/configs/sort.ts
@@ -1499,7 +1685,7 @@ function regexp(options = {}) {
1499
1685
  * @returns The ESLint configuration array with sorting rules.
1500
1686
  */
1501
1687
  function sortPackageJson() {
1502
- return tseslint.config({
1688
+ return [{
1503
1689
  name: "fabdeh/sort/package-json",
1504
1690
  files: ["**/package.json"],
1505
1691
  rules: {
@@ -1594,7 +1780,7 @@ function sortPackageJson() {
1594
1780
  }
1595
1781
  ]
1596
1782
  }
1597
- });
1783
+ }];
1598
1784
  }
1599
1785
  /**
1600
1786
  * Generates a TypeScript ESLint configuration array that enforces sorting rules for `tsconfig.json` files.
@@ -1606,7 +1792,7 @@ function sortPackageJson() {
1606
1792
  * @returns The TypeScript ESLint configuration array with sorting rules.
1607
1793
  */
1608
1794
  function sortTsConfig() {
1609
- return tseslint.config({
1795
+ return [{
1610
1796
  name: "fabdeh/sort/tsconfig-json",
1611
1797
  files: ["**/tsconfig.json", "**/tsconfig.*.json"],
1612
1798
  rules: { "jsonc/sort-keys": [
@@ -1717,7 +1903,7 @@ function sortTsConfig() {
1717
1903
  pathPattern: "^compilerOptions$"
1718
1904
  }
1719
1905
  ] }
1720
- });
1906
+ }];
1721
1907
  }
1722
1908
  //#endregion
1723
1909
  //#region src/configs/stylistic.ts
@@ -1742,7 +1928,7 @@ async function stylistic(options = {}) {
1742
1928
  };
1743
1929
  const stylisticPlugin = await interopDefault(import("@stylistic/eslint-plugin"));
1744
1930
  const config = stylisticPlugin.configs.customize(stylisticOptions);
1745
- return tseslint.config({
1931
+ return [{
1746
1932
  name: "fabdeh/stylistic/rules",
1747
1933
  files: [GLOB_SRC],
1748
1934
  plugins: { "@stylistic": stylisticPlugin },
@@ -1762,7 +1948,7 @@ async function stylistic(options = {}) {
1762
1948
  } }
1763
1949
  ]
1764
1950
  }
1765
- });
1951
+ }];
1766
1952
  }
1767
1953
  //#endregion
1768
1954
  //#region src/configs/tailwindcss.ts
@@ -1796,8 +1982,8 @@ async function tailwindcss(options = {}) {
1796
1982
  files = { files: filesGlob ?? [GLOB_SRC, "**/*.htm?(l)"] };
1797
1983
  parserConfigs = [];
1798
1984
  }
1799
- return tseslint.config(...parserConfigs, {
1800
- name: "@fabdeh/tailwindcss/rules",
1985
+ return [...parserConfigs, {
1986
+ name: "fabdeh/tailwindcss/rules",
1801
1987
  ...files,
1802
1988
  plugins: { "better-tailwindcss": betterTailwindcssPlugin },
1803
1989
  settings: { "better-tailwindcss": settings },
@@ -1810,7 +1996,7 @@ async function tailwindcss(options = {}) {
1810
1996
  "better-tailwindcss/no-restricted-classes": "error"
1811
1997
  } : {}
1812
1998
  }
1813
- });
1999
+ }];
1814
2000
  }
1815
2001
  //#endregion
1816
2002
  //#region src/configs/toml.ts
@@ -1827,10 +2013,9 @@ async function tailwindcss(options = {}) {
1827
2013
  async function toml(options = {}) {
1828
2014
  const { files = [GLOB_TOML], overrides = {}, stylistic = true } = options;
1829
2015
  const { indent = 2 } = typeof stylistic === "object" ? stylistic : {};
1830
- const tomlPlugin = await interopDefault(import("eslint-plugin-toml"));
1831
- return tseslint.config({
2016
+ return [{
1832
2017
  name: "fabdeh/toml/setup",
1833
- plugins: { toml: tomlPlugin }
2018
+ plugins: { toml: await interopDefault(import("eslint-plugin-toml")) }
1834
2019
  }, {
1835
2020
  name: "fabdeh/toml/rules",
1836
2021
  files,
@@ -1860,7 +2045,7 @@ async function toml(options = {}) {
1860
2045
  } : {},
1861
2046
  ...overrides
1862
2047
  }
1863
- });
2048
+ }];
1864
2049
  }
1865
2050
  //#endregion
1866
2051
  //#region src/configs/rules-configs/member-ordering.ts
@@ -1908,7 +2093,7 @@ const MEMBER_ORDERING_OPTIONS = { default: [
1908
2093
  * @param options.parserOptions - Options for the TypeScript parser.
1909
2094
  * @param options.overrides - Additional rule overrides.
1910
2095
  * @param isWorkspaceProject - A boolean indicating whether the project is a workspace project. Defaults to false.
1911
- * @returns A TypedConfigArray containing the TypeScript ESLint configuration.
2096
+ * @returns A TypedConfig[] containing the TypeScript ESLint configuration.
1912
2097
  */
1913
2098
  async function typescript(options = {}, isWorkspaceProject = false) {
1914
2099
  const { enableErasableSyntaxOnly = false, overrides = {}, parserOptions = {}, stylistic = true, type = "app", useRelaxedNamingConventionForCamelAndPascalCases = false } = options;
@@ -1922,7 +2107,7 @@ async function typescript(options = {}, isWorkspaceProject = false) {
1922
2107
  }
1923
2108
  const tsconfigRootDir = getWorkspaceRoot(process.cwd(), process.cwd());
1924
2109
  const defaultProject = getTsConfigFileName(tsconfigRootDir);
1925
- return tseslint.config({
2110
+ return [{
1926
2111
  name: `fabdeh/${type}/typescript/setup`,
1927
2112
  files: [GLOB_TS],
1928
2113
  ignores: [`${GLOB_MARKDOWN}/**`],
@@ -2024,7 +2209,7 @@ async function typescript(options = {}, isWorkspaceProject = false) {
2024
2209
  ...erasableSyntaxOnlyRules,
2025
2210
  ...overrides
2026
2211
  }
2027
- });
2212
+ }];
2028
2213
  }
2029
2214
  //#endregion
2030
2215
  //#region src/configs/unicorn.ts
@@ -2035,7 +2220,7 @@ async function typescript(options = {}, isWorkspaceProject = false) {
2035
2220
  * @returns The configuration array for the Unicorn plugin.
2036
2221
  */
2037
2222
  function unicorn(options = {}) {
2038
- return tseslint.config({
2223
+ return [{
2039
2224
  name: "fabdeh/unicorn/rules",
2040
2225
  files: [GLOB_SRC],
2041
2226
  plugins: { unicorn: unicornPlugin },
@@ -2136,7 +2321,7 @@ function unicorn(options = {}) {
2136
2321
  "unicorn/require-post-message-target-origin": "error",
2137
2322
  "unicorn/throw-new-error": "error"
2138
2323
  } }
2139
- });
2324
+ }];
2140
2325
  }
2141
2326
  //#endregion
2142
2327
  //#region src/configs/vitest.ts
@@ -2167,7 +2352,7 @@ async function vitest(options = {}) {
2167
2352
  useJestDom ? interopDefault(import("eslint-plugin-jest-dom")) : Promise.resolve(void 0),
2168
2353
  useTestingLibrary ? interopDefault(import("eslint-plugin-testing-library")) : Promise.resolve(void 0)
2169
2354
  ]);
2170
- return tseslint.config({
2355
+ return [{
2171
2356
  name: "fabdeh/vitest/rules",
2172
2357
  plugins: {
2173
2358
  vitest: vitestPlugin,
@@ -2214,7 +2399,7 @@ async function vitest(options = {}) {
2214
2399
  ...getJsDocRules("off", true, "both"),
2215
2400
  ...overrides
2216
2401
  }
2217
- });
2402
+ }];
2218
2403
  }
2219
2404
  //#endregion
2220
2405
  //#region src/configs/yaml.ts
@@ -2227,15 +2412,14 @@ async function vitest(options = {}) {
2227
2412
  * @param options.stylistic - A boolean or object to specify stylistic rules. Defaults to `true`.
2228
2413
  * @param options.stylistic.indent - The number of spaces for indentation or 'tab'. Defaults to `2`.
2229
2414
  * @param options.stylistic.quotes - The type of quotes to use ('single', 'double', or 'backtick'). Defaults to `'single'`.
2230
- * @returns A promise that resolves to a `TypedConfigArray` containing the ESLint configuration.
2415
+ * @returns A promise that resolves to a `TypedConfig[]` containing the ESLint configuration.
2231
2416
  */
2232
2417
  async function yaml(options = {}) {
2233
2418
  const { files = [GLOB_YAML], overrides = {}, stylistic = true } = options;
2234
2419
  const { indent = 2, quotes = "single" } = typeof stylistic === "object" ? stylistic : {};
2235
- const yamlPlugin = await interopDefault(import("eslint-plugin-yml"));
2236
- return tseslint.config({
2420
+ return [{
2237
2421
  name: "fabdeh/yaml/setup",
2238
- plugins: { yaml: yamlPlugin }
2422
+ plugins: { yaml: await interopDefault(import("eslint-plugin-yml")) }
2239
2423
  }, {
2240
2424
  name: "fabdeh/yaml/rules",
2241
2425
  language: "yaml/yaml",
@@ -2267,7 +2451,7 @@ async function yaml(options = {}) {
2267
2451
  } : {},
2268
2452
  ...overrides
2269
2453
  }
2270
- });
2454
+ }];
2271
2455
  }
2272
2456
  //#endregion
2273
2457
  //#region src/shared/constants.ts
@@ -2289,18 +2473,24 @@ const OPTIONS_SYMBOL = Symbol("options");
2289
2473
  * @param baseConfig - The base configuration to extend from.
2290
2474
  * @param options - Configuration options.
2291
2475
  * @param userConfigs - Additional user configurations that can be awaited.
2292
- * @returns A promise that resolves to a `TypedConfigArray`.
2476
+ * @returns The merged ESLint configurations.
2293
2477
  * @example
2294
2478
  * ```typescript
2479
+ * import { defineProjectConfig } from '@fabdeh/eslint-config';
2480
+ *
2295
2481
  * import baseConfig from '../../eslint.base.js';
2296
2482
  *
2297
- * const config = await defineProjectConfig(baseConfig, { vitest: true, typescript: { parserOptions: { project: './tsconfig.json' } } });
2483
+ * export default defineProjectConfig(baseConfig, { vitest: true, typescript: { parserOptions: { project: './tsconfig.json' } } });
2298
2484
  * ```
2299
2485
  */
2300
- async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2301
- const resolvedBaseConfig = await baseConfig;
2302
- const workspaceOptions = resolvedBaseConfig[OPTIONS_SYMBOL];
2486
+ function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2487
+ const workspaceOptions = baseConfig[OPTIONS_SYMBOL];
2303
2488
  const { angular: enableAngular = isPackageExists("@angular/core"), jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_PACKAGES.some((p) => isPackageExists(p)), tailwindcss: enableTailwind = false, typescript: typescriptOptions, vitest: enableVitest = isPackageExists("vitest") } = options;
2489
+ let isInEditor = workspaceOptions.isInEditor;
2490
+ if (isInEditor === void 0) {
2491
+ isInEditor = isInEditorEnv();
2492
+ if (isInEditor) console.log("[@fabdeh/eslint-config] Detected running in editor, some rules are disabled.");
2493
+ }
2304
2494
  if (enableNgrx && !enableAngular) throw new Error("NgRx rules can only be enabled if Angular rules are also enabled.");
2305
2495
  const configs = [];
2306
2496
  const stylisticOptions = workspaceOptions.stylistic === false ? false : typeof workspaceOptions.stylistic === "object" ? workspaceOptions.stylistic : {};
@@ -2336,7 +2526,10 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2336
2526
  stylistic: stylisticOptions
2337
2527
  }));
2338
2528
  }
2339
- return tseslint.config(...resolvedBaseConfig, ...await Promise.all(configs), ...await Promise.all(userConfigs));
2529
+ let composer = new FlatConfigComposer();
2530
+ composer = composer.append(baseConfig, ...configs, ...userConfigs);
2531
+ if (isInEditor) composer = composer.disableRulesFix(["prefer-const", "unused-imports/no-unused-imports"], { builtinRules: () => import("eslint/use-at-your-own-risk").then((m) => m.builtinRules) });
2532
+ return composer;
2340
2533
  }
2341
2534
  //#endregion
2342
2535
  //#region src/factories/standard-config.ts
@@ -2346,26 +2539,36 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2346
2539
  *
2347
2540
  * @param options - Configuration options.
2348
2541
  * @param userConfigs - Additional user configurations that can be awaited.
2349
- * @returns A promise that resolves to a `ConfigArrayWithOptions`.
2542
+ * @returns The merged ESLint configurations.
2350
2543
  * @example
2351
2544
  * ```typescript
2352
- * const config = await defineConfig({ vitest: true, typescript: { parserOptions: { project: './tsconfig.json' } } });
2545
+ * import { defineConfig } from '@fabdeh/eslint-config';
2546
+ *
2547
+ * export default defineConfig({ vitest: true, typescript: { parserOptions: { project: './tsconfig.json' } } });
2353
2548
  * ```
2354
2549
  */
2355
- async function defineConfig(options = {}, ...userConfigs) {
2356
- const { angular: enableAngular = isPackageExists("@angular/core"), gitignore: enableGitignore = true, jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_PACKAGES.some((p) => isPackageExists(p)), playwright: enablePlaywright = PLAYWRIGHT_PACKAGES.some((p) => isPackageExists(p)), pnpm: enableCatalogs = false, regexp: enableRegexp = true, tailwindcss: enableTailwind = false, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true, vitest: enableVitest = isPackageExists("vitest") } = options;
2550
+ function defineConfig(options = {}, ...userConfigs) {
2551
+ const { angular: enableAngular = isPackageExists("@angular/core"), gitignore: enableGitignore = true, jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_PACKAGES.some((p) => isPackageExists(p)), playwright: enablePlaywright = PLAYWRIGHT_PACKAGES.some((p) => isPackageExists(p)), pnpm: enableCatalogs = !!findUpSync("pnpm-workspace.yaml"), regexp: enableRegexp = true, tailwindcss: enableTailwind = false, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true, vitest: enableVitest = isPackageExists("vitest") } = options;
2552
+ let isInEditor = options.isInEditor;
2553
+ if (isInEditor === void 0) {
2554
+ isInEditor = isInEditorEnv();
2555
+ if (isInEditor) console.log("[@fabdeh/eslint-config] Detected running in editor, some rules are disabled.");
2556
+ }
2357
2557
  if (enableNgrx && !enableAngular) throw new Error("NgRx rules can only be enabled if Angular rules are also enabled.");
2358
2558
  const stylisticOptions = options.stylistic === false ? false : typeof options.stylistic === "object" ? options.stylistic : {};
2359
2559
  const configs = [];
2360
- if (enableGitignore) if (typeof enableGitignore === "object") configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => tseslint.config(r({
2560
+ if (enableGitignore) if (typeof enableGitignore === "object") configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
2361
2561
  name: "fabdeh/gitignore",
2362
2562
  ...enableGitignore
2363
- }))));
2364
- else configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => tseslint.config(r({
2563
+ })]));
2564
+ else configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
2365
2565
  name: "fabdeh/gitignore",
2366
2566
  strict: true
2367
- }))));
2368
- configs.push(ignores(options.ignores), javascript({ overrides: options.javascript?.overrides }), comments(), node(), imports({ stylistic: stylisticOptions }), perfectionist());
2567
+ })]));
2568
+ configs.push(ignores(options.ignores), javascript({
2569
+ isInEditor,
2570
+ overrides: options.javascript?.overrides
2571
+ }), comments(), node(), imports({ stylistic: stylisticOptions }), perfectionist());
2369
2572
  if (enableJsdoc) configs.push(jsdoc({ stylistic: stylisticOptions }));
2370
2573
  if (enableUnicorn) {
2371
2574
  const unicornOptions = resolveSubOptions(options, "unicorn");
@@ -2399,7 +2602,7 @@ async function defineConfig(options = {}, ...userConfigs) {
2399
2602
  let e2eFolderPath;
2400
2603
  if (enablePlaywright) {
2401
2604
  const playwrightOptions = resolveSubOptions(options, "playwright");
2402
- e2eFolderPath = playwrightOptions.e2eFolderPath ?? await getPlaywrightDirectory() ?? "e2e";
2605
+ e2eFolderPath = playwrightOptions.e2eFolderPath ?? getPlaywrightDirectory() ?? "e2e";
2403
2606
  configs.push(playwright({
2404
2607
  ...playwrightOptions,
2405
2608
  e2eFolderPath
@@ -2426,7 +2629,15 @@ async function defineConfig(options = {}, ...userConfigs) {
2426
2629
  stylistic: stylisticOptions
2427
2630
  }), sortPackageJson(), sortTsConfig());
2428
2631
  }
2429
- if (enableCatalogs) configs.push(pnpm());
2632
+ if (enableCatalogs) {
2633
+ const pnpmOptions = resolveSubOptions(options, "pnpm");
2634
+ configs.push(pnpm({
2635
+ isInEditor,
2636
+ json: options.jsonc !== false,
2637
+ yaml: options.yaml !== false,
2638
+ ...pnpmOptions
2639
+ }));
2640
+ }
2430
2641
  if (options.yaml ?? true) {
2431
2642
  const yamlOptions = resolveSubOptions(options, "yaml");
2432
2643
  configs.push(yaml({
@@ -2445,7 +2656,10 @@ async function defineConfig(options = {}, ...userConfigs) {
2445
2656
  const markdownOptions = resolveSubOptions(options, "markdown");
2446
2657
  configs.push(markdown(markdownOptions));
2447
2658
  }
2448
- return tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
2659
+ let composer = new FlatConfigComposer();
2660
+ composer = composer.append(...configs, ...userConfigs);
2661
+ if (isInEditor) composer = composer.disableRulesFix(["prefer-const", "unused-imports/no-unused-imports"], { builtinRules: () => import("eslint/use-at-your-own-risk").then((m) => m.builtinRules) });
2662
+ return composer;
2449
2663
  }
2450
2664
  //#endregion
2451
2665
  //#region src/factories/workspace-config.ts
@@ -2456,25 +2670,35 @@ async function defineConfig(options = {}, ...userConfigs) {
2456
2670
  *
2457
2671
  * @param options - Configuration options.
2458
2672
  * @param userConfigs - Additional user configurations that can be awaited.
2459
- * @returns A promise that resolves to a `ConfigArrayWithOptions`.
2673
+ * @returns The merged ESLint configurations.
2460
2674
  * @example
2461
2675
  * ```typescript
2462
- * const config = await defineWorkspaceConfig({ vitest: true, typescript: { parserOptions: { project: './tsconfig.json' } } });
2676
+ * import { defineWorkspaceConfig } from '@fabdeh/eslint-config';
2677
+ *
2678
+ * export default defineWorkspaceConfig({ typescript: { parserOptions: { project: './tsconfig.json' } } });
2463
2679
  * ```
2464
2680
  */
2465
- async function defineWorkspaceConfig(options = {}, ...userConfigs) {
2466
- const { gitignore: enableGitignore = true, pnpm: enableCatalogs = false, regexp: enableRegexp = true, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true } = options;
2681
+ function defineWorkspaceConfig(options = {}, ...userConfigs) {
2682
+ const { gitignore: enableGitignore = true, pnpm: enableCatalogs = !!findUpSync("pnpm-workspace.yaml"), regexp: enableRegexp = true, typescript: enableTypescript = isPackageExists("typescript"), unicorn: enableUnicorn = true } = options;
2683
+ let isInEditor = options.isInEditor;
2684
+ if (isInEditor === void 0) {
2685
+ isInEditor = isInEditorEnv();
2686
+ if (isInEditor) console.log("[@fabdeh/eslint-config] Detected running in editor, some rules are disabled.");
2687
+ }
2467
2688
  const stylisticOptions = options.stylistic === false ? false : typeof options.stylistic === "object" ? options.stylistic : {};
2468
2689
  const configs = [];
2469
- if (enableGitignore) if (typeof enableGitignore === "object") configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => tseslint.config(r({
2690
+ if (enableGitignore) if (typeof enableGitignore === "object") configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
2470
2691
  name: "fabdeh/gitignore",
2471
2692
  ...enableGitignore
2472
- }))));
2473
- else configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => tseslint.config(r({
2693
+ })]));
2694
+ else configs.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({
2474
2695
  name: "fabdeh/gitignore",
2475
2696
  strict: true
2476
- }))));
2477
- configs.push(ignores(options.ignores, "workspace"), javascript({ overrides: options.javascript?.overrides }), comments(), node(), imports({ stylistic: stylisticOptions }), perfectionist());
2697
+ })]));
2698
+ configs.push(ignores(options.ignores, "workspace"), javascript({
2699
+ isInEditor,
2700
+ overrides: options.javascript?.overrides
2701
+ }), comments(), node(), imports({ stylistic: stylisticOptions }), perfectionist());
2478
2702
  if (enableUnicorn) {
2479
2703
  const unicornOptions = resolveSubOptions(options, "unicorn");
2480
2704
  configs.push(unicorn(unicornOptions));
@@ -2499,7 +2723,15 @@ async function defineWorkspaceConfig(options = {}, ...userConfigs) {
2499
2723
  stylistic: stylisticOptions
2500
2724
  }), sortPackageJson(), sortTsConfig());
2501
2725
  }
2502
- if (enableCatalogs) configs.push(pnpm());
2726
+ if (enableCatalogs) {
2727
+ const pnpmOptions = resolveSubOptions(options, "pnpm");
2728
+ configs.push(pnpm({
2729
+ isInEditor,
2730
+ json: options.jsonc !== false,
2731
+ yaml: options.yaml !== false,
2732
+ ...pnpmOptions
2733
+ }));
2734
+ }
2503
2735
  if (options.yaml ?? true) {
2504
2736
  const yamlOptions = resolveSubOptions(options, "yaml");
2505
2737
  configs.push(yaml({
@@ -2518,9 +2750,12 @@ async function defineWorkspaceConfig(options = {}, ...userConfigs) {
2518
2750
  const markdownOptions = resolveSubOptions(options, "markdown");
2519
2751
  configs.push(markdown(markdownOptions));
2520
2752
  }
2521
- const eslintConfigWithOptions = tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
2522
- eslintConfigWithOptions[OPTIONS_SYMBOL] = options;
2523
- return eslintConfigWithOptions;
2753
+ let composer = new FlatConfigComposer();
2754
+ composer = composer.append(...configs, ...userConfigs);
2755
+ if (isInEditor) composer = composer.disableRulesFix(["prefer-const", "unused-imports/no-unused-imports"], { builtinRules: () => import("eslint/use-at-your-own-risk").then((m) => m.builtinRules) });
2756
+ const composerWithOptions = composer;
2757
+ composerWithOptions[OPTIONS_SYMBOL] = options;
2758
+ return composerWithOptions;
2524
2759
  }
2525
2760
  //#endregion
2526
- export { GLOB_HTML, GLOB_JS, GLOB_SRC, GLOB_TESTS, GLOB_TS, STYLISTIC_CONFIG_DEFAULT, angular, comments, convertPathToPosix, defineConfig, defineProjectConfig, defineWorkspaceConfig, ensurePackages, getPlaywrightDirectory, getTsConfigFileName, getWorkspaceRoot, ignores, imports, interopDefault, isDirectory, isPackageInScope, javascript, jsdoc, jsonc, markdown, ngrx, node, pathToGlobPattern, perfectionist, playwright, pnpm, regexp, resolveSubOptions, sortPackageJson, sortTsConfig, stylistic, tailwindcss, toml, typescript, unicorn, vitest, yaml };
2761
+ export { CONFIG_PRESET_FULL_OFF, CONFIG_PRESET_FULL_ON, GLOB_HTML, GLOB_JS, GLOB_SRC, GLOB_TESTS, GLOB_TS, STYLISTIC_CONFIG_DEFAULT, angular, comments, convertPathToPosix, defineConfig, defineProjectConfig, defineWorkspaceConfig, ensurePackages, getPlaywrightDirectory, getTsConfigFileName, getWorkspaceRoot, ignores, imports, interopDefault, isDirectory, isInEditorEnv, isInGitHooksOrLintStaged, isPackageInScope, javascript, jsdoc, jsonc, markdown, ngrx, node, pathToGlobPattern, perfectionist, playwright, pnpm, regexp, resolveSubOptions, sortPackageJson, sortTsConfig, stylistic, tailwindcss, toml, typescript, unicorn, vitest, yaml };