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:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user