@omg-dev/server 0.4.28 → 0.4.29
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/dist/index.mjs +6 -2
- package/package.json +4 -4
- package/src/test/build-mode-cron.test.ts +80 -0
- package/src/triggers.ts +19 -2
package/dist/index.mjs
CHANGED
|
@@ -1496,9 +1496,10 @@ async function registerTriggers(entries) {
|
|
|
1496
1496
|
storageHookHandlers.get(e.kind).add(e.handler);
|
|
1497
1497
|
}
|
|
1498
1498
|
}
|
|
1499
|
-
const
|
|
1499
|
+
const inBuild = inBuildMode();
|
|
1500
|
+
const inProcessCron = !inBuild && (vibesMode$1() === "dev" || cronDriver() === "in-process");
|
|
1500
1501
|
if (inProcessCron) scheduleAllCronInProcess();
|
|
1501
|
-
console.log(`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode$1()}, cron=${inProcessCron ? "in-process" : "orchestrator"}`);
|
|
1502
|
+
console.log(`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode$1()}, cron=${inBuild ? "unarmed (build)" : inProcessCron ? "in-process" : "orchestrator"}`);
|
|
1502
1503
|
}
|
|
1503
1504
|
/** Snapshot of the registry — used by the in-dev Inspect endpoints. */
|
|
1504
1505
|
function listTriggers() {
|
|
@@ -1907,6 +1908,9 @@ function vibesMode$1() {
|
|
|
1907
1908
|
_vibesMode$1 = ((typeof process !== "undefined" ? process.env?.VIBES_MODE : void 0) ?? "") === "dev" ? "dev" : "prod";
|
|
1908
1909
|
return _vibesMode$1;
|
|
1909
1910
|
}
|
|
1911
|
+
function inBuildMode() {
|
|
1912
|
+
return (typeof process !== "undefined" ? process.env?.VIBES_BUILD : void 0) === "1";
|
|
1913
|
+
}
|
|
1910
1914
|
function devInspectTriggers() {
|
|
1911
1915
|
return Array.from(triggerRegistry.values()).map(({ entry }, i) => ({
|
|
1912
1916
|
id: `trg_dev_${i}_${entry.handler}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omg-dev/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@restatedev/restate-sdk": "1.14.5",
|
|
17
|
-
"@omg-dev/auth": "0.4.
|
|
18
|
-
"@omg-dev/schema": "0.4.
|
|
19
|
-
"@omg-dev/stream": "0.4.
|
|
17
|
+
"@omg-dev/auth": "0.4.29",
|
|
18
|
+
"@omg-dev/schema": "0.4.29",
|
|
19
|
+
"@omg-dev/stream": "0.4.29",
|
|
20
20
|
"web-push": "^3.6.7"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Regression test for the vibes-build hang (confirmed: `[vibes-build] done.`
|
|
2
|
+
// prints, process never exits when the project declares cron()/on()).
|
|
3
|
+
//
|
|
4
|
+
// Root cause: registerTriggers() arms an in-process setTimeout chain for
|
|
5
|
+
// every cron() handler whenever vibesMode()==="dev" or cronDriver()===
|
|
6
|
+
// "in-process" — including when createVibesServer() is constructed by the
|
|
7
|
+
// vibes vite-plugin's configureServer hook, which prerenderApp() (part of
|
|
8
|
+
// `vibes-build`) triggers indirectly via a nested Vite dev server. Those
|
|
9
|
+
// timers keep the event loop alive forever with no caller left to clear
|
|
10
|
+
// them, since a build process has no request loop to eventually close it.
|
|
11
|
+
//
|
|
12
|
+
// This can only be observed as a live-process behavior (does the process
|
|
13
|
+
// exit on its own?), not through a return-value assertion on
|
|
14
|
+
// registerTriggers() — so this test spawns a real Bun subprocess and checks
|
|
15
|
+
// whether it terminates within a bounded window.
|
|
16
|
+
|
|
17
|
+
import { describe, expect, test } from "bun:test"
|
|
18
|
+
import fs from "node:fs"
|
|
19
|
+
import os from "node:os"
|
|
20
|
+
import path from "node:path"
|
|
21
|
+
|
|
22
|
+
const triggersModulePath = path.join(import.meta.dir, "..", "triggers.ts")
|
|
23
|
+
|
|
24
|
+
function writeRegisterScript(): string {
|
|
25
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "vibes-build-mode-cron-"))
|
|
26
|
+
const file = path.join(dir, "register.ts")
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
file,
|
|
29
|
+
[
|
|
30
|
+
`import { registerTriggers } from ${JSON.stringify(triggersModulePath)}`,
|
|
31
|
+
"await registerTriggers([",
|
|
32
|
+
' { handler: "trg.tick", kind: "cron", key: "* * * * *", module: "/dev/null", exportName: "tick", mod: { tick: async () => {} } },',
|
|
33
|
+
' { handler: "trg.onEvt", kind: "on", key: "some.topic", module: "/dev/null", exportName: "onEvt", mod: { onEvt: async () => {} } },',
|
|
34
|
+
"])",
|
|
35
|
+
'console.log("REGISTERED")',
|
|
36
|
+
"",
|
|
37
|
+
].join("\n"),
|
|
38
|
+
)
|
|
39
|
+
return file
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Runs `script` in a fresh Bun subprocess with `env` merged over the current
|
|
43
|
+
// process env. Resolves `{ exited: true }` if the process exits within
|
|
44
|
+
// `timeoutMs` on its own, or `{ exited: false }` (after force-killing it) if
|
|
45
|
+
// it's still alive at the deadline — the exact symptom of the original bug.
|
|
46
|
+
async function runAndWaitForExit(
|
|
47
|
+
script: string,
|
|
48
|
+
env: Record<string, string>,
|
|
49
|
+
timeoutMs: number,
|
|
50
|
+
): Promise<{ exited: boolean }> {
|
|
51
|
+
const proc = Bun.spawn(["bun", "run", script], {
|
|
52
|
+
env: { ...process.env, ...env },
|
|
53
|
+
stdout: "pipe",
|
|
54
|
+
stderr: "pipe",
|
|
55
|
+
})
|
|
56
|
+
let timedOut = false
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
timedOut = true
|
|
59
|
+
proc.kill()
|
|
60
|
+
}, timeoutMs)
|
|
61
|
+
await proc.exited
|
|
62
|
+
clearTimeout(timer)
|
|
63
|
+
return { exited: !timedOut }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe("registerTriggers build-mode cron gating", () => {
|
|
67
|
+
test("VIBES_BUILD=1: a cron()+on() project terminates on its own", async () => {
|
|
68
|
+
const script = writeRegisterScript()
|
|
69
|
+
const { exited } = await runAndWaitForExit(script, { VIBES_BUILD: "1", VIBES_MODE: "dev" }, 8000)
|
|
70
|
+
expect(exited).toBe(true)
|
|
71
|
+
}, 15000)
|
|
72
|
+
|
|
73
|
+
test("sanity: without VIBES_BUILD, dev-mode cron is still armed (stays alive)", async () => {
|
|
74
|
+
// Mirrors the fixed case above to prove the guard is scoped to build
|
|
75
|
+
// mode only — normal `bun dev` cron scheduling must be unaffected.
|
|
76
|
+
const script = writeRegisterScript()
|
|
77
|
+
const { exited } = await runAndWaitForExit(script, { VIBES_MODE: "dev" }, 2000)
|
|
78
|
+
expect(exited).toBe(false)
|
|
79
|
+
}, 15000)
|
|
80
|
+
})
|
package/src/triggers.ts
CHANGED
|
@@ -242,12 +242,20 @@ export async function registerTriggers(entries: TriggerEntry[]): Promise<void> {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
void registeredFirstTime // suppress unused — kept for future HMR diff
|
|
245
|
-
|
|
245
|
+
// vibes-build sets VIBES_BUILD=1 for its whole process, including the
|
|
246
|
+
// nested Vite dev server it spins up for prerender SSG. A build is not a
|
|
247
|
+
// runtime — nothing should ever arm a live setTimeout chain there, or the
|
|
248
|
+
// process hangs forever after writing its artifacts (confirmed hang: cron
|
|
249
|
+
// schedulers keep the event loop open with no caller left to clear them).
|
|
250
|
+
const inBuild = inBuildMode()
|
|
251
|
+
const inProcessCron = !inBuild && (vibesMode() === "dev" || cronDriver() === "in-process")
|
|
246
252
|
if (inProcessCron) {
|
|
247
253
|
scheduleAllCronInProcess()
|
|
248
254
|
}
|
|
249
255
|
console.log(
|
|
250
|
-
`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode()}, cron=${
|
|
256
|
+
`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode()}, cron=${
|
|
257
|
+
inBuild ? "unarmed (build)" : inProcessCron ? "in-process" : "orchestrator"
|
|
258
|
+
}`,
|
|
251
259
|
)
|
|
252
260
|
}
|
|
253
261
|
|
|
@@ -810,6 +818,15 @@ function vibesMode(): "dev" | "prod" {
|
|
|
810
818
|
return _vibesMode
|
|
811
819
|
}
|
|
812
820
|
|
|
821
|
+
// Set for the whole lifetime of `vibes-build` (packages/vite-plugin/src/build.ts).
|
|
822
|
+
// A build process has no caller left to clear timers once it "finishes" —
|
|
823
|
+
// so cron must never be armed in-process while this is set, however
|
|
824
|
+
// createVibesServer() got invoked (directly, or via prerender's nested dev
|
|
825
|
+
// server).
|
|
826
|
+
function inBuildMode(): boolean {
|
|
827
|
+
return (typeof process !== "undefined" ? process.env?.VIBES_BUILD : undefined) === "1"
|
|
828
|
+
}
|
|
829
|
+
|
|
813
830
|
// ── Dev Inspect data accessors ───────────────────────────────────────────────
|
|
814
831
|
//
|
|
815
832
|
// In dev mode, the dashboard's Inspect tabs read the in-memory rings
|