@webjsdev/cli 0.10.16 → 0.10.17
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/bin/webjs.js +27 -29
- package/lib/dev-supervisor.js +61 -0
- package/package.json +1 -1
package/bin/webjs.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn } from 'node:child_process';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { checkNodeInline, nodeInlineMessage } from '../lib/node-preflight.js';
|
|
6
6
|
import { loadAppEnv, resolvePort } from '../lib/port.js';
|
|
7
|
+
import { planDevSupervisor } from '../lib/dev-supervisor.js';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
@@ -39,7 +40,8 @@ if (cmd !== 'help' && cmd !== undefined) {
|
|
|
39
40
|
const TEMPLATES = ['full-stack', 'api', 'saas'];
|
|
40
41
|
|
|
41
42
|
const USAGE = `webjs commands:
|
|
42
|
-
webjs dev [--port 8080]
|
|
43
|
+
webjs dev [--port 8080] [--no-hot] Start dev server with live reload
|
|
44
|
+
(--no-hot: run in-process, no hot-reload supervisor)
|
|
43
45
|
webjs start [--port 8080] Start production server (serves source directly, no build step)
|
|
44
46
|
webjs test [--server|--browser] Run server + browser tests
|
|
45
47
|
webjs check [--json] Run correctness checks on the app (--json emits structured violations)
|
|
@@ -83,7 +85,8 @@ async function main() {
|
|
|
83
85
|
}
|
|
84
86
|
switch (cmd) {
|
|
85
87
|
case 'dev': {
|
|
86
|
-
// If we're already inside the
|
|
88
|
+
// If we're already inside the reload child (node --watch or bun --hot),
|
|
89
|
+
// start the server directly.
|
|
87
90
|
if (process.env.__WEBJS_DEV_CHILD === '1') {
|
|
88
91
|
const { startServer } = await import('@webjsdev/server');
|
|
89
92
|
// Load `.env` BEFORE resolving the port so a `PORT` set there is in
|
|
@@ -104,36 +107,31 @@ async function main() {
|
|
|
104
107
|
const hint = prismaDevHint(process.cwd());
|
|
105
108
|
if (hint) console.error(hint);
|
|
106
109
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
// because Node caches ESM by URL with no public invalidation API.
|
|
112
|
-
// Build watch paths from directories that exist in the project.
|
|
110
|
+
// Decide how to run: in-process (`--no-hot`), or re-exec'd under the host
|
|
111
|
+
// runtime's hot-reload supervisor (`node --watch` on Node, `bun --hot` on
|
|
112
|
+
// Bun, #514). The branch logic lives in the pure `planDevSupervisor` so it
|
|
113
|
+
// is unit-testable without spawning a process.
|
|
113
114
|
const { existsSync } = await import('node:fs');
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
const plan = planDevSupervisor({
|
|
116
|
+
isBun: !!process.versions.bun,
|
|
117
|
+
argv: process.argv.slice(1),
|
|
118
|
+
noHot: rest.includes('--no-hot'),
|
|
119
|
+
exists: (p) => existsSync(p),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (plan.mode === 'inline') {
|
|
123
|
+
const { startServer } = await import('@webjsdev/server');
|
|
124
|
+
loadAppEnv(process.cwd());
|
|
125
|
+
const port = resolvePort(flag(rest, '--port'));
|
|
126
|
+
await startServer({ appDir: process.cwd(), port, dev: true });
|
|
127
|
+
break;
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
const child = spawn(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...watchPaths,
|
|
129
|
-
...process.argv.slice(1),
|
|
130
|
-
],
|
|
131
|
-
{
|
|
132
|
-
stdio: 'inherit',
|
|
133
|
-
cwd: process.cwd(),
|
|
134
|
-
env: { ...process.env, __WEBJS_DEV_CHILD: '1' },
|
|
135
|
-
},
|
|
136
|
-
);
|
|
130
|
+
const child = spawn(process.execPath, plan.args, {
|
|
131
|
+
stdio: 'inherit',
|
|
132
|
+
cwd: process.cwd(),
|
|
133
|
+
env: { ...process.env, __WEBJS_DEV_CHILD: '1' },
|
|
134
|
+
});
|
|
137
135
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
138
136
|
break;
|
|
139
137
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-server reload supervisor planning for `webjs dev` (issue #514).
|
|
3
|
+
*
|
|
4
|
+
* `webjs dev` re-execs itself under the host runtime's hot-reload supervisor so
|
|
5
|
+
* an edit to a transitively-imported module (an action, query, component, util)
|
|
6
|
+
* takes effect without a manual restart. Both runtimes cache ES modules by
|
|
7
|
+
* resolved URL with no public invalidation API, so the dev re-import in
|
|
8
|
+
* `@webjsdev/server`'s `dev.js` relies on the runtime's own file-watching cache
|
|
9
|
+
* invalidation:
|
|
10
|
+
*
|
|
11
|
+
* - **Node** has no in-place module-cache eviction, so it re-execs under
|
|
12
|
+
* `node --watch`, which RESTARTS the process on a file change (a fresh ESM
|
|
13
|
+
* cache each time). The dev re-import additionally appends a `?t=` cache-bust
|
|
14
|
+
* query that Node honours between restarts.
|
|
15
|
+
* - **Bun** keys its module cache by path and IGNORES that `?t=` query, so the
|
|
16
|
+
* `node --watch` model does not transfer: without help a re-imported module
|
|
17
|
+
* stays STALE on Bun (the #514 bug). Bun's `--hot` invalidates loaded modules
|
|
18
|
+
* on a file change WITHOUT restarting the process, which is exactly what the
|
|
19
|
+
* dev re-import needs; `Bun.serve` is reused across hot reloads, so the
|
|
20
|
+
* listener is not duplicated. `--hot` auto-watches every loaded file, so the
|
|
21
|
+
* node `--watch-path` flags do not apply (and are not Bun flags).
|
|
22
|
+
*
|
|
23
|
+
* This pure planner returns the spawn decision so the bin stays a thin shell and
|
|
24
|
+
* the branch logic is unit-testable without spawning a process.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Plan how `webjs dev` runs its server.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} opts
|
|
31
|
+
* @param {boolean} opts.isBun Whether the host runtime is Bun (`process.versions.bun`).
|
|
32
|
+
* @param {string[]} opts.argv `process.argv.slice(1)` (the script path followed by its args), forwarded to the child verbatim.
|
|
33
|
+
* @param {boolean} opts.noHot Whether `--no-hot` was passed (opt out of the supervisor entirely).
|
|
34
|
+
* @param {(path: string) => boolean} opts.exists Existence check for the Node `--watch-path` targets (relative to cwd). Unused on Bun.
|
|
35
|
+
* @returns {{ mode: 'inline' } | { mode: 'spawn', args: string[] }}
|
|
36
|
+
* `inline` runs the server in this process (no reload watcher); `spawn`
|
|
37
|
+
* re-execs `process.execPath` with `args` and `__WEBJS_DEV_CHILD=1`.
|
|
38
|
+
*/
|
|
39
|
+
export function planDevSupervisor({ isBun, argv, noHot, exists }) {
|
|
40
|
+
// `--no-hot` opts out of the reload supervisor on either runtime: run the dev
|
|
41
|
+
// server in THIS process with no watcher. Degraded dev (a deep-import edit
|
|
42
|
+
// needs a manual restart) but useful under an external process manager or a
|
|
43
|
+
// debugger that wants a single, un-re-exec'd process.
|
|
44
|
+
if (noHot) return { mode: 'inline' };
|
|
45
|
+
|
|
46
|
+
if (isBun) return { mode: 'spawn', args: ['--hot', ...argv] };
|
|
47
|
+
|
|
48
|
+
// Node: re-exec under `node --watch`, watching the project dirs/files that
|
|
49
|
+
// exist. `--watch-preserve-output` keeps prior logs across a restart.
|
|
50
|
+
const watchPaths = [];
|
|
51
|
+
for (const dir of ['app', 'components', 'modules', 'lib', 'actions']) {
|
|
52
|
+
if (exists(dir)) watchPaths.push('--watch-path', dir);
|
|
53
|
+
}
|
|
54
|
+
for (const f of ['middleware.ts', 'middleware.js']) {
|
|
55
|
+
if (exists(f)) watchPaths.push('--watch-path', f);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
mode: 'spawn',
|
|
59
|
+
args: ['--watch', '--watch-preserve-output', ...watchPaths, ...argv],
|
|
60
|
+
};
|
|
61
|
+
}
|