@open-mercato/cli 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/agentic/shared/AGENTS.md.template +1 -1
  3. package/dist/lib/__integration__/TC-INT-007.spec.js +201 -0
  4. package/dist/lib/__integration__/TC-INT-007.spec.js.map +7 -0
  5. package/dist/lib/dev-env-reload.js +89 -0
  6. package/dist/lib/dev-env-reload.js.map +7 -0
  7. package/dist/lib/generators/extensions/ai-agents.js +218 -0
  8. package/dist/lib/generators/extensions/ai-agents.js.map +7 -0
  9. package/dist/lib/generators/extensions/ai-tools.js +56 -1
  10. package/dist/lib/generators/extensions/ai-tools.js.map +2 -2
  11. package/dist/lib/generators/extensions/index.js +2 -0
  12. package/dist/lib/generators/extensions/index.js.map +2 -2
  13. package/dist/lib/testing/integration-discovery.js +102 -5
  14. package/dist/lib/testing/integration-discovery.js.map +2 -2
  15. package/dist/mercato.js +85 -44
  16. package/dist/mercato.js.map +2 -2
  17. package/package.json +5 -5
  18. package/src/__tests__/mercato.test.ts +112 -0
  19. package/src/lib/__integration__/TC-INT-007.spec.ts +228 -0
  20. package/src/lib/__tests__/dev-env-reload.test.ts +62 -0
  21. package/src/lib/dev-env-reload.ts +110 -0
  22. package/src/lib/generators/__tests__/module-subset.test.ts +14 -0
  23. package/src/lib/generators/__tests__/output-snapshots.test.ts +17 -0
  24. package/src/lib/generators/__tests__/scanner.test.ts +1 -1
  25. package/src/lib/generators/__tests__/structural-contracts.test.ts +26 -0
  26. package/src/lib/generators/extensions/ai-agents.ts +240 -0
  27. package/src/lib/generators/extensions/ai-tools.ts +72 -1
  28. package/src/lib/generators/extensions/index.ts +2 -0
  29. package/src/lib/testing/__tests__/integration-discovery.test.ts +68 -0
  30. package/src/lib/testing/integration-discovery.ts +127 -3
  31. package/src/mercato.ts +100 -46
package/src/mercato.ts CHANGED
@@ -12,6 +12,7 @@ import { resolveInitDerivedSecrets } from './lib/init-secrets'
12
12
  import { parseModuleInstallArgs } from './lib/module-install-args'
13
13
  import { resolveNextBuildIdCandidate } from './lib/next-build-id'
14
14
  import { acquireServerStartLock } from './lib/server-start-lock'
15
+ import { createDevEnvReloader, watchDevEnvFiles } from './lib/dev-env-reload'
15
16
  // Lazy-imported to avoid pulling in `testcontainers` (devDependency) at startup
16
17
  const lazyIntegration = () => import('./lib/testing/integration')
17
18
  import type { ChildProcess } from 'node:child_process'
@@ -19,6 +20,7 @@ import path from 'node:path'
19
20
  import fs from 'node:fs'
20
21
 
21
22
  let envLoaded = false
23
+ const initialProcessEnvironmentEntries = Object.entries(process.env)
22
24
 
