@junejs/cli 0.0.49 → 0.0.51-dev.0

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/watch.ts +37 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junejs/cli",
3
- "version": "0.0.49",
3
+ "version": "0.0.51-dev.0",
4
4
  "description": "The `june` CLI — dev / build / deploy / gen / info. A thin bin over @junejs/server.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,8 +20,8 @@
20
20
  "typecheck": "tsc --noEmit"
21
21
  },
22
22
  "dependencies": {
23
- "@junejs/core": ">=0.0.46 <0.1.0",
24
- "@junejs/server": ">=0.0.45 <0.1.0"
23
+ "@junejs/core": ">=0.1.0-dev.0",
24
+ "@junejs/server": ">=0.1.0-dev.0"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
package/src/watch.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  // HMR belongs to the native runtime track; this is the Bun/Node host's story.
6
6
 
7
7
  import { spawn, type ChildProcess } from "node:child_process";
8
- import { watch } from "node:fs";
9
- import { sep } from "node:path";
8
+ import { existsSync, watch } from "node:fs";
9
+ import { join, relative, resolve, sep } from "node:path";
10
10
 
11
11
  const IGNORED_DIRS = new Set(["node_modules", ".git", "dist", ".june"]);
12
12
 
@@ -42,10 +42,10 @@ export function superviseDev(root: string): undefined {
42
42
  });
43
43
  };
44
44
 
45
- const restart = async (file: string) => {
45
+ const restart = async (file: string, contentChanged: boolean) => {
46
46
  if (restarting) return; // an edit mid-restart is picked up by the next save
47
47
  restarting = true;
48
- if (file.startsWith(`content${sep}`)) {
48
+ if (contentChanged) {
49
49
  // The frozen manifest must be fresh BEFORE the new server boots.
50
50
  try {
51
51
  const { generateContent } = await import("@junejs/server");
@@ -80,15 +80,44 @@ export function superviseDev(root: string): undefined {
80
80
  c.kill();
81
81
  };
82
82
 
83
- let pending: string | undefined;
83
+ // Debounced scheduling shared by the root watcher and any content-source watchers. `content`
84
+ // ORs across coalesced events, so an app edit landing right after a content edit can't cancel
85
+ // the pending regen.
86
+ let pending: { file: string; content: boolean } | undefined;
84
87
  let timer: ReturnType<typeof setTimeout> | undefined;
88
+ const schedule = (file: string, content: boolean) => {
89
+ pending = { file, content: content || (pending?.content ?? false) };
90
+ clearTimeout(timer);
91
+ timer = setTimeout(() => void restart(pending!.file, pending!.content), 80);
92
+ };
85
93
  watch(root, { recursive: true }, (_event, file) => {
86
94
  if (!file || ignoredPath(file)) return;
87
- pending = file;
88
- clearTimeout(timer);
89
- timer = setTimeout(() => void restart(pending!), 80);
95
+ schedule(file, file.startsWith(`content${sep}`));
90
96
  });
91
97
 
98
+ // Extra content sources (config `content.sources`) live OUTSIDE the app root — fs.watch(root)
99
+ // can't see them, so each gets its own watcher; any change there is a content change (regen +
100
+ // restart). Config is loaded tolerantly and async: if it can't load yet (a generated config
101
+ // importing app/_content.ts before the first freeze), sources simply aren't watched this run —
102
+ // the next `june dev` picks them up. A config edit that CHANGES the source list also needs a
103
+ // dev rerun (the supervisor process caches the config module).
104
+ void (async () => {
105
+ try {
106
+ const { loadJuneConfig } = await import("@junejs/server");
107
+ const config = await loadJuneConfig(root);
108
+ for (const s of config.content?.sources ?? []) {
109
+ const dir = resolve(root, s.dir);
110
+ if (!existsSync(dir)) continue; // gen fails loudly on this; nothing to watch here
111
+ watch(dir, { recursive: true }, (_event, file) => {
112
+ if (!file || ignoredPath(file)) return;
113
+ schedule(relative(root, join(dir, file)), true);
114
+ });
115
+ }
116
+ } catch {
117
+ /* config not loadable yet — see above */
118
+ }
119
+ })();
120
+
92
121
  for (const sig of ["SIGINT", "SIGTERM"] as const) {
93
122
  process.on(sig, () => {
94
123
  child?.kill(sig);