@open-mercato/cli 0.6.4-develop.3949.1.adc3d0b3b1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.4-develop.3949.1.adc3d0b3b1",
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.3949.1.adc3d0b3b1",
63
- "@open-mercato/shared": "0.6.4-develop.3949.1.adc3d0b3b1",
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.3949.1.adc3d0b3b1"
73
+ "@open-mercato/shared": "0.6.4-develop.3962.1.70f30e284c"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.6.4-develop.3949.1.adc3d0b3b1",
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
- child.exitCode = autoExit.code
41
- child.signalCode = autoExit.signal ?? null
42
- child.emit('exit', child.exitCode, child.signalCode)
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
- const stop = watchDevRuntimeFiles(appDir, (filePath) => {
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): Promise<ManagedProcessExitResult> =>
1882
- new Promise((resolve) => {
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
- return resolve(await startNextDev(runtimeEnv))
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
- startNextDev(runtimeEnv),
1947
- waitForDevRestart(),
1998
+ nextRuntime.exitPromise,
1999
+ restartPromise,
2000
+ backgroundExitPromise,
1948
2001
  ]
1949
2002
 
1950
- // Start workers if enabled
1951
- if (autoSpawnWorkersMode !== 'off') {
1952
- const discoveredWorkers = getRegisteredCliWorkers()
1953
- const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))]
1954
- if (discoveredWorkerQueues.length === 0) {
1955
- 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.')
1956
- } else if (autoSpawnWorkersMode === 'lazy') {
1957
- console.log(`[server] Lazy worker auto-spawn enabled — workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`)
1958
- activeLazySupervisor = startLazyWorkerSupervisor({
1959
- mercatoBin,
1960
- appDir,
1961
- runtimeEnv,
1962
- workers: discoveredWorkers,
1963
- pollMs: resolveLazyPollMs(process.env),
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
- if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
1979
- if (schedulerCommand.status !== 'ok') {
1980
- console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
1981
- } else if (autoSpawnSchedulerMode === 'lazy') {
1982
- console.log('[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.')
1983
- activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
1984
- mercatoBin,
1985
- appDir,
1986
- runtimeEnv,
1987
- pollMs: resolveLazySchedulerPollMs(process.env),
1988
- restartOnUnexpectedExit: resolveLazySchedulerRestart(process.env),
1989
- })
1990
- } else {
1991
- console.log('[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...')
1992
- const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
1993
- stdio: 'inherit',
1994
- env: runtimeEnv,
1995
- cwd: appDir,
1996
- })
1997
- processes.push(schedulerProcess)
1998
- managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine'))
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