@eslint-config-snapshot/cli 0.1.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.
Files changed (28) hide show
  1. package/dist/index.cjs +720 -0
  2. package/dist/index.js +697 -0
  3. package/package.json +26 -0
  4. package/project.json +35 -0
  5. package/src/index.ts +861 -0
  6. package/test/cli.integration.test.ts +247 -0
  7. package/test/cli.npm-isolated.integration.test.ts +109 -0
  8. package/test/cli.pnpm-isolated.integration.test.ts +140 -0
  9. package/test/cli.terminal.integration.test.ts +370 -0
  10. package/test/fixtures/npm-isolated-template/eslint-config-snapshot.config.mjs +16 -0
  11. package/test/fixtures/npm-isolated-template/package.json +7 -0
  12. package/test/fixtures/npm-isolated-template/packages/ws-a/.eslintrc.cjs +7 -0
  13. package/test/fixtures/npm-isolated-template/packages/ws-a/package.json +7 -0
  14. package/test/fixtures/npm-isolated-template/packages/ws-a/src/index.ts +1 -0
  15. package/test/fixtures/npm-isolated-template/packages/ws-b/.eslintrc.cjs +7 -0
  16. package/test/fixtures/npm-isolated-template/packages/ws-b/package.json +7 -0
  17. package/test/fixtures/npm-isolated-template/packages/ws-b/src/index.ts +1 -0
  18. package/test/fixtures/repo/eslint-config-snapshot.config.mjs +16 -0
  19. package/test/fixtures/repo/package.json +7 -0
  20. package/test/fixtures/repo/packages/ws-a/node_modules/eslint/bin/eslint.js +1 -0
  21. package/test/fixtures/repo/packages/ws-a/node_modules/eslint/package.json +4 -0
  22. package/test/fixtures/repo/packages/ws-a/package.json +4 -0
  23. package/test/fixtures/repo/packages/ws-a/src/index.ts +1 -0
  24. package/test/fixtures/repo/packages/ws-b/node_modules/eslint/bin/eslint.js +1 -0
  25. package/test/fixtures/repo/packages/ws-b/node_modules/eslint/package.json +4 -0
  26. package/test/fixtures/repo/packages/ws-b/package.json +4 -0
  27. package/test/fixtures/repo/packages/ws-b/src/index.ts +1 -0
  28. package/tsconfig.json +12 -0
