Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/smoke/project/project-output-dir-safety.test.ts
12921 views
1
/*
2
* project-output-dir-safety.test.ts
3
*
4
* Test for issue #13892: output-dir configurations should not delete project files
5
*
6
* Copyright (C) 2020-2025 Posit Software, PBC
7
*/
8
import { docs } from "../../utils.ts";
9
10
import { join } from "../../../src/deno_ral/path.ts";
11
import { existsSync } from "../../../src/deno_ral/fs.ts";
12
import { testQuartoCmd } from "../../test.ts";
13
import { fileExists, noErrors } from "../../verify.ts";
14
15
// Helper to clean up website output files from a directory
16
// Used when output-dir points to the project directory itself
17
async function cleanWebsiteOutput(dir: string) {
18
const filesToRemove = ["index.html", "test.html", "search.json"];
19
const dirsToRemove = ["site_libs", ".quarto"];
20
21
for (const file of filesToRemove) {
22
const path = join(dir, file);
23
if (existsSync(path)) {
24
await Deno.remove(path);
25
}
26
}
27
for (const subdir of dirsToRemove) {
28
const path = join(dir, subdir);
29
if (existsSync(path)) {
30
await Deno.remove(path, { recursive: true });
31
}
32
}
33
}
34
35
// Helper to create output-dir safety tests
36
function testOutputDirSafety(
37
name: string,
38
outputDir: string | null, // null means output is in project dir (website type)
39
) {
40
const testDir = docs(`project/output-dir-${name}`);
41
const dir = join(Deno.cwd(), testDir);
42
const outputPath = outputDir ? join(dir, outputDir) : dir;
43
44
testQuartoCmd(
45
"render",
46
[testDir],
47
[
48
noErrors,
49
fileExists(join(dir, "marker.txt")), // Project file must survive
50
fileExists(join(outputPath, "test.html")), // Output created correctly
51
],
52
{
53
teardown: async () => {
54
// Clean up rendered output
55
if (outputDir) {
56
// Subdirectory case - remove the whole output dir
57
if (existsSync(outputPath)) {
58
await Deno.remove(outputPath, { recursive: true });
59
}
60
// Clean up .quarto directory
61
const quartoDir = join(dir, ".quarto");
62
if (existsSync(quartoDir)) {
63
await Deno.remove(quartoDir, { recursive: true });
64
}
65
} else {
66
// In-place website case - clean up website output files only
67
await cleanWebsiteOutput(dir);
68
}
69
},
70
},
71
);
72
}
73
74
// Test 1: output-dir: ./ (the bug case from #13892)
75
testOutputDirSafety("safety", null);
76
77
// Test 2: output-dir: . (without trailing slash)
78
testOutputDirSafety("dot", null);
79
80
// Test 3: output-dir: _output (normal subdirectory case)
81
testOutputDirSafety("subdir", "_output");
82
83
// Test 4: output-dir: ../dirname (parent traversal back to project dir)
84
// This tests the case where output-dir references the project directory
85
// via parent traversal (e.g., project in "quarto-proj", output-dir: "../quarto-proj")
86
testOutputDirSafety("parent-ref", null);
87
88