@open-mercato/cli 0.6.8-develop.6889.1.af4501ca97 → 0.6.8-develop.6892.1.f179677788
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 +3 -3
- package/AGENTS.md +6 -1
- package/dist/agentic/guides/module-facts.json +113 -113
- package/dist/agentic/guides/modules/ai_assistant.md +1 -1
- package/dist/agentic/guides/modules/api_docs.md +1 -1
- package/dist/agentic/guides/modules/api_keys.md +1 -1
- package/dist/agentic/guides/modules/attachments.md +1 -1
- package/dist/agentic/guides/modules/audit_logs.md +1 -1
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail.md +1 -1
- package/dist/agentic/guides/modules/channel_imap.md +1 -1
- package/dist/agentic/guides/modules/checkout.md +1 -1
- package/dist/agentic/guides/modules/communication_channels.md +3 -3
- package/dist/agentic/guides/modules/configs.md +1 -1
- package/dist/agentic/guides/modules/content.md +1 -1
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/dashboards.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/design_system.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +1 -1
- package/dist/agentic/guides/modules/directory.md +1 -1
- package/dist/agentic/guides/modules/entities.md +1 -1
- package/dist/agentic/guides/modules/events.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
- package/dist/agentic/guides/modules/generators.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +1 -1
- package/dist/agentic/guides/modules/notifications.md +1 -1
- package/dist/agentic/guides/modules/onboarding.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways.md +1 -1
- package/dist/agentic/guides/modules/perspectives.md +1 -1
- package/dist/agentic/guides/modules/planner.md +1 -1
- package/dist/agentic/guides/modules/portal.md +1 -1
- package/dist/agentic/guides/modules/progress.md +1 -1
- package/dist/agentic/guides/modules/query_index.md +1 -1
- package/dist/agentic/guides/modules/record_locks.md +1 -1
- package/dist/agentic/guides/modules/resources.md +1 -1
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/scheduler.md +1 -1
- package/dist/agentic/guides/modules/search.md +1 -1
- package/dist/agentic/guides/modules/security.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
- package/dist/agentic/guides/modules/sso.md +1 -1
- package/dist/agentic/guides/modules/staff.md +1 -1
- package/dist/agentic/guides/modules/storage_s3.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
- package/dist/agentic/guides/modules/sync_excel.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
- package/dist/agentic/guides/modules/translations.md +1 -1
- package/dist/agentic/guides/modules/webhooks.md +1 -1
- package/dist/agentic/guides/modules/wms.md +1 -1
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +1 -0
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/bin.js +20 -34
- package/dist/bin.js.map +2 -2
- package/dist/lib/cli-bootstrap-mode.js +32 -0
- package/dist/lib/cli-bootstrap-mode.js.map +7 -0
- package/dist/lib/dev-supervisor-manifest.js +105 -0
- package/dist/lib/dev-supervisor-manifest.js.map +7 -0
- package/dist/lib/generators/entity-ids.js +11 -24
- package/dist/lib/generators/entity-ids.js.map +2 -2
- package/dist/lib/generators/module-di.js +10 -23
- package/dist/lib/generators/module-di.js.map +2 -2
- package/dist/lib/generators/module-entities.js +10 -23
- package/dist/lib/generators/module-entities.js.map +2 -2
- package/dist/lib/generators/module-registry.js +394 -31
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/route-manifest-shards.js +170 -0
- package/dist/lib/generators/route-manifest-shards.js.map +7 -0
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/dist/lib/resolver.js +26 -3
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/utils.js +15 -8
- package/dist/lib/utils.js.map +2 -2
- package/dist/mercato.js +9 -6
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/__tests__/mercato.test.ts +144 -0
- package/src/bin.ts +28 -42
- package/src/lib/__tests__/cli-bootstrap-mode.test.ts +22 -0
- package/src/lib/__tests__/content-stable-writes.test.ts +74 -0
- package/src/lib/__tests__/dev-supervisor-manifest.test.ts +131 -0
- package/src/lib/__tests__/queue-worker-supervisor.test.ts +33 -0
- package/src/lib/__tests__/resolver.enterprise.test.ts +50 -0
- package/src/lib/cli-bootstrap-mode.ts +31 -0
- package/src/lib/dev-supervisor-manifest.ts +142 -0
- package/src/lib/generators/__tests__/entity-ids.test.ts +32 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +248 -0
- package/src/lib/generators/__tests__/output-snapshots.test.ts +3 -3
- package/src/lib/generators/__tests__/route-manifest-shards.test.ts +62 -0
- package/src/lib/generators/entity-ids.ts +10 -26
- package/src/lib/generators/module-di.ts +9 -25
- package/src/lib/generators/module-entities.ts +9 -25
- package/src/lib/generators/module-registry.ts +432 -29
- package/src/lib/generators/route-manifest-shards.ts +146 -0
- package/src/lib/queue-worker-supervisor.ts +7 -2
- package/src/lib/resolver.ts +31 -3
- package/src/lib/utils.ts +24 -16
- package/src/mercato.ts +15 -6
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export type RouteManifestShardKind = 'api' | 'backend'
|
|
2
|
+
|
|
3
|
+
export type RouteManifestShardEntry = {
|
|
4
|
+
path: string
|
|
5
|
+
declaration: string
|
|
6
|
+
imports?: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type RouteManifestShardOutput = {
|
|
10
|
+
fileName: string
|
|
11
|
+
content: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type IndexedRouteManifestShardEntry = RouteManifestShardEntry & {
|
|
15
|
+
order: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DYNAMIC_SHARD_KEY = '__dynamic'
|
|
19
|
+
const ROOT_SHARD_KEY = '__root'
|
|
20
|
+
|
|
21
|
+
function normalizeRoutePath(kind: RouteManifestShardKind, pathname: string): string[] {
|
|
22
|
+
const segments = pathname.split('?')[0].split('/').filter(Boolean)
|
|
23
|
+
if (kind === 'backend' && segments[0]?.toLowerCase() === 'backend') {
|
|
24
|
+
segments.shift()
|
|
25
|
+
}
|
|
26
|
+
return segments
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getRouteManifestShardKey(kind: RouteManifestShardKind, pathname: string): string {
|
|
30
|
+
const firstSegment = normalizeRoutePath(kind, pathname)[0]
|
|
31
|
+
if (!firstSegment) return ROOT_SHARD_KEY
|
|
32
|
+
if (
|
|
33
|
+
firstSegment.startsWith('[')
|
|
34
|
+
|| firstSegment.startsWith(':')
|
|
35
|
+
|| firstSegment === '*'
|
|
36
|
+
) {
|
|
37
|
+
return DYNAMIC_SHARD_KEY
|
|
38
|
+
}
|
|
39
|
+
return firstSegment.toLowerCase()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderArray(entries: RouteManifestShardEntry[]): string {
|
|
43
|
+
if (entries.length === 0) return '[]'
|
|
44
|
+
return `[\n${entries.map((entry) => ` ${entry.declaration}`).join(',\n')}\n]`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function uniqueImports(entries: RouteManifestShardEntry[]): string[] {
|
|
48
|
+
return Array.from(new Set(entries.flatMap((entry) => entry.imports ?? [])))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shardStem(kind: RouteManifestShardKind, index: number, key: string): string {
|
|
52
|
+
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
53
|
+
return `${kind}-route-shard.${String(index).padStart(3, '0')}.${safeKey}.generated`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderShardFile(kind: RouteManifestShardKind, entries: IndexedRouteManifestShardEntry[]): string {
|
|
57
|
+
const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'
|
|
58
|
+
const helperImport = kind === 'backend' ? 'resolvePageRouteMetadata, ' : ''
|
|
59
|
+
const imports = uniqueImports(entries)
|
|
60
|
+
return `// AUTO-GENERATED by mercato generate registry (request-scoped route shard)\nimport { ${helperImport}type ${typeName} } from '@open-mercato/shared/modules/registry'\n${imports.length ? `${imports.join('\n')}\n` : ''}\nexport const routes: ${typeName}[] = ${renderArray(entries)}\nexport const routeOrders = [${entries.map((entry) => entry.order).join(', ')}]\n\nexport default routes\n`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderLoaderFile(
|
|
64
|
+
kind: RouteManifestShardKind,
|
|
65
|
+
shards: Array<{ key: string; stem: string; orders: number[] }>,
|
|
66
|
+
): string {
|
|
67
|
+
const isApi = kind === 'api'
|
|
68
|
+
const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'
|
|
69
|
+
const functionName = kind === 'api' ? 'loadApiRouteManifestShard' : 'loadBackendRouteManifestShard'
|
|
70
|
+
const ordinalFunctionName = isApi ? 'loadApiRouteManifestByOrdinal' : 'loadBackendRouteManifestByOrdinal'
|
|
71
|
+
const facadeExportName = isApi ? 'apiRouteFacades' : 'backendRouteFacades'
|
|
72
|
+
const metadataExportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'
|
|
73
|
+
const metadataFileName = isApi ? 'api-route-metadata.generated' : 'backend-route-metadata.generated'
|
|
74
|
+
const shardCases = shards
|
|
75
|
+
.map(({ key, stem }) => ` case ${JSON.stringify(key)}:\n return import('./${stem}').then((entry) => ({ routes: entry.routes, routeOrders: entry.routeOrders }))`)
|
|
76
|
+
.join('\n')
|
|
77
|
+
const hasDynamicShard = shards.some(({ key }) => key === DYNAMIC_SHARD_KEY)
|
|
78
|
+
const routeShardKeys: string[] = []
|
|
79
|
+
for (const shard of shards) {
|
|
80
|
+
for (const order of shard.orders) routeShardKeys[order] = shard.key
|
|
81
|
+
}
|
|
82
|
+
const prefixNormalization = kind === 'backend'
|
|
83
|
+
? ` if (segments[0]?.toLowerCase() === 'backend') segments.shift()\n`
|
|
84
|
+
: ''
|
|
85
|
+
const loadDynamic = hasDynamicShard
|
|
86
|
+
? ` const dynamicShard = await loadShard(${JSON.stringify(DYNAMIC_SHARD_KEY)})\n`
|
|
87
|
+
: ` const dynamicShard: LoadedRouteShard = EMPTY_SHARD\n`
|
|
88
|
+
|
|
89
|
+
return `// AUTO-GENERATED by mercato generate registry (request-scoped route loader)\nimport type { ${typeName} } from '@open-mercato/shared/modules/registry'\nimport { ${metadataExportName} } from './${metadataFileName}'\n\ntype LoadedRouteShard = { routes: ${typeName}[]; routeOrders: number[] }\nconst EMPTY_SHARD: LoadedRouteShard = { routes: [], routeOrders: [] }\nconst ROUTE_SHARD_KEYS = ${JSON.stringify(routeShardKeys)}\n\nfunction resolveShardKey(pathname: string): string {\n const segments = pathname.split('?')[0].split('/').filter(Boolean)\n${prefixNormalization} return segments[0]?.toLowerCase() ?? ${JSON.stringify(ROOT_SHARD_KEY)}\n}\n\nasync function loadShard(key: string): Promise<LoadedRouteShard> {\n switch (key) {\n${shardCases ? `${shardCases}\n` : ''} default:\n return EMPTY_SHARD\n }\n}\n\nexport async function ${ordinalFunctionName}(ordinal: number): Promise<${typeName}> {\n const shardKey = ROUTE_SHARD_KEYS[ordinal]\n if (shardKey === undefined) throw new Error('Unknown generated route manifest ordinal: ' + ordinal)\n const shard = await loadShard(shardKey)\n const index = shard.routeOrders.indexOf(ordinal)\n if (index < 0 || !shard.routes[index]) throw new Error('Generated route manifest ordinal is missing from its shard: ' + ordinal)\n return shard.routes[index]\n}\n\nexport const ${facadeExportName}: ${typeName}[] = ${metadataExportName}.map((metadata, ordinal) => ({\n ...metadata,\n load: async () => {\n const route = await ${ordinalFunctionName}(ordinal)\n return route.load()\n },\n}))\n\nexport async function ${functionName}(pathname: string): Promise<${typeName}[]> {\n const exactShard = await loadShard(resolveShardKey(pathname))\n${loadDynamic} return [\n ...exactShard.routes.map((route, index) => ({ route, order: exactShard.routeOrders[index] })),\n ...dynamicShard.routes.map((route, index) => ({ route, order: dynamicShard.routeOrders[index] })),\n ].sort((a, b) => a.order - b.order).map(({ route }) => route)\n}\n`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function renderRouteManifestShardOutputs(
|
|
93
|
+
kind: RouteManifestShardKind,
|
|
94
|
+
entries: RouteManifestShardEntry[],
|
|
95
|
+
): RouteManifestShardOutput[] {
|
|
96
|
+
const grouped = new Map<string, IndexedRouteManifestShardEntry[]>()
|
|
97
|
+
for (const [order, entry] of entries.entries()) {
|
|
98
|
+
const key = getRouteManifestShardKey(kind, entry.path)
|
|
99
|
+
const existing = grouped.get(key)
|
|
100
|
+
const indexedEntry = { ...entry, order }
|
|
101
|
+
if (existing) existing.push(indexedEntry)
|
|
102
|
+
else grouped.set(key, [indexedEntry])
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sortedGroups = Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b))
|
|
106
|
+
const shards = sortedGroups.map(([key, shardEntries], index) => {
|
|
107
|
+
const stem = shardStem(kind, index, key)
|
|
108
|
+
return {
|
|
109
|
+
key,
|
|
110
|
+
stem,
|
|
111
|
+
orders: shardEntries.map((entry) => entry.order),
|
|
112
|
+
output: {
|
|
113
|
+
fileName: `${stem}.ts`,
|
|
114
|
+
content: renderShardFile(kind, shardEntries),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
const loaderFileName = kind === 'api'
|
|
119
|
+
? 'api-route-shards.generated.ts'
|
|
120
|
+
: 'backend-route-shards.generated.ts'
|
|
121
|
+
|
|
122
|
+
return [
|
|
123
|
+
...shards.map(({ output }) => output),
|
|
124
|
+
{
|
|
125
|
+
fileName: loaderFileName,
|
|
126
|
+
content: renderLoaderFile(kind, shards),
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function renderRouteMetadataOutput(
|
|
132
|
+
kind: RouteManifestShardKind,
|
|
133
|
+
entries: RouteManifestShardEntry[],
|
|
134
|
+
): RouteManifestShardOutput {
|
|
135
|
+
const isApi = kind === 'api'
|
|
136
|
+
const manifestType = isApi ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'
|
|
137
|
+
const metadataType = isApi ? 'ApiRouteMetadataEntry' : 'BackendRouteMetadataEntry'
|
|
138
|
+
const exportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'
|
|
139
|
+
const fileName = isApi ? 'api-route-metadata.generated.ts' : 'backend-route-metadata.generated.ts'
|
|
140
|
+
const helperImport = isApi ? '' : 'resolvePageRouteMetadata, '
|
|
141
|
+
const imports = uniqueImports(entries)
|
|
142
|
+
const content = `// AUTO-GENERATED by mercato generate registry (route metadata without component loaders)\nimport { ${helperImport}type ${manifestType} } from '@open-mercato/shared/modules/registry'\n${imports.length ? `${imports.join('\n')}\n` : ''}\nexport type ${metadataType} = Omit<${manifestType}, 'load'>\n\nexport const ${exportName}: ${metadataType}[] = ${renderArray(entries)}\n\nexport default ${exportName}\n`
|
|
143
|
+
return { fileName, content }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const routeManifestShardFilePattern = /^(?:api|backend)-route-shard\..+\.generated\.(?:ts|checksum)$/
|
|
@@ -32,6 +32,11 @@ import {
|
|
|
32
32
|
import type { ModuleWorker } from '@open-mercato/shared/modules/registry'
|
|
33
33
|
import type { LazyWorkerSpawnMode } from './auto-spawn-workers'
|
|
34
34
|
|
|
35
|
+
export type SupervisorWorkerDescriptor = Pick<
|
|
36
|
+
ModuleWorker,
|
|
37
|
+
'id' | 'moduleId' | 'queue' | 'concurrency'
|
|
38
|
+
>
|
|
39
|
+
|
|
35
40
|
export type LazySupervisorSpawnFn = (
|
|
36
41
|
command: string,
|
|
37
42
|
args: readonly string[],
|
|
@@ -47,7 +52,7 @@ export type LazyWorkerSupervisorOptions = {
|
|
|
47
52
|
mercatoBin: string
|
|
48
53
|
appDir: string
|
|
49
54
|
runtimeEnv: NodeJS.ProcessEnv
|
|
50
|
-
workers:
|
|
55
|
+
workers: readonly SupervisorWorkerDescriptor[]
|
|
51
56
|
pollMs: number
|
|
52
57
|
restartOnUnexpectedExit: boolean
|
|
53
58
|
spawnMode?: LazyWorkerSpawnMode
|
|
@@ -89,7 +94,7 @@ type QueueGroup = {
|
|
|
89
94
|
workerCount: number
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
function groupWorkersByQueue(workers:
|
|
97
|
+
function groupWorkersByQueue(workers: readonly SupervisorWorkerDescriptor[]): QueueGroup[] {
|
|
93
98
|
const groups = new Map<string, QueueGroup>()
|
|
94
99
|
for (const worker of workers) {
|
|
95
100
|
const existing = groups.get(worker.queue)
|
package/src/lib/resolver.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type CliEnvironment = {
|
|
|
25
25
|
export type ModuleEntry = {
|
|
26
26
|
id: string
|
|
27
27
|
from?: '@open-mercato/core' | '@app' | string
|
|
28
|
+
devSupervisorRequiresFullBootstrap?: boolean
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export type PackageInfo = {
|
|
@@ -89,18 +90,45 @@ function pkgRootFor(rootDir: string, from?: string, isMonorepo = true): string {
|
|
|
89
90
|
function parseModuleEntryFromObjectLiteral(node: ts.ObjectLiteralExpression): ModuleEntry | null {
|
|
90
91
|
let id: string | null = null
|
|
91
92
|
let from: string | null = null
|
|
93
|
+
let devSupervisorRequiresFullBootstrap = false
|
|
92
94
|
for (const property of node.properties) {
|
|
93
|
-
if (!ts.isPropertyAssignment(property)
|
|
94
|
-
const key = property.name.
|
|
95
|
+
if (!ts.isPropertyAssignment(property)) continue
|
|
96
|
+
const key = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)
|
|
97
|
+
? property.name.text
|
|
98
|
+
: null
|
|
95
99
|
if (key === 'id' && ts.isStringLiteralLike(property.initializer)) {
|
|
96
100
|
id = property.initializer.text
|
|
97
101
|
}
|
|
98
102
|
if (key === 'from' && ts.isStringLiteralLike(property.initializer)) {
|
|
99
103
|
from = property.initializer.text
|
|
100
104
|
}
|
|
105
|
+
if (key === 'overrides') {
|
|
106
|
+
if (!ts.isObjectLiteralExpression(property.initializer)) {
|
|
107
|
+
devSupervisorRequiresFullBootstrap = true
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
for (const overrideProperty of property.initializer.properties) {
|
|
111
|
+
if (ts.isSpreadAssignment(overrideProperty)) {
|
|
112
|
+
devSupervisorRequiresFullBootstrap = true
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
if (!ts.isPropertyAssignment(overrideProperty)) continue
|
|
116
|
+
const overrideKey = ts.isIdentifier(overrideProperty.name) || ts.isStringLiteralLike(overrideProperty.name)
|
|
117
|
+
? overrideProperty.name.text
|
|
118
|
+
: null
|
|
119
|
+
if (overrideKey === 'workers' || overrideKey === 'cli') {
|
|
120
|
+
devSupervisorRequiresFullBootstrap = true
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
101
125
|
}
|
|
102
126
|
if (!id) return null
|
|
103
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
id,
|
|
129
|
+
from: from ?? '@open-mercato/core',
|
|
130
|
+
...(devSupervisorRequiresFullBootstrap ? { devSupervisorRequiresFullBootstrap: true } : {}),
|
|
131
|
+
}
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
function parseProcessEnvAccess(
|
package/src/lib/utils.ts
CHANGED
|
@@ -96,18 +96,22 @@ export function writeIfChanged(
|
|
|
96
96
|
structure: structureChecksum || '',
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
existingRecord.content !== newRecord.content ||
|
|
102
|
-
(structureChecksum && existingRecord.structure !== newRecord.structure)
|
|
103
|
-
|
|
104
|
-
if (shouldWrite) {
|
|
99
|
+
const outputChanged = !fs.existsSync(filePath) || fs.readFileSync(filePath, 'utf8') !== content
|
|
100
|
+
if (outputChanged) {
|
|
105
101
|
ensureDir(filePath)
|
|
106
102
|
fs.writeFileSync(filePath, content)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const checksumChanged =
|
|
106
|
+
!existingRecord ||
|
|
107
|
+
existingRecord.content !== newRecord.content ||
|
|
108
|
+
existingRecord.structure !== newRecord.structure
|
|
109
|
+
if (checksumChanged) {
|
|
110
|
+
ensureDir(checksumPath)
|
|
107
111
|
writeChecksumRecord(checksumPath, newRecord)
|
|
108
|
-
return true
|
|
109
112
|
}
|
|
110
|
-
|
|
113
|
+
|
|
114
|
+
return outputChanged
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
// Simple comparison without checksum file
|
|
@@ -234,18 +238,22 @@ export function writeGeneratedFile(options: {
|
|
|
234
238
|
const { outFile, checksumFile, content, structureChecksum, result, quiet } = options
|
|
235
239
|
const checksum = { content: calculateChecksum(content), structure: structureChecksum }
|
|
236
240
|
const existing = readChecksumRecord(checksumFile)
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
!existing ||
|
|
240
|
-
existing.content !== checksum.content ||
|
|
241
|
-
existing.structure !== checksum.structure
|
|
242
|
-
if (shouldWrite) {
|
|
241
|
+
const outputChanged = !fs.existsSync(outFile) || fs.readFileSync(outFile, 'utf8') !== content
|
|
242
|
+
if (outputChanged) {
|
|
243
243
|
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
244
244
|
fs.writeFileSync(outFile, content)
|
|
245
|
-
writeChecksumRecord(checksumFile, checksum)
|
|
246
245
|
result.filesWritten.push(outFile)
|
|
247
246
|
} else {
|
|
248
247
|
result.filesUnchanged.push(outFile)
|
|
249
248
|
}
|
|
250
|
-
|
|
249
|
+
|
|
250
|
+
const checksumChanged =
|
|
251
|
+
!existing ||
|
|
252
|
+
existing.content !== checksum.content ||
|
|
253
|
+
existing.structure !== checksum.structure
|
|
254
|
+
if (checksumChanged) {
|
|
255
|
+
fs.mkdirSync(path.dirname(checksumFile), { recursive: true })
|
|
256
|
+
writeChecksumRecord(checksumFile, checksum)
|
|
257
|
+
}
|
|
258
|
+
if (!quiet) logGenerationResult(path.relative(process.cwd(), outFile), outputChanged)
|
|
251
259
|
}
|
package/src/mercato.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { acquireServerStartLock } from './lib/server-start-lock'
|
|
|
43
43
|
import { assertSingleInstanceStrategies } from './lib/single-instance-strategy-guard'
|
|
44
44
|
import { createDevEnvReloader, watchDevEnvFiles } from './lib/dev-env-reload'
|
|
45
45
|
import { quotePostgresIdentifier } from './lib/db/identifiers'
|
|
46
|
+
import { getRegisteredDevSupervisorManifest } from './lib/dev-supervisor-manifest'
|
|
46
47
|
// Lazy-imported to avoid pulling in `testcontainers` (devDependency) at startup
|
|
47
48
|
const lazyIntegration = () => import('./lib/testing/integration')
|
|
48
49
|
import type { ChildProcess } from 'node:child_process'
|
|
@@ -1999,6 +2000,10 @@ export async function run(argv = process.argv) {
|
|
|
1999
2000
|
let lastRestartReason: string | null = null
|
|
2000
2001
|
const generateWatcherMode: GenerateWatcherMode = resolveGenerateWatcherMode(process.env)
|
|
2001
2002
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries)
|
|
2003
|
+
// The CLI binary registers this primitive manifest before dispatching
|
|
2004
|
+
// `server dev`. Direct `run()` callers keep the existing module-registry
|
|
2005
|
+
// fallback for backward compatibility and focused unit tests.
|
|
2006
|
+
const devSupervisorManifest = getRegisteredDevSupervisorManifest()
|
|
2002
2007
|
|
|
2003
2008
|
function cleanup() {
|
|
2004
2009
|
console.log('[server] Shutting down...')
|
|
@@ -2207,14 +2212,15 @@ export async function run(argv = process.argv) {
|
|
|
2207
2212
|
applyEventsSingleDeliveryGuard({ processEnv: process.env, runtimeEnv, autoSpawnWorkersMode })
|
|
2208
2213
|
const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env)
|
|
2209
2214
|
const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
|
|
2210
|
-
const
|
|
2215
|
+
const schedulerStartStatus = devSupervisorManifest?.schedulerStartStatus
|
|
2216
|
+
?? lookupModuleCommand(getCliModules(), 'scheduler', 'start').status
|
|
2211
2217
|
const embedSchedulerInSharedWorker =
|
|
2212
2218
|
shouldEmbedLocalSchedulerInSharedWorker(process.env)
|
|
2213
2219
|
&& queueStrategy === 'local'
|
|
2214
2220
|
&& autoSpawnWorkersMode === 'lazy'
|
|
2215
2221
|
&& resolveLazySpawnMode(process.env) === 'shared'
|
|
2216
2222
|
&& autoSpawnSchedulerMode !== 'off'
|
|
2217
|
-
&&
|
|
2223
|
+
&& schedulerStartStatus === 'ok'
|
|
2218
2224
|
const nextRuntime = startNextDev(runtimeEnv)
|
|
2219
2225
|
const restartPromise = waitForDevRestart()
|
|
2220
2226
|
const backgroundStartAbort = new AbortController()
|
|
@@ -2248,10 +2254,13 @@ export async function run(argv = process.argv) {
|
|
|
2248
2254
|
}
|
|
2249
2255
|
|
|
2250
2256
|
if (autoSpawnWorkersMode !== 'off') {
|
|
2251
|
-
const discoveredWorkers = getRegisteredCliWorkers()
|
|
2257
|
+
const discoveredWorkers = devSupervisorManifest?.workers ?? getRegisteredCliWorkers()
|
|
2252
2258
|
const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))]
|
|
2253
2259
|
if (discoveredWorkerQueues.length === 0) {
|
|
2254
|
-
|
|
2260
|
+
const workerRegistryFile = devSupervisorManifest
|
|
2261
|
+
? 'dev-supervisor.generated.json'
|
|
2262
|
+
: 'modules.cli.generated.ts'
|
|
2263
|
+
console.error(`[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run \`yarn generate\` and verify \`.mercato/generated/${workerRegistryFile}\` contains worker entries. Continuing without auto-spawned workers.`)
|
|
2255
2264
|
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
2256
2265
|
const lazySpawnMode = resolveLazySpawnMode(process.env)
|
|
2257
2266
|
const lazyModeHint = lazySpawnMode === 'shared'
|
|
@@ -2299,8 +2308,8 @@ export async function run(argv = process.argv) {
|
|
|
2299
2308
|
if (embedSchedulerInSharedWorker && activeLazySupervisor) {
|
|
2300
2309
|
console.log('[server] Local scheduler will run inside the lazy shared worker process.')
|
|
2301
2310
|
} else if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
|
|
2302
|
-
if (
|
|
2303
|
-
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(
|
|
2311
|
+
if (schedulerStartStatus !== 'ok') {
|
|
2312
|
+
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand({ status: schedulerStartStatus })}`)
|
|
2304
2313
|
} else if (autoSpawnSchedulerMode === 'lazy') {
|
|
2305
2314
|
console.log('[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.')
|
|
2306
2315
|
activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
|