@doist/react-compiler-tracker
Advanced tools
+7
-0
@@ -5,2 +5,9 @@ # Changelog | ||
| ## [2.0.3](https://github.com/Doist/react-compiler-tracker/compare/react-compiler-tracker-v2.0.2...react-compiler-tracker-v2.0.3) (2026-01-19) | ||
| ### Bug Fixes | ||
| * handle shell-escaped file paths ([#29](https://github.com/Doist/react-compiler-tracker/issues/29)) ([ae78e6a](https://github.com/Doist/react-compiler-tracker/commit/ae78e6afa1ec766a3f9d68ceb67c4accd6556b83)) | ||
| ## [2.0.2](https://github.com/Doist/react-compiler-tracker/compare/react-compiler-tracker-v2.0.1...react-compiler-tracker-v2.0.2) (2026-01-18) | ||
@@ -7,0 +14,0 @@ |
@@ -35,3 +35,3 @@ import { execSync } from 'node:child_process'; | ||
| const output = runCLI(); | ||
| expect(output).toContain('🔍 Checking all 4 source files for React Compiler errors…'); | ||
| expect(output).toContain('🔍 Checking all 5 source files for React Compiler errors…'); | ||
| expect(output).toContain('⚠️ Found 4 React Compiler issues across 2 files'); | ||
@@ -55,5 +55,11 @@ expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow(); | ||
| }); | ||
| it('handles shell-escaped file paths with $ character', () => { | ||
| // Simulate what CI tools do when passing filenames with $ through shell variables | ||
| const output = runCLI(['--check-files', 'src/route.\\$param.tsx']); | ||
| expect(output).toContain('🔍 Checking 1 files for React Compiler errors…'); | ||
| expect(output).not.toContain('File not found'); | ||
| }); | ||
| it('accepts --overwrite flag', () => { | ||
| const output = runCLI(['--overwrite']); | ||
| expect(output).toContain('🔍 Checking all 4 source files for React Compiler errors and recreating records…'); | ||
| expect(output).toContain('🔍 Checking all 5 source files for React Compiler errors and recreating records…'); | ||
| expect(output).toContain('âś… Records file completed. Found 4 total React Compiler issues across 2 files'); | ||
@@ -109,4 +115,4 @@ const records = JSON.parse(readFileSync(recordsPath, 'utf8')); | ||
| const output = runCLI(); | ||
| // Should only find .tsx files (2 instead of 4) | ||
| expect(output).toContain('🔍 Checking all 2 source files for React Compiler errors…'); | ||
| // Should only find .tsx files (3 instead of 5) | ||
| expect(output).toContain('🔍 Checking all 3 source files for React Compiler errors…'); | ||
| }); | ||
@@ -113,0 +119,0 @@ it('uses custom recordsFile from config', () => { |
@@ -30,4 +30,4 @@ import { execSync } from 'node:child_process'; | ||
| /** | ||
| * Normalizes file paths by converting absolute paths to cwd-relative | ||
| * and stripping the git prefix when present. | ||
| * Normalizes file paths by unescaping shell-escaped characters, converting | ||
| * absolute paths to cwd-relative, and stripping the git prefix when present. | ||
| * | ||
@@ -38,2 +38,6 @@ * When running from a package subdirectory in a monorepo, file paths from git | ||
| * | ||
| * Shell escapes (e.g., \$ → $) are only applied to paths containing forward | ||
| * slashes, which indicates Unix-style paths. Paths using only backslashes | ||
| * (Windows native paths) are preserved to avoid corrupting path separators. | ||
| * | ||
| * @example | ||
@@ -46,2 +50,8 @@ * // When cwd is apps/frontend/ (prefix is "apps/frontend/") | ||
| * | ||
| * // Shell-escaped characters are unescaped (Unix-style paths) | ||
| * normalizeFilePaths(["src/route.\\$id.tsx"]) // => ["src/route.$id.tsx"] | ||
| * | ||
| * // Windows-style paths are preserved (no forward slashes) | ||
| * normalizeFilePaths(["src\\utils\\file.ts"]) // => ["src\\utils\\file.ts"] | ||
| * | ||
| * // Paths that don't start with prefix are unchanged | ||
@@ -54,11 +64,20 @@ * normalizeFilePaths(["src/file.tsx"]) // => ["src/file.tsx"] | ||
| return filePaths.map((filePath) => { | ||
| // Only unescape shell-escaped characters for Unix-style paths (containing /). | ||
| // Windows paths use \ as separator, so we preserve them to avoid corruption. | ||
| // This handles Git Bash on Windows and Windows CI which use forward slashes. | ||
| // We only unescape non-alphanumeric characters since shells don't escape letters/digits, | ||
| // but Windows separators are typically followed by alphanumeric directory names. | ||
| // This preserves mixed paths like src/utils\file.ts (valid on Windows). | ||
| const normalized = filePath.includes('/') | ||
| ? filePath.replace(/\\([^a-zA-Z0-9])/g, '$1') | ||
| : filePath; | ||
| // Handle absolute paths by converting to cwd-relative | ||
| if (filePath.startsWith('/')) { | ||
| return relative(cwd, filePath); | ||
| if (normalized.startsWith('/')) { | ||
| return relative(cwd, normalized); | ||
| } | ||
| // Handle monorepo prefix stripping | ||
| if (prefix && filePath.startsWith(prefix)) { | ||
| return filePath.slice(prefix.length); | ||
| if (prefix && normalized.startsWith(prefix)) { | ||
| return normalized.slice(prefix.length); | ||
| } | ||
| return filePath; | ||
| return normalized; | ||
| }); | ||
@@ -65,0 +84,0 @@ } |
@@ -93,2 +93,55 @@ import { execSync } from 'node:child_process'; | ||
| }); | ||
| it('unescapes shell-escaped $ characters', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src/route.\\$id.tsx', 'src/draft.\\$slug.tsx']); | ||
| expect(result).toEqual(['src/route.$id.tsx', 'src/draft.$slug.tsx']); | ||
| }); | ||
| it('unescapes shell-escaped spaces', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src/file\\ with\\ spaces.tsx']); | ||
| expect(result).toEqual(['src/file with spaces.tsx']); | ||
| }); | ||
| it('unescapes multiple different escape characters', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src/route.\\$id\\ (copy).tsx']); | ||
| expect(result).toEqual(['src/route.$id (copy).tsx']); | ||
| }); | ||
| it('handles paths with no escapes unchanged', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src/normal-file.tsx']); | ||
| expect(result).toEqual(['src/normal-file.tsx']); | ||
| }); | ||
| it('unescapes before stripping prefix', () => { | ||
| vi.mocked(execSync).mockReturnValue('apps/frontend/\n'); | ||
| const result = normalizeFilePaths(['apps/frontend/src/route.\\$id.tsx']); | ||
| expect(result).toEqual(['src/route.$id.tsx']); | ||
| }); | ||
| it('preserves Windows-style paths with backslash separators', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src\\utils\\file.ts', 'src\\components\\Button.tsx']); | ||
| expect(result).toEqual(['src\\utils\\file.ts', 'src\\components\\Button.tsx']); | ||
| }); | ||
| it('preserves Windows paths with $ in filename', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| const result = normalizeFilePaths(['src\\$files.ts', 'src\\routes\\$id.tsx']); | ||
| expect(result).toEqual(['src\\$files.ts', 'src\\routes\\$id.tsx']); | ||
| }); | ||
| it('does not unescape bare filenames without forward slashes', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| // Bare filenames without / are ambiguous, so we preserve them | ||
| const result = normalizeFilePaths(['\\$id.tsx', 'file.tsx']); | ||
| expect(result).toEqual(['\\$id.tsx', 'file.tsx']); | ||
| }); | ||
| it('preserves mixed paths with both forward and back slashes', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| // Mixed paths are valid on Windows - backslash separators should be preserved | ||
| const result = normalizeFilePaths(['src/utils\\file.ts', 'src/components\\Button.tsx']); | ||
| expect(result).toEqual(['src/utils\\file.ts', 'src/components\\Button.tsx']); | ||
| }); | ||
| it('unescapes special chars in mixed paths while preserving backslash separators', () => { | ||
| vi.mocked(execSync).mockReturnValue('\n'); | ||
| // Should unescape \$ (non-alphanumeric) but preserve \s in utils\subdir (alphanumeric) | ||
| const result = normalizeFilePaths(['src/utils\\subdir/route.\\$id.tsx']); | ||
| expect(result).toEqual(['src/utils\\subdir/route.$id.tsx']); | ||
| }); | ||
| }); | ||
@@ -95,0 +148,0 @@ describe('filterByGlob', () => { |
+3
-3
| { | ||
| "name": "@doist/react-compiler-tracker", | ||
| "version": "2.0.2", | ||
| "version": "2.0.3", | ||
| "description": "A React Compiler violation tracker to help migrations and prevent regressions", | ||
@@ -52,3 +52,3 @@ "type": "module", | ||
| "@types/babel__core": "7.20.5", | ||
| "@types/node": "25.0.6", | ||
| "@types/node": "25.0.8", | ||
| "babel-plugin-react-compiler": "1.0.0", | ||
@@ -60,3 +60,3 @@ "husky": "9.1.7", | ||
| "typescript": "5.9.3", | ||
| "vitest": "4.0.16" | ||
| "vitest": "4.0.17" | ||
| }, | ||
@@ -63,0 +63,0 @@ "peerDependencies": { |
51178
10.89%1017
8.31%