Files
oh-my-pi/packages/utils/src/temp.ts
T
can1357 e69fa5efd2 style(utils): sort temp.ts imports to satisfy biome
The added node:fs/promises import preceded node:fs, which biome's
organizeImports rejects and fails CI lint. Reorder to unblock merge.

Addresses review feedback on #2382.
2026-06-14 17:27:15 +02:00

131 lines
2.9 KiB
TypeScript

import * as fs from "node:fs";
import * as fsPromises from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
export class TempDir {
#path: string;
private constructor(path: string) {
this.#path = path;
}
static createSync(prefix?: string): TempDir {
return new TempDir(fs.mkdtempSync(normalizePrefix(prefix)));
}
static async create(prefix?: string): Promise<TempDir> {
return new TempDir(await fsPromises.mkdtemp(normalizePrefix(prefix)));
}
#removePromise: Promise<void> | null = null;
path(): string {
return this.#path;
}
absolute(): string {
return path.resolve(this.#path);
}
remove(): Promise<void> {
if (this.#removePromise) {
return this.#removePromise;
}
const removePromise = removeWithRetries(this.#path);
this.#removePromise = removePromise;
return removePromise;
}
removeSync(): void {
removeSyncWithRetries(this.#path);
this.#removePromise = Promise.resolve();
}
toString(): string {
return this.#path;
}
join(...paths: string[]): string {
return path.join(this.#path, ...paths);
}
async [Symbol.asyncDispose](): Promise<void> {
try {
await this.remove();
} catch {
// Ignore cleanup errors
}
}
[Symbol.dispose](): void {
try {
this.removeSync();
} catch {
// Ignore cleanup errors
}
}
}
const kTempDir = os.tmpdir();
function normalizePrefix(prefix?: string): string {
if (!prefix) {
return `${kTempDir}${path.sep}pi-temp-`;
} else if (prefix.startsWith("@")) {
return path.join(kTempDir, prefix.slice(1));
}
return prefix;
}
const kRemoveOptions = { recursive: true, force: true } as const;
const kRemoveRetries = 4;
const kRemoveRetryDelayMs = 10;
const kRetryableRemoveErrorCodes = new Set(["EBUSY", "EPERM", "ENOTEMPTY"]);
const kSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
async function removeWithRetries(target: string): Promise<void> {
for (let attempt = 0; ; attempt++) {
try {
await fsPromises.rm(target, kRemoveOptions);
return;
} catch (err) {
if (!shouldRetryRemove(err, attempt)) throw err;
await Bun.sleep(kRemoveRetryDelayMs);
}
}
}
function removeSyncWithRetries(target: string): void {
for (let attempt = 0; ; attempt++) {
try {
fs.rmSync(target, kRemoveOptions);
return;
} catch (err) {
if (!shouldRetryRemove(err, attempt)) throw err;
sleepSync(kRemoveRetryDelayMs);
}
}
}
function shouldRetryRemove(err: unknown, attempt: number): boolean {
return attempt < kRemoveRetries && process.platform === "win32" && isRetryableRemoveError(err);
}
function isRetryableRemoveError(err: unknown): boolean {
return (
typeof err === "object" &&
err !== null &&
"code" in err &&
typeof err.code === "string" &&
kRetryableRemoveErrorCodes.has(err.code)
);
}
function sleepSync(ms: number): void {
if ("sleepSync" in Bun && typeof Bun.sleepSync === "function") {
Bun.sleepSync(ms);
return;
}
Atomics.wait(kSleepBuffer, 0, 0, ms);
}