@eslint-config-snapshot/api 0.3.2 → 0.4.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 +6 -0
- package/dist/index.cjs +41 -0
- package/dist/index.js +40 -0
- package/package.json +1 -1
- package/src/extract.ts +57 -0
- package/src/index.ts +2 -2
- package/test/extract.test.ts +26 -1
package/CHANGELOG.md
CHANGED
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,
|
|
@@ -288,6 +289,7 @@ var import_node_child_process = require("child_process");
|
|
|
288
289
|
var import_node_fs = require("fs");
|
|
289
290
|
var import_node_module = require("module");
|
|
290
291
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
292
|
+
var import_node_url = require("url");
|
|
291
293
|
function resolveEslintBinForWorkspace(workspaceAbs) {
|
|
292
294
|
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
293
295
|
const req = (0, import_node_module.createRequire)(anchor);
|
|
@@ -352,6 +354,44 @@ function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
|
|
|
352
354
|
throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`);
|
|
353
355
|
}
|
|
354
356
|
const rules = parsed.rules ?? {};
|
|
357
|
+
return normalizeRules(rules);
|
|
358
|
+
}
|
|
359
|
+
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
360
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
361
|
+
const results = [];
|
|
362
|
+
for (const fileAbs of fileAbsList) {
|
|
363
|
+
try {
|
|
364
|
+
const rules = await evaluate(fileAbs);
|
|
365
|
+
results.push({ fileAbs, rules });
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
368
|
+
results.push({ fileAbs, error: normalizedError });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return results;
|
|
372
|
+
}
|
|
373
|
+
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
374
|
+
try {
|
|
375
|
+
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
376
|
+
const req = (0, import_node_module.createRequire)(anchor);
|
|
377
|
+
const eslintModuleEntry = req.resolve("eslint");
|
|
378
|
+
const eslintModule = await import((0, import_node_url.pathToFileURL)(eslintModuleEntry).href);
|
|
379
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
380
|
+
if (ESLintClass) {
|
|
381
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
382
|
+
return async (fileAbs) => {
|
|
383
|
+
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
384
|
+
if (!config || typeof config !== "object") {
|
|
385
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`);
|
|
386
|
+
}
|
|
387
|
+
return normalizeRules(config.rules ?? {});
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
393
|
+
}
|
|
394
|
+
function normalizeRules(rules) {
|
|
355
395
|
const normalized = /* @__PURE__ */ new Map();
|
|
356
396
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
|
357
397
|
normalized.set(ruleName, normalizeRuleEntry(ruleConfig));
|
|
@@ -607,6 +647,7 @@ function getConfigScaffold(preset = "minimal") {
|
|
|
607
647
|
compareSeverity,
|
|
608
648
|
diffSnapshots,
|
|
609
649
|
discoverWorkspaces,
|
|
650
|
+
extractRulesForWorkspaceSamples,
|
|
610
651
|
extractRulesFromPrintConfig,
|
|
611
652
|
findConfigPath,
|
|
612
653
|
getConfigScaffold,
|
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,44 @@ 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
|
+
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
304
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
305
|
+
const results = [];
|
|
306
|
+
for (const fileAbs of fileAbsList) {
|
|
307
|
+
try {
|
|
308
|
+
const rules = await evaluate(fileAbs);
|
|
309
|
+
results.push({ fileAbs, rules });
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
312
|
+
results.push({ fileAbs, error: normalizedError });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return results;
|
|
316
|
+
}
|
|
317
|
+
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
318
|
+
try {
|
|
319
|
+
const anchor = path2.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
320
|
+
const req = createRequire(anchor);
|
|
321
|
+
const eslintModuleEntry = req.resolve("eslint");
|
|
322
|
+
const eslintModule = await import(pathToFileURL(eslintModuleEntry).href);
|
|
323
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
324
|
+
if (ESLintClass) {
|
|
325
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
326
|
+
return async (fileAbs) => {
|
|
327
|
+
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
328
|
+
if (!config || typeof config !== "object") {
|
|
329
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`);
|
|
330
|
+
}
|
|
331
|
+
return normalizeRules(config.rules ?? {});
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
337
|
+
}
|
|
338
|
+
function normalizeRules(rules) {
|
|
300
339
|
const normalized = /* @__PURE__ */ new Map();
|
|
301
340
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
|
302
341
|
normalized.set(ruleName, normalizeRuleEntry(ruleConfig));
|
|
@@ -551,6 +590,7 @@ export {
|
|
|
551
590
|
compareSeverity,
|
|
552
591
|
diffSnapshots,
|
|
553
592
|
discoverWorkspaces,
|
|
593
|
+
extractRulesForWorkspaceSamples,
|
|
554
594
|
extractRulesFromPrintConfig,
|
|
555
595
|
findConfigPath,
|
|
556
596
|
getConfigScaffold,
|
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,61 @@ 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 async function extractRulesForWorkspaceSamples(
|
|
95
|
+
workspaceAbs: string,
|
|
96
|
+
fileAbsList: string[]
|
|
97
|
+
): Promise<WorkspaceExtractionResult[]> {
|
|
98
|
+
const evaluate = await createWorkspaceEvaluator(workspaceAbs)
|
|
99
|
+
const results: WorkspaceExtractionResult[] = []
|
|
100
|
+
|
|
101
|
+
for (const fileAbs of fileAbsList) {
|
|
102
|
+
try {
|
|
103
|
+
const rules = await evaluate(fileAbs)
|
|
104
|
+
results.push({ fileAbs, rules })
|
|
105
|
+
} catch (error: unknown) {
|
|
106
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
107
|
+
results.push({ fileAbs, error: normalizedError })
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return results
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function createWorkspaceEvaluator(
|
|
115
|
+
workspaceAbs: string
|
|
116
|
+
): Promise<(fileAbs: string) => Promise<ExtractedWorkspaceRules>> {
|
|
117
|
+
try {
|
|
118
|
+
const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
|
|
119
|
+
const req = createRequire(anchor)
|
|
120
|
+
const eslintModuleEntry = req.resolve('eslint')
|
|
121
|
+
const eslintModule = (await import(pathToFileURL(eslintModuleEntry).href)) as {
|
|
122
|
+
ESLint?: new (options: { cwd: string }) => { calculateConfigForFile: (fileAbs: string) => Promise<{ rules?: Record<string, unknown> } | undefined> }
|
|
123
|
+
default?: { ESLint?: new (options: { cwd: string }) => { calculateConfigForFile: (fileAbs: string) => Promise<{ rules?: Record<string, unknown> } | undefined> } }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint
|
|
127
|
+
if (ESLintClass) {
|
|
128
|
+
const eslint = new ESLintClass({ cwd: workspaceAbs })
|
|
129
|
+
return async (fileAbs: string) => {
|
|
130
|
+
const config = await eslint.calculateConfigForFile(fileAbs)
|
|
131
|
+
if (!config || typeof config !== 'object') {
|
|
132
|
+
throw new Error(`Empty ESLint print-config output for ${fileAbs}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return normalizeRules(config.rules ?? {})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// fall through to subprocess-based extractor
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (fileAbs: string) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeRules(rules: Record<string, unknown>): ExtractedWorkspaceRules {
|
|
89
146
|
const normalized = new Map<string, NormalizedRuleEntry>()
|
|
90
147
|
|
|
91
148
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,8 @@ 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 { extractRulesFromPrintConfig, resolveEslintBinForWorkspace } from './extract.js'
|
|
11
|
-
export type { ExtractedWorkspaceRules, NormalizedRuleEntry } from './extract.js'
|
|
10
|
+
export { extractRulesForWorkspaceSamples, extractRulesFromPrintConfig, resolveEslintBinForWorkspace } from './extract.js'
|
|
11
|
+
export type { ExtractedWorkspaceRules, NormalizedRuleEntry, WorkspaceExtractionResult } from './extract.js'
|
|
12
12
|
|
|
13
13
|
export { aggregateRules, buildSnapshot, readSnapshotFile, writeSnapshotFile } from './snapshot.js'
|
|
14
14
|
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
|
})
|