lintmax 0.1.15

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.

Potentially problematic release.


This version of lintmax might be problematic. Click here for more details.

@@ -0,0 +1,784 @@
1
+ import { i as DEFAULT_SHARED_IGNORE_PATTERNS, n as BIOME_PATTERN_RULE_OVERRIDES, o as OXLINT_PATTERN_RULE_OVERRIDES, r as BIOME_RULES_OFF, s as SHARED_OVERRIDE_SYMBOL_KEY, t as BIOME_IGNORE_PATTERNS } from "./constants-Cjkf4mJh.mjs";
2
+ import { a as assertJsonSerializable, c as findUnknownRules, d as normalizeObjectListInput, f as normalizePathListInput, h as stripPluginNamespace, i as joinPath, l as isRecord, m as normalizeTailwindOption, n as fromFileUrl, o as assertObject, p as normalizeRulesOffInput, s as assertOptionalString, t as dirnamePath, u as normalizeIgnorePattern } from "./path-Cu_Nf2ct.mjs";
3
+ import { file, spawnSync, write } from "bun";
4
+ import { relative } from "node:path";
5
+ //#region src/core.ts
6
+ var CliExitError = class extends Error {
7
+ code;
8
+ name = "CliExitError";
9
+ constructor({ code, message }) {
10
+ super(message ?? "");
11
+ this.code = code;
12
+ }
13
+ };
14
+ const decoder = new TextDecoder();
15
+ const cacheDir = "node_modules/.cache/lintmax";
16
+ const cwd = process.cwd();
17
+ const ignoreEntries = [".cache/", ".eslintcache"];
18
+ const lintmaxRoot = dirnamePath(dirnamePath(fromFileUrl(import.meta.url)));
19
+ const PRETTIER_MD_ARGS = [
20
+ "--single-quote",
21
+ "--no-semi",
22
+ "--trailing-comma",
23
+ "none",
24
+ "--print-width",
25
+ "80",
26
+ "--arrow-parens",
27
+ "avoid",
28
+ "--tab-width",
29
+ "2",
30
+ "--prose-wrap",
31
+ "preserve"
32
+ ];
33
+ const parseJsonObject = ({ text }) => {
34
+ const parsed = JSON.parse(text);
35
+ return isRecord(parsed) ? parsed : {};
36
+ };
37
+ const decodeText = (bytes) => decoder.decode(bytes ?? new Uint8Array());
38
+ const pathExists = async ({ path }) => file(path).exists();
39
+ const readJson = async ({ path }) => {
40
+ if (!await pathExists({ path })) return {};
41
+ try {
42
+ return parseJsonObject({ text: await file(path).text() });
43
+ } catch {
44
+ return {};
45
+ }
46
+ };
47
+ const readRequiredJson = async ({ path }) => {
48
+ const text = await file(path).text();
49
+ return JSON.parse(text);
50
+ };
51
+ const writeJson = async ({ data, path }) => write(path, `${JSON.stringify(data, null, 2)}\n`);
52
+ const ensureDirectory = ({ directory }) => {
53
+ const result = spawnSync({
54
+ cmd: [
55
+ "mkdir",
56
+ "-p",
57
+ directory
58
+ ],
59
+ stderr: "pipe",
60
+ stdout: "pipe"
61
+ });
62
+ if (result.exitCode === 0) return;
63
+ const stderr = decodeText(result.stderr).trim();
64
+ throw new CliExitError({
65
+ code: result.exitCode,
66
+ message: stderr.length > 0 ? stderr : `Failed to create directory: ${directory}`
67
+ });
68
+ };
69
+ const resolvePackageJsonPath = async ({ pkg }) => {
70
+ try {
71
+ return fromFileUrl(import.meta.resolve(`${pkg}/package.json`));
72
+ } catch {
73
+ const consumerCandidate = joinPath(cwd, "node_modules", pkg, "package.json");
74
+ if (await pathExists({ path: consumerCandidate })) return consumerCandidate;
75
+ return null;
76
+ }
77
+ };
78
+ const resolveBin = async ({ bin, pkg }) => {
79
+ const packageJsonPath = await resolvePackageJsonPath({ pkg });
80
+ if (!packageJsonPath) throw new CliExitError({
81
+ code: 1,
82
+ message: `Cannot find ${pkg} — run: bun add -d ${pkg}`
83
+ });
84
+ const pkgJson = await readRequiredJson({ path: packageJsonPath });
85
+ return joinPath(dirnamePath(packageJsonPath), typeof pkgJson.bin === "string" ? pkgJson.bin : pkgJson.bin?.[bin] ?? "");
86
+ };
87
+ const run = ({ args, command, env, label, silent = false }) => {
88
+ const result = spawnSync({
89
+ cmd: [command, ...args],
90
+ cwd,
91
+ env,
92
+ stderr: silent ? "pipe" : "inherit",
93
+ stdout: silent ? "pipe" : "inherit"
94
+ });
95
+ if (result.exitCode === 0) return;
96
+ if (silent) {
97
+ process.stderr.write(`[${label}]\n`);
98
+ const stdout = decodeText(result.stdout);
99
+ if (stdout.length > 0) process.stderr.write(stdout);
100
+ const stderr = decodeText(result.stderr);
101
+ if (stderr.length > 0) process.stderr.write(stderr);
102
+ }
103
+ throw new CliExitError({ code: result.exitCode });
104
+ };
105
+ const runCapture = ({ args, command, env }) => {
106
+ const result = spawnSync({
107
+ cmd: [command, ...args],
108
+ cwd,
109
+ env,
110
+ stderr: "pipe",
111
+ stdout: "pipe"
112
+ });
113
+ return {
114
+ exitCode: result.exitCode,
115
+ stderr: decodeText(result.stderr),
116
+ stdout: decodeText(result.stdout)
117
+ };
118
+ };
119
+ const readVersion = async () => {
120
+ return (await readRequiredJson({ path: joinPath(lintmaxRoot, "package.json") })).version;
121
+ };
122
+ const usage = ({ version }) => {
123
+ process.stdout.write(`lintmax v${version}\n\n`);
124
+ process.stdout.write("Usage: lintmax <command>\n\n");
125
+ process.stdout.write("Commands:\n");
126
+ process.stdout.write(" init Scaffold config files for a new project\n");
127
+ process.stdout.write(" fix Auto-fix and format all files\n");
128
+ process.stdout.write(" check Check all files without modifying\n");
129
+ process.stdout.write(" rules List all enabled rules\n");
130
+ process.stdout.write(" --version Show version\n");
131
+ };
132
+ //#endregion
133
+ //#region src/index.ts
134
+ const SHARED_OVERRIDE_KEYS = [
135
+ "biome",
136
+ "eslint",
137
+ "oxlint"
138
+ ];
139
+ const ESLINT_IMPORT_MARKER_KEY = "__lintmaxImportRef";
140
+ const ESLINT_IMPORT_SENTINEL = "eslint-import";
141
+ const pkgRoot = dirnamePath(fromFileUrl(import.meta.resolve("../package.json")));
142
+ const normalizeBiomeIgnorePattern = ({ pattern }) => {
143
+ if (pattern.startsWith("!!")) return pattern;
144
+ const trimmed = normalizeIgnorePattern({ pattern });
145
+ return trimmed.startsWith("**/") ? `!!${trimmed}` : `!!**/${trimmed}`;
146
+ };
147
+ const resolveSchemaPath = async ({ cwd }) => {
148
+ try {
149
+ return fromFileUrl(import.meta.resolve("@biomejs/biome/configuration_schema.json"));
150
+ } catch (error) {
151
+ if (!(error instanceof Error)) throw error;
152
+ }
153
+ const consumerCandidate = joinPath(cwd, "node_modules", "@biomejs", "biome", "configuration_schema.json");
154
+ if (await file(consumerCandidate).exists()) return consumerCandidate;
155
+ throw new Error("Cannot find module @biomejs/biome/configuration_schema.json");
156
+ };
157
+ const resolveBiomeSchema = async ({ cwd }) => {
158
+ const schema = await readRequiredJson({ path: await resolveSchemaPath({ cwd }) });
159
+ const rulesProps = schema.$defs.Rules?.properties ?? {};
160
+ const categories = Object.keys(rulesProps).filter((k) => k !== "recommended");
161
+ const ruleMap = /* @__PURE__ */ new Map();
162
+ for (const cat of categories) {
163
+ const key = cat.charAt(0).toUpperCase() + cat.slice(1);
164
+ const props = schema.$defs[key]?.properties;
165
+ if (props) {
166
+ for (const rule of Object.keys(props)) if (rule !== "recommended" && rule !== "all") ruleMap.set(rule, cat);
167
+ }
168
+ }
169
+ return {
170
+ categories,
171
+ ruleMap
172
+ };
173
+ };
174
+ const extractRuleNames = (rules) => {
175
+ const names = [];
176
+ for (const key of Object.keys(rules)) names.push(stripPluginNamespace({ rule: key }));
177
+ return names;
178
+ };
179
+ const assertKnownBiomeRuleNames = ({ categoryMap, label, rules }) => {
180
+ const unknown = findUnknownRules({
181
+ knownRules: new Set(categoryMap.keys()),
182
+ normalizeRule: (rule) => stripPluginNamespace({ rule }),
183
+ rules
184
+ });
185
+ if (unknown.length > 0) throw new Error(`${label} contains unknown biome rules: ${unknown.join(", ")}`);
186
+ };
187
+ const assertKnownOxlintRuleNames = ({ label, rules, knownRules }) => {
188
+ const unknown = findUnknownRules({
189
+ knownRules,
190
+ rules
191
+ });
192
+ if (unknown.length > 0) throw new Error(`${label} contains unknown oxlint rules: ${unknown.join(", ")}`);
193
+ };
194
+ const groupByCategory = ({ categoryMap, ruleNames }) => {
195
+ const result = {};
196
+ for (const rule of ruleNames) {
197
+ const cat = categoryMap.get(rule);
198
+ if (cat) {
199
+ result[cat] ??= {};
200
+ result[cat][rule] = "off";
201
+ }
202
+ }
203
+ return result;
204
+ };
205
+ const normalizeSharedOverrideItem = ({ index, item, label }) => {
206
+ const files = normalizePathListInput({
207
+ label: `${label}[${index}].files`,
208
+ value: item.files
209
+ });
210
+ const biomeRules = normalizeRulesOffInput({
211
+ label: `${label}[${index}].biome`,
212
+ value: item.biome
213
+ });
214
+ const eslintRules = normalizeRulesOffInput({
215
+ label: `${label}[${index}].eslint`,
216
+ value: item.eslint
217
+ });
218
+ const oxlintRules = normalizeRulesOffInput({
219
+ label: `${label}[${index}].oxlint`,
220
+ value: item.oxlint
221
+ });
222
+ if (!(biomeRules || eslintRules || oxlintRules)) throw new Error(`${label}[${index}] must define at least one action: biome, eslint, or oxlint`);
223
+ return {
224
+ biomeRules,
225
+ eslintRules,
226
+ files,
227
+ oxlintRules
228
+ };
229
+ };
230
+ const normalizeSharedOverrides = ({ label, value }) => {
231
+ const out = [];
232
+ if (value === void 0) return out;
233
+ const obj = assertObject({
234
+ label,
235
+ value
236
+ });
237
+ const entries = [];
238
+ for (const [pattern, rawOverride] of Object.entries(obj)) {
239
+ const override = assertObject({
240
+ label: `${label}.${pattern}`,
241
+ value: rawOverride
242
+ });
243
+ for (const key of Object.keys(override)) if (!SHARED_OVERRIDE_KEYS.includes(key)) throw new Error(`${label}.${pattern}.${key} is not supported. Use biome, eslint, or oxlint.`);
244
+ entries.push({
245
+ ...override,
246
+ files: [pattern]
247
+ });
248
+ }
249
+ for (const [i, item] of entries.entries()) out.push(normalizeSharedOverrideItem({
250
+ index: i,
251
+ item,
252
+ label
253
+ }));
254
+ return out;
255
+ };
256
+ const collectSharedRuleOverrides = ({ ruleKey, sharedOverrides }) => {
257
+ const out = [];
258
+ for (const override of sharedOverrides) {
259
+ const rules = override[ruleKey];
260
+ if (rules) out.push({
261
+ files: override.files,
262
+ rules
263
+ });
264
+ }
265
+ return out;
266
+ };
267
+ const parseLinterOffOverrides = ({ assertKnownRules, fileKey, label, requireOff = false, value }) => {
268
+ const out = [];
269
+ for (const [i, override] of normalizeObjectListInput({
270
+ label,
271
+ value
272
+ }).entries()) {
273
+ const itemLabel = requireOff ? `${label}[${i}]` : label;
274
+ if (requireOff && override[fileKey] === void 0) throw new Error(`${label}[${i}].${fileKey} is required`);
275
+ if (requireOff && override.off === void 0) throw new Error(`${label}[${i}].off is required`);
276
+ const normalizedOverrideRules = normalizeRulesOffInput({
277
+ label: `${itemLabel}.off`,
278
+ value: override.off
279
+ });
280
+ if (normalizedOverrideRules) {
281
+ assertKnownRules?.(normalizedOverrideRules);
282
+ out.push({
283
+ files: normalizePathListInput({
284
+ label: `${itemLabel}.${fileKey}`,
285
+ value: override[fileKey]
286
+ }),
287
+ rules: normalizedOverrideRules
288
+ });
289
+ }
290
+ }
291
+ return out;
292
+ };
293
+ const parseTopLevelSyncScalars = ({ options }) => {
294
+ if (options.compact !== void 0 && typeof options.compact !== "boolean") throw new Error("compact must be a boolean");
295
+ const tailwind = normalizeTailwindOption({
296
+ label: "tailwind",
297
+ value: options.tailwind
298
+ });
299
+ assertOptionalString({
300
+ label: "tsconfigRootDir",
301
+ value: options.tsconfigRootDir
302
+ });
303
+ return {
304
+ sharedOverrides: options.overrides === void 0 ? [] : normalizeSharedOverrides({
305
+ label: "overrides",
306
+ value: options.overrides
307
+ }),
308
+ tailwind,
309
+ topLevelIgnores: options.ignores === void 0 ? [] : normalizePathListInput({
310
+ label: "ignores",
311
+ value: options.ignores
312
+ }),
313
+ tsconfigRootDir: options.tsconfigRootDir
314
+ };
315
+ };
316
+ const parseLinterSyncCommon = ({ linter, value }) => {
317
+ const parsed = {};
318
+ if (value.ignores !== void 0) parsed.ignores = normalizePathListInput({
319
+ label: `${linter}.ignores`,
320
+ value: value.ignores
321
+ });
322
+ const normalizedOffRules = normalizeRulesOffInput({
323
+ label: `${linter}.off`,
324
+ value: value.off
325
+ });
326
+ if (normalizedOffRules) parsed.off = Object.keys(normalizedOffRules);
327
+ return parsed;
328
+ };
329
+ const validateBiomeSyncConfig = ({ biome }) => {
330
+ const biomeValue = assertObject({
331
+ label: "biome config",
332
+ value: biome
333
+ });
334
+ const parsedCommon = parseLinterSyncCommon({
335
+ linter: "biome",
336
+ value: biomeValue
337
+ });
338
+ const parsedOverrides = parseLinterOffOverrides({
339
+ fileKey: "includes",
340
+ label: "biome.overrides",
341
+ requireOff: true,
342
+ value: biomeValue.overrides
343
+ }).map((override) => ({
344
+ includes: override.files,
345
+ off: Object.keys(override.rules)
346
+ }));
347
+ const parsed = { ...parsedCommon };
348
+ if (parsedOverrides.length > 0) parsed.overrides = parsedOverrides;
349
+ return parsed;
350
+ };
351
+ const parseEslintAppendEntry = ({ index, item }) => {
352
+ const { $lintmax: marker } = item;
353
+ if (marker === ESLINT_IMPORT_SENTINEL) {
354
+ const allowedKeys = new Set([
355
+ "$lintmax",
356
+ "files",
357
+ "from",
358
+ "ignores",
359
+ "name"
360
+ ]);
361
+ for (const key of Object.keys(item)) if (!allowedKeys.has(key)) throw new Error(`eslint.append[${index}].${key} is not supported for eslint-import entries. Use from, name, files, ignores.`);
362
+ assertOptionalString({
363
+ label: `eslint.append[${index}].from`,
364
+ value: item.from
365
+ });
366
+ const { from } = item;
367
+ if (typeof from !== "string" || from.trim().length === 0) throw new Error(`eslint.append[${index}].from must be a non-empty string`);
368
+ assertOptionalString({
369
+ label: `eslint.append[${index}].name`,
370
+ value: item.name
371
+ });
372
+ return {
373
+ files: item.files === void 0 ? void 0 : normalizePathListInput({
374
+ label: `eslint.append[${index}].files`,
375
+ value: item.files
376
+ }),
377
+ from,
378
+ ignores: item.ignores === void 0 ? void 0 : normalizePathListInput({
379
+ label: `eslint.append[${index}].ignores`,
380
+ value: item.ignores
381
+ }),
382
+ name: typeof item.name === "string" && item.name.trim().length > 0 ? item.name : "default"
383
+ };
384
+ }
385
+ if ("$lintmax" in item) throw new Error(`eslint.append[${index}].$lintmax has unsupported value. Use eslintImport(...) or remove the $lintmax key.`);
386
+ assertJsonSerializable({
387
+ label: `eslint.append[${index}]`,
388
+ value: item
389
+ });
390
+ return { config: item };
391
+ };
392
+ const validateEslintSyncConfig = ({ eslint }) => {
393
+ const eslintValue = assertObject({
394
+ label: "eslint config",
395
+ value: eslint
396
+ });
397
+ const parsed = { ...parseLinterSyncCommon({
398
+ linter: "eslint",
399
+ value: eslintValue
400
+ }) };
401
+ if (eslintValue.tailwind !== void 0) throw new Error("eslint.tailwind is not supported in sync config. Use top-level tailwind.");
402
+ if (eslintValue.tsconfigRootDir !== void 0) throw new Error("eslint.tsconfigRootDir is not supported in sync config. Use top-level tsconfigRootDir.");
403
+ if (eslintValue.append !== void 0) parsed.append = normalizeObjectListInput({
404
+ label: "eslint.append",
405
+ value: eslintValue.append
406
+ }).map((item, i) => parseEslintAppendEntry({
407
+ index: i,
408
+ item
409
+ }));
410
+ return parsed;
411
+ };
412
+ const validateOxlintSyncConfig = ({ oxlint }) => {
413
+ const oxlintValue = assertObject({
414
+ label: "oxlint config",
415
+ value: oxlint
416
+ });
417
+ const parsedCommon = parseLinterSyncCommon({
418
+ linter: "oxlint",
419
+ value: oxlintValue
420
+ });
421
+ const parsedOverrides = parseLinterOffOverrides({
422
+ fileKey: "files",
423
+ label: "oxlint.overrides",
424
+ requireOff: true,
425
+ value: oxlintValue.overrides
426
+ }).map((override) => ({
427
+ files: override.files,
428
+ off: Object.keys(override.rules)
429
+ }));
430
+ const parsed = { ...parsedCommon };
431
+ if (parsedOverrides.length > 0) parsed.overrides = parsedOverrides;
432
+ return parsed;
433
+ };
434
+ const validateSyncOptions = ({ options }) => {
435
+ if (!options) return {
436
+ sharedOverrides: [],
437
+ topLevelIgnores: []
438
+ };
439
+ const parsed = { ...parseTopLevelSyncScalars({ options }) };
440
+ if (options.biome) parsed.biome = validateBiomeSyncConfig({ biome: options.biome });
441
+ if (options.eslint) parsed.eslint = validateEslintSyncConfig({ eslint: options.eslint });
442
+ if (options.oxlint) parsed.oxlint = validateOxlintSyncConfig({ oxlint: options.oxlint });
443
+ return parsed;
444
+ };
445
+ const mergeUniquePatterns = ({ into, patterns }) => {
446
+ if (!patterns) return;
447
+ for (const raw of patterns) {
448
+ const pattern = normalizeIgnorePattern({ pattern: raw });
449
+ if (pattern.length > 0 && !into.includes(pattern)) into.push(pattern);
450
+ }
451
+ };
452
+ const mergeIgnorePatternGroups = ({ groups }) => {
453
+ const merged = [];
454
+ for (const group of groups) mergeUniquePatterns({
455
+ into: merged,
456
+ patterns: group
457
+ });
458
+ return merged;
459
+ };
460
+ const buildUserIgnorePatterns = ({ topLevelIgnores }) => mergeIgnorePatternGroups({ groups: [topLevelIgnores] });
461
+ const buildLinterOptionsWithSharedOverrides = ({ createEmpty, ruleKey, sharedOverrides, source, mapSharedOverride }) => {
462
+ if (!(source || sharedOverrides.length > 0)) return source;
463
+ const next = source ? { ...source } : createEmpty();
464
+ next.off = source?.off;
465
+ const sharedRuleOverrides = collectSharedRuleOverrides({
466
+ ruleKey,
467
+ sharedOverrides
468
+ }).map(mapSharedOverride);
469
+ const localOverrides = source?.overrides ?? [];
470
+ const mergedOverrides = [...sharedRuleOverrides, ...localOverrides];
471
+ if (mergedOverrides.length > 0) next.overrides = mergedOverrides;
472
+ return next;
473
+ };
474
+ const buildBiomeOptions = ({ biomeSource, sharedOverrides }) => buildLinterOptionsWithSharedOverrides({
475
+ createEmpty: () => ({}),
476
+ mapSharedOverride: (override) => ({
477
+ includes: override.files,
478
+ off: Object.keys(override.rules)
479
+ }),
480
+ ruleKey: "biomeRules",
481
+ sharedOverrides,
482
+ source: biomeSource
483
+ });
484
+ const buildOxlintOptions = ({ oxlintSource, sharedOverrides }) => buildLinterOptionsWithSharedOverrides({
485
+ createEmpty: () => ({}),
486
+ mapSharedOverride: (override) => ({
487
+ files: override.files,
488
+ off: Object.keys(override.rules)
489
+ }),
490
+ ruleKey: "oxlintRules",
491
+ sharedOverrides,
492
+ source: oxlintSource
493
+ });
494
+ const buildEslintIgnorePatterns = ({ eslintSource, userIgnorePatterns }) => mergeIgnorePatternGroups({ groups: [userIgnorePatterns, eslintSource?.ignores === void 0 ? void 0 : normalizePathListInput({
495
+ label: "eslint.ignores",
496
+ value: eslintSource.ignores
497
+ })] });
498
+ const buildEslintOptions = ({ eslintSource, sharedOverrides, tailwind, tsconfigRootDir, userIgnorePatterns }) => {
499
+ const eslintIgnores = buildEslintIgnorePatterns({
500
+ eslintSource,
501
+ userIgnorePatterns
502
+ });
503
+ const sharedRuleOverrides = collectSharedRuleOverrides({
504
+ ruleKey: "eslintRules",
505
+ sharedOverrides
506
+ }).map((override) => ({
507
+ files: override.files,
508
+ rules: override.rules
509
+ }));
510
+ const sharedOverrideAppendIndexes = sharedRuleOverrides.map((_, index) => index);
511
+ const eslintImportRefs = [];
512
+ if (eslintSource || sharedOverrides.length > 0 || tailwind !== void 0 || tsconfigRootDir !== void 0) {
513
+ const eslintOptions = { off: eslintSource?.off };
514
+ if (eslintIgnores.length > 0) eslintOptions.ignores = eslintIgnores;
515
+ const appendEntries = [];
516
+ for (const entry of eslintSource?.append ?? []) if ("config" in entry) appendEntries.push(entry.config);
517
+ else {
518
+ const importRefIndex = eslintImportRefs.push({
519
+ exportName: entry.name,
520
+ files: entry.files,
521
+ ignores: entry.ignores,
522
+ source: entry.from
523
+ });
524
+ appendEntries.push({ [ESLINT_IMPORT_MARKER_KEY]: importRefIndex - 1 });
525
+ }
526
+ const mergedAppend = [...sharedRuleOverrides, ...appendEntries];
527
+ if (mergedAppend.length > 0) eslintOptions.append = mergedAppend;
528
+ eslintOptions.tailwind = normalizeTailwindOption({
529
+ label: "tailwind",
530
+ value: tailwind
531
+ }) ?? true;
532
+ eslintOptions.tsconfigRootDir = tsconfigRootDir;
533
+ return {
534
+ eslintImportRefs,
535
+ eslintOptions,
536
+ sharedOverrideAppendIndexes
537
+ };
538
+ }
539
+ if (eslintIgnores.length === 0) return {
540
+ eslintImportRefs,
541
+ eslintOptions: void 0,
542
+ sharedOverrideAppendIndexes
543
+ };
544
+ return {
545
+ eslintImportRefs,
546
+ eslintOptions: {
547
+ ignores: eslintIgnores,
548
+ tailwind: true
549
+ },
550
+ sharedOverrideAppendIndexes
551
+ };
552
+ };
553
+ const normalizeLinterOffOverrides = ({ assertKnownRules, fileKey, label, value }) => parseLinterOffOverrides({
554
+ assertKnownRules,
555
+ fileKey,
556
+ label,
557
+ value
558
+ });
559
+ const normalizeKnownOffRules = ({ assertKnownRules, label, value }) => {
560
+ const normalizedRules = normalizeRulesOffInput({
561
+ label,
562
+ value
563
+ });
564
+ if (!normalizedRules) return;
565
+ assertKnownRules?.(normalizedRules);
566
+ return normalizedRules;
567
+ };
568
+ const createBiomeConfig = async ({ cwd, options, sharedIgnorePatterns }) => {
569
+ const { categories, ruleMap } = await resolveBiomeSchema({ cwd });
570
+ const allRulesOff = [...BIOME_RULES_OFF];
571
+ const normalizedRules = normalizeKnownOffRules({
572
+ assertKnownRules: (rules) => assertKnownBiomeRuleNames({
573
+ categoryMap: ruleMap,
574
+ label: "biome.off",
575
+ rules
576
+ }),
577
+ label: "biome.off",
578
+ value: options?.off
579
+ });
580
+ if (normalizedRules) for (const key of Object.keys(normalizedRules)) {
581
+ const ruleName = stripPluginNamespace({ rule: key });
582
+ if (!allRulesOff.includes(ruleName)) allRulesOff.push(ruleName);
583
+ }
584
+ const ignorePatterns = [...BIOME_IGNORE_PATTERNS];
585
+ const mergedIgnorePatterns = mergeIgnorePatternGroups({ groups: [sharedIgnorePatterns, options?.ignores === void 0 ? void 0 : normalizePathListInput({
586
+ label: "biome.ignores",
587
+ value: options.ignores
588
+ })] });
589
+ for (const pattern of mergedIgnorePatterns) {
590
+ const negated = normalizeBiomeIgnorePattern({ pattern });
591
+ if (!ignorePatterns.includes(negated)) ignorePatterns.push(negated);
592
+ }
593
+ const overrides = [{
594
+ css: { parser: { tailwindDirectives: true } },
595
+ includes: ["**"],
596
+ linter: { rules: groupByCategory({
597
+ categoryMap: ruleMap,
598
+ ruleNames: allRulesOff
599
+ }) }
600
+ }];
601
+ for (const override of BIOME_PATTERN_RULE_OVERRIDES) overrides.push({
602
+ includes: [...override.includes],
603
+ linter: { rules: groupByCategory({
604
+ categoryMap: ruleMap,
605
+ ruleNames: override.rules
606
+ }) }
607
+ });
608
+ for (const override of normalizeLinterOffOverrides({
609
+ assertKnownRules: (rules) => assertKnownBiomeRuleNames({
610
+ categoryMap: ruleMap,
611
+ label: "biome.overrides.off",
612
+ rules
613
+ }),
614
+ fileKey: "includes",
615
+ label: "biome.overrides",
616
+ value: options?.overrides
617
+ })) overrides.push({
618
+ includes: override.files,
619
+ linter: { rules: groupByCategory({
620
+ categoryMap: ruleMap,
621
+ ruleNames: extractRuleNames(override.rules)
622
+ }) }
623
+ });
624
+ return {
625
+ $schema: "https://biomejs.dev/schemas/latest/schema.json",
626
+ assist: { actions: { source: { organizeImports: "off" } } },
627
+ css: {
628
+ formatter: {
629
+ enabled: true,
630
+ quoteStyle: "single"
631
+ },
632
+ parser: { tailwindDirectives: true }
633
+ },
634
+ files: { includes: ["**", ...ignorePatterns] },
635
+ formatter: {
636
+ indentStyle: "space",
637
+ lineWidth: 123
638
+ },
639
+ javascript: { formatter: {
640
+ arrowParentheses: "asNeeded",
641
+ bracketSameLine: true,
642
+ jsxQuoteStyle: "single",
643
+ quoteStyle: "single",
644
+ semicolons: "asNeeded",
645
+ trailingCommas: "none"
646
+ } },
647
+ json: { formatter: { trailingCommas: "none" } },
648
+ linter: {
649
+ domains: {
650
+ next: "all",
651
+ project: "all",
652
+ qwik: "all",
653
+ react: "all",
654
+ solid: "all",
655
+ tailwind: "all",
656
+ test: "all",
657
+ vue: "all"
658
+ },
659
+ rules: Object.fromEntries(categories.map((c) => [c, "error"]))
660
+ },
661
+ overrides
662
+ };
663
+ };
664
+ const createOxlintConfig = async ({ options, sharedIgnorePatterns }) => {
665
+ const base = await readRequiredJson({ path: joinPath(pkgRoot, "oxlintrc.json") });
666
+ const knownRules = new Set([
667
+ ...Object.keys(base.rules),
668
+ ...(base.overrides ?? []).flatMap((override) => Object.keys(override.rules)),
669
+ ...OXLINT_PATTERN_RULE_OVERRIDES.flatMap((override) => Object.keys(override.rules))
670
+ ]);
671
+ const mergedIgnorePatterns = mergeIgnorePatternGroups({ groups: [sharedIgnorePatterns, options?.ignores === void 0 ? void 0 : normalizePathListInput({
672
+ label: "oxlint.ignores",
673
+ value: options.ignores
674
+ })] });
675
+ base.overrides ??= [];
676
+ for (const override of OXLINT_PATTERN_RULE_OVERRIDES) base.overrides.push({
677
+ files: [...override.files],
678
+ rules: { ...override.rules }
679
+ });
680
+ if (mergedIgnorePatterns.length > 0) {
681
+ base.ignorePatterns ??= [];
682
+ for (const pattern of mergedIgnorePatterns) if (!base.ignorePatterns.includes(pattern)) base.ignorePatterns.push(pattern);
683
+ }
684
+ if (!options) return base;
685
+ const normalizedRules = normalizeKnownOffRules({
686
+ assertKnownRules: (rules) => assertKnownOxlintRuleNames({
687
+ knownRules,
688
+ label: "oxlint.off",
689
+ rules
690
+ }),
691
+ label: "oxlint.off",
692
+ value: options.off
693
+ });
694
+ if (normalizedRules) for (const [key, value] of Object.entries(normalizedRules)) base.rules[key] = value;
695
+ for (const override of normalizeLinterOffOverrides({
696
+ assertKnownRules: (rules) => assertKnownOxlintRuleNames({
697
+ knownRules,
698
+ label: "oxlint.overrides.off",
699
+ rules
700
+ }),
701
+ fileKey: "files",
702
+ label: "oxlint.overrides",
703
+ value: options.overrides
704
+ })) base.overrides.push({
705
+ files: override.files,
706
+ rules: override.rules
707
+ });
708
+ return base;
709
+ };
710
+ const resolveSyncImportSource = ({ cwd, dir, source }) => {
711
+ if (!(source.startsWith(".") || source.startsWith(".."))) return source;
712
+ const relativeSource = relative(dir, joinPath(cwd, source)).replaceAll("\\", "/");
713
+ return relativeSource.startsWith(".") ? relativeSource : `./${relativeSource}`;
714
+ };
715
+ const sync = async (options) => {
716
+ const { biome, eslint, oxlint, sharedOverrides, tailwind, topLevelIgnores, tsconfigRootDir } = validateSyncOptions({ options });
717
+ const cwd = process.cwd();
718
+ const dir = joinPath(cwd, cacheDir);
719
+ const userIgnorePatterns = buildUserIgnorePatterns({ topLevelIgnores });
720
+ const sharedIgnorePatterns = mergeIgnorePatternGroups({ groups: [DEFAULT_SHARED_IGNORE_PATTERNS, userIgnorePatterns] });
721
+ const biomeOptions = buildBiomeOptions({
722
+ biomeSource: biome,
723
+ sharedOverrides
724
+ });
725
+ const oxlintOptions = buildOxlintOptions({
726
+ oxlintSource: oxlint,
727
+ sharedOverrides
728
+ });
729
+ const { eslintImportRefs, eslintOptions, sharedOverrideAppendIndexes } = buildEslintOptions({
730
+ eslintSource: eslint,
731
+ sharedOverrides,
732
+ tailwind,
733
+ tsconfigRootDir,
734
+ userIgnorePatterns
735
+ });
736
+ ensureDirectory({ directory: dir });
737
+ const biomeConfig = await createBiomeConfig({
738
+ cwd,
739
+ options: biomeOptions,
740
+ sharedIgnorePatterns
741
+ });
742
+ const oxlintConfig = await createOxlintConfig({
743
+ options: oxlintOptions,
744
+ sharedIgnorePatterns
745
+ });
746
+ const normalizedImportRefs = eslintImportRefs.map((importRef) => ({
747
+ exportName: importRef.exportName,
748
+ files: importRef.files,
749
+ ignores: importRef.ignores,
750
+ source: resolveSyncImportSource({
751
+ cwd,
752
+ dir,
753
+ source: importRef.source
754
+ })
755
+ }));
756
+ const importStatements = normalizedImportRefs.map((importRef, index) => `import * as __lintmaxAppendImport${index} from ${JSON.stringify(importRef.source)}`).join("\n");
757
+ const importEntries = normalizedImportRefs.map((importRef, index) => ` { exportName: ${JSON.stringify(importRef.exportName)}, files: ${JSON.stringify(importRef.files ?? null)}, ignores: ${JSON.stringify(importRef.ignores ?? null)}, module: __lintmaxAppendImport${index} }`).join(",\n");
758
+ const importExpansion = normalizedImportRefs.length === 0 ? "" : `\nconst appendImports = [\n${importEntries}\n]\nconst normalizedAppend = []\nfor (const entry of options.append ?? []) {\n if (entry && typeof entry === 'object' && !Array.isArray(entry) && ${JSON.stringify(ESLINT_IMPORT_MARKER_KEY)} in entry) {\n const importIndex = entry[${JSON.stringify(ESLINT_IMPORT_MARKER_KEY)}]\n if (typeof importIndex !== 'number' || !Number.isInteger(importIndex))\n throw new Error('Invalid eslint import marker index in generated config')\n const importRef = appendImports[importIndex]\n if (!importRef) throw new Error(\`Missing eslint import ref for index \${importIndex}\`)\n const imported = importRef.module[importRef.exportName]\n const importedEntries = Array.isArray(imported) ? imported : [imported]\n for (const importedEntry of importedEntries) {\n if (!importedEntry || typeof importedEntry !== 'object' || Array.isArray(importedEntry))\n throw new Error(\`Imported eslint append from \${importRef.exportName} must resolve to config object(s)\`)\n normalizedAppend.push({\n ...importedEntry,\n ...(Array.isArray(importRef.files) ? { files: [...importRef.files] } : {}),\n ...(Array.isArray(importRef.ignores) ? { ignores: [...importRef.ignores] } : {})\n })\n }\n continue\n }\n normalizedAppend.push(entry)\n}\noptions.append = normalizedAppend\n`;
759
+ const eslintConfig = eslintOptions ? `${importStatements.length > 0 ? `${importStatements}\n` : ""}import { eslint } from 'lintmax/eslint'\nconst options = ${JSON.stringify(eslintOptions)}\nfor (const index of ${JSON.stringify(sharedOverrideAppendIndexes)}) {\n const entry = options.append?.[index]\n if (entry && typeof entry === 'object') entry[Symbol.for(${JSON.stringify(SHARED_OVERRIDE_SYMBOL_KEY)})] = true\n}${importExpansion}export default eslint(options)\n` : "export { default } from 'lintmax/eslint'\n";
760
+ const runtimeConfig = {
761
+ comments: options?.comments !== false,
762
+ compact: options?.compact !== false
763
+ };
764
+ await write(joinPath(dir, "biome.json"), `${JSON.stringify(biomeConfig, null, 2)}\n`);
765
+ await write(joinPath(dir, ".oxlintrc.json"), `${JSON.stringify(oxlintConfig, null, 2)}\n`);
766
+ await write(joinPath(dir, "eslint.generated.mjs"), eslintConfig);
767
+ await write(joinPath(dir, "lintmax.json"), `${JSON.stringify(runtimeConfig, null, 2)}\n`);
768
+ };
769
+ const eslintImport = ({ files, from, ignores, name }) => {
770
+ const entry = {
771
+ $lintmax: ESLINT_IMPORT_SENTINEL,
772
+ from
773
+ };
774
+ if (files !== void 0) entry.files = files;
775
+ if (ignores !== void 0) entry.ignores = ignores;
776
+ if (name !== void 0) entry.name = name;
777
+ return entry;
778
+ };
779
+ const defineConfig = (options) => {
780
+ validateSyncOptions({ options });
781
+ return options;
782
+ };
783
+ //#endregion
784
+ export { run as _, PRETTIER_MD_ARGS as a, writeJson as b, decodeText as c, lintmaxRoot as d, pathExists as f, resolveBin as g, readVersion as h, CliExitError as i, ensureDirectory as l, readRequiredJson as m, eslintImport as n, cacheDir as o, readJson as p, sync as r, cwd as s, defineConfig as t, ignoreEntries as u, runCapture as v, usage as y };