@fabdeh/eslint-config 0.9.1 → 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.
Files changed (4) hide show
  1. package/README.md +115 -60
  2. package/dist/index.d.mts +660 -345
  3. package/dist/index.mjs +209 -241
  4. package/package.json +21 -34
package/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  import tseslint from "typescript-eslint";
2
2
  import { statSync } from "node:fs";
3
- import { dirname, join } from "node:path";
3
+ import { readFile } from "node:fs/promises";
4
+ import { dirname, join, normalize, resolve } from "node:path";
4
5
  import process from "node:process";
6
+ import { glob } from "glob";
5
7
  import { isPackageExists } from "local-pkg";
6
8
  import eslintComments from "@eslint-community/eslint-plugin-eslint-comments";
7
9
  import * as importX from "eslint-plugin-import-x";
@@ -31,22 +33,6 @@ const GLOB_TS_EXT = "?([cm])ts?(x)";
31
33
  */
32
34
  const GLOB_TS = `**/*.${GLOB_TS_EXT}`;
33
35
  /**
34
- * A glob pattern that matches all CSS files.
35
- */
36
- const GLOB_CSS = "**/*.css";
37
- /**
38
- * A glob pattern that matches all PostCSS files.
39
- */
40
- const GLOB_POSTCSS = "**/*.{p,post}css";
41
- /**
42
- * A glob pattern that matches all LESS files.
43
- */
44
- const GLOB_LESS = "**/*.less";
45
- /**
46
- * A glob pattern that matches all SCSS files.
47
- */
48
- const GLOB_SCSS = "**/*.scss";
49
- /**
50
36
  * A glob pattern that matches all JSON files.
51
37
  */
52
38
  const GLOB_JSON = "**/*.json";
@@ -79,29 +65,14 @@ const GLOB_YAML = "**/*.y?(a)ml";
79
65
  */
80
66
  const GLOB_TOML = "**/*.toml";
81
67
  /**
82
- * A glob pattern that matches XML files.
83
- */
84
- const GLOB_XML = "**/*.xml";
85
- /**
86
- * A glob pattern that matches SVG files.
87
- */
88
- const GLOB_SVG = "**/*.svg";
89
- /**
90
- * A glob pattern that matches GraphQL files.
91
- */
92
- const GLOB_GRAPHQL = "**/*.{g,graph}ql";
93
- /**
94
68
  * A glob pattern that matches HTML files.
95
69
  */
96
70
  const GLOB_HTML = "**/*.htm?(l)";
97
71
  /**
98
72
  * A glob pattern that matches test files.
99
73
  */
100
- const GLOB_TESTS = [
101
- `**/*.spec.?([cm])[jt]s`,
102
- `**/*.test.?([cm])[jt]s`,
103
- `**/test-setup.?([cm])[jt]s`
104
- ];
74
+ const GLOB_TESTS = [`**/*.spec.?([cm])[jt]s`, `**/*.test.?([cm])[jt]s`];
75
+ const GLOB_VITEST = [...GLOB_TESTS, `**/test-setup.?([cm])[jt]s`];
105
76
  /**
106
77
  * A glob pattern that matches files to exclude from linting.
107
78
  */
@@ -111,7 +82,7 @@ const GLOB_EXCLUDE = [
111
82
  "**/package-lock.json",
112
83
  "**/yarn.lock",
113
84
  "**/pnpm-lock.yaml",
114
- "**/bun.lockb",
85
+ "**/bun.lock?(b)",
115
86
  "**/output",
116
87
  "**/coverage",
117
88
  "**/temp",
@@ -137,8 +108,7 @@ const GLOB_EXCLUDE = [
137
108
  "**/LICENSE*",
138
109
  "**/__snapshots__",
139
110
  "**/auto-import?(s).d.ts",
140
- "**/components.d.ts",
141
- "**/prettier-types.ts"
111
+ "**/components.d.ts"
142
112
  ];
143
113
  //#endregion
144
114
  //#region src/shared/utils.ts
@@ -172,6 +142,19 @@ function fileExists(path) {
172
142
  }
173
143
  }
