@eslint-config-snapshot/cli 0.1.0 → 0.1.4

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.
@@ -1,29 +1,29 @@
1
- import { spawnSync } from 'node:child_process'
1
+ import { spawnSync } from 'node:child_process'
2
2
  import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
-
7
- const fixtureRoot = path.resolve('test/fixtures/repo')
8
- const cliDist = path.resolve('dist/index.js')
9
-
10
- let tmpDir = ''
11
- let repoRoot = ''
12
-
13
- function run(args: string[]): { status: number; stdout: string; stderr: string } {
14
- const proc = spawnSync(process.execPath, [cliDist, ...args], {
15
- cwd: repoRoot,
16
- encoding: 'utf8',
17
- env: { ...process.env }
18
- })
19
-
20
- return {
21
- status: proc.status ?? 1,
22
- stdout: proc.stdout ?? '',
23
- stderr: proc.stderr ?? ''
24
- }
25
- }
26
-
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
+
7
+ const fixtureRoot = path.resolve('test/fixtures/repo')
8
+ const cliDist = path.resolve('dist/index.js')
9
+
10
+ let tmpDir = ''
11
+ let repoRoot = ''
12
+
13
+ function run(args: string[]): { status: number; stdout: string; stderr: string } {
14
+ const proc = spawnSync(process.execPath, [cliDist, ...args], {
15
+ cwd: repoRoot,
16
+ encoding: 'utf8',
17
+ env: { ...process.env }
18
+ })
19
+
20
+ return {
21
+ status: proc.status ?? 1,
22
+ stdout: proc.stdout ?? '',
23
+ stderr: proc.stderr ?? ''
24
+ }
25
+ }
26
+
27
27
  beforeEach(async () => {
28
28
  tmpDir = await mkdtemp(path.join(os.tmpdir(), 'snapshot-cli-terminal-'))
29
29
  repoRoot = path.join(tmpDir, 'repo')
@@ -49,53 +49,53 @@ beforeEach(async () => {
49
49
  JSON.stringify({ name: 'eslint', version: '9.0.0' }, null, 2)
50
50
  )
51
51
  })
52
-
53
- afterEach(async () => {
54
- if (tmpDir) {
55
- await rm(tmpDir, { recursive: true, force: true })
56
- tmpDir = ''
57
- repoRoot = ''
58
- }
59
- })
60
-
61
- describe('cli terminal invocation', () => {
52
+
53
+ afterEach(async () => {
54
+ if (tmpDir) {
55
+ await rm(tmpDir, { recursive: true, force: true })
56
+ tmpDir = ''
57
+ repoRoot = ''
58
+ }
59
+ })
60
+
61
+ describe('cli terminal invocation', () => {
62
62
  it('prints help text and exits 0', () => {
63
- const result = run(['--help'])
64
- expect(result.status).toBe(0)
65
- expect(result.stdout).toContain('Usage: eslint-config-snapshot [options] [command]')
66
- expect(result.stdout).toContain('check [options]')
67
- expect(result.stdout).toContain('update|snapshot')
68
- expect(result.stdout).toContain('print [options]')
69
- expect(result.stdout).toContain('init')
70
- expect(result.stderr).toBe('')
71
- })
72
-
73
- it('returns 1 for unknown command', () => {
74
- const result = run(['unknown'])
75
- expect(result.status).toBe(1)
76
- expect(result.stdout).toBe('')
77
- expect(result.stderr).toContain("error: unknown command 'unknown'")
78
- })
79
-
63
+ const result = run(['--help'])
64
+ expect(result.status).toBe(0)
65
+ expect(result.stdout).toContain('Usage: eslint-config-snapshot [options] [command]')
66
+ expect(result.stdout).toContain('check [options]')
67
+ expect(result.stdout).toContain('update|snapshot')
68
+ expect(result.stdout).toContain('print [options]')
69
+ expect(result.stdout).toContain('init')
70
+ expect(result.stderr).toBe('')
71
+ })
72
+
73
+ it('returns 1 for unknown command', () => {
74
+ const result = run(['unknown'])
75
+ expect(result.status).toBe(1)
76
+ expect(result.stdout).toBe('')
77
+ expect(result.stderr).toContain("error: unknown command 'unknown'")
78
+ })
79
+
80
80
  it('snapshot succeeds and compare returns clean result', () => {
81
81
  const snapshot = run(['snapshot'])
82
82
  expect(snapshot.status).toBe(0)
83
83
  expect(snapshot.stdout).toContain('Baseline updated:')
84
- expect(snapshot.stderr).toBe('')
85
-
84
+ expect(snapshot.stderr).toBe('')
85
+
86
86
  const compare = run(['compare'])
87
87
  expect(compare.status).toBe(0)
88
88
  expect(compare.stdout).toBe('Great news: no snapshot changes detected.\n')
89
89
  expect(compare.stderr).toBe('')
90
90
  })
91
-
92
- it('default command prints clean summary when no drift', () => {
91
+
92
+ it('default command prints clean summary when no drift', () => {
93
93
  expect(run(['snapshot']).status).toBe(0)
94
94
  const result = run([])
95
95
  expect(result.status).toBe(0)
96
96
  expect(result.stdout).toContain('Great news: no snapshot drift detected.')
97
- })
98
-
97
+ })
98
+
99
99
  it('default command reports missing local snapshots', () => {
100
100
  const result = run([])
101
101
  expect(result.status).toBe(1)
@@ -103,15 +103,15 @@ describe('cli terminal invocation', () => {
103
103
  'Current rule state: 1 groups, 3 rules (severity mix: 2 errors, 0 warnings, 1 off).\nYou are almost set: no baseline snapshot found yet.\nRun `eslint-config-snapshot --update` to create your first baseline.\n'
104
104
  )
105
105
  })
106
-
107
- it('compare returns 1 and deterministic diff output when rules change', async () => {
108
- expect(run(['snapshot']).status).toBe(0)
109
-
110
- await writeFile(
111
- path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
112
- "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: 0 } }))\n"
113
- )
114
-
106
+
107
+ it('compare returns 1 and deterministic diff output when rules change', async () => {
108
+ expect(run(['snapshot']).status).toBe(0)
109
+
110
+ await writeFile(
111
+ path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
112
+ "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: 0 } }))\n"
113
+ )
114
+
115
115
  const compare = run(['compare'])
116
116
  expect(compare.status).toBe(1)
117
117
  expect(compare.stdout).toBe(
@@ -119,15 +119,15 @@ describe('cli terminal invocation', () => {
119
119
  )
120
120
  expect(compare.stderr).toBe('')
121
121
  })
122
-
123
- it('compare shows options changed when severity does not change', async () => {
124
- expect(run(['snapshot']).status).toBe(0)
125
-
126
- await writeFile(
127
- path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
128
- "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: [2, 'smart'] } }))\n"
129
- )
130
-
122
+
123
+ it('compare shows options changed when severity does not change', async () => {
124
+ expect(run(['snapshot']).status).toBe(0)
125
+
126
+ await writeFile(
127
+ path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
128
+ "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: [2, 'smart'] } }))\n"
129
+ )
130
+
131
131
  const compare = run(['compare'])
132
132
  expect(compare.status).toBe(1)
133
133
  expect(compare.stdout).toBe(
@@ -135,34 +135,34 @@ describe('cli terminal invocation', () => {
135
135
  )
136
136
  expect(compare.stderr).toBe('')
137
137
  })
138
-
139
- it('compare prints removed rules as nested list', async () => {
140
- expect(run(['snapshot']).status).toBe(0)
141
-
142
- await writeFile(
143
- path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
144
- "console.log(JSON.stringify({ rules: { 'no-console': 1 } }))\n"
145
- )
146
-
147
- const compare = run(['compare'])
148
- expect(compare.status).toBe(1)
149
- expect(compare.stdout).toContain('removed rules:\n - eqeqeq\n')
150
- expect(compare.stdout).not.toContain('options changed:\n - eqeqeq')
151
- })
152
-
153
- it('status returns clean and changes variants', async () => {
154
- expect(run(['snapshot']).status).toBe(0)
155
-
138
+
139
+ it('compare prints removed rules as nested list', async () => {
140
+ expect(run(['snapshot']).status).toBe(0)
141
+
142
+ await writeFile(
143
+ path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
144
+ "console.log(JSON.stringify({ rules: { 'no-console': 1 } }))\n"
145
+ )
146
+
147
+ const compare = run(['compare'])
148
+ expect(compare.status).toBe(1)
149
+ expect(compare.stdout).toContain('removed rules:\n - eqeqeq\n')
150
+ expect(compare.stdout).not.toContain('options changed:\n - eqeqeq')
151
+ })
152
+
153
+ it('status returns clean and changes variants', async () => {
154
+ expect(run(['snapshot']).status).toBe(0)
155
+
156
156
  const clean = run(['status'])
157
157
  expect(clean.status).toBe(0)
158
158
  expect(clean.stdout).toBe('clean\n')
159
- expect(clean.stderr).toBe('')
160
-
161
- await writeFile(
162
- path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
163
- "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: 0 } }))\n"
164
- )
165
-
159
+ expect(clean.stderr).toBe('')
160
+
161
+ await writeFile(
162
+ path.join(repoRoot, 'packages/ws-a/node_modules/eslint/bin/eslint.js'),
163
+ "console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: 0 } }))\n"
164
+ )
165
+
166
166
  const changed = run(['status'])
167
167
  expect(changed.status).toBe(1)
168
168
  expect(changed.stdout).toBe(
@@ -170,56 +170,56 @@ describe('cli terminal invocation', () => {
170
170
  )
171
171
  expect(changed.stderr).toBe('')
172
172
  })
173
-
174
- it('print returns deterministic json output', () => {
175
- const result = run(['print'])
176
- expect(result.status).toBe(0)
177
- expect(result.stdout).toBe(
178
- `[
179
- {
180
- "groupId": "default",
181
- "rules": {
182
- "eqeqeq": [
183
- "error",
184
- "always"
185
- ],
186
- "no-console": [
187
- "error"
188
- ],
189
- "no-debugger": [
190
- "off"
191
- ]
192
- }
193
- }
194
- ]
195
- `
196
- )
197
- expect(result.stderr).toBe('')
198
- })
199
-
200
- it('print --short returns compact human-readable output', () => {
201
- const result = run(['print', '--short'])
202
- expect(result.status).toBe(0)
203
- expect(result.stdout).toBe(
204
- `group: default
205
- workspaces (2): packages/ws-a, packages/ws-b
206
- rules (3): error 2, warn 0, off 1
207
- eqeqeq: error "always"
208
- no-console: error
209
- no-debugger: off
210
- `
211
- )
212
- expect(result.stderr).toBe('')
213
- })
214
-
173
+
174
+ it('print returns deterministic json output', () => {
175
+ const result = run(['print'])
176
+ expect(result.status).toBe(0)
177
+ expect(result.stdout).toBe(
178
+ `[
179
+ {
180
+ "groupId": "default",
181
+ "rules": {
182
+ "eqeqeq": [
183
+ "error",
184
+ "always"
185
+ ],
186
+ "no-console": [
187
+ "error"
188
+ ],
189
+ "no-debugger": [
190
+ "off"
191
+ ]
192
+ }
193
+ }
194
+ ]
195
+ `
196
+ )
197
+ expect(result.stderr).toBe('')
198
+ })
199
+
200
+ it('print --short returns compact human-readable output', () => {
201
+ const result = run(['print', '--short'])
202
+ expect(result.status).toBe(0)
203
+ expect(result.stdout).toBe(
204
+ `group: default
205
+ workspaces (2): packages/ws-a, packages/ws-b
206
+ rules (3): error 2, warn 0, off 1
207
+ eqeqeq: error "always"
208
+ no-console: error
209
+ no-debugger: off
210
+ `
211
+ )
212
+ expect(result.stderr).toBe('')
213
+ })
214
+
215
215
  it('init handles success and existing-file error paths', async () => {
216
- const initRoot = path.join(tmpDir, 'init-case')
217
- await rm(initRoot, { recursive: true, force: true })
218
- await cp(fixtureRoot, initRoot, { recursive: true })
219
- repoRoot = initRoot
220
-
221
- await rm(path.join(repoRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
222
-
216
+ const initRoot = path.join(tmpDir, 'init-case')
217
+ await rm(initRoot, { recursive: true, force: true })
218
+ await cp(fixtureRoot, initRoot, { recursive: true })
219
+ repoRoot = initRoot
220
+
221
+ await rm(path.join(repoRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
222
+
223
223
  const created = run(['init', '--yes', '--target', 'file', '--preset', 'minimal'])
224
224
  expect(created.status).toBe(0)
225
225
  expect(created.stdout).toBe('Created eslint-config-snapshot.config.mjs\n')
@@ -282,52 +282,52 @@ no-debugger: off
282
282
  expect(forced.stdout).toBe('Created eslint-config-snapshot.config.mjs\n')
283
283
  expect(forced.stderr).toBe('')
284
284
  })
285
-
286
- it('surfaces runtime errors with exit code 1', async () => {
287
- await writeFile(
288
- path.join(repoRoot, 'eslint-config-snapshot.config.mjs'),
289
- `export default {
290
- workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a'] },
291
- grouping: { mode: 'match', allowEmptyGroups: false, groups: [{ name: 'never', match: ['ops/**'] }] },
292
- sampling: { maxFilesPerWorkspace: 8, includeGlobs: ['**/*.ts'], excludeGlobs: ['**/node_modules/**'], hintGlobs: [] }
293
- }
294
- `
295
- )
296
-
297
- const result = run(['snapshot'])
298
- expect(result.status).toBe(1)
299
- expect(result.stdout).toBe('')
300
- expect(result.stderr).toBe('Unmatched workspaces: packages/ws-a\n')
301
- })
302
-
303
- it('loads config from package.json through cosmiconfig', async () => {
304
- await rm(path.join(repoRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
305
- await writeFile(
306
- path.join(repoRoot, 'package.json'),
307
- JSON.stringify(
308
- {
309
- name: 'fixture-repo',
310
- private: true,
311
- workspaces: ['packages/*'],
312
- 'eslint-config-snapshot': {
313
- workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a'] },
314
- grouping: { mode: 'match', groups: [{ name: 'default', match: ['**/*'] }] },
315
- sampling: { maxFilesPerWorkspace: 8, includeGlobs: ['**/*.ts'], excludeGlobs: ['**/node_modules/**'], hintGlobs: [] }
316
- }
317
- },
318
- null,
319
- 2
320
- )
321
- )
322
-
323
- const snapshot = run(['snapshot'])
324
- expect(snapshot.status).toBe(0)
325
-
326
- const raw = await readFile(path.join(repoRoot, '.eslint-config-snapshot/default.json'), 'utf8')
327
- const parsed = JSON.parse(raw) as { workspaces: string[] }
328
- expect(parsed.workspaces).toEqual(['packages/ws-a'])
329
- })
330
-
285
+
286
+ it('surfaces runtime errors with exit code 1', async () => {
287
+ await writeFile(
288
+ path.join(repoRoot, 'eslint-config-snapshot.config.mjs'),
289
+ `export default {
290
+ workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a'] },
291
+ grouping: { mode: 'match', allowEmptyGroups: false, groups: [{ name: 'never', match: ['ops/**'] }] },
292
+ sampling: { maxFilesPerWorkspace: 8, includeGlobs: ['**/*.ts'], excludeGlobs: ['**/node_modules/**'], hintGlobs: [] }
293
+ }
294
+ `
295
+ )
296
+
297
+ const result = run(['snapshot'])
298
+ expect(result.status).toBe(1)
299
+ expect(result.stdout).toBe('')
300
+ expect(result.stderr).toBe('Unmatched workspaces: packages/ws-a\n')
301
+ })
302
+
303
+ it('loads config from package.json through cosmiconfig', async () => {
304
+ await rm(path.join(repoRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
305
+ await writeFile(
306
+ path.join(repoRoot, 'package.json'),
307
+ JSON.stringify(
308
+ {
309
+ name: 'fixture-repo',
310
+ private: true,
311
+ workspaces: ['packages/*'],
312
+ 'eslint-config-snapshot': {
313
+ workspaceInput: { mode: 'manual', workspaces: ['packages/ws-a'] },
314
+ grouping: { mode: 'match', groups: [{ name: 'default', match: ['**/*'] }] },
315
+ sampling: { maxFilesPerWorkspace: 8, includeGlobs: ['**/*.ts'], excludeGlobs: ['**/node_modules/**'], hintGlobs: [] }
316
+ }
317
+ },
318
+ null,
319
+ 2
320
+ )
321
+ )
322
+
323
+ const snapshot = run(['snapshot'])
324
+ expect(snapshot.status).toBe(0)
325
+
326
+ const raw = await readFile(path.join(repoRoot, '.eslint-config-snapshot/default.json'), 'utf8')
327
+ const parsed = JSON.parse(raw) as { workspaces: string[] }
328
+ expect(parsed.workspaces).toEqual(['packages/ws-a'])
329
+ })
330
+
331
331
  it('updates snapshots with --update without command', () => {
332
332
  const result = run(['--update'])
333
333
  expect(result.status).toBe(0)
@@ -346,7 +346,7 @@ no-debugger: off
346
346
  expect(result.stdout).toContain('--yes --force --target file --preset full')
347
347
  expect(result.stderr).toBe('')
348
348
  })
349
-
349
+
350
350
  it('supports canonical check and update commands', () => {
351
351
  const update = run(['update'])
352
352
  expect(update.status).toBe(0)
@@ -1,7 +1,7 @@
1
- {
2
- "name": "npm-isolated-fixture",
3
- "private": true,
4
- "workspaces": [
5
- "packages/*"
6
- ]
7
- }
1
+ {
2
+ "name": "npm-isolated-fixture",
3
+ "private": true,
4
+ "workspaces": [
5
+ "packages/*"
6
+ ]
7
+ }
@@ -4,4 +4,4 @@ module.exports = {
4
4
  'no-console': 'warn',
5
5
  eqeqeq: ['error', 'always']
6
6
  }
7
- }
7
+ }
@@ -1 +1 @@
1
- console.log('a')
1
+ console.log('a')
@@ -4,4 +4,4 @@ module.exports = {
4
4
  'no-console': 'error',
5
5
  'no-debugger': 'off'
6
6
  }
7
- }
7
+ }
@@ -1 +1 @@
1
- console.log('b')
1
+ console.log('b')
@@ -13,4 +13,4 @@ export default {
13
13
  excludeGlobs: ['**/node_modules/**'],
14
14
  hintGlobs: []
15
15
  }
16
- }
16
+ }
@@ -4,4 +4,4 @@
4
4
  "workspaces": [
5
5
  "packages/*"
6
6
  ]
7
- }
7
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "ws-a",
3
3
  "private": true
4
- }
4
+ }
@@ -1 +1 @@
1
- export const a = 1
1
+ export const a = 1
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "ws-b",
3
3
  "private": true
4
- }
4
+ }
@@ -1 +1 @@
1
- export const b = 1
1
+ export const b = 1
package/tsconfig.json CHANGED
@@ -1,12 +1,12 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
-
5
- },
6
- "include": [
7
- "src/**/*.ts"
8
- ],
9
- "references": [
10
-
11
- ]
12
- }
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+
5
+ },
6
+ "include": [
7
+ "src/**/*.ts"
8
+ ],
9
+ "references": [
10
+
11
+ ]
12
+ }