Skip to content

Commit a1c78a7

Browse files
committed
fix: resolve Windows 8.3 short path names in cwd replacement
On Windows CI, tmpdir() may return paths with 8.3 short names (e.g., RUNNER~1) while program output uses long names (runneradmin), or vice versa. Use fs.realpathSync.native() to resolve the cwd to its long-name form and try replacing both variants. This replaces the previous vitest-specific RUN line hack with a general solution that works for all path occurrences in output.
1 parent 812c223 commit a1c78a7

2 files changed

Lines changed: 42 additions & 28 deletions

File tree

packages/tools/src/__tests__/utils.spec.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -146,31 +146,21 @@ Done in 171ms using pnpm v10.16.1
146146
});
147147
});
148148

149-
test('normalize vitest RUN line with Windows absolute path', () => {
150-
const output = ' RUN C:/Users/RUNNER~1/AppData/Local/Temp/vite-plus-test-abc/my-test\n';
151-
expect(replaceUnstableOutput(output)).toBe(' RUN <cwd>\n');
152-
});
153-
154-
test('normalize vitest RUN line with Windows backslash path', () => {
155-
const output = ' RUN C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\vite-plus-test-abc\\my-test\n';
156-
expect(replaceUnstableOutput(output)).toBe(' RUN <cwd>\n');
157-
});
158-
159-
test('normalize vitest RUN line with version and Windows path', () => {
160-
const output = ' RUN v1.0.0 C:/Users/RUNNER~1/AppData/Local/Temp/vite-plus-test-abc/my-test\n';
161-
expect(replaceUnstableOutput(output)).toBe(' RUN v<semver> <cwd>\n');
162-
});
163-
164-
test('preserve vitest RUN line already normalized with <cwd>', () => {
165-
const output = ' RUN <cwd>\n';
166-
expect(replaceUnstableOutput(output)).toBe(' RUN <cwd>\n');
167-
});
168-
169-
test.skipIf(process.platform === 'win32')('normalize vitest RUN line with cwd on Unix', () => {
170-
const cwd = '/tmp/vite-plus-test-abc/my-test';
171-
const output = ` RUN ${cwd}\n`;
172-
expect(replaceUnstableOutput(output, cwd)).toBe(' RUN <cwd>\n');
173-
});
149+
test.skipIf(process.platform !== 'win32')(
150+
'Windows: resolve short path names (8.3) when replacing cwd',
151+
() => {
152+
// On Windows CI, tmpdir() may return short names like RUNNER~1.
153+
// fs.realpathSync.native() resolves them to long names.
154+
// Both forms should be replaced with <cwd>.
155+
const fs = require('node:fs');
156+
const shortCwd = fs.realpathSync(require('node:os').tmpdir());
157+
const longCwd = fs.realpathSync.native(require('node:os').tmpdir());
158+
if (shortCwd !== longCwd) {
159+
const output = ` RUN ${longCwd}\n`;
160+
expect(replaceUnstableOutput(output, shortCwd)).toBe(' RUN <cwd>\n');
161+
}
162+
},
163+
);
174164

175165
test('replace tsdown output', () => {
176166
const output = `

packages/tools/src/utils.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'node:fs';
12
import { homedir } from 'node:os';
23
import path from 'node:path';
34

@@ -8,6 +9,22 @@ const ANSI_ESCAPE_REGEX = new RegExp(
89
'g',
910
);
1011

12+
/**
13+
* On Windows, resolve 8.3 short path names (e.g., RUNNER~1) to long names.
14+
* Uses fs.realpathSync.native() which calls the OS-level realpath and resolves
15+
* short names. Falls back to the original path if it doesn't exist.
16+
*/
17+
function resolveWindowsLongPath(p: string): string {
18+
if (process.platform !== 'win32') {
19+
return p;
20+
}
21+
try {
22+
return fs.realpathSync.native(p);
23+
} catch {
24+
return p;
25+
}
26+
}
27+
1128
export function replaceUnstableOutput(output: string, cwd?: string) {
1229
// Normalize line endings and strip ANSI escapes so snapshots are stable
1330
// across CI platforms and terminal capabilities.
@@ -27,9 +44,19 @@ export function replaceUnstableOutput(output: string, cwd?: string) {
2744
output = output.replaceAll(rawPath, placeholder);
2845
};
2946
replacePathToken(cwd, '<cwd>');
47+
// On Windows, also try the long-path form to handle 8.3 short name mismatches
48+
// (e.g., cwd has RUNNER~1 but output has runneradmin, or vice versa).
49+
const longCwd = resolveWindowsLongPath(cwd);
50+
if (longCwd !== cwd) {
51+
replacePathToken(longCwd, '<cwd>');
52+
}
3053
const parent = path.dirname(cwd);
3154
if (parent !== '/') {
3255
replacePathToken(parent, '<cwd>/..');
56+
const longParent = resolveWindowsLongPath(parent);
57+
if (longParent !== parent) {
58+
replacePathToken(longParent, '<cwd>/..');
59+
}
3360
}
3461
}
3562
// On Windows, normalize path separators in file paths for consistent snapshots.
@@ -40,9 +67,6 @@ export function replaceUnstableOutput(output: string, cwd?: string) {
4067
// Pattern: backslash between alphanumeric/dot/underscore/hyphen chars
4168
output = output.replaceAll(/([a-zA-Z0-9._-])\\([a-zA-Z0-9._-])/g, '$1/$2');
4269
}
43-
// Normalize vitest RUN line: path may not match cwd on Windows due to short path names (RUNNER~1).
44-
// Format: " RUN /path" or " RUN v1.0.0 /path"
45-
output = output.replaceAll(/( RUN (?:v\S+ )?)(?!<cwd>)[A-Za-z]:[\\/]\S+/g, '$1<cwd>');
4670

4771
return (
4872
output

0 commit comments

Comments
 (0)