@jami-studio/core 0.92.11 → 0.92.13
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/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/workspace-dev.ts +1816 -1557
- package/corpus/core/src/deploy/build.ts +3797 -3754
- package/corpus/core/src/vite/client.ts +1 -0
- package/dist/cli/workspace-dev.d.ts +43 -0
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +299 -74
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/deploy/build.d.ts +10 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +694 -656
- package/dist/deploy/build.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
3
4
|
import http from "node:http";
|
|
4
5
|
import { type WorkspaceAppAudience } from "../shared/workspace-app-audience.js";
|
|
5
6
|
export interface WorkspaceApp {
|
|
@@ -25,6 +26,23 @@ export interface WorkspaceApp {
|
|
|
25
26
|
outputTail?: string;
|
|
26
27
|
installing?: boolean;
|
|
27
28
|
installAttempted?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Built-mode lifecycle. `devMode` marks an app that has been promoted to
|
|
31
|
+
* the vite dev server for the rest of the session (source changed, build
|
|
32
|
+
* failed, or no build could be produced). `building` marks a production
|
|
33
|
+
* build in flight as the app's prep child. `buildAttempted` prevents
|
|
34
|
+
* rebuild loops; `buildChecked` caches the once-per-session staleness scan.
|
|
35
|
+
*/
|
|
36
|
+
devMode?: boolean;
|
|
37
|
+
building?: boolean;
|
|
38
|
+
buildAttempted?: boolean;
|
|
39
|
+
buildChecked?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Set before an intentional kill (promotion to dev mode) so the exit
|
|
42
|
+
* handler respawns immediately instead of treating it as a crash.
|
|
43
|
+
*/
|
|
44
|
+
expectedExit?: boolean;
|
|
45
|
+
promoteWatcher?: fs.FSWatcher;
|
|
28
46
|
/**
|
|
29
47
|
* Set true once we've successfully connected to the upstream. After that we
|
|
30
48
|
* skip the readiness probe on every request; the child server stays
|
|
@@ -55,6 +73,31 @@ export interface WorkspaceDevHandle {
|
|
|
55
73
|
}
|
|
56
74
|
export declare function isWorkspaceWatcherLimitError(err: Pick<NodeJS.ErrnoException, "code">): boolean;
|
|
57
75
|
export declare function shouldEagerStartWorkspaceApps(args?: string[], env?: NodeJS.ProcessEnv): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Mixed-mode gateway: when enabled (--built / WORKSPACE_BUILT=1), each app
|
|
78
|
+
* boots from its prebuilt production server (`.output/server/index.mjs`,
|
|
79
|
+
* instant, prod-shaped) instead of a vite dev server. The gateway builds an
|
|
80
|
+
* app first when no fresh build exists, and automatically promotes an app to
|
|
81
|
+
* the vite dev server the moment one of its source files changes — so the
|
|
82
|
+
* app being edited gets hot reload while every other app stays on built
|
|
83
|
+
* output. Nothing is manual: editing is the switch.
|
|
84
|
+
*/
|
|
85
|
+
export declare function shouldRunBuiltWorkspaceApps(args?: string[], env?: NodeJS.ProcessEnv): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Whether a path relative to an app dir counts as "source" for built-mode
|
|
88
|
+
* staleness checks and dev-server promotion. Excludes build outputs, data,
|
|
89
|
+
* dependency trees, and every dot-directory/dot-file (`.output`,
|
|
90
|
+
* `.deploy-tmp`, `.react-router`, `.env`, ...) — an `.env` edit needs a
|
|
91
|
+
* restart, not a permanent promotion to vite.
|
|
92
|
+
*/
|
|
93
|
+
export declare function isBuiltModeSourcePath(relPath: string): boolean;
|
|
94
|
+
export declare function builtWorkspaceAppServerEntry(appDir: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Newest mtime across an app's source files (build outputs, data, and
|
|
97
|
+
* node_modules excluded). Used once per session per app to decide whether
|
|
98
|
+
* the prebuilt server is stale and needs a rebuild before serving.
|
|
99
|
+
*/
|
|
100
|
+
export declare function newestBuiltModeSourceMtimeMs(dir: string, relBase?: string): number;
|
|
58
101
|
/**
|
|
59
102
|
* Whether the gateway should spawn the rest of the apps in the background
|
|
60
103
|
* after the default app boots. Lazy spawn already covers correctness — this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-dev.d.ts","sourceRoot":"","sources":["../../src/cli/workspace-dev.ts"],"names":[],"mappings":";AACA,OAAO,EAAgB,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-dev.d.ts","sourceRoot":"","sources":["../../src/cli/workspace-dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,95 @@ export function shouldEagerStartWorkspaceApps(args = [], env = process.env) {
|
|
|
40
40
|
env.WORKSPACE_EAGER === "1" ||
|
|
41
41
|
env.WORKSPACE_EAGER === "true");
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Mixed-mode gateway: when enabled (--built / WORKSPACE_BUILT=1), each app
|
|
45
|
+
* boots from its prebuilt production server (`.output/server/index.mjs`,
|
|
46
|
+
* instant, prod-shaped) instead of a vite dev server. The gateway builds an
|
|
47
|
+
* app first when no fresh build exists, and automatically promotes an app to
|
|
48
|
+
* the vite dev server the moment one of its source files changes — so the
|
|
49
|
+
* app being edited gets hot reload while every other app stays on built
|
|
50
|
+
* output. Nothing is manual: editing is the switch.
|
|
51
|
+
*/
|
|
52
|
+
export function shouldRunBuiltWorkspaceApps(args = [], env = process.env) {
|
|
53
|
+
if (args.includes("--no-built"))
|
|
54
|
+
return false;
|
|
55
|
+
if (env.WORKSPACE_BUILT === "0" || env.WORKSPACE_BUILT === "false") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return (args.includes("--built") ||
|
|
59
|
+
env.WORKSPACE_BUILT === "1" ||
|
|
60
|
+
env.WORKSPACE_BUILT === "true");
|
|
61
|
+
}
|
|
62
|
+
const BUILT_MODE_IGNORED_SEGMENTS = new Set([
|
|
63
|
+
"node_modules",
|
|
64
|
+
"build",
|
|
65
|
+
"dist",
|
|
66
|
+
"data",
|
|
67
|
+
"media",
|
|
68
|
+
"coverage",
|
|
69
|
+
"test-results",
|
|
70
|
+
"playwright-report",
|
|
71
|
+
]);
|
|
72
|
+
const BUILT_MODE_IGNORED_FILE_PATTERN = /\.(log|tmp|db|db-wal|db-shm|sqlite|sqlite-wal|sqlite-shm)$/i;
|
|
73
|
+
/**
|
|
74
|
+
* Whether a path relative to an app dir counts as "source" for built-mode
|
|
75
|
+
* staleness checks and dev-server promotion. Excludes build outputs, data,
|
|
76
|
+
* dependency trees, and every dot-directory/dot-file (`.output`,
|
|
77
|
+
* `.deploy-tmp`, `.react-router`, `.env`, ...) — an `.env` edit needs a
|
|
78
|
+
* restart, not a permanent promotion to vite.
|
|
79
|
+
*/
|
|
80
|
+
export function isBuiltModeSourcePath(relPath) {
|
|
81
|
+
if (!relPath)
|
|
82
|
+
return false;
|
|
83
|
+
const segments = relPath.split(/[\\/]/).filter(Boolean);
|
|
84
|
+
if (segments.length === 0)
|
|
85
|
+
return false;
|
|
86
|
+
for (const segment of segments) {
|
|
87
|
+
if (segment.startsWith("."))
|
|
88
|
+
return false;
|
|
89
|
+
if (BUILT_MODE_IGNORED_SEGMENTS.has(segment))
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return !BUILT_MODE_IGNORED_FILE_PATTERN.test(relPath);
|
|
93
|
+
}
|
|
94
|
+
export function builtWorkspaceAppServerEntry(appDir) {
|
|
95
|
+
return path.join(appDir, ".output", "server", "index.mjs");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Newest mtime across an app's source files (build outputs, data, and
|
|
99
|
+
* node_modules excluded). Used once per session per app to decide whether
|
|
100
|
+
* the prebuilt server is stale and needs a rebuild before serving.
|
|
101
|
+
*/
|
|
102
|
+
export function newestBuiltModeSourceMtimeMs(dir, relBase = "") {
|
|
103
|
+
let entries;
|
|
104
|
+
try {
|
|
105
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
let newest = 0;
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
if (entry.name.startsWith(".") ||
|
|
115
|
+
BUILT_MODE_IGNORED_SEGMENTS.has(entry.name)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
newest = Math.max(newest, newestBuiltModeSourceMtimeMs(path.join(dir, entry.name), rel));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!entry.isFile() || !isBuiltModeSourcePath(rel))
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
newest = Math.max(newest, fs.statSync(path.join(dir, entry.name)).mtimeMs);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// File vanished mid-scan — ignore.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return newest;
|
|
131
|
+
}
|
|
43
132
|
/**
|
|
44
133
|
* Whether the gateway should spawn the rest of the apps in the background
|
|
45
134
|
* after the default app boots. Lazy spawn already covers correctness — this
|
|
@@ -413,76 +502,78 @@ function renderStartingApp(app) {
|
|
|
413
502
|
? `The workspace gateway will retry in ${Math.max(1, Math.ceil((failure.nextRetryAt - Date.now()) / 1_000))}s. Fix the error below or stop the server with Ctrl+C.`
|
|
414
503
|
: app.installing
|
|
415
504
|
? "The workspace gateway is installing this app's dependencies before starting it."
|
|
416
|
-
:
|
|
505
|
+
: app.building
|
|
506
|
+
? "The workspace gateway is building this app's production server before serving it."
|
|
507
|
+
: "The workspace gateway is waking this app's dev server.";
|
|
417
508
|
const failureOutput = failure?.output.trim();
|
|
418
|
-
return `<!doctype html>
|
|
419
|
-
<html>
|
|
420
|
-
<head>
|
|
421
|
-
<meta charset="utf-8" />
|
|
422
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
423
|
-
<meta http-equiv="refresh" content="${refreshSeconds}" />
|
|
424
|
-
<title>${title}</title>
|
|
425
|
-
<meta name="color-scheme" content="light dark" />
|
|
426
|
-
<style>
|
|
427
|
-
:root { --bg: #fafafa; --fg: #171717; --muted: #737373; --bar-bg: #e5e5e5; --bar-fill: #171717; --danger: #dc2626; --code-bg: #171717; --code-fg: #fafafa; }
|
|
428
|
-
@media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --bar-bg: #262626; --bar-fill: #fafafa; --danger: #f87171; --code-bg: #171717; --code-fg: #f5f5f5; } }
|
|
429
|
-
body { min-height: 100vh; margin: 0; display: grid; place-items: center; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
|
|
430
|
-
main { width: min(680px, calc(100vw - 48px)); }
|
|
431
|
-
.bar { height: 3px; overflow: hidden; border-radius: 999px; background: var(--bar-bg); }
|
|
432
|
-
.bar::before { content: ""; display: block; height: 100%; width: 42%; border-radius: inherit; background: var(--bar-fill); animation: load 1s ease-in-out infinite; }
|
|
433
|
-
main.failed .bar::before { width: 100%; background: var(--danger); animation: none; }
|
|
434
|
-
p { color: var(--muted); }
|
|
435
|
-
pre { max-height: min(46vh, 360px); overflow: auto; margin-top: 20px; padding: 14px 16px; border-radius: 8px; background: var(--code-bg); color: var(--code-fg); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; white-space: pre-wrap; word-break: break-word; }
|
|
436
|
-
@keyframes load { 0% { transform: translateX(-105%); } 100% { transform: translateX(245%); } }
|
|
437
|
-
</style>
|
|
438
|
-
<script>
|
|
439
|
-
(() => {
|
|
440
|
-
const reload = () => window.location.reload();
|
|
441
|
-
setTimeout(reload, ${JSON.stringify(refreshScriptDelay)});
|
|
442
|
-
setInterval(reload, ${JSON.stringify(Math.max(refreshScriptDelay, 3_000))});
|
|
443
|
-
})();
|
|
444
|
-
</script>
|
|
445
|
-
</head>
|
|
446
|
-
<body>
|
|
447
|
-
<main class="${failure ? "failed" : ""}">
|
|
448
|
-
<div class="bar"></div>
|
|
449
|
-
<h1>${title}</h1>
|
|
450
|
-
<p>${escapeHtml(message)}</p>
|
|
451
|
-
${failureOutput ? `<pre>${escapeHtml(failureOutput)}</pre>` : ""}
|
|
452
|
-
</main>
|
|
453
|
-
</body>
|
|
509
|
+
return `<!doctype html>
|
|
510
|
+
<html>
|
|
511
|
+
<head>
|
|
512
|
+
<meta charset="utf-8" />
|
|
513
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
514
|
+
<meta http-equiv="refresh" content="${refreshSeconds}" />
|
|
515
|
+
<title>${title}</title>
|
|
516
|
+
<meta name="color-scheme" content="light dark" />
|
|
517
|
+
<style>
|
|
518
|
+
:root { --bg: #fafafa; --fg: #171717; --muted: #737373; --bar-bg: #e5e5e5; --bar-fill: #171717; --danger: #dc2626; --code-bg: #171717; --code-fg: #fafafa; }
|
|
519
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --bar-bg: #262626; --bar-fill: #fafafa; --danger: #f87171; --code-bg: #171717; --code-fg: #f5f5f5; } }
|
|
520
|
+
body { min-height: 100vh; margin: 0; display: grid; place-items: center; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
|
|
521
|
+
main { width: min(680px, calc(100vw - 48px)); }
|
|
522
|
+
.bar { height: 3px; overflow: hidden; border-radius: 999px; background: var(--bar-bg); }
|
|
523
|
+
.bar::before { content: ""; display: block; height: 100%; width: 42%; border-radius: inherit; background: var(--bar-fill); animation: load 1s ease-in-out infinite; }
|
|
524
|
+
main.failed .bar::before { width: 100%; background: var(--danger); animation: none; }
|
|
525
|
+
p { color: var(--muted); }
|
|
526
|
+
pre { max-height: min(46vh, 360px); overflow: auto; margin-top: 20px; padding: 14px 16px; border-radius: 8px; background: var(--code-bg); color: var(--code-fg); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; white-space: pre-wrap; word-break: break-word; }
|
|
527
|
+
@keyframes load { 0% { transform: translateX(-105%); } 100% { transform: translateX(245%); } }
|
|
528
|
+
</style>
|
|
529
|
+
<script>
|
|
530
|
+
(() => {
|
|
531
|
+
const reload = () => window.location.reload();
|
|
532
|
+
setTimeout(reload, ${JSON.stringify(refreshScriptDelay)});
|
|
533
|
+
setInterval(reload, ${JSON.stringify(Math.max(refreshScriptDelay, 3_000))});
|
|
534
|
+
})();
|
|
535
|
+
</script>
|
|
536
|
+
</head>
|
|
537
|
+
<body>
|
|
538
|
+
<main class="${failure ? "failed" : ""}">
|
|
539
|
+
<div class="bar"></div>
|
|
540
|
+
<h1>${title}</h1>
|
|
541
|
+
<p>${escapeHtml(message)}</p>
|
|
542
|
+
${failureOutput ? `<pre>${escapeHtml(failureOutput)}</pre>` : ""}
|
|
543
|
+
</main>
|
|
544
|
+
</body>
|
|
454
545
|
</html>`;
|
|
455
546
|
}
|
|
456
547
|
function renderIndex(apps) {
|
|
457
|
-
return `<!doctype html>
|
|
458
|
-
<html>
|
|
459
|
-
<head>
|
|
460
|
-
<meta charset="utf-8" />
|
|
461
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
462
|
-
<title>Agent-Native Workspace</title>
|
|
463
|
-
<meta name="color-scheme" content="light dark" />
|
|
464
|
-
<style>
|
|
465
|
-
:root { --bg: #fafafa; --fg: #171717; --muted: #737373; --card-bg: #ffffff; --card-border: #d4d4d4; }
|
|
466
|
-
@media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --card-bg: #171717; --card-border: #262626; } }
|
|
467
|
-
body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 32px; background: var(--bg); color: var(--fg); }
|
|
468
|
-
main { max-width: 760px; margin: 0 auto; }
|
|
469
|
-
a { color: inherit; text-decoration: none; }
|
|
470
|
-
.grid { display: grid; gap: 12px; margin-top: 20px; }
|
|
471
|
-
.card { display: flex; justify-content: space-between; border: 1px solid var(--card-border); border-radius: 8px; padding: 14px 16px; background: var(--card-bg); }
|
|
472
|
-
.muted { color: var(--muted); }
|
|
473
|
-
</style>
|
|
474
|
-
</head>
|
|
475
|
-
<body>
|
|
476
|
-
<main>
|
|
477
|
-
<h1>Agent-Native Workspace</h1>
|
|
478
|
-
<p class="muted">Open an app below. Dispatch is the workspace control plane when installed.</p>
|
|
479
|
-
<div class="grid">
|
|
548
|
+
return `<!doctype html>
|
|
549
|
+
<html>
|
|
550
|
+
<head>
|
|
551
|
+
<meta charset="utf-8" />
|
|
552
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
553
|
+
<title>Agent-Native Workspace</title>
|
|
554
|
+
<meta name="color-scheme" content="light dark" />
|
|
555
|
+
<style>
|
|
556
|
+
:root { --bg: #fafafa; --fg: #171717; --muted: #737373; --card-bg: #ffffff; --card-border: #d4d4d4; }
|
|
557
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --card-bg: #171717; --card-border: #262626; } }
|
|
558
|
+
body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 32px; background: var(--bg); color: var(--fg); }
|
|
559
|
+
main { max-width: 760px; margin: 0 auto; }
|
|
560
|
+
a { color: inherit; text-decoration: none; }
|
|
561
|
+
.grid { display: grid; gap: 12px; margin-top: 20px; }
|
|
562
|
+
.card { display: flex; justify-content: space-between; border: 1px solid var(--card-border); border-radius: 8px; padding: 14px 16px; background: var(--card-bg); }
|
|
563
|
+
.muted { color: var(--muted); }
|
|
564
|
+
</style>
|
|
565
|
+
</head>
|
|
566
|
+
<body>
|
|
567
|
+
<main>
|
|
568
|
+
<h1>Agent-Native Workspace</h1>
|
|
569
|
+
<p class="muted">Open an app below. Dispatch is the workspace control plane when installed.</p>
|
|
570
|
+
<div class="grid">
|
|
480
571
|
${apps
|
|
481
572
|
.map((app) => `<a class="card" href="/${app.id}"><strong>${escapeHtml(app.name)}</strong><span class="muted">/${escapeHtml(app.id)}</span></a>`)
|
|
482
|
-
.join("")}
|
|
483
|
-
</div>
|
|
484
|
-
</main>
|
|
485
|
-
</body>
|
|
573
|
+
.join("")}
|
|
574
|
+
</div>
|
|
575
|
+
</main>
|
|
576
|
+
</body>
|
|
486
577
|
</html>`;
|
|
487
578
|
}
|
|
488
579
|
function hasLocalBin(dir, command) {
|
|
@@ -505,6 +596,7 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
505
596
|
const appPortStart = Number(env.WORKSPACE_APP_PORT_START || DEFAULT_APP_PORT_START);
|
|
506
597
|
const forceVite = env.WORKSPACE_VITE_FORCE === "1";
|
|
507
598
|
const eager = shouldEagerStartWorkspaceApps(args, env);
|
|
599
|
+
const builtMode = shouldRunBuiltWorkspaceApps(args, env);
|
|
508
600
|
const pollingMode = pollingFileWatcherMode(env, root);
|
|
509
601
|
const usePollingFileWatcher = pollingMode === "enable";
|
|
510
602
|
const proxyReadyTimeoutMs = Number(env.WORKSPACE_PROXY_READY_TIMEOUT_MS ?? 30_000);
|
|
@@ -642,9 +734,61 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
642
734
|
app.outputTail = undefined;
|
|
643
735
|
const basePath = `/${app.id}`;
|
|
644
736
|
const shouldInstall = !app.installAttempted && !hasLocalBin(app.dir, "vite");
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
737
|
+
const builtEntry = builtWorkspaceAppServerEntry(app.dir);
|
|
738
|
+
let shouldBuild = false;
|
|
739
|
+
let runBuilt = false;
|
|
740
|
+
if (builtMode && !shouldInstall && !app.devMode) {
|
|
741
|
+
let hasFreshBuild = false;
|
|
742
|
+
if (fs.existsSync(builtEntry)) {
|
|
743
|
+
if (app.buildChecked) {
|
|
744
|
+
hasFreshBuild = true;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
app.buildChecked = true;
|
|
748
|
+
try {
|
|
749
|
+
hasFreshBuild =
|
|
750
|
+
newestBuiltModeSourceMtimeMs(app.dir) <=
|
|
751
|
+
fs.statSync(builtEntry).mtimeMs;
|
|
752
|
+
}
|
|
753
|
+
catch {
|
|
754
|
+
hasFreshBuild = false;
|
|
755
|
+
}
|
|
756
|
+
if (!hasFreshBuild) {
|
|
757
|
+
stdout.write(`[workspace] /${app.id}: production build is stale \u2014 rebuilding before serving\n`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (hasFreshBuild) {
|
|
762
|
+
runBuilt = true;
|
|
763
|
+
}
|
|
764
|
+
else if (!app.buildAttempted) {
|
|
765
|
+
shouldBuild = true;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
app.devMode = true;
|
|
769
|
+
stdout.write(`[workspace] /${app.id}: no usable production build \u2014 falling back to the vite dev server\n`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
let spawnCommand = "pnpm";
|
|
773
|
+
let childArgs;
|
|
774
|
+
if (shouldInstall) {
|
|
775
|
+
childArgs = [
|
|
776
|
+
"--dir",
|
|
777
|
+
root,
|
|
778
|
+
"install",
|
|
779
|
+
"--no-frozen-lockfile",
|
|
780
|
+
"--prefer-offline",
|
|
781
|
+
];
|
|
782
|
+
}
|
|
783
|
+
else if (shouldBuild) {
|
|
784
|
+
childArgs = ["--dir", app.dir, "run", "build"];
|
|
785
|
+
}
|
|
786
|
+
else if (runBuilt) {
|
|
787
|
+
spawnCommand = process.execPath;
|
|
788
|
+
childArgs = [builtEntry];
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
childArgs = [
|
|
648
792
|
"--dir",
|
|
649
793
|
app.dir,
|
|
650
794
|
"exec",
|
|
@@ -656,10 +800,17 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
656
800
|
"--strictPort",
|
|
657
801
|
...(forceVite ? ["--force"] : []),
|
|
658
802
|
];
|
|
803
|
+
}
|
|
659
804
|
if (shouldInstall) {
|
|
660
805
|
stdout.write(`[workspace] Installing dependencies before starting /${app.id}\n`);
|
|
661
806
|
}
|
|
662
|
-
|
|
807
|
+
else if (shouldBuild) {
|
|
808
|
+
stdout.write(`[workspace] Building /${app.id} for built-mode serving (one-time; subsequent boots are instant)\n`);
|
|
809
|
+
}
|
|
810
|
+
else if (runBuilt) {
|
|
811
|
+
stdout.write(`[workspace] /${app.id}: serving prebuilt server\n`);
|
|
812
|
+
}
|
|
813
|
+
const child = spawnProcess(spawnCommand, childArgs, {
|
|
663
814
|
cwd: root,
|
|
664
815
|
stdio: ["ignore", "pipe", "pipe"],
|
|
665
816
|
env: devWatcherEnv({
|
|
@@ -683,10 +834,14 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
683
834
|
VITE_WORKSPACE_GATEWAY_URL: gatewayUrl,
|
|
684
835
|
PORT: String(app.port),
|
|
685
836
|
WORKSPACE_GATEWAY_URL: gatewayUrl,
|
|
837
|
+
...(runBuilt ? { HOST: "127.0.0.1" } : {}),
|
|
686
838
|
}, pollingMode),
|
|
687
839
|
});
|
|
688
840
|
app.process = child;
|
|
689
841
|
app.installing = shouldInstall;
|
|
842
|
+
app.building = shouldBuild;
|
|
843
|
+
if (runBuilt)
|
|
844
|
+
attachPromoteWatcher(app);
|
|
690
845
|
const prefix = `[${app.id}]`;
|
|
691
846
|
const stableTimer = setTimeout(() => {
|
|
692
847
|
app.restartAttempts = 0;
|
|
@@ -707,6 +862,7 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
707
862
|
const wasInstalling = app.installing;
|
|
708
863
|
app.process = undefined;
|
|
709
864
|
app.installing = false;
|
|
865
|
+
app.building = false;
|
|
710
866
|
app.ready = false;
|
|
711
867
|
app.readinessProbe = undefined;
|
|
712
868
|
if (app.restartTimer || shuttingDown)
|
|
@@ -724,21 +880,45 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
724
880
|
child.on("exit", (code, signal) => {
|
|
725
881
|
clearTimeout(stableTimer);
|
|
726
882
|
const wasInstalling = app.installing;
|
|
883
|
+
const wasBuilding = app.building;
|
|
727
884
|
app.process = undefined;
|
|
728
885
|
app.installing = false;
|
|
886
|
+
app.building = false;
|
|
729
887
|
app.ready = false;
|
|
730
888
|
app.readinessProbe = undefined;
|
|
889
|
+
if (app.expectedExit) {
|
|
890
|
+
// Intentional kill (promotion to the vite dev server). Respawn
|
|
891
|
+
// immediately instead of treating it as a crash.
|
|
892
|
+
app.expectedExit = false;
|
|
893
|
+
if (!shuttingDown && !app.restartTimer)
|
|
894
|
+
startApp(app);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
731
897
|
if (app.restartTimer)
|
|
732
898
|
return;
|
|
733
899
|
if (code === 0 || shuttingDown) {
|
|
734
|
-
if (
|
|
735
|
-
|
|
736
|
-
|
|
900
|
+
if (code === 0 && !shuttingDown) {
|
|
901
|
+
if (wasInstalling) {
|
|
902
|
+
app.installAttempted = true;
|
|
903
|
+
startApp(app);
|
|
904
|
+
}
|
|
905
|
+
else if (wasBuilding) {
|
|
906
|
+
app.buildAttempted = true;
|
|
907
|
+
startApp(app);
|
|
908
|
+
}
|
|
737
909
|
}
|
|
738
910
|
return;
|
|
739
911
|
}
|
|
740
912
|
if (wasInstalling)
|
|
741
913
|
app.installAttempted = false;
|
|
914
|
+
if (wasBuilding) {
|
|
915
|
+
// A failed production build must never brick the app — fall back to
|
|
916
|
+
// the vite dev server, which surfaces the error with hot reload.
|
|
917
|
+
app.devMode = true;
|
|
918
|
+
stderr.write(`[${app.id}] production build failed (exit ${code}); falling back to the vite dev server\n`);
|
|
919
|
+
startApp(app);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
742
922
|
scheduleAppRestart(app, {
|
|
743
923
|
code,
|
|
744
924
|
signal,
|
|
@@ -748,6 +928,47 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
748
928
|
});
|
|
749
929
|
});
|
|
750
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* Built-mode promotion: the moment one of an app's source files changes,
|
|
933
|
+
* swap its prebuilt server for the vite dev server on the same port. The
|
|
934
|
+
* proxy doesn't care what listens upstream, so the swap is invisible apart
|
|
935
|
+
* from a brief wake page. Sticky for the rest of the session.
|
|
936
|
+
*/
|
|
937
|
+
function promoteAppToDevServer(app, changedPath) {
|
|
938
|
+
if (app.devMode || shuttingDown)
|
|
939
|
+
return;
|
|
940
|
+
app.devMode = true;
|
|
941
|
+
app.promoteWatcher?.close();
|
|
942
|
+
app.promoteWatcher = undefined;
|
|
943
|
+
stdout.write(`[workspace] /${app.id}: source changed (${changedPath}) \u2014 promoting to the vite dev server with hot reload\n`);
|
|
944
|
+
app.ready = false;
|
|
945
|
+
app.readinessProbe = undefined;
|
|
946
|
+
if (app.process && !app.process.killed) {
|
|
947
|
+
app.expectedExit = true;
|
|
948
|
+
killAppProcessTree(app.process);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
startApp(app);
|
|
952
|
+
}
|
|
953
|
+
function attachPromoteWatcher(app) {
|
|
954
|
+
if (app.promoteWatcher || app.devMode)
|
|
955
|
+
return;
|
|
956
|
+
try {
|
|
957
|
+
const watcher = fs.watch(app.dir, { recursive: true }, (_event, filename) => {
|
|
958
|
+
const rel = filename ? String(filename) : "";
|
|
959
|
+
if (!isBuiltModeSourcePath(rel))
|
|
960
|
+
return;
|
|
961
|
+
promoteAppToDevServer(app, rel);
|
|
962
|
+
});
|
|
963
|
+
watcher.on("error", (err) => {
|
|
964
|
+
handleWatcherError(err);
|
|
965
|
+
});
|
|
966
|
+
app.promoteWatcher = watcher;
|
|
967
|
+
}
|
|
968
|
+
catch (err) {
|
|
969
|
+
handleWatcherError(err);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
751
972
|
function scheduleAppRestart(app, input) {
|
|
752
973
|
if (shuttingDown || app.restartTimer)
|
|
753
974
|
return;
|
|
@@ -772,8 +993,9 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
772
993
|
app.restartTimer.unref();
|
|
773
994
|
}
|
|
774
995
|
function failAppStartupTimeout(app) {
|
|
775
|
-
if (app.installing || app.ready || app.restartTimer)
|
|
996
|
+
if (app.installing || app.building || app.ready || app.restartTimer) {
|
|
776
997
|
return;
|
|
998
|
+
}
|
|
777
999
|
const timeout = formatProxyReadyTimeout(proxyReadyTimeoutMs);
|
|
778
1000
|
const message = `Timed out waiting ${timeout} for /${app.id} to return ` +
|
|
779
1001
|
`an HTTP response on 127.0.0.1:${app.port}.`;
|
|
@@ -826,8 +1048,9 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
826
1048
|
return false;
|
|
827
1049
|
}
|
|
828
1050
|
function ensureReadinessProbe(app) {
|
|
829
|
-
if (app.ready || app.readinessProbe || app.installing)
|
|
1051
|
+
if (app.ready || app.readinessProbe || app.installing || app.building) {
|
|
830
1052
|
return;
|
|
1053
|
+
}
|
|
831
1054
|
app.readinessProbe = waitForHttpReady(app, Date.now() + proxyReadyTimeoutMs)
|
|
832
1055
|
.then((ready) => {
|
|
833
1056
|
if (ready) {
|
|
@@ -1183,7 +1406,7 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
1183
1406
|
stdout.write(`[workspace] Default: ${redirectRootToDefault ? `${gatewayUrl}/${defaultApp}` : gatewayUrl}\n`);
|
|
1184
1407
|
stdout.write(`[workspace] Gateway: ${gatewayUrl}\n`);
|
|
1185
1408
|
const prewarming = shouldPrewarmWorkspaceApps(args, env);
|
|
1186
|
-
stdout.write(`[workspace] Mode: ${eager ? "eager" : prewarming ? "lazy+prewarm" : "lazy"}\n`);
|
|
1409
|
+
stdout.write(`[workspace] Mode: ${eager ? "eager" : prewarming ? "lazy+prewarm" : "lazy"}${builtMode ? "+built (prebuilt servers; edits promote to vite)" : ""}\n`);
|
|
1187
1410
|
for (const app of apps) {
|
|
1188
1411
|
stdout.write(`[workspace] ${app.id}: /${app.id} -> 127.0.0.1:${app.port}\n`);
|
|
1189
1412
|
}
|
|
@@ -1203,6 +1426,8 @@ export async function runWorkspaceDev(options = {}) {
|
|
|
1203
1426
|
shuttingDown = true;
|
|
1204
1427
|
server.close();
|
|
1205
1428
|
for (const app of apps) {
|
|
1429
|
+
app.promoteWatcher?.close();
|
|
1430
|
+
app.promoteWatcher = undefined;
|
|
1206
1431
|
killAppProcessTree(app.process);
|
|
1207
1432
|
}
|
|
1208
1433
|
if (syncTimer)
|