@pajh/buldng 0.0.5 → 0.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pajh/buldng",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "main": "./src/buldng-lib.js",
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  // buldng-files.js
2
2
  // file handling functions for buldng
3
- import { getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, looksLikeFilename, looksLikeHTML,
3
+ import { getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, trackInput, looksLikeFilename, looksLikeHTML,
4
4
  looksLikeCSS, looksLikeJS } from "./buldng-validate.js";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
@@ -49,10 +49,11 @@ export function deepCopy(from, to) {
49
49
  copyRecursive(path.join(src, entry), path.join(dst, entry));
50
50
  }
51
51
  } else {
52
+ trackInput(src);
52
53
  fs.mkdirSync(path.dirname(dst), { recursive: true });
53
54
  fs.copyFileSync(src, dst);
54
55
  }
55
56
  }
56
57
 
57
58
  copyRecursive(absFrom, absTo);
58
- }
59
+ }
package/src/buldng-lib.js CHANGED
@@ -2,11 +2,12 @@
2
2
  import esbuild from "esbuild";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
5
+ import fs from "node:fs";
5
6
  import { readFileSync } from "node:fs";
6
7
  import { createRequire } from "node:module";
7
8
  import { WorkFile } from "./workfile.js";
8
9
  import {
9
- getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, looksLikeFilename, looksLikeHTML,
10
+ getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, trackInput, looksLikeFilename, looksLikeHTML,
10
11
  looksLikeCSS, looksLikeJS, ensureLeadingSlash, ensureWorkFile
11
12
  } from "./buldng-validate.js";
12
13
  import { file, literal, wrap, append, replace, compile, materialise }
@@ -15,7 +16,8 @@ export { file, literal, wrap, append, replace, compile, materialise }
15
16
  from "./buldng-ops.js";
16
17
  export {
17
18
  assertSourceRead, assertDestWrite, setSource, setDest, looksLikeFilename,
18
- looksLikeHTML, looksLikeCSS, looksLikeJS, ensureWorkFile, ensureLeadingSlash
19
+ looksLikeHTML, looksLikeCSS, looksLikeJS, ensureWorkFile, ensureLeadingSlash,
20
+ trackInput
19
21
  } from "./buldng-validate.js";
20
22
  import { task, parallel, finalise } from "./buldng-async.js";
21
23
  import { typescriptCheck } from "./buldng-typescript.js";
@@ -52,7 +54,8 @@ export default {
52
54
  buldngHTMLWrapper,
53
55
  reg,
54
56
  printHeader,
55
- typescriptCheck
57
+ typescriptCheck,
58
+ writeBuildInputsManifest
56
59
  };
57
60
 
58
61
  const here = dirname(fileURLToPath(import.meta.url));
@@ -78,13 +81,41 @@ export function header() {
78
81
  }
79
82
 
80
83
  // --------------------------------------------------
81
- // build-inputs.json manifest
84
+ // Development input manifest
82
85
  // --------------------------------------------------
