@open-mercato/cli 0.6.6-develop.6507.1.cc8b36505d → 0.6.6-develop.6513.1.12f0383844

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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/agentic/guides/module-facts.json +53 -53
  3. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  4. package/dist/agentic/guides/modules/api_docs.md +1 -1
  5. package/dist/agentic/guides/modules/api_keys.md +1 -1
  6. package/dist/agentic/guides/modules/attachments.md +1 -1
  7. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  8. package/dist/agentic/guides/modules/auth.md +1 -1
  9. package/dist/agentic/guides/modules/business_rules.md +1 -1
  10. package/dist/agentic/guides/modules/catalog.md +1 -1
  11. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  12. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  13. package/dist/agentic/guides/modules/checkout.md +1 -1
  14. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  15. package/dist/agentic/guides/modules/configs.md +1 -1
  16. package/dist/agentic/guides/modules/content.md +1 -1
  17. package/dist/agentic/guides/modules/currencies.md +1 -1
  18. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  19. package/dist/agentic/guides/modules/customers.md +1 -1
  20. package/dist/agentic/guides/modules/dashboards.md +1 -1
  21. package/dist/agentic/guides/modules/data_sync.md +1 -1
  22. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  23. package/dist/agentic/guides/modules/directory.md +1 -1
  24. package/dist/agentic/guides/modules/entities.md +1 -1
  25. package/dist/agentic/guides/modules/events.md +1 -1
  26. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  27. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  28. package/dist/agentic/guides/modules/generators.md +1 -1
  29. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  30. package/dist/agentic/guides/modules/integrations.md +1 -1
  31. package/dist/agentic/guides/modules/messages.md +1 -1
  32. package/dist/agentic/guides/modules/notifications.md +1 -1
  33. package/dist/agentic/guides/modules/onboarding.md +1 -1
  34. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  35. package/dist/agentic/guides/modules/perspectives.md +1 -1
  36. package/dist/agentic/guides/modules/planner.md +1 -1
  37. package/dist/agentic/guides/modules/portal.md +1 -1
  38. package/dist/agentic/guides/modules/progress.md +1 -1
  39. package/dist/agentic/guides/modules/query_index.md +1 -1
  40. package/dist/agentic/guides/modules/record_locks.md +1 -1
  41. package/dist/agentic/guides/modules/resources.md +1 -1
  42. package/dist/agentic/guides/modules/sales.md +1 -1
  43. package/dist/agentic/guides/modules/scheduler.md +1 -1
  44. package/dist/agentic/guides/modules/search.md +1 -1
  45. package/dist/agentic/guides/modules/security.md +1 -1
  46. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  47. package/dist/agentic/guides/modules/sso.md +1 -1
  48. package/dist/agentic/guides/modules/staff.md +1 -1
  49. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  50. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  51. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  52. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  53. package/dist/agentic/guides/modules/translations.md +1 -1
  54. package/dist/agentic/guides/modules/webhooks.md +1 -1
  55. package/dist/agentic/guides/modules/workflows.md +1 -1
  56. package/dist/lib/queue-worker-supervisor.js +35 -6
  57. package/dist/lib/queue-worker-supervisor.js.map +2 -2
  58. package/dist/mercato.js +40 -4
  59. package/dist/mercato.js.map +2 -2
  60. package/package.json +5 -5
  61. package/src/lib/__tests__/queue-worker-supervisor.test.ts +91 -0
  62. package/src/lib/queue-worker-supervisor.ts +43 -8
  63. package/src/mercato.ts +54 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.6-develop.6507.1.cc8b36505d",
3
+ "version": "0.6.6-develop.6513.1.12f0383844",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  "@mikro-orm/decorators": "^7.1.5",
61
61
  "@mikro-orm/migrations": "^7.1.5",
62
62
  "@mikro-orm/postgresql": "^7.1.5",
