@open-mercato/cli 0.4.9-develop-94fb251ed3 → 0.4.9-develop-8d8db18714
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/agentic/shared/AGENTS.md.template +2 -0
- package/dist/agentic/shared/ai/skills/eject-and-customize/SKILL.md +3 -1
- package/dist/bin.js +1 -0
- package/dist/bin.js.map +2 -2
- package/dist/lib/__fixtures__/official-module-package/src/index.js +1 -0
- package/dist/lib/__fixtures__/official-module-package/src/index.js.map +7 -0
- package/dist/lib/__fixtures__/official-module-package/src/modules/test_package/index.js +10 -0
- package/dist/lib/__fixtures__/official-module-package/src/modules/test_package/index.js.map +7 -0
- package/dist/lib/eject.js +30 -38
- package/dist/lib/eject.js.map +2 -2
- package/dist/lib/generators/index.js +2 -0
- package/dist/lib/generators/index.js.map +2 -2
- package/dist/lib/generators/module-package-sources.js +45 -0
- package/dist/lib/generators/module-package-sources.js.map +7 -0
- package/dist/lib/module-install-args.js +40 -0
- package/dist/lib/module-install-args.js.map +7 -0
- package/dist/lib/module-install.js +157 -0
- package/dist/lib/module-install.js.map +7 -0
- package/dist/lib/module-package.js +245 -0
- package/dist/lib/module-package.js.map +7 -0
- package/dist/lib/modules-config.js +255 -0
- package/dist/lib/modules-config.js.map +7 -0
- package/dist/lib/resolver.js +19 -5
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +20 -9
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/lib/testing/integration.js +86 -47
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +120 -43
- package/dist/mercato.js.map +3 -3
- package/package.json +5 -4
- package/src/__tests__/mercato.test.ts +6 -1
- package/src/bin.ts +1 -0
- package/src/lib/__fixtures__/official-module-package/dist/modules/test_package/index.js +2 -0
- package/src/lib/__fixtures__/official-module-package/package.json +33 -0
- package/src/lib/__fixtures__/official-module-package/src/index.ts +1 -0
- package/src/lib/__fixtures__/official-module-package/src/modules/test_package/index.ts +6 -0
- package/src/lib/__fixtures__/official-module-package/src/modules/test_package/widgets/injection/test/widget.tsx +3 -0
- package/src/lib/__tests__/eject.test.ts +107 -1
- package/src/lib/__tests__/module-install-args.test.ts +35 -0
- package/src/lib/__tests__/module-install.test.ts +217 -0
- package/src/lib/__tests__/module-package.test.ts +215 -0
- package/src/lib/__tests__/modules-config.test.ts +104 -0
- package/src/lib/__tests__/resolve-environment.test.ts +141 -0
- package/src/lib/eject.ts +45 -55
- package/src/lib/generators/__tests__/generators.test.ts +11 -0
- package/src/lib/generators/__tests__/module-package-sources.test.ts +121 -0
- package/src/lib/generators/index.ts +1 -0
- package/src/lib/generators/module-package-sources.ts +59 -0
- package/src/lib/module-install-args.ts +50 -0
- package/src/lib/module-install.ts +234 -0
- package/src/lib/module-package.ts +355 -0
- package/src/lib/modules-config.ts +393 -0
- package/src/lib/resolver.ts +46 -4
- package/src/lib/testing/__tests__/integration-discovery.test.ts +30 -0
- package/src/lib/testing/integration-discovery.ts +23 -8
- package/src/lib/testing/integration.ts +97 -57
- package/src/mercato.ts +128 -49
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { GenericContainer } from 'testcontainers'
|
|
2
2
|
import { spawn, type ChildProcess, type StdioOptions } from 'node:child_process'
|
|
3
3
|
import { createServer } from 'node:net'
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
5
|
import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
5
6
|
import { createHash } from 'node:crypto'
|
|
6
7
|
import path from 'node:path'
|
|
7
8
|
import { createInterface, type Interface } from 'node:readline/promises'
|
|
8
9
|
import { stdin as input, stdout as output } from 'node:process'
|
|
9
|
-
import {
|
|
10
|
+
import { resolveEnvironment } from '../resolver'
|
|
10
11
|
import { discoverIntegrationSpecFiles as discoverIntegrationSpecFilesShared } from './integration-discovery'
|
|
11
12
|
import { resolveDockerHostFromContext, runCommandAndCapture } from './runtime-utils'
|
|
12
13
|
|
|
@@ -162,8 +163,50 @@ const PLAYWRIGHT_QUICK_FAILURE_THRESHOLD = 6
|
|
|
162
163
|
const PLAYWRIGHT_QUICK_FAILURE_MAX_DURATION_MS = 1_500
|
|
163
164
|
const PLAYWRIGHT_HEALTH_PROBE_INTERVAL_MS = 3_000
|
|
164
165
|
const ANSI_ESCAPE_REGEX = /\x1b\[[0-?]*[ -/]*[@-~]/g // NOSONAR — ANSI escape sequence pattern
|
|
165
|
-
const
|
|
166
|
-
const projectRootDirectory =
|
|
166
|
+
const env = resolveEnvironment()
|
|
167
|
+
const projectRootDirectory = env.rootDir
|
|
168
|
+
const appDirectory = env.appDir
|
|
169
|
+
const corePackageRootDirectory = env.packageRoot('@open-mercato/core')
|
|
170
|
+
const uiPackageRootDirectory = env.packageRoot('@open-mercato/ui')
|
|
171
|
+
|
|
172
|
+
function resolveFirstExistingPath(...candidates: string[]): string | null {
|
|
173
|
+
for (const candidate of candidates) {
|
|
174
|
+
if (existsSync(candidate)) {
|
|
175
|
+
return candidate
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function collectExistingPaths(candidates: Array<string | null | undefined>): string[] {
|
|
182
|
+
const collected = new Set<string>()
|
|
183
|
+
for (const candidate of candidates) {
|
|
184
|
+
if (candidate && existsSync(candidate)) {
|
|
185
|
+
collected.add(candidate)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return Array.from(collected)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readPackageScripts(packageRoot: string): Record<string, string> {
|
|
192
|
+
try {
|
|
193
|
+
const raw = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) as {
|
|
194
|
+
scripts?: Record<string, string>
|
|
195
|
+
}
|
|
196
|
+
return raw.scripts ?? {}
|
|
197
|
+
} catch {
|
|
198
|
+
return {}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const projectScripts = readPackageScripts(projectRootDirectory)
|
|
203
|
+
const appNextConfigPath = resolveFirstExistingPath(
|
|
204
|
+
path.join(appDirectory, 'next.config.ts'),
|
|
205
|
+
path.join(appDirectory, 'next.config.js'),
|
|
206
|
+
path.join(appDirectory, 'next.config.mjs'),
|
|
207
|
+
)
|
|
208
|
+
const APP_MODULES_CHECKSUM_PATH = path.join(appDirectory, '.mercato', 'generated', 'modules.generated.checksum')
|
|
209
|
+
const PROJECT_SUPPORTS_PACKAGE_BUILDS = typeof projectScripts['build:packages'] === 'string'
|
|
167
210
|
const EPHEMERAL_ENV_FILE_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-env.json')
|
|
168
211
|
const EPHEMERAL_ENV_LOCK_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-env.lock')
|
|
169
212
|
const LEGACY_EPHEMERAL_ENV_FILE_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-env.md')
|
|
@@ -172,26 +215,26 @@ const PLAYWRIGHT_INTEGRATION_CONFIG_PATH = '.ai/qa/tests/playwright.config.ts'
|
|
|
172
215
|
const PLAYWRIGHT_RESULTS_JSON_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'test-results', 'results.json')
|
|
173
216
|
const LEGACY_INTEGRATION_TEST_ROOT = path.join(projectRootDirectory, '.ai', 'qa', 'tests')
|
|
174
217
|
const APP_BUILD_ARTIFACTS = [
|
|
175
|
-
path.join(
|
|
176
|
-
path.join(
|
|
177
|
-
path.join(
|
|
178
|
-
path.join(
|
|
218
|
+
path.join(appDirectory, '.mercato', 'next', 'BUILD_ID'),
|
|
219
|
+
path.join(appDirectory, '.mercato', 'generated', 'modules.generated.ts'),
|
|
220
|
+
path.join(corePackageRootDirectory, 'dist', 'index.js'),
|
|
221
|
+
path.join(uiPackageRootDirectory, 'dist', 'index.js'),
|
|
179
222
|
]
|
|
180
|
-
const APP_BUILD_INPUT_PATHS = [
|
|
181
|
-
path.join(
|
|
182
|
-
path.join(
|
|
183
|
-
|
|
184
|
-
path.join(
|
|
185
|
-
path.join(
|
|
186
|
-
path.join(
|
|
187
|
-
path.join(
|
|
188
|
-
path.join(
|
|
189
|
-
path.join(
|
|
190
|
-
path.join(
|
|
223
|
+
const APP_BUILD_INPUT_PATHS = collectExistingPaths([
|
|
224
|
+
path.join(appDirectory, 'src'),
|
|
225
|
+
path.join(appDirectory, 'package.json'),
|
|
226
|
+
appNextConfigPath,
|
|
227
|
+
path.join(appDirectory, 'tsconfig.json'),
|
|
228
|
+
resolveFirstExistingPath(path.join(corePackageRootDirectory, 'src'), path.join(corePackageRootDirectory, 'dist')),
|
|
229
|
+
path.join(corePackageRootDirectory, 'package.json'),
|
|
230
|
+
path.join(corePackageRootDirectory, 'tsconfig.json'),
|
|
231
|
+
resolveFirstExistingPath(path.join(uiPackageRootDirectory, 'src'), path.join(uiPackageRootDirectory, 'dist')),
|
|
232
|
+
path.join(uiPackageRootDirectory, 'package.json'),
|
|
233
|
+
path.join(uiPackageRootDirectory, 'tsconfig.json'),
|
|
191
234
|
path.join(projectRootDirectory, 'package.json'),
|
|
192
235
|
path.join(projectRootDirectory, 'tsconfig.base.json'),
|
|
193
236
|
path.join(projectRootDirectory, 'yarn.lock'),
|
|
194
|
-
]
|
|
237
|
+
])
|
|
195
238
|
const EXPECTED_TEST_FOLDERS = ['auth', 'catalog', 'crm', 'sales', 'admin', 'api', 'integration'] as const
|
|
196
239
|
const FOLDER_TO_CATEGORY_CODE: Record<string, string> = {
|
|
197
240
|
admin: 'ADMIN',
|
|
@@ -295,8 +338,9 @@ function runYarnCommand(
|
|
|
295
338
|
args: string[],
|
|
296
339
|
environment: NodeJS.ProcessEnv,
|
|
297
340
|
opts: { silent?: boolean } = {},
|
|
341
|
+
cwd: string = projectRootDirectory,
|
|
298
342
|
): Promise<void> {
|
|
299
|
-
return runYarnRawCommand(['run', ...args], environment, opts)
|
|
343
|
+
return runYarnRawCommand(['run', ...args], environment, opts, cwd)
|
|
300
344
|
}
|
|
301
345
|
|
|
302
346
|
async function runTimedStep<T>(
|
|
@@ -543,11 +587,12 @@ function runYarnRawCommand(
|
|
|
543
587
|
commandArgs: string[],
|
|
544
588
|
environment: NodeJS.ProcessEnv,
|
|
545
589
|
opts: { silent?: boolean } = {},
|
|
590
|
+
cwd: string = projectRootDirectory,
|
|
546
591
|
): Promise<void> {
|
|
547
592
|
return new Promise((resolve, reject) => {
|
|
548
593
|
const outputMode: StdioOptions = opts.silent ? ['ignore', 'pipe', 'pipe'] : 'inherit'
|
|
549
594
|
const command: ChildProcess = spawn(resolveYarnBinary(), commandArgs, {
|
|
550
|
-
cwd
|
|
595
|
+
cwd,
|
|
551
596
|
env: environment,
|
|
552
597
|
stdio: outputMode,
|
|
553
598
|
})
|
|
@@ -593,24 +638,15 @@ function runNpxCommand(args: string[], environment: NodeJS.ProcessEnv): Promise<
|
|
|
593
638
|
})
|
|
594
639
|
}
|
|
595
640
|
|
|
596
|
-
function runYarnWorkspaceCommand(
|
|
597
|
-
workspaceName: string,
|
|
598
|
-
commandName: string,
|
|
599
|
-
commandArgs: string[],
|
|
600
|
-
environment: NodeJS.ProcessEnv,
|
|
601
|
-
opts: { silent?: boolean } = {},
|
|
602
|
-
): Promise<void> {
|
|
603
|
-
return runYarnRawCommand(['workspace', workspaceName, commandName, ...commandArgs], environment, opts)
|
|
604
|
-
}
|
|
605
|
-
|
|
606
641
|
function startYarnRawCommand(
|
|
607
642
|
commandArgs: string[],
|
|
608
643
|
environment: NodeJS.ProcessEnv,
|
|
609
644
|
opts: { silent?: boolean } = {},
|
|
645
|
+
cwd: string = projectRootDirectory,
|
|
610
646
|
): ChildProcess {
|
|
611
647
|
const outputMode: StdioOptions = opts.silent ? ['ignore', 'pipe', 'pipe'] : 'inherit'
|
|
612
648
|
const processHandle: ChildProcess = spawn(resolveYarnBinary(), commandArgs, {
|
|
613
|
-
cwd
|
|
649
|
+
cwd,
|
|
614
650
|
env: environment,
|
|
615
651
|
stdio: outputMode,
|
|
616
652
|
})
|
|
@@ -621,14 +657,13 @@ function startYarnRawCommand(
|
|
|
621
657
|
return processHandle
|
|
622
658
|
}
|
|
623
659
|
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
commandName: string,
|
|
627
|
-
commandArgs: string[],
|
|
660
|
+
function startYarnCommand(
|
|
661
|
+
args: string[],
|
|
628
662
|
environment: NodeJS.ProcessEnv,
|
|
629
663
|
opts: { silent?: boolean } = {},
|
|
664
|
+
cwd: string = projectRootDirectory,
|
|
630
665
|
): ChildProcess {
|
|
631
|
-
return startYarnRawCommand(['
|
|
666
|
+
return startYarnRawCommand(['run', ...args], environment, opts, cwd)
|
|
632
667
|
}
|
|
633
668
|
|
|
634
669
|
async function assertContainerRuntimeAvailable(): Promise<void> {
|
|
@@ -2501,7 +2536,6 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2501
2536
|
}
|
|
2502
2537
|
}
|
|
2503
2538
|
|
|
2504
|
-
const appWorkspace = '@open-mercato/app'
|
|
2505
2539
|
const shouldUseIsolatedPort = shouldUseIsolatedPortForFreshEnvironment({
|
|
2506
2540
|
reuseExisting: options.reuseExisting,
|
|
2507
2541
|
existingStateBeforeReuseAttempt,
|
|
@@ -2607,9 +2641,9 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2607
2641
|
console.log(`[${options.logPrefix}] Ephemeral database ready at ${databaseHost}:${databasePort}`)
|
|
2608
2642
|
console.log(`[${options.logPrefix}] Initializing application data (includes migrations)...`)
|
|
2609
2643
|
await runTimedStep(options.logPrefix, 'Initializing application data', { expectedSeconds: 45 }, async () =>
|
|
2610
|
-
|
|
2644
|
+
runYarnCommand(['initialize'], commandEnvironment, {
|
|
2611
2645
|
silent: !options.verbose,
|
|
2612
|
-
}))
|
|
2646
|
+
}, appDirectory))
|
|
2613
2647
|
|
|
2614
2648
|
if (!needsBuild) {
|
|
2615
2649
|
console.log(
|
|
@@ -2621,32 +2655,38 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2621
2655
|
} else {
|
|
2622
2656
|
console.log(`[${options.logPrefix}] Build artifacts missing, stale, or out of date; rebuilding artifacts.`)
|
|
2623
2657
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2658
|
+
if (PROJECT_SUPPORTS_PACKAGE_BUILDS) {
|
|
2659
|
+
console.log(`[${options.logPrefix}] Building packages...`)
|
|
2660
|
+
await runTimedStep(options.logPrefix, 'Building packages', { expectedSeconds: 20 }, async () =>
|
|
2661
|
+
runYarnCommand(['build:packages'], commandEnvironment, {
|
|
2662
|
+
silent: !options.verbose,
|
|
2663
|
+
}))
|
|
2664
|
+
} else {
|
|
2665
|
+
console.log(`[${options.logPrefix}] Skipping package build step (no build:packages script at project root).`)
|
|
2666
|
+
}
|
|
2629
2667
|
|
|
2630
2668
|
console.log(`[${options.logPrefix}] Regenerating module artifacts...`)
|
|
2631
|
-
await rm(
|
|
2669
|
+
await rm(APP_MODULES_CHECKSUM_PATH, {
|
|
2632
2670
|
force: true,
|
|
2633
2671
|
})
|
|
2634
2672
|
await runTimedStep(options.logPrefix, 'Regenerating module artifacts', { expectedSeconds: 8 }, async () =>
|
|
2635
2673
|
runYarnCommand(['generate'], commandEnvironment, {
|
|
2636
2674
|
silent: !options.verbose,
|
|
2637
|
-
}))
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2675
|
+
}, appDirectory))
|
|
2676
|
+
|
|
2677
|
+
if (PROJECT_SUPPORTS_PACKAGE_BUILDS) {
|
|
2678
|
+
console.log(`[${options.logPrefix}] Rebuilding packages after generation...`)
|
|
2679
|
+
await runTimedStep(options.logPrefix, 'Rebuilding packages after generation', { expectedSeconds: 20 }, async () =>
|
|
2680
|
+
runYarnCommand(['build:packages'], commandEnvironment, {
|
|
2681
|
+
silent: !options.verbose,
|
|
2682
|
+
}))
|
|
2683
|
+
}
|
|
2644
2684
|
|
|
2645
2685
|
console.log(`[${options.logPrefix}] Building application...`)
|
|
2646
2686
|
await runTimedStep(options.logPrefix, 'Building application', { expectedSeconds: 76 }, async () =>
|
|
2647
|
-
|
|
2687
|
+
runYarnCommand(['build'], commandEnvironment, {
|
|
2648
2688
|
silent: !options.verbose,
|
|
2649
|
-
}))
|
|
2689
|
+
}, appDirectory))
|
|
2650
2690
|
}
|
|
2651
2691
|
|
|
2652
2692
|
if (shouldPersistBuildCache && sourceFingerprintValue) {
|
|
@@ -2654,9 +2694,9 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2654
2694
|
}
|
|
2655
2695
|
|
|
2656
2696
|
console.log(`[${options.logPrefix}] Starting application on ${applicationBaseUrl}...`)
|
|
2657
|
-
const startedAppProcess =
|
|
2697
|
+
const startedAppProcess = startYarnCommand(['start'], commandEnvironment, {
|
|
2658
2698
|
silent: !options.verbose,
|
|
2659
|
-
})
|
|
2699
|
+
}, appDirectory)
|
|
2660
2700
|
applicationProcess = startedAppProcess
|
|
2661
2701
|
|
|
2662
2702
|
await runTimedStep(
|
package/src/mercato.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
|
11
11
|
import { getSslConfig } from '@open-mercato/shared/lib/db/ssl'
|
|
12
12
|
import { getRedisUrl } from '@open-mercato/shared/lib/redis/connection'
|
|
13
13
|
import { resolveInitDerivedSecrets } from './lib/init-secrets'
|
|
14
|
+
import { parseModuleInstallArgs } from './lib/module-install-args'
|
|
14
15
|
// Lazy-imported to avoid pulling in `testcontainers` (devDependency) at startup
|
|
15
16
|
const lazyIntegration = () => import('./lib/testing/integration')
|
|
16
17
|
import type { ChildProcess } from 'node:child_process'
|
|
@@ -53,6 +54,51 @@ async function ensureEnvLoaded() {
|
|
|
53
54
|
} catch {}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
function resolveInstalledBinary(baseDirs: string[], relativeBinPath: string): string {
|
|
58
|
+
const checked = new Set<string>()
|
|
59
|
+
for (const baseDir of baseDirs) {
|
|
60
|
+
const candidate = path.join(baseDir, 'node_modules', relativeBinPath)
|
|
61
|
+
checked.add(candidate)
|
|
62
|
+
if (fs.existsSync(candidate)) return candidate
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Could not find installed binary "${relativeBinPath}". Checked: ${Array.from(checked).join(', ')}`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function handleDirectEjectCommand(args: string[]): Promise<number> {
|
|
70
|
+
const { createResolver } = await import('./lib/resolver')
|
|
71
|
+
const { listEjectableModules, ejectModule } = await import('./lib/eject')
|
|
72
|
+
const resolver = createResolver()
|
|
73
|
+
const commandArgs = args.filter(Boolean)
|
|
74
|
+
const isList = commandArgs.includes('--list') || commandArgs.includes('-l')
|
|
75
|
+
const moduleId = isList ? undefined : commandArgs.find((arg) => !arg.startsWith('-'))
|
|
76
|
+
|
|
77
|
+
if (isList || !moduleId) {
|
|
78
|
+
const ejectable = listEjectableModules(resolver)
|
|
79
|
+
if (ejectable.length === 0) {
|
|
80
|
+
console.log('No ejectable modules found.')
|
|
81
|
+
} else {
|
|
82
|
+
console.log('Ejectable modules:\n')
|
|
83
|
+
for (const mod of ejectable) {
|
|
84
|
+
const desc = mod.description ? ` — ${mod.description}` : ''
|
|
85
|
+
console.log(` ${mod.id} (from: ${mod.from})${desc}`)
|
|
86
|
+
}
|
|
87
|
+
console.log('\nUsage: yarn mercato eject <moduleId>')
|
|
88
|
+
}
|
|
89
|
+
return 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`Ejecting module "${moduleId}"...`)
|
|
93
|
+
ejectModule(resolver, moduleId)
|
|
94
|
+
console.log(`\n✅ Module "${moduleId}" ejected successfully!\n`)
|
|
95
|
+
console.log('Next steps:')
|
|
96
|
+
console.log(' 1. Run generators: yarn mercato generate all')
|
|
97
|
+
console.log(` 2. Customize: edit src/modules/${moduleId}/`)
|
|
98
|
+
console.log(' 3. Start dev: yarn dev')
|
|
99
|
+
return 0
|
|
100
|
+
}
|
|
101
|
+
|
|
56
102
|
// Helper to run a CLI command directly (without spawning a process)
|
|
57
103
|
async function runModuleCommand(
|
|
58
104
|
allModules: Module[],
|
|
@@ -274,13 +320,14 @@ export async function run(argv = process.argv) {
|
|
|
274
320
|
// Step 1: Run generators directly (no process spawn)
|
|
275
321
|
console.log('🔧 Preparing modules (registry, entities, DI)...')
|
|
276
322
|
const { createResolver } = await import('./lib/resolver')
|
|
277
|
-
const { generateEntityIds, generateModuleRegistry, generateModuleRegistryCli, generateModuleEntities, generateModuleDi, generateOpenApi } = await import('./lib/generators')
|
|
323
|
+
const { generateEntityIds, generateModuleRegistry, generateModuleRegistryCli, generateModuleEntities, generateModuleDi, generateModulePackageSources, generateOpenApi } = await import('./lib/generators')
|
|
278
324
|
const resolver = createResolver()
|
|
279
325
|
await generateEntityIds({ resolver, quiet: true })
|
|
280
326
|
await generateModuleRegistry({ resolver, quiet: true })
|
|
281
327
|
await generateModuleRegistryCli({ resolver, quiet: true })
|
|
282
328
|
await generateModuleEntities({ resolver, quiet: true })
|
|
283
329
|
await generateModuleDi({ resolver, quiet: true })
|
|
330
|
+
await generateModulePackageSources({ resolver, quiet: true })
|
|
284
331
|
await generateOpenApi({ resolver, quiet: true })
|
|
285
332
|
console.log('✅ Modules prepared\n')
|
|
286
333
|
|
|
@@ -524,39 +571,72 @@ export async function run(argv = process.argv) {
|
|
|
524
571
|
return exitCode
|
|
525
572
|
}
|
|
526
573
|
|
|
527
|
-
|
|
528
|
-
if (first === 'eject') {
|
|
574
|
+
if (first === 'module') {
|
|
529
575
|
try {
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
|
|
576
|
+
const subcommand = second
|
|
577
|
+
const commandArgs = remaining.filter(Boolean)
|
|
578
|
+
|
|
579
|
+
if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
580
|
+
console.log('Usage: yarn mercato module <add|enable|eject> ...')
|
|
581
|
+
console.log(' yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]')
|
|
582
|
+
console.log(' yarn mercato module enable <packageName> [--module <moduleId>] [--eject]')
|
|
583
|
+
console.log(' yarn mercato module eject <moduleId>')
|
|
584
|
+
return 0
|
|
585
|
+
}
|
|
533
586
|
|
|
534
|
-
|
|
535
|
-
|
|
587
|
+
if (subcommand === 'add') {
|
|
588
|
+
const { createResolver } = await import('./lib/resolver')
|
|
589
|
+
const { addOfficialModule } = await import('./lib/module-install')
|
|
590
|
+
const { packageSpec, eject, moduleId } = parseModuleInstallArgs(commandArgs)
|
|
536
591
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
console.log('No ejectable modules found.')
|
|
541
|
-
} else {
|
|
542
|
-
console.log('Ejectable modules:\n')
|
|
543
|
-
for (const mod of ejectable) {
|
|
544
|
-
const desc = mod.description ? ` — ${mod.description}` : ''
|
|
545
|
-
console.log(` ${mod.id} (from: ${mod.from})${desc}`)
|
|
546
|
-
}
|
|
547
|
-
console.log('\nUsage: yarn mercato eject <moduleId>')
|
|
592
|
+
if (!packageSpec) {
|
|
593
|
+
console.error('Usage: yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]')
|
|
594
|
+
return 1
|
|
548
595
|
}
|
|
596
|
+
|
|
597
|
+
const result = await addOfficialModule(createResolver(), packageSpec, eject, moduleId ?? undefined)
|
|
598
|
+
console.log(`\n✅ Module "${result.moduleId}" enabled from ${result.from}.\n`)
|
|
599
|
+
console.log('Next steps:')
|
|
600
|
+
console.log(' 1. Review generated files if needed: .mercato/generated/')
|
|
601
|
+
console.log(' 2. Start dev: yarn dev')
|
|
549
602
|
return 0
|
|
550
603
|
}
|
|
551
604
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
605
|
+
if (subcommand === 'enable') {
|
|
606
|
+
const packageName = commandArgs.find((arg) => !arg.startsWith('-'))
|
|
607
|
+
if (!packageName) {
|
|
608
|
+
console.error('Usage: yarn mercato module enable <packageName> [--module <moduleId>] [--eject]')
|
|
609
|
+
return 1
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const { createResolver } = await import('./lib/resolver')
|
|
613
|
+
const { enableOfficialModule } = await import('./lib/module-install')
|
|
614
|
+
const { moduleId, eject } = parseModuleInstallArgs(commandArgs)
|
|
615
|
+
const result = await enableOfficialModule(createResolver(), packageName, moduleId ?? undefined, eject)
|
|
616
|
+
console.log(`\n✅ Module "${result.moduleId}" enabled from ${result.from}.\n`)
|
|
617
|
+
console.log('Next steps:')
|
|
618
|
+
console.log(' 1. Review generated files if needed: .mercato/generated/')
|
|
619
|
+
console.log(' 2. Start dev: yarn dev')
|
|
620
|
+
return 0
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (subcommand === 'eject') {
|
|
624
|
+
return handleDirectEjectCommand(commandArgs)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
console.error(`Unknown module subcommand "${subcommand}".`)
|
|
628
|
+
return 1
|
|
629
|
+
} catch (error: unknown) {
|
|
630
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
631
|
+
console.error(`❌ Module command failed: ${message}`)
|
|
632
|
+
return 1
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Handle eject command directly (bootstrap-free)
|
|
637
|
+
if (first === 'eject') {
|
|
638
|
+
try {
|
|
639
|
+
return handleDirectEjectCommand(parts.slice(1))
|
|
560
640
|
} catch (error: unknown) {
|
|
561
641
|
const message = error instanceof Error ? error.message : String(error)
|
|
562
642
|
console.error(`❌ Eject failed: ${message}`)
|
|
@@ -880,7 +960,7 @@ export async function run(argv = process.argv) {
|
|
|
880
960
|
command: 'all',
|
|
881
961
|
run: async (args: string[]) => {
|
|
882
962
|
const { createResolver } = await import('./lib/resolver')
|
|
883
|
-
const { generateEntityIds, generateModuleRegistry, generateModuleRegistryCli, generateModuleEntities, generateModuleDi, generateOpenApi } = await import('./lib/generators')
|
|
963
|
+
const { generateEntityIds, generateModuleRegistry, generateModuleRegistryCli, generateModuleEntities, generateModuleDi, generateModulePackageSources, generateOpenApi } = await import('./lib/generators')
|
|
884
964
|
const resolver = createResolver()
|
|
885
965
|
const quiet = args.includes('--quiet') || args.includes('-q')
|
|
886
966
|
|
|
@@ -890,6 +970,7 @@ export async function run(argv = process.argv) {
|
|
|
890
970
|
await generateModuleRegistryCli({ resolver, quiet })
|
|
891
971
|
await generateModuleEntities({ resolver, quiet })
|
|
892
972
|
await generateModuleDi({ resolver, quiet })
|
|
973
|
+
await generateModulePackageSources({ resolver, quiet })
|
|
893
974
|
await generateOpenApi({ resolver, quiet })
|
|
894
975
|
console.log('All generators completed.')
|
|
895
976
|
},
|
|
@@ -907,9 +988,10 @@ export async function run(argv = process.argv) {
|
|
|
907
988
|
command: 'registry',
|
|
908
989
|
run: async (args: string[]) => {
|
|
909
990
|
const { createResolver } = await import('./lib/resolver')
|
|
910
|
-
const { generateModuleRegistry } = await import('./lib/generators')
|
|
991
|
+
const { generateModulePackageSources, generateModuleRegistry } = await import('./lib/generators')
|
|
911
992
|
const resolver = createResolver()
|
|
912
993
|
await generateModuleRegistry({ resolver, quiet: args.includes('--quiet') })
|
|
994
|
+
await generateModulePackageSources({ resolver, quiet: args.includes('--quiet') })
|
|
913
995
|
},
|
|
914
996
|
},
|
|
915
997
|
{
|
|
@@ -976,13 +1058,10 @@ export async function run(argv = process.argv) {
|
|
|
976
1058
|
command: 'dev',
|
|
977
1059
|
run: async () => {
|
|
978
1060
|
const { spawn } = await import('child_process')
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
// In monorepo, packages are hoisted to root; in standalone, they're in app's node_modules
|
|
985
|
-
const nodeModulesBase = resolver.isMonorepo() ? resolver.getRootDir() : appDir
|
|
1061
|
+
const { resolveEnvironment } = await import('./lib/resolver')
|
|
1062
|
+
const env = resolveEnvironment()
|
|
1063
|
+
const appDir = env.appDir
|
|
1064
|
+
const nodeModulesBases = Array.from(new Set([env.rootDir, appDir]))
|
|
986
1065
|
|
|
987
1066
|
const processes: ChildProcess[] = []
|
|
988
1067
|
const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== 'false'
|
|
@@ -1003,9 +1082,13 @@ export async function run(argv = process.argv) {
|
|
|
1003
1082
|
|
|
1004
1083
|
console.log('[server] Starting Open Mercato in dev mode...')
|
|
1005
1084
|
|
|
1006
|
-
//
|
|
1007
|
-
const
|
|
1008
|
-
const
|
|
1085
|
+
// Ensure module-package-sources.css exists before Next.js starts
|
|
1086
|
+
const { createResolver: createResolverForSources } = await import('./lib/resolver')
|
|
1087
|
+
const { generateModulePackageSources } = await import('./lib/generators')
|
|
1088
|
+
await generateModulePackageSources({ resolver: createResolverForSources(), quiet: true })
|
|
1089
|
+
|
|
1090
|
+
const nextBin = resolveInstalledBinary(nodeModulesBases, 'next/dist/bin/next')
|
|
1091
|
+
const mercatoBin = resolveInstalledBinary(nodeModulesBases, '@open-mercato/cli/bin/mercato')
|
|
1009
1092
|
|
|
1010
1093
|
// Start Next.js dev
|
|
1011
1094
|
const nextProcess = spawn('node', [nextBin, 'dev', '--turbopack'], {
|
|
@@ -1053,13 +1136,10 @@ export async function run(argv = process.argv) {
|
|
|
1053
1136
|
command: 'start',
|
|
1054
1137
|
run: async () => {
|
|
1055
1138
|
const { spawn } = await import('child_process')
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
// In monorepo, packages are hoisted to root; in standalone, they're in app's node_modules
|
|
1062
|
-
const nodeModulesBase = resolver.isMonorepo() ? resolver.getRootDir() : appDir
|
|
1139
|
+
const { resolveEnvironment } = await import('./lib/resolver')
|
|
1140
|
+
const env = resolveEnvironment()
|
|
1141
|
+
const appDir = env.appDir
|
|
1142
|
+
const nodeModulesBases = Array.from(new Set([env.rootDir, appDir]))
|
|
1063
1143
|
|
|
1064
1144
|
const processes: ChildProcess[] = []
|
|
1065
1145
|
const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== 'false'
|
|
@@ -1080,9 +1160,8 @@ export async function run(argv = process.argv) {
|
|
|
1080
1160
|
|
|
1081
1161
|
console.log('[server] Starting Open Mercato in production mode...')
|
|
1082
1162
|
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
const mercatoBin = path.join(nodeModulesBase, 'node_modules/@open-mercato/cli/bin/mercato')
|
|
1163
|
+
const nextBin = resolveInstalledBinary(nodeModulesBases, 'next/dist/bin/next')
|
|
1164
|
+
const mercatoBin = resolveInstalledBinary(nodeModulesBases, '@open-mercato/cli/bin/mercato')
|
|
1086
1165
|
|
|
1087
1166
|
// Start Next.js production server
|
|
1088
1167
|
const nextProcess = spawn('node', [nextBin, 'start'], {
|