Skip to content

Commit 625d66d

Browse files
frankieyanclaude
andcommitted
feat: Add validateFilesExist to error on missing files
Files that don't exist now throw an error instead of being silently ignored. This provides clear feedback when lint-staged passes a path to a file that was deleted. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2ca160a commit 625d66d

4 files changed

Lines changed: 52 additions & 2 deletions

File tree

src/index.integration.test.mts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ describe('CLI', () => {
5454
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
5555
})
5656

57+
it('errors when file does not exist', () => {
58+
const output = runCLI(['--check-files', 'src/nonexistent-file.tsx'])
59+
60+
expect(output).toContain('File not found: src/nonexistent-file.tsx')
61+
})
62+
5763
it('accepts --overwrite flag', () => {
5864
const output = runCLI(['--overwrite'])
5965

src/index.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async function main() {
4747
filePaths: sourceFiles.normalizeFilePaths(filePathParams),
4848
globPattern: config.sourceGlob,
4949
})
50+
sourceFiles.validateFilesExist(filePaths)
5051

5152
return await runStageRecords({
5253
filePaths,
@@ -65,6 +66,7 @@ async function main() {
6566
filePaths: sourceFiles.normalizeFilePaths(filePathParams),
6667
globPattern: config.sourceGlob,
6768
})
69+
sourceFiles.validateFilesExist(filePaths)
6870

6971
return await runCheckFiles({ filePaths, recordsFilePath: config.recordsFile })
7072
}

src/source-files.mts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execSync } from 'node:child_process'
2+
import { existsSync } from 'node:fs'
23
import { relative } from 'node:path'
34
import { glob } from 'glob'
45
import { minimatch } from 'minimatch'
@@ -71,4 +72,16 @@ function filterByGlob({ filePaths, globPattern }: { filePaths: string[]; globPat
7172
return filePaths.filter((filePath) => minimatch(filePath, globPattern))
7273
}
7374

74-
export { getAll, normalizeFilePaths, filterByGlob }
75+
/**
76+
* Validates that all file paths exist on the filesystem.
77+
* Throws an error if any file is not found.
78+
*/
79+
function validateFilesExist(filePaths: string[]) {
80+
for (const filePath of filePaths) {
81+
if (!existsSync(filePath)) {
82+
throw new Error(`File not found: ${filePath}`)
83+
}
84+
}
85+
}
86+
87+
export { getAll, normalizeFilePaths, filterByGlob, validateFilesExist }

src/source-files.test.mts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { execSync } from 'node:child_process'
2+
import { existsSync } from 'node:fs'
23
import { glob } from 'glob'
34
import { afterEach, describe, expect, it, vi } from 'vitest'
4-
import { filterByGlob, getAll, normalizeFilePaths } from './source-files.mjs'
5+
import { filterByGlob, getAll, normalizeFilePaths, validateFilesExist } from './source-files.mjs'
56

67
vi.mock('node:child_process', () => ({
78
execSync: vi.fn(),
89
}))
910

11+
vi.mock('node:fs', () => ({
12+
existsSync: vi.fn(),
13+
}))
14+
1015
vi.mock('glob', () => ({
1116
glob: {
1217
sync: vi.fn(),
@@ -152,3 +157,27 @@ describe('filterByGlob', () => {
152157
expect(result).toEqual(['src/a.ts', 'src/b.tsx'])
153158
})
154159
})
160+
161+
describe('validateFilesExist', () => {
162+
it('throws error when file does not exist', () => {
163+
vi.mocked(existsSync).mockReturnValue(false)
164+
165+
expect(() => validateFilesExist(['nonexistent.tsx'])).toThrow(
166+
'File not found: nonexistent.tsx',
167+
)
168+
})
169+
170+
it('does not throw when all files exist', () => {
171+
vi.mocked(existsSync).mockReturnValue(true)
172+
173+
expect(() => validateFilesExist(['exists.tsx'])).not.toThrow()
174+
})
175+
176+
it('throws on first missing file', () => {
177+
vi.mocked(existsSync).mockImplementation((path) => path === 'exists.tsx')
178+
179+
expect(() => validateFilesExist(['exists.tsx', 'missing.tsx'])).toThrow(
180+
'File not found: missing.tsx',
181+
)
182+
})
183+
})

0 commit comments

Comments
 (0)