174
144
  /**
145
+ * Checks whether the given path points to an existing directory.
146
+ *
147
+ * @param path - Filesystem path to validate.
148
+ * @returns `true` when the path exists and is a directory; otherwise `false`.
149
+ */
150
+ function isDirectory(path) {
151
+ try {
152
+ return statSync(path).isDirectory();
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+ /**
175
158
  * Retrieves the root directory of the workspace.
176
159
  *
177
160
  * This function determines the root directory of the workspace by checking for specific files
@@ -236,7 +219,7 @@ async function ensurePackages(packages) {
236
219
  /**
237
220
  * Returns the object representation of the configuration or an empty object if it is a boolean.
238
221
  *
239
- * @param options - The {@link CreateConfigOptions} object to extract the sub-options from.
222
+ * @param options - The {@link DefineConfigOptions} object to extract the sub-options from.
240
223
  * @param key - The property name.
241
224
  * @returns An object representing the required options.
242
225
  */
@@ -245,6 +228,67 @@ function resolveSubOptions(options, key) {
245
228
  if (typeof option === "boolean") return {};
246
229
  return option ?? {};
247
230
  }
231
+ /**
232
+ * Replace Windows with posix style paths
233
+ *
234
+ * @param filePath - Path to convert
235
+ * @returns Converted filepath
236
+ */
237
+ function convertPathToPosix(filePath) {
238
+ return normalize(filePath).replaceAll("\\", "/");
239
+ }
240
+ /**
241
+ * Checks if a provided path is a directory and returns a glob string matching
242
+ * all files under that directory if so, the path itself otherwise.
243
+ *
244
+ * Reason for this is that `glob` needs `/**` to collect all the files under a
245
+ * directory where as our previous implementation without `glob` simply walked
246
+ * a directory that is passed. So this is to maintain backwards compatibility.
247
+ *
248
+ * Also makes sure all path separators are POSIX style for `glob` compatibility.
249
+ *
250
+ * @param [options] - An options object
251
+ * @param [options.extensions] - An array of accepted extensions
252
+ * @param [options.cwd] - The cwd to use to resolve relative pathnames
253
+ * @returns A function that takes a pathname and returns a glob that
254
+ * matches all files with the provided extensions if
255
+ * pathname is a directory.
256
+ */
257
+ function pathToGlobPattern(options) {
258
+ const cwd = options?.cwd ?? process.cwd();
259
+ const extensions = options?.extensions?.map((ext) => ext.replace(/^\./, "")) ?? [];
260
+ const suffix = extensions.length === 0 ? "/**/*" : extensions.length === 1 ? `/**/*.${extensions[0]}` : `/**/*.{${extensions.join(",")}}`;
261
+ return (path) => {
262
+ let newPath = path;
263
+ if (isDirectory(resolve(cwd, path))) newPath = path.replace(/[/\\]$/, "") + suffix;
264
+ return convertPathToPosix(newPath);
265
+ };
266
+ }
267
+ /**
268
+ * Finds the first valid Playwright test directory declared via `testDir` in any
269
+ * `playwright.config.ts` file under the current workspace.
270
+ *
271
+ * The function:
272
+ * - Locates Playwright config files, excluding `node_modules`.
273
+ * - Reads each config as text.
274
+ * - Extracts `testDir` with a regex.
275
+ * - Resolves `testDir` relative to the config file location.
276
+ * - Returns the first resolved path that exists as a directory.
277
+ *
278
+ * @returns Absolute path to the first existing Playwright test directory, or `undefined` if none is found.
279
+ */
280
+ async function getPlaywrightDirectory() {
281
+ const playwrightConfigs = await glob("**/playwright.config.ts", { ignore: ["**/node_modules/**"] });
282
+ for (const config of playwrightConfigs) try {
283
+ const configContent = await readFile(config, "utf-8");
284
+ const match = /testDir:\s*["'](.+)["'],?/.exec(configContent);
285
+ if (match) {
286
+ const testDir = match[1];
287
+ const resolvedTestDir = resolve(dirname(config), testDir);
288
+ if (isDirectory(resolvedTestDir)) return resolvedTestDir;
289
+ }
290
+ } catch {}
291
+ }
248
292
  //#endregion
249
293
  //#region src/configs/angular.ts
250
294
  /**
@@ -377,195 +421,6 @@ function comments() {
377
421
  });
378
422
  }
379
423
  //#endregion
380
- //#region src/configs/stylistic.ts
381
- const STYLISTIC_CONFIG_DEFAULT = {
382
- semi: true,
383
- arrowParens: true,
384
- braceStyle: "1tbs",
385
- quoteProps: "as-needed"
386
- };
387
- /**
388
- * Generates a stylistic ESLint configuration.
389
- *
390
- * @param [options] - Optional configuration options to customize the stylistic rules.
391
- * @returns A promise that resolves to an array of ESLint configurations.
392
- * @example
393
- * const config = await stylistic({ semi: false });
394
- */
395
- async function stylistic(options = {}) {
396
- const stylisticOptions = {
397
- ...STYLISTIC_CONFIG_DEFAULT,
398
- ...typeof options.stylistic === "boolean" ? {} : options.stylistic
399
- };
400
- const stylisticPlugin = await interopDefault(import("@stylistic/eslint-plugin"));
401
- const config = stylisticPlugin.configs.customize(stylisticOptions);
402
- return tseslint.config({
403
- name: "fabdeh/stylistic/rules",
404
- files: [GLOB_SRC],
405
- plugins: { "@stylistic": stylisticPlugin },
406
- rules: {
407
- ...config.rules,
408
- "@stylistic/comma-dangle": ["error", {
409
- arrays: "always-multiline",
410
- objects: "always-multiline"
411
- }],
412
- "@stylistic/no-extra-semi": "error",
413
- "@stylistic/operator-linebreak": [
414
- "error",
415
- "after",
416
- { overrides: {
417
- "?": "before",
418
- ":": "before"
419
- } }
420
- ]
421
- }
422
- });
423
- }
424
- //#endregion
425
- //#region src/configs/formatters.ts
426
- /**
427
- * Merges the provided Prettier options with any overrides.
428
- *
429
- * @param options - The base Prettier options to merge.
430
- * @param overrides - Required overrides for the Prettier options. Must include a `parser` property.
431
- * @returns The merged Prettier options.
432
- */
433
- function mergePrettierOptions(options, overrides) {
434
- return {
435
- ...options,
436
- ...overrides,
437
- plugins: [...overrides.plugins ?? [], ...options.plugins ?? []]
438
- };
439
- }
440
- /**
441
- * Configures and returns an array of formatters based on the provided options.
442
- *
443
- * @param options - The options for configuring the formatters. If set to `true`, default options will be used.
444
- * @param stylistic - The stylistic options for the formatters.
445
- * @param hasAngularTemplateParser - Optional argument which indicates whether angular rules already registered the template parser or not.
446
- * @returns A promise that resolves to a `TypedConfigArray` containing the configured formatters.
447
- */
448
- async function formatters(options = {}, stylistic = {}, hasAngularTemplateParser = false) {
449
- if (options === true) {
450
- const isPrettierPluginXmlInScope = isPackageInScope("@prettier/plugin-xml");
451
- options = {
452
- css: true,
453
- graphql: true,
454
- html: true,
455
- markdown: true,
456
- slidev: isPackageExists("@slidev/cli"),
457
- svg: isPrettierPluginXmlInScope,
458
- xml: isPrettierPluginXmlInScope
459
- };
460
- }
461
- await ensurePackages([
462
- "eslint-plugin-format",
463
- options.markdown && options.slidev ? "prettier-plugin-slidev" : void 0,
464
- options.xml || options.svg ? "@prettier/plugin-xml" : void 0
465
- ]);
466
- const { indent, quotes, semi } = {
467
- ...STYLISTIC_CONFIG_DEFAULT,
468
- ...stylistic
469
- };
470
- const prettierOptions = {
471
- endOfLine: "auto",
472
- printWidth: 120,
473
- semi,
474
- singleQuote: quotes === "single",
475
- tabWidth: typeof indent === "number" ? indent : 2,
476
- trailingComma: "all",
477
- useTabs: indent === "tab",
478
- ...options.options
479
- };
480
- const prettierXmlOptions = {
481
- xmlQuoteAttributes: "double",
482
- xmlSelfClosingSpace: true,
483
- xmlSortAttributesByKey: false,
484
- xmlWhitespaceSensitivity: "ignore"
485
- };
486
- const formatPlugin = await interopDefault(import("eslint-plugin-format"));
487
- const configs = [{
488
- name: "fabdeh/formatter/setup",
489
- plugins: { format: formatPlugin }
490
- }];
491
- if (options.css) configs.push({
492
- name: "fabdeh/formatter/css",
493
- languageOptions: { parser: formatPlugin.parserPlain },
494
- files: [GLOB_CSS, GLOB_POSTCSS],
495
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "css" })] }
496
- }, {
497
- name: "fabdeh/formatter/scss",
498
- languageOptions: { parser: formatPlugin.parserPlain },
499
- files: [GLOB_SCSS],
500
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "scss" })] }
501
- }, {
502
- name: "fabdeh/formatter/less",
503
- languageOptions: { parser: formatPlugin.parserPlain },
504
- files: [GLOB_LESS],
505
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "less" })] }
506
- });
507
- if (options.html) configs.push({
508
- name: "fabdeh/formatter/html",
509
- ...hasAngularTemplateParser ? {} : { languageOptions: { parser: formatPlugin.parserPlain } },
510
- files: [GLOB_HTML],
511
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "html" })] }
512
- });
513
- if (options.xml) configs.push({
514
- name: "fabdeh/formatter/xml",
515
- languageOptions: { parser: formatPlugin.parserPlain },
516
- files: [GLOB_XML],
517
- rules: { "format/prettier": ["error", mergePrettierOptions({
518
- ...prettierXmlOptions,
519
- ...prettierOptions
520
- }, {
521
- parser: "xml",
522
- plugins: ["@prettier/plugin-xml"]
523
- })] }
524
- });
525
- if (options.svg) configs.push({
526
- name: "fabdeh/formatter/svg",
527
- languageOptions: { parser: formatPlugin.parserPlain },
528
- files: [GLOB_SVG],
529
- rules: { "format/prettier": ["error", mergePrettierOptions({
530
- ...prettierXmlOptions,
531
- ...prettierOptions
532
- }, {
533
- parser: "xml",
534
- plugins: ["@prettier/plugin-xml"]
535
- })] }
536
- });
537
- if (options.markdown) {
538
- const GLOB_SLIDEV = options.slidev ? options.slidev === true ? ["**/.slides.md"] : options.slidev.files : [];
539
- configs.push({
540
- name: "fabdeh/formatter/markdown",
541
- languageOptions: { parser: formatPlugin.parserPlain },
542
- files: [GLOB_MARKDOWN],
543
- ignores: GLOB_SLIDEV,
544
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, {
545
- parser: "markdown",
546
- embeddedLanguageFormatting: "off"
547
- })] }
548
- });
549
- if (options.slidev) configs.push({
550
- name: "fabdeh/formatter/slidev",
551
- languageOptions: { parser: formatPlugin.parserPlain },
552
- files: GLOB_SLIDEV,
553
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, {
554
- embeddedLanguageFormatting: "off",
555
- parser: "slidev",
556
- plugins: ["prettier-plugin-slidev"]
557
- })] }
558
- });
559
- }
560
- if (options.graphql) configs.push({
561
- name: "fabdeh/formatter/graphql",
562
- languageOptions: { parser: formatPlugin.parserPlain },
563
- files: [GLOB_GRAPHQL],
564
- rules: { "format/prettier": ["error", mergePrettierOptions(prettierOptions, { parser: "graphql" })] }
565
- });
566
- return tseslint.config(configs);
567
- }
568
- //#endregion
569
424
  //#region src/configs/ignore.ts
