Repository navigation
Expand file tree
/
Copy pathreferenceOnlyBuild.ts
More file actions
131 lines (120 loc) · 4.37 KB
/
Copy pathreferenceOnlyBuild.ts
File metadata and controls
131 lines (120 loc) · 4.37 KB
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* Builds only the English reference pages (/reference/**), for PR previews
* (processing/p5.js#9303). Every other route 404s in the output.
*
* Astro has no config option to exclude routes from a static build, so this
* wrapper temporarily moves every non-reference entry out of src/pages before
* running `astro build`, and restores them afterwards — also on failure and
* on SIGINT/SIGTERM. Only src/pages/reference/** plus the shared _utils
* modules (imported by layouts and components) stay in place.
*
* Run with: npm run build:reference-only
* Set REFERENCE_SKIP_LOWERCASE=1 to also skip the lowercase alias routes.
*/
import fs from "node:fs";
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath, pathToFileURL } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..", "..");
const pagesDir = path.join(root, "src", "pages");
export const stashDir = path.join(root, ".reference-only-stash");
// Entries in src/pages that the reference build needs
const keep = new Set(["reference", "_utils.ts", "_utils-node.ts"]);
/**
* A non-empty stash dir means a previous run died before restoring
* src/pages (e.g. kill -9; normal failures and Ctrl-C restore on their own).
* Refuse to run rather than mix two runs' stashes, and say how to recover.
*/
export const assertStashDirUsable = (dir: string) => {
let stashed: string[];
try {
stashed = fs.readdirSync(dir);
} catch {
return; // no stash dir: nothing to recover
}
if (stashed.length === 0) return;
throw new Error(
`Refusing to run: ${dir} already exists and is not empty.\n` +
`A previous run was probably interrupted before it could restore ` +
`src/pages. Recover with:\n\n` +
` mv ${path.relative(root, dir)}/* src/pages/ && rmdir ${path.relative(root, dir)}\n`,
);
};
// Sync so it can also run inside a signal handler.
const restoreStash = () => {
let stashed: string[];
try {
stashed = fs.readdirSync(stashDir);
} catch {
return;
}
for (const entry of stashed) {
fs.renameSync(path.join(stashDir, entry), path.join(pagesDir, entry));
}
fs.rmdirSync(stashDir);
console.log("Restored non-reference pages.");
};
let restored = false;
const restoreOnce = () => {
if (restored) return;
restored = true;
restoreStash();
};
let currentChild: ReturnType<typeof spawn> | undefined;
const runAstroBuild = () =>
new Promise<number>((resolve) => {
// `astro build` directly rather than `npm run build`, which would also
// run `astro check` — previews don't need type checking and it costs time.
// detached puts npx and the astro process it spawns in their own process
// group, so the signal handler below can kill both; signalling only the
// npx wrapper leaves the actual build running.
const child = spawn("npx", ["astro", "build"], {
cwd: root,
stdio: "inherit",
detached: true,
env: { ...process.env, REFERENCE_ONLY: "1" },
});
currentChild = child;
child.on("exit", (code, signal) => resolve(signal ? 1 : (code ?? 1)));
child.on("error", (err) => {
console.error(`Failed to start astro build: ${err}`);
resolve(1);
});
});
const main = async () => {
assertStashDirUsable(stashDir);
fs.mkdirSync(stashDir, { recursive: true });
// Kill the build's process group, restore src/pages, and exit with the
// conventional signal exit code. The child is detached, so this group-kill
// is what stops the build on Ctrl-C too.
for (const signal of ["SIGINT", "SIGTERM"] as const) {
process.on(signal, () => {
if (currentChild?.pid) {
try {
process.kill(-currentChild.pid, signal);
} catch {
// the group already exited
}
}
restoreOnce();
process.exit(signal === "SIGINT" ? 130 : 143);
});
}
try {
let moved = 0;
for (const entry of fs.readdirSync(pagesDir)) {
if (keep.has(entry)) continue;
fs.renameSync(path.join(pagesDir, entry), path.join(stashDir, entry));
moved++;
}
console.log(`Moved ${moved} non-reference page entries aside.`);
process.exitCode = await runAstroBuild();
} finally {
restoreOnce();
}
};
// Only run when executed as a script, so tests can import the helpers.
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main();
}