All files write.ts

100% Statements 24/24
100% Branches 4/4
100% Functions 5/5
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56                              23x 23x   23x 52x 22x   2x 1x         56x 56x   20x         52x 52x         52x 52x 52x       23x   23x 54x 53x      
import { randomUUID } from "node:crypto";
import { chmod, mkdir, rename, stat, unlink, writeFile } from "node:fs/promises";
import path from "node:path";
 
export interface PendingWrite {
  content: string;
  mode?: number;
  target: string;
}
 
interface StagedWrite extends PendingWrite {
  temporaryPath: string;
}
 
export async function writeBatch(writes: readonly PendingWrite[]): Promise<void> {
  assertDistinctTargets(writes);
  const staged: StagedWrite[] = [];
 
  try {
    for (const write of writes) staged.push(await stageWrite(write));
    for (const write of staged) await rename(write.temporaryPath, write.target);
  } catch (error) {
    await Promise.allSettled(staged.map((write) => unlink(write.temporaryPath)));
    throw error;
  }
}
 
export async function readMode(filePath: string): Promise<number | undefined> {
  try {
    return (await stat(filePath)).mode;
  } catch {
    return undefined;
  }
}
 
async function stageWrite(write: PendingWrite): Promise<StagedWrite> {
  await mkdir(path.dirname(write.target), { recursive: true });
  const temporaryPath = path.join(
    path.dirname(write.target),
    `.${path.basename(write.target)}.${process.pid}.${randomUUID()}.tmp`,
  );
 
  await writeFile(temporaryPath, write.content, { flag: "wx", mode: write.mode });
  if (write.mode !== undefined) await chmod(temporaryPath, write.mode);
  return { ...write, temporaryPath };
}
 
function assertDistinctTargets(writes: readonly PendingWrite[]): void {
  const targets = new Set<string>();
 
  for (const write of writes) {
    if (targets.has(write.target)) throw new Error(`Multiple outputs resolve to ${write.target}`);
    targets.add(write.target);
  }
}