@pyreon/cli 0.16.0 → 0.18.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.
@@ -2,11 +2,12 @@ import * as fs from 'node:fs'
2
2
  import * as os from 'node:os'
3
3
  import * as path from 'node:path'
4
4
 
5
+ import { describe, expect, it, vi } from 'vitest'
6
+
5
7
  import { type DoctorOptions, doctor } from '../doctor'
6
8
 
7
9
  function makeTmpDir(): string {
8
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pyreon-doctor-'))
9
- return dir
10
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'pyreon-doctor-'))
10
11
  }
11
12
 
12
13
  function writeFile(dir: string, relPath: string, content: string): void {
@@ -15,467 +16,114 @@ function writeFile(dir: string, relPath: string, content: string): void {
15
16
  fs.writeFileSync(full, content, 'utf-8')
16
17
  }
17
18
 
18
- function readFile(dir: string, relPath: string): string {
19
- return fs.readFileSync(path.join(dir, relPath), 'utf-8')
20
- }
21
-
22
- function defaultOptions(cwd: string): DoctorOptions {
19
+ function defaults(cwd: string): DoctorOptions {
23
20
  return { fix: false, json: false, ci: false, cwd }
24
21
  }
25
22
 
26
- const REACT_TSX = `import { useState, useEffect } from "react"
27
-
28
- export function Counter() {
29
- const [count, setCount] = useState(0)
30
- useEffect(() => {
31
- console.log(count)
32
- }, [count])
33
- return <div className="counter">{count}</div>
34
- }
35
- `
36
-
37
- const CLEAN_TSX = `import { signal } from "@pyreon/reactivity"
38
-
39
- export function Counter() {
40
- const count = signal(0)
41
- return <div class="counter">{count()}</div>
42
- }
43
- `
44
-
45
- describe('doctor', () => {
46
- let tmpDir: string
47
- let logSpy: ReturnType<typeof vi.spyOn>
48
-
49
- beforeEach(() => {
50
- tmpDir = makeTmpDir()
51
- logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
52
- })
53
-
54
- afterEach(() => {
55
- logSpy.mockRestore()
56
- fs.rmSync(tmpDir, { recursive: true, force: true })
57
- })
58
-
59
- // ─── detect-only mode ──────────────────────────────────────────────────
60
-
61
- it('detects React patterns in files (no --fix)', async () => {
62
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
63
-
64
- const errorCount = await doctor(defaultOptions(tmpDir))
65
-
66
- expect(errorCount).toBeGreaterThan(0)
67
- })
68
-
69
- it('reports correct file paths and diagnostic counts', async () => {
70
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
71
-
72
- const opts: DoctorOptions = { fix: false, json: true, ci: false, cwd: tmpDir }
73
- await doctor(opts)
74
-
75
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
76
- const result = JSON.parse(output)
77
-
78
- expect(result.passed).toBe(false)
79
- expect(result.files.length).toBe(1)
80
- expect(result.files[0].file).toBe(path.join('src', 'App.tsx'))
81
- expect(result.summary.filesWithIssues).toBe(1)
82
- expect(result.summary.totalErrors).toBeGreaterThan(0)
83
- // Should detect: react-import, use-state, use-effect-deps, class-name-prop
84
- const codes = result.files[0].diagnostics.map((d: { code: string }) => d.code)
85
- expect(codes).toContain('react-import')
86
- expect(codes).toContain('use-state')
87
- expect(codes).toContain('class-name-prop')
88
- })
89
-
90
- // ─── --fix mode ────────────────────────────────────────────────────────
91
-
92
- it('--fix mode rewrites files with migrations', async () => {
93
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
94
-
95
- const opts: DoctorOptions = { fix: true, json: false, ci: false, cwd: tmpDir }
96
- await doctor(opts)
97
-
98
- const updated = readFile(tmpDir, 'src/App.tsx')
99
- // React import should be removed or rewritten
100
- expect(updated).not.toContain('from "react"')
101
- // useState should be migrated to signal
102
- expect(updated).toContain('signal')
103
- // className should be migrated to class
104
- expect(updated).toContain('class=')
105
- })
106
-
107
- it('--fix mode reports totalFixed in JSON output', async () => {
108
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
109
-
110
- const opts: DoctorOptions = { fix: true, json: true, ci: false, cwd: tmpDir }
111
- await doctor(opts)
112
-
113
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
114
- const result = JSON.parse(output)
115
-
116
- expect(result.summary.totalFixed).toBeGreaterThan(0)
117
- })
118
-
119
- // ─── --json mode ───────────────────────────────────────────────────────
120
-
121
- it('--json mode returns structured JSON output', async () => {
122
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
123
-
124
- const opts: DoctorOptions = { fix: false, json: true, ci: false, cwd: tmpDir }
125
- await doctor(opts)
126
-
127
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
128
- const result = JSON.parse(output)
129
-
130
- expect(result).toHaveProperty('passed')
131
- expect(result).toHaveProperty('files')
132
- expect(result).toHaveProperty('summary')
133
- expect(result.summary).toHaveProperty('filesScanned')
134
- expect(result.summary).toHaveProperty('filesWithIssues')
135
- expect(result.summary).toHaveProperty('totalErrors')
136
- expect(result.summary).toHaveProperty('totalFixable')
137
- expect(result.summary).toHaveProperty('totalFixed')
138
- expect(Array.isArray(result.files)).toBe(true)
139
- })
140
-
141
- // ─── --ci mode ─────────────────────────────────────────────────────────
142
-
143
- it('--ci mode returns non-zero error count when issues found', async () => {
144
- writeFile(tmpDir, 'src/App.tsx', REACT_TSX)
145
-
146
- const opts: DoctorOptions = { fix: false, json: false, ci: true, cwd: tmpDir }
147
- const errorCount = await doctor(opts)
148
-
149
- expect(errorCount).toBeGreaterThan(0)
150
- })
151
-
152
- it('--ci mode returns 0 when no issues found', async () => {
153
- writeFile(tmpDir, 'src/App.tsx', CLEAN_TSX)
154
-
155
- const opts: DoctorOptions = { fix: false, json: false, ci: true, cwd: tmpDir }
156
- const errorCount = await doctor(opts)
157
-
158
- expect(errorCount).toBe(0)
159
- })
160
-
161
- // ─── skipping ──────────────────────────────────────────────────────────
162
-
163
- it('skips node_modules and non-source files', async () => {
164
- writeFile(tmpDir, 'node_modules/some-pkg/index.tsx', REACT_TSX)
165
- writeFile(tmpDir, 'dist/bundle.tsx', REACT_TSX)
166
- writeFile(tmpDir, 'assets/readme.md', '# className something useState')
167
- writeFile(tmpDir, 'src/App.tsx', CLEAN_TSX)
168
-
169
- const opts: DoctorOptions = { fix: false, json: true, ci: false, cwd: tmpDir }
170
- await doctor(opts)
171
-
172
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
173
- const result = JSON.parse(output)
174
-
175
- expect(result.passed).toBe(true)
176
- expect(result.summary.filesWithIssues).toBe(0)
177
- // Only the clean .tsx in src/ should be scanned
178
- expect(result.summary.filesScanned).toBe(1)
179
- })
180
-
181
- // ─── clean project ─────────────────────────────────────────────────────
182
-
183
- it('clean project returns no issues', async () => {
184
- writeFile(tmpDir, 'src/App.tsx', CLEAN_TSX)
185
-
186
- const errorCount = await doctor(defaultOptions(tmpDir))
187
-
188
- expect(errorCount).toBe(0)
189
- })
190
-
191
- it('clean project prints success message in human mode', async () => {
192
- writeFile(tmpDir, 'src/App.tsx', CLEAN_TSX)
193
-
194
- await doctor(defaultOptions(tmpDir))
195
-
196
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n')
197
- expect(output).toContain('No issues found')
198
- })
199
-
200
- // ─── hasReactPatterns pre-filter ────────────────────────────────────────
201
-
202
- it('hasReactPatterns pre-filter skips non-React files efficiently', async () => {
203
- // A file with Pyreon-only code should not produce diagnostics
204
- const pyreonOnly = `import { signal, computed, effect } from "@pyreon/reactivity"
205
- import { onMount } from "@pyreon/core"
206
-
207
- export function App() {
208
- const count = signal(0)
209
- const doubled = computed(() => count() * 2)
210
- effect(() => console.log(doubled()))
211
- onMount(() => { console.log("mounted") })
212
- return <div class="app">{count()}</div>
213
- }
214
- `
215
- writeFile(tmpDir, 'src/App.tsx', pyreonOnly)
216
-
217
- const opts: DoctorOptions = { fix: false, json: true, ci: false, cwd: tmpDir }
218
- await doctor(opts)
219
-
220
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
221
- const result = JSON.parse(output)
222
-
223
- expect(result.passed).toBe(true)
224
- expect(result.summary.totalErrors).toBe(0)
225
- })
226
-
227
- // ─── empty directory ────────────────────────────────────────────────────
228
-
229
- it('handles empty directory with no source files', async () => {
230
- const errorCount = await doctor(defaultOptions(tmpDir))
231
-
232
- expect(errorCount).toBe(0)
233
- })
234
-
235
- // ─── multiple files ─────────────────────────────────────────────────────
236
-
237
- it('scans multiple files and aggregates results', async () => {
238
- writeFile(tmpDir, 'src/A.tsx', REACT_TSX)
239
- writeFile(
240
- tmpDir,
241
- 'src/B.tsx',
242
- `import { useState } from "react"
243
- export function B() { const [x, setX] = useState(0); return <div>{x}</div> }
244
- `,
245
- )
246
- writeFile(tmpDir, 'src/C.tsx', CLEAN_TSX)
247
-
248
- const opts: DoctorOptions = { fix: false, json: true, ci: false, cwd: tmpDir }
249
- await doctor(opts)
250
-
251
- const output = logSpy.mock.calls.map((c: unknown[]) => c[0]).join('')
252
- const result = JSON.parse(output)
253
-
254
- expect(result.summary.filesScanned).toBe(3)
255
- expect(result.summary.filesWithIssues).toBe(2)
256
- })
257
- })
258
-
259
- describe('doctor — --audit-tests integration', () => {
260
- let logSpy: ReturnType<typeof vi.spyOn>
261
- let tmpDir: string
262
-
263
- beforeEach(() => {
264
- logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
265
- tmpDir = makeTmpDir()
266
- fs.mkdirSync(path.join(tmpDir, 'packages'), { recursive: true })
267
- })
268
- afterEach(() => {
269
- logSpy.mockRestore()
270
- fs.rmSync(tmpDir, { recursive: true, force: true })
271
- })
272
-
273
- it('does NOT print audit output when --audit-tests is absent (default)', async () => {
274
- writeFile(
275
- tmpDir,
276
- 'packages/x/src/tests/mock.test.ts',
277
- `const vnode = { type: 'div', props: {}, children: [] }`,
278
- )
279
- const opts: DoctorOptions = {
280
- fix: false,
281
- json: false,
282
- ci: false,
283
- cwd: tmpDir,
284
- auditTests: false,
285
- }
286
- await doctor(opts)
287
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
288
- expect(output).not.toContain('Test environment audit')
289
- })
290
-
291
- it('prints the test-audit report when --audit-tests is passed', async () => {
292
- writeFile(
293
- tmpDir,
294
- 'packages/x/src/tests/mock.test.ts',
295
- `const vnode = { type: 'div', props: {}, children: [] }`,
296
- )
297
- const opts: DoctorOptions = {
298
- fix: false,
299
- json: false,
300
- ci: false,
301
- cwd: tmpDir,
302
- auditTests: true,
303
- }
304
- await doctor(opts)
305
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
306
- expect(output).toContain('Test environment audit')
307
- expect(output).toContain('Mock-vnode exposure')
308
- // The HIGH file we wrote surfaces at the default minRisk=medium.
309
- expect(output).toContain('mock.test.ts')
310
- })
311
-
312
- it('emits machine-readable JSON when --json + --audit-tests both set', async () => {
313
- writeFile(
314
- tmpDir,
315
- 'packages/x/src/tests/mock.test.ts',
316
- `const vnode = { type: 'div', props: {}, children: [] }`,
317
- )
318
- const opts: DoctorOptions = {
319
- fix: false,
23
+ describe('doctor() end-to-end', () => {
24
+ // Use `--only react-patterns` for the empty-dir tests — most other
25
+ // gates walk up the dir tree looking for `packages/` (audit-tests)
26
+ // or read the real repo root for known files (doc-claims). Pinning
27
+ // to `react-patterns` isolates the test to the actual tmp dir.
28
+
29
+ it('runs against an empty dir and prints a clean banner', async () => {
30
+ const cwd = makeTmpDir()
31
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
32
+ const exitCode = await doctor({
33
+ ...defaults(cwd),
34
+ only: ['react-patterns'],
35
+ })
36
+ const out = log.mock.calls.map((c) => c[0]).join('\n')
37
+ log.mockRestore()
38
+ fs.rmSync(cwd, { recursive: true, force: true })
39
+
40
+ expect(out).toContain('pyreon doctor')
41
+ expect(out).toContain('Score:')
42
+ expect(exitCode).toBe(0)
43
+ })
44
+
45
+ it('--json emits a DoctorReport object', async () => {
46
+ const cwd = makeTmpDir()
47
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
48
+ await doctor({
49
+ ...defaults(cwd),
320
50
  json: true,
321
- ci: false,
322
- cwd: tmpDir,
323
- auditTests: true,
324
- }
325
- await doctor(opts)
326
- // Two JSON blobs logged separately — doctor result then the audit.
327
- const blobs = logSpy.mock.calls
328
- .map((c: unknown[]) => String(c[0] ?? ''))
329
- .filter((s: string) => s.trim().startsWith('{'))
330
- expect(blobs.length).toBeGreaterThanOrEqual(2)
331
- const auditBlob = blobs.find((s: string) => s.includes('testAudit'))
332
- expect(auditBlob).toBeDefined()
333
- const parsed = JSON.parse(auditBlob!) as { testAudit: { entries: unknown[] } }
334
- expect(Array.isArray(parsed.testAudit.entries)).toBe(true)
335
- })
336
-
337
- it('honours --audit-min-risk=high surfaces only HIGH files', async () => {
338
- writeFile(
339
- tmpDir,
340
- 'packages/x/src/tests/mock.test.ts',
341
- `const vnode = { type: 'div', props: {}, children: [] }`,
342
- )
343
- const opts: DoctorOptions = {
344
- fix: false,
345
- json: false,
346
- ci: false,
347
- cwd: tmpDir,
348
- auditTests: true,
349
- auditMinRisk: 'high',
350
- }
351
- await doctor(opts)
352
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
353
- expect(output).toContain('## HIGH')
354
- expect(output).not.toMatch(/^## MEDIUM/m)
355
- })
356
- })
357
-
358
- // ─── --check-islands integration (PR C) ─────────────────────────────────────
359
-
360
- describe('doctor — --check-islands integration', () => {
361
- let logSpy: ReturnType<typeof vi.spyOn>
362
- let tmpDir: string
363
-
364
- beforeEach(() => {
365
- logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
366
- tmpDir = makeTmpDir()
367
- fs.mkdirSync(path.join(tmpDir, 'packages'), { recursive: true })
368
- })
369
- afterEach(() => {
370
- logSpy.mockRestore()
371
- fs.rmSync(tmpDir, { recursive: true, force: true })
372
- })
373
-
374
- it('does NOT print islands audit output when --check-islands is absent (default)', async () => {
375
- writeFile(
376
- tmpDir,
377
- 'packages/x/src/Counter.tsx',
378
- `import { island } from '@pyreon/server'
379
- export const Counter = island(() => import('./Inner'), { name: 'Counter', hydrate: 'load' })`,
380
- )
381
- const opts: DoctorOptions = {
382
- fix: false,
383
- json: false,
384
- ci: false,
385
- cwd: tmpDir,
386
- checkIslands: false,
387
- }
388
- await doctor(opts)
389
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
390
- expect(output).not.toContain('Islands audit')
391
- })
392
-
393
- it('prints the islands audit when --check-islands is passed', async () => {
394
- writeFile(
395
- tmpDir,
396
- 'packages/x/src/A.tsx',
397
- `import { island } from '@pyreon/server'
398
- export const A = island(() => import('./AInner'), { name: 'Dup', hydrate: 'load' })`,
399
- )
400
- writeFile(
401
- tmpDir,
402
- 'packages/y/src/B.tsx',
403
- `import { island } from '@pyreon/server'
404
- export const B = island(() => import('./BInner'), { name: 'Dup', hydrate: 'load' })`,
405
- )
406
- writeFile(tmpDir, 'packages/x/src/AInner.tsx', `export default () => null`)
407
- writeFile(tmpDir, 'packages/y/src/BInner.tsx', `export default () => null`)
408
- const opts: DoctorOptions = {
409
- fix: false,
410
- json: false,
411
- ci: false,
412
- cwd: tmpDir,
413
- checkIslands: true,
414
- }
415
- await doctor(opts)
416
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
417
- expect(output).toContain('Islands audit')
418
- expect(output).toContain('## duplicate-name')
419
- // Both file locations appear in the human-readable section
420
- expect(output).toContain('A.tsx')
421
- expect(output).toContain('B.tsx')
422
- })
423
-
424
- it('emits machine-readable JSON when --json + --check-islands both set', async () => {
51
+ only: ['react-patterns'],
52
+ })
53
+ const out = log.mock.calls.map((c) => c[0]).join('')
54
+ log.mockRestore()
55
+ fs.rmSync(cwd, { recursive: true, force: true })
56
+
57
+ const parsed = JSON.parse(out)
58
+ expect(parsed.score).toBeTypeOf('number')
59
+ expect(parsed.grade).toMatch(/^[A-F]$/)
60
+ expect(Array.isArray(parsed.findings)).toBe(true)
61
+ expect(Array.isArray(parsed.gates)).toBe(true)
62
+ })
63
+
64
+ it('--ci returns 0 when no error findings', async () => {
65
+ const cwd = makeTmpDir()
66
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
67
+ const exitCode = await doctor({
68
+ ...defaults(cwd),
69
+ ci: true,
70
+ only: ['react-patterns'],
71
+ })
72
+ log.mockRestore()
73
+ fs.rmSync(cwd, { recursive: true, force: true })
74
+ expect(exitCode).toBe(0)
75
+ })
76
+
77
+ it('flags React patterns when detected', async () => {
78
+ const cwd = makeTmpDir()
425
79
  writeFile(
426
- tmpDir,
427
- 'packages/x/src/Orphan.tsx',
428
- `import { island } from '@pyreon/server'
429
- export const Orphan = island(() => import('./Inner'), { name: 'Orphan', hydrate: 'load' })`,
80
+ cwd,
81
+ 'src/App.tsx',
82
+ `import { useState } from "react"\nexport function X() { const [c, setC] = useState(0); return <div className="x">{c}</div> }\n`,
430
83
  )
431
- writeFile(tmpDir, 'packages/x/src/Inner.tsx', `export default () => null`)
432
- const opts: DoctorOptions = {
433
- fix: false,
84
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
85
+ await doctor({ ...defaults(cwd), json: true, only: ['react-patterns'] })
86
+ const out = log.mock.calls.map((c) => c[0]).join('')
87
+ log.mockRestore()
88
+ fs.rmSync(cwd, { recursive: true, force: true })
89
+
90
+ const parsed = JSON.parse(out)
91
+ expect(parsed.findings.length).toBeGreaterThan(0)
92
+ const codes = parsed.findings.map((f: { code: string }) => f.code)
93
+ expect(codes.some((c: string) => c.startsWith('react-patterns/'))).toBe(true)
94
+ })
95
+
96
+ it('legacy --audit-tests maps to --only audit-tests', async () => {
97
+ const cwd = makeTmpDir()
98
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
99
+ await doctor({
100
+ ...defaults(cwd),
434
101
  json: true,
435
- ci: false,
436
- cwd: tmpDir,
437
- checkIslands: true,
438
- }
439
- await doctor(opts)
440
- // Two JSON blobs logged separately — doctor result then the audit.
441
- const blobs = logSpy.mock.calls
442
- .map((c: unknown[]) => String(c[0] ?? ''))
443
- .filter((s: string) => s.trim().startsWith('{'))
444
- expect(blobs.length).toBeGreaterThanOrEqual(2)
445
- const auditBlob = blobs.find((s: string) => s.includes('islandAudit'))
446
- expect(auditBlob).toBeDefined()
447
- const parsed = JSON.parse(auditBlob!) as {
448
- islandAudit: { findings: Array<{ code: string }>; summary: { islandsDeclared: number } }
449
- }
450
- expect(parsed.islandAudit.summary.islandsDeclared).toBe(1)
451
- // The orphan declaration should produce exactly one dead-island finding.
452
- const codes = parsed.islandAudit.findings.map((f) => f.code)
453
- expect(codes).toContain('dead-island')
454
- })
455
-
456
- it('emits a clean green-light section when no findings are present', async () => {
457
- writeFile(
458
- tmpDir,
459
- 'packages/x/src/Counter.tsx',
460
- `import { island } from '@pyreon/server'
461
- export const Counter = island(() => import('./Inner'), { name: 'Counter', hydrate: 'load' })`,
462
- )
463
- writeFile(tmpDir, 'packages/x/src/Inner.tsx', `export default () => null`)
464
- writeFile(
465
- tmpDir,
466
- 'packages/x/src/index.ts',
467
- `export { Counter } from './Counter'`,
468
- )
469
- const opts: DoctorOptions = {
470
- fix: false,
471
- json: false,
472
- ci: false,
473
- cwd: tmpDir,
474
- checkIslands: true,
475
- }
476
- await doctor(opts)
477
- const output = logSpy.mock.calls.map((c: unknown[]) => c.join(' ')).join('\n')
478
- expect(output).toContain('Islands audit')
479
- expect(output).toContain('No island findings')
102
+ auditTests: true,
103
+ })
104
+ const out = log.mock.calls.map((c) => c[0]).join('')
105
+ log.mockRestore()
106
+ fs.rmSync(cwd, { recursive: true, force: true })
107
+
108
+ const parsed = JSON.parse(out)
109
+ const ranGates = parsed.gates
110
+ .filter((g: { meta: { skipped?: boolean } }) => !g.meta.skipped)
111
+ .map((g: { gate: string }) => g.gate)
112
+ expect(ranGates).toEqual(['audit-tests'])
113
+ })
114
+
115
+ it('respects --format=gha for GitHub Actions output', async () => {
116
+ const cwd = makeTmpDir()
117
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
118
+ await doctor({
119
+ ...defaults(cwd),
120
+ format: 'gha',
121
+ only: ['react-patterns'],
122
+ })
123
+ const out = log.mock.calls.map((c) => c[0]).join('\n')
124
+ log.mockRestore()
125
+ fs.rmSync(cwd, { recursive: true, force: true })
126
+
127
+ expect(out).toContain('::notice::pyreon doctor score:')
480
128
  })
481
129
  })