570
425
  /**
571
426
  * Generates a configuration array for ESLint with default and user-defined ignore patterns.
@@ -1515,6 +1370,64 @@ function perfectionist() {
1515
1370
  });
1516
1371
  }
1517
1372
  //#endregion
1373
+ //#region src/configs/playwright.ts
1374
+ /**
1375
+ * Configures and returns an ESLint flat config for Playwright test files.
1376
+ *
1377
+ * @param [options] - Options used to customize the Playwright config.
1378
+ * @param [options.e2eFolderPath] - The path to the e2e folder to prepend to each `options.files` glob pattern (defaults to `e2e`).
1379
+ * @param [options.files] - File globs to target (defaults to `GLOB_TESTS`).
1380
+ * @param [options.overrides] - Custom ESLint rule overrides merged last.
1381
+ * @returns A promise that resolves to a single-entry ESLint config array.
1382
+ * @example
1383
+ * const config = await playwright({
1384
+ * e2eFolderPath: 'tests/e2e',
1385
+ * files: ['**\/*.test.ts'], // will become 'tests\/e2e\/**\/*.test.ts' because of the e2eFolderPath option
1386
+ * overrides: {
1387
+ * 'playwright/no-page-pause': 'off',
1388
+ * },
1389
+ * });
1390
+ */
1391
+ async function playwright(options = {}) {
1392
+ const { e2eFolderPath = "e2e", files = GLOB_TESTS.map((glob) => convertPathToPosix(join(e2eFolderPath, glob))), overrides = {} } = options;
1393
+ if (!isDirectory(e2eFolderPath)) return [];
1394
+ const playwrightPlugin = await interopDefault(import("eslint-plugin-playwright"));
1395
+ return [{
1396
+ name: "fabdeh/playwright/rules",
1397
+ plugins: { playwright: playwrightPlugin },
1398
+ files,
1399
+ rules: {
1400
+ ...playwrightPlugin.configs.recommended.rules,
1401
+ "max-lines": "off",
1402
+ "@typescript-eslint/consistent-type-assertions": "off",
1403
+ "@typescript-eslint/no-empty-function": "off",
1404
+ "@typescript-eslint/no-unsafe-assignment": "off",
1405
+ "@typescript-eslint/no-unsafe-call": "off",
1406
+ "@typescript-eslint/no-unsafe-member-access": "off",
1407
+ "@typescript-eslint/unbound-method": "off",
1408
+ "unicorn/no-null": "off",
1409
+ "playwright/no-get-by-title": "error",
1410
+ "playwright/no-nth-methods": "error",
1411
+ "playwright/prefer-comparison-matcher": "error",
1412
+ "playwright/prefer-equality-matcher": "error",
1413
+ "playwright/prefer-lowercase-title": ["error", { allowedPrefixes: [
1414
+ "GET",
1415
+ "POST",
1416
+ "PUT",
1417
+ "DELETE",
1418
+ "PATCH",
1419
+ "HEAD",
1420
+ "OPTIONS"
1421
+ ] }],
1422
+ "playwright/prefer-native-locators": "error",
1423
+ "playwright/prefer-to-be": "error",
1424
+ "playwright/prefer-to-contain": "error",
1425
+ ...getJsDocRules("off", true, "both"),
1426
+ ...overrides
1427
+ }
1428
+ }];
1429
+ }
1430
+ //#endregion
1518
1431
  //#region src/configs/pnpm.ts
