Skip to content

Commit 0428732

Browse files
frankieyanclaude
andauthored
fix: auto-detect deleted files in --stage-record-file (#38)
* fix: auto-detect deleted files in --stage-record-file Previously relied on lint-staged passing deleted file paths as arguments, but lint-staged only provides modified files (add/change). Now scans all recorded files to detect which no longer exist on disk. Fixes #31 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Log file path when writing to records file --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1d7d973 commit 0428732

3 files changed

Lines changed: 34 additions & 34 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ npx @doist/react-compiler-tracker --overwrite
7474

7575
### `--stage-record-file <file1> <file2> ...`
7676

77-
Checks the provided files and updates the records. Exits with code 1 if errors increase (preventing the commit), otherwise updates the records file for the checked files. Reports when errors decrease, celebrating your progress. Deleted files are automatically removed from the records.
77+
Checks the provided files and updates the records. Exits with code 1 if errors increase (preventing the commit), otherwise updates the records file for the checked files. Reports when errors decrease, celebrating your progress. Deleted files are automatically removed from the records (no need to pass their paths).
7878

7979
```bash
8080
npx @doist/react-compiler-tracker --stage-record-file src/components/Button.tsx src/hooks/useData.ts
@@ -130,8 +130,9 @@ With lint-staged, the matched files are automatically passed as arguments to the
130130
131131
```bash
132132
#!/bin/sh
133-
# Get staged files (including deleted) and pass them to the tracker
134-
FILES=$(git diff --diff-filter=ACMRD --cached --name-only -- '*.tsx' '*.ts' '*.jsx' '*.js' | tr '\n' ' ')
133+
# Get staged files and pass them to the tracker
134+
# (deleted files are auto-detected from records, no need to pass them)
135+
FILES=$(git diff --diff-filter=ACMR --cached --name-only -- '*.tsx' '*.ts' '*.jsx' '*.js' | tr '\n' ' ')
135136
if [ -n "$FILES" ]; then
136137
npx @doist/react-compiler-tracker --stage-record-file $FILES
137138
fi

src/index.integration.test.mts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('CLI', () => {
7575
'🔍 Checking all 5 source files for React Compiler errors and recreating records…',
7676
)
7777
expect(output).toContain(
78-
'✅ Records file completed. Found 4 total React Compiler issues across 2 files',
78+
'✅ Records saved to .react-compiler.rec.json. Found 4 total React Compiler issues across 2 files',
7979
)
8080

8181
const records = JSON.parse(readFileSync(recordsPath, 'utf8'))
@@ -145,20 +145,14 @@ describe('CLI', () => {
145145
records = JSON.parse(readFileSync(recordsPath, 'utf8'))
146146
expect(records.files['src/deleted-file.tsx']).toEqual({ CompileError: 2 })
147147

148-
// Now run --stage-record-file with the deleted file path
149-
// The file src/deleted-file.tsx doesn't exist, simulating deletion
150-
const output = runCLI([
151-
'--stage-record-file',
152-
'src/good-component.tsx',
153-
'src/deleted-file.tsx',
154-
])
148+
// Run --stage-record-file WITHOUT the deleted file in arguments
149+
// (simulating lint-staged which doesn't pass deleted files)
150+
// The tool should auto-detect that src/deleted-file.tsx no longer exists
151+
const output = runCLI(['--stage-record-file', 'src/good-component.tsx'])
155152

156-
// Should log the deleted file being removed
157153
expect(output).toContain('🗑️ Removing 1 deleted file from records:')
158154
expect(output).toContain('• src/deleted-file.tsx')
159-
// Should check only existing files (1 file, not 2)
160155
expect(output).toContain('🔍 Checking 1 file for React Compiler errors')
161-
// Should not error on the deleted file
162156
expect(output).not.toContain('File not found')
163157

164158
// Verify the deleted file was removed from records

src/index.mts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,9 @@ async function main() {
4848
filePaths: sourceFiles.normalizeFilePaths(filePathParams),
4949
globPattern: config.sourceGlob,
5050
})
51-
const { existing, deleted } = sourceFiles.partitionByExistence(filePaths)
5251

5352
return await runStageRecords({
54-
existingFilePaths: existing,
55-
allFilePaths: filePaths,
56-
deletedFilePaths: deleted,
53+
filePaths,
5754
recordsFilePath: config.recordsFile,
5855
})
5956
}
@@ -136,40 +133,46 @@ async function runOverwriteRecords({
136133

137134
if (totalErrors > 0) {
138135
console.log(
139-
`✅ Records file completed. Found ${totalErrors} total React Compiler issues across ${compilerErrors.size} files`,
136+
`✅ Records saved to ${recordsFilePath}. Found ${totalErrors} total React Compiler issues across ${compilerErrors.size} files`,
140137
)
141138
} else {
142-
console.log('🎉 No React Compiler errors found')
139+
console.log(`🎉 Records saved to ${recordsFilePath}. No React Compiler errors found`)
143140
}
144141
}
145142

146143
/**
147144
* Handles the `--stage-record-file` flag by checking provided files and updating the records file.
148145
*
149146
* If errors have increased, the process will exit with code 1 and the records file will not be updated.
150-
* Deleted files are automatically removed from the records.
147+
* Deleted files are automatically detected by checking which recorded files no longer exist on disk.
151148
*/
152149
async function runStageRecords({
153-
existingFilePaths,
154-
allFilePaths,
155-
deletedFilePaths,
150+
filePaths,
156151
recordsFilePath,
157152
}: {
158-
existingFilePaths: string[]
159-
allFilePaths: string[]
160-
deletedFilePaths: string[]
153+
filePaths: string[]
161154
recordsFilePath: string
162155
}) {
156+
const records = recordsFile.load(recordsFilePath)
157+
const recordedFilePaths = records ? Object.keys(records.files) : []
158+
const { deleted: deletedFromRecords } = sourceFiles.partitionByExistence(recordedFilePaths)
159+
160+
const { existing: existingFilePaths, deleted: deletedFromInput } =
161+
sourceFiles.partitionByExistence(filePaths)
162+
163+
const allDeletedFilePaths = [...new Set([...deletedFromRecords, ...deletedFromInput])]
164+
const allFilePaths = [...new Set([...filePaths, ...deletedFromRecords])]
165+
163166
if (!allFilePaths.length) {
164167
console.log('✅ No files to check')
165168
return
166169
}
167170

168-
if (deletedFilePaths.length > 0) {
169-
const deletedFileWord = pluralize(deletedFilePaths.length, 'file', 'files')
170-
const fileList = deletedFilePaths.map((f) => ` • ${f}`).join('\n')
171+
if (allDeletedFilePaths.length > 0) {
172+
const deletedFileWord = pluralize(allDeletedFilePaths.length, 'file', 'files')
173+
const fileList = allDeletedFilePaths.map((f) => ` • ${f}`).join('\n')
171174
console.log(
172-
`🗑️ Removing ${deletedFilePaths.length} deleted ${deletedFileWord} from records:\n${fileList}`,
175+
`🗑️ Removing ${allDeletedFilePaths.length} deleted ${deletedFileWord} from records:\n${fileList}`,
173176
)
174177
}
175178

@@ -191,7 +194,7 @@ async function runStageRecords({
191194
customReactCompilerLogger: customReactCompilerLogger,
192195
})
193196

194-
const records = checkErrorChanges({ filePaths: existingFilePaths, recordsFilePath })
197+
checkErrorChanges({ filePaths: existingFilePaths, recordsFilePath, records })
195198

196199
//
197200
// Update and stage records file (includes deleted files so they get removed from records)
@@ -212,7 +215,7 @@ async function runStageRecords({
212215
exitWithWarning(`Failed to stage records file at ${recordsFileRelativePath}`)
213216
}
214217

215-
console.log('✅ No new React Compiler errors')
218+
console.log(`✅ Records saved to ${recordsFilePath}. No new React Compiler errors`)
216219
}
217220

218221
/**
@@ -304,11 +307,13 @@ function getErrorCount() {
304307
function checkErrorChanges({
305308
filePaths,
306309
recordsFilePath,
310+
records: providedRecords,
307311
}: {
308312
filePaths: string[]
309313
recordsFilePath: string
314+
records?: recordsFile.Records | null
310315
}) {
311-
const records = recordsFile.load(recordsFilePath)
316+
const records = providedRecords ?? recordsFile.load(recordsFilePath)
312317
const { increases, decreases } = recordsFile.getErrorChanges({
313318
filePaths,
314319
existingRecords: records?.files ?? {},

0 commit comments

Comments
 (0)