@santi020k/eslint-config-basic 3.0.0 → 3.2.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.
@@ -0,0 +1,10 @@
1
+ import { ConfigInput, EslintConfigOptions, FlatConfigArray } from '@santi020k/eslint-config-core';
2
+
3
+ interface DefineConfigMetadata {
4
+ extraConfigs: ConfigInput[];
5
+ options?: EslintConfigOptions;
6
+ }
7
+ declare const attachDefineConfigMetadata: (config: FlatConfigArray, metadata: DefineConfigMetadata) => FlatConfigArray;
8
+ declare const getDefineConfigMetadata: (config: unknown) => DefineConfigMetadata | undefined;
9
+
10
+ export { type DefineConfigMetadata, attachDefineConfigMetadata, getDefineConfigMetadata };
@@ -0,0 +1,18 @@
1
+ const DEFINE_CONFIG_METADATA = /* @__PURE__ */ Symbol.for("@santi020k/eslint-config-basic/define-config-metadata");
2
+ const attachDefineConfigMetadata = (config, metadata) => {
3
+ Object.defineProperty(config, DEFINE_CONFIG_METADATA, {
4
+ configurable: false,
5
+ enumerable: false,
6
+ value: metadata,
7
+ writable: false
8
+ });
9
+ return config;
10
+ };
11
+ const getDefineConfigMetadata = (config) => (
12
+ // eslint-disable-next-line security/detect-object-injection -- shared symbol is an internal fixed metadata key
13
+ Array.isArray(config) ? config[DEFINE_CONFIG_METADATA] : void 0
14
+ );
15
+ export {
16
+ attachDefineConfigMetadata,
17
+ getDefineConfigMetadata
18
+ };
@@ -30,29 +30,5 @@ declare const svelte: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
30
30
  declare const tanstackStart: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
31
31
  declare const vite: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
32
32
  declare const vue: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
33
- /**
34
- * @deprecated Remix merged into React Router v7. Use `reactRouter` (the
35
- * `react-router` framework key) instead. This alias loads the legacy Remix
36
- * config and will be removed in the next major version.
37
- */
38
- declare const remix: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
39
- /** @deprecated Use `angular` instead. */
40
- declare const angularConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
41
- /** @deprecated Use `expo` instead. */
42
- declare const expoConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
43
- /** @deprecated Use `nest` instead. */
44
- declare const nestConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
45
- /** @deprecated Use `next` instead. */
46
- declare const nextConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
47
- /** @deprecated Use `preact` instead. */
48
- declare const preactConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
49
- /** @deprecated Use `react` instead. */
50
- declare const reactConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
51
- /** @deprecated Use `solid` instead. */
52
- declare const solidConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
53
- /** @deprecated Use `svelte` instead. */
54
- declare const svelteConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
55
- /** @deprecated Use `vue` instead. */
56
- declare const vueConfig: (options?: FrameworkOptions) => Promise<FlatConfigArray>;
57
33
 
58
- export { type FrameworkFlags, type FrameworkName, type FrameworkOptions, angular, angularConfig, astro, createDetectedFrameworkFlags, expo, expoConfig, getBundledFrameworkConfig, hono, lit, nest, nestConfig, next, nextConfig, nuxt, preact, preactConfig, qwik, react, reactConfig, reactRouter, remix, slidev, solid, solidConfig, svelte, svelteConfig, tanstackStart, vite, vue, vueConfig };
34
+ export { type FrameworkFlags, type FrameworkName, type FrameworkOptions, angular, astro, createDetectedFrameworkFlags, expo, getBundledFrameworkConfig, hono, lit, nest, next, nuxt, preact, qwik, react, reactRouter, slidev, solid, svelte, tanstackStart, vite, vue };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  createModuleLoader
3
3
  } from "@santi020k/eslint-config-core";
4
+ import { isMissingRequestedPackage } from "./optional-package-errors.js";
4
5
  const loadModule = createModuleLoader((specifier) => import.meta.resolve(specifier, import.meta.url));
