@open-mercato/cli 0.6.3-develop.3876.1.d40fe4ec2d → 0.6.3-develop.3894.1.352abf4240
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/in-process-generate-watcher-mode.js +13 -0
- package/dist/lib/in-process-generate-watcher-mode.js.map +7 -0
- package/dist/lib/in-process-generate-watcher.js +112 -0
- package/dist/lib/in-process-generate-watcher.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -1
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +99 -75
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +9 -0
- package/src/lib/__tests__/in-process-generate-watcher-mode.test.ts +20 -0
- package/src/lib/__tests__/in-process-generate-watcher.test.ts +191 -0
- package/src/lib/in-process-generate-watcher-mode.ts +21 -0
- package/src/lib/in-process-generate-watcher.ts +175 -0
- package/src/lib/testing/integration.ts +5 -1
- package/src/mercato.ts +126 -81
package/src/mercato.ts
CHANGED
|
@@ -21,6 +21,14 @@ import {
|
|
|
21
21
|
resolveLazySchedulerRestart,
|
|
22
22
|
} from './lib/auto-spawn-scheduler'
|
|
23
23
|
import { startLazySchedulerSupervisor } from './lib/scheduler-supervisor'
|
|
24
|
+
import {
|
|
25
|
+
startInProcessGenerateWatcher,
|
|
26
|
+
type GenerateWatcherHandle,
|
|
27
|
+
} from './lib/in-process-generate-watcher'
|
|
28
|
+
import {
|
|
29
|
+
resolveGenerateWatcherMode,
|
|
30
|
+
type GenerateWatcherMode,
|
|
31
|
+
} from './lib/in-process-generate-watcher-mode'
|
|
24
32
|
import { parseModuleInstallArgs } from './lib/module-install-args'
|
|
25
33
|
import { resolveNextBuildIdCandidate } from './lib/next-build-id'
|
|
26
34
|
import { acquireServerStartLock } from './lib/server-start-lock'
|
|
@@ -556,6 +564,58 @@ async function runPostGenerateStructuralCachePurge(quiet: boolean): Promise<void
|
|
|
556
564
|
}
|
|
557
565
|
}
|
|
558
566
|
|
|
567
|
+
/**
|
|
568
|
+
* Generator suite invoked by both `mercato generate all` and the in-process
|
|
569
|
+
* generate watcher embedded in `mercato server dev`. Hoisted to module scope
|
|
570
|
+
* so the watcher embedded in the server lifecycle can reuse the same closure
|
|
571
|
+
* without re-importing the closure-scoped version inside `buildBaseModules`.
|
|
572
|
+
*/
|
|
573
|
+
async function runGeneratorSuite(quiet: boolean): Promise<void> {
|
|
574
|
+
const { createResolver } = await import('./lib/resolver')
|
|
575
|
+
const {
|
|
576
|
+
generateEntityIds,
|
|
577
|
+
generateModuleRegistry,
|
|
578
|
+
generateModuleRegistryApp,
|
|
579
|
+
generateModuleRegistryCli,
|
|
580
|
+
generateModuleEntities,
|
|
581
|
+
generateModuleDi,
|
|
582
|
+
generateModulePackageSources,
|
|
583
|
+
generateOpenApi,
|
|
584
|
+
} = await import('./lib/generators')
|
|
585
|
+
const resolver = createResolver()
|
|
586
|
+
await generateEntityIds({ resolver, quiet })
|
|
587
|
+
await generateModuleRegistry({ resolver, quiet })
|
|
588
|
+
await generateModuleRegistryApp({ resolver, quiet })
|
|
589
|
+
await generateModuleRegistryCli({ resolver, quiet })
|
|
590
|
+
await generateModuleEntities({ resolver, quiet })
|
|
591
|
+
await generateModuleDi({ resolver, quiet })
|
|
592
|
+
await generateModulePackageSources({ resolver, quiet })
|
|
593
|
+
await generateOpenApi({ resolver, quiet })
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Builds the structural-fingerprint function used by the in-process generate
|
|
598
|
+
* watcher. Walks the same module roots the legacy `mercato generate watch`
|
|
599
|
+
* CLI command tracked, so the polling semantics are byte-for-byte identical.
|
|
600
|
+
*/
|
|
601
|
+
function createGenerateWatchChecksumFn(): () => Promise<string> {
|
|
602
|
+
return async () => {
|
|
603
|
+
const { createResolver } = await import('./lib/resolver')
|
|
604
|
+
const { calculateStructureChecksum } = await import('./lib/utils')
|
|
605
|
+
const resolver = createResolver()
|
|
606
|
+
const tracked = new Set<string>([
|
|
607
|
+
path.join(resolver.getAppDir(), 'src', 'modules.ts'),
|
|
608
|
+
path.join(resolver.getAppDir(), 'src', 'modules'),
|
|
609
|
+
])
|
|
610
|
+
for (const entry of resolver.loadEnabledModules()) {
|
|
611
|
+
const roots = resolver.getModulePaths(entry)
|
|
612
|
+
tracked.add(roots.appBase)
|
|
613
|
+
tracked.add(roots.pkgBase)
|
|
614
|
+
}
|
|
615
|
+
return calculateStructureChecksum(Array.from(tracked))
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
559
619
|
// Build all CLI modules (registered + built-in)
|
|
560
620
|
async function buildAllModules(): Promise<Module[]> {
|
|
561
621
|
const modules = getCliModules()
|
|
@@ -1475,30 +1535,6 @@ export async function run(argv = process.argv) {
|
|
|
1475
1535
|
},
|
|
1476
1536
|
],
|
|
1477
1537
|
} as any)
|
|
1478
|
-
|
|
1479
|
-
const runGeneratorSuite = async (quiet: boolean) => {
|
|
1480
|
-
const { createResolver } = await import('./lib/resolver')
|
|
1481
|
-
const {
|
|
1482
|
-
generateEntityIds,
|
|
1483
|
-
generateModuleRegistry,
|
|
1484
|
-
generateModuleRegistryApp,
|
|
1485
|
-
generateModuleRegistryCli,
|
|
1486
|
-
generateModuleEntities,
|
|
1487
|
-
generateModuleDi,
|
|
1488
|
-
generateModulePackageSources,
|
|
1489
|
-
generateOpenApi,
|
|
1490
|
-
} = await import('./lib/generators')
|
|
1491
|
-
const resolver = createResolver()
|
|
1492
|
-
|
|
1493
|
-
await generateEntityIds({ resolver, quiet })
|
|
1494
|
-
await generateModuleRegistry({ resolver, quiet })
|
|
1495
|
-
await generateModuleRegistryApp({ resolver, quiet })
|
|
1496
|
-
await generateModuleRegistryCli({ resolver, quiet })
|
|
1497
|
-
await generateModuleEntities({ resolver, quiet })
|
|
1498
|
-
await generateModuleDi({ resolver, quiet })
|
|
1499
|
-
await generateModulePackageSources({ resolver, quiet })
|
|
1500
|
-
await generateOpenApi({ resolver, quiet })
|
|
1501
|
-
}
|
|
1502
1538
|
|
|
1503
1539
|
// Built-in CLI module: generate
|
|
1504
1540
|
all.push({
|
|
@@ -1518,75 +1554,46 @@ export async function run(argv = process.argv) {
|
|
|
1518
1554
|
{
|
|
1519
1555
|
command: 'watch',
|
|
1520
1556
|
run: async (args: string[]) => {
|
|
1521
|
-
const { createResolver } = await import('./lib/resolver')
|
|
1522
|
-
const { calculateStructureChecksum } = await import('./lib/utils')
|
|
1523
1557
|
const quiet = args.includes('--quiet') || args.includes('-q')
|
|
1524
1558
|
const skipInitial = args.includes('--skip-initial')
|
|
1525
1559
|
const intervalArg = args.find((arg) => arg.startsWith('--interval='))
|
|
1526
1560
|
const parsedInterval = intervalArg ? Number.parseInt(intervalArg.split('=')[1] ?? '', 10) : NaN
|
|
1527
1561
|
const intervalMs = Number.isFinite(parsedInterval) && parsedInterval >= 250 ? parsedInterval : 1000
|
|
1528
|
-
let previousChecksum = ''
|
|
1529
|
-
let running = false
|
|
1530
|
-
let pending = false
|
|
1531
|
-
|
|
1532
|
-
const getTrackedPaths = () => {
|
|
1533
|
-
const resolver = createResolver()
|
|
1534
|
-
const tracked = new Set<string>([
|
|
1535
|
-
path.join(resolver.getAppDir(), 'src', 'modules.ts'),
|
|
1536
|
-
path.join(resolver.getAppDir(), 'src', 'modules'),
|
|
1537
|
-
])
|
|
1538
|
-
for (const entry of resolver.loadEnabledModules()) {
|
|
1539
|
-
const roots = resolver.getModulePaths(entry)
|
|
1540
|
-
tracked.add(roots.appBase)
|
|
1541
|
-
tracked.add(roots.pkgBase)
|
|
1542
|
-
}
|
|
1543
|
-
return Array.from(tracked)
|
|
1544
|
-
}
|
|
1545
1562
|
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
try {
|
|
1553
|
-
if (!quiet) {
|
|
1554
|
-
console.log(`[generate:watch] Regenerating (${reason})...`)
|
|
1555
|
-
}
|
|
1563
|
+
const watcher = startInProcessGenerateWatcher({
|
|
1564
|
+
pollMs: intervalMs,
|
|
1565
|
+
skipInitial,
|
|
1566
|
+
quiet,
|
|
1567
|
+
computeStructureChecksum: createGenerateWatchChecksumFn(),
|
|
1568
|
+
runGenerators: async () => {
|
|
1556
1569
|
await runGeneratorSuite(true)
|
|
1557
1570
|
await runPostGenerateStructuralCachePurge(true)
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
}
|
|
1561
|
-
} catch (error) {
|
|
1562
|
-
console.error('[generate:watch] Generation failed:', error instanceof Error ? error.message : error)
|
|
1563
|
-
} finally {
|
|
1564
|
-
running = false
|
|
1565
|
-
if (pending) {
|
|
1566
|
-
pending = false
|
|
1567
|
-
await runWatchGeneration('queued change')
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
+
},
|
|
1572
|
+
})
|
|
1571
1573
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
+
const shutdownSignals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
|
|
1575
|
+
let shuttingDown = false
|
|
1576
|
+
const handleSignal = () => {
|
|
1577
|
+
if (shuttingDown) return
|
|
1578
|
+
shuttingDown = true
|
|
1579
|
+
void watcher.close()
|
|
1574
1580
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
if (skipInitial) {
|
|
1578
|
-
console.log('[generate:watch] Skipping initial regeneration and watching the current generated state.')
|
|
1579
|
-
}
|
|
1580
|
-
console.log(`[generate:watch] Watching structural module files every ${intervalMs}ms`)
|
|
1581
|
+
for (const signal of shutdownSignals) {
|
|
1582
|
+
process.once(signal, handleSignal)
|
|
1581
1583
|
}
|
|
1582
1584
|
|
|
1583
|
-
//
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
await
|
|
1585
|
+
// The watcher's polling timer is `unref()`-ed so the event loop
|
|
1586
|
+
// would otherwise exit immediately for a standalone CLI invocation.
|
|
1587
|
+
// `keepAlive` holds the loop open until a shutdown signal calls
|
|
1588
|
+
// `watcher.close()`, which resolves `watcher.done`.
|
|
1589
|
+
const keepAlive = setInterval(() => {}, 1 << 30)
|
|
1590
|
+
try {
|
|
1591
|
+
await watcher.done
|
|
1592
|
+
} finally {
|
|
1593
|
+
clearInterval(keepAlive)
|
|
1594
|
+
for (const signal of shutdownSignals) {
|
|
1595
|
+
process.removeListener(signal, handleSignal)
|
|
1596
|
+
}
|
|
1590
1597
|
}
|
|
1591
1598
|
},
|
|
1592
1599
|
},
|
|
@@ -1686,6 +1693,8 @@ export async function run(argv = process.argv) {
|
|
|
1686
1693
|
let envChangePromiseResolve: ((result: DevServerRestartResult) => void) | null = null
|
|
1687
1694
|
let activeLazySupervisor: ReturnType<typeof startLazyWorkerSupervisor> | null = null
|
|
1688
1695
|
let activeLazySchedulerSupervisor: ReturnType<typeof startLazySchedulerSupervisor> | null = null
|
|
1696
|
+
let activeGenerateWatcher: GenerateWatcherHandle | null = null
|
|
1697
|
+
const generateWatcherMode: GenerateWatcherMode = resolveGenerateWatcherMode(process.env)
|
|
1689
1698
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries)
|
|
1690
1699
|
|
|
1691
1700
|
function cleanup() {
|
|
@@ -1701,6 +1710,9 @@ export async function run(argv = process.argv) {
|
|
|
1701
1710
|
if (activeLazySchedulerSupervisor) {
|
|
1702
1711
|
void activeLazySchedulerSupervisor.close().catch(() => undefined)
|
|
1703
1712
|
}
|
|
1713
|
+
if (activeGenerateWatcher) {
|
|
1714
|
+
void activeGenerateWatcher.close().catch(() => undefined)
|
|
1715
|
+
}
|
|
1704
1716
|
}
|
|
1705
1717
|
|
|
1706
1718
|
async function cleanupAndWait() {
|
|
@@ -1731,6 +1743,14 @@ export async function run(argv = process.argv) {
|
|
|
1731
1743
|
}
|
|
1732
1744
|
activeLazySchedulerSupervisor = null
|
|
1733
1745
|
}
|
|
1746
|
+
if (activeGenerateWatcher) {
|
|
1747
|
+
try {
|
|
1748
|
+
await activeGenerateWatcher.close()
|
|
1749
|
+
} catch {
|
|
1750
|
+
// In-process generate watcher close errors must never block dev shutdown.
|
|
1751
|
+
}
|
|
1752
|
+
activeGenerateWatcher = null
|
|
1753
|
+
}
|
|
1734
1754
|
// Safety net: remove Next.js dev lock file in case the child didn't clean up
|
|
1735
1755
|
const lockFile = path.join(appDir, '.mercato', 'next', 'dev', 'lock')
|
|
1736
1756
|
try {
|
|
@@ -1881,6 +1901,31 @@ export async function run(argv = process.argv) {
|
|
|
1881
1901
|
}
|
|
1882
1902
|
}
|
|
1883
1903
|
|
|
1904
|
+
if (generateWatcherMode === 'in-process') {
|
|
1905
|
+
// Run the structural regeneration watcher inside this process
|
|
1906
|
+
// instead of spawning a dedicated `mercato generate watch --skip-initial`
|
|
1907
|
+
// sidecar. Saves ~190 MB of resident RSS on a typical dev box
|
|
1908
|
+
// (measured against the legacy sidecar). Opt back into the
|
|
1909
|
+
// sidecar with `OM_DEV_GENERATE_WATCH_MODE=legacy` if needed.
|
|
1910
|
+
console.log('[server] In-process generate watcher enabled — structural changes will regenerate without a sidecar process.')
|
|
1911
|
+
activeGenerateWatcher = startInProcessGenerateWatcher({
|
|
1912
|
+
// `--skip-initial` equivalent: `yarn dev` always runs an
|
|
1913
|
+
// initial `mercato generate` before reaching the server
|
|
1914
|
+
// command, so the watcher must not re-run generators at
|
|
1915
|
+
// boot time. Otherwise dev startup pays a generator pass
|
|
1916
|
+
// twice in a row.
|
|
1917
|
+
skipInitial: true,
|
|
1918
|
+
quiet: false,
|
|
1919
|
+
computeStructureChecksum: createGenerateWatchChecksumFn(),
|
|
1920
|
+
runGenerators: async () => {
|
|
1921
|
+
await runGeneratorSuite(true)
|
|
1922
|
+
await runPostGenerateStructuralCachePurge(true)
|
|
1923
|
+
},
|
|
1924
|
+
})
|
|
1925
|
+
} else {
|
|
1926
|
+
console.log('[server] Legacy out-of-process generate watcher selected via OM_DEV_GENERATE_WATCH_MODE=legacy — expect the dev orchestrator to spawn `mercato generate watch --skip-initial`.')
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1884
1929
|
const firstExit = await Promise.race(managedExitPromises)
|
|
1885
1930
|
await cleanupAndWait()
|
|
1886
1931
|
envChangePromiseResolve = null
|