1519
1432
  /**
1520
1433
  * Generates an array containing configuration settings for PNPM workspace-specific rules and plugins.
@@ -1807,6 +1720,51 @@ function sortTsConfig() {
1807
1720
  });
1808
1721
  }
1809
1722
  //#endregion
1723
+ //#region src/configs/stylistic.ts
1724
+ const STYLISTIC_CONFIG_DEFAULT = {
1725
+ semi: true,
1726
+ arrowParens: true,
1727
+ braceStyle: "1tbs",
1728
+ quoteProps: "as-needed"
1729
+ };
1730
+ /**
1731
+ * Generates a stylistic ESLint configuration.
1732
+ *
1733
+ * @param [options] - Optional configuration options to customize the stylistic rules.
1734
+ * @returns A promise that resolves to an array of ESLint configurations.
1735
+ * @example
1736
+ * const config = await stylistic({ semi: false });
1737
+ */
1738
+ async function stylistic(options = {}) {
1739
+ const stylisticOptions = {
1740
+ ...STYLISTIC_CONFIG_DEFAULT,
1741
+ ...typeof options.stylistic === "boolean" ? {} : options.stylistic
1742
+ };
1743
+ const stylisticPlugin = await interopDefault(import("@stylistic/eslint-plugin"));
1744
+ const config = stylisticPlugin.configs.customize(stylisticOptions);
1745
+ return tseslint.config({
1746
+ name: "fabdeh/stylistic/rules",
1747
+ files: [GLOB_SRC],
1748
+ plugins: { "@stylistic": stylisticPlugin },
1749
+ rules: {
1750
+ ...config.rules,
1751
+ "@stylistic/comma-dangle": ["error", {
1752
+ arrays: "always-multiline",
1753
+ objects: "always-multiline"
1754
+ }],
1755
+ "@stylistic/no-extra-semi": "error",
1756
+ "@stylistic/operator-linebreak": [
1757
+ "error",
1758
+ "after",
1759
+ { overrides: {
1760
+ "?": "before",
1761
+ ":": "before"
1762
+ } }
1763
+ ]
1764
+ }
1765
+ });
1766
+ }
1767
+ //#endregion
1810
1768
  //#region src/configs/tailwindcss.ts