23
25
  async function runWithCapturedExitCode(action: () => Promise<void>): Promise<number> {
24
26
  const previousExitCode = process.exitCode
@@ -311,6 +313,14 @@ type ManagedProcessExitResult = {
311
313
  signal: NodeJS.Signals | null
312
314
  }
313
315
 
316
+ type DevServerRestartResult = {
317
+ label: string
318
+ restart: true
319
+ filePath: string
320
+ }
321
+
322
+ type DevServerExitResult = ManagedProcessExitResult | DevServerRestartResult
323
+
314
324
  type ModuleCommandLookupResult =
315
325
  | {
316
326
  status: 'ok'
@@ -347,6 +357,10 @@ function createManagedProcessExitError(result: ManagedProcessExitResult): Error
347
357
  return new Error(`[server] ${result.label} exited unexpectedly with ${formatManagedProcessExitStatus(result)}.`)
348
358
  }
349
359
 
360
+ function isDevServerRestartResult(result: DevServerExitResult): result is DevServerRestartResult {
361
+ return 'restart' in result && result.restart === true
362
+ }
363
+
350
364
  function formatQueueWorkerLabel(queueNames: string[]): string {
351
365
  if (queueNames.length === 0) return 'Queue worker'
352
366
  const sorted = [...queueNames].sort((a, b) => a.localeCompare(b))
@@ -1642,12 +1656,11 @@ export async function run(argv = process.argv) {
1642
1656
  const appDir = env.appDir
1643
1657
  const nodeModulesBases = Array.from(new Set([env.rootDir, appDir]))
1644
1658
 
1645
- const processes: ChildProcess[] = []
1646
- const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== 'false'
1647
- const autoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER !== 'false'
1648
- const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
1659
+ let processes: ChildProcess[] = []
1649
1660
  let didRetryCorruptedTurbopackCache = false
1650
- const schedulerCommand = lookupModuleCommand(getCliModules(), 'scheduler', 'start')
1661
+ let stopping = false
1662
+ let envChangePromiseResolve: ((result: DevServerRestartResult) => void) | null = null
1663
+ const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries)
1651
1664
 
1652
1665
  function cleanup() {
1653
1666
  console.log('[server] Shutting down...')
@@ -1677,10 +1690,17 @@ export async function run(argv = process.argv) {
1677
1690
  } catch {
1678
1691
  // Lock file may already be removed by Next.js — ignore
1679
1692
  }
1693
+ processes = []
1680
1694
  }
1681
1695
 
1682
- process.on('SIGTERM', cleanup)
1683
- process.on('SIGINT', cleanup)
1696
+ process.on('SIGTERM', () => {
1697
+ stopping = true
1698
+ cleanup()
1699
+ })
1700
+ process.on('SIGINT', () => {
1701
+ stopping = true
1702
+ cleanup()
1703
+ })
1684
1704
 
1685
1705
  console.log('[server] Starting Open Mercato in dev mode...')
1686
1706
 
@@ -1692,11 +1712,24 @@ export async function run(argv = process.argv) {
1692
1712
  const nextBin = resolveInstalledBinary(nodeModulesBases, 'next/dist/bin/next')
1693
1713
  const mercatoBin = resolveInstalledBinary(nodeModulesBases, '@open-mercato/cli/bin/mercato')
1694
1714
 
1695
- const startNextDev = (): Promise<ManagedProcessExitResult> =>
1715
+ const stopEnvWatcher = watchDevEnvFiles(appDir, (filePath) => {
1716
+ envChangePromiseResolve?.({
1717
+ label: 'Environment file change',
1718
+ restart: true,
1719
+ filePath,
1720
+ })
1721
+ })
1722
+
1723
+ const waitForEnvChange = (): Promise<DevServerRestartResult> =>
1724
+ new Promise((resolve) => {
1725
+ envChangePromiseResolve = resolve
1726
+ })
1727
+
1728
+ const startNextDev = (runtimeEnv: NodeJS.ProcessEnv): Promise<ManagedProcessExitResult> =>
1696
1729
  new Promise((resolve) => {
1697
1730
  const nextProcess = spawn('node', [nextBin, 'dev', '--turbopack'], {
1698
1731
  stdio: ['inherit', 'pipe', 'pipe'],
1699
- env: process.env,
1732
+ env: runtimeEnv,
1700
1733
  cwd: appDir,
1701
1734
  })
1702
1735
  processes.push(nextProcess)
@@ -1725,7 +1758,7 @@ export async function run(argv = process.argv) {
1725
1758
  didRetryCorruptedTurbopackCache = true
1726
1759
  console.log('[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...')
1727
1760
  removeTurbopackDevCache(appDir)
1728
- return resolve(await startNextDev())
1761
+ return resolve(await startNextDev(runtimeEnv))
1729
1762
  }
1730
1763
  resolve({
1731
1764
  label: 'Next.js dev server',
@@ -1735,47 +1768,68 @@ export async function run(argv = process.argv) {
1735
1768
  })
1736
1769
  })
1737
1770
 
1738
- const nextExitPromise = startNextDev()
1739
- const managedExitPromises: Promise<ManagedProcessExitResult>[] = [nextExitPromise]
1771
+ try {
1772
+ while (!stopping) {
1773
+ envReloader.reload()
1774
+ const runtimeEnv = buildServerProcessEnvironment(process.env)
1775
+ const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== 'false'
1776
+ const autoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER !== 'false'
1777
+ const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
1778
+ const schedulerCommand = lookupModuleCommand(getCliModules(), 'scheduler', 'start')
1779
+ const managedExitPromises: Promise<DevServerExitResult>[] = [
1780
+ startNextDev(runtimeEnv),
1781
+ waitForEnvChange(),
1782
+ ]
1783
+
1784
+ // Start workers if enabled
1785
+ if (autoSpawnWorkers) {
1786
+ const discoveredWorkerQueues = [...new Set(getRegisteredCliWorkers().map((worker) => worker.queue))]
1787
+ if (discoveredWorkerQueues.length === 0) {
1788
+ 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.')
1789
+ } else {
1790
+ console.log('[server] Starting workers for all queues...')
1791
+ const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
1792
+ stdio: 'inherit',
1793
+ env: runtimeEnv,
1794
+ cwd: appDir,
1795
+ })
1796
+ processes.push(workerProcess)
1797
+ managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)))
1798
+ }
1799
+ }
1740
1800
 
1741
- // Start workers if enabled
1742
- if (autoSpawnWorkers) {
1743
- const discoveredWorkerQueues = [...new Set(getRegisteredCliWorkers().map((worker) => worker.queue))]
1744
- if (discoveredWorkerQueues.length === 0) {
1745
- 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.')
1746
- } else {
1747
- console.log('[server] Starting workers for all queues...')
1748
- const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
1749
- stdio: 'inherit',
1750
- env: process.env,
1751
- cwd: appDir,
1752
- })
1753
- processes.push(workerProcess)
1754
- managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)))
1755
- }
1756
- }
1801
+ if (autoSpawnScheduler && queueStrategy === 'local') {
1802
+ if (schedulerCommand.status !== 'ok') {
1803
+ console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
1804
+ } else {
1805
+ console.log('[server] Starting scheduler polling engine...')
1806
+ const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
1807
+ stdio: 'inherit',
1808
+ env: runtimeEnv,
1809
+ cwd: appDir,
1810
+ })
1811
+ processes.push(schedulerProcess)
1812
+ managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine'))
1813
+ }
1814
+ }
1757
1815
 