63
- "@open-mercato/queue": "0.6.6-develop.6507.1.cc8b36505d",
64
- "@open-mercato/shared": "0.6.6-develop.6507.1.cc8b36505d",
63
+ "@open-mercato/queue": "0.6.6-develop.6513.1.12f0383844",
64
+ "@open-mercato/shared": "0.6.6-develop.6513.1.12f0383844",
65
65
  "cross-spawn": "^7.0.6",
66
66
  "pg": "8.22.0",
67
67
  "semver": "^7.8.5",
@@ -71,10 +71,10 @@
71
71
  "typescript": "^6.0.3"
72
72
  },
73
73
  "peerDependencies": {
74
- "@open-mercato/shared": "0.6.6-develop.6507.1.cc8b36505d"
74
+ "@open-mercato/shared": "0.6.6-develop.6513.1.12f0383844"
75
75
  },
76
76
  "devDependencies": {
77
- "@open-mercato/shared": "0.6.6-develop.6507.1.cc8b36505d",
77
+ "@open-mercato/shared": "0.6.6-develop.6513.1.12f0383844",
78
78
  "@types/jest": "^30.0.0",
79
79
  "jest": "^30.4.2",
80
80
  "ts-jest": "^29.4.11"
@@ -170,6 +170,97 @@ describe('startLazyWorkerSupervisor', () => {
170
170
  expect((child as any).kill).toHaveBeenCalledWith('SIGTERM')
171
171
  })
172
172
 
173
+ it('starts the shared worker with extra arguments when an embedded scheduler needs it', async () => {
174
+ const child = createFakeChild()
175
+ const spawnFn = jest.fn(() => child) as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
176
+ const probeFn = jest.fn(async (queueName, strategy) =>
177
+ emptyProbe(queueName, strategy),
178
+ ) as jest.MockedFunction<LazySupervisorProbeFn>
179
+ const shouldStartSharedWorker = jest.fn(async () => true)
180
+
181
+ const handle = startLazyWorkerSupervisor({
182
+ mercatoBin: '/tmp/mercato',
183
+ appDir: '/tmp/app',
184
+ runtimeEnv: { ...process.env },
185
+ workers: [makeWorker('events')],
186
+ pollMs: 250,
187
+ restartOnUnexpectedExit: false,
188
+ strategy: 'local',
189
+ spawnMode: 'shared',
190
+ sharedWorkerArgs: ['--with-scheduler'],
191
+ shouldStartSharedWorker,
192
+ spawnFn,
193
+ probeFn,
194
+ logger: silentLogger,
195
+ })
196
+
197
+ await flushAsync(20)
198
+
199
+ expect(shouldStartSharedWorker).toHaveBeenCalled()
200
+ expect(spawnFn).toHaveBeenCalledTimes(1)
201
+ expect(spawnFn.mock.calls[0][1]).toEqual([
202
+ '/tmp/mercato',
203
+ 'queue',
204
+ 'worker',
205
+ '--all',
206
+ '--with-scheduler',
207
+ ])
208
+ expect(silentLogger.log).toHaveBeenCalledWith(
209
+ '[lazy-supervisor] Enabled schedule detected — starting shared worker for all queues',
210
+ )
211
+
212
+ await handle.close()
213
+ })
214
+
215
+ it('restarts the shared worker when an embedded scheduler remains enabled', async () => {
216
+ const firstChild = createFakeChild()
217
+ const secondChild = createFakeChild()
218
+ const spawnFn = jest
219
+ .fn()
220
+ .mockImplementationOnce(() => firstChild)
221
+ .mockImplementationOnce(() => secondChild) as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
222
+ const probeFn = jest.fn(async (queueName, strategy) =>
223
+ emptyProbe(queueName, strategy),
224
+ ) as jest.MockedFunction<LazySupervisorProbeFn>
225
+ const shouldStartSharedWorker = jest.fn(async () => true)
226
+ const shouldRestartSharedWorker = jest.fn(async () => true)
227
+
228
+ const handle = startLazyWorkerSupervisor({
229
+ mercatoBin: '/tmp/mercato',
230
+ appDir: '/tmp/app',
231
+ runtimeEnv: { ...process.env },
232
+ workers: [makeWorker('events')],
233
+ pollMs: 250,
234
+ restartOnUnexpectedExit: true,
235
+ strategy: 'local',
236
+ spawnMode: 'shared',
237
+ sharedWorkerArgs: ['--with-scheduler'],
238
+ shouldStartSharedWorker,
239
+ shouldRestartSharedWorker,
240
+ spawnFn,
241
+ probeFn,
242
+ logger: silentLogger,
243
+ })
244
+
245
+ await flushAsync(20)
246
+ expect(spawnFn).toHaveBeenCalledTimes(1)
247
+
248
+ firstChild.triggerExit(1)
249
+ await flushAsync(40)
250
+
251
+ expect(shouldRestartSharedWorker).toHaveBeenCalled()
252
+ expect(spawnFn).toHaveBeenCalledTimes(2)
253
+ expect(spawnFn.mock.calls[1][1]).toEqual([
254
+ '/tmp/mercato',
255
+ 'queue',
256
+ 'worker',
257
+ '--all',
258
+ '--with-scheduler',
259
+ ])
260
+
261
+ await handle.close()
262
+ })
263
+
173
264
  it('does not spawn when probe reports an error', async () => {
174
265
  const spawnFn = jest.fn() as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
175
266
  const probeFn = jest.fn(async (queueName, strategy) => ({
@@ -52,6 +52,12 @@ export type LazyWorkerSupervisorOptions = {
52
52
  restartOnUnexpectedExit: boolean
53
53
  spawnMode?: LazyWorkerSpawnMode
54
54
  strategy?: QueueStrategyType
55
+ /** Start the shared worker even with no ready queue jobs (for an embedded scheduler). */
56
+ shouldStartSharedWorker?: () => Promise<boolean>
57
+ /** Restart the shared worker with no ready queue jobs after an unexpected exit. */
58
+ shouldRestartSharedWorker?: () => Promise<boolean>
59
+ /** Extra arguments for the shared worker command only. */
60
+ sharedWorkerArgs?: readonly string[]
55
61
  /** Override for tests. Defaults to `child_process.spawn`. */
56
62
  spawnFn?: LazySupervisorSpawnFn
57
63
  /** Override for tests. Defaults to `getQueuePendingProbe`. */
@@ -236,21 +242,25 @@ export function startLazyWorkerSupervisor(
236
242
  })
237
243
  }
238
244
 
239
- function spawnSharedWorker(triggerQueueNames: string[]): void {
245
+ function spawnSharedWorker(triggerQueueNames: string[], schedulerTriggered = false): void {
240
246
  if (stopping) return
241
247
  if (activeSharedChild) return
242
248
  if (startingSharedWorker) return
243
249
  startingSharedWorker = true
244
250
 
245
- const triggerSummary = triggerQueueNames.length > 0
246
- ? ` after pending job(s) on ${triggerQueueNames.join(', ')}`
247
- : ''
248
- logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} starting shared worker for all queues`)
251
+ if (schedulerTriggered) {
252
+ logger.log('[lazy-supervisor] Enabled schedule detected starting shared worker for all queues')
253
+ } else {
254
+ const triggerSummary = triggerQueueNames.length > 0
255
+ ? ` in ${triggerQueueNames.join(', ')}`
256
+ : ''
257
+ logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} — starting shared worker for all queues`)
258
+ }
249
259
  let child: ChildProcess
250
260
  try {
251
261
  child = spawnFn(
252
262
  'node',
253
- [options.mercatoBin, 'queue', 'worker', '--all'],
263
+ [options.mercatoBin, 'queue', 'worker', '--all', ...(options.sharedWorkerArgs ?? [])],
254
264
  {
255
265
  stdio: 'inherit',
256
266
  env: options.runtimeEnv,
@@ -306,6 +316,18 @@ export function startLazyWorkerSupervisor(
306
316
  if (readyQueues.length > 0) {
307
317
  logger.warn('[lazy-supervisor] Restarting shared worker because jobs remain pending.')
308
318
  spawnSharedWorker(readyQueues)
319
+ return
320
+ }
321
+ if (options.shouldRestartSharedWorker) {
322
+ try {
323
+ if (await options.shouldRestartSharedWorker()) {
324
+ logger.warn('[lazy-supervisor] Restarting shared worker because an enabled schedule remains.')
325
+ spawnSharedWorker([], true)
326
+ }
327
+ } catch (err) {
328
+ const message = err instanceof Error ? err.message : String(err)
329
+ logger.warn(`[lazy-supervisor] Shared-worker restart condition failed: ${message}`)
330
+ }
309
331
  }
310
332
  })()
311
333
  })
@@ -355,8 +377,21 @@ export function startLazyWorkerSupervisor(
355
377
  spawnQueueWorker(group.queueName)
356
378
  }
357
379
  }
358
- if (spawnMode === 'shared' && readySharedQueues.length > 0) {
359
- spawnSharedWorker(readySharedQueues)
380
+ if (spawnMode === 'shared') {
381
+ if (readySharedQueues.length > 0) {
382
+ spawnSharedWorker(readySharedQueues)
383
+ return
384
+ }
385
+ if (options.shouldStartSharedWorker) {
386
+ try {
387
+ if (await options.shouldStartSharedWorker()) {
388
+ spawnSharedWorker([], true)
389
+ }
390
+ } catch (err) {
391
+ const message = err instanceof Error ? err.message : String(err)
392
+ logger.warn(`[lazy-supervisor] Shared-worker start condition failed: ${message}`)
393
+ }
394
+ }
360
395
  }
361
396
  }
362
397
 
package/src/mercato.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // Note: Generated files and DI container are imported statically to avoid ESM/CJS interop issues.
2
2
  // Commands that need to run before generation (e.g., `init`) handle missing modules gracefully.
3
3
 
4
- import { runWorker } from '@open-mercato/queue/worker'
4
+ import { registerWorkerShutdownHook, runWorker } from '@open-mercato/queue/worker'
5
5
  import type { Module, ModuleWorker } from '@open-mercato/shared/modules/registry'
6
6
  import { getCliModules, hasCliModules, registerCliModules } from './registry'
7
7
  export { getCliModules, hasCliModules, registerCliModules }
@@ -28,7 +28,7 @@ import {
28
28
  resolveLazySchedulerPollMs,
29
29
  resolveLazySchedulerRestart,
30
30
  } from './lib/auto-spawn-scheduler'
31
- import { startLazySchedulerSupervisor } from './lib/scheduler-supervisor'
31
+ import { probeEnabledSchedules, startLazySchedulerSupervisor } from './lib/scheduler-supervisor'
32
32
  import {
33
33
  startInProcessGenerateWatcher,
34
34
  type GenerateWatcherHandle,
@@ -77,6 +77,10 @@ function getRegisteredCliWorkers(modules: Module[] = getCliModules()): ModuleWor
77
77
  return allWorkers
78
78
  }
79
79
 
80
+ function shouldEmbedLocalSchedulerInSharedWorker(env: NodeJS.ProcessEnv = process.env): boolean {
81
+ return parseBooleanToken(env.OM_DEV_EMBED_SCHEDULER_IN_SHARED_WORKER) === true
82
+ }
83
+
80
84
  export function padByCodePointWidth(value: string, targetWidth: number): string {
81
85
  const valueWidth = [...value].length
82
86
  if (valueWidth >= targetWidth) return value
@@ -1548,6 +1552,7 @@ export async function run(argv = process.argv) {
1548
1552
  command: 'worker',
1549
1553
  run: async (args: string[]) => {
1550
1554
  const isAllQueues = args.includes('--all')
1555
+ const runSchedulerInWorker = isAllQueues && args.includes('--with-scheduler')
1551
1556
  const queueName = isAllQueues ? null : args[0]
1552
1557
 
1553
1558
  // Collect all discovered workers from modules
@@ -1616,6 +1621,26 @@ export async function run(argv = process.argv) {
1616
1621
 
1617
1622
  await Promise.all(workerPromises)
1618
1623
 
1624
+ if (runSchedulerInWorker) {
1625
+ const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
1626
+ if (queueStrategy !== 'local') {
1627
+ throw new Error('[worker] --with-scheduler is supported only with QUEUE_STRATEGY=local.')
1628
+ }
1629
+ const schedulerContainer = await createRequestContainer()
1630
+ const localScheduler = schedulerContainer.resolve('localSchedulerService') as {
1631
+ start?: () => Promise<void>
1632
+ stop?: () => Promise<void>
1633
+ } | undefined
1634
+ if (!localScheduler?.start || !localScheduler.stop) {
1635
+ throw new Error('[worker] Local scheduler service is unavailable.')
1636
+ }
1637
+ await localScheduler.start()
1638
+ registerWorkerShutdownHook(async () => {
1639
+ await localScheduler.stop?.()
1640
+ })
1641
+ console.log('[worker] Local scheduler started in the shared worker process.')
1642
+ }
1643
+
1619
1644
  console.log('[worker] All workers started. Press Ctrl+C to stop')
1620
1645
 
1621
1646
  // Keep the process alive
@@ -2088,6 +2113,13 @@ export async function run(argv = process.argv) {
2088
2113
  const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env)
2089
2114
  const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
2090
2115
  const schedulerCommand = lookupModuleCommand(getCliModules(), 'scheduler', 'start')
2116
+ const embedSchedulerInSharedWorker =
2117
+ shouldEmbedLocalSchedulerInSharedWorker(process.env)
2118
+ && queueStrategy === 'local'
2119
+ && autoSpawnWorkersMode === 'lazy'
2120
+ && resolveLazySpawnMode(process.env) === 'shared'
2121
+ && autoSpawnSchedulerMode !== 'off'
2122
+ && schedulerCommand.status === 'ok'
2091
2123
  const nextRuntime = startNextDev(runtimeEnv)
2092
2124
  const restartPromise = waitForDevRestart()
2093
2125
  const backgroundStartAbort = new AbortController()
@@ -2139,6 +2171,23 @@ export async function run(argv = process.argv) {
2139
2171
  pollMs: resolveLazyPollMs(process.env),
2140
2172
  restartOnUnexpectedExit: resolveLazyRestart(process.env),
2141
2173
  spawnMode: lazySpawnMode,
2174
+ ...(embedSchedulerInSharedWorker
2175
+ ? {
2176
+ sharedWorkerArgs: ['--with-scheduler'],
2177
+ shouldStartSharedWorker: async () => {
2178
+ const schedules = await probeEnabledSchedules(runtimeEnv)
2179
+ return !schedules.error && schedules.enabledSchedules > 0
2180
+ },
2181
+ ...(resolveLazySchedulerRestart(process.env)
2182
+ ? {
2183
+ shouldRestartSharedWorker: async () => {
2184
+ const schedules = await probeEnabledSchedules(runtimeEnv)
2185
+ return !schedules.error && schedules.enabledSchedules > 0
2186
+ },
2187
+ }
2188
+ : {}),
2189
+ }
2190
+ : {}),
2142
2191
  })
2143
2192
  } else {
2144
2193
  console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
@@ -2152,7 +2201,9 @@ export async function run(argv = process.argv) {
2152
2201
  }
2153
2202
  }
2154
2203
 
2155
- if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
2204
+ if (embedSchedulerInSharedWorker && activeLazySupervisor) {
2205
+ console.log('[server] Local scheduler will run inside the lazy shared worker process.')
2206
+ } else if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
2156
2207
  if (schedulerCommand.status !== 'ok') {
2157
2208
  console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
2158
2209
  } else if (autoSpawnSchedulerMode === 'lazy') {