@pajh/buldng 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buldng-lib.js +353 -206
- package/dist/buldng-ops.js +258 -0
- package/dist/buldng-validate.js +207 -0
- package/dist/buldng.css +148 -0
- package/dist/clean.sh +14 -0
- package/dist/init.js +2 -0
- package/dist/layout.ts +184 -0
- package/dist/types/global.d.ts +10 -0
- package/dist/workfile.js +69 -0
- package/dist/wrapper.html +24 -0
- package/package.json +12 -3
package/dist/buldng-lib.js
CHANGED
|
@@ -1,261 +1,408 @@
|
|
|
1
1
|
// tools/build-lib.js
|
|
2
|
-
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import esbuild from "esbuild";
|
|
6
5
|
|
|
7
|
-
// --------------------------------------------------
|
|
8
|
-
// GLOBAL SOURCE & DEST ROOTS
|
|
9
|
-
// --------------------------------------------------
|
|
10
|
-
|
|
11
|
-
let SRC = null;
|
|
12
|
-
let DEST = null;
|
|
13
|
-
|
|
14
|
-
// --------------------------------------------------
|
|
15
|
-
// SETTERS WITH SAFETY CHECKS
|
|
16
|
-
// --------------------------------------------------
|
|
17
|
-
|
|
18
|
-
export function setSource(dir) {
|
|
19
|
-
const abs = path.resolve(dir);
|
|
20
|
-
SRC = abs;
|
|
21
|
-
validateRoots();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function setDest(dir) {
|
|
25
|
-
const abs = path.resolve(dir);
|
|
26
|
-
DEST = abs;
|
|
27
|
-
validateRoots();
|
|
28
|
-
}
|
|
29
6
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
import { WorkFile } from "./workfile.js";
|
|
12
|
+
import { getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, looksLikeFilename, looksLikeHTML,
|
|
13
|
+
looksLikeCSS, looksLikeJS, ensureLeadingSlash, ensureWorkFile } from "./buldng-validate.js";
|
|
14
|
+
import { file, literal, wrap, append, replace, compile, materialise}
|
|
15
|
+
from "./buldng-ops.js";
|
|
16
|
+
export { file, literal, wrap, append, replace, compile, materialise }
|
|
17
|
+
from "./buldng-ops.js";
|
|
18
|
+
export { assertSourceRead, assertDestWrite, setSource, setDest, looksLikeFilename,
|
|
19
|
+
looksLikeHTML, looksLikeCSS, looksLikeJS, ensureWorkFile, ensureLeadingSlash } from "./buldng-validate.js";
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
setSource,
|
|
23
|
+
setDest,
|
|
24
|
+
mkdir,
|
|
25
|
+
deepCopy,
|
|
26
|
+
file,
|
|
27
|
+
literal,
|
|
28
|
+
wrap,
|
|
29
|
+
append,
|
|
30
|
+
replace,
|
|
31
|
+
materialise,
|
|
32
|
+
compile,
|
|
33
|
+
csslink,
|
|
34
|
+
scriptlink,
|
|
35
|
+
pageHTML,
|
|
36
|
+
buldngHTMLWrapper,
|
|
37
|
+
reg,
|
|
38
|
+
printHeader
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const pkg = JSON.parse(readFileSync(join(here, "../package.json"), "utf8"));
|
|
43
|
+
const registry = new Map();
|
|
44
|
+
|
|
45
|
+
export function reg(key, value) {
|
|
46
|
+
if (typeof key !== "string") {
|
|
47
|
+
throw new Error(`reg(): key must be a string`);
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
// src cannot be inside dest, dest cannot be inside src
|
|
43
|
-
if (SRC.startsWith(DEST) || DEST.startsWith(SRC)) {
|
|
44
|
-
throw new Error(`SECURITY: src and dest cannot be nested:
|
|
45
|
-
src: ${SRC}
|
|
46
|
-
dest: ${DEST}`);
|
|
49
|
+
|
|
50
|
+
const wf = ensureWorkFile(value, "reg");
|
|
51
|
+
|
|
52
|
+
if (registry.has(key)) {
|
|
53
|
+
console.log(`reg(): overwriting existing key '${key}'`);
|
|
47
54
|
}
|
|
55
|
+
|
|
56
|
+
registry.set(key, wf);
|
|
57
|
+
return wf;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
let headerPrinted = false;
|
|
61
|
+
export function printHeader(importMeta) {
|
|
62
|
+
if (headerPrinted) return;
|
|
63
|
+
headerPrinted = true;
|
|
64
|
+
|
|
65
|
+
const version = pkg.version;
|
|
66
|
+
const timestamp = new Date().toLocaleString("en-IE", { hour12: false });
|
|
67
|
+
|
|
68
|
+
const callerFile = fileURLToPath(importMeta.url);
|
|
69
|
+
const callerDir = dirname(callerFile);
|
|
70
|
+
|
|
71
|
+
console.log(`*local* buldng build system v${version} — ${timestamp}`);
|
|
72
|
+
console.log(`${callerDir}/${callerFile.split("/").pop()}`);
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
append(relPath) {
|
|
82
|
-
const abs = assertSourceRead(relPath);
|
|
83
|
-
const extra = fs.readFileSync(abs, "utf8");
|
|
84
|
-
return new File(this.content + "\n" + extra);
|
|
75
|
+
export function csslink(input) {
|
|
76
|
+
const pre = `<link rel="stylesheet" href="`;
|
|
77
|
+
const post = `">`;
|
|
78
|
+
|
|
79
|
+
// --------------------------------------------------
|
|
80
|
+
// Case 1: string
|
|
81
|
+
// --------------------------------------------------
|
|
82
|
+
if (typeof input === "string") {
|
|
83
|
+
if (!input.endsWith(".css")) {
|
|
84
|
+
throw new Error(`csslink(): expected a .css filename, got '${input}'`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return new WorkFile(
|
|
88
|
+
"literal",
|
|
89
|
+
{ value: pre + ensureLeadingSlash(input) + post },
|
|
90
|
+
[],
|
|
91
|
+
"csslink"
|
|
92
|
+
);
|
|
85
93
|
}
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
|
|
95
|
+
// --------------------------------------------------
|
|
96
|
+
// Case 2: WorkFile
|
|
97
|
+
// --------------------------------------------------
|
|
98
|
+
if (input instanceof WorkFile) {
|
|
99
|
+
return new WorkFile(
|
|
100
|
+
"wrap",
|
|
101
|
+
{ pre, post },
|
|
102
|
+
[input],
|
|
103
|
+
"csslink"
|
|
104
|
+
);
|
|
94
105
|
}
|
|
106
|
+
|
|
107
|
+
throw new Error(`csslink(): expected string or WorkFile, got ${typeof input}`);
|
|
95
108
|
}
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return fs.readFileSync(abs, "utf8");
|
|
115
|
-
});
|
|
116
|
-
return new File(parts.join("\n"));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --------------------------------------------------
|
|
120
|
-
// stitch({ shell, app, css, js }) → File
|
|
121
|
-
// --------------------------------------------------
|
|
122
|
-
|
|
123
|
-
export function stitch({ shell, app, css = [], js = [] }) {
|
|
124
|
-
const shellAbs = assertSourceRead(shell);
|
|
125
|
-
const appAbs = assertSourceRead(app);
|
|
126
|
-
|
|
127
|
-
let html = fs.readFileSync(shellAbs, "utf8");
|
|
128
|
-
const appHtml = fs.readFileSync(appAbs, "utf8");
|
|
129
|
-
|
|
130
|
-
html = html.replace("<!--APP-->", appHtml);
|
|
131
|
-
|
|
132
|
-
const cssLinks = css
|
|
133
|
-
.map(c => `<link rel="stylesheet" href="/${c}">`)
|
|
134
|
-
.join("\n");
|
|
135
|
-
html = html.replace("<!--CSS-->", cssLinks);
|
|
136
|
-
|
|
137
|
-
const jsScripts = js
|
|
138
|
-
.map(j => `<script type="module" src="/${j}"></script>`)
|
|
139
|
-
.join("\n");
|
|
140
|
-
html = html.replace("<!--SCRIPTS-->", jsScripts);
|
|
141
|
-
|
|
142
|
-
return new File(html);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// --------------------------------------------------
|
|
146
|
-
// compile("pages/puzzle.ts") → File
|
|
147
|
-
// --------------------------------------------------
|
|
148
|
-
|
|
149
|
-
export function compile(relTsPath) {
|
|
150
|
-
const abs = assertSourceRead(relTsPath);
|
|
151
|
-
|
|
152
|
-
const result = esbuild.buildSync({
|
|
153
|
-
entryPoints: [abs],
|
|
154
|
-
bundle: true,
|
|
155
|
-
minify: true,
|
|
156
|
-
sourcemap: true,
|
|
157
|
-
format: "esm",
|
|
158
|
-
target: "esnext",
|
|
159
|
-
write: false
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (result.errors.length > 0) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
"Build failed:\n" +
|
|
165
|
-
result.errors.map(e => e.text).join("\n")
|
|
110
|
+
export function scriptlink(input) {
|
|
111
|
+
const pre = `<script type="module" src="/`;
|
|
112
|
+
const post = `"></script>`;
|
|
113
|
+
|
|
114
|
+
// --------------------------------------------------
|
|
115
|
+
// Case 1: string
|
|
116
|
+
// --------------------------------------------------
|
|
117
|
+
if (typeof input === "string") {
|
|
118
|
+
if (!input.endsWith(".js")) {
|
|
119
|
+
throw new Error(`scriptlink(): expected a .js filename, got '${input}'`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return new WorkFile(
|
|
123
|
+
"literal",
|
|
124
|
+
{ value: pre + input + post },
|
|
125
|
+
[],
|
|
126
|
+
"scriptlink"
|
|
166
127
|
);
|
|
167
128
|
}
|
|
168
|
-
|
|
169
|
-
|
|
129
|
+
|
|
130
|
+
// --------------------------------------------------
|
|
131
|
+
// Case 2: WorkFile
|
|
132
|
+
// --------------------------------------------------
|
|
133
|
+
if (input instanceof WorkFile) {
|
|
134
|
+
return new WorkFile(
|
|
135
|
+
"wrap",
|
|
136
|
+
{ pre, post },
|
|
137
|
+
[input],
|
|
138
|
+
"scriptlink"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new Error(`scriptlink(): expected string or WorkFile, got ${typeof input}`);
|
|
170
143
|
}
|
|
171
144
|
|
|
145
|
+
|
|
172
146
|
// --------------------------------------------------
|
|
173
147
|
// mkdir("puzzle")
|
|
174
148
|
// --------------------------------------------------
|
|
175
|
-
|
|
176
149
|
export function mkdir(relDir) {
|
|
177
150
|
const abs = assertDestWrite(relDir);
|
|
178
151
|
fs.mkdirSync(abs, { recursive: true });
|
|
179
152
|
}
|
|
180
153
|
|
|
154
|
+
// --------------------------------------------------
|
|
155
|
+
// deepCopy — raw filesystem path
|
|
156
|
+
// --------------------------------------------------
|
|
181
157
|
export function deepCopy(from, to) {
|
|
182
|
-
|
|
158
|
+
let DEST = getDest();
|
|
159
|
+
|
|
183
160
|
console.log(`deepCopy: ${from} → ${to}`);
|
|
184
|
-
|
|
161
|
+
|
|
185
162
|
const absFrom = path.resolve(from);
|
|
186
|
-
|
|
187
|
-
// to is relative to DEST
|
|
163
|
+
|
|
188
164
|
if (to.includes("..")) {
|
|
189
165
|
throw new Error("SECURITY: Illegal path traversal in deepCopy()");
|
|
190
166
|
}
|
|
191
|
-
|
|
167
|
+
|
|
192
168
|
const absTo = to === "/" ? DEST : path.resolve(DEST, to);
|
|
193
|
-
|
|
194
|
-
// Ensure target is inside DEST
|
|
169
|
+
|
|
195
170
|
if (!absTo.startsWith(DEST)) {
|
|
196
171
|
throw new Error(`SECURITY: deepCopy target escapes dest:
|
|
197
172
|
dest: ${DEST}
|
|
198
173
|
target: ${absTo}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!fs.existsSync(absFrom)) {
|
|
177
|
+
throw new Error(`deepCopy: source does not exist: ${absFrom}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function copyRecursive(src, dst) {
|
|
181
|
+
const stat = fs.statSync(src);
|
|
182
|
+
|
|
183
|
+
if (stat.isDirectory()) {
|
|
184
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
185
|
+
const entries = fs.readdirSync(src);
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
copyRecursive(path.join(src, entry), path.join(dst, entry));
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
191
|
+
fs.copyFileSync(src, dst);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
copyRecursive(absFrom, absTo);
|
|
199
196
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
throw new Error(`deepCopy: source does not exist: ${absFrom}`);
|
|
197
|
+
|
|
198
|
+
export function header() {
|
|
199
|
+
printHeader(import.meta);
|
|
204
200
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
function
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
function packageFileAbs(filename) {
|
|
204
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
205
|
+
const __dirname = dirname(__filename);
|
|
206
|
+
return join(__dirname, filename);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// wraps whatever you provide in the overall buldng html wrapper.
|
|
210
|
+
// also compiles buldng's layout.ts and saves layout.js to the site root.
|
|
211
|
+
export function buldngHTMLWrapper(source) {
|
|
212
|
+
const srcWF = ensureWorkFile(source, "source");
|
|
213
|
+
|
|
214
|
+
// 1. Compile buldng's layout.ts (lazy)
|
|
215
|
+
const layoutTS = new WorkFile("file", { absPath: packageFileAbs("layout.ts") }, []);
|
|
216
|
+
const layoutJS = compile(layoutTS);
|
|
217
|
+
|
|
218
|
+
// 2. Save compiled layout.js to the site root (this triggers execution)
|
|
219
|
+
layoutJS.save("layout.js");
|
|
220
|
+
|
|
221
|
+
// 2.5 Save camera.css to the site root (this triggers execution)
|
|
222
|
+
const buldngCSS = new WorkFile("file", { absPath: packageFileAbs("buldng.css") }, []);
|
|
223
|
+
buldngCSS.save("buldng.css");
|
|
224
|
+
|
|
225
|
+
// 3. Wrap project shell in buldng's wrapper.html
|
|
226
|
+
const wrapperWF = new WorkFile("file", { absPath: packageFileAbs("wrapper.html") }, []);
|
|
227
|
+
return replace(wrapperWF, "<!--CAMERA-STYLE-->", srcWF);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
const _warned = {
|
|
233
|
+
shell: false,
|
|
234
|
+
projectCSS: false
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export function pageHTML({ app, css = null, js = null, out }) {
|
|
238
|
+
if (!out || typeof out !== "string") {
|
|
239
|
+
throw new Error("pageHTML(): 'out' must be a non-empty string");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!app) {
|
|
243
|
+
throw new Error("pageHTML(): 'app' is required and cannot be null");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const appWF = ensureWorkFile(app, "app");
|
|
247
|
+
|
|
248
|
+
// Look up optional registered assets
|
|
249
|
+
const shellWF = registry.get("shell") ?? null;
|
|
250
|
+
const projectCSS = registry.get("projectCSS") ?? null;
|
|
251
|
+
|
|
252
|
+
// One-time warnings
|
|
253
|
+
if (!shellWF && !_warned.shell) {
|
|
254
|
+
console.warn(
|
|
255
|
+
"pageHTML(): no 'shell' registered — building raw page HTML.\n" +
|
|
256
|
+
"To register a shell use: reg('shell', file`layout/shell.html`)"
|
|
257
|
+
);
|
|
258
|
+
_warned.shell = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!projectCSS && !_warned.projectCSS) {
|
|
262
|
+
console.warn(
|
|
263
|
+
"pageHTML(): no 'projectCSS' registered — page will not include project CSS.\n" +
|
|
264
|
+
"To register project CSS use: reg('projectCSS', file`layout/buldng.css`)"
|
|
265
|
+
);
|
|
266
|
+
_warned.projectCSS = true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --------------------------------------------------
|
|
270
|
+
// Build HTML WorkFile lazily
|
|
271
|
+
// --------------------------------------------------
|
|
272
|
+
|
|
273
|
+
let htmlWF;
|
|
274
|
+
|
|
275
|
+
if (shellWF) {
|
|
276
|
+
htmlWF = replace(shellWF, "<!--APP-->", appWF);
|
|
277
|
+
} else {
|
|
278
|
+
htmlWF = appWF;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Insert JS lazily
|
|
282
|
+
if (js) {
|
|
283
|
+
const jsWF = resolveAsset(js, {
|
|
284
|
+
ext: ".js",
|
|
285
|
+
tag: "scriptlink",
|
|
286
|
+
wrap: scriptlink,
|
|
287
|
+
autoPrefix: "js"
|
|
288
|
+
});
|
|
289
|
+
htmlWF = replace(htmlWF, "<!--SCRIPTS-->", jsWF);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Insert CSS lazily (project first)
|
|
293
|
+
const cssWFs = [];
|
|
294
|
+
|
|
295
|
+
if (projectCSS) {
|
|
296
|
+
cssWFs.push(resolveAsset(projectCSS, {
|
|
297
|
+
ext: ".css",
|
|
298
|
+
tag: "csslink",
|
|
299
|
+
wrap: csslink,
|
|
300
|
+
autoPrefix: "css"
|
|
301
|
+
}));
|
|
302
|
+
console.log(`${out}: has projectCSS`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (css) {
|
|
306
|
+
cssWFs.push(resolveAsset(css, {
|
|
307
|
+
ext: ".css",
|
|
308
|
+
tag: "csslink",
|
|
309
|
+
wrap: csslink,
|
|
310
|
+
autoPrefix: "css"
|
|
311
|
+
}));
|
|
312
|
+
console.log(`${out}: has pageCSS`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (cssWFs.length === 0) {
|
|
316
|
+
// No CSS at all → do nothing
|
|
317
|
+
// (This is allowed)
|
|
318
|
+
} else if (cssWFs.length === 1) {
|
|
319
|
+
// One CSS → inject directly
|
|
320
|
+
htmlWF = replace(htmlWF, "<!--CSS-->", cssWFs[0]);
|
|
218
321
|
} else {
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
322
|
+
// Two CSS → append them
|
|
323
|
+
const cssListWF = append(...cssWFs);
|
|
324
|
+
htmlWF = replace(htmlWF, "<!--CSS-->", cssListWF);
|
|
222
325
|
}
|
|
326
|
+
|
|
327
|
+
// The ONLY public collapse operator
|
|
328
|
+
return htmlWF.save(out);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let autoCounter = 1;
|
|
332
|
+
|
|
333
|
+
function resolveAsset(input, {
|
|
334
|
+
ext, // ".css" or ".js"
|
|
335
|
+
tag, // "csslink" or "jslink"
|
|
336
|
+
wrap, // csslink() or stylelink()
|
|
337
|
+
autoPrefix // "css" or "js"
|
|
338
|
+
}) {
|
|
339
|
+
if (input == null) return null;
|
|
340
|
+
|
|
341
|
+
// Case 1: string
|
|
342
|
+
if (typeof input === "string") {
|
|
343
|
+
if (!input.endsWith(ext)) {
|
|
344
|
+
throw new Error(`pageHTML(${ext}): expected a ${ext} filename, got '${input}'`);
|
|
345
|
+
}
|
|
346
|
+
return wrap(input);
|
|
223
347
|
}
|
|
224
348
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// rm_dest("dir") — remove a file or directory inside DEST
|
|
230
|
-
// --------------------------------------------------
|
|
349
|
+
// Case 2: must be WorkFile
|
|
350
|
+
if (!(input instanceof WorkFile)) {
|
|
351
|
+
throw new Error(`pageHTML(${ext}): expected string or WorkFile, got ${typeof input}`);
|
|
352
|
+
}
|
|
231
353
|
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
throw new Error("SECURITY: Illegal path traversal in rmDest()");
|
|
354
|
+
// Case 3: already a link
|
|
355
|
+
if (input.tag === tag) {
|
|
356
|
+
return input;
|
|
236
357
|
}
|
|
237
358
|
|
|
238
|
-
|
|
359
|
+
// Case 4: literal WorkFile
|
|
360
|
+
if (input.op === "literal") {
|
|
361
|
+
const value = input.config.value;
|
|
239
362
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
363
|
+
if (value.endsWith(ext)) {
|
|
364
|
+
return wrap(input);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
throw new Error(
|
|
368
|
+
`pageHTML(${ext}): literal WorkFile does not end in ${ext} → '${value}'`
|
|
369
|
+
);
|
|
244
370
|
}
|
|
245
371
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
372
|
+
// Case 5a: materialise WorkFile
|
|
373
|
+
if (input.op === "materialise") {
|
|
374
|
+
const target = input.config.target;
|
|
375
|
+
|
|
376
|
+
if (!target.endsWith(ext)) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`pageHTML(${ext}): materialise target '${target}' does not end in ${ext}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return wrap(input);
|
|
249
383
|
}
|
|
250
384
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
385
|
+
// Case 5b: blob → auto-materialise
|
|
386
|
+
const autoName = `${autoPrefix}${autoCounter++}${ext}`;
|
|
254
387
|
|
|
388
|
+
console.warn(
|
|
389
|
+
`pageHTML(${ext}): received non-literal WorkFile; auto-materialising as ${autoName}`
|
|
390
|
+
);
|
|
255
391
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
392
|
+
const mat = materialise(input, autoName);
|
|
393
|
+
return wrap(mat);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
// --------------------------------------------------
|
|
398
|
+
// build-inputs.json manifest
|
|
399
|
+
// --------------------------------------------------
|
|
400
|
+
process.on("exit", () => {
|
|
401
|
+
if (process.env.BULDNG_HOT !== "1") return;
|
|
402
|
+
let DEST = getDest();
|
|
403
|
+
let INPUTS = getInputs();
|
|
404
|
+
const manifest = Array.from(INPUTS);
|
|
405
|
+
const out = path.join(DEST, "build-inputs.json");
|
|
406
|
+
fs.writeFileSync(out, JSON.stringify(manifest, null, 2));
|
|
407
|
+
});
|
|
408
|
+
|