@open-mercato/cli 0.6.6-develop.6362.1.a3dd164018 → 0.6.6-develop.6363.1.03c5e3429a
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/lib/generators/module-registry.js +66 -20
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/utils.js +1 -1
- package/dist/lib/utils.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/generators/__tests__/module-subset.test.ts +37 -13
- package/src/lib/generators/__tests__/output-snapshots.test.ts +2 -0
- package/src/lib/generators/__tests__/structural-contracts.test.ts +40 -5
- package/src/lib/generators/module-registry.ts +71 -19
- package/src/lib/utils.ts +1 -0
package/dist/lib/utils.js
CHANGED
|
@@ -170,7 +170,7 @@ function writeGeneratedFile(options) {
|
|
|
170
170
|
const { outFile, checksumFile, content, structureChecksum, result, quiet } = options;
|
|
171
171
|
const checksum = { content: calculateChecksum(content), structure: structureChecksum };
|
|
172
172
|
const existing = readChecksumRecord(checksumFile);
|
|
173
|
-
const shouldWrite = !existing || existing.content !== checksum.content || existing.structure !== checksum.structure;
|
|
173
|
+
const shouldWrite = !fs.existsSync(outFile) || !existing || existing.content !== checksum.content || existing.structure !== checksum.structure;
|
|
174
174
|
if (shouldWrite) {
|
|
175
175
|
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
176
176
|
fs.writeFileSync(outFile, content);
|
package/dist/lib/utils.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/utils.ts"],
|
|
4
|
-
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport crypto from 'node:crypto'\nimport { pathToFileURL } from 'node:url'\n\nexport type ChecksumRecord = {\n content: string\n structure: string\n}\n\nexport interface GeneratorResult {\n filesWritten: string[]\n filesUnchanged: string[]\n errors: string[]\n}\n\nexport function calculateChecksum(content: string): string {\n return crypto.createHash('md5').update(content).digest('hex')\n}\n\nexport function readChecksumRecord(filePath: string): ChecksumRecord | null {\n if (!fs.existsSync(filePath)) {\n return null\n }\n try {\n const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Partial<ChecksumRecord>\n if (parsed && typeof parsed.content === 'string' && typeof parsed.structure === 'string') {\n return { content: parsed.content, structure: parsed.structure }\n }\n } catch {\n // Invalid checksum file\n }\n return null\n}\n\nexport function writeChecksumRecord(filePath: string, record: ChecksumRecord): void {\n fs.writeFileSync(filePath, JSON.stringify(record) + '\\n')\n}\n\nfunction collectStructureEntries(target: string, base: string, acc: string[]): void {\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(target, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))\n } catch (err) {\n acc.push(`error:${path.relative(base, target)}:${(err as Error).message}`)\n return\n }\n\n for (const entry of entries) {\n const fullPath = path.join(target, entry.name)\n const rel = path.relative(base, fullPath)\n try {\n const stat = fs.statSync(fullPath)\n if (entry.isDirectory()) {\n acc.push(`dir:${rel}:${stat.mtimeMs}`)\n collectStructureEntries(fullPath, base, acc)\n } else if (entry.isFile()) {\n acc.push(`file:${rel}:${stat.size}:${stat.mtimeMs}`)\n } else {\n acc.push(`other:${rel}:${stat.mtimeMs}`)\n }\n } catch {\n // File was deleted between readdir and stat - skip it\n continue\n }\n }\n}\n\nexport function calculateStructureChecksum(paths: string[]): string {\n const normalized = Array.from(new Set(paths.map((p) => path.resolve(p)))).sort((a, b) => a.localeCompare(b))\n const entries: string[] = []\n for (const target of normalized) {\n if (!fs.existsSync(target)) {\n entries.push(`missing:${target}`)\n continue\n }\n const stat = fs.statSync(target)\n entries.push(`${stat.isDirectory() ? 'dir' : 'file'}:${target}:${stat.mtimeMs}`)\n if (stat.isDirectory()) collectStructureEntries(target, target, entries)\n }\n return calculateChecksum(entries.join('\\n'))\n}\n\nexport function writeIfChanged(\n filePath: string,\n content: string,\n checksumPath?: string,\n structureChecksum?: string\n): boolean {\n const newChecksum = calculateChecksum(content)\n\n if (checksumPath) {\n const existingRecord = readChecksumRecord(checksumPath)\n const newRecord: ChecksumRecord = {\n content: newChecksum,\n structure: structureChecksum || '',\n }\n\n const shouldWrite =\n !existingRecord ||\n existingRecord.content !== newRecord.content ||\n (structureChecksum && existingRecord.structure !== newRecord.structure)\n\n if (shouldWrite) {\n ensureDir(filePath)\n fs.writeFileSync(filePath, content)\n writeChecksumRecord(checksumPath, newRecord)\n return true\n }\n return false\n }\n\n // Simple comparison without checksum file\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, 'utf8')\n if (existing === content) {\n return false\n }\n }\n\n ensureDir(filePath)\n fs.writeFileSync(filePath, content)\n return true\n}\n\nexport function ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n}\n\n// Allowed path substrings for safe deletion. Include both POSIX and Windows separators.\nconst ALLOWED_RIMRAF_PATTERNS = [\n '/generated/',\n '/dist/',\n '/.mercato/',\n '/entities/',\n '\\\\generated\\\\',\n '\\\\dist\\\\',\n '\\\\.mercato\\\\',\n '\\\\entities\\\\',\n]\n\nexport function rimrafDir(dir: string, opts?: { allowedPatterns?: string[] }): void {\n if (!fs.existsSync(dir)) return\n\n // Safety check: only allow deletion within known safe directories\n const resolved = path.resolve(dir)\n const allowed = opts?.allowedPatterns ?? ALLOWED_RIMRAF_PATTERNS\n\n // Normalize resolved path to support matching against both POSIX and Windows patterns\n const normalized = {\n posix: resolved.replace(/\\\\/g, '/'),\n win: resolved.replace(/\\//g, '\\\\'),\n }\n\n if (!allowed.some((pattern) => normalized.posix.includes(pattern) || normalized.win.includes(pattern))) {\n throw new Error(`Refusing to delete directory outside allowed paths: ${resolved}. Allowed patterns: ${allowed.join(', ')}`)\n }\n\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const p = path.join(dir, entry.name)\n if (entry.isDirectory()) rimrafDir(p, opts)\n else fs.unlinkSync(p)\n }\n fs.rmdirSync(dir)\n}\n\nexport function toVar(s: string): string {\n return s.replace(/[^a-zA-Z0-9_]/g, '_')\n}\n\nexport function toSnake(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/\\W+/g, '_')\n .replace(/_{2,}/g, '_')\n .replace(/(?:^_+|_+$)/g, '')\n .toLowerCase()\n}\n\nexport function sourceFileHasNamedExport(filePath: string, exportName: string): boolean {\n let source: string\n try {\n source = fs.readFileSync(filePath, 'utf8')\n } catch {\n return false\n }\n\n const escaped = exportName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n if (new RegExp(`export\\\\s+(?:const|let|var|function|class|async\\\\s+function)\\\\s+${escaped}\\\\b`).test(source)) {\n return true\n }\n if (new RegExp(`export\\\\s+\\\\{[^}]*\\\\b${escaped}\\\\b[^}]*\\\\}`).test(source)) {\n return true\n }\n return false\n}\n\nexport async function moduleHasExport(filePath: string, exportName: string): Promise<boolean> {\n if (sourceFileHasNamedExport(filePath, exportName)) {\n return true\n }\n try {\n const isAbsolutePath = path.isAbsolute(filePath)\n const importUrl = isAbsolutePath ? pathToFileURL(filePath).href : filePath\n const mod = await import(importUrl)\n return mod != null && Object.prototype.hasOwnProperty.call(mod, exportName)\n } catch {\n return false\n }\n}\n\nexport function logGenerationResult(label: string, changed: boolean): void {\n if (changed) {\n console.log(`Generated ${label}`)\n }\n}\n\nexport function createGeneratorResult(): GeneratorResult {\n return {\n filesWritten: [],\n filesUnchanged: [],\n errors: [],\n }\n}\n\nexport function writeGeneratedFile(options: {\n outFile: string\n checksumFile: string\n content: string\n structureChecksum: string\n result: GeneratorResult\n quiet?: boolean\n}): void {\n const { outFile, checksumFile, content, structureChecksum, result, quiet } = options\n const checksum = { content: calculateChecksum(content), structure: structureChecksum }\n const existing = readChecksumRecord(checksumFile)\n const shouldWrite =\n !existing ||\n existing.content !== checksum.content ||\n existing.structure !== checksum.structure\n if (shouldWrite) {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, content)\n writeChecksumRecord(checksumFile, checksum)\n result.filesWritten.push(outFile)\n } else {\n result.filesUnchanged.push(outFile)\n }\n if (!quiet) logGenerationResult(path.relative(process.cwd(), outFile), shouldWrite)\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport crypto from 'node:crypto'\nimport { pathToFileURL } from 'node:url'\n\nexport type ChecksumRecord = {\n content: string\n structure: string\n}\n\nexport interface GeneratorResult {\n filesWritten: string[]\n filesUnchanged: string[]\n errors: string[]\n}\n\nexport function calculateChecksum(content: string): string {\n return crypto.createHash('md5').update(content).digest('hex')\n}\n\nexport function readChecksumRecord(filePath: string): ChecksumRecord | null {\n if (!fs.existsSync(filePath)) {\n return null\n }\n try {\n const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Partial<ChecksumRecord>\n if (parsed && typeof parsed.content === 'string' && typeof parsed.structure === 'string') {\n return { content: parsed.content, structure: parsed.structure }\n }\n } catch {\n // Invalid checksum file\n }\n return null\n}\n\nexport function writeChecksumRecord(filePath: string, record: ChecksumRecord): void {\n fs.writeFileSync(filePath, JSON.stringify(record) + '\\n')\n}\n\nfunction collectStructureEntries(target: string, base: string, acc: string[]): void {\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(target, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))\n } catch (err) {\n acc.push(`error:${path.relative(base, target)}:${(err as Error).message}`)\n return\n }\n\n for (const entry of entries) {\n const fullPath = path.join(target, entry.name)\n const rel = path.relative(base, fullPath)\n try {\n const stat = fs.statSync(fullPath)\n if (entry.isDirectory()) {\n acc.push(`dir:${rel}:${stat.mtimeMs}`)\n collectStructureEntries(fullPath, base, acc)\n } else if (entry.isFile()) {\n acc.push(`file:${rel}:${stat.size}:${stat.mtimeMs}`)\n } else {\n acc.push(`other:${rel}:${stat.mtimeMs}`)\n }\n } catch {\n // File was deleted between readdir and stat - skip it\n continue\n }\n }\n}\n\nexport function calculateStructureChecksum(paths: string[]): string {\n const normalized = Array.from(new Set(paths.map((p) => path.resolve(p)))).sort((a, b) => a.localeCompare(b))\n const entries: string[] = []\n for (const target of normalized) {\n if (!fs.existsSync(target)) {\n entries.push(`missing:${target}`)\n continue\n }\n const stat = fs.statSync(target)\n entries.push(`${stat.isDirectory() ? 'dir' : 'file'}:${target}:${stat.mtimeMs}`)\n if (stat.isDirectory()) collectStructureEntries(target, target, entries)\n }\n return calculateChecksum(entries.join('\\n'))\n}\n\nexport function writeIfChanged(\n filePath: string,\n content: string,\n checksumPath?: string,\n structureChecksum?: string\n): boolean {\n const newChecksum = calculateChecksum(content)\n\n if (checksumPath) {\n const existingRecord = readChecksumRecord(checksumPath)\n const newRecord: ChecksumRecord = {\n content: newChecksum,\n structure: structureChecksum || '',\n }\n\n const shouldWrite =\n !existingRecord ||\n existingRecord.content !== newRecord.content ||\n (structureChecksum && existingRecord.structure !== newRecord.structure)\n\n if (shouldWrite) {\n ensureDir(filePath)\n fs.writeFileSync(filePath, content)\n writeChecksumRecord(checksumPath, newRecord)\n return true\n }\n return false\n }\n\n // Simple comparison without checksum file\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, 'utf8')\n if (existing === content) {\n return false\n }\n }\n\n ensureDir(filePath)\n fs.writeFileSync(filePath, content)\n return true\n}\n\nexport function ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n}\n\n// Allowed path substrings for safe deletion. Include both POSIX and Windows separators.\nconst ALLOWED_RIMRAF_PATTERNS = [\n '/generated/',\n '/dist/',\n '/.mercato/',\n '/entities/',\n '\\\\generated\\\\',\n '\\\\dist\\\\',\n '\\\\.mercato\\\\',\n '\\\\entities\\\\',\n]\n\nexport function rimrafDir(dir: string, opts?: { allowedPatterns?: string[] }): void {\n if (!fs.existsSync(dir)) return\n\n // Safety check: only allow deletion within known safe directories\n const resolved = path.resolve(dir)\n const allowed = opts?.allowedPatterns ?? ALLOWED_RIMRAF_PATTERNS\n\n // Normalize resolved path to support matching against both POSIX and Windows patterns\n const normalized = {\n posix: resolved.replace(/\\\\/g, '/'),\n win: resolved.replace(/\\//g, '\\\\'),\n }\n\n if (!allowed.some((pattern) => normalized.posix.includes(pattern) || normalized.win.includes(pattern))) {\n throw new Error(`Refusing to delete directory outside allowed paths: ${resolved}. Allowed patterns: ${allowed.join(', ')}`)\n }\n\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const p = path.join(dir, entry.name)\n if (entry.isDirectory()) rimrafDir(p, opts)\n else fs.unlinkSync(p)\n }\n fs.rmdirSync(dir)\n}\n\nexport function toVar(s: string): string {\n return s.replace(/[^a-zA-Z0-9_]/g, '_')\n}\n\nexport function toSnake(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/\\W+/g, '_')\n .replace(/_{2,}/g, '_')\n .replace(/(?:^_+|_+$)/g, '')\n .toLowerCase()\n}\n\nexport function sourceFileHasNamedExport(filePath: string, exportName: string): boolean {\n let source: string\n try {\n source = fs.readFileSync(filePath, 'utf8')\n } catch {\n return false\n }\n\n const escaped = exportName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n if (new RegExp(`export\\\\s+(?:const|let|var|function|class|async\\\\s+function)\\\\s+${escaped}\\\\b`).test(source)) {\n return true\n }\n if (new RegExp(`export\\\\s+\\\\{[^}]*\\\\b${escaped}\\\\b[^}]*\\\\}`).test(source)) {\n return true\n }\n return false\n}\n\nexport async function moduleHasExport(filePath: string, exportName: string): Promise<boolean> {\n if (sourceFileHasNamedExport(filePath, exportName)) {\n return true\n }\n try {\n const isAbsolutePath = path.isAbsolute(filePath)\n const importUrl = isAbsolutePath ? pathToFileURL(filePath).href : filePath\n const mod = await import(importUrl)\n return mod != null && Object.prototype.hasOwnProperty.call(mod, exportName)\n } catch {\n return false\n }\n}\n\nexport function logGenerationResult(label: string, changed: boolean): void {\n if (changed) {\n console.log(`Generated ${label}`)\n }\n}\n\nexport function createGeneratorResult(): GeneratorResult {\n return {\n filesWritten: [],\n filesUnchanged: [],\n errors: [],\n }\n}\n\nexport function writeGeneratedFile(options: {\n outFile: string\n checksumFile: string\n content: string\n structureChecksum: string\n result: GeneratorResult\n quiet?: boolean\n}): void {\n const { outFile, checksumFile, content, structureChecksum, result, quiet } = options\n const checksum = { content: calculateChecksum(content), structure: structureChecksum }\n const existing = readChecksumRecord(checksumFile)\n const shouldWrite =\n !fs.existsSync(outFile) ||\n !existing ||\n existing.content !== checksum.content ||\n existing.structure !== checksum.structure\n if (shouldWrite) {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, content)\n writeChecksumRecord(checksumFile, checksum)\n result.filesWritten.push(outFile)\n } else {\n result.filesUnchanged.push(outFile)\n }\n if (!quiet) logGenerationResult(path.relative(process.cwd(), outFile), shouldWrite)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6363.1.03c5e3429a",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@mikro-orm/decorators": "^7.1.4",
|
|
61
61
|
"@mikro-orm/migrations": "^7.1.4",
|
|
62
62
|
"@mikro-orm/postgresql": "^7.1.4",
|
|
63
|
-
"@open-mercato/queue": "0.6.6-develop.
|
|
64
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
63
|
+
"@open-mercato/queue": "0.6.6-develop.6363.1.03c5e3429a",
|
|
64
|
+
"@open-mercato/shared": "0.6.6-develop.6363.1.03c5e3429a",
|
|
65
65
|
"cross-spawn": "^7.0.6",
|
|
66
66
|
"pg": "8.22.0",
|
|
67
67
|
"semver": "^7.8.5",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"typescript": "^6.0.3"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
74
|
+
"@open-mercato/shared": "0.6.6-develop.6363.1.03c5e3429a"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
77
|
+
"@open-mercato/shared": "0.6.6-develop.6363.1.03c5e3429a",
|
|
78
78
|
"@types/jest": "^30.0.0",
|
|
79
79
|
"jest": "^30.4.2",
|
|
80
80
|
"ts-jest": "^29.4.11"
|
|
@@ -214,7 +214,7 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
214
214
|
`))
|
|
215
215
|
expect(readGenerated(tmpDir, 'frontend-routes.generated.ts')).toBe(dedent(`
|
|
216
216
|
// AUTO-GENERATED by mercato generate registry
|
|
217
|
-
import type
|
|
217
|
+
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from '@open-mercato/shared/modules/registry'
|
|
218
218
|
|
|
219
219
|
export const frontendRoutes: FrontendRouteManifestEntry[] = [
|
|
220
220
|
|
|
@@ -224,7 +224,7 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
224
224
|
`))
|
|
225
225
|
expect(readGenerated(tmpDir, 'backend-routes.generated.ts')).toBe(dedent(`
|
|
226
226
|
// AUTO-GENERATED by mercato generate registry
|
|
227
|
-
import type
|
|
227
|
+
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from '@open-mercato/shared/modules/registry'
|
|
228
228
|
|
|
229
229
|
export const backendRoutes: BackendRouteManifestEntry[] = [
|
|
230
230
|
|
|
@@ -309,20 +309,20 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
309
309
|
`))
|
|
310
310
|
expect(readGenerated(tmpDir, 'frontend-routes.generated.ts')).toBe(dedent(`
|
|
311
311
|
// AUTO-GENERATED by mercato generate registry
|
|
312
|
-
import type
|
|
312
|
+
import { resolvePageRouteMetadata, type FrontendRouteManifestEntry } from '@open-mercato/shared/modules/registry'
|
|
313
313
|
|
|
314
314
|
export const frontendRoutes: FrontendRouteManifestEntry[] = [
|
|
315
|
-
{ moduleId: "orders",
|
|
315
|
+
{ moduleId: "orders", ...resolvePageRouteMetadata("/", (undefined as any)), load: async () => { const mod = await import("@open-mercato/core/modules/orders/frontend/page"); return (mod.default ?? mod) as any } }
|
|
316
316
|
]
|
|
317
317
|
|
|
318
318
|
export default frontendRoutes
|
|
319
319
|
`))
|
|
320
320
|
expect(readGenerated(tmpDir, 'backend-routes.generated.ts')).toBe(dedent(`
|
|
321
321
|
// AUTO-GENERATED by mercato generate registry
|
|
322
|
-
import type
|
|
322
|
+
import { resolvePageRouteMetadata, type BackendRouteManifestEntry } from '@open-mercato/shared/modules/registry'
|
|
323
323
|
|
|
324
324
|
export const backendRoutes: BackendRouteManifestEntry[] = [
|
|
325
|
-
{ moduleId: "orders",
|
|
325
|
+
{ moduleId: "orders", ...resolvePageRouteMetadata("/backend/orders", (undefined as any)), load: async () => { const mod = await import("@open-mercato/core/modules/orders/backend/page"); return (mod.default ?? mod) as any } }
|
|
326
326
|
]
|
|
327
327
|
|
|
328
328
|
export default backendRoutes
|
|
@@ -463,14 +463,22 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
463
463
|
expect(output).toContain('subscriber_event_meta:on-created')
|
|
464
464
|
})
|
|
465
465
|
|
|
466
|
-
it('keeps
|
|
466
|
+
it('keeps runtime metadata fallback for settings/profile route icons that are not serializable yet', async () => {
|
|
467
467
|
touchFile(
|
|
468
|
-
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', '
|
|
468
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', 'settings', 'page.tsx'),
|
|
469
469
|
'export default function Page() { return null }\n',
|
|
470
470
|
)
|
|
471
471
|
touchFile(
|
|
472
|
-
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', '
|
|
473
|
-
"import React from 'react'\nconst icon = React.createElement('svg', null)\nexport const metadata = { requireAuth: true, pageTitle: '
|
|
472
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', 'settings', 'page.meta.ts'),
|
|
473
|
+
"import React from 'react'\nconst icon = React.createElement('svg', null)\nexport const metadata = { requireAuth: true, pageTitle: 'Settings', pageContext: 'settings', icon }\n",
|
|
474
|
+
)
|
|
475
|
+
touchFile(
|
|
476
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', 'profile', 'page.tsx'),
|
|
477
|
+
'export default function Page() { return null }\n',
|
|
478
|
+
)
|
|
479
|
+
touchFile(
|
|
480
|
+
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'iconic', 'backend', 'profile', 'page.meta.ts'),
|
|
481
|
+
"import React from 'react'\nconst icon = React.createElement('svg', null)\nexport const metadata = { requireAuth: true, pageTitle: 'Profile', pageContext: 'profile', icon }\n",
|
|
474
482
|
)
|
|
475
483
|
|
|
476
484
|
const enabled: ModuleEntry[] = [
|
|
@@ -481,9 +489,12 @@ describe('generateModuleRegistry with module subsets', () => {
|
|
|
481
489
|
|
|
482
490
|
expect(result.errors).toEqual([])
|
|
483
491
|
const output = readGenerated(tmpDir, 'backend-routes.generated.ts')!
|
|
484
|
-
expect(output).toMatch(/import \* as .* from "@open-mercato\/core\/modules\/iconic\/backend\/
|
|
485
|
-
expect(output).
|
|
486
|
-
expect(output).toContain('
|
|
492
|
+
expect(output).toMatch(/import \* as .* from "@open-mercato\/core\/modules\/iconic\/backend\/settings\/page\.meta"/)
|
|
493
|
+
expect(output).toMatch(/import \* as .* from "@open-mercato\/core\/modules\/iconic\/backend\/profile\/page\.meta"/)
|
|
494
|
+
expect(output).toContain('resolvePageRouteMetadata("/backend/settings", (((BMManifest')
|
|
495
|
+
expect(output).toContain('resolvePageRouteMetadata("/backend/profile", (((BMManifest')
|
|
496
|
+
expect(output).not.toContain('"pageContext":"settings"')
|
|
497
|
+
expect(output).not.toContain('"pageContext":"profile"')
|
|
487
498
|
})
|
|
488
499
|
|
|
489
500
|
it('handles module with only widgets — no pages or subscribers', async () => {
|
|
@@ -1447,6 +1458,10 @@ describe('generateModuleRegistryApp with module subsets', () => {
|
|
|
1447
1458
|
const output = readGenerated(tmpDir, 'modules.app.generated.ts')
|
|
1448
1459
|
expect(output).not.toBeNull()
|
|
1449
1460
|
expect(output).toContain('export const modules: Module[] = [')
|
|
1461
|
+
|
|
1462
|
+
const bootstrapOutput = readGenerated(tmpDir, 'modules.bootstrap.generated.ts')
|
|
1463
|
+
expect(bootstrapOutput).not.toBeNull()
|
|
1464
|
+
expect(bootstrapOutput).toContain('export const modules: Module[] = [')
|
|
1450
1465
|
})
|
|
1451
1466
|
|
|
1452
1467
|
it('app output excludes CLI commands and keeps lazy workers/subscribers', async () => {
|
|
@@ -1482,6 +1497,15 @@ describe('generateModuleRegistryApp with module subsets', () => {
|
|
|
1482
1497
|
expect(output).toContain('createLazyModuleSubscriber')
|
|
1483
1498
|
expect(output).toContain('createLazyModuleWorker')
|
|
1484
1499
|
expect(output).toContain('dashboardWidgets:')
|
|
1500
|
+
|
|
1501
|
+
const bootstrapOutput = readGenerated(tmpDir, 'modules.bootstrap.generated.ts')!
|
|
1502
|
+
expect(bootstrapOutput).toContain('id: "runtime_mod"')
|
|
1503
|
+
expect(bootstrapOutput).not.toContain('cli:')
|
|
1504
|
+
expect(bootstrapOutput).toContain('subscribers:')
|
|
1505
|
+
expect(bootstrapOutput).toContain('workers:')
|
|
1506
|
+
expect(bootstrapOutput).toContain('createLazyModuleSubscriber')
|
|
1507
|
+
expect(bootstrapOutput).toContain('createLazyModuleWorker')
|
|
1508
|
+
expect(bootstrapOutput).toContain('dashboardWidgets:')
|
|
1485
1509
|
})
|
|
1486
1510
|
})
|
|
1487
1511
|
|
|
@@ -691,6 +691,7 @@ describe('generator output compatibility', () => {
|
|
|
691
691
|
'message-types.generated.ts',
|
|
692
692
|
'messages.client.generated.ts',
|
|
693
693
|
'modules.generated.ts',
|
|
694
|
+
'modules.i18n.generated.ts',
|
|
694
695
|
'modules.runtime.generated.ts',
|
|
695
696
|
'notification-handlers.generated.ts',
|
|
696
697
|
'notifications.client.generated.ts',
|
|
@@ -720,6 +721,7 @@ describe('generator output compatibility', () => {
|
|
|
720
721
|
'entities.ids.generated.ts',
|
|
721
722
|
'entity-fields-registry.ts',
|
|
722
723
|
'modules.app.generated.ts',
|
|
724
|
+
'modules.bootstrap.generated.ts',
|
|
723
725
|
'modules.cli.generated.ts',
|
|
724
726
|
'enabled-module-ids.generated.ts',
|
|
725
727
|
]))
|
|
@@ -572,7 +572,7 @@ describe('frontend-routes.generated.ts', () => {
|
|
|
572
572
|
|
|
573
573
|
it('contains orders frontend route with correct pattern', () => {
|
|
574
574
|
expect(content).toContain('moduleId: "orders"')
|
|
575
|
-
expect(content).toContain('
|
|
575
|
+
expect(content).toContain('resolvePageRouteMetadata("/",')
|
|
576
576
|
})
|
|
577
577
|
})
|
|
578
578
|
|
|
@@ -602,10 +602,15 @@ describe('backend-routes.generated.ts', () => {
|
|
|
602
602
|
})
|
|
603
603
|
|
|
604
604
|
it('each route entry has pattern, moduleId, and load function', () => {
|
|
605
|
-
expect(content).toContain('
|
|
606
|
-
expect(content).toContain('requireAuth:')
|
|
605
|
+
expect(content).toContain('resolvePageRouteMetadata(')
|
|
607
606
|
expect(content).toContain('load: async () =>')
|
|
608
607
|
})
|
|
608
|
+
|
|
609
|
+
it('keeps backend page imports lazy in route entries', () => {
|
|
610
|
+
expect(content).toContain('@open-mercato/core/modules/orders/backend')
|
|
611
|
+
expect(content).toContain('load: async () =>')
|
|
612
|
+
expect(content).toContain('import("@open-mercato/core/modules/orders/backend')
|
|
613
|
+
})
|
|
609
614
|
})
|
|
610
615
|
|
|
611
616
|
describe('api-routes.generated.ts', () => {
|
|
@@ -632,6 +637,11 @@ describe('api-routes.generated.ts', () => {
|
|
|
632
637
|
expect(content).toContain('path: "/products"')
|
|
633
638
|
expect(content).toMatch(/methods:.*GET.*POST.*PUT.*DELETE/)
|
|
634
639
|
})
|
|
640
|
+
|
|
641
|
+
it('keeps API handler imports lazy in route entries', () => {
|
|
642
|
+
expect(content).toContain('@open-mercato/core/modules/orders/api')
|
|
643
|
+
expect(content).toContain('load: async () => import(')
|
|
644
|
+
})
|
|
635
645
|
})
|
|
636
646
|
|
|
637
647
|
// ---------------------------------------------------------------------------
|
|
@@ -1241,14 +1251,39 @@ describe('modules.app.generated.ts', () => {
|
|
|
1241
1251
|
})
|
|
1242
1252
|
})
|
|
1243
1253
|
|
|
1254
|
+
describe('modules.bootstrap.generated.ts', () => {
|
|
1255
|
+
it('exports a bootstrap-only module manifest without route components', async () => {
|
|
1256
|
+
const enabled = scaffoldFixture()
|
|
1257
|
+
const resolver = createMockResolver(enabled)
|
|
1258
|
+
await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1259
|
+
const content = readGenerated('modules.bootstrap.generated.ts')
|
|
1260
|
+
|
|
1261
|
+
expect(content).toContain('export const modules: Module[] = [')
|
|
1262
|
+
expect(content).toContain('export default modules')
|
|
1263
|
+
expect(content).toContain('id: "orders"')
|
|
1264
|
+
expect(content).toContain('subscribers:')
|
|
1265
|
+
expect(content).toContain('orders.order.created')
|
|
1266
|
+
expect(content).toContain('setup:')
|
|
1267
|
+
expect(content).toContain('features:')
|
|
1268
|
+
|
|
1269
|
+
expect(content).not.toContain('frontendRoutes:')
|
|
1270
|
+
expect(content).not.toContain('backendRoutes:')
|
|
1271
|
+
expect(content).not.toContain('createElement')
|
|
1272
|
+
expect(content).not.toContain('/page.meta')
|
|
1273
|
+
expect(content).not.toContain('/frontend/')
|
|
1274
|
+
expect(content).not.toContain('/backend/')
|
|
1275
|
+
expect(content).not.toContain('cli:')
|
|
1276
|
+
})
|
|
1277
|
+
})
|
|
1278
|
+
|
|
1244
1279
|
describe('bootstrap-modules.generated.ts', () => {
|
|
1245
|
-
it('exports legacy bootstrapModules alias from modules.
|
|
1280
|
+
it('exports legacy bootstrapModules alias from modules.bootstrap.generated.ts', async () => {
|
|
1246
1281
|
const enabled = scaffoldFixture()
|
|
1247
1282
|
const resolver = createMockResolver(enabled)
|
|
1248
1283
|
await generateModuleRegistryApp({ resolver, quiet: true })
|
|
1249
1284
|
const content = readGenerated('bootstrap-modules.generated.ts')
|
|
1250
1285
|
|
|
1251
|
-
expect(content).toContain('modules.
|
|
1286
|
+
expect(content).toContain('modules.bootstrap.generated')
|
|
1252
1287
|
expect(content).toContain('export const bootstrapModules: Module[] = modules')
|
|
1253
1288
|
})
|
|
1254
1289
|
})
|
|
@@ -1377,6 +1377,10 @@ function buildPageRouteProps(metaExpr: string, routePath: string): string {
|
|
|
1377
1377
|
return `pattern: ${toLiteral(routePath || '/')}, requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, requireCustomerAuth: (${metaExpr})?.requireCustomerAuth, requireCustomerFeatures: (${metaExpr})?.requireCustomerFeatures, nav: (${metaExpr})?.nav, title: (${metaExpr})?.pageTitle ?? (${metaExpr})?.title, titleKey: (${metaExpr})?.pageTitleKey ?? (${metaExpr})?.titleKey, group: (${metaExpr})?.pageGroup ?? (${metaExpr})?.group, groupKey: (${metaExpr})?.pageGroupKey ?? (${metaExpr})?.groupKey, icon: (${metaExpr})?.icon, order: (${metaExpr})?.pageOrder ?? (${metaExpr})?.order, priority: (${metaExpr})?.pagePriority ?? (${metaExpr})?.priority, navHidden: (${metaExpr})?.navHidden, visible: (${metaExpr})?.visible, enabled: (${metaExpr})?.enabled, breadcrumb: (${metaExpr})?.breadcrumb, pageContext: (${metaExpr})?.pageContext, placement: (${metaExpr})?.placement`
|
|
1378
1378
|
}
|
|
1379
1379
|
|
|
1380
|
+
function buildPageRouteManifestSpread(metaExpr: string, routePath: string): string {
|
|
1381
|
+
return `...resolvePageRouteMetadata(${toLiteral(routePath || '/')}, ${metaExpr})`
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1380
1384
|
function normalizeBreadcrumb(raw: unknown): SerializablePageMetadata['breadcrumb'] {
|
|
1381
1385
|
if (!Array.isArray(raw)) return undefined
|
|
1382
1386
|
const breadcrumb = raw
|
|
@@ -1583,7 +1587,7 @@ async function processPageFiles(options: {
|
|
|
1583
1587
|
sourceFile: metaPath,
|
|
1584
1588
|
metaPath,
|
|
1585
1589
|
runtimeExpr: `(((${manifestMetaImportName} as any).metadata) as any)`,
|
|
1586
|
-
allowRuntimeFallback:
|
|
1590
|
+
allowRuntimeFallback: true,
|
|
1587
1591
|
})
|
|
1588
1592
|
manifestMetaExpr = manifestMetadata.manifestExpr
|
|
1589
1593
|
if (manifestMetadata.requiresRuntimeImport) {
|
|
@@ -1601,7 +1605,7 @@ async function processPageFiles(options: {
|
|
|
1601
1605
|
const manifestMetadata = await loadPageMetadataForManifest({
|
|
1602
1606
|
sourceFile,
|
|
1603
1607
|
runtimeExpr: `(((${manifestMetaImportName} as any).metadata) as any)`,
|
|
1604
|
-
allowRuntimeFallback:
|
|
1608
|
+
allowRuntimeFallback: true,
|
|
1605
1609
|
})
|
|
1606
1610
|
manifestMetaExpr = manifestMetadata.manifestExpr
|
|
1607
1611
|
if (manifestMetadata.requiresRuntimeImport) {
|
|
@@ -1614,7 +1618,7 @@ async function processPageFiles(options: {
|
|
|
1614
1618
|
}
|
|
1615
1619
|
const baseProps = buildPageRouteProps(metaExpr, routePath)
|
|
1616
1620
|
const runtimeBaseProps = buildPageRouteProps(runtimeMetaExpr, routePath)
|
|
1617
|
-
const manifestBaseProps =
|
|
1621
|
+
const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
|
|
1618
1622
|
eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
|
|
1619
1623
|
runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
|
|
1620
1624
|
manifestRoutes.push(`{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`)
|
|
@@ -1661,7 +1665,7 @@ async function processPageFiles(options: {
|
|
|
1661
1665
|
sourceFile: metaPath,
|
|
1662
1666
|
metaPath,
|
|
1663
1667
|
runtimeExpr: `(((${manifestMetaImportName} as any).metadata) as any)`,
|
|
1664
|
-
allowRuntimeFallback:
|
|
1668
|
+
allowRuntimeFallback: false,
|
|
1665
1669
|
})
|
|
1666
1670
|
manifestMetaExpr = manifestMetadata.manifestExpr
|
|
1667
1671
|
if (manifestMetadata.requiresRuntimeImport) {
|
|
@@ -1679,7 +1683,7 @@ async function processPageFiles(options: {
|
|
|
1679
1683
|
const manifestMetadata = await loadPageMetadataForManifest({
|
|
1680
1684
|
sourceFile,
|
|
1681
1685
|
runtimeExpr: `(((${manifestMetaImportName} as any).metadata) as any)`,
|
|
1682
|
-
allowRuntimeFallback:
|
|
1686
|
+
allowRuntimeFallback: false,
|
|
1683
1687
|
})
|
|
1684
1688
|
manifestMetaExpr = manifestMetadata.manifestExpr
|
|
1685
1689
|
if (manifestMetadata.requiresRuntimeImport) {
|
|
@@ -1692,7 +1696,7 @@ async function processPageFiles(options: {
|
|
|
1692
1696
|
}
|
|
1693
1697
|
const baseProps = buildPageRouteProps(metaExpr, routePath)
|
|
1694
1698
|
const runtimeBaseProps = buildPageRouteProps(runtimeMetaExpr, routePath)
|
|
1695
|
-
const manifestBaseProps =
|
|
1699
|
+
const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
|
|
1696
1700
|
eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
|
|
1697
1701
|
runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
|
|
1698
1702
|
manifestRoutes.push(`{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`)
|
|
@@ -2291,8 +2295,12 @@ function renderAstLegacyManifestOutput(options: {
|
|
|
2291
2295
|
imports?: GeneratedImportStatement[]
|
|
2292
2296
|
entries: string[]
|
|
2293
2297
|
}): string {
|
|
2298
|
+
const usesPageRouteMetadataHelper = options.typeName === 'FrontendRouteManifestEntry'
|
|
2299
|
+
|| options.typeName === 'BackendRouteManifestEntry'
|
|
2294
2300
|
const importSection = [
|
|
2295
|
-
|
|
2301
|
+
usesPageRouteMetadataHelper
|
|
2302
|
+
? `import { resolvePageRouteMetadata, type ${options.typeName} } from '@open-mercato/shared/modules/registry'`
|
|
2303
|
+
: `import type { ${options.typeName} } from '@open-mercato/shared/modules/registry'`,
|
|
2296
2304
|
...(options.imports ?? []).map((entry) => serializeGeneratedImport(entry)),
|
|
2297
2305
|
].join('\n')
|
|
2298
2306
|
|
|
@@ -2631,8 +2639,9 @@ function processTranslationsAst(options: {
|
|
|
2631
2639
|
appImportBase: string
|
|
2632
2640
|
pkgImportBase: string
|
|
2633
2641
|
imports: string[]
|
|
2642
|
+
extraImports?: string[]
|
|
2634
2643
|
}): GeneratedObjectEntry[] {
|
|
2635
|
-
const { roots, modId, appImportBase, pkgImportBase, imports } = options
|
|
2644
|
+
const { roots, modId, appImportBase, pkgImportBase, imports, extraImports } = options
|
|
2636
2645
|
const i18nApp = path.join(roots.appBase, 'i18n')
|
|
2637
2646
|
const pkgI18nBase = resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase
|
|
2638
2647
|
const i18nCore = path.join(pkgI18nBase, 'i18n')
|
|
@@ -2665,6 +2674,8 @@ function processTranslationsAst(options: {
|
|
|
2665
2674
|
const appName = `T_${toVar(modId)}_${toVar(locale)}_A`
|
|
2666
2675
|
imports.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
|
|
2667
2676
|
imports.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
|
|
2677
|
+
extraImports?.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
|
|
2678
|
+
extraImports?.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
|
|
2668
2679
|
translations.push({
|
|
2669
2680
|
name: JSON.stringify(locale),
|
|
2670
2681
|
value: objectLiteral([
|
|
@@ -2678,6 +2689,7 @@ function processTranslationsAst(options: {
|
|
|
2678
2689
|
if (appHas) {
|
|
2679
2690
|
const appName = `T_${toVar(modId)}_${toVar(locale)}_A`
|
|
2680
2691
|
imports.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
|
|
2692
|
+
extraImports?.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
|
|
2681
2693
|
translations.push({
|
|
2682
2694
|
name: JSON.stringify(locale),
|
|
2683
2695
|
value: asExpression(identifier(appName), 'unknown as Record<string, string>'),
|
|
@@ -2688,6 +2700,7 @@ function processTranslationsAst(options: {
|
|
|
2688
2700
|
if (coreHas) {
|
|
2689
2701
|
const coreName = `T_${toVar(modId)}_${toVar(locale)}_C`
|
|
2690
2702
|
imports.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
|
|
2703
|
+
extraImports?.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
|
|
2691
2704
|
translations.push({
|
|
2692
2705
|
name: JSON.stringify(locale),
|
|
2693
2706
|
value: asExpression(identifier(coreName), 'unknown as Record<string, string>'),
|
|
@@ -3367,6 +3380,10 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3367
3380
|
const outputDir = resolver.getOutputDir()
|
|
3368
3381
|
const outFile = path.join(outputDir, 'modules.app.generated.ts')
|
|
3369
3382
|
const checksumFile = path.join(outputDir, 'modules.app.generated.checksum')
|
|
3383
|
+
const bootstrapOutFile = path.join(outputDir, 'modules.bootstrap.generated.ts')
|
|
3384
|
+
const bootstrapChecksumFile = path.join(outputDir, 'modules.bootstrap.generated.checksum')
|
|
3385
|
+
const i18nOutFile = path.join(outputDir, 'modules.i18n.generated.ts')
|
|
3386
|
+
const i18nChecksumFile = path.join(outputDir, 'modules.i18n.generated.checksum')
|
|
3370
3387
|
const legacyOutFile = path.join(outputDir, 'bootstrap-modules.generated.ts')
|
|
3371
3388
|
const legacyChecksumFile = path.join(outputDir, 'bootstrap-modules.generated.checksum')
|
|
3372
3389
|
const enabledIdsOutFile = path.join(outputDir, 'enabled-module-ids.generated.ts')
|
|
@@ -3379,7 +3396,11 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3379
3396
|
verifyThirdPartyModuleShape(entry, resolver.getModulePaths(entry).pkgBase)
|
|
3380
3397
|
}
|
|
3381
3398
|
const imports: string[] = []
|
|
3399
|
+
const bootstrapImports: string[] = []
|
|
3400
|
+
const i18nImports: string[] = []
|
|
3382
3401
|
const moduleDecls: WriterFunction[] = []
|
|
3402
|
+
const bootstrapModuleDecls: WriterFunction[] = []
|
|
3403
|
+
const i18nModuleDecls: WriterFunction[] = []
|
|
3383
3404
|
const importIdRef = { value: 0 }
|
|
3384
3405
|
const trackedRoots = new Set<string>()
|
|
3385
3406
|
const requiresByModule = new Map<string, string[]>()
|
|
@@ -3418,6 +3439,7 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3418
3439
|
infoImportName = `I${importIdRef.value++}_${toVar(modId)}`
|
|
3419
3440
|
const importPath = sanitizeGeneratedModuleSpecifier(indexResolved.importPath)
|
|
3420
3441
|
imports.push(buildImportStatement(`* as ${infoImportName}`, importPath))
|
|
3442
|
+
bootstrapImports.push(buildImportStatement(`* as ${infoImportName}`, importPath))
|
|
3421
3443
|
try {
|
|
3422
3444
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
3423
3445
|
const mod = require(indexResolved.absolutePath)
|
|
@@ -3429,11 +3451,13 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3429
3451
|
|
|
3430
3452
|
{
|
|
3431
3453
|
const setup = resolveConventionFile(roots, imps, 'setup.ts', 'SETUP', modId, importIdRef, imports)
|
|
3454
|
+
if (setup) bootstrapImports.push(buildImportStatement(`* as ${setup.importName}`, setup.importPath))
|
|
3432
3455
|
if (setup) setupImportName = setup.importName
|
|
3433
3456
|
}
|
|
3434
3457
|
|
|
3435
3458
|
{
|
|
3436
3459
|
const encryption = resolveConventionFile(roots, imps, 'encryption.ts', 'ENCRYPTION', modId, importIdRef, imports)
|
|
3460
|
+
if (encryption) bootstrapImports.push(buildImportStatement(`* as ${encryption.importName}`, encryption.importPath))
|
|
3437
3461
|
if (encryption) encryptionImportName = encryption.importName
|
|
3438
3462
|
}
|
|
3439
3463
|
|
|
@@ -3442,12 +3466,13 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3442
3466
|
if (resolved) {
|
|
3443
3467
|
const importName = `INTEGRATION_${toVar(modId)}_${importIdRef.value++}`
|
|
3444
3468
|
imports.push(buildImportStatement(`* as ${importName}`, resolved.importPath))
|
|
3469
|
+
bootstrapImports.push(buildImportStatement(`* as ${importName}`, resolved.importPath))
|
|
3445
3470
|
integrationImportName = importName
|
|
3446
3471
|
}
|
|
3447
3472
|
}
|
|
3448
3473
|
|
|
3449
3474
|
{
|
|
3450
|
-
const ext = resolveConventionFile(roots, imps, 'data/extensions.ts', 'X', modId, importIdRef, imports)
|
|
3475
|
+
const ext = resolveConventionFile(roots, imps, 'data/extensions.ts', 'X', modId, importIdRef, imports, bootstrapImports)
|
|
3451
3476
|
if (ext) extensionsImportName = ext.importName
|
|
3452
3477
|
}
|
|
3453
3478
|
|
|
@@ -3457,17 +3482,18 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3457
3482
|
const importName = `ACL_${toVar(modId)}_${importIdRef.value++}`
|
|
3458
3483
|
const importPath = sanitizeGeneratedModuleSpecifier(aclResolved.importPath)
|
|
3459
3484
|
imports.push(buildImportStatement(`* as ${importName}`, importPath))
|
|
3485
|
+
bootstrapImports.push(buildImportStatement(`* as ${importName}`, importPath))
|
|
3460
3486
|
featuresImportName = importName
|
|
3461
3487
|
}
|
|
3462
3488
|
}
|
|
3463
3489
|
|
|
3464
3490
|
{
|
|
3465
|
-
const ce = resolveConventionFile(roots, imps, 'ce.ts', 'CE', modId, importIdRef, imports)
|
|
3491
|
+
const ce = resolveConventionFile(roots, imps, 'ce.ts', 'CE', modId, importIdRef, imports, bootstrapImports)
|
|
3466
3492
|
if (ce) customEntitiesImportName = ce.importName
|
|
3467
3493
|
}
|
|
3468
3494
|
|
|
3469
3495
|
{
|
|
3470
|
-
const fields = resolveConventionFile(roots, imps, 'data/fields.ts', 'F', modId, importIdRef, imports)
|
|
3496
|
+
const fields = resolveConventionFile(roots, imps, 'data/fields.ts', 'F', modId, importIdRef, imports, bootstrapImports)
|
|
3471
3497
|
if (fields) fieldsImportName = fields.importName
|
|
3472
3498
|
}
|
|
3473
3499
|
|
|
@@ -3511,13 +3537,17 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3511
3537
|
}
|
|
3512
3538
|
}
|
|
3513
3539
|
|
|
3540
|
+
const translationImports: string[] = []
|
|
3514
3541
|
translations.push(...processTranslationsAst({
|
|
3515
3542
|
roots,
|
|
3516
3543
|
modId,
|
|
3517
3544
|
appImportBase,
|
|
3518
3545
|
pkgImportBase: imps.pkgBase,
|
|
3519
3546
|
imports,
|
|
3547
|
+
extraImports: translationImports,
|
|
3520
3548
|
}))
|
|
3549
|
+
bootstrapImports.push(...translationImports)
|
|
3550
|
+
i18nImports.push(...translationImports)
|
|
3521
3551
|
|
|
3522
3552
|
subscribers.push(...await processSubscribersAst({
|
|
3523
3553
|
roots,
|
|
@@ -3547,18 +3577,16 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3547
3577
|
{ name: 'id', value: modId },
|
|
3548
3578
|
{ name: 'customFieldSets', value: buildModuleCustomFieldSetsValue({ fieldsImportName, customEntitiesImportName, moduleId: modId }) },
|
|
3549
3579
|
]
|
|
3580
|
+
const i18nModuleEntries: GeneratedObjectEntry[] = [
|
|
3581
|
+
{ name: 'id', value: modId },
|
|
3582
|
+
]
|
|
3550
3583
|
|
|
3551
3584
|
if (infoImportName) {
|
|
3552
3585
|
moduleEntries.push({ name: 'info', value: propertyAccess(identifier(infoImportName), 'metadata') })
|
|
3553
3586
|
}
|
|
3554
|
-
if (frontendRoutes.length > 0) {
|
|
3555
|
-
moduleEntries.push({ name: 'frontendRoutes', value: arrayLiteral(frontendRoutes, writeValue) })
|
|
3556
|
-
}
|
|
3557
|
-
if (backendRoutes.length > 0) {
|
|
3558
|
-
moduleEntries.push({ name: 'backendRoutes', value: arrayLiteral(backendRoutes, writeValue) })
|
|
3559
|
-
}
|
|
3560
3587
|
if (translations.length > 0) {
|
|
3561
3588
|
moduleEntries.push({ name: 'translations', value: objectLiteral(translations) })
|
|
3589
|
+
i18nModuleEntries.push({ name: 'translations', value: objectLiteral(translations) })
|
|
3562
3590
|
}
|
|
3563
3591
|
if (subscribers.length > 0) {
|
|
3564
3592
|
moduleEntries.push({ name: 'subscribers', value: arrayLiteral(subscribers, writeValue) })
|
|
@@ -3642,7 +3670,17 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3642
3670
|
})
|
|
3643
3671
|
}
|
|
3644
3672
|
|
|
3645
|
-
|
|
3673
|
+
const routeAwareModuleEntries = [...moduleEntries]
|
|
3674
|
+
if (frontendRoutes.length > 0) {
|
|
3675
|
+
routeAwareModuleEntries.push({ name: 'frontendRoutes', value: arrayLiteral(frontendRoutes, writeValue) })
|
|
3676
|
+
}
|
|
3677
|
+
if (backendRoutes.length > 0) {
|
|
3678
|
+
routeAwareModuleEntries.push({ name: 'backendRoutes', value: arrayLiteral(backendRoutes, writeValue) })
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
moduleDecls.push(objectLiteral(routeAwareModuleEntries))
|
|
3682
|
+
bootstrapModuleDecls.push(objectLiteral(moduleEntries))
|
|
3683
|
+
i18nModuleDecls.push(objectLiteral(i18nModuleEntries))
|
|
3646
3684
|
}
|
|
3647
3685
|
|
|
3648
3686
|
const output = renderAstModuleRegistryFile({
|
|
@@ -3652,11 +3690,23 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3652
3690
|
moduleEntries: moduleDecls,
|
|
3653
3691
|
includeCreateElementImport: hasRouteComponents,
|
|
3654
3692
|
})
|
|
3693
|
+
const bootstrapOutput = renderAstModuleRegistryFile({
|
|
3694
|
+
fileName: 'modules.bootstrap.generated.ts',
|
|
3695
|
+
generator: 'registry (bootstrap version)',
|
|
3696
|
+
imports: bootstrapImports,
|
|
3697
|
+
moduleEntries: bootstrapModuleDecls,
|
|
3698
|
+
})
|
|
3699
|
+
const i18nOutput = renderAstModuleRegistryFile({
|
|
3700
|
+
fileName: 'modules.i18n.generated.ts',
|
|
3701
|
+
generator: 'registry (i18n version)',
|
|
3702
|
+
imports: i18nImports,
|
|
3703
|
+
moduleEntries: i18nModuleDecls,
|
|
3704
|
+
})
|
|
3655
3705
|
const legacyOutput = renderAstLegacyAliasFile({
|
|
3656
3706
|
fileName: 'bootstrap-modules.generated.ts',
|
|
3657
3707
|
exportName: 'bootstrapModules',
|
|
3658
3708
|
exportType: 'Module[]',
|
|
3659
|
-
sourceFileName: 'modules.
|
|
3709
|
+
sourceFileName: 'modules.bootstrap.generated.ts',
|
|
3660
3710
|
initializer: identifier('modules'),
|
|
3661
3711
|
})
|
|
3662
3712
|
|
|
@@ -3684,6 +3734,8 @@ export async function generateModuleRegistryApp(options: ModuleRegistryOptions):
|
|
|
3684
3734
|
|
|
3685
3735
|
const structureChecksum = calculateStructureChecksum(Array.from(trackedRoots))
|
|
3686
3736
|
writeGeneratedFile({ outFile, checksumFile, content: output, structureChecksum, result, quiet })
|
|
3737
|
+
writeGeneratedFile({ outFile: bootstrapOutFile, checksumFile: bootstrapChecksumFile, content: bootstrapOutput, structureChecksum, result, quiet })
|
|
3738
|
+
writeGeneratedFile({ outFile: i18nOutFile, checksumFile: i18nChecksumFile, content: i18nOutput, structureChecksum, result, quiet })
|
|
3687
3739
|
writeGeneratedFile({ outFile: legacyOutFile, checksumFile: legacyChecksumFile, content: legacyOutput, structureChecksum, result, quiet })
|
|
3688
3740
|
|
|
3689
3741
|
const enabledIdsOutput = renderEnabledModuleIdsFile(enabled.map((entry) => entry.id))
|
package/src/lib/utils.ts
CHANGED
|
@@ -235,6 +235,7 @@ export function writeGeneratedFile(options: {
|
|
|
235
235
|
const checksum = { content: calculateChecksum(content), structure: structureChecksum }
|
|
236
236
|
const existing = readChecksumRecord(checksumFile)
|
|
237
237
|
const shouldWrite =
|
|
238
|
+
!fs.existsSync(outFile) ||
|
|
238
239
|
!existing ||
|
|
239
240
|
existing.content !== checksum.content ||
|
|
240
241
|
existing.structure !== checksum.structure
|