@open-mercato/cli 0.6.8-develop.7032.1.70d74d4bfe → 0.6.8-develop.7038.1.ea954afd80
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/guides/module-facts.json +126 -126
- package/dist/agentic/guides/module-facts.v2.json +126 -126
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/lib/generators/ast/index.js +4 -1
- package/dist/lib/generators/ast/index.js.map +2 -2
- package/dist/lib/generators/ast/source-file.js +8 -1
- package/dist/lib/generators/ast/source-file.js.map +2 -2
- package/dist/lib/generators/ast/writers.js +9 -1
- package/dist/lib/generators/ast/writers.js.map +2 -2
- package/dist/lib/generators/module-registry.js +343 -183
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/route-manifest-shards.js +4 -1
- package/dist/lib/generators/route-manifest-shards.js.map +2 -2
- package/dist/lib/spawn.js +3 -2
- package/dist/lib/spawn.js.map +2 -2
- package/dist/lib/testing/integration.js +162 -24
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__tests__/spawn.test.ts +23 -0
- package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +14 -0
- package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +23 -0
- package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +64 -0
- package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +176 -0
- package/src/lib/generators/__tests__/command-loaders.test.ts +239 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +96 -72
- package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
- package/src/lib/generators/__tests__/registry-variant-parity.test.ts +258 -0
- package/src/lib/generators/__tests__/route-manifest-shards.test.ts +15 -0
- package/src/lib/generators/__tests__/structural-contracts.test.ts +3 -3
- package/src/lib/generators/ast/index.ts +2 -1
- package/src/lib/generators/ast/source-file.ts +13 -0
- package/src/lib/generators/ast/writers.ts +26 -1
- package/src/lib/generators/module-registry.ts +417 -236
- package/src/lib/generators/route-manifest-shards.ts +8 -1
- package/src/lib/spawn.ts +5 -4
- package/src/lib/testing/__tests__/integration.test.ts +435 -0
- package/src/lib/testing/integration.ts +254 -32
|
@@ -186,65 +186,65 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
186
186
|
expect(output).toContain('export default modules')
|
|
187
187
|
})
|
|
188
188
|
|
|
189
|
-
it('
|
|
189
|
+
it('pins the exact emitted empty output format for the refactored registry files', async () => {
|
|
190
190
|
const resolver = createMockResolver(tmpDir, [])
|
|
191
191
|
const result = await generateModuleRegistry({ resolver, quiet: true })
|
|
192
192
|
|
|
193
193
|
expect(result.errors).toEqual([])
|
|
194
194
|
expect(readGenerated(tmpDir, 'modules.generated.ts')).toBe(dedent(`
|
|
195
195
|
// AUTO-GENERATED by mercato generate registry
|
|
196
|
-
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from
|
|
196
|
+
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from "@open-mercato/shared/modules/registry";
|
|
197
197
|
|
|
198
|
-
export const modules: Module[] = [
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
export
|
|
198
|
+
export const modules: Module[] = [];
|
|
199
|
+
|
|
200
|
+
export default modules;
|
|
201
|
+
|
|
202
|
+
export const modulesInfo = modules.map((m) => ({
|
|
203
|
+
id: m.id,
|
|
204
|
+
...m.info || {}
|
|
205
|
+
}));
|
|
203
206
|
`))
|
|
204
207
|
expect(readGenerated(tmpDir, 'modules.runtime.generated.ts')).toBe(dedent(`
|
|
205
208
|
// AUTO-GENERATED by mercato generate registry
|
|
206
|
-
import { createElement } from
|
|
207
|
-
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from
|
|
209
|
+
import { createElement } from "react";
|
|
210
|
+
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from "@open-mercato/shared/modules/registry";
|
|
208
211
|
|
|
209
|
-
export const modules: Module[] = [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
export
|
|
212
|
+
export const modules: Module[] = [];
|
|
213
|
+
|
|
214
|
+
export default modules;
|
|
215
|
+
|
|
216
|
+
export const modulesInfo = modules.map((m) => ({
|
|
217
|
+
id: m.id,
|
|
218
|
+
...m.info || {}
|
|
219
|
+
}));
|
|
214
220
|
`))
|
|
215
221
|
expect(readGenerated(tmpDir, 'frontend-routes.generated.ts')).toBe(dedent(`
|
|
216
222
|
// AUTO-GENERATED by mercato generate registry
|
|
217
|
-
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from
|
|
223
|
+
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
218
224
|
|
|
219
|
-
export const frontendRoutes: FrontendRouteManifestEntry[] = [
|
|
220
|
-
|
|
221
|
-
]
|
|
225
|
+
export const frontendRoutes: FrontendRouteManifestEntry[] = [];
|
|
222
226
|
|
|
223
|
-
export default frontendRoutes
|
|
227
|
+
export default frontendRoutes;
|
|
224
228
|
`))
|
|
225
229
|
expect(readGenerated(tmpDir, 'backend-routes.generated.ts')).toBe(dedent(`
|
|
226
230
|
// AUTO-GENERATED by mercato generate registry
|
|
227
|
-
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from
|
|
231
|
+
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
228
232
|
|
|
229
|
-
export const backendRoutes: BackendRouteManifestEntry[] = [
|
|
230
|
-
|
|
231
|
-
]
|
|
233
|
+
export const backendRoutes: BackendRouteManifestEntry[] = [];
|
|
232
234
|
|
|
233
|
-
export default backendRoutes
|
|
235
|
+
export default backendRoutes;
|
|
234
236
|
`))
|
|
235
237
|
expect(readGenerated(tmpDir, 'api-routes.generated.ts')).toBe(dedent(`
|
|
236
238
|
// AUTO-GENERATED by mercato generate registry
|
|
237
|
-
import type { ApiRouteManifestEntry } from
|
|
239
|
+
import type { ApiRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
238
240
|
|
|
239
|
-
export const apiRoutes: ApiRouteManifestEntry[] = [
|
|
240
|
-
|
|
241
|
-
]
|
|
241
|
+
export const apiRoutes: ApiRouteManifestEntry[] = [];
|
|
242
242
|
|
|
243
|
-
export default apiRoutes
|
|
243
|
+
export default apiRoutes;
|
|
244
244
|
`))
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
-
it('
|
|
247
|
+
it('pins the exact emitted populated output format for the refactored registry files', async () => {
|
|
248
248
|
touchFile(
|
|
249
249
|
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'orders', 'index.ts'),
|
|
250
250
|
"export const metadata = { id: 'orders', label: 'Orders' }\n",
|
|
@@ -267,75 +267,99 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
267
267
|
|
|
268
268
|
expect(result.errors).toEqual([])
|
|
269
269
|
const modulesGenerated = readGenerated(tmpDir, 'modules.generated.ts')
|
|
270
|
-
expect(modulesGenerated).toContain(`import { createElement } from
|
|
270
|
+
expect(modulesGenerated).toContain(`import { createElement } from "react"`)
|
|
271
271
|
expect(modulesGenerated).toContain(`import * as I0_orders from "@open-mercato/core/modules/orders/index"`)
|
|
272
272
|
expect(modulesGenerated).not.toContain(`import C1_orders_index`)
|
|
273
273
|
expect(modulesGenerated).not.toContain(`import B5_orders_index`)
|
|
274
|
-
expect(modulesGenerated).
|
|
275
|
-
expect(modulesGenerated).
|
|
274
|
+
expect(modulesGenerated).toMatch(/frontendRoutes:\s*\[\s*\{ pattern: "\/"/)
|
|
275
|
+
expect(modulesGenerated).toMatch(/backendRoutes:\s*\[\s*\{ pattern: "\/backend\/orders"/)
|
|
276
276
|
expect(modulesGenerated).toContain(`Component: async (props: any) => { const mod = await import("@open-mercato/core/modules/orders/frontend/page")`)
|
|
277
277
|
expect(modulesGenerated).toContain(`Component: async (props: any) => { const mod = await import("@open-mercato/core/modules/orders/backend/page")`)
|
|
278
|
-
expect(modulesGenerated).
|
|
278
|
+
expect(modulesGenerated).toMatch(/apis:\s*\[\s*\{ path: \(\(R9_orders_orders as any\)\.metadata\?\.path/)
|
|
279
|
+
expect(modulesGenerated).toContain(`{ path: ((R9_orders_orders as any).metadata?.path ?? "/orders/orders"), metadata: (R9_orders_orders as any).metadata, handlers: R9_orders_orders as any }`)
|
|
279
280
|
expect(readGenerated(tmpDir, 'modules.runtime.generated.ts')).toBe(dedent(`
|
|
280
281
|
// AUTO-GENERATED by mercato generate registry
|
|
281
|
-
import { createElement } from
|
|
282
|
-
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from
|
|
283
|
-
import * as I0_orders from "@open-mercato/core/modules/orders/index"
|
|
284
|
-
import * as MRuntime3_orders_index from "@open-mercato/core/modules/orders/frontend/page"
|
|
285
|
-
import * as BMRuntime7_orders_index from "@open-mercato/core/modules/orders/backend/page"
|
|
282
|
+
import { createElement } from "react";
|
|
283
|
+
import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from "@open-mercato/shared/modules/registry";
|
|
284
|
+
import * as I0_orders from "@open-mercato/core/modules/orders/index";
|
|
285
|
+
import * as MRuntime3_orders_index from "@open-mercato/core/modules/orders/frontend/page";
|
|
286
|
+
import * as BMRuntime7_orders_index from "@open-mercato/core/modules/orders/backend/page";
|
|
286
287
|
|
|
287
288
|
export const modules: Module[] = [
|
|
288
|
-
|
|
289
|
+
{
|
|
289
290
|
id: "orders",
|
|
290
291
|
info: I0_orders.metadata,
|
|
291
|
-
frontendRoutes: [
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
292
|
+
frontendRoutes: [
|
|
293
|
+
{ pattern: "/", requireAuth: ((((MRuntime3_orders_index as any).metadata) as any))?.requireAuth, requireRoles: ((((MRuntime3_orders_index as any).metadata) as any))?.requireRoles, requireFeatures: ((((MRuntime3_orders_index as any).metadata) as any))?.requireFeatures, requireCustomerAuth: ((((MRuntime3_orders_index as any).metadata) as any))?.requireCustomerAuth, requireCustomerFeatures: ((((MRuntime3_orders_index as any).metadata) as any))?.requireCustomerFeatures, nav: ((((MRuntime3_orders_index as any).metadata) as any))?.nav, title: ((((MRuntime3_orders_index as any).metadata) as any))?.pageTitle ?? ((((MRuntime3_orders_index as any).metadata) as any))?.title, titleKey: ((((MRuntime3_orders_index as any).metadata) as any))?.pageTitleKey ?? ((((MRuntime3_orders_index as any).metadata) as any))?.titleKey, group: ((((MRuntime3_orders_index as any).metadata) as any))?.pageGroup ?? ((((MRuntime3_orders_index as any).metadata) as any))?.group, groupKey: ((((MRuntime3_orders_index as any).metadata) as any))?.pageGroupKey ?? ((((MRuntime3_orders_index as any).metadata) as any))?.groupKey, icon: ((((MRuntime3_orders_index as any).metadata) as any))?.icon, order: ((((MRuntime3_orders_index as any).metadata) as any))?.pageOrder ?? ((((MRuntime3_orders_index as any).metadata) as any))?.order, priority: ((((MRuntime3_orders_index as any).metadata) as any))?.pagePriority ?? ((((MRuntime3_orders_index as any).metadata) as any))?.priority, navHidden: ((((MRuntime3_orders_index as any).metadata) as any))?.navHidden, visible: ((((MRuntime3_orders_index as any).metadata) as any))?.visible, enabled: ((((MRuntime3_orders_index as any).metadata) as any))?.enabled, breadcrumb: ((((MRuntime3_orders_index as any).metadata) as any))?.breadcrumb, pageContext: ((((MRuntime3_orders_index as any).metadata) as any))?.pageContext, placement: ((((MRuntime3_orders_index as any).metadata) as any))?.placement, Component: async (props: any) => { const mod = await import("@open-mercato/core/modules/orders/frontend/page"); const Component = (mod.default ?? mod) as any; return createElement(Component, props) } }
|
|
294
|
+
],
|
|
295
|
+
backendRoutes: [
|
|
296
|
+
{ pattern: "/backend/orders", requireAuth: ((((BMRuntime7_orders_index as any).metadata) as any))?.requireAuth, requireRoles: ((((BMRuntime7_orders_index as any).metadata) as any))?.requireRoles, requireFeatures: ((((BMRuntime7_orders_index as any).metadata) as any))?.requireFeatures, requireCustomerAuth: ((((BMRuntime7_orders_index as any).metadata) as any))?.requireCustomerAuth, requireCustomerFeatures: ((((BMRuntime7_orders_index as any).metadata) as any))?.requireCustomerFeatures, nav: ((((BMRuntime7_orders_index as any).metadata) as any))?.nav, title: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageTitle ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.title, titleKey: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageTitleKey ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.titleKey, group: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageGroup ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.group, groupKey: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageGroupKey ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.groupKey, icon: ((((BMRuntime7_orders_index as any).metadata) as any))?.icon, order: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageOrder ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.order, priority: ((((BMRuntime7_orders_index as any).metadata) as any))?.pagePriority ?? ((((BMRuntime7_orders_index as any).metadata) as any))?.priority, navHidden: ((((BMRuntime7_orders_index as any).metadata) as any))?.navHidden, visible: ((((BMRuntime7_orders_index as any).metadata) as any))?.visible, enabled: ((((BMRuntime7_orders_index as any).metadata) as any))?.enabled, breadcrumb: ((((BMRuntime7_orders_index as any).metadata) as any))?.breadcrumb, pageContext: ((((BMRuntime7_orders_index as any).metadata) as any))?.pageContext, placement: ((((BMRuntime7_orders_index as any).metadata) as any))?.placement, Component: async (props: any) => { const mod = await import("@open-mercato/core/modules/orders/backend/page"); const Component = (mod.default ?? mod) as any; return createElement(Component, props) } }
|
|
297
|
+
],
|
|
298
|
+
apis: [
|
|
299
|
+
{ path: "/orders/orders", metadata: undefined, handlers: { GET: async (req: Request, ctx?: any) => { const mod = await import("@open-mercato/core/modules/orders/api/orders/route"); return (mod as any).GET(req, ctx) } } }
|
|
300
|
+
],
|
|
301
|
+
customFieldSets: []
|
|
305
302
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
export default modules
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
export default modules;
|
|
306
|
+
|
|
307
|
+
export const modulesInfo = modules.map((m) => ({
|
|
308
|
+
id: m.id,
|
|
309
|
+
...m.info || {}
|
|
310
|
+
}));
|
|
309
311
|
`))
|
|
310
312
|
expect(readGenerated(tmpDir, 'frontend-routes.generated.ts')).toBe(dedent(`
|
|
311
313
|
// AUTO-GENERATED by mercato generate registry
|
|
312
|
-
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from
|
|
314
|
+
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
313
315
|
|
|
314
316
|
export const frontendRoutes: FrontendRouteManifestEntry[] = [
|
|
315
|
-
|
|
316
|
-
|
|
317
|
+
{
|
|
318
|
+
moduleId: "orders",
|
|
319
|
+
...resolvePageRouteMetadata("/", (undefined as any)),
|
|
320
|
+
load: async () => {
|
|
321
|
+
const mod = await import("@open-mercato/core/modules/orders/frontend/page");
|
|
322
|
+
return ((mod.default ?? mod) as any);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
];
|
|
317
326
|
|
|
318
|
-
export default frontendRoutes
|
|
327
|
+
export default frontendRoutes;
|
|
319
328
|
`))
|
|
320
329
|
expect(readGenerated(tmpDir, 'backend-routes.generated.ts')).toBe(dedent(`
|
|
321
330
|
// AUTO-GENERATED by mercato generate registry
|
|
322
|
-
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from
|
|
331
|
+
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
323
332
|
|
|
324
333
|
export const backendRoutes: BackendRouteManifestEntry[] = [
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
{
|
|
335
|
+
moduleId: "orders",
|
|
336
|
+
...resolvePageRouteMetadata("/backend/orders", (undefined as any)),
|
|
337
|
+
load: async () => {
|
|
338
|
+
const mod = await import("@open-mercato/core/modules/orders/backend/page");
|
|
339
|
+
return ((mod.default ?? mod) as any);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
];
|
|
327
343
|
|
|
328
|
-
export default backendRoutes
|
|
344
|
+
export default backendRoutes;
|
|
329
345
|
`))
|
|
330
346
|
expect(readGenerated(tmpDir, 'api-routes.generated.ts')).toBe(dedent(`
|
|
331
347
|
// AUTO-GENERATED by mercato generate registry
|
|
332
|
-
import type { ApiRouteManifestEntry } from
|
|
348
|
+
import type { ApiRouteManifestEntry } from "@open-mercato/shared/modules/registry";
|
|
333
349
|
|
|
334
350
|
export const apiRoutes: ApiRouteManifestEntry[] = [
|
|
335
|
-
|
|
336
|
-
|
|
351
|
+
{
|
|
352
|
+
moduleId: "orders",
|
|
353
|
+
kind: "route-file",
|
|
354
|
+
path: "/orders/orders",
|
|
355
|
+
methods: [
|
|
356
|
+
"GET"
|
|
357
|
+
],
|
|
358
|
+
load: async () => import("@open-mercato/core/modules/orders/api/orders/route")
|
|
359
|
+
}
|
|
360
|
+
];
|
|
337
361
|
|
|
338
|
-
export default apiRoutes
|
|
362
|
+
export default apiRoutes;
|
|
339
363
|
`))
|
|
340
364
|
})
|
|
341
365
|
|
|
@@ -575,7 +599,7 @@ export { POST, PUT, DELETE }
|
|
|
575
599
|
expect(result.errors).toEqual([])
|
|
576
600
|
const apiRoutes = readGenerated(tmpDir, 'api-routes.generated.ts')!
|
|
577
601
|
expect(apiRoutes).toContain('path: "/customers/people"')
|
|
578
|
-
expect(apiRoutes).
|
|
602
|
+
expect(apiRoutes).toMatch(/methods:\s*\[[\s\S]*?"GET",[\s\S]*?"POST",[\s\S]*?"PUT",[\s\S]*?"DELETE"[\s\S]*?\]/)
|
|
579
603
|
})
|
|
580
604
|
|
|
581
605
|
it('partitions API and backend loader graphs while preserving legacy manifests', async () => {
|
|
@@ -660,7 +684,7 @@ export const { metadata, GET, POST, PUT, DELETE } = crud
|
|
|
660
684
|
expect(result.errors).toEqual([])
|
|
661
685
|
const apiRoutes = readGenerated(tmpDir, 'api-routes.generated.ts')!
|
|
662
686
|
expect(apiRoutes).toContain('path: "/example/todos"')
|
|
663
|
-
expect(apiRoutes).
|
|
687
|
+
expect(apiRoutes).toMatch(/methods:\s*\[[\s\S]*?"GET",[\s\S]*?"POST",[\s\S]*?"PUT",[\s\S]*?"DELETE"[\s\S]*?\]/)
|
|
664
688
|
})
|
|
665
689
|
|
|
666
690
|
it('uses metadata.path in api route manifests when source import fails during generation', async () => {
|
|
@@ -1047,7 +1071,7 @@ export function GET() {
|
|
|
1047
1071
|
)
|
|
1048
1072
|
|
|
1049
1073
|
expect(output).toContain('@open-mercato/core/modules/customers/i18n/en.json')
|
|
1050
|
-
expect(output).
|
|
1074
|
+
expect(output).toMatch(/['"]en['"]:/)
|
|
1051
1075
|
})
|
|
1052
1076
|
})
|
|
1053
1077
|
|
|
@@ -832,7 +832,7 @@ describe('generator output compatibility', () => {
|
|
|
832
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
|
-
`import { registerBackendRouteManifests, registerFrontendRouteManifests } from
|
|
835
|
+
`import { registerBackendRouteManifests, registerFrontendRouteManifests } from "@open-mercato/shared/modules/registry"`,
|
|
836
836
|
)
|
|
837
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]*\}/)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-implementation parity between the two module registry variants.
|
|
3
|
+
*
|
|
4
|
+
* `generateModuleRegistry` (modules.generated.ts / modules.runtime.generated.ts) and
|
|
5
|
+
* `generateModuleRegistryApp` (modules.app.generated.ts) describe the same modules from
|
|
6
|
+
* the same discovery input. Nothing asserted that they agree, which is what let the two
|
|
7
|
+
* emitters drift far enough apart to need this migration in the first place.
|
|
8
|
+
*
|
|
9
|
+
* These tests generate every variant from one fixture, parse the emitted module entries,
|
|
10
|
+
* and assert that for each module id the properties both variants are expected to carry
|
|
11
|
+
* are present or absent identically. Properties only one variant is supposed to carry are
|
|
12
|
+
* asserted explicitly as known, intentional divergences, so a future change that erases or
|
|
13
|
+
* widens one of them fails here rather than silently.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import os from 'node:os'
|
|
18
|
+
import ts from 'typescript-js'
|
|
19
|
+
import type { PackageResolver, ModuleEntry } from '../../resolver'
|
|
20
|
+
import { generateModuleRegistry, generateModuleRegistryApp } from '../module-registry'
|
|
21
|
+
|
|
22
|
+
/** Properties both registry variants must agree on, per module. */
|
|
23
|
+
const SHARED_PROPERTIES = [
|
|
24
|
+
'id',
|
|
25
|
+
'info',
|
|
26
|
+
'frontendRoutes',
|
|
27
|
+
'backendRoutes',
|
|
28
|
+
'translations',
|
|
29
|
+
'subscribers',
|
|
30
|
+
'workers',
|
|
31
|
+
'entityExtensions',
|
|
32
|
+
'customFieldSets',
|
|
33
|
+
'features',
|
|
34
|
+
'customEntities',
|
|
35
|
+
'setup',
|
|
36
|
+
'defaultEncryptionMaps',
|
|
37
|
+
'integrations',
|
|
38
|
+
'bundles',
|
|
39
|
+
] as const
|
|
40
|
+
|
|
41
|
+
let tmpDir: string
|
|
42
|
+
let outputDir: string
|
|
43
|
+
let fileMtimeNonce = 0
|
|
44
|
+
|
|
45
|
+
function touchFile(filePath: string, content = ''): void {
|
|
46
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
47
|
+
fs.writeFileSync(filePath, content)
|
|
48
|
+
const mtime = new Date(Date.now() + fileMtimeNonce)
|
|
49
|
+
fileMtimeNonce += 1
|
|
50
|
+
fs.utimesSync(filePath, mtime, mtime)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pkgModulePath(modId: string, ...segments: string[]): string {
|
|
54
|
+
return path.join(tmpDir, 'packages', 'core', 'src', 'modules', modId, ...segments)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createMockResolver(enabled: ModuleEntry[]): PackageResolver {
|
|
58
|
+
return {
|
|
59
|
+
isMonorepo: () => true,
|
|
60
|
+
getRootDir: () => tmpDir,
|
|
61
|
+
getAppDir: () => path.join(tmpDir, 'app'),
|
|
62
|
+
getOutputDir: () => outputDir,
|
|
63
|
+
getModulesConfigPath: () => path.join(tmpDir, 'app', 'src', 'modules.ts'),
|
|
64
|
+
discoverPackages: () => [],
|
|
65
|
+
loadEnabledModules: () => enabled,
|
|
66
|
+
getModulePaths: (entry: ModuleEntry) => ({
|
|
67
|
+
appBase: path.join(tmpDir, 'app', 'src', 'modules', entry.id),
|
|
68
|
+
pkgBase: path.join(tmpDir, 'packages', 'core', 'src', 'modules', entry.id),
|
|
69
|
+
}),
|
|
70
|
+
getModuleImportBase: (entry: ModuleEntry) => ({
|
|
71
|
+
appBase: `@/modules/${entry.id}`,
|
|
72
|
+
pkgBase: `@open-mercato/core/modules/${entry.id}`,
|
|
73
|
+
}),
|
|
74
|
+
getPackageOutputDir: () => outputDir,
|
|
75
|
+
getPackageRoot: () => path.join(tmpDir, 'packages', 'core'),
|
|
76
|
+
} as unknown as PackageResolver
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A module carrying every optional property, plus a deliberately bare one, so the
|
|
81
|
+
* parity assertions cover both the "present in both" and "absent in both" directions.
|
|
82
|
+
*/
|
|
83
|
+
function scaffoldFixture(): ModuleEntry[] {
|
|
84
|
+
touchFile(pkgModulePath('orders', 'index.ts'), "export const metadata = { id: 'orders', label: 'Orders' }\n")
|
|
85
|
+
touchFile(pkgModulePath('orders', 'frontend', 'page.tsx'), 'export default function OrdersPage() { return null }\n')
|
|
86
|
+
touchFile(pkgModulePath('orders', 'backend', 'page.tsx'), 'export default function OrdersBackendPage() { return null }\n')
|
|
87
|
+
touchFile(
|
|
88
|
+
pkgModulePath('orders', 'api', 'orders', 'route.ts'),
|
|
89
|
+
"export const metadata = { path: '/orders' }\nexport function GET() { return new Response('ok') }\n",
|
|
90
|
+
)
|
|
91
|
+
touchFile(
|
|
92
|
+
pkgModulePath('orders', 'subscribers', 'on-created.ts'),
|
|
93
|
+
"export const metadata = { event: 'orders.order.created', persistent: true }\nexport default async function handler() {}\n",
|
|
94
|
+
)
|
|
95
|
+
touchFile(
|
|
96
|
+
pkgModulePath('orders', 'workers', 'sync-job.ts'),
|
|
97
|
+
"export const metadata = { queue: 'orders.sync', concurrency: 2 }\nexport default async function handler() {}\n",
|
|
98
|
+
)
|
|
99
|
+
touchFile(
|
|
100
|
+
pkgModulePath('orders', 'data', 'extensions.ts'),
|
|
101
|
+
"export const extensions = [{ sourceEntity: 'orders:sales_order', targetEntity: 'customers:person', foreignKey: 'customer_id' }]\n",
|
|
102
|
+
)
|
|
103
|
+
touchFile(pkgModulePath('orders', 'acl.ts'), "export const features = ['orders.view', 'orders.create']\n")
|
|
104
|
+
touchFile(pkgModulePath('orders', 'setup.ts'), "export const setup = { defaultRoleFeatures: ['orders.view'] }\n")
|
|
105
|
+
touchFile(
|
|
106
|
+
pkgModulePath('orders', 'encryption.ts'),
|
|
107
|
+
"export const defaultEncryptionMaps = [{ entityId: 'orders:sales_order', fields: [{ field: 'customer_email' }] }]\nexport default defaultEncryptionMaps\n",
|
|
108
|
+
)
|
|
109
|
+
touchFile(
|
|
110
|
+
pkgModulePath('orders', 'integration.ts'),
|
|
111
|
+
"export const integration = { id: 'orders.shipping', label: 'Shipping' }\nexport const bundle = { id: 'orders.bundle', label: 'Bundle' }\n",
|
|
112
|
+
)
|
|
113
|
+
touchFile(pkgModulePath('orders', 'ce.ts'), "export const entities = [{ id: 'orders:custom', fields: [{ id: 'note', kind: 'text' }] }]\n")
|
|
114
|
+
touchFile(pkgModulePath('orders', 'i18n', 'en.json'), JSON.stringify({ orders: { title: 'Orders' } }))
|
|
115
|
+
touchFile(pkgModulePath('orders', 'cli.ts'), 'export default { command: "orders", run: async () => {} }\n')
|
|
116
|
+
|
|
117
|
+
touchFile(pkgModulePath('bare', 'index.ts'), "export const metadata = { id: 'bare', label: 'Bare' }\n")
|
|
118
|
+
|
|
119
|
+
return [
|
|
120
|
+
{ id: 'orders', from: '@open-mercato/core' },
|
|
121
|
+
{ id: 'bare', from: '@open-mercato/core' },
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readGenerated(fileName: string): string {
|
|
126
|
+
const filePath = path.join(outputDir, fileName)
|
|
127
|
+
if (!fs.existsSync(filePath)) throw new Error(`Expected generated file not found: ${fileName}`)
|
|
128
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Maps module id → the set of property names that module's entry declares. */
|
|
132
|
+
function parseModulePropertiesById(content: string, fileName: string): Map<string, Set<string>> {
|
|
133
|
+
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
|
134
|
+
|
|
135
|
+
let modulesArray: ts.ArrayLiteralExpression | null = null
|
|
136
|
+
const visit = (node: ts.Node): void => {
|
|
137
|
+
if (
|
|
138
|
+
!modulesArray
|
|
139
|
+
&& ts.isVariableDeclaration(node)
|
|
140
|
+
&& ts.isIdentifier(node.name)
|
|
141
|
+
&& node.name.text === 'modules'
|
|
142
|
+
&& node.initializer
|
|
143
|
+
&& ts.isArrayLiteralExpression(node.initializer)
|
|
144
|
+
) {
|
|
145
|
+
modulesArray = node.initializer
|
|
146
|
+
}
|
|
147
|
+
ts.forEachChild(node, visit)
|
|
148
|
+
}
|
|
149
|
+
visit(sourceFile)
|
|
150
|
+
if (!modulesArray) throw new Error(`No exported "modules" array literal found in ${fileName}`)
|
|
151
|
+
|
|
152
|
+
const byId = new Map<string, Set<string>>()
|
|
153
|
+
for (const element of (modulesArray as ts.ArrayLiteralExpression).elements) {
|
|
154
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
155
|
+
throw new Error(`Non-object module entry in ${fileName}`)
|
|
156
|
+
}
|
|
157
|
+
const names = new Set<string>()
|
|
158
|
+
let moduleId: string | null = null
|
|
159
|
+
for (const property of element.properties) {
|
|
160
|
+
if (!ts.isPropertyAssignment(property)) continue
|
|
161
|
+
const name = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)
|
|
162
|
+
? property.name.text
|
|
163
|
+
: null
|
|
164
|
+
if (!name) continue
|
|
165
|
+
names.add(name)
|
|
166
|
+
if (name === 'id' && ts.isStringLiteralLike(property.initializer)) {
|
|
167
|
+
moduleId = property.initializer.text
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!moduleId) throw new Error(`Module entry without a literal id in ${fileName}`)
|
|
171
|
+
byId.set(moduleId, names)
|
|
172
|
+
}
|
|
173
|
+
return byId
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function restrictTo(names: Set<string>, allowed: readonly string[]): string[] {
|
|
177
|
+
return allowed.filter((name) => names.has(name)).sort()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
describe('module registry variant parity', () => {
|
|
181
|
+
let mainVariant: Map<string, Set<string>>
|
|
182
|
+
let runtimeVariant: Map<string, Set<string>>
|
|
183
|
+
let appVariant: Map<string, Set<string>>
|
|
184
|
+
|
|
185
|
+
beforeAll(async () => {
|
|
186
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'registry-parity-'))
|
|
187
|
+
outputDir = path.join(tmpDir, 'app', '.mercato', 'generated')
|
|
188
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
189
|
+
|
|
190
|
+
const resolver = createMockResolver(scaffoldFixture())
|
|
191
|
+
const mainResult = await generateModuleRegistry({ resolver, quiet: true })
|
|
192
|
+
const appResult = await generateModuleRegistryApp({ resolver, quiet: true })
|
|
193
|
+
expect(mainResult.errors).toEqual([])
|
|
194
|
+
expect(appResult.errors).toEqual([])
|
|
195
|
+
|
|
196
|
+
mainVariant = parseModulePropertiesById(readGenerated('modules.generated.ts'), 'modules.generated.ts')
|
|
197
|
+
runtimeVariant = parseModulePropertiesById(
|
|
198
|
+
readGenerated('modules.runtime.generated.ts'),
|
|
199
|
+
'modules.runtime.generated.ts',
|
|
200
|
+
)
|
|
201
|
+
appVariant = parseModulePropertiesById(readGenerated('modules.app.generated.ts'), 'modules.app.generated.ts')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
afterAll(() => {
|
|
205
|
+
if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('discovers the same module ids in every variant', () => {
|
|
209
|
+
const ids = [...mainVariant.keys()].sort()
|
|
210
|
+
expect(ids).toEqual(['bare', 'orders'])
|
|
211
|
+
expect([...runtimeVariant.keys()].sort()).toEqual(ids)
|
|
212
|
+
expect([...appVariant.keys()].sort()).toEqual(ids)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it.each(['orders', 'bare'])('main and app variants agree on the shared properties of "%s"', (moduleId) => {
|
|
216
|
+
const main = mainVariant.get(moduleId)
|
|
217
|
+
const app = appVariant.get(moduleId)
|
|
218
|
+
expect(main).toBeDefined()
|
|
219
|
+
expect(app).toBeDefined()
|
|
220
|
+
expect(restrictTo(main!, SHARED_PROPERTIES)).toEqual(restrictTo(app!, SHARED_PROPERTIES))
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it.each(['orders', 'bare'])('main and runtime variants agree on the shared properties of "%s"', (moduleId) => {
|
|
224
|
+
const main = mainVariant.get(moduleId)
|
|
225
|
+
const runtime = runtimeVariant.get(moduleId)
|
|
226
|
+
expect(main).toBeDefined()
|
|
227
|
+
expect(runtime).toBeDefined()
|
|
228
|
+
expect(restrictTo(main!, SHARED_PROPERTIES)).toEqual(restrictTo(runtime!, SHARED_PROPERTIES))
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('carries every optional property for the fully-featured module', () => {
|
|
232
|
+
expect(restrictTo(mainVariant.get('orders')!, SHARED_PROPERTIES)).toEqual([...SHARED_PROPERTIES].sort())
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('omits every optional property for the bare module', () => {
|
|
236
|
+
expect(restrictTo(mainVariant.get('bare')!, SHARED_PROPERTIES)).toEqual(['customFieldSets', 'id', 'info'])
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe('known, intentional divergences', () => {
|
|
240
|
+
it('only the main variant carries eager api handlers and the cli entry point', () => {
|
|
241
|
+
expect(mainVariant.get('orders')!.has('apis')).toBe(true)
|
|
242
|
+
expect(mainVariant.get('orders')!.has('cli')).toBe(true)
|
|
243
|
+
expect(appVariant.get('orders')!.has('apis')).toBe(false)
|
|
244
|
+
expect(appVariant.get('orders')!.has('cli')).toBe(false)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('the runtime variant carries api handlers but not the cli entry point', () => {
|
|
248
|
+
expect(runtimeVariant.get('orders')!.has('apis')).toBe(true)
|
|
249
|
+
expect(runtimeVariant.get('orders')!.has('cli')).toBe(false)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('only the main and app variants carry dashboardWidgets, and only the app variant emits it unconditionally', () => {
|
|
253
|
+
expect(mainVariant.get('bare')!.has('dashboardWidgets')).toBe(false)
|
|
254
|
+
expect(appVariant.get('bare')!.has('dashboardWidgets')).toBe(true)
|
|
255
|
+
expect(runtimeVariant.get('orders')!.has('dashboardWidgets')).toBe(false)
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
})
|
|
@@ -47,6 +47,21 @@ describe('route manifest shards', () => {
|
|
|
47
47
|
expect(loader).toContain(`const ROUTE_SHARD_KEYS = ["customers","__dynamic","customers","__root"]`)
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
it('indents every line of a multi-line declaration inside the shard array', () => {
|
|
51
|
+
const declaration = ['{', ' moduleId: "customers",', ' path: "/customers"', '} as any'].join('\n')
|
|
52
|
+
const outputs = renderRouteManifestShardOutputs('api', [{ path: '/customers', declaration }])
|
|
53
|
+
const shard = outputs.find((entry) => entry.fileName.includes('route-shard.'))?.content ?? ''
|
|
54
|
+
|
|
55
|
+
expect(shard).toContain([
|
|
56
|
+
'export const routes: ApiRouteManifestEntry[] = [',
|
|
57
|
+
' {',
|
|
58
|
+
' moduleId: "customers",',
|
|
59
|
+
' path: "/customers"',
|
|
60
|
+
' } as any',
|
|
61
|
+
']',
|
|
62
|
+
].join('\n'))
|
|
63
|
+
})
|
|
64
|
+
|
|
50
65
|
it('renders metadata without load functions while retaining metadata imports', () => {
|
|
51
66
|
const output = renderRouteMetadataOutput('backend', [{
|
|
52
67
|
path: '/backend/settings',
|
|
@@ -507,8 +507,8 @@ describe('modules.generated.ts', () => {
|
|
|
507
507
|
})
|
|
508
508
|
|
|
509
509
|
it('modules include translation locale keys', () => {
|
|
510
|
-
expect(content).
|
|
511
|
-
expect(content).
|
|
510
|
+
expect(content).toMatch(/['"]en['"]:/)
|
|
511
|
+
expect(content).toMatch(/['"]pl['"]:/)
|
|
512
512
|
})
|
|
513
513
|
|
|
514
514
|
it('modules include customFieldSets reference from ce.ts', () => {
|
|
@@ -635,7 +635,7 @@ describe('api-routes.generated.ts', () => {
|
|
|
635
635
|
|
|
636
636
|
it('products API route has all 4 methods', () => {
|
|
637
637
|
expect(content).toContain('path: "/products"')
|
|
638
|
-
expect(content).toMatch(/methods
|
|
638
|
+
expect(content).toMatch(/methods:\s*\[\s*"GET",\s*"POST",\s*"PUT",\s*"DELETE",?\s*\]/)
|
|
639
639
|
})
|
|
640
640
|
|
|
641
641
|
it('keeps API handler imports lazy in route entries', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { addImportSpec, addImportSpecs, addImportStatement, addImportStatements, type GeneratedImportSpec } from './imports'
|
|
2
|
-
export { addAutoGeneratedComment, createGeneratedSourceFile, getSourceText } from './source-file'
|
|
2
|
+
export { addAutoGeneratedComment, createGeneratedSourceFile, getSourceText, renderGeneratedValue } from './source-file'
|
|
3
3
|
export {
|
|
4
4
|
arrayLiteral,
|
|
5
5
|
assignmentStatement,
|
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
callExpression,
|
|
12
12
|
conditionalExpression,
|
|
13
13
|
elementAccess,
|
|
14
|
+
escapeGeneratedJsonLiteral,
|
|
14
15
|
expressionStatement,
|
|
15
16
|
forOfStatement,
|
|
16
17
|
identifier,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IndentationText, Project, type SourceFile } from 'ts-morph'
|
|
2
|
+
import { writeValue, type GeneratedValue } from './writers'
|
|
2
3
|
|
|
3
4
|
let sharedProject: Project | null = null
|
|
4
5
|
|
|
@@ -26,6 +27,18 @@ export function createGeneratedSourceFile(fileName: string): SourceFile {
|
|
|
26
27
|
return project.createSourceFile(fileName, '', { overwrite: true })
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Renders a single generated value to source text, outside of any source file. Generators
|
|
32
|
+
* that hand a pre-rendered expression to a consumer expecting a string (route manifest
|
|
33
|
+
* shards) use this instead of concatenating the expression by hand, so the AST writers stay
|
|
34
|
+
* the one place an expression is constructed.
|
|
35
|
+
*/
|
|
36
|
+
export function renderGeneratedValue(value: GeneratedValue): string {
|
|
37
|
+
const writer = getProject().createWriter()
|
|
38
|
+
writeValue(writer, value)
|
|
39
|
+
return writer.toString()
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
export function addAutoGeneratedComment(sourceFile: SourceFile, generator: string): void {
|
|
30
43
|
sourceFile.insertText(0, `// AUTO-GENERATED by mercato generate ${generator}\n`)
|
|
31
44
|
}
|