83
- process.on("exit", () => {
84
- if (process.env.BULDNG_HOT !== "1") return;
85
- let DEST = getDest();
86
- let INPUTS = getInputs();
87
- const manifest = Array.from(INPUTS);
88
- const out = path.join(DEST, "build-inputs.json");
86
+ export function writeBuildInputsManifest(additionalInputs = []) {
87
+ for (const input of additionalInputs) {
88
+ trackInput(input);
89
+ }
90
+
91
+ const manifest = Array.from(getInputs())
92
+ .filter(input => fs.existsSync(input) && fs.statSync(input).isFile())
93
+ .sort();
94
+ const out = assertDestWrite("build-inputs.json");
95
+
89
96
  fs.writeFileSync(out, JSON.stringify(manifest, null, 2));
97
+ fs.chmodSync(out, 0o444);
98
+ console.log(`build-inputs.json: ${manifest.length} files recorded`);
99
+
100
+ return manifest;
101
+ }
102
+
103
+ function requestedBuildInputsManifest() {
104
+ return process.argv.slice(2).includes("--manifest");
105
+ }
106
+
107
+ function projectRootInputs() {
108
+ const buildScript = process.argv[1];
109
+ const projectRoot = dirname(buildScript);
110
+ return [
111
+ buildScript,
112
+ join(projectRoot, "package.json"),
113
+ join(projectRoot, "tsconfig.json"),
114
+ join(projectRoot, "safe_clean.sh")
115
+ ].filter(input => fs.existsSync(input));
116
+ }
117
+
118
+ process.on("exit", exitCode => {
119
+ if (exitCode !== 0 || !requestedBuildInputsManifest()) return;
120
+ writeBuildInputsManifest(projectRootInputs());
90
121
  });
package/src/buldng-ops.js CHANGED
@@ -5,7 +5,7 @@ import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import esbuild from "esbuild";
7
7
 
8
- import { assertSourceRead, assertDestWrite, looksLikeFilename, ensureWorkFile } from "./buldng-validate.js";
8
+ import { assertSourceRead, assertDestWrite, trackInput, looksLikeFilename, ensureWorkFile } from "./buldng-validate.js";
9
9
  export const OPS = {
10
10
  file: op_file,
11
11
  literal: op_literal,
@@ -107,10 +107,13 @@ export function op_compile(config, children) {
107
107
  const result = esbuild.buildSync({
108
108
  entryPoints: [entry],
109
109
  bundle: true,
110
+ metafile: true,
110
111
  ...compileOptions,
111
112
  write: false
112
113
  });
113
114
 
115
+ trackEsbuildInputs(result);
116
+
114
117
  if (result.errors?.length) {
115
118
  throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
116
119
  }
@@ -121,16 +124,19 @@ export function op_compile(config, children) {
121
124
  // Otherwise compile from stdin
122
125
  const srcText = child.execute();
123
126
 
124
- const result = esbuild.buildSync({
127
+ const result = esbuild.buildSync({
125
128
  stdin: {
126
129
  contents: srcText,
127
130
  resolveDir: process.cwd(),
128
131
  sourcefile: "input.ts"
129
132
  },
130
- bundle: true,
131
- ...compileOptions,
132
- write: false
133
- });
133
+ bundle: true,
134
+ metafile: true,
135
+ ...compileOptions,
136
+ write: false
137
+ });
138
+
139
+ trackEsbuildInputs(result);
134
140
 
135
141
  if (result.errors?.length) {
136
142
  throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
@@ -139,6 +145,15 @@ export function op_compile(config, children) {
139
145
  return result.outputFiles[0].text;
140
146
  }
141
147
 
148
+ function trackEsbuildInputs(result) {
149
+ for (const input of Object.keys(result.metafile?.inputs ?? {})) {
150
+ const abs = path.resolve(input);
151
+ if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
152
+ trackInput(abs);
153
+ }
154
+ }
155
+ }
156
+
142
157
  // --------------------------------------------------
143
158
  // MATERIALISE — write file if identical or new
144
159
  // --------------------------------------------------
@@ -158,10 +158,17 @@ export function getInputs() {
158
158
  }
159
159
 
160
160
  export function trackInput(absPath) {
161
- if (!SRC || !DEST) return;
162
- if (absPath.startsWith(SRC) && !absPath.startsWith(DEST)) {
163
- INPUTS.add(absPath);
161
+ const abs = path.resolve(absPath);
162
+
163
+ if (DEST) {
164
+ const relativeToDest = path.relative(DEST, abs);
165
+ const isInsideDest = relativeToDest === "" ||
166
+ (!relativeToDest.startsWith("..") && !path.isAbsolute(relativeToDest));
167
+
168
+ if (isInsideDest) return;
164
169
  }
170
+
171
+ INPUTS.add(abs);
165
172
  }
166
173
 
167
174
  export function looksLikeFilename(s) {
@@ -204,4 +211,4 @@ export function ensureWorkFile(x, fieldName = "value") {
204
211
  }
205
212
 
206
213
  throw new Error(`${fieldName} must be WorkFile or string`);
207
- }
214
+ }
package/src/serve-lib.js CHANGED
@@ -6,8 +6,8 @@ import { exec, spawn } from "node:child_process";
6
6
 
7
7
  export const VALID_FLAGS = {
8
8
  "log": { type: "boolean" },
9
- "hot": { type: "boolean" },
10
9
  "dev-errors": { type: "boolean" },
10
+ "no-hot": { type: "boolean" },
11
11
  "map": { type: "value" },
12
12
  "root": { type: "value" },
13
13
  "port": { type: "value" },
@@ -18,15 +18,15 @@ export const VALID_FLAGS = {
18
18
  // STATE CREATION
19
19
  // --------------------------------------------------
20
20
 
21
- export function createState({ ROOT, PORT, MAPPINGS, MIME, LOG_ALL, HOT_REQUESTED, DEV_ERRORS }) {
21
+ export function createState({ ROOT, PORT, MAPPINGS, MIME, LOG_ALL, DEV_ERRORS, NO_HOT }) {
22
22
  return {
23
23
  ROOT,
24
24
  PORT,
25
25
  MAPPINGS,
26
26
  MIME,
27
27
  LOG_ALL,
28
- HOT_REQUESTED,
29
28
  DEV_ERRORS,
29
+ NO_HOT,
30
30
  hotEnabled: false,
31
31
  injections: { html: [] },
32
32
  running: false,
@@ -354,6 +354,16 @@ function stopWatching(state) {
354
354
  state.watchers = [];
355
355
  }
356
356
 
357
+ export function stopHotReload(state) {
358
+ state.hotEnabled = false;
359
+ stopWatching(state);
360
+
361
+ for (const response of state.sseClients) {
362
+ if (!response.writableEnded) response.end();
363
+ }
364
+ state.sseClients = [];
365
+ }
366
+
357
367
  function startWatching(files, state) {
358
368
  stopWatching(state);
359
369
 
@@ -382,7 +392,7 @@ function rebuild(state) {
382
392
  if (!state.hotEnabled) return;
383
393
 
384
394
  console.log("[hot] rebuilding…");
385
- const child = spawn("npm", ["run", "build"], { stdio: "inherit" });
395
+ const child = spawn("npm", ["run", "build:dev"], { stdio: "inherit" });
386
396
 
387
397
  child.on("exit", (code) => {
388
398
  if (code !== 0) {
@@ -405,7 +415,7 @@ function rebuild(state) {
405
415
  return;
406
416
  }
407
417
 
408
- console.log(`[hot] activewatching ${watched} files`);
418
+ console.log(`[hot] hot reloading enabled — ${watched} files being monitored`);
409
419
  });
410
420
  }
411
421
 
@@ -416,26 +426,25 @@ function sendSSE(state, msg) {
416
426
  }
417
427
 
418
428
  export function initHotReload(state) {
419
- if (!state.HOT_REQUESTED) {
420
- console.log("[hot] off pass --hot to enable");
429
+ if (state.NO_HOT) {
430
+ console.log("[hot] disabled by --no-hot");
421
431
  return;
422
432
  }
423
433
 
424
- console.log("[hot] requested");
425
434
  const files = loadManifest(state);
426
435
  if (!files) {
427
- console.warn("[hot] no build-inputs.json; disabled");
436
+ console.log("[hot] no manifest found — hot reloading disabled");
428
437
  return;
429
438
  }
430
439
 
431
440
  const watched = startWatching(files, state);
432
441
  if (watched === 0) {
433
- console.warn("[hot] no watchable files; disabled");
442
+ console.log("[hot] manifest has no watchable files — hot reloading disabled");
434
443
  return;
435
444
  }
436
445
 
437
446
  state.hotEnabled = true;
438
- console.log(`[hot] activewatching ${watched} files`);
447
+ console.log(`[hot] hot reloading enabled — ${watched} files being monitored`);
439
448
  }
440
449
 
441
450
  // --------------------------------------------------
@@ -563,5 +572,3 @@ try {
563
572
  }
564
573
 
565
574
  }
566
-
567
-
package/src/serve.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  registerInject,
16
16
  sanitiseRoot,
17
17
  sanitisePort,
18
+ stopHotReload,
18
19
  local,
19
20
  ARGS
20
21
  } from "./serve-lib.js";
@@ -92,8 +93,8 @@ function init() {
92
93
  MAPPINGS: [],
93
94
  MIME: MIME,
94
95
  LOG_ALL: ARGS.get("log"),
95
- HOT_REQUESTED: ARGS.get("hot"),
96
- DEV_ERRORS: ARGS.get("dev-errors")
96
+ DEV_ERRORS: ARGS.get("dev-errors"),
97
+ NO_HOT: ARGS.get("no-hot")
97
98
  });
98
99
 
99
100
  loadMappings(state);
@@ -147,18 +148,17 @@ function serve(state) {
147
148
  response.send(res);
148
149
  });
149
150
 
150
- process.on("SIGTERM", () => {
151
- // clean shutdown logic
152
- server.close(() => {
153
- process.exit(0);
154
- });
155
- });
156
-
157
- // Optional graceful shutdown
158
- process.on("SIGINT", () => {
151
+ let shuttingDown = false;
152
+ const shutdown = () => {
153
+ if (shuttingDown) return;
154
+ shuttingDown = true;
159
155
  teardown(state);
160
156
  server.close(() => process.exit(0));
161
- });
157
+ server.closeAllConnections();
158
+ };
159
+
160
+ process.once("SIGTERM", shutdown);
161
+ process.once("SIGINT", shutdown);
162
162
 
163
163
  server.listen(state.PORT, () => {
164
164
  console.log(`[serve] root: ${state.ROOT} at http://localhost:${state.PORT}`);
@@ -176,6 +176,7 @@ function serve(state) {
176
176
 
177
177
  function teardown(state) {
178
178
  state.running = false;
179
+ stopHotReload(state);
179
180
  console.log("Server shutting down.");
180
181
  }
181
182
 
@@ -190,4 +191,4 @@ function main() {
190
191
 
191
192
  }
192
193
 
193
- main();
194
+ main();