@@ -0,0 +1,247 @@
1
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ import { parseInitPresetChoice, parseInitTargetChoice, runCli } from '../src/index.js'
7
+
8
+ const fixtureRoot = path.resolve('test/fixtures/repo')
9
+
10
+ afterAll(async () => {
11
+ await rm(path.join(fixtureRoot, '.eslint-config-snapshot'), { recursive: true, force: true })
12
+ })
13
+
14
+ beforeEach(async () => {
15
+ await rm(path.join(fixtureRoot, '.eslint-config-snapshot'), { recursive: true, force: true })
16
+ await mkdir(path.join(fixtureRoot, 'packages/ws-a/node_modules/eslint/bin'), { recursive: true })
17
+ await mkdir(path.join(fixtureRoot, 'packages/ws-b/node_modules/eslint/bin'), { recursive: true })
18
+
19
+ await writeFile(
20
+ path.join(fixtureRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
21
+ "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: [2, 'always'] } }))\n"
22
+ )
23
+ await writeFile(
24
+ path.join(fixtureRoot, 'packages/ws-a/node_modules/eslint/package.json'),
25
+ JSON.stringify({ name: 'eslint', version: '9.0.0' }, null, 2)
26
+ )
27
+
28
+ await writeFile(
29
+ path.join(fixtureRoot, 'packages/ws-b/node_modules/eslint/bin/eslint.js'),
30
+ "console.log(JSON.stringify({ rules: { 'no-console': 2, 'no-debugger': 0 } }))\n"
31
+ )
32
+ await writeFile(
33
+ path.join(fixtureRoot, 'packages/ws-b/node_modules/eslint/package.json'),
34
+ JSON.stringify({ name: 'eslint', version: '9.0.0' }, null, 2)
35
+ )
36
+ })
37
+
38
+ describe('cli integration', () => {
39
+ it('parses init interactive target choices from numeric and aliases', () => {
40
+ expect(parseInitTargetChoice('')).toBe('package-json')
41
+ expect(parseInitTargetChoice('1')).toBe('package-json')
42
+ expect(parseInitTargetChoice('package')).toBe('package-json')
43
+ expect(parseInitTargetChoice('pkg')).toBe('package-json')
44
+ expect(parseInitTargetChoice('2')).toBe('file')
45
+ expect(parseInitTargetChoice('file')).toBe('file')
46
+ expect(parseInitTargetChoice('invalid')).toBeUndefined()
47
+ })
48
+
49
+ it('parses init interactive preset choices from numeric and aliases', () => {
50
+ expect(parseInitPresetChoice('')).toBe('minimal')
51
+ expect(parseInitPresetChoice('1')).toBe('minimal')
52
+ expect(parseInitPresetChoice('min')).toBe('minimal')
53
+ expect(parseInitPresetChoice('2')).toBe('full')
54
+ expect(parseInitPresetChoice('full')).toBe('full')
55
+ expect(parseInitPresetChoice('invalid')).toBeUndefined()
56
+ })
57
+
58
+ it('snapshot writes deterministic snapshot files', async () => {
59
+ const code = await runCli('snapshot', fixtureRoot)
60
+ expect(code).toBe(0)
61
+
62
+ const snapshotRaw = await readFile(path.join(fixtureRoot, '.eslint-config-snapshot/default.json'), 'utf8')
63
+ const snapshot = JSON.parse(snapshotRaw)
64
+
65
+ expect(snapshot).toEqual({
66
+ formatVersion: 1,
67
+ groupId: 'default',
68
+ workspaces: ['packages/ws-a', 'packages/ws-b'],
69
+ rules: {
70
+ eqeqeq: ['error', 'always'],
71
+ 'no-console': ['error'],
72
+ 'no-debugger': ['off']
73
+ }
74
+ })
75
+
76
+ expect(snapshotRaw.endsWith('\n')).toBe(true)
77
+ expect(snapshotRaw.includes('src/index.ts')).toBe(false)
78
+ })
79
+
80
+ it('compare returns non-zero when snapshots changed', async () => {
81
+ expect(await runCli('snapshot', fixtureRoot)).toBe(0)
82
+
83
+ await writeFile(
84
+ path.join(fixtureRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
85
+ "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: 0 } }))\n"
86
+ )
87
+
88
+ const code = await runCli('compare', fixtureRoot)
89
+ expect(code).toBe(1)
90
+ })
91
+
92
+ it('status is minimal and exits 0 when clean', async () => {
93
+ await runCli('snapshot', fixtureRoot)
94
+
95
+ const writeSpy = vi.spyOn(process.stdout, 'write')
96
+ const code = await runCli('status', fixtureRoot)
97
+ expect(code).toBe(0)
98
+ expect(writeSpy).toHaveBeenCalledWith('clean\n')
99
+ })
100
+
101
+ it('print emits aggregated rules and exits 0', async () => {
102
+ const writeSpy = vi.spyOn(process.stdout, 'write')
103
+ const code = await runCli('print', fixtureRoot)
104
+ expect(code).toBe(0)
105
+ expect(writeSpy).toHaveBeenCalled()
106
+ })
107
+
108
+ it('print --short emits compact human-readable output', async () => {
109
+ const writeSpy = vi.spyOn(process.stdout, 'write')
110
+ const code = await runCli('print', fixtureRoot, ['--short'])
111
+ expect(code).toBe(0)
112
+ expect(writeSpy).toHaveBeenCalledWith(
113
+ `group: default
114
+ workspaces (2): packages/ws-a, packages/ws-b
115
+ rules (3): error 2, warn 0, off 1
116
+ eqeqeq: error "always"
117
+ no-console: error
118
+ no-debugger: off
119
+ `
120
+ )
121
+ })
122
+
123
+ it('init creates scaffold config file when target=file', async () => {
124
+ const tmp = await mkdtemp(path.join(os.tmpdir(), 'snapshot-init-'))
125
+ const code = await runCli('init', tmp, ['--yes', '--target', 'file', '--preset', 'full'])
126
+ expect(code).toBe(0)
127
+
128
+ const content = await readFile(path.join(tmp, 'eslint-config-snapshot.config.mjs'), 'utf8')
129
+ expect(content).toContain("workspaceInput: { mode: 'discover' }")
130
+
131
+ await rm(tmp, { recursive: true, force: true })
132
+ })
133
+
134
+ it('init writes minimal config to package.json when target=package-json', async () => {
135
+ const tmp = await mkdtemp(path.join(os.tmpdir(), 'snapshot-init-pkg-'))
136
+ await writeFile(path.join(tmp, 'package.json'), JSON.stringify({ name: 'fixture', private: true }, null, 2))
137
+
138
+ const code = await runCli('init', tmp, ['--yes', '--target', 'package-json', '--preset', 'minimal'])
139
+ expect(code).toBe(0)
140
+
141
+ const packageJsonRaw = await readFile(path.join(tmp, 'package.json'), 'utf8')
142
+ const parsed = JSON.parse(packageJsonRaw) as {
143
+ 'eslint-config-snapshot'?: Record<string, unknown>
144
+ }
145
+ expect(parsed['eslint-config-snapshot']).toEqual({})
146
+
147
+ await rm(tmp, { recursive: true, force: true })
148
+ })
149
+
150
+ it('help prints usage and exits 0', async () => {
151
+ const writeSpy = vi.spyOn(process.stdout, 'write')
152
+ const code = await runCli('--help', fixtureRoot)
153
+ expect(code).toBe(0)
154
+ expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:'))
155
+ })
156
+
157
+ it('runs update mode without command', async () => {
158
+ const writeSpy = vi.spyOn(process.stdout, 'write')
159
+ const code = await runCli(undefined, fixtureRoot, ['--update'])
160
+ expect(code).toBe(0)
161
+ expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('Baseline updated:'))
162
+ })
163
+
164
+ it('supports canonical check and update commands', async () => {
165
+ expect(await runCli('update', fixtureRoot)).toBe(0)
166
+ expect(await runCli('check', fixtureRoot)).toBe(0)
167
+ })
168
+
169
+ it('supports ordered multi-group matching with first match wins', async () => {
170
+ const tmp = await mkdtemp(path.join(os.tmpdir(), 'snapshot-cli-grouped-'))
171
+ await cp(fixtureRoot, tmp, { recursive: true })
172
+
173
+ await writeFile(
174
+ path.join(tmp, 'eslint-config-snapshot.config.mjs'),
175
+ `export default {
176
+ workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a', 'packages/ws-b'] },
177
+ grouping: {
178
+ mode: 'match',
179
+ groups: [
180
+ { name: 'modern', match: ['packages/**', '!packages/ws-b'] },
181
+ { name: 'legacy', match: ['packages/ws-b'] }
182
+ ]
183
+ },
184
+ sampling: {
185
+ maxFilesPerWorkspace: 8,
186
+ includeGlobs: ['**/*.ts'],
187
+ excludeGlobs: ['**/node_modules/**'],
188
+ hintGlobs: []
189
+ }
190
+ }
191
+ `
192
+ )
193
+
194
+ const code = await runCli('snapshot', tmp)
195
+ expect(code).toBe(0)
196
+
197
+ const modern = JSON.parse(await readFile(path.join(tmp, '.eslint-config-snapshot/modern.json'), 'utf8'))
198
+ const legacy = JSON.parse(await readFile(path.join(tmp, '.eslint-config-snapshot/legacy.json'), 'utf8'))
199
+
200
+ expect(modern.workspaces).toEqual(['packages/ws-a'])
201
+ expect(modern.rules).toEqual({
202
+ eqeqeq: ['error', 'always'],
203
+ 'no-console': ['warn']
204
+ })
205
+
206
+ expect(legacy.workspaces).toEqual(['packages/ws-b'])
207
+ expect(legacy.rules).toEqual({
208
+ 'no-console': ['error'],
209
+ 'no-debugger': ['off']
210
+ })
211
+
212
+ await rm(tmp, { recursive: true, force: true })
213
+ })
214
+
215
+ it('supports standalone mode with workspace path group ids', async () => {
216
+ const tmp = await mkdtemp(path.join(os.tmpdir(), 'snapshot-cli-standalone-'))
217
+ await cp(fixtureRoot, tmp, { recursive: true })
218
+
219
+ await writeFile(
220
+ path.join(tmp, 'eslint-config-snapshot.config.mjs'),
221
+ `export default {
222
+ workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a', 'packages/ws-b'] },
223
+ grouping: { mode: 'standalone' },
224
+ sampling: {
225
+ maxFilesPerWorkspace: 8,
226
+ includeGlobs: ['**/*.ts'],
227
+ excludeGlobs: ['**/node_modules/**'],
228
+ hintGlobs: []
229
+ }
230
+ }
231
+ `
232
+ )
233
+
234
+ const snapshotCode = await runCli('snapshot', tmp)
235
+ expect(snapshotCode).toBe(0)
236
+
237
+ const wsAPath = path.join(tmp, '.eslint-config-snapshot/packages/ws-a.json')
238
+ const wsBPath = path.join(tmp, '.eslint-config-snapshot/packages/ws-b.json')
239
+ expect(JSON.parse(await readFile(wsAPath, 'utf8')).groupId).toBe('packages/ws-a')
240
+ expect(JSON.parse(await readFile(wsBPath, 'utf8')).groupId).toBe('packages/ws-b')
241
+
242
+ const compareCode = await runCli('compare', tmp)
243
+ expect(compareCode).toBe(0)
244
+
245
+ await rm(tmp, { recursive: true, force: true })
246
+ })
247
+ })
@@ -0,0 +1,109 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { access, cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { setTimeout as delay } from 'node:timers/promises'
6
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
7
+
8
+ const templateRoot = path.resolve('test/fixtures/npm-isolated-template')
9
+ const cliDist = path.resolve('dist/index.js')
10
+
11
+ let fixtureRoot = ''
12
+
13
+ function npmCmd(): string {
14
+ const base = path.dirname(process.execPath)
15
+ return process.platform === 'win32' ? path.join(base, 'npm.cmd') : path.join(base, 'npm')
16
+ }
17
+
18
+ function run(command: string, args: string[], cwd: string): { status: number; stdout: string; stderr: string } {
19
+ const proc = spawnSync(command, args, {
20
+ cwd,
21
+ encoding: 'utf8',
22
+ env: { ...process.env },
23
+ shell: process.platform === 'win32'
24
+ })
25
+
26
+ return {
27
+ status: proc.status ?? 1,
28
+ stdout: proc.stdout ?? '',
29
+ stderr: `${proc.stderr ?? ''}${proc.error ? `\n${String(proc.error)}` : ''}`
30
+ }
31
+ }
32
+
33
+ async function runWithRetry(
34
+ command: string,
35
+ args: string[],
36
+ cwd: string,
37
+ retries = 2
38
+ ): Promise<{ status: number; stdout: string; stderr: string }> {
39
+ let attempt = 0
40
+ let lastResult = run(command, args, cwd)
41
+ while (lastResult.status !== 0 && attempt < retries) {
42
+ attempt += 1
43
+ await delay(1000 * attempt)
44
+ lastResult = run(command, args, cwd)
45
+ }
46
+ return lastResult
47
+ }
48
+
49
+ describe('cli npm-isolated integration', () => {
50
+ beforeAll(async () => {
51
+ const tmpBase = await mkdtemp(path.join(os.tmpdir(), 'snapshot-npm-it-'))
52
+ fixtureRoot = path.join(tmpBase, 'repo')
53
+ await cp(templateRoot, fixtureRoot, { recursive: true })
54
+
55
+ const wsA = path.join(fixtureRoot, 'packages/ws-a')
56
+ const wsB = path.join(fixtureRoot, 'packages/ws-b')
57
+
58
+ const installA = await runWithRetry(npmCmd(), ['install', '--no-audit', '--no-fund', '--workspaces=false'], wsA)
59
+ expect(installA.status, `${installA.stdout}\n${installA.stderr}`).toBe(0)
60
+ await access(path.join(wsA, 'node_modules/eslint/package.json'))
61
+
62
+ const installB = await runWithRetry(npmCmd(), ['install', '--no-audit', '--no-fund', '--workspaces=false'], wsB)
63
+ expect(installB.status, `${installB.stdout}\n${installB.stderr}`).toBe(0)
64
+ await access(path.join(wsB, 'node_modules/eslint/package.json'))
65
+ }, 180000)
66
+
67
+ afterAll(async () => {
68
+ if (fixtureRoot) {
69
+ await rm(path.dirname(fixtureRoot), { recursive: true, force: true })
70
+ }
71
+ })
72
+
73
+ it('runs commands in isolated subprocesses with workspace-local npm eslint', async () => {
74
+ const snapshot = run(process.execPath, [cliDist, 'snapshot'], fixtureRoot)
75
+ expect(snapshot.status, snapshot.stderr).toBe(0)
76
+
77
+ const snapshotRaw = await readFile(path.join(fixtureRoot, '.eslint-config-snapshot/default.json'), 'utf8')
78
+ const parsed = JSON.parse(snapshotRaw)
79
+ expect(parsed).toEqual({
80
+ formatVersion: 1,
81
+ groupId: 'default',
82
+ workspaces: ['packages/ws-a', 'packages/ws-b'],
83
+ rules: {
84
+ eqeqeq: ['error', 'always'],
85
+ 'no-console': ['error'],
86
+ 'no-debugger': ['off']
87
+ }
88
+ })
89
+
90
+ const compareClean = run(process.execPath, [cliDist, 'compare'], fixtureRoot)
91
+ expect(compareClean.status, compareClean.stdout + compareClean.stderr).toBe(0)
92
+
93
+ const statusClean = run(process.execPath, [cliDist, 'status'], fixtureRoot)
94
+ expect(statusClean.status).toBe(0)
95
+ expect(statusClean.stdout).toContain('clean')
96
+
97
+ const printOut = run(process.execPath, [cliDist, 'print'], fixtureRoot)
98
+ expect(printOut.status, printOut.stderr).toBe(0)
99
+ expect(printOut.stdout).toContain('"groupId": "default"')
100
+
101
+ await writeFile(
102
+ path.join(fixtureRoot, 'packages/ws-a/.eslintrc.cjs'),
103
+ "module.exports = { root: true, rules: { 'no-console': 'warn', eqeqeq: 'off' } }\n"
104
+ )
105
+
106
+ const compareChanged = run(process.execPath, [cliDist, 'compare'], fixtureRoot)
107
+ expect(compareChanged.status).toBe(1)
108
+ }, 180000)
109
+ })
@@ -0,0 +1,140 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { access, cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { setTimeout as delay } from 'node:timers/promises'
6
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
7
+
8
+ const templateRoot = path.resolve('test/fixtures/npm-isolated-template')
9
+ const cliDist = path.resolve('dist/index.js')
10
+
11
+ let fixtureRoot = ''
12
+
13
+ type RunResult = {
14
+ status: number
15
+ stdout: string
16
+ stderr: string
17
+ errorCode?: string
18
+ }
19
+
20
+ type ExecCandidate = {
21
+ command: string
22
+ argsPrefix: string[]
23
+ }
24
+
25
+ function getPnpmCandidates(): ExecCandidate[] {
26
+ const candidates: ExecCandidate[] = []
27
+ const execPath = process.env.npm_execpath
28
+ if (execPath && execPath.toLowerCase().includes('pnpm')) {
29
+ candidates.push({ command: process.execPath, argsPrefix: [execPath] })
30
+ }
31
+
32
+ candidates.push({ command: 'pnpm', argsPrefix: [] })
33
+ if (process.platform === 'win32') {
34
+ candidates.push({ command: 'pnpm.cmd', argsPrefix: [] })
35
+ }
36
+
37
+ return candidates
38
+ }
39
+
40
+ function run(command: string, args: string[], cwd: string): RunResult {
41
+ const proc = spawnSync(command, args, {
42
+ cwd,
43
+ encoding: 'utf8',
44
+ env: { ...process.env },
45
+ shell: process.platform === 'win32'
46
+ })
47
+
48
+ return {
49
+ status: proc.status ?? 1,
50
+ stdout: proc.stdout ?? '',
51
+ stderr: `${proc.stderr ?? ''}${proc.error ? `\n${String(proc.error)}` : ''}`,
52
+ errorCode: proc.error?.code
53
+ }
54
+ }
55
+
56
+ async function runWithRetry(
57
+ command: string,
58
+ args: string[],
59
+ cwd: string,
60
+ retries = 2
61
+ ): Promise<RunResult> {
62
+ let attempt = 0
63
+ let lastResult = run(command, args, cwd)
64
+ while (lastResult.status !== 0 && attempt < retries) {
65
+ attempt += 1
66
+ await delay(1000 * attempt)
67
+ lastResult = run(command, args, cwd)
68
+ }
69
+ return lastResult
70
+ }
71
+
72
+ async function runPnpmWithRetry(args: string[], cwd: string, retries = 2): Promise<RunResult> {
73
+ const candidates = getPnpmCandidates()
74
+ let lastResult: RunResult = { status: 1, stdout: '', stderr: 'pnpm command not found' }
75
+
76
+ for (const candidate of candidates) {
77
+ const attempt = await runWithRetry(candidate.command, [...candidate.argsPrefix, ...args], cwd, retries)
78
+ if (attempt.status === 0) {
79
+ return attempt
80
+ }
81
+
82
+ lastResult = attempt
83
+ if (attempt.errorCode !== 'ENOENT' && !attempt.stderr.includes('ENOENT')) {
84
+ return attempt
85
+ }
86
+ }
87
+
88
+ return lastResult
89
+ }
90
+
91
+ describe('cli pnpm-isolated integration', () => {
92
+ beforeAll(async () => {
93
+ const tmpBase = await mkdtemp(path.join(os.tmpdir(), 'snapshot-pnpm-it-'))
94
+ fixtureRoot = path.join(tmpBase, 'repo')
95
+ await cp(templateRoot, fixtureRoot, { recursive: true })
96
+
97
+ const wsA = path.join(fixtureRoot, 'packages/ws-a')
98
+ const wsB = path.join(fixtureRoot, 'packages/ws-b')
99
+
100
+ const installA = await runPnpmWithRetry(['install', '--ignore-workspace', '--no-frozen-lockfile'], wsA)
101
+ expect(installA.status, `${installA.stdout}\n${installA.stderr}`).toBe(0)
102
+ await access(path.join(wsA, 'node_modules/eslint/package.json'))
103
+
104
+ const installB = await runPnpmWithRetry(['install', '--ignore-workspace', '--no-frozen-lockfile'], wsB)
105
+ expect(installB.status, `${installB.stdout}\n${installB.stderr}`).toBe(0)
106
+ await access(path.join(wsB, 'node_modules/eslint/package.json'))
107
+ }, 180000)
108
+
109
+ afterAll(async () => {
110
+ if (fixtureRoot) {
111
+ await rm(path.dirname(fixtureRoot), { recursive: true, force: true })
112
+ }
113
+ })
114
+
115
+ it('runs commands with workspace-local eslint installed by pnpm in isolated mode', async () => {
116
+ const snapshot = run(process.execPath, [cliDist, 'snapshot'], fixtureRoot)
117
+ expect(snapshot.status, snapshot.stderr).toBe(0)
118
+
119
+ const snapshotRaw = await readFile(path.join(fixtureRoot, '.eslint-config-snapshot/default.json'), 'utf8')
120
+ const parsed = JSON.parse(snapshotRaw)
121
+ expect(parsed).toEqual({
122
+ formatVersion: 1,
123
+ groupId: 'default',
124
+ workspaces: ['packages/ws-a', 'packages/ws-b'],
125
+ rules: {
126
+ eqeqeq: ['error', 'always'],
127
+ 'no-console': ['error'],
128
+ 'no-debugger': ['off']
129
+ }
130
+ })
131
+
132
+ await writeFile(
133
+ path.join(fixtureRoot, 'packages/ws-a/.eslintrc.cjs'),
134
+ "module.exports = { root: true, rules: { 'no-console': 'warn', eqeqeq: 'off' } }\n"
135
+ )
136
+
137
+ const compareChanged = run(process.execPath, [cliDist, 'compare'], fixtureRoot)
138
+ expect(compareChanged.status).toBe(1)
139
+ }, 180000)
140
+ })