@open-mercato/cli 0.6.8-develop.6891.1.4dca3f1ad3 → 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/manifest.json +1 -1
- 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,142 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { ModuleWorker } from '@open-mercato/shared/modules/registry'
|
|
4
|
+
|
|
5
|
+
export const DEV_SUPERVISOR_MANIFEST_FILE = 'dev-supervisor.generated.json'
|
|
6
|
+
export const DEV_SUPERVISOR_MANIFEST_VERSION = 1
|
|
7
|
+
|
|
8
|
+
export type DevSupervisorWorkerDescriptor = Pick<
|
|
9
|
+
ModuleWorker,
|
|
10
|
+
'id' | 'moduleId' | 'queue' | 'concurrency'
|
|
11
|
+
>
|
|
12
|
+
|
|
13
|
+
export type DevSupervisorSchedulerStartStatus =
|
|
14
|
+
| 'ok'
|
|
15
|
+
| 'missing-module'
|
|
16
|
+
| 'missing-cli'
|
|
17
|
+
| 'missing-command'
|
|
18
|
+
|
|
19
|
+
export type DevSupervisorManifest = {
|
|
20
|
+
version: typeof DEV_SUPERVISOR_MANIFEST_VERSION
|
|
21
|
+
workers: DevSupervisorWorkerDescriptor[]
|
|
22
|
+
schedulerStartStatus: DevSupervisorSchedulerStartStatus
|
|
23
|
+
requiresFullBootstrap: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let registeredManifest: DevSupervisorManifest | null = null
|
|
27
|
+
|
|
28
|
+
function manifestError(filePath: string, detail: string): Error {
|
|
29
|
+
return new Error(
|
|
30
|
+
`Invalid generated dev supervisor manifest at ${filePath}: ${detail}. ` +
|
|
31
|
+
'Run `yarn generate` and retry.',
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
36
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseWorkerDescriptor(
|
|
40
|
+
value: unknown,
|
|
41
|
+
index: number,
|
|
42
|
+
filePath: string,
|
|
43
|
+
): DevSupervisorWorkerDescriptor {
|
|
44
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
45
|
+
throw manifestError(filePath, `workers[${index}] must be an object`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const worker = value as Record<string, unknown>
|
|
49
|
+
if (!isNonEmptyString(worker.id)) {
|
|
50
|
+
throw manifestError(filePath, `workers[${index}].id must be a non-empty string`)
|
|
51
|
+
}
|
|
52
|
+
if (!isNonEmptyString(worker.queue)) {
|
|
53
|
+
throw manifestError(filePath, `workers[${index}].queue must be a non-empty string`)
|
|
54
|
+
}
|
|
55
|
+
if (typeof worker.concurrency !== 'number' || !Number.isFinite(worker.concurrency)) {
|
|
56
|
+
throw manifestError(filePath, `workers[${index}].concurrency must be a finite number`)
|
|
57
|
+
}
|
|
58
|
+
if (worker.moduleId !== undefined && !isNonEmptyString(worker.moduleId)) {
|
|
59
|
+
throw manifestError(filePath, `workers[${index}].moduleId must be a non-empty string when present`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
id: worker.id,
|
|
64
|
+
...(worker.moduleId === undefined ? {} : { moduleId: worker.moduleId }),
|
|
65
|
+
queue: worker.queue,
|
|
66
|
+
concurrency: worker.concurrency,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseManifest(value: unknown, filePath: string): DevSupervisorManifest {
|
|
71
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
72
|
+
throw manifestError(filePath, 'root value must be an object')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const manifest = value as Record<string, unknown>
|
|
76
|
+
if (manifest.version !== DEV_SUPERVISOR_MANIFEST_VERSION) {
|
|
77
|
+
throw manifestError(
|
|
78
|
+
filePath,
|
|
79
|
+
`unsupported version ${String(manifest.version)} (expected ${DEV_SUPERVISOR_MANIFEST_VERSION})`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(manifest.workers)) {
|
|
83
|
+
throw manifestError(filePath, 'workers must be an array')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const schedulerStartStatus = manifest.schedulerStartStatus
|
|
87
|
+
if (
|
|
88
|
+
schedulerStartStatus !== 'ok' &&
|
|
89
|
+
schedulerStartStatus !== 'missing-module' &&
|
|
90
|
+
schedulerStartStatus !== 'missing-cli' &&
|
|
91
|
+
schedulerStartStatus !== 'missing-command'
|
|
92
|
+
) {
|
|
93
|
+
throw manifestError(filePath, 'schedulerStartStatus is invalid')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
version: DEV_SUPERVISOR_MANIFEST_VERSION,
|
|
98
|
+
workers: manifest.workers.map((worker, index) => parseWorkerDescriptor(worker, index, filePath)),
|
|
99
|
+
schedulerStartStatus,
|
|
100
|
+
// Version-1 manifests produced before this safety marker existed cannot
|
|
101
|
+
// prove that worker/CLI overrides were accounted for. Preserve the old
|
|
102
|
+
// full-bootstrap behavior until `yarn generate` writes an explicit false.
|
|
103
|
+
requiresFullBootstrap: manifest.requiresFullBootstrap !== false,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function canUseLightweightDevSupervisor(manifest: DevSupervisorManifest): boolean {
|
|
108
|
+
return manifest.requiresFullBootstrap === false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function loadDevSupervisorManifest(appDir: string): DevSupervisorManifest {
|
|
112
|
+
const filePath = path.join(appDir, '.mercato', 'generated', DEV_SUPERVISOR_MANIFEST_FILE)
|
|
113
|
+
let source: string
|
|
114
|
+
try {
|
|
115
|
+
source = fs.readFileSync(filePath, 'utf8')
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
118
|
+
throw manifestError(filePath, `file could not be read (${detail})`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let parsed: unknown
|
|
122
|
+
try {
|
|
123
|
+
parsed = JSON.parse(source)
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
126
|
+
throw manifestError(filePath, `JSON could not be parsed (${detail})`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return parseManifest(parsed, filePath)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function registerDevSupervisorManifest(manifest: DevSupervisorManifest): void {
|
|
133
|
+
registeredManifest = manifest
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getRegisteredDevSupervisorManifest(): DevSupervisorManifest | null {
|
|
137
|
+
return registeredManifest
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resetDevSupervisorManifestForTests(): void {
|
|
141
|
+
registeredManifest = null
|
|
142
|
+
}
|
|
@@ -201,6 +201,38 @@ describe('generateEntityIds', () => {
|
|
|
201
201
|
expect(registry).not.toContain("import * as sales_order from './entities/sales_order/index'")
|
|
202
202
|
})
|
|
203
203
|
|
|
204
|
+
it('preserves every generated entity output mtime when the output bytes are unchanged', async () => {
|
|
205
|
+
const moduleEntry: ModuleEntry = { id: 'orders', from: '@open-mercato/core' }
|
|
206
|
+
const entitiesFile = path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'orders', 'data', 'entities.ts')
|
|
207
|
+
fs.mkdirSync(path.dirname(entitiesFile), { recursive: true })
|
|
208
|
+
fs.writeFileSync(entitiesFile, 'export class SalesOrder { id!: string; totalGross!: number }\n')
|
|
209
|
+
|
|
210
|
+
const resolver = createMockResolver(tmpDir, [moduleEntry])
|
|
211
|
+
const firstResult = await generateEntityIds({ resolver, quiet: true })
|
|
212
|
+
const outputDir = resolver.getOutputDir()
|
|
213
|
+
const generatedFiles = [
|
|
214
|
+
path.join(outputDir, 'entities.ids.generated.ts'),
|
|
215
|
+
path.join(outputDir, 'entity-fields-registry.ts'),
|
|
216
|
+
path.join(outputDir, 'entities', 'sales_order', 'index.ts'),
|
|
217
|
+
]
|
|
218
|
+
const oldTime = new Date(Date.now() - 60_000)
|
|
219
|
+
for (const filePath of generatedFiles) {
|
|
220
|
+
fs.utimesSync(filePath, oldTime, oldTime)
|
|
221
|
+
}
|
|
222
|
+
const mtimesBefore = new Map(
|
|
223
|
+
generatedFiles.map((filePath) => [filePath, fs.statSync(filePath).mtimeMs]),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const secondResult = await generateEntityIds({ resolver, quiet: true })
|
|
227
|
+
|
|
228
|
+
expect(firstResult.filesWritten).toEqual(expect.arrayContaining(generatedFiles))
|
|
229
|
+
expect(secondResult.filesWritten).toEqual([])
|
|
230
|
+
expect(secondResult.filesUnchanged).toEqual(expect.arrayContaining(generatedFiles))
|
|
231
|
+
for (const filePath of generatedFiles) {
|
|
232
|
+
expect(fs.statSync(filePath).mtimeMs).toBe(mtimesBefore.get(filePath))
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
|
|
204
236
|
it('reuses package generated entity metadata in standalone mode', async () => {
|
|
205
237
|
const moduleEntry: ModuleEntry = { id: 'directory', from: '@open-mercato/core' }
|
|
206
238
|
const packageRoot = path.join(tmpDir, 'node_modules', '@open-mercato', 'core')
|
|
@@ -578,6 +578,66 @@ export { POST, PUT, DELETE }
|
|
|
578
578
|
expect(apiRoutes).toContain('methods: ["GET", "POST", "PUT", "DELETE"]')
|
|
579
579
|
})
|
|
580
580
|
|
|
581
|
+
it('partitions API and backend loader graphs while preserving legacy manifests', async () => {
|
|
582
|
+
scaffoldModule(tmpDir, 'customers', 'pkg', ['api/people/route.ts', 'backend/people/page.tsx'])
|
|
583
|
+
scaffoldModule(tmpDir, 'sales', 'pkg', ['api/orders/route.ts', 'backend/orders/page.tsx'])
|
|
584
|
+
touchFile(
|
|
585
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'customers', 'api', 'people', 'route.ts'),
|
|
586
|
+
`export const metadata = { path: '/customers/people' }\nexport function GET() { return null }\n`,
|
|
587
|
+
)
|
|
588
|
+
touchFile(
|
|
589
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'customers', 'backend', 'people', 'page.tsx'),
|
|
590
|
+
`export const metadata = { pageTitle: 'People' }\nexport default function Page() { return null }\n`,
|
|
591
|
+
)
|
|
592
|
+
touchFile(
|
|
593
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'sales', 'api', 'orders', 'route.ts'),
|
|
594
|
+
`export const metadata = { path: '/sales/orders' }\nexport function POST() { return null }\n`,
|
|
595
|
+
)
|
|
596
|
+
touchFile(
|
|
597
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'sales', 'backend', 'orders', 'page.tsx'),
|
|
598
|
+
`export const metadata = { pageTitle: 'Orders' }\nexport default function Page() { return null }\n`,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
const resolver = createMockResolver(tmpDir, [
|
|
602
|
+
{ id: 'customers', from: '@open-mercato/core' },
|
|
603
|
+
{ id: 'sales', from: '@open-mercato/core' },
|
|
604
|
+
])
|
|
605
|
+
await generateModuleRegistry({ resolver, quiet: true })
|
|
606
|
+
|
|
607
|
+
const outputDir = path.join(tmpDir, 'output', 'generated')
|
|
608
|
+
const generatedNames = fs.readdirSync(outputDir)
|
|
609
|
+
const apiShardNames = generatedNames.filter((name) => /^api-route-shard\..+\.generated\.ts$/.test(name))
|
|
610
|
+
const backendShardNames = generatedNames.filter((name) => /^backend-route-shard\..+\.generated\.ts$/.test(name))
|
|
611
|
+
const apiShards = apiShardNames.map((name) => fs.readFileSync(path.join(outputDir, name), 'utf8'))
|
|
612
|
+
const backendShards = backendShardNames.map((name) => fs.readFileSync(path.join(outputDir, name), 'utf8'))
|
|
613
|
+
|
|
614
|
+
expect(apiShardNames).toHaveLength(2)
|
|
615
|
+
expect(backendShardNames).toHaveLength(2)
|
|
616
|
+
expect(apiShards.filter((source) => source.includes('/customers/people'))).toHaveLength(1)
|
|
617
|
+
expect(apiShards.filter((source) => source.includes('/sales/orders'))).toHaveLength(1)
|
|
618
|
+
expect(backendShards.filter((source) => source.includes('/backend/people'))).toHaveLength(1)
|
|
619
|
+
expect(backendShards.filter((source) => source.includes('/backend/orders'))).toHaveLength(1)
|
|
620
|
+
|
|
621
|
+
const apiLoader = readGenerated(tmpDir, 'api-route-shards.generated.ts')!
|
|
622
|
+
const backendLoader = readGenerated(tmpDir, 'backend-route-shards.generated.ts')!
|
|
623
|
+
expect(apiLoader).not.toContain('@open-mercato/core/modules/customers/api')
|
|
624
|
+
expect(backendLoader).not.toContain('@open-mercato/core/modules/customers/backend')
|
|
625
|
+
|
|
626
|
+
const apiMetadata = readGenerated(tmpDir, 'api-route-metadata.generated.ts')!
|
|
627
|
+
const backendMetadata = readGenerated(tmpDir, 'backend-route-metadata.generated.ts')!
|
|
628
|
+
expect(apiMetadata).not.toContain('load: async')
|
|
629
|
+
expect(backendMetadata).not.toContain('load: async')
|
|
630
|
+
expect(readGenerated(tmpDir, 'api-routes.generated.ts')).toContain('/customers/people')
|
|
631
|
+
expect(readGenerated(tmpDir, 'backend-routes.generated.ts')).toContain('/backend/orders')
|
|
632
|
+
|
|
633
|
+
const customersOnlyResolver = createMockResolver(tmpDir, [
|
|
634
|
+
{ id: 'customers', from: '@open-mercato/core' },
|
|
635
|
+
])
|
|
636
|
+
await generateModuleRegistry({ resolver: customersOnlyResolver, quiet: true })
|
|
637
|
+
const remainingApiShards = fs.readdirSync(outputDir).filter((name) => /^api-route-shard\..+\.generated\.ts$/.test(name))
|
|
638
|
+
expect(remainingApiShards).toHaveLength(1)
|
|
639
|
+
})
|
|
640
|
+
|
|
581
641
|
it('keeps all HTTP methods for route.ts files that destructure-export crud handlers', async () => {
|
|
582
642
|
scaffoldModule(tmpDir, 'example', 'pkg', ['api/todos/route.ts'])
|
|
583
643
|
touchFile(
|
|
@@ -1062,6 +1122,85 @@ export default async function handle(): Promise<void> {}
|
|
|
1062
1122
|
expect(output).toContain("workers:")
|
|
1063
1123
|
})
|
|
1064
1124
|
|
|
1125
|
+
it('writes a content-stable primitive dev supervisor manifest', async () => {
|
|
1126
|
+
scaffoldModule(tmpDir, 'events', 'pkg', ['workers/dispatch.ts'])
|
|
1127
|
+
scaffoldModule(tmpDir, 'scheduler', 'pkg', ['cli.ts'])
|
|
1128
|
+
touchFile(
|
|
1129
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'events', 'workers', 'dispatch.ts'),
|
|
1130
|
+
`export const metadata = { id: 'events:dispatch', queue: 'events', concurrency: 3 }\nexport default async function dispatch() {}\n`,
|
|
1131
|
+
)
|
|
1132
|
+
touchFile(
|
|
1133
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'scheduler', 'cli.ts'),
|
|
1134
|
+
`export default [{ command: 'start', run() {} }]\n`,
|
|
1135
|
+
)
|
|
1136
|
+
const resolver = createMockResolver(tmpDir, [
|
|
1137
|
+
{ id: 'events', from: '@open-mercato/core' },
|
|
1138
|
+
{ id: 'scheduler', from: '@open-mercato/core' },
|
|
1139
|
+
])
|
|
1140
|
+
|
|
1141
|
+
const first = await generateModuleRegistryCli({ resolver, quiet: true })
|
|
1142
|
+
const manifestPath = path.join(tmpDir, 'output', 'generated', 'dev-supervisor.generated.json')
|
|
1143
|
+
expect(JSON.parse(fs.readFileSync(manifestPath, 'utf8'))).toEqual({
|
|
1144
|
+
version: 1,
|
|
1145
|
+
workers: [{
|
|
1146
|
+
id: 'events:dispatch',
|
|
1147
|
+
moduleId: 'events',
|
|
1148
|
+
queue: 'events',
|
|
1149
|
+
concurrency: 3,
|
|
1150
|
+
}],
|
|
1151
|
+
schedulerStartStatus: 'ok',
|
|
1152
|
+
requiresFullBootstrap: false,
|
|
1153
|
+
})
|
|
1154
|
+
expect(first.filesWritten).toContain(manifestPath)
|
|
1155
|
+
|
|
1156
|
+
const second = await generateModuleRegistryCli({ resolver, quiet: true })
|
|
1157
|
+
expect(second.filesUnchanged).toContain(manifestPath)
|
|
1158
|
+
expect(second.filesWritten).not.toContain(manifestPath)
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1161
|
+
it('falls back to full bootstrap for worker overrides and computed scheduler commands', async () => {
|
|
1162
|
+
scaffoldModule(tmpDir, 'events', 'pkg', ['workers/dispatch.ts'])
|
|
1163
|
+
scaffoldModule(tmpDir, 'scheduler', 'pkg', ['cli.ts'])
|
|
1164
|
+
touchFile(
|
|
1165
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'events', 'workers', 'dispatch.ts'),
|
|
1166
|
+
`export const metadata = { id: 'events:dispatch', queue: 'events', concurrency: 3 }\nexport default async function dispatch() {}\n`,
|
|
1167
|
+
)
|
|
1168
|
+
touchFile(
|
|
1169
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'scheduler', 'cli.ts'),
|
|
1170
|
+
`const command = 'start'\nexport default [{ command, run() {} }]\n`,
|
|
1171
|
+
)
|
|
1172
|
+
const resolver = createMockResolver(tmpDir, [
|
|
1173
|
+
{ id: 'events', from: '@open-mercato/core', devSupervisorRequiresFullBootstrap: true },
|
|
1174
|
+
{ id: 'scheduler', from: '@open-mercato/core' },
|
|
1175
|
+
])
|
|
1176
|
+
|
|
1177
|
+
await generateModuleRegistryCli({ resolver, quiet: true })
|
|
1178
|
+
const manifestPath = path.join(tmpDir, 'output', 'generated', 'dev-supervisor.generated.json')
|
|
1179
|
+
expect(JSON.parse(fs.readFileSync(manifestPath, 'utf8'))).toMatchObject({
|
|
1180
|
+
schedulerStartStatus: 'missing-command',
|
|
1181
|
+
requiresFullBootstrap: true,
|
|
1182
|
+
})
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
it('falls back to full bootstrap when app code registers programmatic worker overrides', async () => {
|
|
1186
|
+
scaffoldModule(tmpDir, 'events', 'pkg', ['workers/dispatch.ts'])
|
|
1187
|
+
touchFile(
|
|
1188
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'events', 'workers', 'dispatch.ts'),
|
|
1189
|
+
`export const metadata = { id: 'events:dispatch', queue: 'events', concurrency: 3 }\nexport default async function dispatch() {}\n`,
|
|
1190
|
+
)
|
|
1191
|
+
touchFile(
|
|
1192
|
+
path.join(tmpDir, 'app', 'src', 'bootstrap-overrides.ts'),
|
|
1193
|
+
`import { applyWorkerOverrides as overrideWorkers } from '@open-mercato/shared/modules/overrides'\noverrideWorkers({ 'events:dispatch': null })\n`,
|
|
1194
|
+
)
|
|
1195
|
+
const resolver = createMockResolver(tmpDir, [
|
|
1196
|
+
{ id: 'events', from: '@open-mercato/core' },
|
|
1197
|
+
])
|
|
1198
|
+
|
|
1199
|
+
await generateModuleRegistryCli({ resolver, quiet: true })
|
|
1200
|
+
const manifestPath = path.join(tmpDir, 'output', 'generated', 'dev-supervisor.generated.json')
|
|
1201
|
+
expect(JSON.parse(fs.readFileSync(manifestPath, 'utf8')).requiresFullBootstrap).toBe(true)
|
|
1202
|
+
})
|
|
1203
|
+
|
|
1065
1204
|
it('handles disabling a module that was previously enabled', async () => {
|
|
1066
1205
|
scaffoldModule(tmpDir, 'keep_mod', 'pkg', [
|
|
1067
1206
|
'subscribers/handler.ts',
|
|
@@ -1480,6 +1619,115 @@ describe('generateModuleRegistryApp with module subsets', () => {
|
|
|
1480
1619
|
const bootstrapOutput = readGenerated(tmpDir, 'modules.bootstrap.generated.ts')
|
|
1481
1620
|
expect(bootstrapOutput).not.toBeNull()
|
|
1482
1621
|
expect(bootstrapOutput).toContain('export const modules: Module[] = [')
|
|
1622
|
+
|
|
1623
|
+
const i18nLoadersOutput = readGenerated(tmpDir, 'modules.i18n.loaders.generated.ts')
|
|
1624
|
+
expect(i18nLoadersOutput).toContain('export async function loadI18nModules')
|
|
1625
|
+
expect(i18nLoadersOutput).toContain('default:')
|
|
1626
|
+
expect(i18nLoadersOutput).toContain('return []')
|
|
1627
|
+
})
|
|
1628
|
+
|
|
1629
|
+
it('keeps the legacy i18n registry while emitting deterministic locale-specific shards', async () => {
|
|
1630
|
+
touchFile(
|
|
1631
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'localized', 'i18n', 'en.json'),
|
|
1632
|
+
JSON.stringify({ localized: { title: 'Core title' } }),
|
|
1633
|
+
)
|
|
1634
|
+
touchFile(
|
|
1635
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'localized', 'i18n', 'pl.json'),
|
|
1636
|
+
JSON.stringify({ localized: { title: 'Tytuł' } }),
|
|
1637
|
+
)
|
|
1638
|
+
touchFile(
|
|
1639
|
+
path.join(tmpDir, 'app', 'src', 'modules', 'localized', 'i18n', 'en.json'),
|
|
1640
|
+
JSON.stringify({ localized: { title: 'App title' } }),
|
|
1641
|
+
)
|
|
1642
|
+
touchFile(
|
|
1643
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'localized', 'i18n', '.hardcoded-allowlist.json'),
|
|
1644
|
+
JSON.stringify({ allow: ['framework chrome'] }),
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
const resolver = createMockResolver(tmpDir, [
|
|
1648
|
+
{ id: 'localized', from: '@open-mercato/core' },
|
|
1649
|
+
])
|
|
1650
|
+
const result = await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1651
|
+
|
|
1652
|
+
expect(result.errors).toEqual([])
|
|
1653
|
+
|
|
1654
|
+
const legacyI18nOutput = readGenerated(tmpDir, 'modules.i18n.generated.ts')!
|
|
1655
|
+
expect(legacyI18nOutput).toContain('@open-mercato/core/modules/localized/i18n/en.json')
|
|
1656
|
+
expect(legacyI18nOutput).toContain('@/modules/localized/i18n/en.json')
|
|
1657
|
+
expect(legacyI18nOutput).toContain('@open-mercato/core/modules/localized/i18n/pl.json')
|
|
1658
|
+
expect(legacyI18nOutput).toContain('export const modules: Module[] = [')
|
|
1659
|
+
|
|
1660
|
+
const bootstrapOutput = readGenerated(tmpDir, 'modules.bootstrap.generated.ts')!
|
|
1661
|
+
expect(bootstrapOutput).not.toContain('/i18n/')
|
|
1662
|
+
expect(bootstrapOutput).not.toContain('translations:')
|
|
1663
|
+
|
|
1664
|
+
const loadersOutput = readGenerated(tmpDir, 'modules.i18n.loaders.generated.ts')!
|
|
1665
|
+
expect(loadersOutput).not.toContain('.json')
|
|
1666
|
+
expect(loadersOutput).toContain('case "en":')
|
|
1667
|
+
expect(loadersOutput).toContain('import("./modules.i18n.en.generated")')
|
|
1668
|
+
expect(loadersOutput).toContain('case "pl":')
|
|
1669
|
+
expect(loadersOutput).not.toContain('hardcoded-allowlist')
|
|
1670
|
+
expect(loadersOutput.indexOf('case "en":')).toBeLessThan(loadersOutput.indexOf('case "pl":'))
|
|
1671
|
+
|
|
1672
|
+
const enShard = readGenerated(tmpDir, 'modules.i18n.en.generated.ts')!
|
|
1673
|
+
expect(enShard).toContain('@open-mercato/core/modules/localized/i18n/en.json')
|
|
1674
|
+
expect(enShard).toContain('@/modules/localized/i18n/en.json')
|
|
1675
|
+
expect(enShard).not.toContain('/i18n/pl.json')
|
|
1676
|
+
expect(enShard).toContain('id: "localized"')
|
|
1677
|
+
expect(enShard).toContain('translations:')
|
|
1678
|
+
|
|
1679
|
+
const plShard = readGenerated(tmpDir, 'modules.i18n.pl.generated.ts')!
|
|
1680
|
+
expect(plShard).toContain('@open-mercato/core/modules/localized/i18n/pl.json')
|
|
1681
|
+
expect(plShard).not.toContain('/i18n/en.json')
|
|
1682
|
+
expect(plShard).not.toContain('@/modules/localized/i18n/pl.json')
|
|
1683
|
+
expect(readGenerated(tmpDir, 'modules.i18n._hardcoded-allowlist.generated.ts')).toBeNull()
|
|
1684
|
+
|
|
1685
|
+
const secondResult = await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1686
|
+
expect(secondResult.filesWritten).toEqual([])
|
|
1687
|
+
|
|
1688
|
+
fs.rmSync(path.join(
|
|
1689
|
+
tmpDir,
|
|
1690
|
+
'packages',
|
|
1691
|
+
'core',
|
|
1692
|
+
'src',
|
|
1693
|
+
'modules',
|
|
1694
|
+
'localized',
|
|
1695
|
+
'i18n',
|
|
1696
|
+
'pl.json',
|
|
1697
|
+
))
|
|
1698
|
+
await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1699
|
+
expect(readGenerated(tmpDir, 'modules.i18n.pl.generated.ts')).toBeNull()
|
|
1700
|
+
expect(fs.existsSync(path.join(
|
|
1701
|
+
tmpDir,
|
|
1702
|
+
'output',
|
|
1703
|
+
'generated',
|
|
1704
|
+
'modules.i18n.pl.generated.checksum',
|
|
1705
|
+
))).toBe(false)
|
|
1706
|
+
expect(readGenerated(tmpDir, 'modules.i18n.loaders.generated.ts')).not.toContain('case "pl":')
|
|
1707
|
+
})
|
|
1708
|
+
|
|
1709
|
+
it('uses standalone source-mirror translations in locale-specific shards', async () => {
|
|
1710
|
+
touchFile(
|
|
1711
|
+
path.join(tmpDir, 'node_modules', 'pkg', 'src', 'modules', 'customers', 'i18n', 'en.json'),
|
|
1712
|
+
JSON.stringify({ customers: { people: { title: 'People' } } }),
|
|
1713
|
+
)
|
|
1714
|
+
touchFile(
|
|
1715
|
+
path.join(tmpDir, 'node_modules', 'pkg', 'dist', 'modules', 'customers', 'index.js'),
|
|
1716
|
+
'export const metadata = { id: "customers" }\n',
|
|
1717
|
+
)
|
|
1718
|
+
|
|
1719
|
+
const resolver = createStandaloneMockResolver(tmpDir, [
|
|
1720
|
+
{ id: 'customers', from: '@open-mercato/core' },
|
|
1721
|
+
])
|
|
1722
|
+
const result = await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1723
|
+
|
|
1724
|
+
expect(result.errors).toEqual([])
|
|
1725
|
+
const shard = fs.readFileSync(
|
|
1726
|
+
path.join(tmpDir, '.mercato', 'generated', 'modules.i18n.en.generated.ts'),
|
|
1727
|
+
'utf8',
|
|
1728
|
+
)
|
|
1729
|
+
expect(shard).toContain('@open-mercato/core/modules/customers/i18n/en.json')
|
|
1730
|
+
expect(shard).toContain('id: "customers"')
|
|
1483
1731
|
})
|
|
1484
1732
|
|
|
1485
1733
|
it('app output excludes CLI commands and keeps lazy workers/subscribers', async () => {
|
|
@@ -829,12 +829,12 @@ describe('generator output compatibility', () => {
|
|
|
829
829
|
|
|
830
830
|
const content = readGenerated('bootstrap-registrations.generated.ts')
|
|
831
831
|
expect(content).not.toBeNull()
|
|
832
|
-
expect(content).toContain(`import {
|
|
832
|
+
expect(content).toContain(`import { backendRouteFacades } from "./backend-route-shards.generated"`)
|
|
833
833
|
expect(content).toContain(`import { frontendRoutes } from "./frontend-routes.generated"`)
|
|
834
834
|
expect(content).toContain(
|
|
835
835
|
`import { registerBackendRouteManifests, registerFrontendRouteManifests } from '@open-mercato/shared/modules/registry'`,
|
|
836
836
|
)
|
|
837
|
-
expect(content).toMatch(/export function runBootstrapRegistrations\(\): void \{[\s\S]*registerBackendRouteManifests\(
|
|
837
|
+
expect(content).toMatch(/export function runBootstrapRegistrations\(\): void \{[\s\S]*registerBackendRouteManifests\(backendRouteFacades\)[\s\S]*\}/)
|
|
838
838
|
expect(content).toMatch(/export function runBootstrapRegistrations\(\): void \{[\s\S]*registerFrontendRouteManifests\(frontendRoutes\)[\s\S]*\}/)
|
|
839
839
|
})
|
|
840
840
|
|
|
@@ -846,6 +846,6 @@ describe('generator output compatibility', () => {
|
|
|
846
846
|
|
|
847
847
|
const content = readGenerated('bootstrap-registrations.generated.ts')
|
|
848
848
|
expect(content).not.toBeNull()
|
|
849
|
-
expect(content).toContain(`registerBackendRouteManifests(
|
|
849
|
+
expect(content).toContain(`registerBackendRouteManifests(backendRouteFacades)`)
|
|
850
850
|
})
|
|
851
851
|
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRouteManifestShardKey,
|
|
3
|
+
renderRouteManifestShardOutputs,
|
|
4
|
+
renderRouteMetadataOutput,
|
|
5
|
+
} from '../route-manifest-shards'
|
|
6
|
+
|
|
7
|
+
describe('route manifest shards', () => {
|
|
8
|
+
it('groups static request prefixes and preserves dynamic-prefix fallbacks', () => {
|
|
9
|
+
expect(getRouteManifestShardKey('api', '/customers/[id]')).toBe('customers')
|
|
10
|
+
expect(getRouteManifestShardKey('backend', '/backend/sales/orders')).toBe('sales')
|
|
11
|
+
expect(getRouteManifestShardKey('backend', '/backend/[tenant]/settings')).toBe('__dynamic')
|
|
12
|
+
expect(getRouteManifestShardKey('api', '/')).toBe('__root')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders a small loader graph with exact and dynamic shards', () => {
|
|
16
|
+
const outputs = renderRouteManifestShardOutputs('api', [
|
|
17
|
+
{ path: '/customers', declaration: `{ moduleId: 'customers' } as any` },
|
|
18
|
+
{ path: '/sales/orders', declaration: `{ moduleId: 'sales' } as any` },
|
|
19
|
+
{ path: '/[module]/health', declaration: `{ moduleId: 'dynamic' } as any` },
|
|
20
|
+
])
|
|
21
|
+
const loader = outputs.find((entry) => entry.fileName === 'api-route-shards.generated.ts')?.content ?? ''
|
|
22
|
+
|
|
23
|
+
expect(outputs.filter((entry) => entry.fileName.includes('route-shard.'))).toHaveLength(3)
|
|
24
|
+
expect(loader).toContain(`case "customers":`)
|
|
25
|
+
expect(loader).toContain(`case "sales":`)
|
|
26
|
+
expect(loader).toContain(`const dynamicShard = await loadShard("__dynamic")`)
|
|
27
|
+
expect(loader).toContain(`export const apiRouteFacades`)
|
|
28
|
+
expect(loader).toContain(`loadApiRouteManifestByOrdinal(ordinal)`)
|
|
29
|
+
expect(loader).not.toContain("moduleId: 'customers'")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('retains global route order when exact and dynamic entries interleave', () => {
|
|
33
|
+
const outputs = renderRouteManifestShardOutputs('api', [
|
|
34
|
+
{ path: '/customers/new', declaration: `{ path: '/customers/new' } as any` },
|
|
35
|
+
{ path: '/[module]/health', declaration: `{ path: '/[module]/health' } as any` },
|
|
36
|
+
{ path: '/customers/[id]', declaration: `{ path: '/customers/[id]' } as any` },
|
|
37
|
+
{ path: '/', declaration: `{ path: '/' } as any` },
|
|
38
|
+
])
|
|
39
|
+
const customerShard = outputs.find((entry) => entry.content.includes(`/customers/new`))?.content ?? ''
|
|
40
|
+
const dynamicShard = outputs.find((entry) => entry.content.includes(`/[module]/health`))?.content ?? ''
|
|
41
|
+
const loader = outputs.find((entry) => entry.fileName === 'api-route-shards.generated.ts')?.content ?? ''
|
|
42
|
+
|
|
43
|
+
expect(customerShard).toContain('export const routeOrders = [0, 2]')
|
|
44
|
+
expect(dynamicShard).toContain('export const routeOrders = [1]')
|
|
45
|
+
expect(loader).toContain(`case "__root":`)
|
|
46
|
+
expect(loader).toContain(`.sort((a, b) => a.order - b.order)`)
|
|
47
|
+
expect(loader).toContain(`const ROUTE_SHARD_KEYS = ["customers","__dynamic","customers","__root"]`)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('renders metadata without load functions while retaining metadata imports', () => {
|
|
51
|
+
const output = renderRouteMetadataOutput('backend', [{
|
|
52
|
+
path: '/backend/settings',
|
|
53
|
+
declaration: `{ moduleId: 'settings', ...resolvePageRouteMetadata('/backend/settings', META.metadata) }`,
|
|
54
|
+
imports: [`import * as META from '@app/settings/meta'`],
|
|
55
|
+
}])
|
|
56
|
+
|
|
57
|
+
expect(output.fileName).toBe('backend-route-metadata.generated.ts')
|
|
58
|
+
expect(output.content).toContain(`import * as META from '@app/settings/meta'`)
|
|
59
|
+
expect(output.content).toContain(`Omit<BackendRouteManifestEntry, 'load'>`)
|
|
60
|
+
expect(output.content).not.toContain('load: async')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -5,17 +5,13 @@ import { VariableDeclarationKind } from 'ts-morph'
|
|
|
5
5
|
import type { PackageResolver, ModuleEntry } from '../resolver'
|
|
6
6
|
import { MODULE_CODE_EXTENSIONS } from './scanner'
|
|
7
7
|
import {
|
|
8
|
-
calculateChecksum,
|
|
9
|
-
readChecksumRecord,
|
|
10
|
-
writeChecksumRecord,
|
|
11
|
-
ensureDir,
|
|
12
8
|
toVar,
|
|
13
9
|
toSnake,
|
|
14
10
|
rimrafDir,
|
|
15
|
-
logGenerationResult,
|
|
16
|
-
writeIfChanged,
|
|
17
11
|
type GeneratorResult,
|
|
18
12
|
createGeneratorResult,
|
|
13
|
+
writeGeneratedFile,
|
|
14
|
+
writeIfChanged,
|
|
19
15
|
} from '../utils'
|
|
20
16
|
import { addAutoGeneratedComment, createGeneratedSourceFile, elementAccess, getSourceText, identifier, returnStatement } from './ast'
|
|
21
17
|
|
|
@@ -438,26 +434,14 @@ export async function generateEntityIds(options: EntityIdsOptions): Promise<Gene
|
|
|
438
434
|
})
|
|
439
435
|
const consolidatedSrc = getSourceText(consolidatedSourceFile)
|
|
440
436
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (shouldWrite) {
|
|
451
|
-
ensureDir(outFile)
|
|
452
|
-
fs.writeFileSync(outFile, consolidatedSrc)
|
|
453
|
-
writeChecksumRecord(checksumFile, { content: newChecksum, structure: '' })
|
|
454
|
-
result.filesWritten.push(outFile)
|
|
455
|
-
if (!quiet) {
|
|
456
|
-
logGenerationResult(path.relative(process.cwd(), outFile), true)
|
|
457
|
-
}
|
|
458
|
-
} else {
|
|
459
|
-
result.filesUnchanged.push(outFile)
|
|
460
|
-
}
|
|
437
|
+
writeGeneratedFile({
|
|
438
|
+
outFile,
|
|
439
|
+
checksumFile,
|
|
440
|
+
content: consolidatedSrc,
|
|
441
|
+
structureChecksum: '',
|
|
442
|
+
result,
|
|
443
|
+
quiet,
|
|
444
|
+
})
|
|
461
445
|
|
|
462
446
|
// Write per-group outputs
|
|
463
447
|
const groups = Object.keys(grouped) as GroupKey[]
|
|
@@ -4,14 +4,10 @@ import { StructureKind, VariableDeclarationKind } from 'ts-morph'
|
|
|
4
4
|
import type { PackageResolver } from '../resolver'
|
|
5
5
|
import { MODULE_CODE_EXTENSIONS } from './scanner'
|
|
6
6
|
import {
|
|
7
|
-
calculateChecksum,
|
|
8
|
-
readChecksumRecord,
|
|
9
|
-
writeChecksumRecord,
|
|
10
|
-
ensureDir,
|
|
11
7
|
toVar,
|
|
12
|
-
logGenerationResult,
|
|
13
8
|
type GeneratorResult,
|
|
14
9
|
createGeneratorResult,
|
|
10
|
+
writeGeneratedFile,
|
|
15
11
|
} from '../utils'
|
|
16
12
|
import {
|
|
17
13
|
addAutoGeneratedComment,
|
|
@@ -96,26 +92,14 @@ export async function generateModuleDi(options: ModuleDiOptions): Promise<Genera
|
|
|
96
92
|
})
|
|
97
93
|
const output = getSourceText(sourceFile)
|
|
98
94
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (shouldWrite) {
|
|
109
|
-
ensureDir(outFile)
|
|
110
|
-
fs.writeFileSync(outFile, output)
|
|
111
|
-
writeChecksumRecord(checksumFile, { content: newChecksum, structure: '' })
|
|
112
|
-
result.filesWritten.push(outFile)
|
|
113
|
-
if (!quiet) {
|
|
114
|
-
logGenerationResult(path.relative(process.cwd(), outFile), true)
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
result.filesUnchanged.push(outFile)
|
|
118
|
-
}
|
|
95
|
+
writeGeneratedFile({
|
|
96
|
+
outFile,
|
|
97
|
+
checksumFile,
|
|
98
|
+
content: output,
|
|
99
|
+
structureChecksum: '',
|
|
100
|
+
result,
|
|
101
|
+
quiet,
|
|
102
|
+
})
|
|
119
103
|
|
|
120
104
|
return result
|
|
121
105
|
}
|