@eslint-config-snapshot/cli 1.2.0 → 1.3.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 +11 -0
- package/dist/index.cjs +801 -325
- package/dist/index.js +793 -316
- package/package.json +2 -2
- package/src/commands/catalog.ts +394 -0
- package/src/commands/check.ts +62 -40
- package/src/formatters.ts +115 -27
- package/src/index.ts +69 -7
- package/src/run-context.ts +50 -30
- package/src/runtime.ts +203 -93
- package/test/cli.integration.test.ts +92 -0
- package/test/cli.terminal.integration.test.ts +70 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-config-snapshot/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"commander": "^14.0.3",
|
|
32
32
|
"debug": "^4.4.3",
|
|
33
33
|
"fast-glob": "^3.3.3",
|
|
34
|
-
"@eslint-config-snapshot/api": "1.
|
|
34
|
+
"@eslint-config-snapshot/api": "1.3.0"
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import fg from 'fast-glob'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { formatShortCatalog, type RuleEntry, type RuleObject, type UsageStats } from '../formatters.js'
|
|
6
|
+
import { resolveGroupRuleCatalogs } from '../runtime.js'
|
|
7
|
+
import { type TerminalIO } from '../terminal.js'
|
|
8
|
+
import { writeDiscoveredWorkspacesSummary, writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
|
|
9
|
+
import { prepareSnapshotExecution } from './snapshot-executor.js'
|
|
10
|
+
|
|
11
|
+
export type CatalogFormat = 'json' | 'short'
|
|
12
|
+
|
|
13
|
+
const CATALOG_FILE_SUFFIX = '.catalog.json'
|
|
14
|
+
|
|
15
|
+
type CatalogRow = {
|
|
16
|
+
groupId: string
|
|
17
|
+
availableRules: string[]
|
|
18
|
+
coreRules: string[]
|
|
19
|
+
pluginRulesByPrefix: Record<string, string[]>
|
|
20
|
+
observedRules: string[]
|
|
21
|
+
missingRules: string[]
|
|
22
|
+
observedOffRules: string[]
|
|
23
|
+
observedActiveRules: string[]
|
|
24
|
+
totalStats: UsageStats & { observedOutsideCatalog: number }
|
|
25
|
+
coreStats: UsageStats
|
|
26
|
+
pluginStats: Array<{ pluginId: string } & UsageStats>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type CatalogBaselineFile = CatalogRow & { formatVersion: 1 }
|
|
30
|
+
|
|
31
|
+
type CatalogCheckDiff = {
|
|
32
|
+
groupId: string
|
|
33
|
+
availableBefore: number
|
|
34
|
+
availableAfter: number
|
|
35
|
+
introducedAvailable: number
|
|
36
|
+
removedAvailable: number
|
|
37
|
+
inUseBefore: number
|
|
38
|
+
inUseAfter: number
|
|
39
|
+
errorBefore: number
|
|
40
|
+
errorAfter: number
|
|
41
|
+
warnBefore: number
|
|
42
|
+
warnAfter: number
|
|
43
|
+
offBefore: number
|
|
44
|
+
offAfter: number
|
|
45
|
+
activeBefore: number
|
|
46
|
+
activeAfter: number
|
|
47
|
+
inactiveBefore: number
|
|
48
|
+
inactiveAfter: number
|
|
49
|
+
missingBefore: number
|
|
50
|
+
missingAfter: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function executeCatalog(
|
|
54
|
+
cwd: string,
|
|
55
|
+
terminal: TerminalIO,
|
|
56
|
+
snapshotDir: string,
|
|
57
|
+
format: CatalogFormat,
|
|
58
|
+
missingOnly: boolean
|
|
59
|
+
): Promise<number> {
|
|
60
|
+
const rows = await computeCatalogRows(cwd, terminal, snapshotDir, `catalog:${format}`, true)
|
|
61
|
+
|
|
62
|
+
if (format === 'short') {
|
|
63
|
+
terminal.write(formatShortCatalog(rows, missingOnly))
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const output = rows.map((row) => {
|
|
68
|
+
if (!missingOnly) {
|
|
69
|
+
return row
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
groupId: row.groupId,
|
|
74
|
+
totalStats: row.totalStats,
|
|
75
|
+
coreStats: row.coreStats,
|
|
76
|
+
pluginStats: row.pluginStats,
|
|
77
|
+
missingRules: row.missingRules
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
terminal.write(`${JSON.stringify(output, null, 2)}\n`)
|
|
82
|
+
return rows.length === 0 ? 1 : 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function executeCatalogUpdate(cwd: string, terminal: TerminalIO, snapshotDir: string): Promise<number> {
|
|
86
|
+
const rows = await computeCatalogRows(cwd, terminal, snapshotDir, 'catalog:update', false)
|
|
87
|
+
await writeCatalogBaselineFiles(cwd, snapshotDir, rows)
|
|
88
|
+
|
|
89
|
+
const groups = rows.length
|
|
90
|
+
const available = rows.reduce((sum, row) => sum + row.totalStats.totalAvailable, 0)
|
|
91
|
+
const inUse = rows.reduce((sum, row) => sum + row.totalStats.inUse, 0)
|
|
92
|
+
terminal.write(`🧪 Catalog baseline updated: ${groups} groups, ${available} available rules, ${inUse} currently in use.\n`)
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function executeCatalogCheck(cwd: string, terminal: TerminalIO, snapshotDir: string): Promise<number> {
|
|
97
|
+
const rows = await computeCatalogRows(cwd, terminal, snapshotDir, 'catalog:check', false)
|
|
98
|
+
const current = new Map(rows.map((row) => [row.groupId, row]))
|
|
99
|
+
const stored = await loadCatalogBaselineFiles(cwd, snapshotDir)
|
|
100
|
+
|
|
101
|
+
if (stored.size === 0) {
|
|
102
|
+
terminal.write('No catalog baseline found yet.\n')
|
|
103
|
+
terminal.write('Run `eslint-config-snapshot catalog-update` or `eslint-config-snapshot --update --experimental-with-catalog`.\n')
|
|
104
|
+
return 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const diffs = compareCatalogBaselines(stored, current)
|
|
108
|
+
if (diffs.length === 0) {
|
|
109
|
+
terminal.write('Great news: no catalog drift detected.\n')
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
terminal.write(`⚠️ Heads up: catalog drift detected in ${diffs.length} groups.\n`)
|
|
114
|
+
for (const diff of diffs) {
|
|
115
|
+
terminal.write(
|
|
116
|
+
[
|
|
117
|
+
`group ${diff.groupId}`,
|
|
118
|
+
` available: ${diff.availableBefore} -> ${diff.availableAfter} (+${diff.introducedAvailable}/-${diff.removedAvailable})`,
|
|
119
|
+
` in use: ${diff.inUseBefore} -> ${diff.inUseAfter}`,
|
|
120
|
+
` severity: error ${diff.errorBefore} -> ${diff.errorAfter} | warn ${diff.warnBefore} -> ${diff.warnAfter} | off ${diff.offBefore} -> ${diff.offAfter}`,
|
|
121
|
+
` active: ${diff.activeBefore} -> ${diff.activeAfter}`,
|
|
122
|
+
` off: ${diff.inactiveBefore} -> ${diff.inactiveAfter}`,
|
|
123
|
+
` not used: ${diff.missingBefore} -> ${diff.missingAfter}`
|
|
124
|
+
].join('\n')
|
|
125
|
+
)
|
|
126
|
+
terminal.write('\n')
|
|
127
|
+
}
|
|
128
|
+
terminal.subtle('Tip: run `eslint-config-snapshot catalog-update` when you intentionally accept catalog changes.\n')
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function computeCatalogRows(
|
|
133
|
+
cwd: string,
|
|
134
|
+
terminal: TerminalIO,
|
|
135
|
+
snapshotDir: string,
|
|
136
|
+
commandLabel: string,
|
|
137
|
+
printDiscoverySummary: boolean
|
|
138
|
+
): Promise<CatalogRow[]> {
|
|
139
|
+
const prepared = await prepareSnapshotExecution({
|
|
140
|
+
cwd,
|
|
141
|
+
snapshotDir,
|
|
142
|
+
terminal,
|
|
143
|
+
commandLabel,
|
|
144
|
+
progressMessage: '🔎 Checking current ESLint configuration...\n'
|
|
145
|
+
})
|
|
146
|
+
if (!prepared.ok) {
|
|
147
|
+
throw new Error(`Catalog operation aborted with exit code ${prepared.exitCode}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { foundConfig, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared
|
|
151
|
+
if (!foundConfig && printDiscoverySummary) {
|
|
152
|
+
writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces)
|
|
153
|
+
}
|
|
154
|
+
if (printDiscoverySummary) {
|
|
155
|
+
writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const catalogs = await resolveGroupRuleCatalogs(cwd)
|
|
159
|
+
return [...currentSnapshots.values()]
|
|
160
|
+
.map((snapshot) => {
|
|
161
|
+
const observedRules = Object.keys(snapshot.rules).sort((a, b) => a.localeCompare(b))
|
|
162
|
+
const catalog = catalogs.get(snapshot.groupId)
|
|
163
|
+
const availableRules = catalog?.allRules ?? []
|
|
164
|
+
const availableRuleSet = new Set(availableRules)
|
|
165
|
+
const missingRules = availableRules.filter((ruleName) => !snapshot.rules[ruleName])
|
|
166
|
+
const observedOffRules = observedRules.filter((ruleName) => isRuleOffOnly(snapshot.rules[ruleName]))
|
|
167
|
+
const observedActiveRules = observedRules.filter((ruleName) => !isRuleOffOnly(snapshot.rules[ruleName]))
|
|
168
|
+
const observedOutsideCatalog = observedRules.filter((ruleName) => !availableRuleSet.has(ruleName)).length
|
|
169
|
+
|
|
170
|
+
const coreRules = catalog?.coreRules ?? []
|
|
171
|
+
const coreStats = buildUsageStats(coreRules, snapshot.rules)
|
|
172
|
+
const pluginRulesByPrefix = catalog?.pluginRulesByPrefix ?? {}
|
|
173
|
+
const pluginStats = Object.entries(pluginRulesByPrefix)
|
|
174
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
175
|
+
.map(([pluginId, rules]) => ({
|
|
176
|
+
pluginId: pluginId.slice(0, -1),
|
|
177
|
+
...buildUsageStats(rules, snapshot.rules)
|
|
178
|
+
}))
|
|
179
|
+
|
|
180
|
+
const totalStats = {
|
|
181
|
+
...buildUsageStats(availableRules, snapshot.rules),
|
|
182
|
+
observedOutsideCatalog
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
groupId: snapshot.groupId,
|
|
187
|
+
availableRules,
|
|
188
|
+
coreRules,
|
|
189
|
+
pluginRulesByPrefix,
|
|
190
|
+
observedRules,
|
|
191
|
+
missingRules,
|
|
192
|
+
observedOffRules,
|
|
193
|
+
observedActiveRules,
|
|
194
|
+
totalStats,
|
|
195
|
+
coreStats,
|
|
196
|
+
pluginStats
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
.sort((a, b) => a.groupId.localeCompare(b.groupId))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function writeCatalogBaselineFiles(cwd: string, snapshotDir: string, rows: CatalogRow[]): Promise<void> {
|
|
203
|
+
await mkdir(path.join(cwd, snapshotDir), { recursive: true })
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
const filePath = path.join(cwd, snapshotDir, `${row.groupId}${CATALOG_FILE_SUFFIX}`)
|
|
206
|
+
await mkdir(path.dirname(filePath), { recursive: true })
|
|
207
|
+
const payload: CatalogBaselineFile = {
|
|
208
|
+
formatVersion: 1,
|
|
209
|
+
...row
|
|
210
|
+
}
|
|
211
|
+
await writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8')
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function loadCatalogBaselineFiles(cwd: string, snapshotDir: string): Promise<Map<string, CatalogBaselineFile>> {
|
|
216
|
+
const dir = path.join(cwd, snapshotDir)
|
|
217
|
+
const rawFiles = await fg(`**/*${CATALOG_FILE_SUFFIX}`, {
|
|
218
|
+
cwd: dir,
|
|
219
|
+
absolute: true,
|
|
220
|
+
onlyFiles: true,
|
|
221
|
+
dot: true,
|
|
222
|
+
suppressErrors: true
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const map = new Map<string, CatalogBaselineFile>()
|
|
226
|
+
const sortedFiles = rawFiles.map(String).sort((a, b) => a.localeCompare(b))
|
|
227
|
+
for (const filePath of sortedFiles) {
|
|
228
|
+
const raw = await readFile(filePath, 'utf8')
|
|
229
|
+
const parsed = JSON.parse(raw) as CatalogBaselineFile
|
|
230
|
+
map.set(parsed.groupId, parsed)
|
|
231
|
+
}
|
|
232
|
+
return map
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function compareCatalogBaselines(
|
|
236
|
+
before: Map<string, CatalogBaselineFile>,
|
|
237
|
+
after: Map<string, CatalogRow>
|
|
238
|
+
): CatalogCheckDiff[] {
|
|
239
|
+
const ids = [...new Set([...before.keys(), ...after.keys()])].sort((a, b) => a.localeCompare(b))
|
|
240
|
+
const diffs: CatalogCheckDiff[] = []
|
|
241
|
+
|
|
242
|
+
for (const id of ids) {
|
|
243
|
+
const prev = before.get(id)
|
|
244
|
+
const next = after.get(id)
|
|
245
|
+
if (!prev || !next) {
|
|
246
|
+
diffs.push({
|
|
247
|
+
groupId: id,
|
|
248
|
+
availableBefore: prev?.totalStats.totalAvailable ?? 0,
|
|
249
|
+
availableAfter: next?.totalStats.totalAvailable ?? 0,
|
|
250
|
+
introducedAvailable: next ? diffSet(next.availableRules, prev?.availableRules ?? []).length : 0,
|
|
251
|
+
removedAvailable: prev ? diffSet(prev.availableRules, next?.availableRules ?? []).length : 0,
|
|
252
|
+
inUseBefore: prev?.totalStats.inUse ?? 0,
|
|
253
|
+
inUseAfter: next?.totalStats.inUse ?? 0,
|
|
254
|
+
errorBefore: prev?.totalStats.error ?? 0,
|
|
255
|
+
errorAfter: next?.totalStats.error ?? 0,
|
|
256
|
+
warnBefore: prev?.totalStats.warn ?? 0,
|
|
257
|
+
warnAfter: next?.totalStats.warn ?? 0,
|
|
258
|
+
offBefore: prev?.totalStats.off ?? 0,
|
|
259
|
+
offAfter: next?.totalStats.off ?? 0,
|
|
260
|
+
activeBefore: prev?.totalStats.active ?? 0,
|
|
261
|
+
activeAfter: next?.totalStats.active ?? 0,
|
|
262
|
+
inactiveBefore: prev?.totalStats.inactive ?? 0,
|
|
263
|
+
inactiveAfter: next?.totalStats.inactive ?? 0,
|
|
264
|
+
missingBefore: prev?.totalStats.missing ?? 0,
|
|
265
|
+
missingAfter: next?.totalStats.missing ?? 0
|
|
266
|
+
})
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const introduced = diffSet(next.availableRules, prev.availableRules).length
|
|
271
|
+
const removed = diffSet(prev.availableRules, next.availableRules).length
|
|
272
|
+
const changed =
|
|
273
|
+
introduced > 0 ||
|
|
274
|
+
removed > 0 ||
|
|
275
|
+
prev.totalStats.inUse !== next.totalStats.inUse ||
|
|
276
|
+
prev.totalStats.error !== next.totalStats.error ||
|
|
277
|
+
prev.totalStats.warn !== next.totalStats.warn ||
|
|
278
|
+
prev.totalStats.off !== next.totalStats.off ||
|
|
279
|
+
prev.totalStats.active !== next.totalStats.active ||
|
|
280
|
+
prev.totalStats.inactive !== next.totalStats.inactive ||
|
|
281
|
+
prev.totalStats.missing !== next.totalStats.missing
|
|
282
|
+
if (!changed) {
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
diffs.push({
|
|
287
|
+
groupId: id,
|
|
288
|
+
availableBefore: prev.totalStats.totalAvailable,
|
|
289
|
+
availableAfter: next.totalStats.totalAvailable,
|
|
290
|
+
introducedAvailable: introduced,
|
|
291
|
+
removedAvailable: removed,
|
|
292
|
+
inUseBefore: prev.totalStats.inUse,
|
|
293
|
+
inUseAfter: next.totalStats.inUse,
|
|
294
|
+
errorBefore: prev.totalStats.error,
|
|
295
|
+
errorAfter: next.totalStats.error,
|
|
296
|
+
warnBefore: prev.totalStats.warn,
|
|
297
|
+
warnAfter: next.totalStats.warn,
|
|
298
|
+
offBefore: prev.totalStats.off,
|
|
299
|
+
offAfter: next.totalStats.off,
|
|
300
|
+
activeBefore: prev.totalStats.active,
|
|
301
|
+
activeAfter: next.totalStats.active,
|
|
302
|
+
inactiveBefore: prev.totalStats.inactive,
|
|
303
|
+
inactiveAfter: next.totalStats.inactive,
|
|
304
|
+
missingBefore: prev.totalStats.missing,
|
|
305
|
+
missingAfter: next.totalStats.missing
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return diffs
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function diffSet(source: string[], target: string[]): string[] {
|
|
313
|
+
const targetSet = new Set(target)
|
|
314
|
+
return source.filter((item) => !targetSet.has(item))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function buildUsageStats(availableRules: string[], observedRules: RuleObject): UsageStats {
|
|
318
|
+
let inUse = 0
|
|
319
|
+
let active = 0
|
|
320
|
+
let inactive = 0
|
|
321
|
+
let error = 0
|
|
322
|
+
let warn = 0
|
|
323
|
+
let off = 0
|
|
324
|
+
|
|
325
|
+
for (const ruleName of availableRules) {
|
|
326
|
+
const observed = observedRules[ruleName]
|
|
327
|
+
if (!observed) {
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
inUse += 1
|
|
331
|
+
const severity = getPrimarySeverity(observed)
|
|
332
|
+
if (severity === 'error') {
|
|
333
|
+
error += 1
|
|
334
|
+
active += 1
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
if (severity === 'warn') {
|
|
338
|
+
warn += 1
|
|
339
|
+
active += 1
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
342
|
+
off += 1
|
|
343
|
+
inactive += 1
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const totalAvailable = availableRules.length
|
|
347
|
+
const missing = Math.max(0, totalAvailable - inUse)
|
|
348
|
+
return {
|
|
349
|
+
totalAvailable,
|
|
350
|
+
inUse,
|
|
351
|
+
active,
|
|
352
|
+
inactive,
|
|
353
|
+
error,
|
|
354
|
+
warn,
|
|
355
|
+
off,
|
|
356
|
+
missing,
|
|
357
|
+
inUsePct: toPercent(inUse, totalAvailable),
|
|
358
|
+
activePctOfInUse: toPercent(active, inUse)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function isRuleOffOnly(entry: RuleObject[string] | undefined): boolean {
|
|
363
|
+
if (!entry) {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
if (!Array.isArray(entry[0])) {
|
|
367
|
+
return (entry as RuleEntry)[0] === 'off'
|
|
368
|
+
}
|
|
369
|
+
const variants = entry as RuleEntry[]
|
|
370
|
+
return variants.every((variant) => variant[0] === 'off')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getPrimarySeverity(entry: RuleObject[string]): 'error' | 'warn' | 'off' {
|
|
374
|
+
if (!Array.isArray(entry[0])) {
|
|
375
|
+
const single = entry as RuleEntry
|
|
376
|
+
return single[0]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const variants = entry as RuleEntry[]
|
|
380
|
+
if (variants.some((variant) => variant[0] === 'error')) {
|
|
381
|
+
return 'error'
|
|
382
|
+
}
|
|
383
|
+
if (variants.some((variant) => variant[0] === 'warn')) {
|
|
384
|
+
return 'warn'
|
|
385
|
+
}
|
|
386
|
+
return 'off'
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function toPercent(value: number, total: number): number {
|
|
390
|
+
if (total === 0) {
|
|
391
|
+
return 0
|
|
392
|
+
}
|
|
393
|
+
return Number(((value / total) * 100).toFixed(1))
|
|
394
|
+
}
|
package/src/commands/check.ts
CHANGED
|
@@ -52,61 +52,83 @@ export async function executeCheck(
|
|
|
52
52
|
writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces)
|
|
53
53
|
}
|
|
54
54
|
if (storedSnapshots.size === 0) {
|
|
55
|
-
|
|
56
|
-
terminal.write(
|
|
57
|
-
`Rules found in this analysis: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).\n`
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
const canPromptBaseline = defaultInvocation || format === 'summary'
|
|
61
|
-
if (canPromptBaseline && terminal.isInteractive) {
|
|
62
|
-
const shouldCreateBaseline = await terminal.askYesNo(
|
|
63
|
-
'No baseline yet. Do you want to save this analyzed rule state as your baseline now? [Y/n] ',
|
|
64
|
-
true
|
|
65
|
-
)
|
|
66
|
-
if (shouldCreateBaseline) {
|
|
67
|
-
await writeSnapshots(cwd, snapshotDir, currentSnapshots)
|
|
68
|
-
const createdSummary = summarizeSnapshots(currentSnapshots)
|
|
69
|
-
terminal.write(`Great start: baseline created with ${createdSummary.groups} groups and ${createdSummary.rules} rules.\n`)
|
|
70
|
-
terminal.subtle(UPDATE_HINT)
|
|
71
|
-
return 0
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
terminal.write('You are almost set: no baseline snapshot found yet.\n')
|
|
76
|
-
terminal.write('Run `eslint-config-snapshot --update` to create your first baseline.\n')
|
|
77
|
-
return 1
|
|
55
|
+
return handleMissingBaseline(cwd, format, defaultInvocation, terminal, snapshotDir, currentSnapshots)
|
|
78
56
|
}
|
|
79
57
|
|
|
80
58
|
const changes = compareSnapshotMaps(storedSnapshots, currentSnapshots)
|
|
81
59
|
const eslintVersionsByGroup = terminal.showProgress ? await resolveGroupEslintVersions(cwd) : new Map<string, string[]>()
|
|
82
60
|
|
|
83
61
|
if (format === 'status') {
|
|
84
|
-
|
|
85
|
-
terminal.write('clean\n')
|
|
86
|
-
return 0
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
terminal.write('changes\n')
|
|
90
|
-
terminal.subtle(UPDATE_HINT)
|
|
91
|
-
return 1
|
|
62
|
+
return handleStatusFormat(terminal, changes.length > 0)
|
|
92
63
|
}
|
|
93
64
|
|
|
94
65
|
if (format === 'diff') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
return handleDiffFormat(terminal, changes, eslintVersionsByGroup)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByGroup)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function handleMissingBaseline(
|
|
73
|
+
cwd: string,
|
|
74
|
+
format: CheckFormat,
|
|
75
|
+
defaultInvocation: boolean,
|
|
76
|
+
terminal: TerminalIO,
|
|
77
|
+
snapshotDir: string,
|
|
78
|
+
currentSnapshots: Map<string, BuiltSnapshot>
|
|
79
|
+
): Promise<number> {
|
|
80
|
+
const summary = summarizeSnapshots(currentSnapshots)
|
|
81
|
+
terminal.write(
|
|
82
|
+
`Rules found in this analysis: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).\n`
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const canPromptBaseline = defaultInvocation || format === 'summary'
|
|
86
|
+
if (canPromptBaseline && terminal.isInteractive) {
|
|
87
|
+
const shouldCreateBaseline = await terminal.askYesNo(
|
|
88
|
+
'No baseline yet. Do you want to save this analyzed rule state as your baseline now? [Y/n] ',
|
|
89
|
+
true
|
|
90
|
+
)
|
|
91
|
+
if (shouldCreateBaseline) {
|
|
92
|
+
await writeSnapshots(cwd, snapshotDir, currentSnapshots)
|
|
93
|
+
const createdSummary = summarizeSnapshots(currentSnapshots)
|
|
94
|
+
terminal.write(`Great start: baseline created with ${createdSummary.groups} groups and ${createdSummary.rules} rules.\n`)
|
|
95
|
+
terminal.subtle(UPDATE_HINT)
|
|
98
96
|
return 0
|
|
99
97
|
}
|
|
98
|
+
}
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
terminal.write('You are almost set: no baseline snapshot found yet.\n')
|
|
101
|
+
terminal.write('Run `eslint-config-snapshot --update` to create your first baseline.\n')
|
|
102
|
+
return 1
|
|
103
|
+
}
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
function handleStatusFormat(terminal: TerminalIO, hasChanges: boolean): number {
|
|
106
|
+
if (!hasChanges) {
|
|
107
|
+
terminal.write('clean\n')
|
|
108
|
+
return 0
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
terminal.write('changes\n')
|
|
112
|
+
terminal.subtle(UPDATE_HINT)
|
|
113
|
+
return 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function handleDiffFormat(
|
|
117
|
+
terminal: TerminalIO,
|
|
118
|
+
changes: Array<{ groupId: string; diff: SnapshotDiff }>,
|
|
119
|
+
eslintVersionsByGroup: GroupEslintVersions
|
|
120
|
+
): number {
|
|
121
|
+
if (changes.length === 0) {
|
|
122
|
+
terminal.write('Great news: no snapshot changes detected.\n')
|
|
123
|
+
writeEslintVersionSummary(terminal, eslintVersionsByGroup)
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const change of changes) {
|
|
128
|
+
terminal.write(`${formatDiff(change.groupId, change.diff)}\n`)
|
|
129
|
+
}
|
|
130
|
+
terminal.subtle(UPDATE_HINT)
|
|
131
|
+
return 1
|
|
110
132
|
}
|
|
111
133
|
|
|
112
134
|
function printWhatChanged(
|