@eslint-config-snapshot/api 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,610 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
34
+ aggregateRules: () => aggregateRules,
35
+ assignGroupsByMatch: () => assignGroupsByMatch,
36
+ buildSnapshot: () => buildSnapshot,
37
+ canonicalizeJson: () => canonicalizeJson,
38
+ compareSeverity: () => compareSeverity,
39
+ diffSnapshots: () => diffSnapshots,
40
+ discoverWorkspaces: () => discoverWorkspaces,
41
+ extractRulesFromPrintConfig: () => extractRulesFromPrintConfig,
42
+ findConfigPath: () => findConfigPath,
43
+ getConfigScaffold: () => getConfigScaffold,
44
+ hasDiff: () => hasDiff,
45
+ loadConfig: () => loadConfig,
46
+ normalizePath: () => normalizePath,
47
+ normalizeSeverity: () => normalizeSeverity,
48
+ readSnapshotFile: () => readSnapshotFile,
49
+ resolveEslintBinForWorkspace: () => resolveEslintBinForWorkspace,
50
+ sampleWorkspaceFiles: () => sampleWorkspaceFiles,
51
+ sortUnique: () => sortUnique,
52
+ writeSnapshotFile: () => writeSnapshotFile
53
+ });
54
+ module.exports = __toCommonJS(index_exports);
55
+
56
+ // src/core.ts
57
+ function normalizePath(input) {
58
+ const withSlashes = input.replaceAll("\\", "/");
59
+ const collapsed = withSlashes.replaceAll(/\/+/g, "/");
60
+ const withoutTrailing = collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
61
+ return withoutTrailing === "" ? "." : withoutTrailing;
62
+ }
63
+ function sortUnique(list) {
64
+ return [...new Set(list.map((item) => normalizePath(item)))].sort();
65
+ }
66
+ function canonicalizeJson(value) {
67
+ if (value === null || value === void 0) {
68
+ return value;
69
+ }
70
+ if (Array.isArray(value)) {
71
+ return value.map((entry) => canonicalizeJson(entry));
72
+ }
73
+ if (typeof value === "object") {
74
+ const record = value;
75
+ const result = {};
76
+ for (const key of Object.keys(record).sort()) {
77
+ const entry = record[key];
78
+ if (entry !== void 0) {
79
+ result[key] = canonicalizeJson(entry);
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ return value;
85
+ }
86
+ function compareSeverity(a, b) {
87
+ const rank = { off: 0, warn: 1, error: 2 };
88
+ return rank[a] - rank[b];
89
+ }
90
+ function normalizeSeverity(value) {
91
+ if (value === 0 || value === "off") {
92
+ return "off";
93
+ }
94
+ if (value === 1 || value === "warn") {
95
+ return "warn";
96
+ }
97
+ if (value === 2 || value === "error") {
98
+ return "error";
99
+ }
100
+ throw new Error(`Unsupported severity: ${String(value)}`);
101
+ }
102
+
103
+ // src/workspace.ts
104
+ var import_get_packages = require("@manypkg/get-packages");
105
+ var import_node_path = __toESM(require("path"), 1);
106
+ var import_picomatch = __toESM(require("picomatch"), 1);
107
+ async function discoverWorkspaces(options) {
108
+ const cwd = options?.cwd ? import_node_path.default.resolve(options.cwd) : process.cwd();
109
+ const workspaceInput = options?.workspaceInput ?? { mode: "discover" };
110
+ if (workspaceInput.mode === "manual") {
111
+ const rootAbs2 = import_node_path.default.resolve(workspaceInput.rootAbs ?? cwd);
112
+ return {
113
+ rootAbs: rootAbs2,
114
+ workspacesRel: sortUnique(workspaceInput.workspaces)
115
+ };
116
+ }
117
+ const { rootDir, packages } = await (0, import_get_packages.getPackages)(cwd);
118
+ const workspacesAbs = packages.map((pkg) => pkg.dir);
119
+ const rootAbs = rootDir ? import_node_path.default.resolve(rootDir) : lowestCommonAncestor(workspacesAbs);
120
+ const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(import_node_path.default.relative(rootAbs, entry))));
121
+ return {
122
+ rootAbs,
123
+ workspacesRel
124
+ };
125
+ }
126
+ function assignGroupsByMatch(workspacesRel, groups) {
127
+ const sortedWorkspaces = sortUnique([...workspacesRel]);
128
+ const assignments = /* @__PURE__ */ new Map();
129
+ for (const group of groups) {
130
+ assignments.set(group.name, []);
131
+ }
132
+ const unmatched = [];
133
+ for (const workspace of sortedWorkspaces) {
134
+ let assigned = false;
135
+ for (const group of groups) {
136
+ if (matchesWorkspace(workspace, group.match)) {
137
+ assignments.get(group.name)?.push(workspace);
138
+ assigned = true;
139
+ break;
140
+ }
141
+ }
142
+ if (!assigned) {
143
+ unmatched.push(workspace);
144
+ }
145
+ }
146
+ if (unmatched.length > 0) {
147
+ throw new Error(`Unmatched workspaces: ${unmatched.join(", ")}`);
148
+ }
149
+ return groups.map((group) => ({
150
+ name: group.name,
151
+ workspaces: assignments.get(group.name) ?? []
152
+ }));
153
+ }
154
+ function matchesWorkspace(workspace, patterns) {
155
+ const positives = patterns.filter((pattern) => !pattern.startsWith("!"));
156
+ const negatives = patterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
157
+ const isPositiveMatch = positives.some((pattern) => (0, import_picomatch.default)(pattern, { dot: true })(workspace));
158
+ if (!isPositiveMatch) {
159
+ return false;
160
+ }
161
+ const isNegativeMatch = negatives.some((pattern) => (0, import_picomatch.default)(pattern, { dot: true })(workspace));
162
+ return !isNegativeMatch;
163
+ }
164
+ function lowestCommonAncestor(paths) {
165
+ if (paths.length === 0) {
166
+ return process.cwd();
167
+ }
168
+ const segments = paths.map((entry) => import_node_path.default.resolve(entry).split(import_node_path.default.sep));
169
+ const minLen = Math.min(...segments.map((parts) => parts.length));
170
+ const common = [];
171
+ for (let index = 0; index < minLen; index += 1) {
172
+ const value = segments[0][index];
173
+ if (segments.every((parts) => parts[index] === value)) {
174
+ common.push(value);
175
+ } else {
176
+ break;
177
+ }
178
+ }
179
+ if (common.length === 0) {
180
+ return import_node_path.default.parse(import_node_path.default.resolve(paths[0])).root;
181
+ }
182
+ return common.join(import_node_path.default.sep);
183
+ }
184
+
185
+ // src/sampling.ts
186
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
187
+ var import_picomatch2 = __toESM(require("picomatch"), 1);
188
+ async function sampleWorkspaceFiles(workspaceAbs, config) {
189
+ const all = await (0, import_fast_glob.default)(config.includeGlobs, {
190
+ cwd: workspaceAbs,
191
+ ignore: config.excludeGlobs,
192
+ onlyFiles: true,
193
+ dot: true,
194
+ unique: true
195
+ });
196
+ const normalized = sortUnique(all.map((entry) => normalizePath(entry)));
197
+ if (normalized.length === 0) {
198
+ return [];
199
+ }
200
+ if (normalized.length <= config.maxFilesPerWorkspace) {
201
+ return normalized;
202
+ }
203
+ if (config.hintGlobs.length === 0) {
204
+ return selectDistributed(normalized, config.maxFilesPerWorkspace);
205
+ }
206
+ const hinted = normalized.filter((entry) => config.hintGlobs.some((pattern) => (0, import_picomatch2.default)(pattern, { dot: true })(entry)));
207
+ const notHinted = normalized.filter((entry) => !hinted.includes(entry));
208
+ return selectDistributed([...hinted, ...notHinted], config.maxFilesPerWorkspace);
209
+ }
210
+ function selectDistributed(files, count) {
211
+ if (files.length <= count) {
212
+ return files;
213
+ }
214
+ const selected = [];
215
+ const selectedSet = /* @__PURE__ */ new Set();
216
+ const tokenSeen = /* @__PURE__ */ new Set();
217
+ for (const file of files) {
218
+ if (selected.length >= count) {
219
+ break;
220
+ }
221
+ const token = getPrimaryToken(file);
222
+ if (!token || tokenSeen.has(token)) {
223
+ continue;
224
+ }
225
+ tokenSeen.add(token);
226
+ selected.push(file);
227
+ selectedSet.add(file);
228
+ }
229
+ if (selected.length >= count) {
230
+ return sortUnique(selected).slice(0, count);
231
+ }
232
+ const remaining = files.filter((file) => !selectedSet.has(file));
233
+ const needed = count - selected.length;
234
+ const spaced = pickUniformly(remaining, needed);
235
+ return sortUnique([...selected, ...spaced]).slice(0, count);
236
+ }
237
+ function pickUniformly(files, count) {
238
+ if (count <= 0 || files.length === 0) {
239
+ return [];
240
+ }
241
+ if (files.length <= count) {
242
+ return files;
243
+ }
244
+ if (count === 1) {
245
+ return [files[0]];
246
+ }
247
+ const picked = [];
248
+ const usedIndices = /* @__PURE__ */ new Set();
249
+ for (let index = 0; index < count; index += 1) {
250
+ const raw = Math.round(index * (files.length - 1) / (count - 1));
251
+ const safeIndex = nextFreeIndex(raw, usedIndices, files.length);
252
+ usedIndices.add(safeIndex);
253
+ picked.push(files[safeIndex]);
254
+ }
255
+ return picked;
256
+ }
257
+ function nextFreeIndex(candidate, used, max) {
258
+ if (!used.has(candidate)) {
259
+ return candidate;
260
+ }
261
+ for (let delta = 1; delta < max; delta += 1) {
262
+ const forward = candidate + delta;
263
+ if (forward < max && !used.has(forward)) {
264
+ return forward;
265
+ }
266
+ const backward = candidate - delta;
267
+ if (backward >= 0 && !used.has(backward)) {
268
+ return backward;
269
+ }
270
+ }
271
+ return candidate;
272
+ }
273
+ function getPrimaryToken(file) {
274
+ const parts = file.split("/");
275
+ const basename = parts.slice(-1)[0];
276
+ if (!basename) {
277
+ return null;
278
+ }
279
+ const nameOnly = basename.replace(/\.[^.]+$/u, "");
280
+ const expanded = nameOnly.replaceAll(/([a-z])([A-Z])/gu, "$1 $2").replaceAll(/[_\-.]+/gu, " ").toLowerCase();
281
+ const token = expanded.split(/\s+/u).find((entry) => entry.length > 1 && !GENERIC_TOKENS.has(entry));
282
+ return token ?? null;
283
+ }
284
+ var GENERIC_TOKENS = /* @__PURE__ */ new Set(["src", "index", "main", "test", "spec", "package", "packages", "lib", "dist"]);
285
+
286
+ // src/extract.ts
287
+ var import_node_child_process = require("child_process");
288
+ var import_node_fs = require("fs");
289
+ var import_node_module = require("module");
290
+ var import_node_path2 = __toESM(require("path"), 1);
291
+ function resolveEslintBinForWorkspace(workspaceAbs) {
292
+ const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
293
+ const req = (0, import_node_module.createRequire)(anchor);
294
+ try {
295
+ return req.resolve("eslint/bin/eslint.js");
296
+ } catch {
297
+ try {
298
+ const eslintEntry = req.resolve("eslint");
299
+ const eslintRoot = findPackageRoot(eslintEntry);
300
+ const packageJsonPath = import_node_path2.default.join(eslintRoot, "package.json");
301
+ const packageJson = JSON.parse((0, import_node_fs.readFileSync)(packageJsonPath, "utf8"));
302
+ const relativeBin = resolveBinPath(packageJson.bin);
303
+ const binAbs = import_node_path2.default.resolve(eslintRoot, relativeBin);
304
+ if ((0, import_node_fs.existsSync)(binAbs)) {
305
+ return binAbs;
306
+ }
307
+ } catch {
308
+ }
309
+ throw new Error(`Unable to resolve eslint from workspace: ${workspaceAbs}`);
310
+ }
311
+ }
312
+ function resolveBinPath(bin) {
313
+ if (typeof bin === "string") {
314
+ return bin;
315
+ }
316
+ if (typeof bin?.eslint === "string") {
317
+ return bin.eslint;
318
+ }
319
+ return "bin/eslint.js";
320
+ }
321
+ function findPackageRoot(entryAbs) {
322
+ let current = import_node_path2.default.dirname(entryAbs);
323
+ while (true) {
324
+ const packageJsonPath = import_node_path2.default.join(current, "package.json");
325
+ if ((0, import_node_fs.existsSync)(packageJsonPath)) {
326
+ return current;
327
+ }
328
+ const parent = import_node_path2.default.dirname(current);
329
+ if (parent === current) {
330
+ throw new Error("Package root not found");
331
+ }
332
+ current = parent;
333
+ }
334
+ }
335
+ function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
336
+ const eslintBin = resolveEslintBinForWorkspace(workspaceAbs);
337
+ const proc = (0, import_node_child_process.spawnSync)(process.execPath, [eslintBin, "--print-config", fileAbs], {
338
+ cwd: workspaceAbs,
339
+ encoding: "utf8"
340
+ });
341
+ if (proc.status !== 0) {
342
+ throw new Error(`Failed to run eslint --print-config for ${fileAbs}`);
343
+ }
344
+ const stdout = proc.stdout.trim();
345
+ if (stdout.length === 0 || stdout === "undefined") {
346
+ throw new Error(`Empty ESLint print-config output for ${fileAbs}`);
347
+ }
348
+ let parsed;
349
+ try {
350
+ parsed = JSON.parse(stdout);
351
+ } catch {
352
+ throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`);
353
+ }
354
+ const rules = parsed.rules ?? {};
355
+ const normalized = /* @__PURE__ */ new Map();
356
+ for (const [ruleName, ruleConfig] of Object.entries(rules)) {
357
+ normalized.set(ruleName, normalizeRuleEntry(ruleConfig));
358
+ }
359
+ return normalized;
360
+ }
361
+ function normalizeRuleEntry(raw) {
362
+ if (Array.isArray(raw)) {
363
+ if (raw.length === 0) {
364
+ throw new Error("Rule configuration array cannot be empty");
365
+ }
366
+ const severity = normalizeSeverity(raw[0]);
367
+ const rest = raw.slice(1).map((item) => canonicalizeJson(item));
368
+ if (rest.length === 0) {
369
+ return [severity];
370
+ }
371
+ if (rest.length === 1) {
372
+ return [severity, rest[0]];
373
+ }
374
+ return [severity, canonicalizeJson(rest)];
375
+ }
376
+ return [normalizeSeverity(raw)];
377
+ }
378
+
379
+ // src/snapshot.ts
380
+ var import_promises = require("fs/promises");
381
+ var import_node_path3 = __toESM(require("path"), 1);
382
+ function aggregateRules(ruleMaps) {
383
+ const aggregated = /* @__PURE__ */ new Map();
384
+ for (const rules of ruleMaps) {
385
+ for (const [ruleName, nextEntry] of rules.entries()) {
386
+ const currentEntry = aggregated.get(ruleName);
387
+ if (!currentEntry) {
388
+ aggregated.set(ruleName, canonicalizeJson(nextEntry));
389
+ continue;
390
+ }
391
+ const severityCmp = compareSeverity(nextEntry[0], currentEntry[0]);
392
+ if (severityCmp > 0) {
393
+ aggregated.set(ruleName, canonicalizeJson(nextEntry));
394
+ continue;
395
+ }
396
+ if (severityCmp < 0) {
397
+ continue;
398
+ }
399
+ const currentOptions = currentEntry.length > 1 ? canonicalizeJson(currentEntry[1]) : void 0;
400
+ const nextOptions = nextEntry.length > 1 ? canonicalizeJson(nextEntry[1]) : void 0;
401
+ if (JSON.stringify(currentOptions) !== JSON.stringify(nextOptions)) {
402
+ throw new Error(`Conflicting rule options for ${ruleName} at severity ${currentEntry[0]}`);
403
+ }
404
+ }
405
+ }
406
+ return new Map([...aggregated.entries()].sort(([a], [b]) => a.localeCompare(b)));
407
+ }
408
+ function buildSnapshot(groupId, workspaces, rules) {
409
+ const sortedRules = [...rules.entries()].sort(([a], [b]) => a.localeCompare(b));
410
+ const rulesObject = {};
411
+ for (const [name, config] of sortedRules) {
412
+ rulesObject[name] = canonicalizeJson(config);
413
+ }
414
+ return {
415
+ formatVersion: 1,
416
+ groupId,
417
+ workspaces: sortUnique(workspaces),
418
+ rules: rulesObject
419
+ };
420
+ }
421
+ async function writeSnapshotFile(snapshotDirAbs, snapshot) {
422
+ await (0, import_promises.mkdir)(snapshotDirAbs, { recursive: true });
423
+ const filePath = import_node_path3.default.join(snapshotDirAbs, `${snapshot.groupId}.json`);
424
+ await (0, import_promises.mkdir)(import_node_path3.default.dirname(filePath), { recursive: true });
425
+ const payload = JSON.stringify(snapshot, null, 2);
426
+ await (0, import_promises.writeFile)(filePath, `${payload}
427
+ `, "utf8");
428
+ return filePath;
429
+ }
430
+ async function readSnapshotFile(fileAbs) {
431
+ const raw = await (0, import_promises.readFile)(fileAbs, "utf8");
432
+ return JSON.parse(raw);
433
+ }
434
+
435
+ // src/diff.ts
436
+ function diffSnapshots(before, after) {
437
+ const beforeRules = before.rules;
438
+ const afterRules = after.rules;
439
+ const beforeNames = Object.keys(beforeRules).sort();
440
+ const afterNames = Object.keys(afterRules).sort();
441
+ const introducedRules = afterNames.filter((name) => !beforeNames.includes(name));
442
+ const removedRules = beforeNames.filter((name) => !afterNames.includes(name));
443
+ const severityChanges = [];
444
+ const optionChanges = [];
445
+ for (const name of beforeNames.filter((entry) => afterNames.includes(entry))) {
446
+ const oldEntry = beforeRules[name];
447
+ const newEntry = afterRules[name];
448
+ if (oldEntry[0] !== newEntry[0]) {
449
+ severityChanges.push({
450
+ rule: name,
451
+ before: oldEntry[0],
452
+ after: newEntry[0]
453
+ });
454
+ }
455
+ const oldOptions = oldEntry.length > 1 ? canonicalizeJson(oldEntry[1]) : void 0;
456
+ const newOptions = newEntry.length > 1 ? canonicalizeJson(newEntry[1]) : void 0;
457
+ if (oldEntry[0] === "off" || newEntry[0] === "off") {
458
+ if (oldEntry[0] === "off" && newEntry[0] === "off") {
459
+ if (oldOptions !== void 0 && newOptions === void 0) {
460
+ removedRules.push(name);
461
+ } else if (oldOptions === void 0 && newOptions !== void 0) {
462
+ introducedRules.push(name);
463
+ }
464
+ }
465
+ continue;
466
+ }
467
+ if (JSON.stringify(oldOptions) !== JSON.stringify(newOptions)) {
468
+ optionChanges.push({
469
+ rule: name,
470
+ before: oldOptions,
471
+ after: newOptions
472
+ });
473
+ }
474
+ }
475
+ const beforeWorkspaces = sortUnique(before.workspaces);
476
+ const afterWorkspaces = sortUnique(after.workspaces);
477
+ return {
478
+ introducedRules: sortUnique(introducedRules),
479
+ removedRules: sortUnique(removedRules),
480
+ severityChanges,
481
+ optionChanges,
482
+ workspaceMembershipChanges: {
483
+ added: afterWorkspaces.filter((ws) => !beforeWorkspaces.includes(ws)),
484
+ removed: beforeWorkspaces.filter((ws) => !afterWorkspaces.includes(ws))
485
+ }
486
+ };
487
+ }
488
+ function hasDiff(diff) {
489
+ return diff.introducedRules.length > 0 || diff.removedRules.length > 0 || diff.severityChanges.length > 0 || diff.optionChanges.length > 0 || diff.workspaceMembershipChanges.added.length > 0 || diff.workspaceMembershipChanges.removed.length > 0;
490
+ }
491
+
492
+ // src/config.ts
493
+ var import_cosmiconfig = require("cosmiconfig");
494
+ var import_node_path4 = __toESM(require("path"), 1);
495
+ var DEFAULT_CONFIG = {
496
+ workspaceInput: { mode: "discover" },
497
+ grouping: {
498
+ mode: "match",
499
+ groups: [{ name: "default", match: ["**/*"] }]
500
+ },
501
+ sampling: {
502
+ maxFilesPerWorkspace: 8,
503
+ includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs}"],
504
+ excludeGlobs: ["**/node_modules/**", "**/dist/**"],
505
+ hintGlobs: []
506
+ }
507
+ };
508
+ var SPEC_SEARCH_PLACES = [
509
+ ".eslint-config-snapshot.js",
510
+ ".eslint-config-snapshot.cjs",
511
+ ".eslint-config-snapshot.mjs",
512
+ "eslint-config-snapshot.config.js",
513
+ "eslint-config-snapshot.config.cjs",
514
+ "eslint-config-snapshot.config.mjs",
515
+ "package.json",
516
+ ".eslint-config-snapshotrc",
517
+ ".eslint-config-snapshotrc.json",
518
+ ".eslint-config-snapshotrc.yaml",
519
+ ".eslint-config-snapshotrc.yml",
520
+ ".eslint-config-snapshotrc.js",
521
+ ".eslint-config-snapshotrc.cjs",
522
+ ".eslint-config-snapshotrc.mjs"
523
+ ];
524
+ async function loadConfig(cwd) {
525
+ const found = await findConfigPath(cwd);
526
+ if (!found) {
527
+ return DEFAULT_CONFIG;
528
+ }
529
+ return found.config;
530
+ }
531
+ async function findConfigPath(cwd) {
532
+ const root = import_node_path4.default.resolve(cwd ?? process.cwd());
533
+ const explorer = (0, import_cosmiconfig.cosmiconfig)("eslint-config-snapshot", {
534
+ searchPlaces: SPEC_SEARCH_PLACES,
535
+ stopDir: root
536
+ });
537
+ const result = await explorer.search(root);
538
+ if (!result) {
539
+ return null;
540
+ }
541
+ const maybeConfig = await loadUserConfig(result.config);
542
+ const config = {
543
+ ...DEFAULT_CONFIG,
544
+ ...maybeConfig,
545
+ grouping: {
546
+ ...DEFAULT_CONFIG.grouping,
547
+ ...maybeConfig.grouping
548
+ },
549
+ sampling: {
550
+ ...DEFAULT_CONFIG.sampling,
551
+ ...maybeConfig.sampling
552
+ }
553
+ };
554
+ return {
555
+ path: result.filepath,
556
+ config
557
+ };
558
+ }
559
+ async function loadUserConfig(rawConfig) {
560
+ const resolved = typeof rawConfig === "function" ? await rawConfig() : rawConfig;
561
+ if (resolved === null || resolved === void 0) {
562
+ return {};
563
+ }
564
+ if (typeof resolved !== "object" || Array.isArray(resolved)) {
565
+ throw new TypeError("Invalid config export: expected object, function, or async function returning an object");
566
+ }
567
+ return resolved;
568
+ }
569
+ function getConfigScaffold(preset = "minimal") {
570
+ if (preset === "minimal") {
571
+ return "export default {}\n";
572
+ }
573
+ return `export default {
574
+ workspaceInput: { mode: 'discover' },
575
+ grouping: {
576
+ mode: 'match',
577
+ groups: [{ name: 'default', match: ['**/*'] }]
578
+ },
579
+ sampling: {
580
+ maxFilesPerWorkspace: 8,
581
+ includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
582
+ excludeGlobs: ['**/node_modules/**', '**/dist/**'],
583
+ hintGlobs: []
584
+ }
585
+ }
586
+ `;
587
+ }
588
+ // Annotate the CommonJS export names for ESM import in node:
589
+ 0 && (module.exports = {
590
+ DEFAULT_CONFIG,
591
+ aggregateRules,
592
+ assignGroupsByMatch,
593
+ buildSnapshot,
594
+ canonicalizeJson,
595
+ compareSeverity,
596
+ diffSnapshots,
597
+ discoverWorkspaces,
598
+ extractRulesFromPrintConfig,
599
+ findConfigPath,
600
+ getConfigScaffold,
601
+ hasDiff,
602
+ loadConfig,
603
+ normalizePath,
604
+ normalizeSeverity,
605
+ readSnapshotFile,
606
+ resolveEslintBinForWorkspace,
607
+ sampleWorkspaceFiles,
608
+ sortUnique,
609
+ writeSnapshotFile
610
+ });