1811
1769
  /**
1812
1770
  * Generates an ESLint configuration array for Tailwind CSS using better-tailwindcss plugin.
@@ -2186,21 +2144,24 @@ function unicorn(options = {}) {
2186
2144
  * Configures and returns an ESLint configuration array for Vitest.
2187
2145
  *
2188
2146
  * @param [options] - The options to customize the configuration.
2147
+ * @param [options.files] - File globs to target (defaults to `GLOB_VITEST`).
2189
2148
  * @param [options.overrides] - Custom rule overrides.
2190
2149
  * @param [options.useJestDom] - Whether to use the `@testing-library/jest-dom` plugin.
2191
2150
  * @param [options.useTestingLibrary] - Whether to use the `@testing-library/angular` plugin.
2151
+ * @param [options.e2eFolderPath] - The path to the e2e folder to ignore (this is only used internally by the `defineConfig` function, not by the user).
2192
2152
  * @returns A promise that resolves to the ESLint configuration array.
2193
2153
  * @example
2194
2154
  * const config = await vitest({
2155
+ * files: ['**\/*.spec.ts'],
2195
2156
  * overrides: {
2196
- * 'no-console': 'warn',
2157
+ * 'vitest/prefer-lowercase-title': 'warn',
2197
2158
  * },
2198
2159
  * useJestDom: true,
2199
2160
  * useTestingLibrary: false,
2200
2161
  * });
2201
2162
  */
