@pajh/buldng 0.0.4 → 0.0.5
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/package.json +7 -2
- package/src/MIME.json +11 -0
- package/src/buldng-async.js +20 -0
- package/src/buldng-files.js +58 -0
- package/src/buldng-html.js +262 -0
- package/src/buldng-lib.js +42 -417
- package/src/buldng-modal.ts +348 -0
- package/src/buldng-typescript.js +77 -0
- package/src/buldng-web.ts +55 -180
- package/src/buldng.css +130 -0
- package/src/dev-errors.js +16 -0
- package/src/layout-err.ts +171 -0
- package/src/layout.ts +4 -0
- package/src/serve-lib.js +567 -0
- package/src/serve.js +193 -0
- package/src/types/global.d.ts +9 -0
- package/src/wrapper.html +2 -0
- package/src/buldng-dev-errors.js +0 -84
package/src/serve-lib.js
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
// serve-lib.js
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { exec, spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
export const VALID_FLAGS = {
|
|
8
|
+
"log": { type: "boolean" },
|
|
9
|
+
"hot": { type: "boolean" },
|
|
10
|
+
"dev-errors": { type: "boolean" },
|
|
11
|
+
"map": { type: "value" },
|
|
12
|
+
"root": { type: "value" },
|
|
13
|
+
"port": { type: "value" },
|
|
14
|
+
"browser": { type: "value" }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// --------------------------------------------------
|
|
18
|
+
// STATE CREATION
|
|
19
|
+
// --------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export function createState({ ROOT, PORT, MAPPINGS, MIME, LOG_ALL, HOT_REQUESTED, DEV_ERRORS }) {
|
|
22
|
+
return {
|
|
23
|
+
ROOT,
|
|
24
|
+
PORT,
|
|
25
|
+
MAPPINGS,
|
|
26
|
+
MIME,
|
|
27
|
+
LOG_ALL,
|
|
28
|
+
HOT_REQUESTED,
|
|
29
|
+
DEV_ERRORS,
|
|
30
|
+
hotEnabled: false,
|
|
31
|
+
injections: { html: [] },
|
|
32
|
+
running: false,
|
|
33
|
+
sseClients: [],
|
|
34
|
+
watchers: []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// backing store for parsed arguments
|
|
39
|
+
export const ARGS = new Map();
|
|
40
|
+
|
|
41
|
+
// --------------------------------------------------
|
|
42
|
+
// LOCAL PATH HELPER
|
|
43
|
+
// --------------------------------------------------
|
|
44
|
+
|
|
45
|
+
export const local = (strings, ...values) => {
|
|
46
|
+
const raw = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ""), "");
|
|
47
|
+
const cleaned = raw.replace(/^\.?\//, "");
|
|
48
|
+
return `${process.cwd()}/${cleaned}`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function processArgs() {
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
const a = args[i];
|
|
56
|
+
|
|
57
|
+
// must start with --
|
|
58
|
+
if (!a.startsWith("--")) {
|
|
59
|
+
console.error(`Unexpected argument: ${a}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const key = a.slice(2); // trim "--"
|
|
64
|
+
const next = (i < args.length -1) ? args[i+1] : "--dummy";
|
|
65
|
+
|
|
66
|
+
if (next.startsWith("--")) { // flag
|
|
67
|
+
validated_set(key, true);
|
|
68
|
+
} else {
|
|
69
|
+
validated_set(key, next);
|
|
70
|
+
i++; // skip next
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// returns the value or undefined
|
|
77
|
+
export function arg(key) {
|
|
78
|
+
return ARGS.get(key);
|
|
79
|
+
}
|
|
80
|
+
function validated_set(key, value) {
|
|
81
|
+
|
|
82
|
+
// --------------------------------------------------
|
|
83
|
+
// internal sanity checks
|
|
84
|
+
// --------------------------------------------------
|
|
85
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
86
|
+
console.error(`internal error: bad argmap key "${key}"`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isStringValue = (typeof value === "string" && value.length > 0);
|
|
91
|
+
const isTrueValue = (value === true);
|
|
92
|
+
|
|
93
|
+
if (!isStringValue && !isTrueValue) {
|
|
94
|
+
console.error(`internal error: bad argmap value for "${key}": ${value}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const spec = VALID_FLAGS[key];
|
|
99
|
+
if (!spec) {
|
|
100
|
+
console.error(`[args] Unknown argument: --${key}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (spec.type === "boolean") {
|
|
105
|
+
if (!isTrueValue) {
|
|
106
|
+
console.error(`[args] Cannot set a value for boolean flag --${key}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
ARGS.set(key, true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (spec.type === "value") {
|
|
114
|
+
if (!isStringValue) {
|
|
115
|
+
console.error(`[args] Usage: --${key} <value>`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
ARGS.set(key, value);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.error(`internal error: bad VALID_FLAGS entry for "${key}"`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --------------------------------------------------
|
|
127
|
+
// INJECTION SYSTEM
|
|
128
|
+
// --------------------------------------------------
|
|
129
|
+
|
|
130
|
+
export function registerInject(state, type, marker, content, mode = "pre") {
|
|
131
|
+
if (!["pre", "post", "replace"].includes(mode)) {
|
|
132
|
+
throw new Error(`Unknown injection mode: ${mode}`);
|
|
133
|
+
}
|
|
134
|
+
state.injections[type] ??= [];
|
|
135
|
+
state.injections[type].push({ marker, content, mode });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function inject(block, injection) {
|
|
139
|
+
const { marker, content, mode } = injection;
|
|
140
|
+
switch (mode) {
|
|
141
|
+
case "replace": return block.replace(marker, content);
|
|
142
|
+
case "pre": return block.replace(marker, content + marker);
|
|
143
|
+
case "post": return block.replace(marker, marker + content);
|
|
144
|
+
default: throw new Error(`Unknown injection mode: ${mode}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function applyInjections(data, type, state) {
|
|
149
|
+
const list = state.injections[type];
|
|
150
|
+
if (!list || list.length === 0) return data;
|
|
151
|
+
|
|
152
|
+
let text = data.toString("utf8");
|
|
153
|
+
for (const inj of list) text = inject(text, inj);
|
|
154
|
+
return Buffer.from(text, "utf8");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function sanitisePort(port) {
|
|
158
|
+
const n = Number(port);
|
|
159
|
+
|
|
160
|
+
if (!Number.isInteger(n)) {
|
|
161
|
+
console.error(`[args] port must be an integer: ${port}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (n < 1 || n > 65535) {
|
|
166
|
+
console.error(`[args] port must be between 1 and 65535: ${port}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return n;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function sanitiseRoot(root) {
|
|
174
|
+
// must be a non-empty string
|
|
175
|
+
if (typeof root !== "string" || root.trim() === "") {
|
|
176
|
+
console.error(`[args] root must be a non-empty string`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// resolve relative paths from where the server was launched
|
|
181
|
+
const resolved = path.resolve(root);
|
|
182
|
+
|
|
183
|
+
// check existence
|
|
184
|
+
let stat;
|
|
185
|
+
try {
|
|
186
|
+
stat = fs.statSync(resolved);
|
|
187
|
+
} catch {
|
|
188
|
+
console.error(`[args] root directory does not exist: ${resolved}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// check it's a directory
|
|
193
|
+
if (!stat.isDirectory()) {
|
|
194
|
+
console.error(`[args] root is not a directory: ${resolved}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// check user can read it
|
|
199
|
+
try {
|
|
200
|
+
fs.accessSync(resolved, fs.constants.R_OK);
|
|
201
|
+
} catch {
|
|
202
|
+
console.error(`[args] root directory is not readable by current user: ${resolved}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return resolved;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
// --------------------------------------------------
|
|
211
|
+
// MAPPING SYSTEM — unified, minimal, correct
|
|
212
|
+
// --------------------------------------------------
|
|
213
|
+
|
|
214
|
+
function resolveMappedPath(urlPath, MAPPINGS) {
|
|
215
|
+
|
|
216
|
+
if (!MAPPINGS || MAPPINGS.length === 0) return null;
|
|
217
|
+
|
|
218
|
+
let to = null;
|
|
219
|
+
|
|
220
|
+
// 1. FILE MAPPING
|
|
221
|
+
const exact = MAPPINGS.find(m => m.type === "file" && urlPath === m.from);
|
|
222
|
+
if (exact) {
|
|
223
|
+
to = exact.to;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 2. DIRECTORY MAPPING
|
|
227
|
+
if (!to) {
|
|
228
|
+
const dir = MAPPINGS.find(
|
|
229
|
+
m => m.type === "directory" &&
|
|
230
|
+
(urlPath === m.from || urlPath.startsWith(m.from + "/"))
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (dir) {
|
|
234
|
+
const relative = urlPath.slice(dir.from.length).replace(/^\/+/, "");
|
|
235
|
+
to = path.join(dir.to, relative);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 3. NOT MAPPED
|
|
240
|
+
if (!to) return null;
|
|
241
|
+
|
|
242
|
+
// 4. SINGLE buldng:// interception point
|
|
243
|
+
if (to.startsWith("buldng://")) {
|
|
244
|
+
const filename = to.slice("buldng://".length);
|
|
245
|
+
|
|
246
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
247
|
+
const __dirname = path.dirname(__filename);
|
|
248
|
+
|
|
249
|
+
return path.join(__dirname, filename);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 5. Normal project-relative mapping
|
|
253
|
+
return path.resolve(process.cwd(), to);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// --------------------------------------------------
|
|
257
|
+
// New file resolving system.
|
|
258
|
+
// --------------------------------------------------
|
|
259
|
+
function stripSuffix(url) {
|
|
260
|
+
const match = url.match(/^[^?#]*/);
|
|
261
|
+
const base = match[0];
|
|
262
|
+
const suffix = url.slice(base.length); // includes ? or #
|
|
263
|
+
return { base, suffix };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function rawResolve(base, ROOT, MAPPINGS) {
|
|
267
|
+
const mapped = resolveMappedPath(base, MAPPINGS);
|
|
268
|
+
|
|
269
|
+
const abs = mapped
|
|
270
|
+
? mapped
|
|
271
|
+
: path.join(ROOT, base);
|
|
272
|
+
|
|
273
|
+
if (!fs.existsSync(abs)) {
|
|
274
|
+
return { type: "missing", absPath: abs };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const stat = fs.statSync(abs);
|
|
278
|
+
|
|
279
|
+
if (stat.isFile()) return { type: "file", absPath: abs };
|
|
280
|
+
if (stat.isDirectory()) return { type: "dir", absPath: abs };
|
|
281
|
+
|
|
282
|
+
return { type: "missing", absPath: abs };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolve(url, ROOT, MAPPINGS) {
|
|
286
|
+
const { base, suffix } = stripSuffix(url);
|
|
287
|
+
const raw = rawResolve(base, ROOT, MAPPINGS);
|
|
288
|
+
|
|
289
|
+
// If URL explicitly asks for a directory, but rawResolve found a file,
|
|
290
|
+
// treat it as missing (forced directory → 404).
|
|
291
|
+
if (base.endsWith("/") && raw.type === "file") {
|
|
292
|
+
return {
|
|
293
|
+
type: "missing",
|
|
294
|
+
absPath: raw.absPath,
|
|
295
|
+
resolvedUrl: base + suffix
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 1. Exact file always wins
|
|
300
|
+
if (raw.type === "file") {
|
|
301
|
+
return {
|
|
302
|
+
type: "file",
|
|
303
|
+
absPath: raw.absPath,
|
|
304
|
+
resolvedUrl: base + suffix
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 2. Missing → 404 immediately
|
|
309
|
+
if (raw.type === "missing") {
|
|
310
|
+
return {
|
|
311
|
+
type: "missing",
|
|
312
|
+
absPath: raw.absPath,
|
|
313
|
+
resolvedUrl: base + suffix
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 3. Directory → try index.html
|
|
318
|
+
const indexPath = path.join(raw.absPath, "index.html");
|
|
319
|
+
|
|
320
|
+
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
|
|
321
|
+
return {
|
|
322
|
+
type: "file",
|
|
323
|
+
absPath: indexPath,
|
|
324
|
+
resolvedUrl: base + "/index.html" + suffix
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 4. Directory but no index.html → 404
|
|
329
|
+
return {
|
|
330
|
+
type: "missing",
|
|
331
|
+
absPath: indexPath,
|
|
332
|
+
resolvedUrl: base + suffix
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// --------------------------------------------------
|
|
337
|
+
// HOT RELOAD SYSTEM
|
|
338
|
+
// --------------------------------------------------
|
|
339
|
+
|
|
340
|
+
function loadManifest(state) {
|
|
341
|
+
const manifestPath = path.join(state.ROOT, "build-inputs.json");
|
|
342
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const list = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
346
|
+
return Array.isArray(list) ? list : null;
|
|
347
|
+
} catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function stopWatching(state) {
|
|
353
|
+
for (const w of state.watchers) w.close();
|
|
354
|
+
state.watchers = [];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function startWatching(files, state) {
|
|
358
|
+
stopWatching(state);
|
|
359
|
+
|
|
360
|
+
state.watchers = files.map(f => {
|
|
361
|
+
try {
|
|
362
|
+
return fs.watch(f, () => {
|
|
363
|
+
console.log(`[hot] change detected in ${f}`);
|
|
364
|
+
stopWatching(state);
|
|
365
|
+
rebuild(state);
|
|
366
|
+
});
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}).filter(Boolean);
|
|
371
|
+
|
|
372
|
+
return state.watchers.length;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function disableHot(state, reason) {
|
|
376
|
+
state.hotEnabled = false;
|
|
377
|
+
stopWatching(state);
|
|
378
|
+
console.warn(`[hot] disabled: ${reason}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function rebuild(state) {
|
|
382
|
+
if (!state.hotEnabled) return;
|
|
383
|
+
|
|
384
|
+
console.log("[hot] rebuilding…");
|
|
385
|
+
const child = spawn("npm", ["run", "build"], { stdio: "inherit" });
|
|
386
|
+
|
|
387
|
+
child.on("exit", (code) => {
|
|
388
|
+
if (code !== 0) {
|
|
389
|
+
disableHot(state, `build exited with code ${code}`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log("[hot] rebuild complete");
|
|
394
|
+
sendSSE(state, "reload");
|
|
395
|
+
|
|
396
|
+
const newFiles = loadManifest(state);
|
|
397
|
+
if (!newFiles) {
|
|
398
|
+
disableHot(state, "build-inputs.json missing after rebuild");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const watched = startWatching(newFiles, state);
|
|
403
|
+
if (watched === 0) {
|
|
404
|
+
disableHot(state, "no files watchable");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.log(`[hot] active — watching ${watched} files`);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function sendSSE(state, msg) {
|
|
413
|
+
for (const res of state.sseClients) {
|
|
414
|
+
res.write(`data: ${msg}\n\n`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function initHotReload(state) {
|
|
419
|
+
if (!state.HOT_REQUESTED) {
|
|
420
|
+
console.log("[hot] off — pass --hot to enable");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log("[hot] requested");
|
|
425
|
+
const files = loadManifest(state);
|
|
426
|
+
if (!files) {
|
|
427
|
+
console.warn("[hot] no build-inputs.json; disabled");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const watched = startWatching(files, state);
|
|
432
|
+
if (watched === 0) {
|
|
433
|
+
console.warn("[hot] no watchable files; disabled");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
state.hotEnabled = true;
|
|
438
|
+
console.log(`[hot] active — watching ${watched} files`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// --------------------------------------------------
|
|
442
|
+
// DEV ERRORS
|
|
443
|
+
// --------------------------------------------------
|
|
444
|
+
|
|
445
|
+
export function initDevErrors(state) {
|
|
446
|
+
if (!state.DEV_ERRORS) {
|
|
447
|
+
console.log("[dev-errors] off");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
451
|
+
const __dirname = path.dirname(__filename);
|
|
452
|
+
|
|
453
|
+
const devErrorsFile = path.join(__dirname, "dev-errors.js");
|
|
454
|
+
if (fs.existsSync(devErrorsFile)) {
|
|
455
|
+
console.log("[dev-errors] active");
|
|
456
|
+
} else {
|
|
457
|
+
state.DEV_ERRORS = false;
|
|
458
|
+
console.warn("[dev-errors] disabled (requested but missing:", devErrorsFile, ")");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// --------------------------------------------------
|
|
463
|
+
// REQUEST HANDLER (called from serve.js)
|
|
464
|
+
// --------------------------------------------------
|
|
465
|
+
|
|
466
|
+
export function handleHttpRequest(urlPath, req, state) {
|
|
467
|
+
// --------------------------------------------------
|
|
468
|
+
// HOT RELOAD (unchanged)
|
|
469
|
+
// --------------------------------------------------
|
|
470
|
+
if (state.hotEnabled && req.url === "/__hot") {
|
|
471
|
+
return {
|
|
472
|
+
status: 200,
|
|
473
|
+
headers: {
|
|
474
|
+
"Content-Type": "text/event-stream",
|
|
475
|
+
"Cache-Control": "no-cache",
|
|
476
|
+
"Connection": "keep-alive"
|
|
477
|
+
},
|
|
478
|
+
body: Buffer.from("\n"),
|
|
479
|
+
send(res) {
|
|
480
|
+
res.writeHead(this.status, this.headers);
|
|
481
|
+
res.write(this.body);
|
|
482
|
+
state.sseClients.push(res);
|
|
483
|
+
req.on("close", () => {
|
|
484
|
+
state.sseClients = state.sseClients.filter(r => r !== res);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const { absPath, type, resolvedUrl } = resolve(urlPath, state.ROOT, state.MAPPINGS);
|
|
491
|
+
|
|
492
|
+
// --------------------------------------------------
|
|
493
|
+
// 404 BEFORE FILE READ
|
|
494
|
+
// --------------------------------------------------
|
|
495
|
+
if (type === "missing") {
|
|
496
|
+
if (state.LOG_ALL) {
|
|
497
|
+
console.log(`[serve](404) ${urlPath} -> ${absPath}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
status: 404,
|
|
502
|
+
headers: {},
|
|
503
|
+
body: Buffer.from("Not found"),
|
|
504
|
+
send(res) {
|
|
505
|
+
res.writeHead(this.status);
|
|
506
|
+
res.end(this.body);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// --------------------------------------------------
|
|
512
|
+
// INTERNAL ERROR: resolve() MUST NOT RETURN DIR HERE
|
|
513
|
+
// --------------------------------------------------
|
|
514
|
+
if (type !== "file") {
|
|
515
|
+
console.error("INTERNAL ERROR: resolve() returned non-file type");
|
|
516
|
+
return {
|
|
517
|
+
status: 500,
|
|
518
|
+
headers: {},
|
|
519
|
+
body: Buffer.from("Internal server error"),
|
|
520
|
+
send(res) {
|
|
521
|
+
res.writeHead(this.status);
|
|
522
|
+
res.end(this.body);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// --------------------------------------------------
|
|
528
|
+
// FILE READ (ONLY FOR type === file)
|
|
529
|
+
// --------------------------------------------------
|
|
530
|
+
try {
|
|
531
|
+
let data = fs.readFileSync(absPath);
|
|
532
|
+
const ext = path.extname(absPath);
|
|
533
|
+
const mime = state.MIME[ext] || "application/octet-stream";
|
|
534
|
+
|
|
535
|
+
data = applyInjections(data, ext.slice(1), state);
|
|
536
|
+
|
|
537
|
+
if (state.LOG_ALL) {
|
|
538
|
+
console.log(`[serve](200) ${urlPath} -> ${absPath}`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
status: 200,
|
|
543
|
+
headers: { "Content-Type": mime },
|
|
544
|
+
body: data,
|
|
545
|
+
send(res) {
|
|
546
|
+
res.writeHead(this.status, this.headers);
|
|
547
|
+
res.end(this.body);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.error("FILE READ ERROR:", err);
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
status: 500,
|
|
556
|
+
headers: {},
|
|
557
|
+
body: Buffer.from("Internal server error"),
|
|
558
|
+
send(res) {
|
|
559
|
+
res.writeHead(this.status);
|
|
560
|
+
res.end(this.body);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
|