@open-mercato/cli 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1
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/.turbo/turbo-build.log +1 -1
- package/dist/lib/auto-spawn-scheduler.js +37 -0
- package/dist/lib/auto-spawn-scheduler.js.map +7 -0
- package/dist/lib/scheduler-supervisor.js +216 -0
- package/dist/lib/scheduler-supervisor.js.map +7 -0
- package/dist/lib/testing/integration.js +9 -1
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +54 -8
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -7
- package/src/__tests__/mercato.test.ts +88 -0
- package/src/lib/__tests__/auto-spawn-scheduler.test.ts +63 -0
- package/src/lib/__tests__/scheduler-supervisor.test.ts +199 -0
- package/src/lib/auto-spawn-scheduler.ts +39 -0
- package/src/lib/scheduler-supervisor.ts +286 -0
- package/src/lib/testing/__tests__/integration.test.ts +12 -0
- package/src/lib/testing/integration.ts +9 -1
- package/src/mercato.ts +56 -8
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
2
|
+
const DEFAULT_LAZY_SCHEDULER_POLL_MS = 1e3;
|
|
3
|
+
const MIN_LAZY_SCHEDULER_POLL_MS = 250;
|
|
4
|
+
function resolveAutoSpawnSchedulerEnabled(env = process.env) {
|
|
5
|
+
const legacy = parseBooleanToken(env.AUTO_SPAWN_SCHEDULER);
|
|
6
|
+
if (legacy !== null) return legacy;
|
|
7
|
+
const aliased = parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER);
|
|
8
|
+
if (aliased !== null) return aliased;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
function resolveAutoSpawnSchedulerLazy(env = process.env) {
|
|
12
|
+
return parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER_LAZY) === true;
|
|
13
|
+
}
|
|
14
|
+
function resolveAutoSpawnSchedulerMode(env = process.env) {
|
|
15
|
+
if (!resolveAutoSpawnSchedulerEnabled(env)) return "off";
|
|
16
|
+
if (resolveAutoSpawnSchedulerLazy(env)) return "lazy";
|
|
17
|
+
return "eager";
|
|
18
|
+
}
|
|
19
|
+
function resolveLazySchedulerPollMs(env = process.env) {
|
|
20
|
+
const raw = env.OM_AUTO_SPAWN_SCHEDULER_LAZY_POLL_MS;
|
|
21
|
+
if (typeof raw !== "string" || raw.trim() === "") return DEFAULT_LAZY_SCHEDULER_POLL_MS;
|
|
22
|
+
const parsed = Number(raw);
|
|
23
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LAZY_SCHEDULER_POLL_MS;
|
|
24
|
+
return Math.max(MIN_LAZY_SCHEDULER_POLL_MS, Math.floor(parsed));
|
|
25
|
+
}
|
|
26
|
+
function resolveLazySchedulerRestart(env = process.env) {
|
|
27
|
+
const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER_LAZY_RESTART);
|
|
28
|
+
return parsed === null ? true : parsed;
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
resolveAutoSpawnSchedulerEnabled,
|
|
32
|
+
resolveAutoSpawnSchedulerLazy,
|
|
33
|
+
resolveAutoSpawnSchedulerMode,
|
|
34
|
+
resolveLazySchedulerPollMs,
|
|
35
|
+
resolveLazySchedulerRestart
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=auto-spawn-scheduler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/auto-spawn-scheduler.ts"],
|
|
4
|
+
"sourcesContent": ["import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport type AutoSpawnSchedulerMode = 'off' | 'eager' | 'lazy'\n\nexport type AutoSpawnSchedulerEnvSource = Pick<NodeJS.ProcessEnv, string> | Record<string, string | undefined>\n\nconst DEFAULT_LAZY_SCHEDULER_POLL_MS = 1000\nconst MIN_LAZY_SCHEDULER_POLL_MS = 250\n\nexport function resolveAutoSpawnSchedulerEnabled(env: AutoSpawnSchedulerEnvSource = process.env): boolean {\n const legacy = parseBooleanToken(env.AUTO_SPAWN_SCHEDULER)\n if (legacy !== null) return legacy\n const aliased = parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER)\n if (aliased !== null) return aliased\n return true\n}\n\nexport function resolveAutoSpawnSchedulerLazy(env: AutoSpawnSchedulerEnvSource = process.env): boolean {\n return parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER_LAZY) === true\n}\n\nexport function resolveAutoSpawnSchedulerMode(env: AutoSpawnSchedulerEnvSource = process.env): AutoSpawnSchedulerMode {\n if (!resolveAutoSpawnSchedulerEnabled(env)) return 'off'\n if (resolveAutoSpawnSchedulerLazy(env)) return 'lazy'\n return 'eager'\n}\n\nexport function resolveLazySchedulerPollMs(env: AutoSpawnSchedulerEnvSource = process.env): number {\n const raw = env.OM_AUTO_SPAWN_SCHEDULER_LAZY_POLL_MS\n if (typeof raw !== 'string' || raw.trim() === '') return DEFAULT_LAZY_SCHEDULER_POLL_MS\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LAZY_SCHEDULER_POLL_MS\n return Math.max(MIN_LAZY_SCHEDULER_POLL_MS, Math.floor(parsed))\n}\n\nexport function resolveLazySchedulerRestart(env: AutoSpawnSchedulerEnvSource = process.env): boolean {\n const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_SCHEDULER_LAZY_RESTART)\n return parsed === null ? true : parsed\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,yBAAyB;AAMlC,MAAM,iCAAiC;AACvC,MAAM,6BAA6B;AAE5B,SAAS,iCAAiC,MAAmC,QAAQ,KAAc;AACxG,QAAM,SAAS,kBAAkB,IAAI,oBAAoB;AACzD,MAAI,WAAW,KAAM,QAAO;AAC5B,QAAM,UAAU,kBAAkB,IAAI,uBAAuB;AAC7D,MAAI,YAAY,KAAM,QAAO;AAC7B,SAAO;AACT;AAEO,SAAS,8BAA8B,MAAmC,QAAQ,KAAc;AACrG,SAAO,kBAAkB,IAAI,4BAA4B,MAAM;AACjE;AAEO,SAAS,8BAA8B,MAAmC,QAAQ,KAA6B;AACpH,MAAI,CAAC,iCAAiC,GAAG,EAAG,QAAO;AACnD,MAAI,8BAA8B,GAAG,EAAG,QAAO;AAC/C,SAAO;AACT;AAEO,SAAS,2BAA2B,MAAmC,QAAQ,KAAa;AACjG,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,GAAI,QAAO;AACzD,QAAM,SAAS,OAAO,GAAG;AACzB,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO,KAAK,IAAI,4BAA4B,KAAK,MAAM,MAAM,CAAC;AAChE;AAEO,SAAS,4BAA4B,MAAmC,QAAQ,KAAc;AACnG,QAAM,SAAS,kBAAkB,IAAI,oCAAoC;AACzE,SAAO,WAAW,OAAO,OAAO;AAClC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
import { getSslConfig } from "@open-mercato/shared/lib/db/ssl";
|
|
3
|
+
const PROBE_ERROR_LOG_THROTTLE_MS = 6e4;
|
|
4
|
+
function coerceCount(value) {
|
|
5
|
+
if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
|
|
6
|
+
if (typeof value === "bigint") return Number(value);
|
|
7
|
+
if (typeof value === "string") {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0;
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
function isMissingScheduledJobsTableError(error) {
|
|
14
|
+
return Boolean(
|
|
15
|
+
error && typeof error === "object" && "code" in error && error.code === "42P01"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
async function probeEnabledSchedules(env = process.env) {
|
|
19
|
+
const databaseUrl = env.DATABASE_URL?.trim();
|
|
20
|
+
if (!databaseUrl) {
|
|
21
|
+
return {
|
|
22
|
+
enabledSchedules: 0,
|
|
23
|
+
dueSchedules: 0,
|
|
24
|
+
error: true,
|
|
25
|
+
errorMessage: "DATABASE_URL is not set"
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const { Client } = await import("pg");
|
|
29
|
+
const client = new Client({
|
|
30
|
+
connectionString: databaseUrl,
|
|
31
|
+
ssl: getSslConfig()
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
await client.connect();
|
|
35
|
+
const result = await client.query(`
|
|
36
|
+
select
|
|
37
|
+
count(*)::int as enabled_schedules,
|
|
38
|
+
count(*) filter (where next_run_at <= now())::int as due_schedules
|
|
39
|
+
from scheduled_jobs
|
|
40
|
+
where is_enabled = true
|
|
41
|
+
and deleted_at is null
|
|
42
|
+
`);
|
|
43
|
+
const row = result.rows[0] ?? {};
|
|
44
|
+
return {
|
|
45
|
+
enabledSchedules: coerceCount(row.enabled_schedules),
|
|
46
|
+
dueSchedules: coerceCount(row.due_schedules),
|
|
47
|
+
error: false
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (isMissingScheduledJobsTableError(error)) {
|
|
51
|
+
return {
|
|
52
|
+
enabledSchedules: 0,
|
|
53
|
+
dueSchedules: 0,
|
|
54
|
+
error: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
enabledSchedules: 0,
|
|
59
|
+
dueSchedules: 0,
|
|
60
|
+
error: true,
|
|
61
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
62
|
+
};
|
|
63
|
+
} finally {
|
|
64
|
+
await client.end().catch(() => void 0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function startLazySchedulerSupervisor(options) {
|
|
68
|
+
const logger = options.logger ?? console;
|
|
69
|
+
const probe = options.probeFn ?? probeEnabledSchedules;
|
|
70
|
+
const spawnFn = options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args, opts));
|
|
71
|
+
let stopping = false;
|
|
72
|
+
let started = false;
|
|
73
|
+
let starting = false;
|
|
74
|
+
let activeChild;
|
|
75
|
+
let pollTimer = null;
|
|
76
|
+
let lastProbeErrorLoggedAt = 0;
|
|
77
|
+
let doneResolve = null;
|
|
78
|
+
const done = new Promise((resolve) => {
|
|
79
|
+
doneResolve = resolve;
|
|
80
|
+
});
|
|
81
|
+
logger.log("[lazy-scheduler] Watching for enabled schedules before starting the polling engine.");
|
|
82
|
+
function logProbeError(message) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
if (now - lastProbeErrorLoggedAt < PROBE_ERROR_LOG_THROTTLE_MS) return;
|
|
85
|
+
lastProbeErrorLoggedAt = now;
|
|
86
|
+
logger.warn(`[lazy-scheduler] Schedule probe failed: ${message}`);
|
|
87
|
+
}
|
|
88
|
+
function spawnScheduler() {
|
|
89
|
+
if (stopping || activeChild || starting) return;
|
|
90
|
+
starting = true;
|
|
91
|
+
logger.log("[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.");
|
|
92
|
+
let child;
|
|
93
|
+
try {
|
|
94
|
+
child = spawnFn("node", [options.mercatoBin, "scheduler", "start"], {
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
env: options.runtimeEnv,
|
|
97
|
+
cwd: options.appDir
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
starting = false;
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
+
logger.error(`[lazy-scheduler] Failed to spawn scheduler: ${message}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
activeChild = child;
|
|
106
|
+
started = true;
|
|
107
|
+
starting = false;
|
|
108
|
+
options.onSpawn?.(child);
|
|
109
|
+
child.on("exit", (code, signal) => {
|
|
110
|
+
activeChild = void 0;
|
|
111
|
+
options.onChildExit?.(code, signal);
|
|
112
|
+
if (stopping) return;
|
|
113
|
+
const expected = signal === "SIGTERM" || signal === "SIGINT";
|
|
114
|
+
if (expected) return;
|
|
115
|
+
const reason = code !== null ? `code ${code}` : `signal ${signal ?? "unknown"}`;
|
|
116
|
+
logger.warn(`[lazy-scheduler] Scheduler polling engine exited (${reason}).`);
|
|
117
|
+
if (!options.restartOnUnexpectedExit) return;
|
|
118
|
+
void (async () => {
|
|
119
|
+
const result = await safeProbe();
|
|
120
|
+
if (!result || result.error) return;
|
|
121
|
+
if (result.enabledSchedules > 0) {
|
|
122
|
+
logger.warn("[lazy-scheduler] Restarting scheduler because enabled schedules still exist.");
|
|
123
|
+
spawnScheduler();
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function safeProbe() {
|
|
129
|
+
try {
|
|
130
|
+
return await probe(options.runtimeEnv);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
logProbeError(message);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function tickOnce() {
|
|
138
|
+
if (stopping || activeChild || starting) return;
|
|
139
|
+
const result = await safeProbe();
|
|
140
|
+
if (!result) return;
|
|
141
|
+
if (result.error) {
|
|
142
|
+
if (result.errorMessage) logProbeError(result.errorMessage);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (result.enabledSchedules > 0) {
|
|
146
|
+
spawnScheduler();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function scheduleNext() {
|
|
150
|
+
if (stopping || activeChild) return;
|
|
151
|
+
pollTimer = setTimeout(() => {
|
|
152
|
+
void (async () => {
|
|
153
|
+
try {
|
|
154
|
+
await tickOnce();
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
logger.warn(`[lazy-scheduler] Poll cycle failed: ${message}`);
|
|
158
|
+
} finally {
|
|
159
|
+
scheduleNext();
|
|
160
|
+
}
|
|
161
|
+
})();
|
|
162
|
+
}, options.pollMs);
|
|
163
|
+
pollTimer.unref?.();
|
|
164
|
+
}
|
|
165
|
+
void (async () => {
|
|
166
|
+
try {
|
|
167
|
+
await tickOnce();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
logger.warn(`[lazy-scheduler] Initial poll failed: ${message}`);
|
|
171
|
+
} finally {
|
|
172
|
+
scheduleNext();
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
async function close() {
|
|
176
|
+
if (stopping) {
|
|
177
|
+
await done;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
stopping = true;
|
|
181
|
+
if (pollTimer) {
|
|
182
|
+
clearTimeout(pollTimer);
|
|
183
|
+
pollTimer = null;
|
|
184
|
+
}
|
|
185
|
+
const child = activeChild;
|
|
186
|
+
if (child) {
|
|
187
|
+
await new Promise((resolve) => {
|
|
188
|
+
if (child.exitCode !== null || child.signalCode !== null) return resolve();
|
|
189
|
+
child.once("exit", () => resolve());
|
|
190
|
+
try {
|
|
191
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
194
|
+
logger.warn(`[lazy-scheduler] Failed to send SIGTERM to scheduler: ${message}`);
|
|
195
|
+
resolve();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
activeChild = void 0;
|
|
199
|
+
}
|
|
200
|
+
doneResolve?.();
|
|
201
|
+
doneResolve = null;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
get started() {
|
|
205
|
+
return started;
|
|
206
|
+
},
|
|
207
|
+
getActiveChild: () => activeChild,
|
|
208
|
+
close,
|
|
209
|
+
done
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
probeEnabledSchedules,
|
|
214
|
+
startLazySchedulerSupervisor
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=scheduler-supervisor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/scheduler-supervisor.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Lazy local scheduler supervisor.\n *\n * Keeps the server runtime from spawning the local scheduler polling engine\n * until at least one enabled schedule exists. The scheduler process itself is\n * still the existing `mercato scheduler start` command, so schedule execution,\n * locking, RBAC checks, and queue/command targets remain unchanged.\n */\n\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ChildProcess, SpawnOptions } from 'node:child_process'\nimport { getSslConfig } from '@open-mercato/shared/lib/db/ssl'\n\nexport type SchedulerScheduleProbeResult = {\n enabledSchedules: number\n dueSchedules: number\n error?: boolean\n errorMessage?: string\n}\n\nexport type LazySchedulerSupervisorProbeFn = (\n env: NodeJS.ProcessEnv,\n) => Promise<SchedulerScheduleProbeResult>\n\nexport type LazySchedulerSupervisorSpawnFn = (\n command: string,\n args: readonly string[],\n options: SpawnOptions,\n) => ChildProcess\n\nexport type LazySchedulerSupervisorOptions = {\n mercatoBin: string\n appDir: string\n runtimeEnv: NodeJS.ProcessEnv\n pollMs: number\n restartOnUnexpectedExit: boolean\n spawnFn?: LazySchedulerSupervisorSpawnFn\n probeFn?: LazySchedulerSupervisorProbeFn\n logger?: Pick<Console, 'log' | 'warn' | 'error'>\n onSpawn?: (child: ChildProcess) => void\n onChildExit?: (code: number | null, signal: NodeJS.Signals | null) => void\n}\n\nexport type LazySchedulerSupervisorHandle = {\n readonly started: boolean\n getActiveChild(): ChildProcess | undefined\n close(): Promise<void>\n done: Promise<void>\n}\n\nconst PROBE_ERROR_LOG_THROTTLE_MS = 60_000\n\nfunction coerceCount(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return Math.max(0, Math.floor(value))\n if (typeof value === 'bigint') return Number(value)\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0\n }\n return 0\n}\n\nfunction isMissingScheduledJobsTableError(error: unknown): boolean {\n return Boolean(\n error\n && typeof error === 'object'\n && 'code' in error\n && (error as { code?: unknown }).code === '42P01',\n )\n}\n\nexport async function probeEnabledSchedules(env: NodeJS.ProcessEnv = process.env): Promise<SchedulerScheduleProbeResult> {\n const databaseUrl = env.DATABASE_URL?.trim()\n if (!databaseUrl) {\n return {\n enabledSchedules: 0,\n dueSchedules: 0,\n error: true,\n errorMessage: 'DATABASE_URL is not set',\n }\n }\n\n const { Client } = await import('pg')\n const client = new Client({\n connectionString: databaseUrl,\n ssl: getSslConfig(),\n })\n\n try {\n await client.connect()\n const result = await client.query(`\n select\n count(*)::int as enabled_schedules,\n count(*) filter (where next_run_at <= now())::int as due_schedules\n from scheduled_jobs\n where is_enabled = true\n and deleted_at is null\n `)\n const row = result.rows[0] ?? {}\n return {\n enabledSchedules: coerceCount(row.enabled_schedules),\n dueSchedules: coerceCount(row.due_schedules),\n error: false,\n }\n } catch (error) {\n if (isMissingScheduledJobsTableError(error)) {\n return {\n enabledSchedules: 0,\n dueSchedules: 0,\n error: false,\n }\n }\n return {\n enabledSchedules: 0,\n dueSchedules: 0,\n error: true,\n errorMessage: error instanceof Error ? error.message : String(error),\n }\n } finally {\n await client.end().catch(() => undefined)\n }\n}\n\nexport function startLazySchedulerSupervisor(\n options: LazySchedulerSupervisorOptions,\n): LazySchedulerSupervisorHandle {\n const logger = options.logger ?? console\n const probe = options.probeFn ?? probeEnabledSchedules\n const spawnFn: LazySchedulerSupervisorSpawnFn =\n options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args as string[], opts))\n\n let stopping = false\n let started = false\n let starting = false\n let activeChild: ChildProcess | undefined\n let pollTimer: ReturnType<typeof setTimeout> | null = null\n let lastProbeErrorLoggedAt = 0\n let doneResolve: (() => void) | null = null\n const done = new Promise<void>((resolve) => {\n doneResolve = resolve\n })\n\n logger.log('[lazy-scheduler] Watching for enabled schedules before starting the polling engine.')\n\n function logProbeError(message: string): void {\n const now = Date.now()\n if (now - lastProbeErrorLoggedAt < PROBE_ERROR_LOG_THROTTLE_MS) return\n lastProbeErrorLoggedAt = now\n logger.warn(`[lazy-scheduler] Schedule probe failed: ${message}`)\n }\n\n function spawnScheduler(): void {\n if (stopping || activeChild || starting) return\n starting = true\n logger.log('[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.')\n\n let child: ChildProcess\n try {\n child = spawnFn('node', [options.mercatoBin, 'scheduler', 'start'], {\n stdio: 'inherit',\n env: options.runtimeEnv,\n cwd: options.appDir,\n })\n } catch (error) {\n starting = false\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`[lazy-scheduler] Failed to spawn scheduler: ${message}`)\n return\n }\n\n activeChild = child\n started = true\n starting = false\n options.onSpawn?.(child)\n\n child.on('exit', (code, signal) => {\n activeChild = undefined\n options.onChildExit?.(code, signal)\n if (stopping) return\n const expected = signal === 'SIGTERM' || signal === 'SIGINT'\n if (expected) return\n const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`\n logger.warn(`[lazy-scheduler] Scheduler polling engine exited (${reason}).`)\n if (!options.restartOnUnexpectedExit) return\n void (async () => {\n const result = await safeProbe()\n if (!result || result.error) return\n if (result.enabledSchedules > 0) {\n logger.warn('[lazy-scheduler] Restarting scheduler because enabled schedules still exist.')\n spawnScheduler()\n }\n })()\n })\n }\n\n async function safeProbe(): Promise<SchedulerScheduleProbeResult | null> {\n try {\n return await probe(options.runtimeEnv)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logProbeError(message)\n return null\n }\n }\n\n async function tickOnce(): Promise<void> {\n if (stopping || activeChild || starting) return\n const result = await safeProbe()\n if (!result) return\n if (result.error) {\n if (result.errorMessage) logProbeError(result.errorMessage)\n return\n }\n if (result.enabledSchedules > 0) {\n spawnScheduler()\n }\n }\n\n function scheduleNext(): void {\n if (stopping || activeChild) return\n pollTimer = setTimeout(() => {\n void (async () => {\n try {\n await tickOnce()\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`[lazy-scheduler] Poll cycle failed: ${message}`)\n } finally {\n scheduleNext()\n }\n })()\n }, options.pollMs)\n pollTimer.unref?.()\n }\n\n void (async () => {\n try {\n await tickOnce()\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`[lazy-scheduler] Initial poll failed: ${message}`)\n } finally {\n scheduleNext()\n }\n })()\n\n async function close(): Promise<void> {\n if (stopping) {\n await done\n return\n }\n stopping = true\n if (pollTimer) {\n clearTimeout(pollTimer)\n pollTimer = null\n }\n\n const child = activeChild\n if (child) {\n await new Promise<void>((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) return resolve()\n child.once('exit', () => resolve())\n try {\n if (!child.killed) child.kill('SIGTERM')\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`[lazy-scheduler] Failed to send SIGTERM to scheduler: ${message}`)\n resolve()\n }\n })\n activeChild = undefined\n }\n\n doneResolve?.()\n doneResolve = null\n }\n\n return {\n get started() {\n return started\n },\n getActiveChild: () => activeChild,\n close,\n done,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -592,7 +592,15 @@ async function hasBuildInputChangesSince(timestampMs, options = {}) {
|
|
|
592
592
|
continue;
|
|
593
593
|
}
|
|
594
594
|
seenPaths.add(resolvedPath);
|
|
595
|
-
|
|
595
|
+
let fileStat;
|
|
596
|
+
try {
|
|
597
|
+
fileStat = await stat(resolvedPath);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
if (error.code === "ENOENT") {
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
596
604
|
if (fileStat.isFile() && fileStat.mtimeMs > timestampMs) {
|
|
597
605
|
return true;
|
|
598
606
|
}
|