@santi020k/eslint-config-lite 2.0.0-beta.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @santi020k/eslint-config-lite
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`dfba51d`](https://github.com/santi020k/eslint-config-basic/commit/dfba51dca7387a71a263af206eb86fd8df15f387) Thanks [@santi020k](https://github.com/santi020k)! - Improve config migration handling and enhance TypeScript options detection
8
+
9
+ - [#87](https://github.com/santi020k/eslint-config-basic/pull/87) [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc) Thanks [@santi020k](https://github.com/santi020k)! - Add `@santi020k/eslint-config-lite` as an optional composer package for projects that want to install framework and integration config packages manually. The full `@santi020k/eslint-config-basic` package remains the recommended default install.
10
+
11
+ Expose integration composition helpers from `@santi020k/eslint-config-integrations` so the lite package can lazy-load integration configs only when selected.
12
+
13
+ Teach `basic-eslint doctor` to recognize lite configs and warn when detected frameworks or integrations are missing their manually installed config packages.
14
+
15
+ Add `basic-eslint doctor --lite-install` to print the detected package-manager install command for switching a project to the lite package, including framework config packages, integrations, ESLint, and TypeScript when needed.
16
+
17
+ Document package-choice metrics, a Basic-to-Lite migration recipe, and a lite-specific `Preset.All` warning.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [[`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`dfba51d`](https://github.com/santi020k/eslint-config-basic/commit/dfba51dca7387a71a263af206eb86fd8df15f387), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`59158db`](https://github.com/santi020k/eslint-config-basic/commit/59158dbc0b48de041062f4d0c75b1f7e3a1e779c), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc), [`59158db`](https://github.com/santi020k/eslint-config-basic/commit/59158dbc0b48de041062f4d0c75b1f7e3a1e779c), [`2ee503b`](https://github.com/santi020k/eslint-config-basic/commit/2ee503be55f5484624ee2e873fe59f348709eadc)]:
22
+ - @santi020k/eslint-config-core@2.0.0
23
+ - @santi020k/eslint-config-typescript@2.0.0
24
+
25
+ ## 2.0.0-beta.3
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [[`af152cd`](https://github.com/santi020k/eslint-config-basic/commit/af152cda17961f9de8ca7bf069739f151fca65be)]:
30
+ - @santi020k/eslint-config-core@2.0.0-beta.3
31
+ - @santi020k/eslint-config-typescript@2.0.0-beta.3
32
+
33
+ ## 2.0.0-beta.2
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies []:
38
+ - @santi020k/eslint-config-core@2.0.0-beta.2
39
+ - @santi020k/eslint-config-typescript@2.0.0-beta.2
40
+
3
41
  ## 2.0.0-beta.1
4
42
 
5
43
  ### Minor Changes
@@ -1,4 +1,7 @@
1
- import { loadModule } from "./lazy.js";
1
+ import {
2
+ createModuleLoader
3
+ } from "@santi020k/eslint-config-core";
4
+ const loadModule = createModuleLoader((specifier) => import.meta.resolve(specifier));
2
5
  const createDetectedFrameworkFlags = (detectedFrameworks = []) => Object.fromEntries(
3
6
  detectedFrameworks.map((framework) => [framework, true])
4
7
  );
@@ -30,6 +33,7 @@ const loadFrameworkConfigInput = (frameworkName) => {
30
33
  const loader = frameworkLoaders.get(frameworkName);
31
34
  if (!loader) return Promise.resolve([]);
32
35
  const pending = loader().catch((error) => {
36
+ frameworkConfigCache.delete(frameworkName);
33
37
  throw new Error(
34
38
  `Unable to load optional framework config "${frameworkName}". Install "@santi020k/eslint-config-${frameworkName}" when using @santi020k/eslint-config-lite, or remove that framework from your eslintConfig options.`,
35
39
  { cause: error }
package/dist/index.js CHANGED
@@ -1,25 +1,34 @@
1
- import { existsSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import {
3
+ applyDetectionControls,
4
+ applyStrictMode,
5
+ applyStrictProfileDefaults,
4
6
  coreConfig,
5
7
  createCoreConfig,
8
+ DEFAULT_IGNORES,
6
9
  detectProjectOptions,
7
10
  Extension,
8
- Format,
11
+ findTailwindEntryPoint,
12
+ GENERATED_CODE_IGNORES,
13
+ getStrictMode,
9
14
  gitignore,
10
15
  Library,
16
+ mergeFrameworkOption,
17
+ mergeOptionalBucket,
11
18
  NextMode,
19
+ patchImportGroups,
12
20
  Preset,
21
+ resolveTsconfigRootDir,
22
+ resolveTypescriptOptions,
13
23
  Runtime,
14
- Setting,
15
- Testing,
16
- Tool
24
+ scopeConfigToProject,
25
+ Setting
17
26
  } from "@santi020k/eslint-config-core";
18
27
  import { createTypescriptConfig } from "@santi020k/eslint-config-typescript";
19
- import { applyStrictMode } from "./compose.js";
20
28
  import { createDetectedFrameworkFlags } from "./frameworks.js";
21
29
  import { getIntegrationConfigs, getPrettierConfig } from "./integrations.js";
22
30
  import { resolveFramework, resolvePreset } from "./resolvers.js";
31
+ import { buildTailwindSettingsConfig } from "./tailwind.js";
23
32
  import {
24
33
  angular,
25
34
  angularConfig,
@@ -56,7 +65,7 @@ import {
56
65
  createImportGroups,
57
66
  detectProjectOptions as detectProjectOptions2,
58
67
  Extension as Extension2,
59
- Format as Format2,
68
+ Format,
60
69
  getGlobalsForRuntime,
61
70
  gitignore as gitignore2,
62
71
  hasReactConfig,
@@ -67,249 +76,10 @@ import {
67
76
  ReactConfigKeys,
68
77
  Runtime as Runtime2,
69
78
  Setting as Setting2,
70
- Testing as Testing2,
71
- Tool as Tool2
79
+ Testing,
80
+ Tool
72
81
  } from "@santi020k/eslint-config-core";
73
82
  import { tsConfig, typescriptConfig } from "@santi020k/eslint-config-typescript";
74
- const toUniqueArray = (values) => [...new Set(values)];
75
- const DEFAULT_IGNORES = [
76
- "**/dist/**",
77
- "**/build/**",
78
- "**/coverage/**",
79
- "**/.turbo/**",
80
- "**/.next/**",
81
- "**/.open-next/**",
82
- "**/.astro/**",
83
- "**/.svelte-kit/**",
84
- "**/.vercel/**",
85
- "**/.wrangler/**",
86
- "**/playwright-report/**",
87
- "**/test-results/**",
88
- "**/node_modules/**",
89
- "**/tsconfig.tsbuildinfo",
90
- // Package manager lock files (machine-generated, slow to lint)
91
- "**/pnpm-lock.yaml",
92
- "**/yarn.lock",
93
- "**/package-lock.json",
94
- "**/bun.lock",
95
- "**/bun.lockb",
96
- // AI coding-assistant artifacts (rule/skill folders managed by tools, not source code)
97
- "**/.agent/**",
98
- "**/.agents/**",
99
- "**/.aider*",
100
- "**/.aider*/**",
101
- "**/.claude/**",
102
- "**/.clinerules/**",
103
- "**/.codex/**",
104
- "**/.copilot/**",
105
- "**/.cursor/**",
106
- "**/.gemini/**",
107
- "**/.kiro/**",
108
- "**/.opencode/**",
109
- "**/.roo/**",
110
- "**/.windsurf/**"
111
- ];
112
- const GENERATED_CODE_IGNORES = [
113
- "**/__generated__/**",
114
- "**/generated/**",
115
- "**/codegen/**",
116
- "**/*.generated.*",
117
- "**/*.gen.*",
118
- "**/graphql.ts",
119
- "**/graphql.tsx",
120
- "**/gql/**",
121
- "**/.prisma/**"
122
- ];
123
- const TAILWIND_ENTRYPOINT_CANDIDATES = [
124
- "src/styles/global.css",
125
- "src/app/globals.css",
126
- "src/globals.css",
127
- "src/index.css",
128
- "app/globals.css",
129
- "styles/global.css"
130
- ];
131
- const OPTIONAL_BUCKETS = {
132
- extensions: Object.values(Extension),
133
- formats: Object.values(Format),
134
- libraries: Object.values(Library),
135
- testing: Object.values(Testing),
136
- tools: Object.values(Tool)
137
- };
138
- const mergeArrayOption = (detectedValues, presetValues, explicitValues, strategy) => {
139
- if (strategy === "replace") {
140
- if (explicitValues) return toUniqueArray(explicitValues);
141
- if (presetValues) return toUniqueArray(presetValues);
142
- return toUniqueArray(detectedValues);
143
- }
144
- return toUniqueArray([
145
- ...detectedValues,
146
- ...presetValues ?? [],
147
- ...explicitValues ?? []
148
- ]);
149
- };
150
- const isOptionalBucketValue = (bucket, value) => OPTIONAL_BUCKETS[bucket].includes(value);
151
- const getFeatureEntries = (options, bucket, enabled) => [
152
- ...Object.entries(options?.features ?? {}),
153
- ...Object.entries(options?.integrations ?? {})
154
- ].filter(([name, value]) => value === enabled && isOptionalBucketValue(bucket, name)).map(([name]) => name);
155
- const applyFeatureDisables = (values, options, bucket) => {
156
- const disabled = new Set(getFeatureEntries(options, bucket, false));
157
- return values.filter((value) => !disabled.has(value));
158
- };
159
- const mergeOptionalBucket = (bucket, detectedValues, presetValues, explicitValues, options, strategy) => {
160
- const featureEntries = getFeatureEntries(options, bucket, true);
161
- const hasExplicitInput = explicitValues !== void 0 || featureEntries.length > 0;
162
- const mergedExplicit = hasExplicitInput ? [...explicitValues ?? [], ...featureEntries] : void 0;
163
- return applyFeatureDisables(
164
- mergeArrayOption(detectedValues, presetValues, mergedExplicit, strategy),
165
- options,
166
- bucket
167
- );
168
- };
169
- const mergeFrameworkOption = (detectedFrameworks, presetFrameworks, explicitFrameworks, strategy) => {
170
- if (strategy === "replace") {
171
- if (explicitFrameworks) return { ...explicitFrameworks };
172
- if (presetFrameworks) return { ...presetFrameworks };
173
- return { ...detectedFrameworks };
174
- }
175
- return {
176
- ...detectedFrameworks,
177
- ...presetFrameworks ?? {},
178
- ...explicitFrameworks ?? {}
179
- };
180
- };
181
- const resolveDetectionOptions = (detection, defaults = {}) => {
182
- const defaultControls = {
183
- extensions: true,
184
- formats: true,
185
- frameworks: true,
186
- libraries: true,
187
- nextMode: true,
188
- projects: false,
189
- runtime: true,
190
- testing: true,
191
- tools: true,
192
- typescript: true,
193
- ...defaults
194
- };
195
- if (detection === false) {
196
- return Object.fromEntries(
197
- Object.keys(defaultControls).map((key) => [key, false])
198
- );
199
- }
200
- if (detection === true || detection === void 0) {
201
- return defaultControls;
202
- }
203
- return {
204
- ...defaultControls,
205
- ...detection
206
- };
207
- };
208
- const applyArrayDetectionControls = (controls, detected) => ({
209
- detectedFrameworks: controls.frameworks ? detected.detectedFrameworks : [],
210
- extensions: controls.extensions ? detected.extensions : [],
211
- formats: controls.formats ? detected.formats : [],
212
- libraries: controls.libraries ? detected.libraries : [],
213
- testing: controls.testing ? detected.testing : [],
214
- tools: controls.tools ? detected.tools : []
215
- });
216
- const applyScalarDetectionControls = (controls, detected) => ({
217
- nextMode: controls.nextMode ? detected.nextMode : void 0,
218
- preset: controls.typescript && controls.runtime ? detected.preset : void 0,
219
- projects: controls.projects ? detected.projects : void 0,
220
- runtime: controls.runtime ? detected.runtime : void 0,
221
- typescript: controls.typescript ? detected.typescript : false
222
- });
223
- const applyDetectionControls = (detected, detection, defaults) => {
224
- const controls = resolveDetectionOptions(detection, defaults);
225
- return {
226
- ...detected,
227
- ...applyArrayDetectionControls(controls, detected),
228
- ...applyScalarDetectionControls(controls, detected)
229
- };
230
- };
231
- const getStrictMode = (explicitStrict, presetStrict) => explicitStrict ?? presetStrict ?? false;
232
- const applyStrictProfileDefaults = (extensions, strict) => {
233
- if (strict !== "pedantic") return extensions;
234
- return toUniqueArray([...extensions, Extension.BestPractices]);
235
- };
236
- const hasTsconfig = (rootDir) => [
237
- "tsconfig.eslint.json",
238
- "tsconfig.json",
239
- "tsconfig.base.json",
240
- "tsconfig.app.json",
241
- "tsconfig.node.json"
242
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- config root is user-selected project context
243
- ].some((fileName) => existsSync(join(rootDir, fileName)));
244
- const resolveTypescriptOptions = (typescript) => {
245
- if (!typescript || typescript === "off") return false;
246
- if (typescript === true) return { mode: "type-aware" };
247
- if (typeof typescript === "string") return { mode: typescript };
248
- if (typescript.mode === "off") return false;
249
- return {
250
- ...typescript,
251
- mode: typescript.mode ?? "type-aware"
252
- };
253
- };
254
- const resolveTsconfigRootDir = (rootDir, typescript, explicitRootDir) => {
255
- if (explicitRootDir) return explicitRootDir;
256
- return resolveTypescriptOptions(typescript) && hasTsconfig(rootDir) ? rootDir : void 0;
257
- };
258
- const findTailwindEntryPoint = (rootDir) => TAILWIND_ENTRYPOINT_CANDIDATES.find(
259
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- probes known stylesheet candidates under project root
260
- (candidate) => existsSync(join(rootDir, candidate))
261
- );
262
- const scopeFilePattern = (projectPath, pattern) => {
263
- if (typeof pattern === "string") {
264
- const isNegated = pattern.startsWith("!");
265
- const basePattern = (isNegated ? pattern.slice(1) : pattern).replace(/^\.\//, "");
266
- const scoped = `${projectPath.replace(/\/$/, "")}/${basePattern}`;
267
- return isNegated ? `!${scoped}` : scoped;
268
- }
269
- if (Array.isArray(pattern)) {
270
- return pattern.map((item) => scopeFilePattern(projectPath, item));
271
- }
272
- return pattern;
273
- };
274
- const scopeConfigToProject = (config, projectPath) => {
275
- if ("ignores" in config && !config.files) {
276
- return {
277
- ...config,
278
- ignores: config.ignores?.map((pattern) => scopeFilePattern(projectPath, pattern))
279
- };
280
- }
281
- return {
282
- ...config,
283
- files: Array.isArray(config.files) ? config.files.map((pattern) => scopeFilePattern(projectPath, pattern)) : [`${projectPath.replace(/\/$/, "")}/**/*`]
284
- };
285
- };
286
- const patchImportGroupsConfig = (config, workspacePatterns) => {
287
- const rule = config.rules?.["simple-import-sort/imports"];
288
- if (!Array.isArray(rule) || !rule[1]) return config;
289
- const ruleOpts = rule[1];
290
- const existingGroups = ruleOpts.groups ?? [];
291
- if (existingGroups.length === 0) return config;
292
- const externalIdx = existingGroups.findIndex((g) => g.some((p) => p.includes("^@?\\w")));
293
- const insertAt = externalIdx >= 0 ? externalIdx : existingGroups.length;
294
- return {
295
- ...config,
296
- rules: {
297
- ...config.rules,
298
- "simple-import-sort/imports": [
299
- rule[0],
300
- { ...ruleOpts, groups: [...existingGroups.slice(0, insertAt), workspacePatterns, ...existingGroups.slice(insertAt)] }
301
- ]
302
- }
303
- };
304
- };
305
- const patchImportGroups = (allConfigs, workspacePrefixes) => {
306
- const workspacePatterns = workspacePrefixes.map(
307
- (p) => `^${p.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}(/.*|$)`
308
- );
309
- return allConfigs.map(
310
- (item) => Array.isArray(item) ? item : patchImportGroupsConfig(item, workspacePatterns)
311
- );
312
- };
313
83
  const resolveLiteSetup = (options) => ({
314
84
  autoFrameworks: options?.autoFrameworks ?? true,
315
85
  detectRootDir: options?.detectRootDir ?? options?.tsconfigRootDir,
@@ -361,7 +131,22 @@ const buildLiteIgnoresConfig = (useDefaultIgnores, useGeneratedCodeIgnores, useG
361
131
  name: "eslint-config-lite/ignores"
362
132
  }] : []
363
133
  });
364
- const logLiteDebug = (autoFrameworks, detectRootDir, nextMode, optionMergeStrategy, preset, resolvedFrameworks, resolvedTypescript, runtime, tsconfigRootDir, uniqueExtensions, uniqueFormats, uniqueLibraries, uniqueTesting, uniqueTools) => {
134
+ const logLiteDebug = ({
135
+ autoFrameworks,
136
+ detectRootDir,
137
+ nextMode,
138
+ optionMergeStrategy,
139
+ preset,
140
+ resolvedFrameworks,
141
+ resolvedTypescript,
142
+ runtime,
143
+ tsconfigRootDir,
144
+ uniqueExtensions,
145
+ uniqueFormats,
146
+ uniqueLibraries,
147
+ uniqueTesting,
148
+ uniqueTools
149
+ }) => {
365
150
  if (!process.env.ESLINT_CONFIG_LITE_DEBUG && !process.env.ESLINT_BASIC_DEBUG) return;
366
151
  process.stdout.write(`[ESLint Config Lite] Resolved options: ${JSON.stringify({
367
152
  autoFrameworks,
@@ -381,56 +166,53 @@ const logLiteDebug = (autoFrameworks, detectRootDir, nextMode, optionMergeStrate
381
166
  }, null, 2)}
382
167
  `);
383
168
  };
169
+ const FRAMEWORK_EXTRA_OPTS = {
170
+ astro: (ctx) => ({ hasReact: ctx.hasReact, hasSolid: ctx.hasSolid, hasSvelte: ctx.hasSvelte, hasVue: ctx.hasVue }),
171
+ hono: (ctx) => ({ runtime: ctx.runtime }),
172
+ slidev: (ctx) => ({ runtime: ctx.runtime }),
173
+ vite: (ctx) => ({ runtime: ctx.runtime })
174
+ };
175
+ const resolveEnabledFrameworks = async (frameworks, ctx) => {
176
+ const entries = await Promise.all(
177
+ Object.entries(frameworks).filter((entry) => Boolean(entry[1])).map(
178
+ ([name, value]) => (
179
+ // eslint-disable-next-line security/detect-object-injection
180
+ resolveFramework(name, value, FRAMEWORK_EXTRA_OPTS[name]?.(ctx)).then((config) => [name, config])
181
+ )
182
+ )
183
+ );
184
+ return Object.fromEntries(entries);
185
+ };
384
186
  const buildLiteEslintConfigs = async (params) => {
385
187
  const {
386
- angularParam,
387
- astroOptions,
388
- astroParam,
389
188
  defaultIgnores,
390
- expoParam,
189
+ frameworkConfigs,
391
190
  gitignoreConfig,
392
- honoParam,
393
- litParam,
394
- nestParam,
191
+ hasReact,
395
192
  nextMode,
396
- nextParam,
397
- nuxtParam,
398
- preactParam,
399
- qwikParam,
400
- reactParam,
401
- reactRouterParam,
402
- remixParam,
403
193
  resolvedFrameworks,
404
194
  resolvedTypescript,
405
195
  runtimeCoreConfig,
406
- slidevParam,
407
- solidParam,
408
- svelteParam,
409
- tailwindEntryPoint,
410
- tanstackStartParam,
196
+ tailwindOptions,
411
197
  tsconfigRootDir,
412
198
  uniqueExtensions,
413
199
  uniqueFormats,
414
200
  uniqueLibraries,
415
201
  uniqueTesting,
416
202
  uniqueTools,
417
- userIgnores,
418
- viteParam,
419
- vueParam
203
+ userIgnores
420
204
  } = params;
421
- const { hasReact } = astroOptions;
205
+ const get = (name) => {
206
+ const entry = Object.entries(frameworkConfigs).find(([k]) => k === name);
207
+ return entry ? entry[1] : [];
208
+ };
209
+ const tailwindSettingsConfig = buildTailwindSettingsConfig(tailwindOptions);
422
210
  return [
423
211
  ...defaultIgnores,
424
212
  ...userIgnores,
425
- // Settings
426
213
  ...gitignoreConfig,
427
- // Global TSConfig Root Dir fix
428
214
  ...tsconfigRootDir ? [{
429
- languageOptions: {
430
- parserOptions: {
431
- tsconfigRootDir
432
- }
433
- },
215
+ languageOptions: { parserOptions: { tsconfigRootDir } },
434
216
  name: "eslint-config-lite/tsconfig-root-dir"
435
217
  }] : [],
436
218
  {
@@ -447,66 +229,59 @@ const buildLiteEslintConfigs = async (params) => {
447
229
  },
448
230
  name: "eslint-config-lite/commonjs"
449
231
  },
450
- // Core JS config with runtime-aware globals
451
232
  ...runtimeCoreConfig,
452
- // React config (included if any React-based framework is used/passed)
453
- ...hasReact ? reactParam : [],
454
- ...nextParam,
455
- ...expoParam,
456
- ...nestParam,
457
- ...honoParam,
458
- ...vueParam,
459
- ...svelteParam,
460
- ...solidParam,
461
- ...angularParam,
462
- ...qwikParam,
463
- ...remixParam,
464
- ...reactRouterParam,
465
- ...tanstackStartParam,
466
- ...nuxtParam,
467
- ...preactParam,
468
- ...litParam,
469
- ...slidevParam,
470
- ...viteParam,
233
+ ...hasReact ? get("react") : [],
234
+ ...get("next"),
235
+ ...get("expo"),
236
+ ...get("nest"),
237
+ ...get("hono"),
238
+ ...get("vue"),
239
+ ...get("svelte"),
240
+ ...get("solid"),
241
+ ...get("angular"),
242
+ ...get("qwik"),
243
+ ...get("remix"),
244
+ ...get("react-router"),
245
+ ...get("tanstack-start"),
246
+ ...get("nuxt"),
247
+ ...get("preact"),
248
+ ...get("lit"),
249
+ ...get("slidev"),
250
+ ...get("vite"),
471
251
  ...resolvedTypescript ? createTypescriptConfig({
472
252
  ...resolvedTypescript,
473
253
  tsconfigRootDir: resolvedTypescript.tsconfigRootDir ?? tsconfigRootDir
474
254
  }) : [],
475
- // Astro needs to run after generic TypeScript so its parser and false-positive
476
- // workarounds win for .astro files and embedded expressions.
477
- ...astroParam,
478
- // Next.js App Router overrides (#12)
479
- ...resolvedFrameworks.next && nextMode === NextMode.AppRouter ? [
480
- {
481
- files: ["app/**/*.{ts,tsx}", "src/**/*.{ts,tsx}"],
482
- name: "eslint-config-next/app-router-overrides",
483
- rules: {
484
- "@next/next/no-html-link-for-pages": "off"
485
- }
486
- }
487
- ] : [],
488
- // Integrations
489
- ...await getIntegrationConfigs(
490
- uniqueLibraries,
491
- uniqueTools,
492
- uniqueTesting,
493
- uniqueFormats,
494
- uniqueExtensions
495
- ),
496
- ...tailwindEntryPoint ? [{
497
- name: "eslint-config-lite/tailwind-settings",
498
- settings: {
499
- "better-tailwindcss": {
500
- detectComponentClasses: true,
501
- entryPoint: tailwindEntryPoint
502
- }
503
- }
255
+ // Astro runs after TypeScript so its parser wins for .astro files and embedded expressions.
256
+ ...get("astro"),
257
+ ...resolvedFrameworks.next && nextMode === NextMode.AppRouter ? [{
258
+ files: ["app/**/*.{ts,tsx}", "src/**/*.{ts,tsx}"],
259
+ name: "eslint-config-next/app-router-overrides",
260
+ rules: { "@next/next/no-html-link-for-pages": "off" }
504
261
  }] : [],
505
- // Prettier always last
262
+ ...await getIntegrationConfigs(uniqueLibraries, uniqueTools, uniqueTesting, uniqueFormats, uniqueExtensions),
263
+ {
264
+ files: ["**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}"],
265
+ name: "eslint-config-lite/scripts-overrides",
266
+ rules: {
267
+ "n/no-unpublished-import": "off",
268
+ ...uniqueExtensions.includes(Extension.Security) ? { "security/detect-non-literal-fs-filename": "off" } : {}
269
+ }
270
+ },
271
+ ...tailwindSettingsConfig ? [tailwindSettingsConfig] : [],
506
272
  ...await getPrettierConfig(uniqueTools)
507
273
  ];
508
274
  };
275
+ const emitLiteWarnings = (options) => {
276
+ if (options?.frameworks && "remix" in options.frameworks) {
277
+ process.emitWarning('`frameworks.remix` is deprecated and will be removed in the next major. Please use `frameworks["react-router"]` instead.', "eslint-config-lite");
278
+ }
279
+ if (options?.typescript && typeof options.typescript === "object" && "project" in options.typescript) {
280
+ process.emitWarning("`typescript.project` is ignored in v2. Type-aware linting now relies on typescript-eslint projectService.", "eslint-config-lite");
281
+ }
282
+ };
509
283
  const eslintConfig = async (options) => {
284
+ emitLiteWarnings(options);
510
285
  const {
511
286
  detection,
512
287
  extensions: optExtensions,
@@ -519,6 +294,7 @@ const eslintConfig = async (options) => {
519
294
  runtime: optRuntime,
520
295
  settings: optSettings,
521
296
  strict: optStrict,
297
+ tailwind: optTailwind,
522
298
  testing: optTesting,
523
299
  tools: optTools,
524
300
  tsconfigRootDir: optTsconfigRootDir,
@@ -555,7 +331,12 @@ const eslintConfig = async (options) => {
555
331
  const tsconfigRootDir = resolveTsconfigRootDir(rootDir, typescript, optTsconfigRootDir);
556
332
  const extensions = applyStrictProfileDefaults(configuredExtensions, strict);
557
333
  const resolvedFrameworks = applyLiteFrameworkImpliedDeps({ ...frameworks });
558
- const uniqueLibraries = [...new Set(libraries)];
334
+ const uniqueLibraries = (() => {
335
+ const libs = [...new Set(libraries)];
336
+ if (optTailwind === false) return libs.filter((l) => l !== Library.Tailwind);
337
+ if (!optTailwind) return libs;
338
+ return [.../* @__PURE__ */ new Set([...libs, Library.Tailwind])];
339
+ })();
559
340
  const uniqueTesting = [...new Set(testing)];
560
341
  const uniqueFormats = [...new Set(formats)];
561
342
  const uniqueTools = [...new Set(tools)];
@@ -568,7 +349,19 @@ const eslintConfig = async (options) => {
568
349
  const useGitignore = !uniqueSettings.includes(Setting.NoGitignore);
569
350
  const useDefaultIgnores = !uniqueSettings.includes(Setting.NoDefaultIgnores);
570
351
  const useGeneratedCodeIgnores = !uniqueSettings.includes(Setting.NoGeneratedCodeIgnores);
571
- const tailwindEntryPoint = uniqueLibraries.includes(Library.Tailwind) ? findTailwindEntryPoint(rootDir) : void 0;
352
+ const tailwindOptions = (() => {
353
+ if (!uniqueLibraries.includes(Library.Tailwind)) return void 0;
354
+ const opts = typeof optTailwind === "object" ? optTailwind : {};
355
+ const entryPoint = opts.entryPoint ?? findTailwindEntryPoint(rootDir);
356
+ const hasCustomConfig = entryPoint || opts.detectComponentClasses !== void 0 || opts.ignore?.length || opts.noUnknownClasses !== void 0;
357
+ if (!hasCustomConfig) return void 0;
358
+ return {
359
+ detectComponentClasses: opts.detectComponentClasses ?? true,
360
+ ...entryPoint ? { entryPoint } : {},
361
+ ...opts.ignore?.length ? { ignore: opts.ignore } : {},
362
+ ...opts.noUnknownClasses === void 0 ? {} : { noUnknownClasses: opts.noUnknownClasses }
363
+ };
364
+ })();
572
365
  const runtimeCoreConfig = runtime === Runtime.Universal ? coreConfig : createCoreConfig(runtime);
573
366
  const { defaultIgnores, gitignoreConfig, userIgnores } = buildLiteIgnoresConfig(
574
367
  useDefaultIgnores,
@@ -576,86 +369,29 @@ const eslintConfig = async (options) => {
576
369
  useGitignore,
577
370
  optIgnores
578
371
  );
579
- const [
580
- reactParam,
581
- nextParam,
582
- astroParam,
583
- expoParam,
584
- nestParam,
585
- honoParam,
586
- vueParam,
587
- svelteParam,
588
- solidParam,
589
- angularParam,
590
- qwikParam,
591
- remixParam,
592
- reactRouterParam,
593
- tanstackStartParam,
594
- nuxtParam,
595
- preactParam,
596
- litParam,
597
- slidevParam,
598
- viteParam
599
- ] = await Promise.all([
600
- resolveFramework("react", resolvedFrameworks.react),
601
- resolveFramework("next", resolvedFrameworks.next),
602
- resolveFramework("astro", resolvedFrameworks.astro, { hasReact, hasSolid, hasSvelte, hasVue }),
603
- resolveFramework("expo", resolvedFrameworks.expo),
604
- resolveFramework("nest", resolvedFrameworks.nest),
605
- resolveFramework("hono", resolvedFrameworks.hono, { runtime }),
606
- resolveFramework("vue", resolvedFrameworks.vue),
607
- resolveFramework("svelte", resolvedFrameworks.svelte),
608
- resolveFramework("solid", resolvedFrameworks.solid),
609
- resolveFramework("angular", resolvedFrameworks.angular),
610
- resolveFramework("qwik", resolvedFrameworks.qwik),
611
- resolveFramework("remix", resolvedFrameworks.remix),
612
- resolveFramework("react-router", resolvedFrameworks["react-router"]),
613
- resolveFramework("tanstack-start", resolvedFrameworks["tanstack-start"]),
614
- resolveFramework("nuxt", resolvedFrameworks.nuxt),
615
- resolveFramework("preact", resolvedFrameworks.preact),
616
- resolveFramework("lit", resolvedFrameworks.lit),
617
- resolveFramework("slidev", resolvedFrameworks.slidev, { runtime }),
618
- resolveFramework("vite", resolvedFrameworks.vite, { runtime })
619
- ]);
372
+ const frameworkConfigs = await resolveEnabledFrameworks(
373
+ resolvedFrameworks,
374
+ { hasReact, hasSolid, hasSvelte, hasVue, runtime }
375
+ );
620
376
  const configs = await buildLiteEslintConfigs({
621
- angularParam,
622
- astroOptions: { hasReact, hasSolid, hasSvelte, hasVue },
623
- astroParam,
624
377
  defaultIgnores,
625
- expoParam,
378
+ frameworkConfigs,
626
379
  gitignoreConfig,
627
- honoParam,
628
- litParam,
629
- nestParam,
380
+ hasReact,
630
381
  nextMode,
631
- nextParam,
632
- nuxtParam,
633
- preactParam,
634
- qwikParam,
635
- reactParam,
636
- reactRouterParam,
637
- remixParam,
638
382
  resolvedFrameworks,
639
383
  resolvedTypescript,
640
- rootDir,
641
- runtime,
642
384
  runtimeCoreConfig,
643
- slidevParam,
644
- solidParam,
645
- svelteParam,
646
- tailwindEntryPoint,
647
- tanstackStartParam,
385
+ tailwindOptions,
648
386
  tsconfigRootDir,
649
387
  uniqueExtensions,
650
388
  uniqueFormats,
651
389
  uniqueLibraries,
652
390
  uniqueTesting,
653
391
  uniqueTools,
654
- userIgnores,
655
- viteParam,
656
- vueParam
392
+ userIgnores
657
393
  });
658
- logLiteDebug(
394
+ logLiteDebug({
659
395
  autoFrameworks,
660
396
  detectRootDir,
661
397
  nextMode,
@@ -670,7 +406,7 @@ const eslintConfig = async (options) => {
670
406
  uniqueLibraries,
671
407
  uniqueTesting,
672
408
  uniqueTools
673
- );
409
+ });
674
410
  const projectConfigs = await Promise.all(
675
411
  Object.entries(configuredProjects).map(async ([projectPath, projectOptions]) => {
676
412
  const projectRoot = join(detectRootDir ?? process.cwd(), projectPath);
@@ -690,15 +426,15 @@ const eslintConfig = async (options) => {
690
426
  const defineConfig = eslintConfig;
691
427
  export {
692
428
  Extension2 as Extension,
693
- Format2 as Format,
429
+ Format,
694
430
  Library2 as Library,
695
431
  NextMode2 as NextMode,
696
432
  Preset2 as Preset,
697
433
  ReactConfigKeys,
698
434
  Runtime2 as Runtime,
699
435
  Setting2 as Setting,
700
- Testing2 as Testing,
701
- Tool2 as Tool,
436
+ Testing,
437
+ Tool,
702
438
  angular,
703
439
  angularConfig,
704
440
  astro,
@@ -1,7 +1,8 @@
1
1
  import {
2
+ createModuleLoader,
2
3
  Tool
3
4
  } from "@santi020k/eslint-config-core";
4
- import { loadModule } from "./lazy.js";
5
+ const loadModule = createModuleLoader((specifier) => import.meta.resolve(specifier));
5
6
  const hasRequestedIntegrations = (libraries, tools, testing, formats, extensions) => libraries.length > 0 || tools.length > 0 || testing.length > 0 || formats.length > 0 || extensions.length > 0;
6
7
  let integrationsModule;
7
8
  const loadIntegrations = async () => {
package/dist/resolvers.js CHANGED
@@ -20,7 +20,7 @@ const resolveFramework = async (frameworkName, framework, options) => {
20
20
  if (typeof framework.default === "function") {
21
21
  return framework.default(options);
22
22
  }
23
- return framework.default;
23
+ if (Array.isArray(framework.default)) return framework.default;
24
24
  }
25
25
  if (Array.isArray(framework)) return framework;
26
26
  throw new TypeError(
@@ -0,0 +1,6 @@
1
+ import { TailwindOptions } from '@santi020k/eslint-config-core';
2
+ import { TSESLint } from '@typescript-eslint/utils';
3
+
4
+ declare const buildTailwindSettingsConfig: (tailwindOptions: TailwindOptions | undefined) => TSESLint.FlatConfig.Config | undefined;
5
+
6
+ export { buildTailwindSettingsConfig };
@@ -0,0 +1,27 @@
1
+ const buildTailwindSettingsConfig = (tailwindOptions) => {
2
+ if (!tailwindOptions) return void 0;
3
+ const { noUnknownClasses, ...settingsOptions } = tailwindOptions;
4
+ const unknownClassOptions = {};
5
+ if (tailwindOptions.entryPoint) unknownClassOptions.entryPoint = tailwindOptions.entryPoint;
6
+ if (tailwindOptions.ignore?.length) unknownClassOptions.ignore = tailwindOptions.ignore;
7
+ const hasUnknownClassOptions = Object.keys(unknownClassOptions).length > 0;
8
+ let noUnknownClassesRule;
9
+ if (hasUnknownClassOptions || noUnknownClasses !== void 0) {
10
+ const severity = noUnknownClasses ?? "error";
11
+ if (severity === false) {
12
+ noUnknownClassesRule = "off";
13
+ } else if (hasUnknownClassOptions) {
14
+ noUnknownClassesRule = [severity, unknownClassOptions];
15
+ } else {
16
+ noUnknownClassesRule = severity;
17
+ }
18
+ }
19
+ return {
20
+ name: "eslint-config-lite/tailwind-settings",
21
+ ...noUnknownClassesRule === void 0 ? {} : { rules: { "better-tailwindcss/no-unknown-classes": noUnknownClassesRule } },
22
+ settings: { "better-tailwindcss": settingsOptions }
23
+ };
24
+ };
25
+ export {
26
+ buildTailwindSettingsConfig
27
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@santi020k/eslint-config-lite",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,58 +23,58 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@typescript-eslint/utils": "^8.61.1",
26
- "@santi020k/eslint-config-core": "^2.0.0-beta.1",
27
- "@santi020k/eslint-config-typescript": "^2.0.0-beta.1"
26
+ "@santi020k/eslint-config-core": "^2.0.0",
27
+ "@santi020k/eslint-config-typescript": "^2.0.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^25.9.3",
31
31
  "publint": "^0.3.21",
32
32
  "tsup": "^8.5.1",
33
33
  "typescript": "^6.0.3",
34
- "@santi020k/eslint-config-angular": "^2.0.0-beta.1",
35
- "@santi020k/eslint-config-astro": "^2.0.0-beta.1",
36
- "@santi020k/eslint-config-expo": "^2.0.0-beta.1",
37
- "@santi020k/eslint-config-hono": "^2.0.0-beta.1",
38
- "@santi020k/eslint-config-integrations": "^2.0.0-beta.1",
39
- "@santi020k/eslint-config-lit": "^2.0.0-beta.1",
40
- "@santi020k/eslint-config-nest": "^2.0.0-beta.1",
41
- "@santi020k/eslint-config-next": "^2.0.0-beta.1",
42
- "@santi020k/eslint-config-nuxt": "^2.0.0-beta.1",
43
- "@santi020k/eslint-config-preact": "^2.0.0-beta.1",
44
- "@santi020k/eslint-config-qwik": "^2.0.0-beta.1",
45
- "@santi020k/eslint-config-react": "^2.0.0-beta.1",
46
- "@santi020k/eslint-config-react-router": "^2.0.0-beta.1",
47
- "@santi020k/eslint-config-remix": "^2.0.0-beta.1",
48
- "@santi020k/eslint-config-slidev": "^2.0.0-beta.1",
49
- "@santi020k/eslint-config-solid": "^2.0.0-beta.1",
50
- "@santi020k/eslint-config-svelte": "^2.0.0-beta.1",
51
- "@santi020k/eslint-config-tanstack-start": "^2.0.0-beta.1",
52
- "@santi020k/eslint-config-vite": "^2.0.0-beta.1",
53
- "@santi020k/eslint-config-vue": "^2.0.0-beta.1"
34
+ "@santi020k/eslint-config-angular": "^2.0.0",
35
+ "@santi020k/eslint-config-astro": "^2.0.0",
36
+ "@santi020k/eslint-config-expo": "^2.0.0",
37
+ "@santi020k/eslint-config-hono": "^2.0.0",
38
+ "@santi020k/eslint-config-integrations": "^2.0.0",
39
+ "@santi020k/eslint-config-lit": "^2.0.0",
40
+ "@santi020k/eslint-config-nest": "^2.0.0",
41
+ "@santi020k/eslint-config-next": "^2.0.0",
42
+ "@santi020k/eslint-config-nuxt": "^2.0.0",
43
+ "@santi020k/eslint-config-preact": "^2.0.0",
44
+ "@santi020k/eslint-config-qwik": "^2.0.0",
45
+ "@santi020k/eslint-config-react": "^2.0.0",
46
+ "@santi020k/eslint-config-react-router": "^2.0.0",
47
+ "@santi020k/eslint-config-remix": "^2.0.0",
48
+ "@santi020k/eslint-config-slidev": "^2.0.0",
49
+ "@santi020k/eslint-config-solid": "^2.0.0",
50
+ "@santi020k/eslint-config-svelte": "^2.0.0",
51
+ "@santi020k/eslint-config-tanstack-start": "^2.0.0",
52
+ "@santi020k/eslint-config-vite": "^2.0.0",
53
+ "@santi020k/eslint-config-vue": "^2.0.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "eslint": "^10.0.0",
57
57
  "typescript": ">=5.0.0",
58
- "@santi020k/eslint-config-angular": "^2.0.0-beta.1",
59
- "@santi020k/eslint-config-astro": "^2.0.0-beta.1",
60
- "@santi020k/eslint-config-expo": "^2.0.0-beta.1",
61
- "@santi020k/eslint-config-hono": "^2.0.0-beta.1",
62
- "@santi020k/eslint-config-integrations": "^2.0.0-beta.1",
63
- "@santi020k/eslint-config-lit": "^2.0.0-beta.1",
64
- "@santi020k/eslint-config-nest": "^2.0.0-beta.1",
65
- "@santi020k/eslint-config-next": "^2.0.0-beta.1",
66
- "@santi020k/eslint-config-nuxt": "^2.0.0-beta.1",
67
- "@santi020k/eslint-config-preact": "^2.0.0-beta.1",
68
- "@santi020k/eslint-config-qwik": "^2.0.0-beta.1",
69
- "@santi020k/eslint-config-react": "^2.0.0-beta.1",
70
- "@santi020k/eslint-config-react-router": "^2.0.0-beta.1",
71
- "@santi020k/eslint-config-remix": "^2.0.0-beta.1",
72
- "@santi020k/eslint-config-slidev": "^2.0.0-beta.1",
73
- "@santi020k/eslint-config-solid": "^2.0.0-beta.1",
74
- "@santi020k/eslint-config-svelte": "^2.0.0-beta.1",
75
- "@santi020k/eslint-config-tanstack-start": "^2.0.0-beta.1",
76
- "@santi020k/eslint-config-vite": "^2.0.0-beta.1",
77
- "@santi020k/eslint-config-vue": "^2.0.0-beta.1"
58
+ "@santi020k/eslint-config-astro": "^2.0.0",
59
+ "@santi020k/eslint-config-hono": "^2.0.0",
60
+ "@santi020k/eslint-config-integrations": "^2.0.0",
61
+ "@santi020k/eslint-config-expo": "^2.0.0",
62
+ "@santi020k/eslint-config-nest": "^2.0.0",
63
+ "@santi020k/eslint-config-lit": "^2.0.0",
64
+ "@santi020k/eslint-config-next": "^2.0.0",
65
+ "@santi020k/eslint-config-nuxt": "^2.0.0",
66
+ "@santi020k/eslint-config-preact": "^2.0.0",
67
+ "@santi020k/eslint-config-qwik": "^2.0.0",
68
+ "@santi020k/eslint-config-react": "^2.0.0",
69
+ "@santi020k/eslint-config-angular": "^2.0.0",
70
+ "@santi020k/eslint-config-react-router": "^2.0.0",
71
+ "@santi020k/eslint-config-remix": "^2.0.0",
72
+ "@santi020k/eslint-config-slidev": "^2.0.0",
73
+ "@santi020k/eslint-config-solid": "^2.0.0",
74
+ "@santi020k/eslint-config-svelte": "^2.0.0",
75
+ "@santi020k/eslint-config-tanstack-start": "^2.0.0",
76
+ "@santi020k/eslint-config-vite": "^2.0.0",
77
+ "@santi020k/eslint-config-vue": "^2.0.0"
78
78
  },
79
79
  "peerDependenciesMeta": {
80
80
  "@santi020k/eslint-config-angular": {
package/dist/compose.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import { FlatConfigArray } from '@santi020k/eslint-config-core';
2
-
3
- type NormalizedStrictMode = 'ci' | 'pedantic' | 'recommended';
4
- declare const normalizeStrictMode: (strict: boolean | NormalizedStrictMode | undefined) => NormalizedStrictMode;
5
- /**
6
- * Applies strict mode by promoting all 'warn' rules to 'error'.
7
- */
8
- declare const applyStrictMode: (configs: FlatConfigArray, strict: boolean | NormalizedStrictMode | undefined) => FlatConfigArray;
9
-
10
- export { type NormalizedStrictMode, applyStrictMode, normalizeStrictMode };
package/dist/compose.js DELETED
@@ -1,30 +0,0 @@
1
- const normalizeStrictMode = (strict) => {
2
- if (strict === true) return "ci";
3
- if (strict === "ci" || strict === "pedantic") return strict;
4
- return "recommended";
5
- };
6
- const promoteRuleSeverity = (value) => {
7
- if (value === void 0) return void 0;
8
- if (value === "warn" || value === 1) return "error";
9
- if (Array.isArray(value) && (value[0] === "warn" || value[0] === 1)) {
10
- return ["error", ...value.slice(1)];
11
- }
12
- return value;
13
- };
14
- const applyStrictMode = (configs, strict) => {
15
- const strictMode = normalizeStrictMode(strict);
16
- if (strictMode === "recommended") return configs;
17
- return configs.map((config) => {
18
- if (config.rules) {
19
- const strictRules = Object.fromEntries(
20
- Object.entries(config.rules).map(([key, value]) => [key, promoteRuleSeverity(value)])
21
- );
22
- return { ...config, rules: strictRules };
23
- }
24
- return config;
25
- });
26
- };
27
- export {
28
- applyStrictMode,
29
- normalizeStrictMode
30
- };
package/dist/lazy.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare const loadModule: <T = unknown>(specifier: string) => Promise<T>;
2
-
3
- export { loadModule };
package/dist/lazy.js DELETED
@@ -1,9 +0,0 @@
1
- const isVitest = typeof process !== "undefined" && process.env.VITEST;
2
- const dynamicImport = isVitest ? (specifier) => import(
3
- /* @vite-ignore */
4
- specifier
5
- ) : Reflect.construct(Function, ["specifier", "return import(specifier)"]);
6
- const loadModule = async (specifier) => await dynamicImport(specifier);
7
- export {
8
- loadModule
9
- };