@open-mercato/cli 0.6.4-develop.3944.1.4100aa7fbe → 0.6.4-develop.3962.1.70f30e284c
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/mercato.js +150 -95
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +12 -3
- package/src/lib/__tests__/dev-env-reload.test.ts +4 -2
- package/src/mercato.ts +124 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.4-develop.
|
|
3
|
+
"version": "0.6.4-develop.3962.1.70f30e284c",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"@mikro-orm/decorators": "^7.1.1",
|
|
60
60
|
"@mikro-orm/migrations": "^7.1.1",
|
|
61
61
|
"@mikro-orm/postgresql": "^7.1.1",
|
|
62
|
-
"@open-mercato/queue": "0.6.4-develop.
|
|
63
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
62
|
+
"@open-mercato/queue": "0.6.4-develop.3962.1.70f30e284c",
|
|
63
|
+
"@open-mercato/shared": "0.6.4-develop.3962.1.70f30e284c",
|
|
64
64
|
"cross-spawn": "^7.0.6",
|
|
65
65
|
"pg": "8.21.0",
|
|
66
66
|
"semver": "^7.8.1",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"typescript": "^6.0.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
73
|
+
"@open-mercato/shared": "0.6.4-develop.3962.1.70f30e284c"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
76
|
+
"@open-mercato/shared": "0.6.4-develop.3962.1.70f30e284c",
|
|
77
77
|
"@types/jest": "^30.0.0",
|
|
78
78
|
"jest": "^30.4.2",
|
|
79
79
|
"ts-jest": "^29.4.11"
|
|
@@ -37,9 +37,18 @@ function buildMockChildProcessModule(routeAutoExit: MockChildSpawnRouter) {
|
|
|
37
37
|
if (autoExit) {
|
|
38
38
|
queueMicrotask(() => {
|
|
39
39
|
if (child.exitCode !== null || child.signalCode !== null) return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if (pathIncludes(spawnargs[1] ?? '', 'next/dist/bin/next') && spawnargs.includes('dev')) {
|
|
41
|
+
child.stdout.emit('data', '✓ Ready in 1ms\n')
|
|
42
|
+
}
|
|
43
|
+
queueMicrotask(() => {
|
|
44
|
+
child.exitCode = autoExit.code
|
|
45
|
+
child.signalCode = autoExit.signal ?? null
|
|
46
|
+
child.emit('exit', child.exitCode, child.signalCode)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
} else if (pathIncludes(spawnargs[1] ?? '', 'next/dist/bin/next') && spawnargs.includes('dev')) {
|
|
50
|
+
queueMicrotask(() => {
|
|
51
|
+
child.stdout.emit('data', '✓ Ready in 1ms\n')
|
|
43
52
|
})
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -67,15 +67,17 @@ describe('dev env reload helpers', () => {
|
|
|
67
67
|
const generatedFile = path.join(generatedDir, 'backend-routes.generated.ts')
|
|
68
68
|
fs.writeFileSync(generatedFile, 'export const routes = []\n')
|
|
69
69
|
|
|
70
|
+
let stop = () => {}
|
|
70
71
|
const seen = new Promise<string>((resolve) => {
|
|
71
|
-
|
|
72
|
+
stop = watchDevRuntimeFiles(appDir, (filePath) => {
|
|
72
73
|
stop()
|
|
73
74
|
resolve(filePath)
|
|
74
75
|
}, { debounceMs: 10 })
|
|
75
76
|
})
|
|
76
77
|
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
77
79
|
fs.writeFileSync(generatedFile, 'export const routes = [1]\n')
|
|
78
80
|
|
|
79
|
-
await expect(seen).resolves.toBe(generatedFile)
|
|
81
|
+
await expect(seen.finally(stop)).resolves.toBe(generatedFile)
|
|
80
82
|
})
|
|
81
83
|
})
|
package/src/mercato.ts
CHANGED
|
@@ -427,6 +427,36 @@ function writeDevSplashRuntimeReady(reason?: string): void {
|
|
|
427
427
|
})
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
function resolveDevWarmupReadyTimeoutMs(environment: NodeJS.ProcessEnv = process.env): number {
|
|
431
|
+
const parsed = Number.parseInt(environment.OM_DEV_WARMUP_READY_TIMEOUT_MS ?? '', 10)
|
|
432
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed
|
|
433
|
+
return 300_000
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function waitForDevWarmupReadyFile(
|
|
437
|
+
filePath: string | undefined,
|
|
438
|
+
options: {
|
|
439
|
+
timeoutMs?: number
|
|
440
|
+
signal?: AbortSignal
|
|
441
|
+
} = {},
|
|
442
|
+
): Promise<'ready' | 'timeout' | 'aborted'> {
|
|
443
|
+
const normalized = filePath?.trim()
|
|
444
|
+
if (!normalized) return 'ready'
|
|
445
|
+
const timeoutMs = options.timeoutMs ?? resolveDevWarmupReadyTimeoutMs()
|
|
446
|
+
const startedAt = Date.now()
|
|
447
|
+
|
|
448
|
+
while (true) {
|
|
449
|
+
if (options.signal?.aborted) return 'aborted'
|
|
450
|
+
try {
|
|
451
|
+
if (fs.existsSync(normalized)) return 'ready'
|
|
452
|
+
} catch {
|
|
453
|
+
// Keep polling; the runtime wrapper owns this best-effort marker.
|
|
454
|
+
}
|
|
455
|
+
if (timeoutMs >= 0 && Date.now() - startedAt >= timeoutMs) return 'timeout'
|
|
456
|
+
await new Promise((resolve) => setTimeout(resolve, 250))
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
430
460
|
type ModuleCommandLookupResult =
|
|
431
461
|
| {
|
|
432
462
|
status: 'ok'
|
|
@@ -1878,8 +1908,15 @@ export async function run(argv = process.argv) {
|
|
|
1878
1908
|
devRestartPromiseResolve = resolve
|
|
1879
1909
|
})
|
|
1880
1910
|
|
|
1881
|
-
const startNextDev = (runtimeEnv: NodeJS.ProcessEnv):
|
|
1882
|
-
|
|
1911
|
+
const startNextDev = (runtimeEnv: NodeJS.ProcessEnv): {
|
|
1912
|
+
exitPromise: Promise<ManagedProcessExitResult>
|
|
1913
|
+
readyPromise: Promise<void>
|
|
1914
|
+
} => {
|
|
1915
|
+
let readyResolve: () => void = () => undefined
|
|
1916
|
+
const readyPromise = new Promise<void>((resolve) => {
|
|
1917
|
+
readyResolve = resolve
|
|
1918
|
+
})
|
|
1919
|
+
const exitPromise = new Promise<ManagedProcessExitResult>((resolve) => {
|
|
1883
1920
|
writeDevSplashRuntimeStarting(
|
|
1884
1921
|
lastRestartReason
|
|
1885
1922
|
? `Restarting Next.js dev server. Reason: ${lastRestartReason}`
|
|
@@ -1903,6 +1940,7 @@ export async function run(argv = process.argv) {
|
|
|
1903
1940
|
reportedReady = true
|
|
1904
1941
|
writeDevSplashRuntimeReady(lastRestartReason ?? undefined)
|
|
1905
1942
|
lastRestartReason = null
|
|
1943
|
+
readyResolve()
|
|
1906
1944
|
}
|
|
1907
1945
|
}
|
|
1908
1946
|
|
|
@@ -1924,7 +1962,9 @@ export async function run(argv = process.argv) {
|
|
|
1924
1962
|
writeDevSplashRuntimeRestarting(lastRestartReason)
|
|
1925
1963
|
console.log('[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...')
|
|
1926
1964
|
removeTurbopackDevCache(appDir)
|
|
1927
|
-
|
|
1965
|
+
const restarted = startNextDev(runtimeEnv)
|
|
1966
|
+
restarted.readyPromise.then(readyResolve)
|
|
1967
|
+
return resolve(await restarted.exitPromise)
|
|
1928
1968
|
}
|
|
1929
1969
|
resolve({
|
|
1930
1970
|
label: 'Next.js dev server',
|
|
@@ -1933,6 +1973,8 @@ export async function run(argv = process.argv) {
|
|
|
1933
1973
|
})
|
|
1934
1974
|
})
|
|
1935
1975
|
})
|
|
1976
|
+
return { exitPromise, readyPromise }
|
|
1977
|
+
}
|
|
1936
1978
|
|
|
1937
1979
|
try {
|
|
1938
1980
|
while (!stopping) {
|
|
@@ -1942,62 +1984,92 @@ export async function run(argv = process.argv) {
|
|
|
1942
1984
|
const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env)
|
|
1943
1985
|
const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
|
|
1944
1986
|
const schedulerCommand = lookupModuleCommand(getCliModules(), 'scheduler', 'start')
|
|
1987
|
+
const nextRuntime = startNextDev(runtimeEnv)
|
|
1988
|
+
const restartPromise = waitForDevRestart()
|
|
1989
|
+
const backgroundStartAbort = new AbortController()
|
|
1990
|
+
const cancelBackgroundStart = () => backgroundStartAbort.abort()
|
|
1991
|
+
nextRuntime.exitPromise.finally(cancelBackgroundStart)
|
|
1992
|
+
restartPromise.then(cancelBackgroundStart)
|
|
1993
|
+
let backgroundExitResolve: (result: ManagedProcessExitResult) => void = () => undefined
|
|
1994
|
+
const backgroundExitPromise = new Promise<ManagedProcessExitResult>((resolve) => {
|
|
1995
|
+
backgroundExitResolve = resolve
|
|
1996
|
+
})
|
|
1945
1997
|
const managedExitPromises: Promise<DevServerExitResult>[] = [
|
|
1946
|
-
|
|
1947
|
-
|
|
1998
|
+
nextRuntime.exitPromise,
|
|
1999
|
+
restartPromise,
|
|
2000
|
+
backgroundExitPromise,
|
|
1948
2001
|
]
|
|
1949
2002
|
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
1965
|
-
})
|
|
1966
|
-
} else {
|
|
1967
|
-
console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
|
|
1968
|
-
const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
|
|
1969
|
-
stdio: 'inherit',
|
|
1970
|
-
env: runtimeEnv,
|
|
1971
|
-
cwd: appDir,
|
|
1972
|
-
})
|
|
1973
|
-
processes.push(workerProcess)
|
|
1974
|
-
managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)))
|
|
2003
|
+
const startBackgroundServices = async () => {
|
|
2004
|
+
if (stopping || backgroundStartAbort.signal.aborted) return
|
|
2005
|
+
|
|
2006
|
+
// Keep first-route compilation responsive: greenfield setup can
|
|
2007
|
+
// leave vector/fulltext jobs ready. When the dev wrapper is
|
|
2008
|
+
// active, wait for its /login + /backend warmup marker before
|
|
2009
|
+
// workers and scheduler begin consuming CPU and database I/O.
|
|
2010
|
+
const warmupReady = await waitForDevWarmupReadyFile(process.env.OM_DEV_WARMUP_READY_FILE, {
|
|
2011
|
+
timeoutMs: resolveDevWarmupReadyTimeoutMs(process.env),
|
|
2012
|
+
signal: backgroundStartAbort.signal,
|
|
2013
|
+
})
|
|
2014
|
+
if (warmupReady === 'aborted' || stopping || backgroundStartAbort.signal.aborted) return
|
|
2015
|
+
if (warmupReady === 'timeout') {
|
|
2016
|
+
console.warn('[server] Timed out waiting for dev warmup marker; starting background services anyway.')
|
|
1975
2017
|
}
|
|
1976
|
-
}
|
|
1977
2018
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2019
|
+
if (autoSpawnWorkersMode !== 'off') {
|
|
2020
|
+
const discoveredWorkers = getRegisteredCliWorkers()
|
|
2021
|
+
const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))]
|
|
2022
|
+
if (discoveredWorkerQueues.length === 0) {
|
|
2023
|
+
console.error('[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.')
|
|
2024
|
+
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
2025
|
+
console.log(`[server] Lazy worker auto-spawn enabled — workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`)
|
|
2026
|
+
activeLazySupervisor = startLazyWorkerSupervisor({
|
|
2027
|
+
mercatoBin,
|
|
2028
|
+
appDir,
|
|
2029
|
+
runtimeEnv,
|
|
2030
|
+
workers: discoveredWorkers,
|
|
2031
|
+
pollMs: resolveLazyPollMs(process.env),
|
|
2032
|
+
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
2033
|
+
})
|
|
2034
|
+
} else {
|
|
2035
|
+
console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
|
|
2036
|
+
const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
|
|
2037
|
+
stdio: 'inherit',
|
|
2038
|
+
env: runtimeEnv,
|
|
2039
|
+
cwd: appDir,
|
|
2040
|
+
})
|
|
2041
|
+
processes.push(workerProcess)
|
|
2042
|
+
waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)).then(backgroundExitResolve)
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
|
|
2047
|
+
if (schedulerCommand.status !== 'ok') {
|
|
2048
|
+
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
|
|
2049
|
+
} else if (autoSpawnSchedulerMode === 'lazy') {
|
|
2050
|
+
console.log('[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.')
|
|
2051
|
+
activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
|
|
2052
|
+
mercatoBin,
|
|
2053
|
+
appDir,
|
|
2054
|
+
runtimeEnv,
|
|
2055
|
+
pollMs: resolveLazySchedulerPollMs(process.env),
|
|
2056
|
+
restartOnUnexpectedExit: resolveLazySchedulerRestart(process.env),
|
|
2057
|
+
})
|
|
2058
|
+
} else {
|
|
2059
|
+
console.log('[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...')
|
|
2060
|
+
const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
|
|
2061
|
+
stdio: 'inherit',
|
|
2062
|
+
env: runtimeEnv,
|
|
2063
|
+
cwd: appDir,
|
|
2064
|
+
})
|
|
2065
|
+
processes.push(schedulerProcess)
|
|
2066
|
+
waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine').then(backgroundExitResolve)
|
|
2067
|
+
}
|
|
1999
2068
|
}
|
|
2000
2069
|
}
|
|
2070
|
+
nextRuntime.readyPromise.then(() => {
|
|
2071
|
+
void startBackgroundServices()
|
|
2072
|
+
})
|
|
2001
2073
|
|
|
2002
2074
|
if (generateWatcherMode === 'in-process') {
|
|
2003
2075
|
// Run the structural regeneration watcher inside this process
|