5
6
  const createDetectedFrameworkFlags = (detectedFrameworks = []) => Object.fromEntries(
6
7
  detectedFrameworks.map((framework) => [framework, true])
@@ -18,7 +19,6 @@ const frameworkLoaders = /* @__PURE__ */ new Map([
18
19
  ["qwik", async () => (await loadModule("@santi020k/eslint-config-qwik")).qwik],
19
20
  ["react", async () => (await loadModule("@santi020k/eslint-config-react")).reactConfig],
20
21
  ["react-router", async () => (await loadModule("@santi020k/eslint-config-react-router")).reactRouter],
21
- ["remix", async () => (await loadModule("@santi020k/eslint-config-remix")).default],
22
22
  ["slidev", async () => (await loadModule("@santi020k/eslint-config-slidev")).slidev],
23
23
  ["solid", async () => (await loadModule("@santi020k/eslint-config-solid")).solidConfig],
24
24
  ["svelte", async () => (await loadModule("@santi020k/eslint-config-svelte")).svelteConfig],
@@ -34,8 +34,10 @@ const loadFrameworkConfigInput = (frameworkName) => {
34
34
  if (!loader) return Promise.resolve([]);
35
35
  const pending = loader().catch((error) => {
36
36
  frameworkConfigCache.delete(frameworkName);
37
+ const specifier = `@santi020k/eslint-config-${frameworkName}`;
38
+ const remediation = isMissingRequestedPackage(error, specifier) ? `Install "${specifier}" or remove that framework from your defineConfig options.` : `The installed package "${specifier}" failed while evaluating. Inspect this error's cause for the original dependency or runtime failure.`;
37
39
  throw new Error(
38
- `Unable to load optional framework config "${frameworkName}". Install "@santi020k/eslint-config-${frameworkName}" or remove that framework from your eslintConfig options.`,
40
+ `Unable to load optional framework config "${frameworkName}". ` + remediation,
39
41
  { cause: error }
40
42
  );
41
43
  });
@@ -65,45 +67,25 @@ const svelte = createBundledFramework("svelte");
65
67
  const tanstackStart = createBundledFramework("tanstack-start");
66
68
  const vite = createBundledFramework("vite");
67
69
  const vue = createBundledFramework("vue");
68
- const remix = createBundledFramework("remix");
69
- const angularConfig = angular;
70
- const expoConfig = expo;
71
- const nestConfig = nest;
72
- const nextConfig = next;
73
- const preactConfig = preact;
74
- const reactConfig = react;
75
- const solidConfig = solid;
76
- const svelteConfig = svelte;
77
- const vueConfig = vue;
78
70
  export {
79
71
  angular,
80
- angularConfig,
81
72
  astro,
82
73
  createDetectedFrameworkFlags,
83
74
  expo,
84
- expoConfig,
85
75
  getBundledFrameworkConfig,
86
76
  hono,
87
77
  lit,
88
78
  nest,
89
- nestConfig,
90
79
  next,
91
- nextConfig,
92
80
  nuxt,
93
81
  preact,
94
- preactConfig,
95
82
  qwik,
96
83
  react,
97
- reactConfig,
98
84
  reactRouter,
99
- remix,
100
85
  slidev,
101
86
  solid,
102
- solidConfig,
103
87
  svelte,
104
- svelteConfig,
105
88
  tanstackStart,
106
89
  vite,
107
- vue,
108
- vueConfig
90
+ vue
109
91
  };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,14 @@
1
- import { EslintConfigOptions, ConfigInput, FlatConfigArray } from '@santi020k/eslint-config-core';
2
- export { DetectedFrameworkName, DetectionOptions, EslintConfigOptions, Extension, ExtensionName, ExtensionOption, FlatConfigArray, Format, FormatName, FormatOption, ImportGroupOptions, ImportedFramework, Library, LibraryName, LibraryOption, NextMode, NextModeName, NextModeOption, OptionalConfigMap, OptionalConfigName, Preset, PresetName, PresetOption, ProjectConfigOptions, ReactConfigKeys, Runtime, RuntimeName, RuntimeOption, Setting, SettingName, SettingOption, StrictMode, Testing, TestingName, TestingOption, Tool, ToolName, ToolOption, TypeScriptMode, TypeScriptOptions, coreConfig, createCoreConfig, createImportGroups, detectProjectOptions, getGlobalsForRuntime, gitignore, groups, hasReactConfig, jsConfig } from '@santi020k/eslint-config-core';
3
- export { AGENT_TARGETS, AgentTarget, EslintConfigFeatures, GenerateSkillOptions, GenerateSkillResult, generateAgentSkills, generateSkillContent } from './agent-skill-generator.js';
4
- export { angular, angularConfig, astro, expo, expoConfig, hono, lit, nest, nestConfig, next, nextConfig, nuxt, preact, preactConfig, qwik, react, reactConfig, reactRouter, remix, slidev, solid, solidConfig, svelte, svelteConfig, tanstackStart, vite, vue, vueConfig } from './frameworks.js';
5
- export { tsConfig, typescriptConfig } from '@santi020k/eslint-config-typescript';
1
+ import { FlatConfigArray, EslintConfigOptions, ConfigInput } from '@santi020k/eslint-config-core';
2
+ export { DetectedFrameworkName, DetectionOptions, EslintConfigOptions, Extension, ExtensionName, ExtensionOption, FlatConfigArray, Format, FormatName, FormatOption, ImportGroupOptions, ImportedFramework, Library, LibraryName, LibraryOption, NextMode, NextModeName, NextModeOption, OptionalConfigMap, OptionalConfigName, Preset, PresetName, PresetOption, ProjectConfigOptions, ReactConfigKeys, Runtime, RuntimeName, RuntimeOption, Setting, SettingName, SettingOption, StrictMode, Testing, TestingName, TestingOption, Tool, ToolName, ToolOption, TypeScriptMode, TypeScriptOptions, coreConfig, createCoreConfig, createGitignoreConfig, createImportGroups, detectProjectOptions, getGlobalsForRuntime, groups, hasReactConfig } from '@santi020k/eslint-config-core';
3
+ export { angular, astro, expo, hono, lit, nest, next, nuxt, preact, qwik, react, reactRouter, slidev, solid, svelte, tanstackStart, vite, vue } from './frameworks.js';
4
+ export { typescriptConfig } from '@santi020k/eslint-config-typescript';
6
5
 
6
+ /**
7
+ * Copies already-loaded plugin objects onto rule blocks that reference them.
8
+ * ESLint 10 validates plugin availability per effective config object, while
9
+ * feature packs often keep plugin setup and consumer overrides separate.
10
+ */
11
+ declare const attachReferencedPlugins: (configs: FlatConfigArray) => FlatConfigArray;
7
12
  /**
8
13
  * Generates the ESLint configuration array, applying configurations
9
14
  * and integration settings based on the input configuration.
@@ -12,10 +17,6 @@ export { tsConfig, typescriptConfig } from '@santi020k/eslint-config-typescript'
12
17
  * @param {ConfigInput[]} extraConfigs - Local flat-config overrides appended after generated config
13
18
  * @returns {FlatConfigArray} The final ESLint configuration array
14
19
  */
15
- declare const eslintConfig: (options?: EslintConfigOptions, ...extraConfigs: ConfigInput[]) => Promise<FlatConfigArray>;
16
- /**
17
- * Alias for `eslintConfig()` that reads naturally in `eslint.config.*` files.
18
- */
19
20
  declare const defineConfig: (options?: EslintConfigOptions, ...extraConfigs: ConfigInput[]) => Promise<FlatConfigArray>;
20
21
 
21
- export { defineConfig, eslintConfig };
22
+ export { attachReferencedPlugins, defineConfig };
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- import { join } from "node:path";
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
2
3
  import {
3
4
  applyDetectionControls,
4
5
  applyStrictMode,
5
6
  applyStrictProfileDefaults,
6
7
  coreConfig,
7
8
  createCoreConfig,
9
+ createGitignoreConfig,
8
10
  DEFAULT_IGNORES,
9
11
  detectProjectOptions,
10
12
  Extension,
@@ -12,7 +14,6 @@ import {
12
14
  flattenConfigInputs,
13
15
  GENERATED_CODE_IGNORES,
14
16
  getStrictMode,
15
- gitignore,
16
17
  Library,
17
18
  mergeFrameworkOption,
18
19
  mergeOptionalBucket,
@@ -28,57 +29,42 @@ import {
28
29
  Testing
29
30
  } from "@santi020k/eslint-config-core";
30
31
  import { createTypescriptConfig } from "@santi020k/eslint-config-typescript";
32
+ import { attachDefineConfigMetadata } from "./define-config-metadata.js";
31
33
  import { createDetectedFrameworkFlags } from "./frameworks.js";
32
34
  import { getIntegrationConfigs, getPrettierConfig } from "./integrations.js";
33
35
  import { resolveFramework, resolvePreset } from "./resolvers.js";
34
36
  import { buildTailwindSettingsConfig } from "./tailwind.js";
35
- import {
36
- AGENT_TARGETS,
37
- generateAgentSkills,
38
- generateSkillContent
39
- } from "./agent-skill-generator.js";
40
37
  import {
41
38
  angular,
42
- angularConfig,
43
39
  astro,
44
40
  expo,
45
- expoConfig,
46
41
  hono,
47
42
  lit,
48
43
  nest,
49
- nestConfig,
50
44
  next,
51
- nextConfig,
52
45
  nuxt,
53
46
  preact,
54
- preactConfig,
55
47
  qwik,
56
48
  react,
57
- reactConfig,
58
49
  reactRouter,
59
- remix,
60
50
  slidev,
61
51
  solid,
62
- solidConfig,
63
52
  svelte,
64
- svelteConfig,
65
53
  tanstackStart,
66
54
  vite,
67
- vue,
68
- vueConfig
55
+ vue
69
56
  } from "./frameworks.js";
70
57
  import {
71
58
  coreConfig as coreConfig2,
72
59
  createCoreConfig as createCoreConfig2,
60
+ createGitignoreConfig as createGitignoreConfig2,
73
61
  createImportGroups,
74
62
  detectProjectOptions as detectProjectOptions2,
75
63
  Extension as Extension2,
76
64
  Format,
77
65
  getGlobalsForRuntime,
78
- gitignore as gitignore2,
79
66
  groups,
80
67
  hasReactConfig,
81
- jsConfig,
82
68
  Library as Library2,
83
69
  NextMode as NextMode2,
84
70
  Preset as Preset2,
@@ -88,7 +74,7 @@ import {
88
74
  Testing as Testing2,
89
75
  Tool
90
76
  } from "@santi020k/eslint-config-core";
91
- import { tsConfig, typescriptConfig } from "@santi020k/eslint-config-typescript";
77
+ import { typescriptConfig } from "@santi020k/eslint-config-typescript";
92
78
  const buildTailwindResult = (options, entryPoint, rootDir) => ({
93
79
  cwd: options.cwd ?? rootDir,
94
80
  detectComponentClasses: options.detectComponentClasses ?? true,
@@ -125,12 +111,52 @@ const applyTestingFileOverrides = (configs, testingFiles) => {
125
111
  };
126
112
  });
127
113
  };
128
- const resolveConfigSetup = (options) => ({
129
- autoFrameworks: options?.autoFrameworks ?? true,
130
- detectRootDir: options?.detectRootDir ?? options?.tsconfigRootDir,
131
- optionMergeStrategy: options?.optionMergeStrategy ?? "merge",
132
- requestedPreset: options?.preset
133
- });
114
+ const ESLINT_CONFIG_FILENAMES = [
115
+ "eslint.config.js",
116
+ "eslint.config.cjs",
117
+ "eslint.config.mjs",
118
+ "eslint.config.ts",
119
+ "eslint.config.cts",
120
+ "eslint.config.mts"
121
+ ];
122
+ const findConfigRootFromStack = (stack = new Error().stack) => {
123
+ if (!stack) return void 0;
124
+ for (const line of stack.split("\n")) {
125
+ const configFilename = ESLINT_CONFIG_FILENAMES.find((filename) => line.includes(filename));
126
+ if (!configFilename) continue;
127
+ const filenameStart = line.indexOf(configFilename);
128
+ const fileUrlStart = line.lastIndexOf("file://", filenameStart);
129
+ const windowsPathStart = line.slice(0, filenameStart).search(/[A-Za-z]:[\\/]/);
130
+ const absolutePathStart = line.indexOf("/");
131
+ let configPathStart = absolutePathStart;
132
+ if (windowsPathStart >= 0) configPathStart = windowsPathStart;
133
+ if (fileUrlStart >= 0) configPathStart = fileUrlStart;
134
+ try {
135
+ if (configPathStart < 0) continue;
136
+ const configPath = line.slice(configPathStart, filenameStart + configFilename.length);
137
+ return dirname(configPath.startsWith("file:") ? fileURLToPath(configPath) : configPath);
138
+ } catch {
139
+ }
140
+ }
141
+ return void 0;
142
+ };
143
+ const resolveConfigSetup = (options) => {
144
+ const {
145
+ autoFrameworks = true,
146
+ detectRootDir,
147
+ optionMergeStrategy = "merge",
148
+ preset: requestedPreset,
149
+ root,
150
+ tsconfigRootDir
151
+ } = options ?? {};
152
+ const implicitRootDir = root ?? detectRootDir ?? tsconfigRootDir ?? findConfigRootFromStack();
153
+ return {
154
+ autoFrameworks,
155
+ detectRootDir: implicitRootDir,
156
+ optionMergeStrategy,
157
+ requestedPreset
158
+ };
159
+ };
134
160
  const resolveUniqueLibraries = (libraries, tailwind) => {
135
161
  const uniqueLibraries = [...new Set(libraries)];
136
162
  if (tailwind === false) return uniqueLibraries.filter((library) => library !== Library.Tailwind);
@@ -154,12 +180,26 @@ const resolveBucketDefaults = (detected) => ({
154
180
  detectedTesting: detected.testing ?? [],
155
181
  detectedTools: detected.tools ?? []
156
182
  });
183
+ const scopeWorkspaceDetection = (detected, isWorkspace) => isWorkspace ? {
184
+ ...detected,
185
+ detectedFrameworks: [],
186
+ libraries: [],
187
+ nextMode: void 0,
188
+ runtime: Runtime.Universal
189
+ } : detected;
190
+ const resolveDetectedOptions = (detectRootDir, detection, requestedPreset) => {
191
+ const rawDetected = detectProjectOptions(detectRootDir);
192
+ const shouldDetectProjects = requestedPreset === Preset.Monorepo || rawDetected.preset === Preset.Monorepo;
193
+ const detected = applyDetectionControls(rawDetected, detection, { projects: shouldDetectProjects });
194
+ const hasDetectedProjects = Object.keys(detected.projects ?? {}).length > 0;
195
+ return scopeWorkspaceDetection(detected, hasDetectedProjects);
196
+ };
157
197
  const resolveNextModeValue = (options, presetDefaults, detected) => options?.nextMode ?? presetDefaults.nextMode ?? detected.nextMode ?? NextMode.Pages;
158
198
  const resolveRuntimeValue = (options, presetDefaults, detected, usePresetRuntime) => options?.runtime ?? (usePresetRuntime ? presetDefaults.runtime : void 0) ?? detected.runtime ?? Runtime.Universal;
159
199
  const resolveSettingsValue = (options, detected) => options?.settings ?? detected.settings ?? [];
160
200
  const resolveTypescriptValue = (options, presetDefaults, detected) => options?.typescript ?? presetDefaults.typescript ?? detected.typescript ?? false;
161
201
  const needsReactAutoAdd = (frameworks) => Boolean(
162
- frameworks.next ?? frameworks.expo ?? frameworks.remix ?? frameworks["react-router"] ?? (frameworks["tanstack-start"] && !frameworks.solid)
202
+ frameworks.next ?? frameworks.expo ?? frameworks["react-router"] ?? (frameworks["tanstack-start"] && !frameworks.solid)
163
203
  ) && !frameworks.react;
164
204
  const applyFrameworkImpliedDeps = (frameworks) => {
165
205
  const result = { ...frameworks };
@@ -168,12 +208,12 @@ const applyFrameworkImpliedDeps = (frameworks) => {
168
208
  if (result.nuxt && !result.vue) result.vue = true;
169
209
  return result;
170
210
  };
171
- const buildIgnoresConfig = (useDefaultIgnores, useGeneratedCodeIgnores, useGitignore, options) => ({
211
+ const buildIgnoresConfig = (useDefaultIgnores, useGeneratedCodeIgnores, useGitignore, options, rootDir) => ({
172
212
  defaultIgnores: useDefaultIgnores ? [{
173
213
  ignores: [...DEFAULT_IGNORES, ...useGeneratedCodeIgnores ? GENERATED_CODE_IGNORES : []],
174
214
  name: "eslint-config-basic/default-ignores"
175
215
  }] : [],
176
- gitignoreConfig: useGitignore ? gitignore : [],
216
+ gitignoreConfig: useGitignore ? createGitignoreConfig(rootDir) : [],
177
217
  userIgnores: options?.ignores?.length ? [{
178
218
  ignores: options.ignores,
179
219
  name: "eslint-config-basic/ignores"
@@ -227,6 +267,16 @@ const buildEslintConfigs = async (params) => {
227
267
  { hasReact, hasSolid, hasSvelte, hasVue, runtime, tsconfigRootDir }
228
268
  );
229
269
  const get = (name) => fw[name] ?? [];
270
+ const typescriptConfigs = resolvedTypescript ? createTypescriptConfig({
271
+ ...resolvedTypescript,
272
+ tsconfigRootDir: resolvedTypescript.tsconfigRootDir ?? tsconfigRootDir
273
+ }) : [];
274
+ const untypedTypescriptConfigs = typescriptConfigs.filter(
275
+ (config) => config.name === "eslint-config-typescript/untyped-files"
276
+ );
277
+ const typedTypescriptConfigs = typescriptConfigs.filter(
278
+ (config) => config.name !== "eslint-config-typescript/untyped-files"
279
+ );
230
280
  return [
231
281
  ...defaultIgnores,
232
282
  ...userIgnores,
@@ -260,7 +310,6 @@ const buildEslintConfigs = async (params) => {
260
310
  ...get("solid"),
261
311
  ...get("angular"),
262
312
  ...get("qwik"),
263
- ...get("remix"),
264
313
  ...get("react-router"),
265
314
  ...get("tanstack-start"),
266
315
  ...get("nuxt"),
@@ -268,10 +317,7 @@ const buildEslintConfigs = async (params) => {
268
317
  ...get("lit"),
269
318
  ...get("slidev"),
270
319
  ...get("vite"),
271
- ...resolvedTypescript ? createTypescriptConfig({
272
- ...resolvedTypescript,
273
- tsconfigRootDir: resolvedTypescript.tsconfigRootDir ?? tsconfigRootDir
274
- }) : [],
320
+ ...typedTypescriptConfigs,
275
321
  ...get("astro"),
276
322
  ...resolvedFrameworks.next && nextMode === NextMode.AppRouter ? [{
277
323
  files: ["app/**/*.{ts,tsx}", "src/**/*.{ts,tsx}"],
@@ -291,6 +337,7 @@ const buildEslintConfigs = async (params) => {
291
337
  }
292
338
  },
293
339
  ...tailwindOptions ? [buildTailwindSettingsConfig(tailwindOptions)] : [],
340
+ ...untypedTypescriptConfigs,
294
341
  ...await getPrettierConfig(uniqueTools)
295
342
  ];
296
343
  };
@@ -329,11 +376,6 @@ const logEslintDebug = ({
329
376
  }, null, 2)}
330
377
  `);
331
378
  };
332
- const emitBasicWarnings = (options) => {
333
- if (options?.frameworks && "remix" in options.frameworks) {
334
- process.emitWarning('[eslint-config-basic] Warning: `frameworks.remix` is deprecated and will be removed in the next major. Please use `frameworks["react-router"]` instead.');
335
- }
336
- };
337
379
  const emitAstroDoctorWarning = (extensions, frameworks) => {
338
380
  if (!extensions.includes(Extension.AstroDoctor) || frameworks.astro) return;
339
381
  process.emitWarning(
@@ -344,17 +386,62 @@ const resolveProjectConfigs = async (configuredProjects, projectDefaults, detect
344
386
  Object.entries(configuredProjects).map(async ([projectPath, projectOptions]) => {
345
387
  const projectRoot = join(detectRootDir ?? process.cwd(), projectPath);
346
388
  const inheritedOptions = mergeProjectOptions(projectDefaults ?? {}, projectOptions);
347
- const scopedConfigs = await eslintConfig({
389
+ const scopedConfigs = await defineConfig({
348
390
  autoFrameworks,
349
391
  ...inheritedOptions,
350
- detectRootDir: inheritedOptions.detectRootDir ?? projectRoot,
351
392
  projectDefaults: void 0,
352
393
  projects: void 0,
353
- tsconfigRootDir: inheritedOptions.tsconfigRootDir ?? projectRoot
394
+ root: inheritedOptions.root ?? inheritedOptions.detectRootDir ?? projectRoot
354
395
  });
355
- return scopedConfigs.map((config) => scopeConfigToProject(config, projectPath));
396
+ return scopedConfigs.map((config) => config.name === "eslint-config/gitignore" ? config : scopeConfigToProject(config, projectPath));
356
397
  })
357
398
  );
399
+ const resolveInheritedProjectDefaults = (options) => mergeProjectOptions(
400
+ {
401
+ ...options?.detection === void 0 ? {} : { detection: options.detection },
402
+ ...options?.tailwind === void 0 ? {} : { tailwind: options.tailwind }
403
+ },
404
+ options?.projectDefaults ?? {}
405
+ );
406
+ const getPluginNameFromRule = (ruleName, availablePluginNames) => {
407
+ const segments = ruleName.split("/");
408
+ if (segments.length < 2) return void 0;
409
+ const registeredPrefix = [...availablePluginNames].filter((pluginName) => ruleName.startsWith(`${pluginName}/`)).sort((left, right) => right.length - left.length)[0];
410
+ if (registeredPrefix) return registeredPrefix;
411
+ return ruleName.startsWith("@") && segments.length > 2 ? segments.slice(0, 2).join("/") : segments[0];
412
+ };
413
+ const attachReferencedPlugins = (configs) => {
414
+ const availablePlugins = /* @__PURE__ */ new Map();
415
+ const ambiguousPluginNames = /* @__PURE__ */ new Set();
416
+ for (const config of configs) {
417
+ for (const [pluginName, plugin] of Object.entries(config.plugins ?? {})) {
418
+ const existingPlugin = availablePlugins.get(pluginName);
419
+ if (existingPlugin && existingPlugin !== plugin) {
420
+ availablePlugins.delete(pluginName);
421
+ ambiguousPluginNames.add(pluginName);
422
+ } else if (!ambiguousPluginNames.has(pluginName)) {
423
+ availablePlugins.set(pluginName, plugin);
424
+ }
425
+ }
426
+ }
427
+ return configs.map((config) => {
428
+ const requiredPlugins = new Set(
429
+ Object.keys(config.rules ?? {}).map((ruleName) => getPluginNameFromRule(ruleName, availablePlugins.keys())).filter((pluginName) => pluginName !== void 0)
430
+ );
431
+ const missingPluginEntries = [...requiredPlugins].flatMap((pluginName) => {
432
+ const plugin = availablePlugins.get(pluginName);
433
+ return !Object.hasOwn(config.plugins ?? {}, pluginName) && plugin ? [[pluginName, plugin]] : [];
434
+ });
435
+ if (missingPluginEntries.length === 0) return config;
436
+ return {
437
+ ...config,
438
+ plugins: {
439
+ ...config.plugins,
440
+ ...Object.fromEntries(missingPluginEntries)
441
+ }
442
+ };
443
+ });
444
+ };
358
445
  const getConfigsParams = (options, rootDir, runtime, uniqueLibraries, uniqueSettings, resolvedFrameworks) => {
359
446
  const hasReact = !!resolvedFrameworks.react;
360
447
  const hasVue = !!resolvedFrameworks.vue;
@@ -365,7 +452,13 @@ const getConfigsParams = (options, rootDir, runtime, uniqueLibraries, uniqueSett
365
452
  const useGeneratedCodeIgnores = !uniqueSettings.includes(Setting.NoGeneratedCodeIgnores);
366
453
  const tailwindOptions = uniqueLibraries.includes(Library.Tailwind) ? resolveTailwindOptions(rootDir, options?.tailwind) : void 0;
367
454
  const runtimeCoreConfig = runtime === Runtime.Universal ? coreConfig : createCoreConfig(runtime);
368
- const { defaultIgnores, gitignoreConfig, userIgnores } = buildIgnoresConfig(useDefaultIgnores, useGeneratedCodeIgnores, useGitignore, options);
455
+ const { defaultIgnores, gitignoreConfig, userIgnores } = buildIgnoresConfig(
456
+ useDefaultIgnores,
457
+ useGeneratedCodeIgnores,
458
+ useGitignore,
459
+ options,
460
+ rootDir
461
+ );
369
462
  return {
370
463
  astroOptions: { hasReact, hasSolid, hasSvelte, hasVue },
371
464
  defaultIgnores,
@@ -375,8 +468,7 @@ const getConfigsParams = (options, rootDir, runtime, uniqueLibraries, uniqueSett
375
468
  userIgnores
376
469
  };
377
470
  };
378
- const eslintConfig = async (options, ...extraConfigs) => {
379
- emitBasicWarnings(options);
471
+ const defineConfig = async (options, ...extraConfigs) => {
380
472
  const {
381
473
  detection,
382
474
  extensions: optExtensions,
@@ -390,12 +482,7 @@ const eslintConfig = async (options, ...extraConfigs) => {
390
482
  workspacePrefixes
391
483
  } = options ?? {};
392
484
  const { autoFrameworks, detectRootDir, optionMergeStrategy, requestedPreset } = resolveConfigSetup(options);
393
- const shouldDefaultProjectDetection = requestedPreset === Preset.Monorepo;
394
- const detected = applyDetectionControls(
395
- detectProjectOptions(detectRootDir),
396
- detection,
397
- { projects: shouldDefaultProjectDetection }
398
- );
485
+ const detected = resolveDetectedOptions(detectRootDir, detection, requestedPreset);
399
486
  const { frameworkDefaults, preset, presetDefaults } = resolvePresetMeta(requestedPreset, detected, autoFrameworks);
400
487
  const configuredProjects = resolveConfiguredProjects(detected, options);
401
488
  const { detectedExtensions, detectedFormats, detectedLibraries, detectedTesting, detectedTools } = resolveBucketDefaults(detected);
@@ -496,17 +583,16 @@ const eslintConfig = async (options, ...extraConfigs) => {
496
583
  });
497
584
  const projectConfigs = await resolveProjectConfigs(
498
585
  configuredProjects,
499
- options?.projectDefaults,
586
+ resolveInheritedProjectDefaults(options),
500
587
  detectRootDir,
501
588
  options?.autoFrameworks
502
589
  );
503
590
  const allConfigs = [...configs, ...projectConfigs.flat(), ...flattenConfigInputs(extraConfigs)];
504
591
  const patchedConfigs = workspacePrefixes?.length ? patchImportGroups(allConfigs, workspacePrefixes) : allConfigs;
505
- return applyStrictMode(patchedConfigs, strict);
592
+ const finalConfig = attachReferencedPlugins(applyStrictMode(patchedConfigs, strict));
593
+ return attachDefineConfigMetadata(finalConfig, { extraConfigs, options });
506
594
  };
507
- const defineConfig = eslintConfig;
508
595
  export {
509
- AGENT_TARGETS,
510
596
  Extension2 as Extension,
511
597
  Format,
512
598
  Library2 as Library,
@@ -518,46 +604,32 @@ export {
518
604
  Testing2 as Testing,
519
605
  Tool,
520
606
  angular,
521
- angularConfig,
522
607
  astro,
608
+ attachReferencedPlugins,
523
609
  coreConfig2 as coreConfig,
524
610
  createCoreConfig2 as createCoreConfig,
611
+ createGitignoreConfig2 as createGitignoreConfig,
525
612
  createImportGroups,
526
613
  defineConfig,
527
614
  detectProjectOptions2 as detectProjectOptions,
528
- eslintConfig,
529
615
  expo,
530
- expoConfig,
531
- generateAgentSkills,
532
- generateSkillContent,
533
616
  getGlobalsForRuntime,
534
- gitignore2 as gitignore,
535
617
  groups,
536
618
  hasReactConfig,
537
619
  hono,
538
- jsConfig,
539
620
  lit,
540
621
  nest,
541
- nestConfig,
542
622
  next,
543
- nextConfig,
544
623
  nuxt,
545
624
  preact,
546
- preactConfig,
547
625
  qwik,
548
626
  react,
549
- reactConfig,
550
627
  reactRouter,
551
- remix,
552
628
  slidev,
553
629
  solid,
554
- solidConfig,
555
630
  svelte,
556
- svelteConfig,
557
631
  tanstackStart,
558
- tsConfig,
559
632
  typescriptConfig,
560
633
  vite,
561
- vue,
562
- vueConfig
634
+ vue
563
635
  };
@@ -1,40 +1,48 @@
1
1
  import {
2
2
  createModuleLoader,
3
- Tool
3
+ resolveConfigFeatures
4
4
  } from "@santi020k/eslint-config-core";
5
+ import { isMissingRequestedPackage } from "./optional-package-errors.js";
5
6
  const loadModule = createModuleLoader((specifier) => import.meta.resolve(specifier, import.meta.url));
6
- let integrationsModule;
7
- const loadIntegrations = async () => {
8
- integrationsModule ??= loadModule("@santi020k/eslint-config-integrations");
7
+ const packCache = /* @__PURE__ */ new Map();
8
+ const loadFeaturePack = async (specifier) => {
9
+ let pending = packCache.get(specifier);
10
+ if (!pending) {
11
+ pending = loadModule(specifier);
12
+ packCache.set(specifier, pending);
13
+ }
9
14
  try {
10
- return await integrationsModule;
15
+ const module = await pending;
16
+ if (!module.features) {
17
+ throw new Error(`Installed package "${specifier}" does not export a feature registry.`);
18
+ }
19
+ return module.features;
11
20
  } catch (error) {
12
- integrationsModule = void 0;
21
+ packCache.delete(specifier);
22
+ const remediation = isMissingRequestedPackage(error, specifier) ? "Install it for the selected options, or remove those options from defineConfig()." : "The installed feature pack failed while evaluating. Inspect this error's cause for the original dependency or runtime failure.";
13
23
  throw new Error(
14
- 'Unable to load optional package "@santi020k/eslint-config-integrations". Install it for integrations, or remove the selected integration options.',
24
+ `Unable to load optional feature pack "${specifier}". ` + remediation,
15
25
  { cause: error }
16
26
  );
17
27
  }
18
28
  };
29
+ const loadSelectedPacks = async (libraries, tools, testing, formats, extensions) => (await Promise.all([
30
+ extensions.length > 0 ? loadFeaturePack("@santi020k/eslint-config-extensions/registry") : [],
31
+ formats.length > 0 ? loadFeaturePack("@santi020k/eslint-config-formats/registry") : [],
32
+ libraries.length > 0 ? loadFeaturePack("@santi020k/eslint-config-libraries/registry") : [],
33
+ testing.length > 0 ? loadFeaturePack("@santi020k/eslint-config-testing/registry") : [],
34
+ tools.length > 0 ? loadFeaturePack("@santi020k/eslint-config-tools/registry") : []
35
+ ])).flat();
19
36
  const getIntegrationConfigs = async (libraries, tools, testing, formats, extensions) => {
20
- if (libraries.length === 0 && tools.length === 0 && testing.length === 0 && formats.length === 0 && extensions.length === 0) return [];
21
- const module = await loadIntegrations();
22
- if (!module.getIntegrationConfigs) {
23
- throw new Error(
24
- 'Installed "@santi020k/eslint-config-integrations" is incompatible. Update it to v3 or newer.'
25
- );
26
- }
27
- return module.getIntegrationConfigs(libraries, tools, testing, formats, extensions);
37
+ const selected = [...libraries, ...tools, ...testing, ...formats, ...extensions];
38
+ if (selected.length === 0) return [];
39
+ const features = await loadSelectedPacks(libraries, tools, testing, formats, extensions);
40
+ return resolveConfigFeatures(features, selected);
28
41
  };
29
42
  const getPrettierConfig = async (tools) => {
30
- if (!tools.includes(Tool.Prettier)) return [];
31
- const module = await loadIntegrations();
32
- if (!module.getPrettierConfig) {
33
- throw new Error(
34
- 'Installed "@santi020k/eslint-config-integrations" is incompatible. Update it to v3 or newer.'
35
- );
36
- }
37
- return module.getPrettierConfig(tools);
43
+ if (tools.length === 0) return [];
44
+ const features = await loadFeaturePack("@santi020k/eslint-config-tools/registry");
45
+ return resolveConfigFeatures(features, tools, "finalizer");
38
46
  };
39
47
  export {
40
48
  getIntegrationConfigs,
@@ -0,0 +1,3 @@
1
+ declare const isMissingRequestedPackage: (error: unknown, specifier: string) => boolean;
2
+
3
+ export { isMissingRequestedPackage };