@eslint-config-snapshot/api 0.3.2 → 0.5.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 +12 -0
- package/dist/index.cjs +56 -0
- package/dist/index.js +54 -0
- package/package.json +1 -1
- package/src/extract.ts +74 -0
- package/src/index.ts +7 -2
- package/test/extract.test.ts +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @eslint-config-snapshot/api
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add ESLint runtime version reporting by group in CLI summaries and improve pnpm/corepack isolated test resilience.
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Minor release with improved CLI output consistency and faster workspace extraction using ESLint API fallback strategy.
|
|
14
|
+
|
|
3
15
|
## 0.3.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
compareSeverity: () => compareSeverity,
|
|
39
39
|
diffSnapshots: () => diffSnapshots,
|
|
40
40
|
discoverWorkspaces: () => discoverWorkspaces,
|
|
41
|
+
extractRulesForWorkspaceSamples: () => extractRulesForWorkspaceSamples,
|
|
41
42
|
extractRulesFromPrintConfig: () => extractRulesFromPrintConfig,
|
|
42
43
|
findConfigPath: () => findConfigPath,
|
|
43
44
|
getConfigScaffold: () => getConfigScaffold,
|
|
@@ -47,6 +48,7 @@ __export(index_exports, {
|
|
|
47
48
|
normalizeSeverity: () => normalizeSeverity,
|
|
48
49
|
readSnapshotFile: () => readSnapshotFile,
|
|
49
50
|
resolveEslintBinForWorkspace: () => resolveEslintBinForWorkspace,
|
|
51
|
+
resolveEslintVersionForWorkspace: () => resolveEslintVersionForWorkspace,
|
|
50
52
|
sampleWorkspaceFiles: () => sampleWorkspaceFiles,
|
|
51
53
|
sortUnique: () => sortUnique,
|
|
52
54
|
writeSnapshotFile: () => writeSnapshotFile
|
|
@@ -288,6 +290,7 @@ var import_node_child_process = require("child_process");
|
|
|
288
290
|
var import_node_fs = require("fs");
|
|
289
291
|
var import_node_module = require("module");
|
|
290
292
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
293
|
+
var import_node_url = require("url");
|
|
291
294
|
function resolveEslintBinForWorkspace(workspaceAbs) {
|
|
292
295
|
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
293
296
|
const req = (0, import_node_module.createRequire)(anchor);
|
|
@@ -352,6 +355,57 @@ function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
|
|
|
352
355
|
throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`);
|
|
353
356
|
}
|
|
354
357
|
const rules = parsed.rules ?? {};
|
|
358
|
+
return normalizeRules(rules);
|
|
359
|
+
}
|
|
360
|
+
function resolveEslintVersionForWorkspace(workspaceAbs) {
|
|
361
|
+
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
362
|
+
const req = (0, import_node_module.createRequire)(anchor);
|
|
363
|
+
try {
|
|
364
|
+
const packageJsonPath = req.resolve("eslint/package.json");
|
|
365
|
+
const packageJson = JSON.parse((0, import_node_fs.readFileSync)(packageJsonPath, "utf8"));
|
|
366
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
367
|
+
return packageJson.version;
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
return "unknown";
|
|
372
|
+
}
|
|
373
|
+
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
374
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
375
|
+
const results = [];
|
|
376
|
+
for (const fileAbs of fileAbsList) {
|
|
377
|
+
try {
|
|
378
|
+
const rules = await evaluate(fileAbs);
|
|
379
|
+
results.push({ fileAbs, rules });
|
|
380
|
+
} catch (error) {
|
|
381
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
382
|
+
results.push({ fileAbs, error: normalizedError });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return results;
|
|
386
|
+
}
|
|
387
|
+
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
388
|
+
try {
|
|
389
|
+
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
390
|
+
const req = (0, import_node_module.createRequire)(anchor);
|
|
391
|
+
const eslintModuleEntry = req.resolve("eslint");
|
|
392
|
+
const eslintModule = await import((0, import_node_url.pathToFileURL)(eslintModuleEntry).href);
|
|
393
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
394
|
+
if (ESLintClass) {
|
|
395
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
396
|
+
return async (fileAbs) => {
|
|
397
|
+
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
398
|
+
if (!config || typeof config !== "object") {
|
|
399
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`);
|
|
400
|
+
}
|
|
401
|
+
return normalizeRules(config.rules ?? {});
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
407
|
+
}
|
|
408
|
+
function normalizeRules(rules) {
|
|
355
409
|
const normalized = /* @__PURE__ */ new Map();
|
|
356
410
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
|
357
411
|
normalized.set(ruleName, normalizeRuleEntry(ruleConfig));
|
|
@@ -607,6 +661,7 @@ function getConfigScaffold(preset = "minimal") {
|
|
|
607
661
|
compareSeverity,
|
|
608
662
|
diffSnapshots,
|
|
609
663
|
discoverWorkspaces,
|
|
664
|
+
extractRulesForWorkspaceSamples,
|
|
610
665
|
extractRulesFromPrintConfig,
|
|
611
666
|
findConfigPath,
|
|
612
667
|
getConfigScaffold,
|
|
@@ -616,6 +671,7 @@ function getConfigScaffold(preset = "minimal") {
|
|
|
616
671
|
normalizeSeverity,
|
|
617
672
|
readSnapshotFile,
|
|
618
673
|
resolveEslintBinForWorkspace,
|
|
674
|
+
resolveEslintVersionForWorkspace,
|
|
619
675
|
sampleWorkspaceFiles,
|
|
620
676
|
sortUnique,
|
|
621
677
|
writeSnapshotFile
|
package/dist/index.js
CHANGED
|
@@ -233,6 +233,7 @@ import { spawnSync } from "child_process";
|
|
|
233
233
|
import { existsSync, readFileSync } from "fs";
|
|
234
234
|
import { createRequire } from "module";
|
|
235
235
|
import path2 from "path";
|
|
236
|
+
import { pathToFileURL } from "url";
|
|
236
237
|
function resolveEslintBinForWorkspace(workspaceAbs) {
|
|
237
238
|
const anchor = path2.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
238
239
|
const req = createRequire(anchor);
|
|
@@ -297,6 +298,57 @@ function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
|
|
|
297
298
|
throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`);
|
|
298
299
|
}
|
|
299
300
|
const rules = parsed.rules ?? {};
|
|
301
|
+
return normalizeRules(rules);
|
|
302
|
+
}
|
|
303
|
+
function resolveEslintVersionForWorkspace(workspaceAbs) {
|
|
304
|
+
const anchor = path2.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
305
|
+
const req = createRequire(anchor);
|
|
306
|
+
try {
|
|
307
|
+
const packageJsonPath = req.resolve("eslint/package.json");
|
|
308
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
309
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
310
|
+
return packageJson.version;
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
return "unknown";
|
|
315
|
+
}
|
|
316
|
+
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
317
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
318
|
+
const results = [];
|
|
319
|
+
for (const fileAbs of fileAbsList) {
|
|
320
|
+
try {
|
|
321
|
+
const rules = await evaluate(fileAbs);
|
|
322
|
+
results.push({ fileAbs, rules });
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
325
|
+
results.push({ fileAbs, error: normalizedError });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return results;
|
|
329
|
+
}
|
|
330
|
+
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
331
|
+
try {
|
|
332
|
+
const anchor = path2.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
333
|
+
const req = createRequire(anchor);
|
|
334
|
+
const eslintModuleEntry = req.resolve("eslint");
|
|
335
|
+
const eslintModule = await import(pathToFileURL(eslintModuleEntry).href);
|
|
336
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
337
|
+
if (ESLintClass) {
|
|
338
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
339
|
+
return async (fileAbs) => {
|
|
340
|
+
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
341
|
+
if (!config || typeof config !== "object") {
|
|
342
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`);
|
|
343
|
+
}
|
|
344
|
+
return normalizeRules(config.rules ?? {});
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
350
|
+
}
|
|
351
|
+
function normalizeRules(rules) {
|
|
300
352
|
const normalized = /* @__PURE__ */ new Map();
|
|
301
353
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
|
302
354
|
normalized.set(ruleName, normalizeRuleEntry(ruleConfig));
|
|
@@ -551,6 +603,7 @@ export {
|
|
|
551
603
|
compareSeverity,
|
|
552
604
|
diffSnapshots,
|
|
553
605
|
discoverWorkspaces,
|
|
606
|
+
extractRulesForWorkspaceSamples,
|
|
554
607
|
extractRulesFromPrintConfig,
|
|
555
608
|
findConfigPath,
|
|
556
609
|
getConfigScaffold,
|
|
@@ -560,6 +613,7 @@ export {
|
|
|
560
613
|
normalizeSeverity,
|
|
561
614
|
readSnapshotFile,
|
|
562
615
|
resolveEslintBinForWorkspace,
|
|
616
|
+
resolveEslintVersionForWorkspace,
|
|
563
617
|
sampleWorkspaceFiles,
|
|
564
618
|
sortUnique,
|
|
565
619
|
writeSnapshotFile
|
package/package.json
CHANGED
package/src/extract.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { spawnSync } from 'node:child_process'
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { createRequire } from 'node:module'
|
|
4
4
|
import path from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
5
6
|
|
|
6
7
|
import { canonicalizeJson, normalizeSeverity } from './core.js'
|
|
7
8
|
|
|
8
9
|
export type NormalizedRuleEntry = [severity: 'off' | 'warn' | 'error'] | [severity: 'off' | 'warn' | 'error', options: unknown]
|
|
9
10
|
|
|
10
11
|
export type ExtractedWorkspaceRules = Map<string, NormalizedRuleEntry>
|
|
12
|
+
export type WorkspaceExtractionResult = { fileAbs: string; rules?: ExtractedWorkspaceRules; error?: Error }
|
|
11
13
|
|
|
12
14
|
export function resolveEslintBinForWorkspace(workspaceAbs: string): string {
|
|
13
15
|
const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
|
|
@@ -86,6 +88,78 @@ export function extractRulesFromPrintConfig(workspaceAbs: string, fileAbs: strin
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
const rules = (parsed as { rules?: Record<string, unknown> }).rules ?? {}
|
|
91
|
+
return normalizeRules(rules)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveEslintVersionForWorkspace(workspaceAbs: string): string {
|
|
95
|
+
const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
|
|
96
|
+
const req = createRequire(anchor)
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const packageJsonPath = req.resolve('eslint/package.json')
|
|
100
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { version?: string }
|
|
101
|
+
if (typeof packageJson.version === 'string' && packageJson.version.length > 0) {
|
|
102
|
+
return packageJson.version
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// fall through to deterministic unknown marker
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return 'unknown'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function extractRulesForWorkspaceSamples(
|
|
112
|
+
workspaceAbs: string,
|
|
113
|
+
fileAbsList: string[]
|
|
114
|
+
): Promise<WorkspaceExtractionResult[]> {
|
|
115
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs)
|
|
116
|
+
const results: WorkspaceExtractionResult[] = []
|
|
117
|
+
|
|
118
|
+
for (const fileAbs of fileAbsList) {
|
|
119
|
+
try {
|
|
120
|
+
const rules = await evaluate(fileAbs)
|
|
121
|
+
results.push({ fileAbs, rules })
|
|
122
|
+
} catch (error: unknown) {
|
|
123
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
124
|
+
results.push({ fileAbs, error: normalizedError })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return results
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function createWorkspaceEvaluator(
|
|
132
|
+
workspaceAbs: string
|
|
133
|
+
): Promise<(fileAbs: string) => Promise<ExtractedWorkspaceRules>> {
|
|
134
|
+
try {
|
|
135
|
+
const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
|
|
136
|
+
const req = createRequire(anchor)
|
|
137
|
+
const eslintModuleEntry = req.resolve('eslint')
|
|
138
|
+
const eslintModule = (await import(pathToFileURL(eslintModuleEntry).href)) as {
|
|
139
|
+
ESLint?: new (options: { cwd: string }) => { calculateConfigForFile: (fileAbs: string) => Promise<{ rules?: Record<string, unknown> } | undefined> }
|
|
140
|
+
default?: { ESLint?: new (options: { cwd: string }) => { calculateConfigForFile: (fileAbs: string) => Promise<{ rules?: Record<string, unknown> } | undefined> } }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint
|
|
144
|
+
if (ESLintClass) {
|
|
145
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs })
|
|
146
|
+
return async (fileAbs: string) => {
|
|
147
|
+
const config = await eslint.calculateConfigForFile(fileAbs)
|
|
148
|
+
if (!config || typeof config !== 'object') {
|
|
149
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return normalizeRules(config.rules ?? {})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// fall through to subprocess-based extractor
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (fileAbs: string) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeRules(rules: Record<string, unknown>): ExtractedWorkspaceRules {
|
|
89
163
|
const normalized = new Map<string, NormalizedRuleEntry>()
|
|
90
164
|
|
|
91
165
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,13 @@ export type { GroupAssignment, GroupDefinition, WorkspaceDiscovery, WorkspaceInp
|
|
|
7
7
|
export { sampleWorkspaceFiles } from './sampling.js'
|
|
8
8
|
export type { SamplingConfig } from './sampling.js'
|
|
9
9
|
|
|
10
|
-
export {
|
|
11
|
-
|
|
10
|
+
export {
|
|
11
|
+
extractRulesForWorkspaceSamples,
|
|
12
|
+
extractRulesFromPrintConfig,
|
|
13
|
+
resolveEslintBinForWorkspace,
|
|
14
|
+
resolveEslintVersionForWorkspace
|
|
15
|
+
} from './extract.js'
|
|
16
|
+
export type { ExtractedWorkspaceRules, NormalizedRuleEntry, WorkspaceExtractionResult } from './extract.js'
|
|
12
17
|
|
|
13
18
|
export { aggregateRules, buildSnapshot, readSnapshotFile, writeSnapshotFile } from './snapshot.js'
|
|
14
19
|
export type { SnapshotFile } from './snapshot.js'
|
package/test/extract.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'node:os'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { afterAll, describe, expect, it } from 'vitest'
|
|
5
5
|
|
|
6
|
-
import { extractRulesFromPrintConfig, resolveEslintBinForWorkspace } from '../src/index.js'
|
|
6
|
+
import { extractRulesForWorkspaceSamples, extractRulesFromPrintConfig, resolveEslintBinForWorkspace } from '../src/index.js'
|
|
7
7
|
|
|
8
8
|
const workspace = path.join(os.tmpdir(), `snapshot-extract-${Date.now()}`)
|
|
9
9
|
|
|
@@ -137,4 +137,29 @@ describe('extract', () => {
|
|
|
137
137
|
`Failed to run eslint --print-config for ${fileAbs}`
|
|
138
138
|
)
|
|
139
139
|
})
|
|
140
|
+
|
|
141
|
+
it('extracts multiple sampled files in one workspace call', async () => {
|
|
142
|
+
const multiWorkspace = `${workspace}-multi`
|
|
143
|
+
await mkdir(path.join(multiWorkspace, 'node_modules/eslint/bin'), { recursive: true })
|
|
144
|
+
await mkdir(path.join(multiWorkspace, 'src'), { recursive: true })
|
|
145
|
+
await writeFile(path.join(multiWorkspace, 'node_modules/eslint/package.json'), JSON.stringify({ name: 'eslint', version: '0.0.0' }, null, 2))
|
|
146
|
+
await writeFile(
|
|
147
|
+
path.join(multiWorkspace, 'node_modules/eslint/bin/eslint.js'),
|
|
148
|
+
"console.log(JSON.stringify({ rules: { 'no-console': 1 } }))\n"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const fileA = path.join(multiWorkspace, 'src/a.ts')
|
|
152
|
+
const fileB = path.join(multiWorkspace, 'src/b.ts')
|
|
153
|
+
await writeFile(fileA, 'export const a = 1\n')
|
|
154
|
+
await writeFile(fileB, 'export const b = 1\n')
|
|
155
|
+
|
|
156
|
+
const extracted = await extractRulesForWorkspaceSamples(multiWorkspace, [fileA, fileB])
|
|
157
|
+
expect(extracted).toHaveLength(2)
|
|
158
|
+
for (const entry of extracted) {
|
|
159
|
+
expect(entry.error).toBeUndefined()
|
|
160
|
+
expect(entry.rules ? Object.fromEntries(entry.rules.entries()) : {}).toEqual({
|
|
161
|
+
'no-console': ['warn']
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
})
|
|
140
165
|
})
|