|
1 | 1 | import {createHash} from "node:crypto";
|
2 | 2 | import {existsSync} from "node:fs";
|
3 |
| -import {access, constants, copyFile, readFile, stat, writeFile} from "node:fs/promises"; |
| 3 | +import {access, constants, copyFile, readFile, realpath, stat, writeFile} from "node:fs/promises"; |
4 | 4 | import {basename, dirname, extname, join} from "node:path/posix";
|
5 | 5 | import type {Config} from "./config.js";
|
6 | 6 | import {CliError, isEnoent} from "./error.js";
|
7 |
| -import {getClientPath, prepareOutput, visitMarkdownFiles} from "./files.js"; |
| 7 | +import {getClientPath, maybeStat, prepareOutput, visitMarkdownFiles} from "./files.js"; |
8 | 8 | import {getModuleHash} from "./javascript/module.js";
|
9 | 9 | import {transpileModule} from "./javascript/transpile.js";
|
10 | 10 | import type {Logger, Writer} from "./logger.js";
|
@@ -345,13 +345,25 @@ export class FileBuildEffects implements BuildEffects {
|
345 | 345 | async copyFile(sourcePath: string, outputPath: string): Promise<void> {
|
346 | 346 | const destination = join(this.outputRoot, outputPath);
|
347 | 347 | this.logger.log(destination);
|
| 348 | + await noClobber(destination); |
348 | 349 | await prepareOutput(destination);
|
349 | 350 | await copyFile(sourcePath, destination);
|
350 | 351 | }
|
351 | 352 | async writeFile(outputPath: string, contents: string | Buffer): Promise<void> {
|
352 | 353 | const destination = join(this.outputRoot, outputPath);
|
353 | 354 | this.logger.log(destination);
|
| 355 | + await noClobber(destination); |
354 | 356 | await prepareOutput(destination);
|
355 | 357 | await writeFile(destination, contents);
|
356 | 358 | }
|
357 | 359 | }
|
| 360 | + |
| 361 | +// Asserts that the destination file is not already present. This can happen if |
| 362 | +// the same file is referenced with inconsistent naming (e.g. upper vs. |
| 363 | +// lowercase), and would result in a broken site. |
| 364 | +async function noClobber(path) { |
| 365 | + if (await maybeStat(path)) { |
| 366 | + const real = (await realpath(path)).slice((await realpath(".")).length + 1); |
| 367 | + throw new Error(`${faint("File name conflict over")} ${real}.`); |
| 368 | + } |
| 369 | +} |
0 commit comments