@open-mercato/cli 0.6.3 → 0.6.4-canary.3910.1.0ff5e9e9be
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/AGENTS.md +1 -1
- package/dist/agentic/shared/AGENTS.md.template +0 -1
- package/dist/lib/dev-env-reload.js +1 -40
- package/dist/lib/dev-env-reload.js.map +2 -2
- package/dist/lib/testing/integration.js +0 -7
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +98 -244
- package/dist/mercato.js.map +2 -2
- package/package.json +8 -7
- package/src/__tests__/mercato.test.ts +3 -13
- package/src/lib/__tests__/dev-env-reload.test.ts +1 -21
- package/src/lib/dev-env-reload.ts +0 -47
- package/src/lib/testing/integration.ts +0 -7
- package/src/mercato.ts +67 -241
- package/dist/lib/generate-watch-structure.js +0 -153
- package/dist/lib/generate-watch-structure.js.map +0 -7
- package/src/lib/__tests__/generate-watch-structure.test.ts +0 -80
- package/src/lib/generate-watch-structure.ts +0 -170
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4-canary.3910.1.0ff5e9e9be",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -59,21 +59,21 @@
|
|
|
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.
|
|
63
|
-
"@open-mercato/shared": "0.6.
|
|
62
|
+
"@open-mercato/queue": "0.6.4-canary.3910.1.0ff5e9e9be",
|
|
63
|
+
"@open-mercato/shared": "0.6.4-canary.3910.1.0ff5e9e9be",
|
|
64
64
|
"cross-spawn": "^7.0.6",
|
|
65
65
|
"pg": "8.21.0",
|
|
66
66
|
"semver": "^7.8.1",
|
|
67
|
-
"testcontainers": "^12.0.
|
|
67
|
+
"testcontainers": "^12.0.0",
|
|
68
68
|
"ts-morph": "^28.0.0",
|
|
69
69
|
"tsx": "^4.22.3",
|
|
70
70
|
"typescript": "^6.0.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@open-mercato/shared": "0.6.
|
|
73
|
+
"@open-mercato/shared": "0.6.4-canary.3910.1.0ff5e9e9be"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.
|
|
76
|
+
"@open-mercato/shared": "0.6.4-canary.3910.1.0ff5e9e9be",
|
|
77
77
|
"@types/jest": "^30.0.0",
|
|
78
78
|
"jest": "^30.4.2",
|
|
79
79
|
"ts-jest": "^29.4.11"
|
|
@@ -85,5 +85,6 @@
|
|
|
85
85
|
"type": "git",
|
|
86
86
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
87
87
|
"directory": "packages/cli"
|
|
88
|
-
}
|
|
88
|
+
},
|
|
89
|
+
"stableVersion": "0.6.3"
|
|
89
90
|
}
|
|
@@ -37,18 +37,9 @@ function buildMockChildProcessModule(routeAutoExit: MockChildSpawnRouter) {
|
|
|
37
37
|
if (autoExit) {
|
|
38
38
|
queueMicrotask(() => {
|
|
39
39
|
if (child.exitCode !== null || child.signalCode !== null) return
|
|
40
|
-
|
|
41
|
-
|
|
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')
|
|
40
|
+
child.exitCode = autoExit.code
|
|
41
|
+
child.signalCode = autoExit.signal ?? null
|
|
42
|
+
child.emit('exit', child.exitCode, child.signalCode)
|
|
52
43
|
})
|
|
53
44
|
}
|
|
54
45
|
|
|
@@ -1058,7 +1049,6 @@ describe('server start managed process exits', () => {
|
|
|
1058
1049
|
envChangeCallback = onChange
|
|
1059
1050
|
return jest.fn()
|
|
1060
1051
|
}),
|
|
1061
|
-
watchDevRuntimeFiles: jest.fn(() => jest.fn()),
|
|
1062
1052
|
}))
|
|
1063
1053
|
jest.doMock('../lib/generators', () => ({
|
|
1064
1054
|
generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import { createDevEnvReloader, resolveDevEnvFilePaths
|
|
4
|
+
import { createDevEnvReloader, resolveDevEnvFilePaths } from '../dev-env-reload'
|
|
5
5
|
import { normalizeTestPath } from './path-helpers'
|
|
6
6
|
|
|
7
7
|
describe('dev env reload helpers', () => {
|
|
@@ -60,24 +60,4 @@ describe('dev env reload helpers', () => {
|
|
|
60
60
|
expect(environment.SHELL_VALUE).toBe('shell-value')
|
|
61
61
|
expect(environment.REMOVED_LATER).toBeUndefined()
|
|
62
62
|
})
|
|
63
|
-
|
|
64
|
-
it('watches generated runtime files when explicitly requested', async () => {
|
|
65
|
-
const generatedDir = path.join(appDir, '.mercato', 'generated')
|
|
66
|
-
fs.mkdirSync(generatedDir, { recursive: true })
|
|
67
|
-
const generatedFile = path.join(generatedDir, 'backend-routes.generated.ts')
|
|
68
|
-
fs.writeFileSync(generatedFile, 'export const routes = []\n')
|
|
69
|
-
|
|
70
|
-
let stop = () => {}
|
|
71
|
-
const seen = new Promise<string>((resolve) => {
|
|
72
|
-
stop = watchDevRuntimeFiles(appDir, (filePath) => {
|
|
73
|
-
stop()
|
|
74
|
-
resolve(filePath)
|
|
75
|
-
}, { debounceMs: 10 })
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
79
|
-
fs.writeFileSync(generatedFile, 'export const routes = [1]\n')
|
|
80
|
-
|
|
81
|
-
await expect(seen.finally(stop)).resolves.toBe(generatedFile)
|
|
82
|
-
})
|
|
83
63
|
})
|
|
@@ -2,8 +2,6 @@ import dotenv from 'dotenv'
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
-
const RUNTIME_GRAPH_FILE_PATTERN = /\.generated(?:\.[a-z0-9]+)?(?:\.tsx?|\.checksum)$/i
|
|
6
|
-
|
|
7
5
|
const DEV_ENV_PRIORITY = [
|
|
8
6
|
'.env',
|
|
9
7
|
'.env.development',
|
|
@@ -110,48 +108,3 @@ export function watchDevEnvFiles(
|
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
|
-
|
|
114
|
-
export function watchDevRuntimeFiles(
|
|
115
|
-
appDir: string,
|
|
116
|
-
onChange: (filePath: string) => void,
|
|
117
|
-
options: { debounceMs?: number } = {},
|
|
118
|
-
): () => void {
|
|
119
|
-
const debounceMs = options.debounceMs ?? 250
|
|
120
|
-
const generatedDir = path.join(appDir, '.mercato', 'generated')
|
|
121
|
-
const timers = new Map<string, NodeJS.Timeout>()
|
|
122
|
-
|
|
123
|
-
if (!fs.existsSync(generatedDir)) {
|
|
124
|
-
return () => {}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let watcher: fs.FSWatcher | null = null
|
|
128
|
-
try {
|
|
129
|
-
watcher = fs.watch(generatedDir, (eventType, fileName) => {
|
|
130
|
-
if (eventType !== 'change' && eventType !== 'rename') return
|
|
131
|
-
const name = String(fileName ?? '')
|
|
132
|
-
if (!name) return
|
|
133
|
-
if (!RUNTIME_GRAPH_FILE_PATTERN.test(name)) return
|
|
134
|
-
|
|
135
|
-
const filePath = path.join(generatedDir, name)
|
|
136
|
-
const existingTimer = timers.get(filePath)
|
|
137
|
-
if (existingTimer) {
|
|
138
|
-
clearTimeout(existingTimer)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
timers.set(filePath, setTimeout(() => {
|
|
142
|
-
timers.delete(filePath)
|
|
143
|
-
onChange(filePath)
|
|
144
|
-
}, debounceMs))
|
|
145
|
-
})
|
|
146
|
-
} catch {
|
|
147
|
-
return () => {}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return () => {
|
|
151
|
-
for (const timer of timers.values()) {
|
|
152
|
-
clearTimeout(timer)
|
|
153
|
-
}
|
|
154
|
-
timers.clear()
|
|
155
|
-
watcher?.close()
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -2949,13 +2949,6 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2949
2949
|
// on `DEMO_MODE !== 'false'` in the backend layout, so opting out here
|
|
2950
2950
|
// only affects the integration test runtime — dev + prod stay unchanged.
|
|
2951
2951
|
DEMO_MODE: 'false',
|
|
2952
|
-
// Disable the feature-toggle resolution cache. Tests flip overrides
|
|
2953
|
-
// (e.g. example_customers_sync enabled/bidirectional) between cases; the
|
|
2954
|
-
// 1-minute resolution cache can serve a stale value across set/clear
|
|
2955
|
-
// under fast churn, which made the example.todo.* persistent subscribers
|
|
2956
|
-
// skip enqueueing inbound sync jobs (TC-CRM-028 inbound trio flake).
|
|
2957
|
-
// Fresh DB reads make flag-gated sync deterministic in tests only.
|
|
2958
|
-
OM_FEATURE_TOGGLES_CACHE_DISABLED: '1',
|
|
2959
2952
|
OM_CLI_QUIET: '1',
|
|
2960
2953
|
MERCATO_QUIET: '1',
|
|
2961
2954
|
QUEUE_BASE_DIR: EPHEMERAL_QUEUE_BASE_DIR,
|
package/src/mercato.ts
CHANGED
|
@@ -341,122 +341,6 @@ type DevServerRestartResult = {
|
|
|
341
341
|
|
|
342
342
|
type DevServerExitResult = ManagedProcessExitResult | DevServerRestartResult
|
|
343
343
|
|
|
344
|
-
function resolveDevRuntimeBaseUrl(environment: NodeJS.ProcessEnv = process.env): string {
|
|
345
|
-
const configured =
|
|
346
|
-
environment.APP_URL
|
|
347
|
-
?? environment.NEXT_PUBLIC_APP_URL
|
|
348
|
-
?? environment.NEXTAUTH_URL
|
|
349
|
-
if (configured?.trim()) {
|
|
350
|
-
return configured.trim().replace(/\/+$/, '')
|
|
351
|
-
}
|
|
352
|
-
return `http://localhost:${environment.PORT?.trim() || '3000'}`
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function writeDevSplashChildState(state: Record<string, unknown>): void {
|
|
356
|
-
if (process.env.OM_DEV_SPLASH_RUNTIME_WRAPPER === '1') return
|
|
357
|
-
const stateFile = process.env.OM_DEV_SPLASH_CHILD_STATE_FILE
|
|
358
|
-
if (!stateFile?.trim()) return
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
fs.mkdirSync(path.dirname(stateFile), { recursive: true })
|
|
362
|
-
fs.writeFileSync(stateFile, `${JSON.stringify({
|
|
363
|
-
mode: process.env.OM_DEV_SPLASH_MODE || 'dev',
|
|
364
|
-
failed: false,
|
|
365
|
-
failureLines: [],
|
|
366
|
-
failureCommand: null,
|
|
367
|
-
...state,
|
|
368
|
-
}, null, 2)}\n`)
|
|
369
|
-
} catch {
|
|
370
|
-
// Splash state is best-effort; terminal logs remain authoritative.
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function writeDevSplashRuntimeStarting(detail = 'Starting Next.js dev server'): void {
|
|
375
|
-
writeDevSplashChildState({
|
|
376
|
-
phase: 'Preparing app runtime',
|
|
377
|
-
detail,
|
|
378
|
-
ready: false,
|
|
379
|
-
readyUrl: null,
|
|
380
|
-
loginUrl: null,
|
|
381
|
-
progressLabel: 'Launching app runtime',
|
|
382
|
-
activity: detail,
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function resolveSplashProgressFallback(): { current: number; total: number } {
|
|
387
|
-
const current = Number.parseInt(process.env.OM_DEV_SPLASH_STAGE_CURRENT ?? '', 10)
|
|
388
|
-
const total = Number.parseInt(process.env.OM_DEV_SPLASH_STAGE_TOTAL ?? '', 10)
|
|
389
|
-
if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
|
|
390
|
-
return { current, total }
|
|
391
|
-
}
|
|
392
|
-
if (process.env.OM_DEV_SPLASH_MODE === 'greenfield' || process.env.OM_DEV_SPLASH_MODE === 'setup') {
|
|
393
|
-
return { current: 5, total: 5 }
|
|
394
|
-
}
|
|
395
|
-
return { current: 3, total: 3 }
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function writeDevSplashRuntimeRestarting(reason: string): void {
|
|
399
|
-
const progress = resolveSplashProgressFallback()
|
|
400
|
-
writeDevSplashChildState({
|
|
401
|
-
phase: 'App runtime is restarting',
|
|
402
|
-
detail: `Reason: ${reason}`,
|
|
403
|
-
ready: false,
|
|
404
|
-
readyUrl: null,
|
|
405
|
-
loginUrl: null,
|
|
406
|
-
progressCurrent: progress.current,
|
|
407
|
-
progressTotal: progress.total,
|
|
408
|
-
progressLabel: 'Restarting app runtime',
|
|
409
|
-
activity: `App runtime restart: ${reason}`,
|
|
410
|
-
})
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function writeDevSplashRuntimeReady(reason?: string): void {
|
|
414
|
-
const readyUrl = resolveDevRuntimeBaseUrl()
|
|
415
|
-
const progress = resolveSplashProgressFallback()
|
|
416
|
-
writeDevSplashChildState({
|
|
417
|
-
phase: 'App is ready',
|
|
418
|
-
detail: reason ? `Restart completed after ${reason}` : 'Next.js dev server is ready',
|
|
419
|
-
ready: true,
|
|
420
|
-
readyUrl,
|
|
421
|
-
loginUrl: `${readyUrl}/login`,
|
|
422
|
-
progressCurrent: progress.current,
|
|
423
|
-
progressTotal: progress.total,
|
|
424
|
-
progressPercent: 100,
|
|
425
|
-
progressLabel: 'App is ready',
|
|
426
|
-
activity: reason ? `Restart completed after ${reason}` : 'App runtime is ready',
|
|
427
|
-
})
|
|
428
|
-
}
|
|
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
|
-
|
|
460
344
|
type ModuleCommandLookupResult =
|
|
461
345
|
| {
|
|
462
346
|
status: 'ok'
|
|
@@ -717,17 +601,18 @@ async function runGeneratorSuite(quiet: boolean): Promise<void> {
|
|
|
717
601
|
function createGenerateWatchChecksumFn(): () => Promise<string> {
|
|
718
602
|
return async () => {
|
|
719
603
|
const { createResolver } = await import('./lib/resolver')
|
|
720
|
-
const {
|
|
604
|
+
const { calculateStructureChecksum } = await import('./lib/utils')
|
|
721
605
|
const resolver = createResolver()
|
|
722
|
-
const
|
|
606
|
+
const tracked = new Set<string>([
|
|
607
|
+
path.join(resolver.getAppDir(), 'src', 'modules.ts'),
|
|
608
|
+
path.join(resolver.getAppDir(), 'src', 'modules'),
|
|
609
|
+
])
|
|
723
610
|
for (const entry of resolver.loadEnabledModules()) {
|
|
724
611
|
const roots = resolver.getModulePaths(entry)
|
|
725
|
-
|
|
612
|
+
tracked.add(roots.appBase)
|
|
613
|
+
tracked.add(roots.pkgBase)
|
|
726
614
|
}
|
|
727
|
-
return
|
|
728
|
-
modulesFile: path.join(resolver.getAppDir(), 'src', 'modules.ts'),
|
|
729
|
-
moduleRoots,
|
|
730
|
-
})
|
|
615
|
+
return calculateStructureChecksum(Array.from(tracked))
|
|
731
616
|
}
|
|
732
617
|
}
|
|
733
618
|
|
|
@@ -1805,11 +1690,10 @@ export async function run(argv = process.argv) {
|
|
|
1805
1690
|
let processes: ChildProcess[] = []
|
|
1806
1691
|
let didRetryCorruptedTurbopackCache = false
|
|
1807
1692
|
let stopping = false
|
|
1808
|
-
let
|
|
1693
|
+
let envChangePromiseResolve: ((result: DevServerRestartResult) => void) | null = null
|
|
1809
1694
|
let activeLazySupervisor: ReturnType<typeof startLazyWorkerSupervisor> | null = null
|
|
1810
1695
|
let activeLazySchedulerSupervisor: ReturnType<typeof startLazySchedulerSupervisor> | null = null
|
|
1811
1696
|
let activeGenerateWatcher: GenerateWatcherHandle | null = null
|
|
1812
|
-
let lastRestartReason: string | null = null
|
|
1813
1697
|
const generateWatcherMode: GenerateWatcherMode = resolveGenerateWatcherMode(process.env)
|
|
1814
1698
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries)
|
|
1815
1699
|
|
|
@@ -1897,31 +1781,20 @@ export async function run(argv = process.argv) {
|
|
|
1897
1781
|
const mercatoBin = resolveInstalledBinary(nodeModulesBases, '@open-mercato/cli/bin/mercato')
|
|
1898
1782
|
|
|
1899
1783
|
const stopEnvWatcher = watchDevEnvFiles(appDir, (filePath) => {
|
|
1900
|
-
|
|
1784
|
+
envChangePromiseResolve?.({
|
|
1901
1785
|
label: 'Environment file change',
|
|
1902
1786
|
restart: true,
|
|
1903
1787
|
filePath,
|
|
1904
1788
|
})
|
|
1905
1789
|
})
|
|
1906
|
-
|
|
1790
|
+
|
|
1791
|
+
const waitForEnvChange = (): Promise<DevServerRestartResult> =>
|
|
1907
1792
|
new Promise((resolve) => {
|
|
1908
|
-
|
|
1793
|
+
envChangePromiseResolve = resolve
|
|
1909
1794
|
})
|
|
1910
1795
|
|
|
1911
|
-
const startNextDev = (runtimeEnv: NodeJS.ProcessEnv):
|
|
1912
|
-
|
|
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) => {
|
|
1920
|
-
writeDevSplashRuntimeStarting(
|
|
1921
|
-
lastRestartReason
|
|
1922
|
-
? `Restarting Next.js dev server. Reason: ${lastRestartReason}`
|
|
1923
|
-
: 'Starting Next.js dev server',
|
|
1924
|
-
)
|
|
1796
|
+
const startNextDev = (runtimeEnv: NodeJS.ProcessEnv): Promise<ManagedProcessExitResult> =>
|
|
1797
|
+
new Promise((resolve) => {
|
|
1925
1798
|
const nextProcess = spawn('node', [nextBin, 'dev', '--turbopack'], {
|
|
1926
1799
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
1927
1800
|
env: runtimeEnv,
|
|
@@ -1930,18 +1803,11 @@ export async function run(argv = process.argv) {
|
|
|
1930
1803
|
processes.push(nextProcess)
|
|
1931
1804
|
|
|
1932
1805
|
let combinedOutput = ''
|
|
1933
|
-
let reportedReady = false
|
|
1934
1806
|
const appendOutput = (chunk: string) => {
|
|
1935
1807
|
combinedOutput += chunk
|
|
1936
1808
|
if (combinedOutput.length > 32_768) {
|
|
1937
1809
|
combinedOutput = combinedOutput.slice(-32_768)
|
|
1938
1810
|
}
|
|
1939
|
-
if (!reportedReady && /\bready in\b/i.test(chunk)) {
|
|
1940
|
-
reportedReady = true
|
|
1941
|
-
writeDevSplashRuntimeReady(lastRestartReason ?? undefined)
|
|
1942
|
-
lastRestartReason = null
|
|
1943
|
-
readyResolve()
|
|
1944
|
-
}
|
|
1945
1811
|
}
|
|
1946
1812
|
|
|
1947
1813
|
nextProcess.stdout?.on('data', (chunk: Buffer | string) => {
|
|
@@ -1958,13 +1824,9 @@ export async function run(argv = process.argv) {
|
|
|
1958
1824
|
nextProcess.on('exit', async (code, signal) => {
|
|
1959
1825
|
if (!didRetryCorruptedTurbopackCache && isTurbopackCacheCorruption(combinedOutput)) {
|
|
1960
1826
|
didRetryCorruptedTurbopackCache = true
|
|
1961
|
-
lastRestartReason = 'corrupted Turbopack dev cache'
|
|
1962
|
-
writeDevSplashRuntimeRestarting(lastRestartReason)
|
|
1963
1827
|
console.log('[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...')
|
|
1964
1828
|
removeTurbopackDevCache(appDir)
|
|
1965
|
-
|
|
1966
|
-
restarted.readyPromise.then(readyResolve)
|
|
1967
|
-
return resolve(await restarted.exitPromise)
|
|
1829
|
+
return resolve(await startNextDev(runtimeEnv))
|
|
1968
1830
|
}
|
|
1969
1831
|
resolve({
|
|
1970
1832
|
label: 'Next.js dev server',
|
|
@@ -1973,8 +1835,6 @@ export async function run(argv = process.argv) {
|
|
|
1973
1835
|
})
|
|
1974
1836
|
})
|
|
1975
1837
|
})
|
|
1976
|
-
return { exitPromise, readyPromise }
|
|
1977
|
-
}
|
|
1978
1838
|
|
|
1979
1839
|
try {
|
|
1980
1840
|
while (!stopping) {
|
|
@@ -1984,92 +1844,62 @@ export async function run(argv = process.argv) {
|
|
|
1984
1844
|
const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env)
|
|
1985
1845
|
const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
|
|
1986
1846
|
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
|
-
})
|
|
1997
1847
|
const managedExitPromises: Promise<DevServerExitResult>[] = [
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
backgroundExitPromise,
|
|
1848
|
+
startNextDev(runtimeEnv),
|
|
1849
|
+
waitForEnvChange(),
|
|
2001
1850
|
]
|
|
2002
1851
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
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
|
-
}
|
|
1852
|
+
// Start workers if enabled
|
|
1853
|
+
if (autoSpawnWorkersMode !== 'off') {
|
|
1854
|
+
const discoveredWorkers = getRegisteredCliWorkers()
|
|
1855
|
+
const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))]
|
|
1856
|
+
if (discoveredWorkerQueues.length === 0) {
|
|
1857
|
+
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.')
|
|
1858
|
+
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
1859
|
+
console.log(`[server] Lazy worker auto-spawn enabled — workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`)
|
|
1860
|
+
activeLazySupervisor = startLazyWorkerSupervisor({
|
|
1861
|
+
mercatoBin,
|
|
1862
|
+
appDir,
|
|
1863
|
+
runtimeEnv,
|
|
1864
|
+
workers: discoveredWorkers,
|
|
1865
|
+
pollMs: resolveLazyPollMs(process.env),
|
|
1866
|
+
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
1867
|
+
})
|
|
1868
|
+
} else {
|
|
1869
|
+
console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
|
|
1870
|
+
const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
|
|
1871
|
+
stdio: 'inherit',
|
|
1872
|
+
env: runtimeEnv,
|
|
1873
|
+
cwd: appDir,
|
|
1874
|
+
})
|
|
1875
|
+
processes.push(workerProcess)
|
|
1876
|
+
managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)))
|
|
2044
1877
|
}
|
|
1878
|
+
}
|
|
2045
1879
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
}
|
|
1880
|
+
if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
|
|
1881
|
+
if (schedulerCommand.status !== 'ok') {
|
|
1882
|
+
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(schedulerCommand)}`)
|
|
1883
|
+
} else if (autoSpawnSchedulerMode === 'lazy') {
|
|
1884
|
+
console.log('[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.')
|
|
1885
|
+
activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
|
|
1886
|
+
mercatoBin,
|
|
1887
|
+
appDir,
|
|
1888
|
+
runtimeEnv,
|
|
1889
|
+
pollMs: resolveLazySchedulerPollMs(process.env),
|
|
1890
|
+
restartOnUnexpectedExit: resolveLazySchedulerRestart(process.env),
|
|
1891
|
+
})
|
|
1892
|
+
} else {
|
|
1893
|
+
console.log('[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...')
|
|
1894
|
+
const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
|
|
1895
|
+
stdio: 'inherit',
|
|
1896
|
+
env: runtimeEnv,
|
|
1897
|
+
cwd: appDir,
|
|
1898
|
+
})
|
|
1899
|
+
processes.push(schedulerProcess)
|
|
1900
|
+
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine'))
|
|
2068
1901
|
}
|
|
2069
1902
|
}
|
|
2070
|
-
nextRuntime.readyPromise.then(() => {
|
|
2071
|
-
void startBackgroundServices()
|
|
2072
|
-
})
|
|
2073
1903
|
|
|
2074
1904
|
if (generateWatcherMode === 'in-process') {
|
|
2075
1905
|
// Run the structural regeneration watcher inside this process
|
|
@@ -2097,15 +1927,11 @@ export async function run(argv = process.argv) {
|
|
|
2097
1927
|
}
|
|
2098
1928
|
|
|
2099
1929
|
const firstExit = await Promise.race(managedExitPromises)
|
|
2100
|
-
if (isDevServerRestartResult(firstExit)) {
|
|
2101
|
-
lastRestartReason = `${firstExit.label.toLowerCase()} (${path.basename(firstExit.filePath)})`
|
|
2102
|
-
writeDevSplashRuntimeRestarting(lastRestartReason)
|
|
2103
|
-
}
|
|
2104
1930
|
await cleanupAndWait()
|
|
2105
|
-
|
|
1931
|
+
envChangePromiseResolve = null
|
|
2106
1932
|
|
|
2107
1933
|
if (isDevServerRestartResult(firstExit)) {
|
|
2108
|
-
console.log(`[server] Detected
|
|
1934
|
+
console.log(`[server] Detected environment file change (${path.basename(firstExit.filePath)}). Restarting app runtime...`)
|
|
2109
1935
|
continue
|
|
2110
1936
|
}
|
|
2111
1937
|
|