Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
9b641cfc36 [autofix.ci] apply automated fixes 2025-11-29 02:14:28 +00:00
Claude
7755740ad9 fix(windows): resolve mkdir(".") ENOENT error (#21901)
On Windows, mkdir(".", {recursive: true}) was failing with ENOENT
because path normalization was returning an empty string for ".",
which Windows APIs reject.

Two fixes:
1. In resolve_path.zig: Return "." instead of empty string when
   normalizing "." path
2. In types.zig: Resolve relative paths to absolute before passing
   to Windows kernel APIs

Fixes #21901

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 18:12:38 -08:00
3 changed files with 123 additions and 0 deletions

View File

@@ -634,6 +634,12 @@ pub const PathLike = union(enum) {
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal);
}
const normal = path_handler.normalizeStringBuf(s, b, true, .windows, false);
// If the normalized path is still relative (like "."), resolve it to an absolute path
if (!std.fs.path.isAbsoluteWindows(normal)) {
const cwd_path = std.posix.getcwd(buf) catch @panic("Error getting cwd");
const resolved = path_handler.joinAbsStringBuf(cwd_path, b, &.{normal}, .windows);
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), resolved);
}
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal);
}

View File

@@ -931,6 +931,13 @@ pub fn normalizeStringGenericTZ(
buf_i += 1;
}
// If normalization resulted in an empty path, return "."
// This happens when the input is just "." or consists only of dots/separators that get normalized away
if (buf_i == buf_start and path_.len > 0) {
buf[buf_i] = '.';
buf_i += 1;
}
if (options.zero_terminate) {
buf[buf_i] = 0;
}

View File

@@ -0,0 +1,110 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows } from "harness";
// https://github.com/oven-sh/bun/issues/21901
test("mkdir('.') should succeed with recursive option on Windows", async () => {
if (!isWindows) {
test.skip();
return;
}
// Test mkdirSync with recursive option
{
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const fs = require('fs');
try {
fs.mkdirSync('.', { recursive: true });
console.log('mkdirSync success');
} catch (err) {
console.error('mkdirSync error:', err.message);
process.exit(1);
}
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("mkdirSync success");
expect(exitCode).toBe(0);
}
// Test mkdir with recursive option (promise)
{
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const fs = require('fs').promises;
(async () => {
try {
await fs.mkdir('.', { recursive: true });
console.log('mkdir success');
} catch (err) {
console.error('mkdir error:', err.message);
process.exit(1);
}
})();
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("mkdir success");
expect(exitCode).toBe(0);
}
// Test Promise.allSettled with multiple mkdir('.') calls
{
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const fs = require('fs').promises;
(async () => {
try {
const results = await Promise.allSettled([
fs.mkdir('.', { recursive: true }),
fs.mkdir('.', { recursive: true }),
fs.mkdir('.', { recursive: true }),
]);
const allSucceeded = results.every(r => r.status === 'fulfilled');
if (allSucceeded) {
console.log('Promise.allSettled success');
} else {
const failed = results.filter(r => r.status === 'rejected');
console.error('Some promises failed:', failed.map(f => f.reason?.message).join(', '));
process.exit(1);
}
} catch (err) {
console.error('Promise.allSettled error:', err.message);
process.exit(1);
}
})();
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("Promise.allSettled success");
expect(exitCode).toBe(0);
}
});