@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 +12 -0
- package/dist/index.cjs +282 -53
- package/dist/index.js +281 -53
- package/package.json +1 -1
- package/src/catalog.ts +247 -0
- package/src/config.ts +3 -0
- package/src/diff.ts +56 -28
- package/src/index.ts +3 -0
- package/src/sampling.ts +47 -28
- package/test/catalog.test.ts +81 -0
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
910
|
-
|
|
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
|
|
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,
|