fix(utils): create log dir before redirecting stderr guard

On a fresh profile ~/.omp/logs may not exist yet (the logger creates it
lazily), so opening getLogPath() with "a" threw and the guard fell back to
/dev/null — discarding macOS diagnostics and native crash reports instead
of preserving them in the omp log. mkdir the redirect target's parent
(recursive) before opening; the /dev/null fallback stays as the safety net.
This commit is contained in:
Kormákur
2026-07-11 00:38:14 +00:00
parent 46a898f872
commit 17486d2009
2 changed files with 51 additions and 1 deletions
+7 -1
View File
@@ -23,6 +23,7 @@
*/
import { dlopen, FFIType } from "bun:ffi";
import * as fs from "node:fs";
import * as path from "node:path";
import { getLogPath } from "./dirs";
const STDOUT_FILENO = 1;
@@ -101,7 +102,12 @@ export function suppressTerminalStderr(options?: SuppressTerminalStderrOptions):
let redirectFd: number;
try {
redirectFd = fs.openSync(options?.redirectPath ?? getLogPath(), "a");
const redirectPath = options?.redirectPath ?? getLogPath();
// getLogsDir() only computes the path; the logger creates it lazily, so
// on a fresh profile ~/.omp/logs may not exist yet. Create it here so
// diagnostics land in the log instead of falling through to /dev/null.
fs.mkdirSync(path.dirname(redirectPath), { recursive: true });
redirectFd = fs.openSync(redirectPath, "a");
} catch {
try {
redirectFd = fs.openSync("/dev/null", "w");
+44
View File
@@ -94,4 +94,48 @@ describe("stderr guard", () => {
expect(fs.existsSync(redirectPath)).toBe(false);
}
});
it("creates the redirect target's parent directory when it does not exist", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "stderr-guard-"));
tempDirs.push(dir);
// Nested path whose parent dirs do not exist yet — mirrors a fresh
// profile where ~/.omp/logs has not been created by the logger.
const missingParent = path.join(dir, "nested", "deep");
const redirectPath = path.join(missingParent, "redirect.log");
const probePath = path.join(dir, "probe.ts");
fs.writeFileSync(
probePath,
[
`import { restoreTerminalStderr, suppressTerminalStderr } from ${JSON.stringify(GUARD_MODULE)};`,
`import * as fs from "node:fs";`,
`const redirectPath = process.argv[2];`,
`const forced = suppressTerminalStderr({ force: true, redirectPath });`,
`if (forced) fs.writeSync(2, "hidden\\n");`,
`restoreTerminalStderr();`,
`process.stdout.write(JSON.stringify({ forced }));`,
].join("\n"),
);
const proc = Bun.spawn([process.execPath, probePath, redirectPath], {
stdout: "pipe",
stderr: "pipe",
});
const [stdout, exitCode] = await Promise.all([
new Response(proc.stdout as ReadableStream<Uint8Array>).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
const { forced } = JSON.parse(stdout) as { forced: boolean };
if (forced) {
// The guard must have created the missing parent and written there,
// not fallen back to /dev/null.
expect(fs.existsSync(missingParent)).toBe(true);
expect(fs.readFileSync(redirectPath, "utf8")).toBe("hidden\n");
} else {
// Inert (no libc fd ops): returns before opening the redirect, so the
// parent is never created.
expect(fs.existsSync(missingParent)).toBe(false);
}
});
});