@eslint-config-snapshot/api 1.2.0 → 1.3.1

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,17 @@
1
1
  # @eslint-config-snapshot/api
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Improve catalog check/update summaries and dedupe baseline totals across groups.
8
+
9
+ ## 1.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Promote catalog baseline hooks and OSS compatibility hardening with improved docs.
14
+
3
15
  ## 1.2.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  canonicalizeJson: () => canonicalizeJson,
38
38
  compareSeverity: () => compareSeverity,
39
39
  diffSnapshots: () => diffSnapshots,
40
+ discoverWorkspaceRuleCatalog: () => discoverWorkspaceRuleCatalog,
40
41
  discoverWorkspaces: () => discoverWorkspaces,
41
42
  extractRulesForWorkspaceSamples: () => extractRulesForWorkspaceSamples,
42
43
  extractRulesFromPrintConfig: () => extractRulesFromPrintConfig,
@@ -330,6 +331,18 @@ function selectDistributed(files, count, tokenHints) {
330
331
  return sortUnique([...selected, ...preferredPicked, ...fallbackPicked]).slice(0, count);
331
332
  }
332
333
  function pickUniformly(files, count) {
334
+ const early = pickUniformlyEarlyReturn(files, count);
335
+ if (early) {
336
+ return early;
337
+ }
338
+ const picked = [];
339
+ const usedIndices = /* @__PURE__ */ new Set();
340
+ appendAnchors(files, count, picked, usedIndices);
341
+ appendDistributed(files, count, picked, usedIndices);
342
+ appendSequentialFallback(files, count, picked, usedIndices);
343
+ return picked;
344
+ }
345
+ function pickUniformlyEarlyReturn(files, count) {
333
346
  if (count <= 0 || files.length === 0) {
334
347
  return [];
335
348
  }
@@ -337,26 +350,30 @@ function pickUniformly(files, count) {
337
350
  return files;
338
351
  }
339
352
  if (count === 1) {
340
- return [files[0]];
353
+ return [files[0]].filter((entry) => entry !== void 0);
341
354
  }
342
- const picked = [];
343
- const usedIndices = /* @__PURE__ */ new Set();
344
- if (count >= 3) {
345
- const anchorIndices = [0, Math.floor((files.length - 1) / 2), files.length - 1];
346
- for (const anchorIndex of anchorIndices) {
347
- if (picked.length >= count || usedIndices.has(anchorIndex)) {
348
- continue;
349
- }
350
- usedIndices.add(anchorIndex);
351
- const anchored = files[anchorIndex];
352
- if (anchored !== void 0) {
353
- picked.push(anchored);
354
- }
355
+ return void 0;
356
+ }
357
+ function appendAnchors(files, count, picked, usedIndices) {
358
+ if (count < 3) {
359
+ return;
360
+ }
361
+ const anchorIndices = [0, Math.floor((files.length - 1) / 2), files.length - 1];
362
+ for (const anchorIndex of anchorIndices) {
363
+ if (picked.length >= count || usedIndices.has(anchorIndex)) {
364
+ continue;
365
+ }
366
+ usedIndices.add(anchorIndex);
367
+ const anchored = files[anchorIndex];
368
+ if (anchored !== void 0) {
369
+ picked.push(anchored);
355
370
  }
356
371
  }
372
+ }
373
+ function appendDistributed(files, count, picked, usedIndices) {
357
374
  for (const candidate of buildDistributedCandidates(files.length, count)) {
358
375
  if (picked.length >= count) {
359
- break;
376
+ return;
360
377
  }
361
378
  const safeIndex = nextFreeIndex(candidate, usedIndices, files.length);
362
379
  if (usedIndices.has(safeIndex)) {
@@ -368,19 +385,21 @@ function pickUniformly(files, count) {
368
385
  picked.push(selected);
369
386
  }
370
387
  }
371
- if (picked.length < count) {
372
- for (let index = 0; index < files.length && picked.length < count; index += 1) {
373
- if (usedIndices.has(index)) {
374
- continue;
375
- }
376
- usedIndices.add(index);
377
- const fallback = files[index];
378
- if (fallback !== void 0) {
379
- picked.push(fallback);
380
- }
388
+ }
389
+ function appendSequentialFallback(files, count, picked, usedIndices) {
390
+ if (picked.length >= count) {
391
+ return;
392
+ }
393
+ for (let index = 0; index < files.length && picked.length < count; index += 1) {
394
+ if (usedIndices.has(index)) {
395
+ continue;
396
+ }
397
+ usedIndices.add(index);
398
+ const fallback = files[index];
399
+ if (fallback !== void 0) {
400
+ picked.push(fallback);
381
401
  }
382
402
  }
383
- return picked;
384
403
  }
385
404
  function buildDistributedCandidates(length, count) {
386
405
  if (length <= 0 || count <= 0) {
@@ -906,35 +925,11 @@ function diffSnapshots(before, after) {
906
925
  if (oldSerialized === newSerialized) {
907
926
  continue;
908
927
  }
909
- const oldIsOnlyOff = oldVariants.every((entry) => entry[0] === "off");
910
- const newIsOnlyOff = newVariants.every((entry) => entry[0] === "off");
911
- if (oldIsOnlyOff || newIsOnlyOff) {
912
- if (oldIsOnlyOff && newIsOnlyOff) {
913
- const oldHasOptions = oldVariants.some((variant) => variant.length > 1);
914
- const newHasOptions = newVariants.some((variant) => variant.length > 1);
915
- if (oldHasOptions && !newHasOptions) {
916
- removedRules.push(name);
917
- } else if (!oldHasOptions && newHasOptions) {
918
- introducedRules.push(name);
919
- } else if (oldVariants.length > newVariants.length) {
920
- removedRules.push(name);
921
- } else if (oldVariants.length < newVariants.length) {
922
- introducedRules.push(name);
923
- } else {
924
- optionChanges.push({
925
- rule: name,
926
- before: oldVariants,
927
- after: newVariants
928
- });
929
- }
930
- }
928
+ if (hasOffOnlyVariants(oldVariants) || hasOffOnlyVariants(newVariants)) {
929
+ applyOffOnlyVariantDiff(name, oldVariants, newVariants, introducedRules, removedRules, optionChanges);
931
930
  continue;
932
931
  }
933
- optionChanges.push({
934
- rule: name,
935
- before: oldVariants,
936
- after: newVariants
937
- });
932
+ pushOptionChange(optionChanges, name, oldVariants, newVariants);
938
933
  }
939
934
  const beforeWorkspaces = sortUnique(before.workspaces);
940
935
  const afterWorkspaces = sortUnique(after.workspaces);
@@ -949,6 +944,41 @@ function diffSnapshots(before, after) {
949
944
  }
950
945
  };
951
946
  }
947
+ function hasOffOnlyVariants(variants) {
948
+ return variants.every((entry) => entry[0] === "off");
949
+ }
950
+ function applyOffOnlyVariantDiff(ruleName, oldVariants, newVariants, introducedRules, removedRules, optionChanges) {
951
+ const oldIsOnlyOff = hasOffOnlyVariants(oldVariants);
952
+ const newIsOnlyOff = hasOffOnlyVariants(newVariants);
953
+ if (!oldIsOnlyOff || !newIsOnlyOff) {
954
+ return;
955
+ }
956
+ const oldHasOptions = hasAnyVariantOptions(oldVariants);
957
+ const newHasOptions = hasAnyVariantOptions(newVariants);
958
+ if (oldHasOptions && !newHasOptions) {
959
+ removedRules.push(ruleName);
960
+ return;
961
+ }
962
+ if (!oldHasOptions && newHasOptions) {
963
+ introducedRules.push(ruleName);
964
+ return;
965
+ }
966
+ if (oldVariants.length > newVariants.length) {
967
+ removedRules.push(ruleName);
968
+ return;
969
+ }
970
+ if (oldVariants.length < newVariants.length) {
971
+ introducedRules.push(ruleName);
972
+ return;
973
+ }
974
+ pushOptionChange(optionChanges, ruleName, oldVariants, newVariants);
975
+ }
976
+ function hasAnyVariantOptions(variants) {
977
+ return variants.some((variant) => variant.length > 1);
978
+ }
979
+ function pushOptionChange(optionChanges, rule, before, after) {
980
+ optionChanges.push({ rule, before, after });
981
+ }
952
982
  function toVariants(entry) {
953
983
  if (!Array.isArray(entry[0])) {
954
984
  return [canonicalizeJson(entry)];
@@ -968,6 +998,7 @@ function hasDiff(diff) {
968
998
  var import_cosmiconfig = require("cosmiconfig");
969
999
  var import_node_path4 = __toESM(require("path"), 1);
970
1000
  var DEFAULT_CONFIG = {
1001
+ experimentalWithCatalog: false,
971
1002
  workspaceInput: { mode: "discover" },
972
1003
  grouping: {
973
1004
  mode: "match",
@@ -1045,6 +1076,7 @@ function getConfigScaffold(preset = "minimal") {
1045
1076
  return "export default {}\n";
1046
1077
  }
1047
1078
  return `export default {
1079
+ experimentalWithCatalog: false,
1048
1080
  workspaceInput: { mode: 'discover' },
1049
1081
  grouping: {
1050
1082
  mode: 'match',
@@ -1084,6 +1116,202 @@ function getConfigScaffold(preset = "minimal") {
1084
1116
  }
1085
1117
  `;
1086
1118
  }
1119
+
1120
+ // src/catalog.ts
1121
+ var import_promises3 = require("fs/promises");
1122
+ var import_node_module2 = require("module");
1123
+ var import_node_path5 = __toESM(require("path"), 1);
1124
+ var import_node_url2 = require("url");
1125
+ async function discoverWorkspaceRuleCatalog(workspaceAbs) {
1126
+ const anchor = import_node_path5.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
1127
+ const req = (0, import_node_module2.createRequire)(anchor);
1128
+ const coreRules = await discoverCoreRules(req);
1129
+ const pluginPackageNames = await discoverPluginPackageNames(workspaceAbs);
1130
+ const pluginRulesByPrefix = {};
1131
+ for (const packageName of pluginPackageNames) {
1132
+ const prefix = pluginPrefixFromPackageName(packageName);
1133
+ if (!prefix) {
1134
+ continue;
1135
+ }
1136
+ const ruleNames = await discoverPluginRuleNames(req, packageName);
1137
+ if (ruleNames.length === 0) {
1138
+ continue;
1139
+ }
1140
+ const current = pluginRulesByPrefix[prefix] ?? [];
1141
+ current.push(...ruleNames.map((ruleName) => `${prefix}${ruleName}`));
1142
+ pluginRulesByPrefix[prefix] = sortUnique2(current);
1143
+ }
1144
+ const allRules = sortUnique2([...coreRules, ...Object.values(pluginRulesByPrefix).flat()]);
1145
+ return {
1146
+ coreRules,
1147
+ pluginRulesByPrefix: Object.fromEntries(
1148
+ Object.entries(pluginRulesByPrefix).sort((a, b) => a[0].localeCompare(b[0]))
1149
+ ),
1150
+ allRules
1151
+ };
1152
+ }
1153
+ async function discoverCoreRules(req) {
1154
+ try {
1155
+ const resolved = req.resolve("eslint/use-at-your-own-risk");
1156
+ const moduleExports = await import((0, import_node_url2.pathToFileURL)(resolved).href);
1157
+ const builtinRules = moduleExports.builtinRules ?? moduleExports.default?.builtinRules;
1158
+ if (!builtinRules) {
1159
+ return [];
1160
+ }
1161
+ return sortUnique2([...builtinRules.keys()]);
1162
+ } catch {
1163
+ return [];
1164
+ }
1165
+ }
1166
+ async function discoverPluginRuleNames(req, packageName) {
1167
+ try {
1168
+ const resolved = req.resolve(packageName);
1169
+ const moduleExports = await import((0, import_node_url2.pathToFileURL)(resolved).href);
1170
+ const pluginRules = moduleExports.rules ?? moduleExports.default?.rules;
1171
+ if (!pluginRules || typeof pluginRules !== "object") {
1172
+ return [];
1173
+ }
1174
+ return sortUnique2(Object.keys(pluginRules));
1175
+ } catch {
1176
+ return [];
1177
+ }
1178
+ }
1179
+ function pluginPrefixFromPackageName(packageName) {
1180
+ if (packageName.startsWith("@")) {
1181
+ const [scope, name] = packageName.split("/");
1182
+ if (!scope || !name) {
1183
+ return void 0;
1184
+ }
1185
+ if (name === "eslint-plugin") {
1186
+ return `${scope}/`;
1187
+ }
1188
+ if (name.startsWith("eslint-plugin-")) {
1189
+ return `${scope}/${name.slice("eslint-plugin-".length)}/`;
1190
+ }
1191
+ return void 0;
1192
+ }
1193
+ if (packageName === "eslint-plugin") {
1194
+ return "";
1195
+ }
1196
+ if (packageName.startsWith("eslint-plugin-")) {
1197
+ return `${packageName.slice("eslint-plugin-".length)}/`;
1198
+ }
1199
+ return void 0;
1200
+ }
1201
+ async function discoverPluginPackageNames(workspaceAbs) {
1202
+ const results = /* @__PURE__ */ new Set();
1203
+ const nodeModulesDirectories = collectNodeModulesDirectories(workspaceAbs);
1204
+ for (const nodeModulesDirectory of nodeModulesDirectories) {
1205
+ const packageNames = await readPluginPackagesFromNodeModules(nodeModulesDirectory);
1206
+ for (const packageName of packageNames) {
1207
+ results.add(packageName);
1208
+ }
1209
+ }
1210
+ for (const packageJsonPath of collectPackageJsonPaths(workspaceAbs)) {
1211
+ const dependencyNames = await readPluginPackageNamesFromPackageJson(packageJsonPath);
1212
+ for (const packageName of dependencyNames) {
1213
+ results.add(packageName);
1214
+ }
1215
+ }
1216
+ return sortUnique2([...results]);
1217
+ }
1218
+ function collectNodeModulesDirectories(workspaceAbs) {
1219
+ const directories = [];
1220
+ let current = import_node_path5.default.resolve(workspaceAbs);
1221
+ while (true) {
1222
+ directories.push(import_node_path5.default.join(current, "node_modules"));
1223
+ const parent = import_node_path5.default.dirname(current);
1224
+ if (parent === current) {
1225
+ break;
1226
+ }
1227
+ current = parent;
1228
+ }
1229
+ return directories;
1230
+ }
1231
+ function collectPackageJsonPaths(workspaceAbs) {
1232
+ const paths = [];
1233
+ let current = import_node_path5.default.resolve(workspaceAbs);
1234
+ while (true) {
1235
+ paths.push(import_node_path5.default.join(current, "package.json"));
1236
+ const parent = import_node_path5.default.dirname(current);
1237
+ if (parent === current) {
1238
+ break;
1239
+ }
1240
+ current = parent;
1241
+ }
1242
+ return paths;
1243
+ }
1244
+ async function readPluginPackagesFromNodeModules(nodeModulesDirectory) {
1245
+ try {
1246
+ const entries = await (0, import_promises3.readdir)(nodeModulesDirectory, { withFileTypes: true });
1247
+ const results = [];
1248
+ for (const entry of entries) {
1249
+ if (!entry.isDirectory()) {
1250
+ continue;
1251
+ }
1252
+ if (isRootPluginDirectory(entry.name)) {
1253
+ results.push(entry.name);
1254
+ continue;
1255
+ }
1256
+ if (!entry.name.startsWith("@")) {
1257
+ continue;
1258
+ }
1259
+ const scopedPackages = await readScopedPluginPackages(nodeModulesDirectory, entry.name);
1260
+ results.push(...scopedPackages);
1261
+ }
1262
+ return results;
1263
+ } catch {
1264
+ return [];
1265
+ }
1266
+ }
1267
+ function isRootPluginDirectory(name) {
1268
+ return name.startsWith("eslint-plugin-");
1269
+ }
1270
+ async function readScopedPluginPackages(nodeModulesDirectory, scopeName) {
1271
+ const scopeDirectory = import_node_path5.default.join(nodeModulesDirectory, scopeName);
1272
+ const scopedEntries = await (0, import_promises3.readdir)(scopeDirectory, { withFileTypes: true });
1273
+ const packages = [];
1274
+ for (const scopedEntry of scopedEntries) {
1275
+ if (!scopedEntry.isDirectory()) {
1276
+ continue;
1277
+ }
1278
+ if (isScopedPluginDirectory(scopedEntry.name)) {
1279
+ packages.push(`${scopeName}/${scopedEntry.name}`);
1280
+ }
1281
+ }
1282
+ return packages;
1283
+ }
1284
+ function isScopedPluginDirectory(name) {
1285
+ return name === "eslint-plugin" || name.startsWith("eslint-plugin-");
1286
+ }
1287
+ async function readPluginPackageNamesFromPackageJson(packageJsonPath) {
1288
+ try {
1289
+ const raw = await (0, import_promises3.readFile)(packageJsonPath, "utf8");
1290
+ const parsed = JSON.parse(raw);
1291
+ const allNames = /* @__PURE__ */ new Set();
1292
+ for (const record of [
1293
+ parsed.dependencies,
1294
+ parsed.devDependencies,
1295
+ parsed.peerDependencies,
1296
+ parsed.optionalDependencies
1297
+ ]) {
1298
+ for (const name of Object.keys(record ?? {})) {
1299
+ if (isEslintPluginPackageName(name)) {
1300
+ allNames.add(name);
1301
+ }
1302
+ }
1303
+ }
1304
+ return [...allNames];
1305
+ } catch {
1306
+ return [];
1307
+ }
1308
+ }
1309
+ function isEslintPluginPackageName(name) {
1310
+ return name.startsWith("eslint-plugin-") || name === "eslint-plugin" || name.includes("/eslint-plugin-") || name.endsWith("/eslint-plugin");
1311
+ }
1312
+ function sortUnique2(values) {
1313
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
1314
+ }
1087
1315
  // Annotate the CommonJS export names for ESM import in node:
1088
1316
  0 && (module.exports = {
1089
1317
  DEFAULT_CONFIG,
@@ -1093,6 +1321,7 @@ function getConfigScaffold(preset = "minimal") {
1093
1321
  canonicalizeJson,
1094
1322
  compareSeverity,
1095
1323
  diffSnapshots,
1324
+ discoverWorkspaceRuleCatalog,
1096
1325
  discoverWorkspaces,
1097
1326
  extractRulesForWorkspaceSamples,
1098
1327
  extractRulesFromPrintConfig,