@open-mercato/cli 0.5.1-develop.2744.9c8be0dd93 → 0.5.1-develop.2756.cce1739df3
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/dist/mercato.js +74 -33
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +225 -0
- package/src/mercato.ts +87 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2756.cce1739df3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"@mikro-orm/decorators": "^7.0.10",
|
|
60
60
|
"@mikro-orm/migrations": "^7.0.10",
|
|
61
61
|
"@mikro-orm/postgresql": "^7.0.10",
|
|
62
|
-
"@open-mercato/queue": "0.5.1-develop.
|
|
63
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
62
|
+
"@open-mercato/queue": "0.5.1-develop.2756.cce1739df3",
|
|
63
|
+
"@open-mercato/shared": "0.5.1-develop.2756.cce1739df3",
|
|
64
64
|
"cross-spawn": "^7.0.6",
|
|
65
65
|
"pg": "8.20.0",
|
|
66
66
|
"semver": "^7.7.4",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"typescript": "^5.9.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
73
|
+
"@open-mercato/shared": "0.5.1-develop.2756.cce1739df3"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
76
|
+
"@open-mercato/shared": "0.5.1-develop.2756.cce1739df3",
|
|
77
77
|
"@types/jest": "^30.0.0",
|
|
78
78
|
"jest": "^30.3.0",
|
|
79
79
|
"ts-jest": "^29.4.9"
|
|
@@ -350,6 +350,109 @@ describe('init command failure output', () => {
|
|
|
350
350
|
consoleErrorSpy.mockRestore()
|
|
351
351
|
consoleLogSpy.mockRestore()
|
|
352
352
|
})
|
|
353
|
+
|
|
354
|
+
it('keeps init successful when lean presets disable optional modules', async () => {
|
|
355
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
|
356
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
|
|
357
|
+
|
|
358
|
+
const configsRestoreDefaults = jest.fn().mockResolvedValue(undefined)
|
|
359
|
+
const authSetup = jest.fn().mockResolvedValue(undefined)
|
|
360
|
+
const authSeedRoles = jest.fn().mockResolvedValue(undefined)
|
|
361
|
+
const entitiesSeedEncryption = jest.fn().mockResolvedValue(undefined)
|
|
362
|
+
const queryIndexReindex = jest.fn().mockResolvedValue(undefined)
|
|
363
|
+
|
|
364
|
+
jest.doMock('child_process', () => ({
|
|
365
|
+
execSync: jest.fn(),
|
|
366
|
+
}))
|
|
367
|
+
jest.doMock('pg', () => ({
|
|
368
|
+
Client: jest.fn().mockImplementation(() => ({
|
|
369
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
370
|
+
query: jest.fn().mockResolvedValue({
|
|
371
|
+
rows: [{ org_id: 'org-1', tenant_id: 'tenant-1' }],
|
|
372
|
+
}),
|
|
373
|
+
end: jest.fn().mockResolvedValue(undefined),
|
|
374
|
+
})),
|
|
375
|
+
}))
|
|
376
|
+
jest.doMock('../lib/generators', () => ({
|
|
377
|
+
generateEntityIds: jest.fn().mockResolvedValue(undefined),
|
|
378
|
+
generateModuleRegistry: jest.fn().mockResolvedValue(undefined),
|
|
379
|
+
generateModuleRegistryApp: jest.fn().mockResolvedValue(undefined),
|
|
380
|
+
generateModuleRegistryCli: jest.fn().mockResolvedValue(undefined),
|
|
381
|
+
generateModuleEntities: jest.fn().mockResolvedValue(undefined),
|
|
382
|
+
generateModuleDi: jest.fn().mockResolvedValue(undefined),
|
|
383
|
+
generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
|
|
384
|
+
generateOpenApi: jest.fn().mockResolvedValue(undefined),
|
|
385
|
+
}))
|
|
386
|
+
jest.doMock('../lib/db', () => ({
|
|
387
|
+
dbMigrate: jest.fn().mockResolvedValue(undefined),
|
|
388
|
+
}))
|
|
389
|
+
jest.doMock('../lib/resolver', () => ({
|
|
390
|
+
createResolver: () => ({
|
|
391
|
+
getAppDir: () => '/tmp/test-app',
|
|
392
|
+
}),
|
|
393
|
+
}))
|
|
394
|
+
jest.doMock('@open-mercato/shared/lib/bootstrap/dynamicLoader', () => ({
|
|
395
|
+
bootstrapFromAppRoot: jest.fn().mockResolvedValue({
|
|
396
|
+
modules: [
|
|
397
|
+
{
|
|
398
|
+
id: 'configs',
|
|
399
|
+
cli: [{ command: 'restore-defaults', run: configsRestoreDefaults }],
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
id: 'auth',
|
|
403
|
+
cli: [
|
|
404
|
+
{ command: 'setup', run: authSetup },
|
|
405
|
+
{ command: 'seed-roles', run: authSeedRoles },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: 'entities',
|
|
410
|
+
cli: [{ command: 'seed-encryption', run: entitiesSeedEncryption }],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'query_index',
|
|
414
|
+
cli: [{ command: 'reindex', run: queryIndexReindex }],
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
}),
|
|
418
|
+
}))
|
|
419
|
+
jest.doMock('@open-mercato/shared/lib/di/container', () => ({
|
|
420
|
+
createRequestContainer: jest.fn().mockResolvedValue({
|
|
421
|
+
resolve: jest.fn().mockReturnValue({}),
|
|
422
|
+
}),
|
|
423
|
+
}))
|
|
424
|
+
jest.doMock(
|
|
425
|
+
'@open-mercato/core/modules/auth/lib/setup-app',
|
|
426
|
+
() => ({
|
|
427
|
+
ensureCustomRoleAcls: jest.fn().mockResolvedValue(undefined),
|
|
428
|
+
}),
|
|
429
|
+
{ virtual: true },
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
const mercato = await import('../mercato')
|
|
433
|
+
const exitCode = await mercato.run(['node', 'mercato', 'init'])
|
|
434
|
+
|
|
435
|
+
expect(exitCode).toBe(0)
|
|
436
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled()
|
|
437
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
438
|
+
'⏭️ Skipping "feature_toggles:seed-defaults" — module not enabled',
|
|
439
|
+
)
|
|
440
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
441
|
+
'⏭️ Skipping "dashboards:seed-defaults" — module not enabled',
|
|
442
|
+
)
|
|
443
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
444
|
+
'⏭️ Skipping "dashboards:enable-analytics-widgets" — module not enabled',
|
|
445
|
+
)
|
|
446
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
447
|
+
'⏭️ Skipping "search:reindex" — module not enabled',
|
|
448
|
+
)
|
|
449
|
+
expect(configsRestoreDefaults).toHaveBeenCalled()
|
|
450
|
+
expect(authSetup).toHaveBeenCalled()
|
|
451
|
+
expect(queryIndexReindex).toHaveBeenCalledWith(['--force', '--tenant', 'tenant-1'])
|
|
452
|
+
|
|
453
|
+
consoleErrorSpy.mockRestore()
|
|
454
|
+
consoleLogSpy.mockRestore()
|
|
455
|
+
})
|
|
353
456
|
})
|
|
354
457
|
|
|
355
458
|
describe('generate post-step structural cache purge', () => {
|
|
@@ -456,3 +559,125 @@ describe('generate post-step structural cache purge', () => {
|
|
|
456
559
|
consoleLogSpy.mockRestore()
|
|
457
560
|
})
|
|
458
561
|
})
|
|
562
|
+
|
|
563
|
+
describe('server dev managed process exits', () => {
|
|
564
|
+
const originalAutoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER
|
|
565
|
+
|
|
566
|
+
beforeEach(() => {
|
|
567
|
+
jest.restoreAllMocks()
|
|
568
|
+
jest.resetModules()
|
|
569
|
+
process.env.AUTO_SPAWN_SCHEDULER = 'false'
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
afterEach(() => {
|
|
573
|
+
jest.dontMock('child_process')
|
|
574
|
+
jest.dontMock('node:fs')
|
|
575
|
+
jest.dontMock('../lib/generators')
|
|
576
|
+
jest.dontMock('../lib/resolver')
|
|
577
|
+
jest.resetModules()
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
afterAll(() => {
|
|
581
|
+
process.env.AUTO_SPAWN_SCHEDULER = originalAutoSpawnScheduler
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('fails loudly when a managed child exits cleanly but unexpectedly', async () => {
|
|
585
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
|
586
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
|
|
587
|
+
|
|
588
|
+
jest.doMock('node:fs', () => {
|
|
589
|
+
const actual = jest.requireActual('node:fs')
|
|
590
|
+
return {
|
|
591
|
+
...actual,
|
|
592
|
+
existsSync: jest.fn((candidate: string) =>
|
|
593
|
+
candidate.includes('next/dist/bin/next') || candidate.includes('@open-mercato/cli/bin/mercato'),
|
|
594
|
+
),
|
|
595
|
+
unlinkSync: jest.fn(),
|
|
596
|
+
}
|
|
597
|
+
})
|
|
598
|
+
jest.doMock('../lib/generators', () => ({
|
|
599
|
+
generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
|
|
600
|
+
}))
|
|
601
|
+
jest.doMock('../lib/resolver', () => ({
|
|
602
|
+
resolveEnvironment: () => ({
|
|
603
|
+
appDir: '/tmp/test-app',
|
|
604
|
+
rootDir: '/tmp/test-root',
|
|
605
|
+
}),
|
|
606
|
+
createResolver: () => ({}),
|
|
607
|
+
}))
|
|
608
|
+
jest.doMock('child_process', () => {
|
|
609
|
+
const { EventEmitter } = jest.requireActual('node:events')
|
|
610
|
+
|
|
611
|
+
const createChild = (
|
|
612
|
+
spawnargs: string[],
|
|
613
|
+
autoExit?: { code: number | null; signal?: NodeJS.Signals | null },
|
|
614
|
+
) => {
|
|
615
|
+
const child = new EventEmitter() as any
|
|
616
|
+
child.stdout = new EventEmitter()
|
|
617
|
+
child.stderr = new EventEmitter()
|
|
618
|
+
child.spawnargs = spawnargs
|
|
619
|
+
child.killed = false
|
|
620
|
+
child.exitCode = null
|
|
621
|
+
child.signalCode = null
|
|
622
|
+
child.kill = jest.fn((signal: NodeJS.Signals = 'SIGTERM') => {
|
|
623
|
+
child.killed = true
|
|
624
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
625
|
+
return true
|
|
626
|
+
}
|
|
627
|
+
child.signalCode = signal
|
|
628
|
+
queueMicrotask(() => {
|
|
629
|
+
child.emit('exit', null, signal)
|
|
630
|
+
})
|
|
631
|
+
return true
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
if (autoExit) {
|
|
635
|
+
queueMicrotask(() => {
|
|
636
|
+
if (child.exitCode !== null || child.signalCode !== null) return
|
|
637
|
+
child.exitCode = autoExit.code
|
|
638
|
+
child.signalCode = autoExit.signal ?? null
|
|
639
|
+
child.emit('exit', child.exitCode, child.signalCode)
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return child
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
spawn: jest.fn((_command: string, args: string[]) => {
|
|
648
|
+
if (args[0]?.includes('next/dist/bin/next')) {
|
|
649
|
+
return createChild(['node', ...args])
|
|
650
|
+
}
|
|
651
|
+
if (args.slice(1).join(' ') === 'queue worker --all') {
|
|
652
|
+
return createChild(['node', ...args], { code: 0 })
|
|
653
|
+
}
|
|
654
|
+
return createChild(['node', ...args])
|
|
655
|
+
}),
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
const mercato = await import('../mercato')
|
|
660
|
+
mercato.registerCliModules([
|
|
661
|
+
{
|
|
662
|
+
id: 'events',
|
|
663
|
+
workers: [
|
|
664
|
+
{
|
|
665
|
+
queue: 'events',
|
|
666
|
+
concurrency: 1,
|
|
667
|
+
handler: jest.fn(),
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
} as any,
|
|
671
|
+
])
|
|
672
|
+
|
|
673
|
+
const exitCode = await mercato.run(['node', 'mercato', 'server', 'dev'])
|
|
674
|
+
|
|
675
|
+
expect(exitCode).toBe(1)
|
|
676
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
677
|
+
'💥 Failed: [server] Queue worker exited unexpectedly with exit code 0.',
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
consoleErrorSpy.mockRestore()
|
|
681
|
+
consoleLogSpy.mockRestore()
|
|
682
|
+
})
|
|
683
|
+
})
|
package/src/mercato.ts
CHANGED
|
@@ -305,6 +305,38 @@ function buildServerProcessEnvironment(environment: NodeJS.ProcessEnv): NodeJS.P
|
|
|
305
305
|
return runtimeEnv
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
type ManagedProcessExitResult = {
|
|
309
|
+
label: string
|
|
310
|
+
code: number | null
|
|
311
|
+
signal: NodeJS.Signals | null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function waitForManagedProcessExit(proc: ChildProcess, label: string): Promise<ManagedProcessExitResult> {
|
|
315
|
+
return new Promise((resolve) => {
|
|
316
|
+
proc.on('exit', (code, signal) => {
|
|
317
|
+
resolve({ label, code, signal })
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isExpectedManagedExitSignal(signal: NodeJS.Signals | null): boolean {
|
|
323
|
+
return signal === 'SIGINT' || signal === 'SIGTERM'
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatManagedProcessExitStatus(result: ManagedProcessExitResult): string {
|
|
327
|
+
if (typeof result.code === 'number') {
|
|
328
|
+
return `exit code ${result.code}`
|
|
329
|
+
}
|
|
330
|
+
if (result.signal) {
|
|
331
|
+
return `signal ${result.signal}`
|
|
332
|
+
}
|
|
333
|
+
return 'an unknown status'
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function createManagedProcessExitError(result: ManagedProcessExitResult): Error {
|
|
337
|
+
return new Error(`[server] ${result.label} exited unexpectedly with ${formatManagedProcessExitStatus(result)}.`)
|
|
338
|
+
}
|
|
339
|
+
|
|
308
340
|
function ensureNextBuildIdInConfiguredDistDir(appDir: string): void {
|
|
309
341
|
const configuredDistDir = path.join(appDir, '.mercato', 'next')
|
|
310
342
|
const configuredBuildIdPath = path.join(configuredDistDir, 'BUILD_ID')
|
|
@@ -371,14 +403,14 @@ async function runModuleCommand(
|
|
|
371
403
|
commandName: string,
|
|
372
404
|
args: string[] = [],
|
|
373
405
|
options: { optional?: boolean; silentOptional?: boolean } = {},
|
|
374
|
-
): Promise<
|
|
406
|
+
): Promise<boolean> {
|
|
375
407
|
const mod = allModules.find((m) => m.id === moduleName)
|
|
376
408
|
if (!mod) {
|
|
377
409
|
if (options.optional) {
|
|
378
410
|
if (!options.silentOptional) {
|
|
379
411
|
console.log(`⏭️ Skipping "${moduleName}:${commandName}" — module not enabled`)
|
|
380
412
|
}
|
|
381
|
-
return
|
|
413
|
+
return false
|
|
382
414
|
}
|
|
383
415
|
throw new Error(`Module not found: "${moduleName}"`)
|
|
384
416
|
}
|
|
@@ -387,7 +419,7 @@ async function runModuleCommand(
|
|
|
387
419
|
if (!options.silentOptional) {
|
|
388
420
|
console.log(`⏭️ Skipping "${moduleName}:${commandName}" — module has no CLI commands`)
|
|
389
421
|
}
|
|
390
|
-
return
|
|
422
|
+
return false
|
|
391
423
|
}
|
|
392
424
|
throw new Error(`Module "${moduleName}" has no CLI commands`)
|
|
393
425
|
}
|
|
@@ -397,11 +429,12 @@ async function runModuleCommand(
|
|
|
397
429
|
if (!options.silentOptional) {
|
|
398
430
|
console.log(`⏭️ Skipping "${moduleName}:${commandName}" — command not found`)
|
|
399
431
|
}
|
|
400
|
-
return
|
|
432
|
+
return false
|
|
401
433
|
}
|
|
402
434
|
throw new Error(`Command "${commandName}" not found in module "${moduleName}"`)
|
|
403
435
|
}
|
|
404
436
|
await cmd.run(args)
|
|
437
|
+
return true
|
|
405
438
|
}
|
|
406
439
|
|
|
407
440
|
async function runPostGenerateStructuralCachePurge(quiet: boolean): Promise<void> {
|
|
@@ -760,8 +793,11 @@ export async function run(argv = process.argv) {
|
|
|
760
793
|
console.log('✅ RBAC setup complete:', { tenantId, organizationId: orgId }, '\n')
|
|
761
794
|
|
|
762
795
|
console.log('🎛️ Seeding feature toggle defaults...')
|
|
763
|
-
await runModuleCommand(allModules, 'feature_toggles', 'seed-defaults', [])
|
|
764
|
-
|
|
796
|
+
if (await runModuleCommand(allModules, 'feature_toggles', 'seed-defaults', [], { optional: true })) {
|
|
797
|
+
console.log('🎛️ ✅ Feature toggle defaults seeded\n')
|
|
798
|
+
} else {
|
|
799
|
+
console.log('')
|
|
800
|
+
}
|
|
765
801
|
|
|
766
802
|
if (tenantId) {
|
|
767
803
|
console.log('👥 Seeding tenant-scoped roles...')
|
|
@@ -828,22 +864,31 @@ export async function run(argv = process.argv) {
|
|
|
828
864
|
)
|
|
829
865
|
const stressArgs = ['--tenant', tenantId, '--org', orgId, '--count', String(stressTestCount)]
|
|
830
866
|
if (stressTestLite) stressArgs.push('--lite')
|
|
831
|
-
await runModuleCommand(allModules, 'customers', 'seed-stresstest', stressArgs, { optional: true })
|
|
832
|
-
|
|
867
|
+
if (await runModuleCommand(allModules, 'customers', 'seed-stresstest', stressArgs, { optional: true })) {
|
|
868
|
+
console.log(`✅ Stress test customers seeded (requested ${stressTestCount})\n`)
|
|
869
|
+
} else {
|
|
870
|
+
console.log('')
|
|
871
|
+
}
|
|
833
872
|
}
|
|
834
873
|
|
|
835
874
|
console.log('🧩 Enabling default dashboard widgets...')
|
|
836
|
-
await runModuleCommand(allModules, 'dashboards', 'seed-defaults', ['--tenant', tenantId], { optional: true })
|
|
837
|
-
|
|
875
|
+
if (await runModuleCommand(allModules, 'dashboards', 'seed-defaults', ['--tenant', tenantId], { optional: true })) {
|
|
876
|
+
console.log('✅ Dashboard widgets enabled\n')
|
|
877
|
+
} else {
|
|
878
|
+
console.log('')
|
|
879
|
+
}
|
|
838
880
|
|
|
839
881
|
console.log('📊 Enabling analytics widgets for admin and employee roles...')
|
|
840
|
-
await runModuleCommand(allModules, 'dashboards', 'enable-analytics-widgets', [
|
|
882
|
+
if (await runModuleCommand(allModules, 'dashboards', 'enable-analytics-widgets', [
|
|
841
883
|
'--tenant',
|
|
842
884
|
tenantId,
|
|
843
885
|
'--roles',
|
|
844
886
|
'admin,employee',
|
|
845
|
-
])
|
|
846
|
-
|
|
887
|
+
], { optional: true })) {
|
|
888
|
+
console.log('✅ Analytics widgets enabled for roles\n')
|
|
889
|
+
} else {
|
|
890
|
+
console.log('')
|
|
891
|
+
}
|
|
847
892
|
|
|
848
893
|
} else {
|
|
849
894
|
console.log('⚠️ Could not get organization ID or tenant ID, skipping seeding steps\n')
|
|
@@ -853,13 +898,19 @@ export async function run(argv = process.argv) {
|
|
|
853
898
|
const vectorArgs = tenantId
|
|
854
899
|
? ['--tenant', tenantId, ...(orgId ? ['--org', orgId] : [])]
|
|
855
900
|
: ['--purgeFirst=false']
|
|
856
|
-
await runModuleCommand(allModules, 'search', 'reindex', vectorArgs, { optional: true })
|
|
857
|
-
|
|
901
|
+
if (await runModuleCommand(allModules, 'search', 'reindex', vectorArgs, { optional: true })) {
|
|
902
|
+
console.log('✅ Search indexes built\n')
|
|
903
|
+
} else {
|
|
904
|
+
console.log('')
|
|
905
|
+
}
|
|
858
906
|
|
|
859
907
|
console.log('🔍 Rebuilding query indexes...')
|
|
860
908
|
const queryIndexArgs = ['--force', ...(tenantId ? ['--tenant', tenantId] : [])]
|
|
861
|
-
await runModuleCommand(allModules, 'query_index', 'reindex', queryIndexArgs, { optional: true })
|
|
862
|
-
|
|
909
|
+
if (await runModuleCommand(allModules, 'query_index', 'reindex', queryIndexArgs, { optional: true })) {
|
|
910
|
+
console.log('✅ Query indexes rebuilt\n')
|
|
911
|
+
} else {
|
|
912
|
+
console.log('')
|
|
913
|
+
}
|
|
863
914
|
|
|
864
915
|
const adminPasswordOverride = derivedSecrets.adminPassword
|
|
865
916
|
const employeePasswordOverride = derivedSecrets.employeePassword
|
|
@@ -1598,11 +1649,11 @@ export async function run(argv = process.argv) {
|
|
|
1598
1649
|
const nextBin = resolveInstalledBinary(nodeModulesBases, 'next/dist/bin/next')
|
|
1599
1650
|
const mercatoBin = resolveInstalledBinary(nodeModulesBases, '@open-mercato/cli/bin/mercato')
|
|
1600
1651
|
|
|
1601
|
-
const startNextDev = (): Promise<
|
|
1652
|
+
const startNextDev = (): Promise<ManagedProcessExitResult> =>
|
|
1602
1653
|
new Promise((resolve) => {
|
|
1603
1654
|
const nextProcess = spawn('node', [nextBin, 'dev', '--turbopack'], {
|
|
1604
1655
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
1605
|
-
env:
|
|
1656
|
+
env: runtimeEnv,
|
|
1606
1657
|
cwd: appDir,
|
|
1607
1658
|
})
|
|
1608
1659
|
processes.push(nextProcess)
|
|
@@ -1626,19 +1677,23 @@ export async function run(argv = process.argv) {
|
|
|
1626
1677
|
appendOutput(text)
|
|
1627
1678
|
})
|
|
1628
1679
|
|
|
1629
|
-
nextProcess.on('exit', async () => {
|
|
1680
|
+
nextProcess.on('exit', async (code, signal) => {
|
|
1630
1681
|
if (!didRetryCorruptedTurbopackCache && isTurbopackCacheCorruption(combinedOutput)) {
|
|
1631
1682
|
didRetryCorruptedTurbopackCache = true
|
|
1632
1683
|
console.log('[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...')
|
|
1633
1684
|
removeTurbopackDevCache(appDir)
|
|
1634
|
-
await startNextDev()
|
|
1635
|
-
return resolve()
|
|
1685
|
+
return resolve(await startNextDev())
|
|
1636
1686
|
}
|
|
1637
|
-
resolve(
|
|
1687
|
+
resolve({
|
|
1688
|
+
label: 'Next.js dev server',
|
|
1689
|
+
code,
|
|
1690
|
+
signal,
|
|
1691
|
+
})
|
|
1638
1692
|
})
|
|
1639
1693
|
})
|
|
1640
1694
|
|
|
1641
1695
|
const nextExitPromise = startNextDev()
|
|
1696
|
+
const managedExitPromises: Promise<ManagedProcessExitResult>[] = [nextExitPromise]
|
|
1642
1697
|
|
|
1643
1698
|
// Start workers if enabled
|
|
1644
1699
|
if (autoSpawnWorkers) {
|
|
@@ -1649,10 +1704,11 @@ export async function run(argv = process.argv) {
|
|
|
1649
1704
|
console.log('[server] Starting workers for all queues...')
|
|
1650
1705
|
const workerProcess = spawn('node', [mercatoBin, 'queue', 'worker', '--all'], {
|
|
1651
1706
|
stdio: 'inherit',
|
|
1652
|
-
env:
|
|
1707
|
+
env: runtimeEnv,
|
|
1653
1708
|
cwd: appDir,
|
|
1654
1709
|
})
|
|
1655
1710
|
processes.push(workerProcess)
|
|
1711
|
+
managedExitPromises.push(waitForManagedProcessExit(workerProcess, 'Queue worker'))
|
|
1656
1712
|
}
|
|
1657
1713
|
}
|
|
1658
1714
|
|
|
@@ -1660,28 +1716,20 @@ export async function run(argv = process.argv) {
|
|
|
1660
1716
|
console.log('[server] Starting scheduler polling engine...')
|
|
1661
1717
|
const schedulerProcess = spawn('node', [mercatoBin, 'scheduler', 'start'], {
|
|
1662
1718
|
stdio: 'inherit',
|
|
1663
|
-
env:
|
|
1719
|
+
env: runtimeEnv,
|
|
1664
1720
|
cwd: appDir,
|
|
1665
1721
|
})
|
|
1666
1722
|
processes.push(schedulerProcess)
|
|
1723
|
+
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, 'Scheduler polling engine'))
|
|
1667
1724
|
}
|
|
1668
1725
|
|
|
1669
|
-
|
|
1670
|
-
await Promise.race(
|
|
1671
|
-
[
|
|
1672
|
-
nextExitPromise,
|
|
1673
|
-
...processes
|
|
1674
|
-
.filter((proc) => proc.spawnargs[1] !== nextBin)
|
|
1675
|
-
.map(
|
|
1676
|
-
(proc) =>
|
|
1677
|
-
new Promise<void>((resolve) => {
|
|
1678
|
-
proc.on('exit', () => resolve())
|
|
1679
|
-
})
|
|
1680
|
-
),
|
|
1681
|
-
]
|
|
1682
|
-
)
|
|
1726
|
+
const firstExit = await Promise.race(managedExitPromises)
|
|
1683
1727
|
|
|
1684
1728
|
await cleanupAndWait()
|
|
1729
|
+
|
|
1730
|
+
if (!isExpectedManagedExitSignal(firstExit.signal)) {
|
|
1731
|
+
throw createManagedProcessExitError(firstExit)
|
|
1732
|
+
}
|
|
1685
1733
|
},
|
|
1686
1734
|
},
|
|
1687
1735
|
{
|