1758
- if (autoSpawnScheduler && queueStrategy === 'local') {
1759
- if (schedulerCommand.status !== 'ok') {
1760
- console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
1761
- } else {
1762
- console.log('[server] Starting scheduler polling engine...')
1763
- const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
1764
- stdio: 'inherit',
1765
- env: process.env,
1766
- cwd: appDir,
1767
- })
1768
- processes.push(schedulerProcess)
1769
- managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine'))
1770
- }
1771
- }
1816
+ const firstExit = await Promise.race(managedExitPromises)
1817
+ await cleanupAndWait()
1818
+ envChangePromiseResolve = null
1772
1819
 
1773
- const firstExit = await Promise.race(managedExitPromises)
1820
+ if (isDevServerRestartResult(firstExit)) {
1821
+ console.log(`[server] Detected environment file change (${path.basename(firstExit.filePath)}). Restarting app runtime...`)
1822
+ continue
1823
+ }
1774
1824
 
1775
- await cleanupAndWait()
1825
+ if (!isExpectedManagedExitSignal(firstExit.signal)) {
1826
+ throw createManagedProcessExitError(firstExit)
1827
+ }
1776
1828
 
1777
- if (!isExpectedManagedExitSignal(firstExit.signal)) {
1778
- throw createManagedProcessExitError(firstExit)
1829
+ stopping = true
1830
+ }
1831
+ } finally {
1832
+ stopEnvWatcher()
1779
1833
  }
1780
1834
  },
1781
1835
  },