2202
2163
  async function vitest(options = {}) {
2203
- const { overrides = {}, useJestDom = isPackageExists("@testing-library/jest-dom"), useTestingLibrary = isPackageExists("@testing-library/angular") } = options;
2164
+ const { files = GLOB_VITEST, overrides = {}, useJestDom = isPackageExists("@testing-library/jest-dom"), useTestingLibrary = isPackageExists("@testing-library/angular"), enableVitestGlobals = true, e2eFolderPath } = options;
2204
2165
  const [vitestPlugin, jestDomPlugin, testingLibraryPlugin] = await Promise.all([
2205
2166
  interopDefault(import("@vitest/eslint-plugin")),
2206
2167
  useJestDom ? interopDefault(import("eslint-plugin-jest-dom")) : Promise.resolve(void 0),
@@ -2215,11 +2176,11 @@ async function vitest(options = {}) {
2215
2176
  },
2216
2177
  languageOptions: { globals: {
2217
2178
  ...globals.node,
2218
- ...globals.vitest,
2219
- ...vitestPlugin.environments.env.globals
2179
+ ...enableVitestGlobals ? vitestPlugin.environments.env.globals : {}
2220
2180
  } },
2221
2181
  settings: { vitest: { typecheck: true } },
2222
- files: [...GLOB_TESTS],
2182
+ files,
2183
+ ...e2eFolderPath ? { ignores: [pathToGlobPattern()(e2eFolderPath)] } : {},
2223
2184
  rules: {
2224
2185
  ...vitestPlugin.configs.recommended.rules,
2225
2186
  ...jestDomPlugin?.configs["flat/recommended"].rules,
@@ -2316,6 +2277,7 @@ const NGRX_PACKAGES = [
2316
2277
  "@ngrx/signals",
2317
2278
  "@ngrx/operators"
2318
2279
  ];
2280
+ const PLAYWRIGHT_PACKAGES = ["@playwright/test", "playwright"];
2319
2281
  const OPTIONS_SYMBOL = Symbol("options");
2320
2282
  //#endregion
2321
2283
  //#region src/factories/project-config.ts
@@ -2354,10 +2316,6 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2354
2316
  if (enableAngular) {
2355
2317
  const angularOptions = resolveSubOptions(options, "angular");
2356
2318
  configs.push(angular(angularOptions));
2357
- if (workspaceOptions.formatters) {
2358
- const htmlFormatters = resolvedBaseConfig.find((c) => c.name === "fabdeh/formatter/html");
2359
- if (htmlFormatters) delete htmlFormatters.languageOptions;
2360
- }
2361
2319
  }
2362
2320
  if (enableNgrx) {
2363
2321
  const ngrxOptions = resolveSubOptions(options, "ngrx");
@@ -2395,7 +2353,7 @@ async function defineProjectConfig(baseConfig, options = {}, ...userConfigs) {
2395
2353
  * ```
2396
2354
  */
2397
2355
  async function defineConfig(options = {}, ...userConfigs) {
2398
- const { angular: enableAngular = isPackageExists("@angular/core"), gitignore: enableGitignore = true, jsdoc: enableJsdoc = options.type === "lib", ngrx: enableNgrx = NGRX_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;
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;
2399
2357
  if (enableNgrx && !enableAngular) throw new Error("NgRx rules can only be enabled if Angular rules are also enabled.");
2400
2358
  const stylisticOptions = options.stylistic === false ? false : typeof options.stylistic === "object" ? options.stylistic : {};
2401
2359
  const configs = [];
@@ -2438,9 +2396,21 @@ async function defineConfig(options = {}, ...userConfigs) {
2438
2396
  useRelaxedNamingConventionForCamelAndPascalCases: typescriptOptions.useRelaxedNamingConventionForCamelAndPascalCases
2439
2397
  }));
2440
2398
  }
2399
+ let e2eFolderPath;
2400
+ if (enablePlaywright) {
2401
+ const playwrightOptions = resolveSubOptions(options, "playwright");
2402
+ e2eFolderPath = playwrightOptions.e2eFolderPath ?? await getPlaywrightDirectory() ?? "e2e";
2403
+ configs.push(playwright({
2404
+ ...playwrightOptions,
2405
+ e2eFolderPath
2406
+ }));
2407
+ }
2441
2408
  if (enableVitest) {
2442
2409
  const vitestOptions = resolveSubOptions(options, "vitest");
2443
- configs.push(vitest(vitestOptions));
2410
+ configs.push(vitest({
2411
+ ...vitestOptions,
2412
+ e2eFolderPath
2413
+ }));
2444
2414
  }
2445
2415
  if (enableTailwind) {
2446
2416
  const tailwindcssOptions = resolveSubOptions(options, "tailwindcss");
@@ -2475,7 +2445,6 @@ async function defineConfig(options = {}, ...userConfigs) {
2475
2445
  const markdownOptions = resolveSubOptions(options, "markdown");
2476
2446
  configs.push(markdown(markdownOptions));
2477
2447
  }
2478
- if (options.formatters) configs.push(formatters(options.formatters, typeof stylisticOptions === "boolean" ? {} : stylisticOptions, Boolean(enableAngular)));
2479
2448
  return tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
2480
2449
  }
2481
2450
  //#endregion
@@ -2549,10 +2518,9 @@ async function defineWorkspaceConfig(options = {}, ...userConfigs) {
2549
2518
  const markdownOptions = resolveSubOptions(options, "markdown");
2550
2519
  configs.push(markdown(markdownOptions));
2551
2520
  }
2552
- if (options.formatters) configs.push(formatters(options.formatters, typeof stylisticOptions === "boolean" ? {} : stylisticOptions, false));
2553
2521
  const eslintConfigWithOptions = tseslint.config(...await Promise.all(configs), ...await Promise.all(userConfigs));
2554
2522
  eslintConfigWithOptions[OPTIONS_SYMBOL] = options;
2555
2523
  return eslintConfigWithOptions;
2556
2524
  }
2557
2525
  //#endregion
2558
- export { GLOB_HTML, GLOB_JS, GLOB_SRC, GLOB_TESTS, GLOB_TS, STYLISTIC_CONFIG_DEFAULT, angular, comments, defineConfig, defineProjectConfig, defineWorkspaceConfig, ensurePackages, formatters, getTsConfigFileName, getWorkspaceRoot, ignores, imports, interopDefault, isPackageInScope, javascript, jsdoc, jsonc, markdown, ngrx, node, perfectionist, pnpm, regexp, resolveSubOptions, sortPackageJson, sortTsConfig, stylistic, tailwindcss, toml, typescript, unicorn, vitest, yaml };
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 };