@open-mercato/cli 0.4.2-canary-c02407ff85

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.
Files changed (51) hide show
  1. package/bin/mercato +21 -0
  2. package/build.mjs +78 -0
  3. package/dist/bin.js +51 -0
  4. package/dist/bin.js.map +7 -0
  5. package/dist/index.js +5 -0
  6. package/dist/index.js.map +7 -0
  7. package/dist/lib/db/commands.js +350 -0
  8. package/dist/lib/db/commands.js.map +7 -0
  9. package/dist/lib/db/index.js +7 -0
  10. package/dist/lib/db/index.js.map +7 -0
  11. package/dist/lib/generators/entity-ids.js +257 -0
  12. package/dist/lib/generators/entity-ids.js.map +7 -0
  13. package/dist/lib/generators/index.js +12 -0
  14. package/dist/lib/generators/index.js.map +7 -0
  15. package/dist/lib/generators/module-di.js +73 -0
  16. package/dist/lib/generators/module-di.js.map +7 -0
  17. package/dist/lib/generators/module-entities.js +104 -0
  18. package/dist/lib/generators/module-entities.js.map +7 -0
  19. package/dist/lib/generators/module-registry.js +1081 -0
  20. package/dist/lib/generators/module-registry.js.map +7 -0
  21. package/dist/lib/resolver.js +205 -0
  22. package/dist/lib/resolver.js.map +7 -0
  23. package/dist/lib/utils.js +161 -0
  24. package/dist/lib/utils.js.map +7 -0
  25. package/dist/mercato.js +1045 -0
  26. package/dist/mercato.js.map +7 -0
  27. package/dist/registry.js +7 -0
  28. package/dist/registry.js.map +7 -0
  29. package/jest.config.cjs +19 -0
  30. package/package.json +71 -0
  31. package/src/__tests__/mercato.test.ts +90 -0
  32. package/src/bin.ts +74 -0
  33. package/src/index.ts +2 -0
  34. package/src/lib/__tests__/resolver.test.ts +101 -0
  35. package/src/lib/__tests__/utils.test.ts +270 -0
  36. package/src/lib/db/__tests__/commands.test.ts +131 -0
  37. package/src/lib/db/commands.ts +431 -0
  38. package/src/lib/db/index.ts +1 -0
  39. package/src/lib/generators/__tests__/generators.test.ts +197 -0
  40. package/src/lib/generators/entity-ids.ts +336 -0
  41. package/src/lib/generators/index.ts +4 -0
  42. package/src/lib/generators/module-di.ts +89 -0
  43. package/src/lib/generators/module-entities.ts +124 -0
  44. package/src/lib/generators/module-registry.ts +1222 -0
  45. package/src/lib/resolver.ts +308 -0
  46. package/src/lib/utils.ts +200 -0
  47. package/src/mercato.ts +1106 -0
  48. package/src/registry.ts +2 -0
  49. package/tsconfig.build.json +4 -0
  50. package/tsconfig.json +12 -0
  51. package/watch.mjs +6 -0
@@ -0,0 +1,1081 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ calculateChecksum,
5
+ calculateStructureChecksum,
6
+ readChecksumRecord,
7
+ writeChecksumRecord,
8
+ toVar,
9
+ moduleHasExport,
10
+ logGenerationResult,
11
+ createGeneratorResult
12
+ } from "../utils.js";
13
+ async function generateModuleRegistry(options) {
14
+ const { resolver, quiet = false } = options;
15
+ const result = createGeneratorResult();
16
+ const outputDir = resolver.getOutputDir();
17
+ const outFile = path.join(outputDir, "modules.generated.ts");
18
+ const checksumFile = path.join(outputDir, "modules.generated.checksum");
19
+ const widgetsOutFile = path.join(outputDir, "dashboard-widgets.generated.ts");
20
+ const widgetsChecksumFile = path.join(outputDir, "dashboard-widgets.generated.checksum");
21
+ const injectionWidgetsOutFile = path.join(outputDir, "injection-widgets.generated.ts");
22
+ const injectionWidgetsChecksumFile = path.join(outputDir, "injection-widgets.generated.checksum");
23
+ const injectionTablesOutFile = path.join(outputDir, "injection-tables.generated.ts");
24
+ const injectionTablesChecksumFile = path.join(outputDir, "injection-tables.generated.checksum");
25
+ const searchOutFile = path.join(outputDir, "search.generated.ts");
26
+ const searchChecksumFile = path.join(outputDir, "search.generated.checksum");
27
+ const enabled = resolver.loadEnabledModules();
28
+ const imports = [];
29
+ const moduleDecls = [];
30
+ let importId = 0;
31
+ const trackedRoots = /* @__PURE__ */ new Set();
32
+ const requiresByModule = /* @__PURE__ */ new Map();
33
+ const allDashboardWidgets = /* @__PURE__ */ new Map();
34
+ const allInjectionWidgets = /* @__PURE__ */ new Map();
35
+ const allInjectionTables = [];
36
+ const searchConfigs = [];
37
+ const searchImports = [];
38
+ for (const entry of enabled) {
39
+ const modId = entry.id;
40
+ const roots = resolver.getModulePaths(entry);
41
+ const imps = resolver.getModuleImportBase(entry);
42
+ trackedRoots.add(roots.appBase);
43
+ trackedRoots.add(roots.pkgBase);
44
+ const isAppModule = entry.from === "@app";
45
+ const appImportBase = isAppModule ? `../../src/modules/${modId}` : imps.appBase;
46
+ const frontendRoutes = [];
47
+ const backendRoutes = [];
48
+ const apis = [];
49
+ let cliImportName = null;
50
+ const translations = [];
51
+ const subscribers = [];
52
+ const workers = [];
53
+ let infoImportName = null;
54
+ let extensionsImportName = null;
55
+ let fieldsImportName = null;
56
+ let featuresImportName = null;
57
+ let customEntitiesImportName = null;
58
+ let searchImportName = null;
59
+ let customFieldSetsExpr = "[]";
60
+ const dashboardWidgets = [];
61
+ const injectionWidgets = [];
62
+ let injectionTableImportName = null;
63
+ const appIndex = path.join(roots.appBase, "index.ts");
64
+ const pkgIndex = path.join(roots.pkgBase, "index.ts");
65
+ const indexTs = fs.existsSync(appIndex) ? appIndex : fs.existsSync(pkgIndex) ? pkgIndex : null;
66
+ if (indexTs) {
67
+ infoImportName = `I${importId++}_${toVar(modId)}`;
68
+ const importPath = indexTs.startsWith(roots.appBase) ? `${appImportBase}/index` : `${imps.pkgBase}/index`;
69
+ imports.push(`import * as ${infoImportName} from '${importPath}'`);
70
+ try {
71
+ const mod = require(indexTs);
72
+ const reqs = mod?.metadata && Array.isArray(mod.metadata.requires) ? mod.metadata.requires : void 0;
73
+ if (reqs && reqs.length) requiresByModule.set(modId, reqs);
74
+ } catch {
75
+ }
76
+ }
77
+ const feApp = path.join(roots.appBase, "frontend");
78
+ const fePkg = path.join(roots.pkgBase, "frontend");
79
+ if (fs.existsSync(feApp) || fs.existsSync(fePkg)) {
80
+ const found = [];
81
+ const walk = (dir, rel = []) => {
82
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
83
+ if (e.isDirectory()) {
84
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
85
+ walk(path.join(dir, e.name), [...rel, e.name]);
86
+ } else if (e.isFile() && e.name.endsWith(".tsx")) found.push([...rel, e.name].join("/"));
87
+ }
88
+ };
89
+ if (fs.existsSync(fePkg)) walk(fePkg);
90
+ if (fs.existsSync(feApp)) walk(feApp);
91
+ let files = Array.from(new Set(found));
92
+ const isDynamic = (p) => /\/(\[|\[\[\.\.\.)/.test(p) || /^\[/.test(p);
93
+ files.sort((a, b) => {
94
+ const ad = isDynamic(a) ? 1 : 0;
95
+ const bd = isDynamic(b) ? 1 : 0;
96
+ if (ad !== bd) return ad - bd;
97
+ return a.localeCompare(b);
98
+ });
99
+ for (const rel of files.filter((f) => f.endsWith("/page.tsx") || f === "page.tsx")) {
100
+ const segs = rel.split("/");
101
+ segs.pop();
102
+ const importName = `C${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
103
+ const pageModName = `CM${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
104
+ const appFile = path.join(feApp, ...segs, "page.tsx");
105
+ const fromApp = fs.existsSync(appFile);
106
+ const sub = segs.length ? `${segs.join("/")}/page` : "page";
107
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/frontend/${sub}`;
108
+ const routePath = "/" + (segs.join("/") || "");
109
+ const metaCandidates = [
110
+ path.join(fromApp ? feApp : fePkg, ...segs, "page.meta.ts"),
111
+ path.join(fromApp ? feApp : fePkg, ...segs, "meta.ts")
112
+ ];
113
+ const metaPath = metaCandidates.find((p) => fs.existsSync(p));
114
+ let metaExpr = "undefined";
115
+ if (metaPath) {
116
+ const metaImportName = `M${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
117
+ const metaImportPath = `${fromApp ? appImportBase : imps.pkgBase}/frontend/${[...segs, path.basename(metaPath).replace(/\.ts$/, "")].join("/")}`;
118
+ imports.push(`import * as ${metaImportName} from '${metaImportPath}'`);
119
+ metaExpr = `(${metaImportName}.metadata as any)`;
120
+ imports.push(`import ${importName} from '${importPath}'`);
121
+ } else {
122
+ metaExpr = `(${pageModName} as any).metadata`;
123
+ imports.push(`import ${importName}, * as ${pageModName} from '${importPath}'`);
124
+ }
125
+ frontendRoutes.push(
126
+ `{ pattern: '${routePath || "/"}', requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, 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, Component: ${importName} }`
127
+ );
128
+ }
129
+ for (const rel of files.filter((f) => !f.endsWith("/page.tsx") && f !== "page.tsx")) {
130
+ const segs = rel.split("/");
131
+ const file = segs.pop();
132
+ const name = file.replace(/\.tsx$/, "");
133
+ const routeSegs = [...segs, name].filter(Boolean);
134
+ const importName = `C${importId++}_${toVar(modId)}_${toVar(routeSegs.join("_") || "index")}`;
135
+ const pageModName = `CM${importId++}_${toVar(modId)}_${toVar(routeSegs.join("_") || "index")}`;
136
+ const appFile = path.join(feApp, ...segs, `${name}.tsx`);
137
+ const fromApp = fs.existsSync(appFile);
138
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/frontend/${[...segs, name].join("/")}`;
139
+ const routePath = "/" + (routeSegs.join("/") || "");
140
+ const metaCandidates = [
141
+ path.join(fromApp ? feApp : fePkg, ...segs, name + ".meta.ts"),
142
+ path.join(fromApp ? feApp : fePkg, ...segs, "meta.ts")
143
+ ];
144
+ const metaPath = metaCandidates.find((p) => fs.existsSync(p));
145
+ let metaExpr = "undefined";
146
+ if (metaPath) {
147
+ const metaImportName = `M${importId++}_${toVar(modId)}_${toVar(routeSegs.join("_") || "index")}`;
148
+ const metaBase = path.basename(metaPath);
149
+ const metaImportSub = metaBase === "meta.ts" ? "meta" : name + ".meta";
150
+ const metaImportPath = `${fromApp ? appImportBase : imps.pkgBase}/frontend/${[...segs, metaImportSub].join("/")}`;
151
+ imports.push(`import * as ${metaImportName} from '${metaImportPath}'`);
152
+ metaExpr = `(${metaImportName}.metadata as any)`;
153
+ imports.push(`import ${importName} from '${importPath}'`);
154
+ } else {
155
+ metaExpr = `(${pageModName} as any).metadata`;
156
+ imports.push(`import ${importName}, * as ${pageModName} from '${importPath}'`);
157
+ }
158
+ frontendRoutes.push(
159
+ `{ pattern: '${routePath || "/"}', requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, title: (${metaExpr})?.pageTitle ?? (${metaExpr})?.title, titleKey: (${metaExpr})?.pageTitleKey ?? (${metaExpr})?.titleKey, group: (${metaExpr})?.pageGroup ?? (${metaExpr})?.group, groupKey: (${metaExpr})?.pageGroupKey ?? (${metaExpr})?.groupKey, visible: (${metaExpr})?.visible, enabled: (${metaExpr})?.enabled, Component: ${importName} }`
160
+ );
161
+ }
162
+ }
163
+ {
164
+ const appFile = path.join(roots.appBase, "data", "extensions.ts");
165
+ const pkgFile = path.join(roots.pkgBase, "data", "extensions.ts");
166
+ const hasApp = fs.existsSync(appFile);
167
+ const hasPkg = fs.existsSync(pkgFile);
168
+ if (hasApp || hasPkg) {
169
+ const importName = `X_${toVar(modId)}_${importId++}`;
170
+ const importPath = hasApp ? `${appImportBase}/data/extensions` : `${imps.pkgBase}/data/extensions`;
171
+ imports.push(`import * as ${importName} from '${importPath}'`);
172
+ extensionsImportName = importName;
173
+ }
174
+ }
175
+ {
176
+ const rootApp = path.join(roots.appBase, "acl.ts");
177
+ const rootPkg = path.join(roots.pkgBase, "acl.ts");
178
+ const hasRoot = fs.existsSync(rootApp) || fs.existsSync(rootPkg);
179
+ if (hasRoot) {
180
+ const importName = `ACL_${toVar(modId)}_${importId++}`;
181
+ const useApp = fs.existsSync(rootApp) ? rootApp : rootPkg;
182
+ const importPath = useApp.startsWith(roots.appBase) ? `${appImportBase}/acl` : `${imps.pkgBase}/acl`;
183
+ imports.push(`import * as ${importName} from '${importPath}'`);
184
+ featuresImportName = importName;
185
+ }
186
+ }
187
+ {
188
+ const appFile = path.join(roots.appBase, "ce.ts");
189
+ const pkgFile = path.join(roots.pkgBase, "ce.ts");
190
+ const hasApp = fs.existsSync(appFile);
191
+ const hasPkg = fs.existsSync(pkgFile);
192
+ if (hasApp || hasPkg) {
193
+ const importName = `CE_${toVar(modId)}_${importId++}`;
194
+ const importPath = hasApp ? `${appImportBase}/ce` : `${imps.pkgBase}/ce`;
195
+ imports.push(`import * as ${importName} from '${importPath}'`);
196
+ customEntitiesImportName = importName;
197
+ }
198
+ }
199
+ {
200
+ const appFile = path.join(roots.appBase, "search.ts");
201
+ const pkgFile = path.join(roots.pkgBase, "search.ts");
202
+ const hasApp = fs.existsSync(appFile);
203
+ const hasPkg = fs.existsSync(pkgFile);
204
+ if (hasApp || hasPkg) {
205
+ const importName = `SEARCH_${toVar(modId)}_${importId++}`;
206
+ const importPath = hasApp ? `${appImportBase}/search` : `${imps.pkgBase}/search`;
207
+ const importStmt = `import * as ${importName} from '${importPath}'`;
208
+ imports.push(importStmt);
209
+ searchImports.push(importStmt);
210
+ searchImportName = importName;
211
+ }
212
+ }
213
+ {
214
+ const appFile = path.join(roots.appBase, "data", "fields.ts");
215
+ const pkgFile = path.join(roots.pkgBase, "data", "fields.ts");
216
+ const hasApp = fs.existsSync(appFile);
217
+ const hasPkg = fs.existsSync(pkgFile);
218
+ if (hasApp || hasPkg) {
219
+ const importName = `F_${toVar(modId)}_${importId++}`;
220
+ const importPath = hasApp ? `${appImportBase}/data/fields` : `${imps.pkgBase}/data/fields`;
221
+ imports.push(`import * as ${importName} from '${importPath}'`);
222
+ fieldsImportName = importName;
223
+ }
224
+ }
225
+ const beApp = path.join(roots.appBase, "backend");
226
+ const bePkg = path.join(roots.pkgBase, "backend");
227
+ if (fs.existsSync(beApp) || fs.existsSync(bePkg)) {
228
+ const found = [];
229
+ const walk = (dir, rel = []) => {
230
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
231
+ if (e.isDirectory()) {
232
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
233
+ walk(path.join(dir, e.name), [...rel, e.name]);
234
+ } else if (e.isFile() && e.name.endsWith(".tsx")) found.push([...rel, e.name].join("/"));
235
+ }
236
+ };
237
+ if (fs.existsSync(bePkg)) walk(bePkg);
238
+ if (fs.existsSync(beApp)) walk(beApp);
239
+ let files = Array.from(new Set(found));
240
+ const isDynamic = (p) => /\/(\[|\[\[\.\.\.)/.test(p) || /^\[/.test(p);
241
+ files.sort((a, b) => {
242
+ const ad = isDynamic(a) ? 1 : 0;
243
+ const bd = isDynamic(b) ? 1 : 0;
244
+ if (ad !== bd) return ad - bd;
245
+ return a.localeCompare(b);
246
+ });
247
+ for (const rel of files.filter((f) => f.endsWith("/page.tsx") || f === "page.tsx")) {
248
+ const segs = rel.split("/");
249
+ segs.pop();
250
+ const importName = `B${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
251
+ const pageModName = `BM${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
252
+ const appFile = path.join(beApp, ...segs, "page.tsx");
253
+ const fromApp = fs.existsSync(appFile);
254
+ const sub = segs.length ? `${segs.join("/")}/page` : "page";
255
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/backend/${sub}`;
256
+ const basePath = segs.join("/") || modId;
257
+ const routePath = "/backend/" + basePath;
258
+ const metaCandidates = [
259
+ path.join(fromApp ? beApp : bePkg, ...segs, "page.meta.ts"),
260
+ path.join(fromApp ? beApp : bePkg, ...segs, "meta.ts")
261
+ ];
262
+ const metaPath = metaCandidates.find((p) => fs.existsSync(p));
263
+ let metaExpr = "undefined";
264
+ if (metaPath) {
265
+ const metaImportName = `BM${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
266
+ const metaImportPath = `${fromApp ? appImportBase : imps.pkgBase}/backend/${[...segs, path.basename(metaPath).replace(/\.ts$/, "")].join("/")}`;
267
+ imports.push(`import * as ${metaImportName} from '${metaImportPath}'`);
268
+ metaExpr = `(${metaImportName}.metadata as any)`;
269
+ imports.push(`import ${importName} from '${importPath}'`);
270
+ } else {
271
+ metaExpr = `(${pageModName} as any).metadata`;
272
+ imports.push(`import ${importName}, * as ${pageModName} from '${importPath}'`);
273
+ }
274
+ backendRoutes.push(
275
+ `{ pattern: '${routePath}', requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, 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, Component: ${importName} }`
276
+ );
277
+ }
278
+ for (const rel of files.filter((f) => !f.endsWith("/page.tsx") && f !== "page.tsx")) {
279
+ const segs = rel.split("/");
280
+ const file = segs.pop();
281
+ const name = file.replace(/\.tsx$/, "");
282
+ const importName = `B${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
283
+ const pageModName = `BM${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
284
+ const appFile = path.join(beApp, ...segs, `${name}.tsx`);
285
+ const fromApp = fs.existsSync(appFile);
286
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/backend/${[...segs, name].join("/")}`;
287
+ const routePath = "/backend/" + [modId, ...segs, name].filter(Boolean).join("/");
288
+ const metaCandidates = [
289
+ path.join(fromApp ? beApp : bePkg, ...segs, name + ".meta.ts"),
290
+ path.join(fromApp ? beApp : bePkg, ...segs, "meta.ts")
291
+ ];
292
+ const metaPath = metaCandidates.find((p) => fs.existsSync(p));
293
+ let metaExpr = "undefined";
294
+ if (metaPath) {
295
+ const metaImportName = `BM${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
296
+ const metaBase = path.basename(metaPath);
297
+ const metaImportSub = metaBase === "meta.ts" ? "meta" : name + ".meta";
298
+ const metaImportPath = `${fromApp ? appImportBase : imps.pkgBase}/backend/${[...segs, metaImportSub].join("/")}`;
299
+ imports.push(`import * as ${metaImportName} from '${metaImportPath}'`);
300
+ metaExpr = `${metaImportName}.metadata`;
301
+ imports.push(`import ${importName} from '${importPath}'`);
302
+ } else {
303
+ metaExpr = `(${pageModName} as any).metadata`;
304
+ imports.push(`import ${importName}, * as ${pageModName} from '${importPath}'`);
305
+ }
306
+ backendRoutes.push(
307
+ `{ pattern: '${routePath}', requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, 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, Component: ${importName} }`
308
+ );
309
+ }
310
+ }
311
+ const apiApp = path.join(roots.appBase, "api");
312
+ const apiPkg = path.join(roots.pkgBase, "api");
313
+ if (fs.existsSync(apiApp) || fs.existsSync(apiPkg)) {
314
+ const routeFiles = [];
315
+ const walk = (dir, rel = []) => {
316
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
317
+ if (e.isDirectory()) {
318
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
319
+ walk(path.join(dir, e.name), [...rel, e.name]);
320
+ } else if (e.isFile() && e.name === "route.ts") routeFiles.push([...rel, e.name].join("/"));
321
+ }
322
+ };
323
+ if (fs.existsSync(apiPkg)) walk(apiPkg);
324
+ if (fs.existsSync(apiApp)) walk(apiApp);
325
+ const routeList = Array.from(new Set(routeFiles));
326
+ const isDynamicRoute = (p) => p.split("/").some((seg) => /\[|\[\[\.\.\./.test(seg));
327
+ routeList.sort((a, b) => {
328
+ const ad = isDynamicRoute(a) ? 1 : 0;
329
+ const bd = isDynamicRoute(b) ? 1 : 0;
330
+ if (ad !== bd) return ad - bd;
331
+ return a.localeCompare(b);
332
+ });
333
+ for (const rel of routeList) {
334
+ const segs = rel.split("/");
335
+ segs.pop();
336
+ const reqSegs = [modId, ...segs];
337
+ const importName = `R${importId++}_${toVar(modId)}_${toVar(segs.join("_") || "index")}`;
338
+ const appFile = path.join(apiApp, ...segs, "route.ts");
339
+ const fromApp = fs.existsSync(appFile);
340
+ const apiSegPath = segs.join("/");
341
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/api${apiSegPath ? `/${apiSegPath}` : ""}/route`;
342
+ const routePath = "/" + reqSegs.filter(Boolean).join("/");
343
+ const sourceFile = fromApp ? appFile : path.join(apiPkg, ...segs, "route.ts");
344
+ const hasOpenApi = await moduleHasExport(sourceFile, "openApi");
345
+ const docsPart = hasOpenApi ? `, docs: ${importName}.openApi` : "";
346
+ imports.push(`import * as ${importName} from '${importPath}'`);
347
+ apis.push(`{ path: '${routePath}', metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`);
348
+ }
349
+ const plainFiles = [];
350
+ const methodNames = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
351
+ const walkPlain = (dir, rel = []) => {
352
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
353
+ if (e.isDirectory()) {
354
+ if (methodNames.has(e.name.toLowerCase())) continue;
355
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
356
+ walkPlain(path.join(dir, e.name), [...rel, e.name]);
357
+ } else if (e.isFile() && e.name.endsWith(".ts") && e.name !== "route.ts") {
358
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
359
+ plainFiles.push([...rel, e.name].join("/"));
360
+ }
361
+ }
362
+ };
363
+ if (fs.existsSync(apiPkg)) walkPlain(apiPkg);
364
+ if (fs.existsSync(apiApp)) walkPlain(apiApp);
365
+ const plainList = Array.from(new Set(plainFiles));
366
+ for (const rel of plainList) {
367
+ const segs = rel.split("/");
368
+ const file = segs.pop();
369
+ const pathWithoutExt = file.replace(/\.ts$/, "");
370
+ const fullSegs = [...segs, pathWithoutExt];
371
+ const routePath = "/" + [modId, ...fullSegs].filter(Boolean).join("/");
372
+ const importName = `R${importId++}_${toVar(modId)}_${toVar(fullSegs.join("_") || "index")}`;
373
+ const appFile = path.join(apiApp, ...fullSegs) + ".ts";
374
+ const fromApp = fs.existsSync(appFile);
375
+ const plainSegPath = fullSegs.join("/");
376
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/api${plainSegPath ? `/${plainSegPath}` : ""}`;
377
+ const pkgFile = path.join(apiPkg, ...fullSegs) + ".ts";
378
+ const sourceFile = fromApp ? appFile : pkgFile;
379
+ const hasOpenApi = await moduleHasExport(sourceFile, "openApi");
380
+ const docsPart = hasOpenApi ? `, docs: ${importName}.openApi` : "";
381
+ imports.push(`import * as ${importName} from '${importPath}'`);
382
+ apis.push(`{ path: '${routePath}', metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`);
383
+ }
384
+ const methods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
385
+ for (const method of methods) {
386
+ const coreMethodDir = path.join(apiPkg, method.toLowerCase());
387
+ const appMethodDir = path.join(apiApp, method.toLowerCase());
388
+ const methodDir = fs.existsSync(appMethodDir) ? appMethodDir : coreMethodDir;
389
+ if (!fs.existsSync(methodDir)) continue;
390
+ const apiFiles = [];
391
+ const walk2 = (dir, rel = []) => {
392
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
393
+ if (e.isDirectory()) {
394
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
395
+ walk2(path.join(dir, e.name), [...rel, e.name]);
396
+ } else if (e.isFile() && e.name.endsWith(".ts")) {
397
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
398
+ apiFiles.push([...rel, e.name].join("/"));
399
+ }
400
+ }
401
+ };
402
+ walk2(methodDir);
403
+ const methodList = Array.from(new Set(apiFiles));
404
+ for (const rel of methodList) {
405
+ const segs = rel.split("/");
406
+ const file = segs.pop();
407
+ const pathWithoutExt = file.replace(/\.ts$/, "");
408
+ const fullSegs = [...segs, pathWithoutExt];
409
+ const routePath = "/" + [modId, ...fullSegs].filter(Boolean).join("/");
410
+ const importName = `H${importId++}_${toVar(modId)}_${toVar(method)}_${toVar(fullSegs.join("_"))}`;
411
+ const fromApp = methodDir === appMethodDir;
412
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/api/${method.toLowerCase()}/${fullSegs.join("/")}`;
413
+ const metaName = `RM${importId++}_${toVar(modId)}_${toVar(method)}_${toVar(fullSegs.join("_"))}`;
414
+ const sourceFile = path.join(methodDir, ...segs, file);
415
+ const hasOpenApi = await moduleHasExport(sourceFile, "openApi");
416
+ const docsPart = hasOpenApi ? `, docs: ${metaName}.openApi` : "";
417
+ imports.push(`import ${importName}, * as ${metaName} from '${importPath}'`);
418
+ apis.push(`{ method: '${method}', path: '${routePath}', handler: ${importName}, metadata: ${metaName}.metadata${docsPart} }`);
419
+ }
420
+ }
421
+ }
422
+ const cliApp = path.join(roots.appBase, "cli.ts");
423
+ const cliPkg = path.join(roots.pkgBase, "cli.ts");
424
+ const cliPath = fs.existsSync(cliApp) ? cliApp : fs.existsSync(cliPkg) ? cliPkg : null;
425
+ if (cliPath) {
426
+ const importName = `CLI_${toVar(modId)}`;
427
+ const importPath = cliPath.startsWith(roots.appBase) ? `${appImportBase}/cli` : `${imps.pkgBase}/cli`;
428
+ imports.push(`import ${importName} from '${importPath}'`);
429
+ cliImportName = importName;
430
+ }
431
+ const i18nApp = path.join(roots.appBase, "i18n");
432
+ const i18nCore = path.join(roots.pkgBase, "i18n");
433
+ const locales = /* @__PURE__ */ new Set();
434
+ if (fs.existsSync(i18nCore)) {
435
+ for (const e of fs.readdirSync(i18nCore, { withFileTypes: true }))
436
+ if (e.isFile() && e.name.endsWith(".json")) locales.add(e.name.replace(/\.json$/, ""));
437
+ }
438
+ if (fs.existsSync(i18nApp)) {
439
+ for (const e of fs.readdirSync(i18nApp, { withFileTypes: true }))
440
+ if (e.isFile() && e.name.endsWith(".json")) locales.add(e.name.replace(/\.json$/, ""));
441
+ }
442
+ for (const locale of locales) {
443
+ const coreHas = fs.existsSync(path.join(i18nCore, `${locale}.json`));
444
+ const appHas = fs.existsSync(path.join(i18nApp, `${locale}.json`));
445
+ if (coreHas && appHas) {
446
+ const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
447
+ const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
448
+ imports.push(`import ${cName} from '${imps.pkgBase}/i18n/${locale}.json'`);
449
+ imports.push(`import ${aName} from '${appImportBase}/i18n/${locale}.json'`);
450
+ translations.push(
451
+ `'${locale}': { ...( ${cName} as unknown as Record<string,string> ), ...( ${aName} as unknown as Record<string,string> ) }`
452
+ );
453
+ } else if (appHas) {
454
+ const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
455
+ imports.push(`import ${aName} from '${appImportBase}/i18n/${locale}.json'`);
456
+ translations.push(`'${locale}': ${aName} as unknown as Record<string,string>`);
457
+ } else if (coreHas) {
458
+ const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
459
+ imports.push(`import ${cName} from '${imps.pkgBase}/i18n/${locale}.json'`);
460
+ translations.push(`'${locale}': ${cName} as unknown as Record<string,string>`);
461
+ }
462
+ }
463
+ const subApp = path.join(roots.appBase, "subscribers");
464
+ const subPkg = path.join(roots.pkgBase, "subscribers");
465
+ if (fs.existsSync(subApp) || fs.existsSync(subPkg)) {
466
+ const found = [];
467
+ const walk = (dir, rel = []) => {
468
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
469
+ if (e.isDirectory()) {
470
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
471
+ walk(path.join(dir, e.name), [...rel, e.name]);
472
+ } else if (e.isFile() && e.name.endsWith(".ts")) {
473
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
474
+ found.push([...rel, e.name].join("/"));
475
+ }
476
+ }
477
+ };
478
+ if (fs.existsSync(subPkg)) walk(subPkg);
479
+ if (fs.existsSync(subApp)) walk(subApp);
480
+ const files = Array.from(new Set(found));
481
+ for (const rel of files) {
482
+ const segs = rel.split("/");
483
+ const file = segs.pop();
484
+ const name = file.replace(/\.ts$/, "");
485
+ const importName = `Subscriber${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
486
+ const metaName = `SubscriberMeta${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
487
+ const appFile = path.join(subApp, ...segs, `${name}.ts`);
488
+ const fromApp = fs.existsSync(appFile);
489
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/subscribers/${[...segs, name].join("/")}`;
490
+ imports.push(`import ${importName}, * as ${metaName} from '${importPath}'`);
491
+ const sid = [modId, ...segs, name].filter(Boolean).join(":");
492
+ subscribers.push(
493
+ `{ id: (((${metaName}.metadata) as any)?.id || '${sid}'), event: ((${metaName}.metadata) as any)?.event, persistent: ((${metaName}.metadata) as any)?.persistent, handler: ${importName} }`
494
+ );
495
+ }
496
+ }
497
+ {
498
+ const wrkApp = path.join(roots.appBase, "workers");
499
+ const wrkPkg = path.join(roots.pkgBase, "workers");
500
+ if (fs.existsSync(wrkApp) || fs.existsSync(wrkPkg)) {
501
+ const found = [];
502
+ const walk = (dir, rel = []) => {
503
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
504
+ if (e.isDirectory()) {
505
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
506
+ walk(path.join(dir, e.name), [...rel, e.name]);
507
+ } else if (e.isFile() && e.name.endsWith(".ts")) {
508
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
509
+ found.push([...rel, e.name].join("/"));
510
+ }
511
+ }
512
+ };
513
+ if (fs.existsSync(wrkPkg)) walk(wrkPkg);
514
+ if (fs.existsSync(wrkApp)) walk(wrkApp);
515
+ const files = Array.from(new Set(found));
516
+ for (const rel of files) {
517
+ const segs = rel.split("/");
518
+ const file = segs.pop();
519
+ const name = file.replace(/\.ts$/, "");
520
+ const appFile = path.join(wrkApp, ...segs, `${name}.ts`);
521
+ const fromApp = fs.existsSync(appFile);
522
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/workers/${[...segs, name].join("/")}`;
523
+ if (!await moduleHasExport(importPath, "metadata")) continue;
524
+ const importName = `Worker${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
525
+ const metaName = `WorkerMeta${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
526
+ imports.push(`import ${importName}, * as ${metaName} from '${importPath}'`);
527
+ const wid = [modId, "workers", ...segs, name].filter(Boolean).join(":");
528
+ workers.push(
529
+ `{ id: (${metaName}.metadata as { id?: string })?.id || '${wid}', queue: (${metaName}.metadata as { queue: string }).queue, concurrency: (${metaName}.metadata as { concurrency?: number })?.concurrency ?? 1, handler: ${importName} as (job: unknown, ctx: unknown) => Promise<void> }`
530
+ );
531
+ }
532
+ }
533
+ }
534
+ {
535
+ const parts = [];
536
+ if (fieldsImportName)
537
+ parts.push(`(( ${fieldsImportName}.default ?? ${fieldsImportName}.fieldSets) as any) || []`);
538
+ if (customEntitiesImportName)
539
+ parts.push(
540
+ `((( ${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || []).filter((e: any) => Array.isArray(e.fields) && e.fields.length).map((e: any) => ({ entity: e.id, fields: e.fields, source: '${modId}' }))`
541
+ );
542
+ customFieldSetsExpr = parts.length ? `[...${parts.join(", ...")}]` : "[]";
543
+ }
544
+ {
545
+ const widgetApp = path.join(roots.appBase, "widgets", "dashboard");
546
+ const widgetPkg = path.join(roots.pkgBase, "widgets", "dashboard");
547
+ if (fs.existsSync(widgetApp) || fs.existsSync(widgetPkg)) {
548
+ const found = [];
549
+ const walk = (dir, rel = []) => {
550
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
551
+ if (e.isDirectory()) {
552
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
553
+ walk(path.join(dir, e.name), [...rel, e.name]);
554
+ } else if (e.isFile() && /^widget\.(t|j)sx?$/.test(e.name)) {
555
+ found.push([...rel, e.name].join("/"));
556
+ }
557
+ }
558
+ };
559
+ if (fs.existsSync(widgetPkg)) walk(widgetPkg);
560
+ if (fs.existsSync(widgetApp)) walk(widgetApp);
561
+ const files = Array.from(new Set(found)).sort();
562
+ for (const rel of files) {
563
+ const appFile = path.join(widgetApp, ...rel.split("/"));
564
+ const fromApp = fs.existsSync(appFile);
565
+ const segs = rel.split("/");
566
+ const file = segs.pop();
567
+ const base = file.replace(/\.(t|j)sx?$/, "");
568
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/widgets/dashboard/${[...segs, base].join("/")}`;
569
+ const key = [modId, ...segs, base].filter(Boolean).join(":");
570
+ const source = fromApp ? "app" : "package";
571
+ dashboardWidgets.push(
572
+ `{ moduleId: '${modId}', key: '${key}', source: '${source}', loader: () => import('${importPath}').then((mod) => mod.default ?? mod) }`
573
+ );
574
+ const existing = allDashboardWidgets.get(key);
575
+ if (!existing || existing.source !== "app" && source === "app") {
576
+ allDashboardWidgets.set(key, { moduleId: modId, source, importPath });
577
+ }
578
+ }
579
+ }
580
+ }
581
+ {
582
+ const widgetApp = path.join(roots.appBase, "widgets", "injection");
583
+ const widgetPkg = path.join(roots.pkgBase, "widgets", "injection");
584
+ if (fs.existsSync(widgetApp) || fs.existsSync(widgetPkg)) {
585
+ const found = [];
586
+ const walk = (dir, rel = []) => {
587
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
588
+ if (e.isDirectory()) {
589
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
590
+ walk(path.join(dir, e.name), [...rel, e.name]);
591
+ } else if (e.isFile() && /^widget\.(t|j)sx?$/.test(e.name)) {
592
+ found.push([...rel, e.name].join("/"));
593
+ }
594
+ }
595
+ };
596
+ if (fs.existsSync(widgetPkg)) walk(widgetPkg);
597
+ if (fs.existsSync(widgetApp)) walk(widgetApp);
598
+ const files = Array.from(new Set(found)).sort();
599
+ for (const rel of files) {
600
+ const appFile = path.join(widgetApp, ...rel.split("/"));
601
+ const fromApp = fs.existsSync(appFile);
602
+ const segs = rel.split("/");
603
+ const file = segs.pop();
604
+ const base = file.replace(/\.(t|j)sx?$/, "");
605
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/widgets/injection/${[...segs, base].join("/")}`;
606
+ const key = [modId, ...segs, base].filter(Boolean).join(":");
607
+ const source = fromApp ? "app" : "package";
608
+ injectionWidgets.push(
609
+ `{ moduleId: '${modId}', key: '${key}', source: '${source}', loader: () => import('${importPath}').then((mod) => mod.default ?? mod) }`
610
+ );
611
+ const existing = allInjectionWidgets.get(key);
612
+ if (!existing || existing.source !== "app" && source === "app") {
613
+ allInjectionWidgets.set(key, { moduleId: modId, source, importPath });
614
+ }
615
+ }
616
+ }
617
+ }
618
+ {
619
+ const appFile = path.join(roots.appBase, "widgets", "injection-table.ts");
620
+ const pkgFile = path.join(roots.pkgBase, "widgets", "injection-table.ts");
621
+ const hasApp = fs.existsSync(appFile);
622
+ const hasPkg = fs.existsSync(pkgFile);
623
+ if (hasApp || hasPkg) {
624
+ const importName = `InjTable_${toVar(modId)}_${importId++}`;
625
+ const importPath = hasApp ? `${appImportBase}/widgets/injection-table` : `${imps.pkgBase}/widgets/injection-table`;
626
+ imports.push(`import * as ${importName} from '${importPath}'`);
627
+ injectionTableImportName = importName;
628
+ allInjectionTables.push({ moduleId: modId, importPath, importName });
629
+ }
630
+ }
631
+ if (searchImportName) {
632
+ searchConfigs.push(`{ moduleId: '${modId}', config: (${searchImportName}.default ?? ${searchImportName}.searchConfig ?? ${searchImportName}.config ?? null) }`);
633
+ }
634
+ moduleDecls.push(`{
635
+ id: '${modId}',
636
+ ${infoImportName ? `info: ${infoImportName}.metadata,` : ""}
637
+ ${frontendRoutes.length ? `frontendRoutes: [${frontendRoutes.join(", ")}],` : ""}
638
+ ${backendRoutes.length ? `backendRoutes: [${backendRoutes.join(", ")}],` : ""}
639
+ ${apis.length ? `apis: [${apis.join(", ")}],` : ""}
640
+ ${cliImportName ? `cli: ${cliImportName},` : ""}
641
+ ${translations.length ? `translations: { ${translations.join(", ")} },` : ""}
642
+ ${subscribers.length ? `subscribers: [${subscribers.join(", ")}],` : ""}
643
+ ${workers.length ? `workers: [${workers.join(", ")}],` : ""}
644
+ ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as import('@open-mercato/shared/modules/entities').EntityExtension[]) || [],` : ""}
645
+ customFieldSets: ${customFieldSetsExpr},
646
+ ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ""}
647
+ ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ""}
648
+ ${dashboardWidgets.length ? `dashboardWidgets: [${dashboardWidgets.join(", ")}],` : ""}
649
+ }`);
650
+ }
651
+ const output = `// AUTO-GENERATED by mercato generate registry
652
+ import type { Module } from '@open-mercato/shared/modules/registry'
653
+ ${imports.join("\n")}
654
+
655
+ export const modules: Module[] = [
656
+ ${moduleDecls.join(",\n ")}
657
+ ]
658
+ export const modulesInfo = modules.map(m => ({ id: m.id, ...(m.info || {}) }))
659
+ export default modules
660
+ `;
661
+ const widgetEntriesList = Array.from(allDashboardWidgets.entries()).sort(([a], [b]) => a.localeCompare(b));
662
+ const widgetDecls = widgetEntriesList.map(
663
+ ([key, data]) => ` { moduleId: '${data.moduleId}', key: '${key}', source: '${data.source}', loader: () => import('${data.importPath}').then((mod) => mod.default ?? mod) }`
664
+ );
665
+ const widgetsOutput = `// AUTO-GENERATED by mercato generate registry
666
+ import type { ModuleDashboardWidgetEntry } from '@open-mercato/shared/modules/registry'
667
+
668
+ export const dashboardWidgetEntries: ModuleDashboardWidgetEntry[] = [
669
+ ${widgetDecls.join(",\n")}
670
+ ]
671
+ `;
672
+ const searchEntriesLiteral = searchConfigs.join(",\n ");
673
+ const searchImportSection = searchImports.join("\n");
674
+ const searchOutput = `// AUTO-GENERATED by mercato generate registry
675
+ import type { SearchModuleConfig } from '@open-mercato/shared/modules/search'
676
+ ${searchImportSection ? `
677
+ ${searchImportSection}
678
+ ` : "\n"}type SearchConfigEntry = { moduleId: string; config: SearchModuleConfig | null }
679
+
680
+ const entriesRaw: SearchConfigEntry[] = [
681
+ ${searchEntriesLiteral ? ` ${searchEntriesLiteral}
682
+ ` : ""}]
683
+ const entries = entriesRaw.filter((entry): entry is { moduleId: string; config: SearchModuleConfig } => entry.config != null)
684
+
685
+ export const searchModuleConfigEntries = entries
686
+ export const searchModuleConfigs: SearchModuleConfig[] = entries.map((entry) => entry.config)
687
+ `;
688
+ {
689
+ const enabledIds = new Set(enabled.map((e) => e.id));
690
+ const problems = [];
691
+ for (const [modId, reqs] of requiresByModule.entries()) {
692
+ const missing = reqs.filter((r) => !enabledIds.has(r));
693
+ if (missing.length) {
694
+ problems.push(`- Module "${modId}" requires: ${missing.join(", ")}`);
695
+ }
696
+ }
697
+ if (problems.length) {
698
+ console.error("\nModule dependency check failed:");
699
+ for (const p of problems) console.error(p);
700
+ console.error("\nFix: Enable required module(s) in src/modules.ts. Example:");
701
+ console.error(
702
+ " export const enabledModules = [ { id: '" + Array.from(new Set(requiresByModule.values()).values()).join("' }, { id: '") + "' } ]"
703
+ );
704
+ process.exit(1);
705
+ }
706
+ }
707
+ const structureChecksum = calculateStructureChecksum(Array.from(trackedRoots));
708
+ const modulesChecksum = { content: calculateChecksum(output), structure: structureChecksum };
709
+ const existingModulesChecksum = readChecksumRecord(checksumFile);
710
+ const shouldWriteModules = !existingModulesChecksum || existingModulesChecksum.content !== modulesChecksum.content || existingModulesChecksum.structure !== modulesChecksum.structure;
711
+ if (shouldWriteModules) {
712
+ fs.mkdirSync(path.dirname(outFile), { recursive: true });
713
+ fs.writeFileSync(outFile, output);
714
+ writeChecksumRecord(checksumFile, modulesChecksum);
715
+ result.filesWritten.push(outFile);
716
+ } else {
717
+ result.filesUnchanged.push(outFile);
718
+ }
719
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), outFile), shouldWriteModules);
720
+ const widgetsChecksum = { content: calculateChecksum(widgetsOutput), structure: structureChecksum };
721
+ const existingWidgetsChecksum = readChecksumRecord(widgetsChecksumFile);
722
+ const shouldWriteWidgets = !existingWidgetsChecksum || existingWidgetsChecksum.content !== widgetsChecksum.content || existingWidgetsChecksum.structure !== widgetsChecksum.structure;
723
+ if (shouldWriteWidgets) {
724
+ fs.writeFileSync(widgetsOutFile, widgetsOutput);
725
+ writeChecksumRecord(widgetsChecksumFile, widgetsChecksum);
726
+ result.filesWritten.push(widgetsOutFile);
727
+ } else {
728
+ result.filesUnchanged.push(widgetsOutFile);
729
+ }
730
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), widgetsOutFile), shouldWriteWidgets);
731
+ const injectionWidgetEntriesList = Array.from(allInjectionWidgets.entries()).sort(([a], [b]) => a.localeCompare(b));
732
+ const injectionWidgetDecls = injectionWidgetEntriesList.map(
733
+ ([key, data]) => ` { moduleId: '${data.moduleId}', key: '${key}', source: '${data.source}', loader: () => import('${data.importPath}').then((mod) => mod.default ?? mod) }`
734
+ );
735
+ const injectionWidgetsOutput = `// AUTO-GENERATED by mercato generate registry
736
+ import type { ModuleInjectionWidgetEntry } from '@open-mercato/shared/modules/registry'
737
+
738
+ export const injectionWidgetEntries: ModuleInjectionWidgetEntry[] = [
739
+ ${injectionWidgetDecls.join(",\n")}
740
+ ]
741
+ `;
742
+ const injectionTableImports = allInjectionTables.map(
743
+ (entry) => `import * as ${entry.importName} from '${entry.importPath}'`
744
+ );
745
+ const injectionTableDecls = allInjectionTables.map(
746
+ (entry) => ` { moduleId: '${entry.moduleId}', table: ((${entry.importName}.default ?? ${entry.importName}.injectionTable) as any) || {} }`
747
+ );
748
+ const injectionTablesOutput = `// AUTO-GENERATED by mercato generate registry
749
+ import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
750
+ ${injectionTableImports.join("\n")}
751
+
752
+ export const injectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> = [
753
+ ${injectionTableDecls.join(",\n")}
754
+ ]
755
+ `;
756
+ const injectionWidgetsChecksum = { content: calculateChecksum(injectionWidgetsOutput), structure: structureChecksum };
757
+ const existingInjectionWidgetsChecksum = readChecksumRecord(injectionWidgetsChecksumFile);
758
+ const shouldWriteInjectionWidgets = !existingInjectionWidgetsChecksum || existingInjectionWidgetsChecksum.content !== injectionWidgetsChecksum.content || existingInjectionWidgetsChecksum.structure !== injectionWidgetsChecksum.structure;
759
+ if (shouldWriteInjectionWidgets) {
760
+ fs.writeFileSync(injectionWidgetsOutFile, injectionWidgetsOutput);
761
+ writeChecksumRecord(injectionWidgetsChecksumFile, injectionWidgetsChecksum);
762
+ result.filesWritten.push(injectionWidgetsOutFile);
763
+ } else {
764
+ result.filesUnchanged.push(injectionWidgetsOutFile);
765
+ }
766
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), injectionWidgetsOutFile), shouldWriteInjectionWidgets);
767
+ const injectionTablesChecksum = { content: calculateChecksum(injectionTablesOutput), structure: structureChecksum };
768
+ const existingInjectionTablesChecksum = readChecksumRecord(injectionTablesChecksumFile);
769
+ const shouldWriteInjectionTables = !existingInjectionTablesChecksum || existingInjectionTablesChecksum.content !== injectionTablesChecksum.content || existingInjectionTablesChecksum.structure !== injectionTablesChecksum.structure;
770
+ if (shouldWriteInjectionTables) {
771
+ fs.writeFileSync(injectionTablesOutFile, injectionTablesOutput);
772
+ writeChecksumRecord(injectionTablesChecksumFile, injectionTablesChecksum);
773
+ result.filesWritten.push(injectionTablesOutFile);
774
+ } else {
775
+ result.filesUnchanged.push(injectionTablesOutFile);
776
+ }
777
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), injectionTablesOutFile), shouldWriteInjectionTables);
778
+ const searchChecksum = { content: calculateChecksum(searchOutput), structure: structureChecksum };
779
+ const existingSearchChecksum = readChecksumRecord(searchChecksumFile);
780
+ const shouldWriteSearch = !existingSearchChecksum || existingSearchChecksum.content !== searchChecksum.content || existingSearchChecksum.structure !== searchChecksum.structure;
781
+ if (shouldWriteSearch) {
782
+ fs.writeFileSync(searchOutFile, searchOutput);
783
+ writeChecksumRecord(searchChecksumFile, searchChecksum);
784
+ result.filesWritten.push(searchOutFile);
785
+ } else {
786
+ result.filesUnchanged.push(searchOutFile);
787
+ }
788
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), searchOutFile), shouldWriteSearch);
789
+ return result;
790
+ }
791
+ async function generateModuleRegistryCli(options) {
792
+ const { resolver, quiet = false } = options;
793
+ const result = createGeneratorResult();
794
+ const outputDir = resolver.getOutputDir();
795
+ const outFile = path.join(outputDir, "modules.cli.generated.ts");
796
+ const checksumFile = path.join(outputDir, "modules.cli.generated.checksum");
797
+ const enabled = resolver.loadEnabledModules();
798
+ const imports = [];
799
+ const moduleDecls = [];
800
+ let importId = 0;
801
+ const trackedRoots = /* @__PURE__ */ new Set();
802
+ const requiresByModule = /* @__PURE__ */ new Map();
803
+ for (const entry of enabled) {
804
+ const modId = entry.id;
805
+ const roots = resolver.getModulePaths(entry);
806
+ const imps = resolver.getModuleImportBase(entry);
807
+ trackedRoots.add(roots.appBase);
808
+ trackedRoots.add(roots.pkgBase);
809
+ const isAppModule = entry.from === "@app";
810
+ const appImportBase = isAppModule ? `../../src/modules/${modId}` : imps.appBase;
811
+ let cliImportName = null;
812
+ const translations = [];
813
+ const subscribers = [];
814
+ const workers = [];
815
+ let infoImportName = null;
816
+ let extensionsImportName = null;
817
+ let fieldsImportName = null;
818
+ let featuresImportName = null;
819
+ let customEntitiesImportName = null;
820
+ let vectorImportName = null;
821
+ let customFieldSetsExpr = "[]";
822
+ const appIndex = path.join(roots.appBase, "index.ts");
823
+ const pkgIndex = path.join(roots.pkgBase, "index.ts");
824
+ const indexTs = fs.existsSync(appIndex) ? appIndex : fs.existsSync(pkgIndex) ? pkgIndex : null;
825
+ if (indexTs) {
826
+ infoImportName = `I${importId++}_${toVar(modId)}`;
827
+ const importPath = indexTs.startsWith(roots.appBase) ? `${appImportBase}/index` : `${imps.pkgBase}/index`;
828
+ imports.push(`import * as ${infoImportName} from '${importPath}'`);
829
+ try {
830
+ const mod = require(indexTs);
831
+ const reqs = mod?.metadata && Array.isArray(mod.metadata.requires) ? mod.metadata.requires : void 0;
832
+ if (reqs && reqs.length) requiresByModule.set(modId, reqs);
833
+ } catch {
834
+ }
835
+ }
836
+ {
837
+ const appFile = path.join(roots.appBase, "data", "extensions.ts");
838
+ const pkgFile = path.join(roots.pkgBase, "data", "extensions.ts");
839
+ const hasApp = fs.existsSync(appFile);
840
+ const hasPkg = fs.existsSync(pkgFile);
841
+ if (hasApp || hasPkg) {
842
+ const importName = `X_${toVar(modId)}_${importId++}`;
843
+ const importPath = hasApp ? `${appImportBase}/data/extensions` : `${imps.pkgBase}/data/extensions`;
844
+ imports.push(`import * as ${importName} from '${importPath}'`);
845
+ extensionsImportName = importName;
846
+ }
847
+ }
848
+ {
849
+ const rootApp = path.join(roots.appBase, "acl.ts");
850
+ const rootPkg = path.join(roots.pkgBase, "acl.ts");
851
+ const hasRoot = fs.existsSync(rootApp) || fs.existsSync(rootPkg);
852
+ if (hasRoot) {
853
+ const importName = `ACL_${toVar(modId)}_${importId++}`;
854
+ const useApp = fs.existsSync(rootApp) ? rootApp : rootPkg;
855
+ const importPath = useApp.startsWith(roots.appBase) ? `${appImportBase}/acl` : `${imps.pkgBase}/acl`;
856
+ imports.push(`import * as ${importName} from '${importPath}'`);
857
+ featuresImportName = importName;
858
+ }
859
+ }
860
+ {
861
+ const appFile = path.join(roots.appBase, "ce.ts");
862
+ const pkgFile = path.join(roots.pkgBase, "ce.ts");
863
+ const hasApp = fs.existsSync(appFile);
864
+ const hasPkg = fs.existsSync(pkgFile);
865
+ if (hasApp || hasPkg) {
866
+ const importName = `CE_${toVar(modId)}_${importId++}`;
867
+ const importPath = hasApp ? `${appImportBase}/ce` : `${imps.pkgBase}/ce`;
868
+ imports.push(`import * as ${importName} from '${importPath}'`);
869
+ customEntitiesImportName = importName;
870
+ }
871
+ }
872
+ {
873
+ const appFile = path.join(roots.appBase, "vector.ts");
874
+ const pkgFile = path.join(roots.pkgBase, "vector.ts");
875
+ const hasApp = fs.existsSync(appFile);
876
+ const hasPkg = fs.existsSync(pkgFile);
877
+ if (hasApp || hasPkg) {
878
+ const importName = `VECTOR_${toVar(modId)}_${importId++}`;
879
+ const importPath = hasApp ? `${appImportBase}/vector` : `${imps.pkgBase}/vector`;
880
+ imports.push(`import * as ${importName} from '${importPath}'`);
881
+ vectorImportName = importName;
882
+ }
883
+ }
884
+ {
885
+ const appFile = path.join(roots.appBase, "data", "fields.ts");
886
+ const pkgFile = path.join(roots.pkgBase, "data", "fields.ts");
887
+ const hasApp = fs.existsSync(appFile);
888
+ const hasPkg = fs.existsSync(pkgFile);
889
+ if (hasApp || hasPkg) {
890
+ const importName = `F_${toVar(modId)}_${importId++}`;
891
+ const importPath = hasApp ? `${appImportBase}/data/fields` : `${imps.pkgBase}/data/fields`;
892
+ imports.push(`import * as ${importName} from '${importPath}'`);
893
+ fieldsImportName = importName;
894
+ }
895
+ }
896
+ const cliApp = path.join(roots.appBase, "cli.ts");
897
+ const cliPkg = path.join(roots.pkgBase, "cli.ts");
898
+ const cliPath = fs.existsSync(cliApp) ? cliApp : fs.existsSync(cliPkg) ? cliPkg : null;
899
+ if (cliPath) {
900
+ const importName = `CLI_${toVar(modId)}`;
901
+ const importPath = cliPath.startsWith(roots.appBase) ? `${appImportBase}/cli` : `${imps.pkgBase}/cli`;
902
+ imports.push(`import ${importName} from '${importPath}'`);
903
+ cliImportName = importName;
904
+ }
905
+ const i18nApp = path.join(roots.appBase, "i18n");
906
+ const i18nCore = path.join(roots.pkgBase, "i18n");
907
+ const locales = /* @__PURE__ */ new Set();
908
+ if (fs.existsSync(i18nCore)) {
909
+ for (const e of fs.readdirSync(i18nCore, { withFileTypes: true }))
910
+ if (e.isFile() && e.name.endsWith(".json")) locales.add(e.name.replace(/\.json$/, ""));
911
+ }
912
+ if (fs.existsSync(i18nApp)) {
913
+ for (const e of fs.readdirSync(i18nApp, { withFileTypes: true }))
914
+ if (e.isFile() && e.name.endsWith(".json")) locales.add(e.name.replace(/\.json$/, ""));
915
+ }
916
+ for (const locale of locales) {
917
+ const coreHas = fs.existsSync(path.join(i18nCore, `${locale}.json`));
918
+ const appHas = fs.existsSync(path.join(i18nApp, `${locale}.json`));
919
+ if (coreHas && appHas) {
920
+ const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
921
+ const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
922
+ imports.push(`import ${cName} from '${imps.pkgBase}/i18n/${locale}.json'`);
923
+ imports.push(`import ${aName} from '${appImportBase}/i18n/${locale}.json'`);
924
+ translations.push(
925
+ `'${locale}': { ...( ${cName} as unknown as Record<string,string> ), ...( ${aName} as unknown as Record<string,string> ) }`
926
+ );
927
+ } else if (appHas) {
928
+ const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
929
+ imports.push(`import ${aName} from '${appImportBase}/i18n/${locale}.json'`);
930
+ translations.push(`'${locale}': ${aName} as unknown as Record<string,string>`);
931
+ } else if (coreHas) {
932
+ const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
933
+ imports.push(`import ${cName} from '${imps.pkgBase}/i18n/${locale}.json'`);
934
+ translations.push(`'${locale}': ${cName} as unknown as Record<string,string>`);
935
+ }
936
+ }
937
+ const subApp = path.join(roots.appBase, "subscribers");
938
+ const subPkg = path.join(roots.pkgBase, "subscribers");
939
+ if (fs.existsSync(subApp) || fs.existsSync(subPkg)) {
940
+ const found = [];
941
+ const walk = (dir, rel = []) => {
942
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
943
+ if (e.isDirectory()) {
944
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
945
+ walk(path.join(dir, e.name), [...rel, e.name]);
946
+ } else if (e.isFile() && e.name.endsWith(".ts")) {
947
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
948
+ found.push([...rel, e.name].join("/"));
949
+ }
950
+ }
951
+ };
952
+ if (fs.existsSync(subPkg)) walk(subPkg);
953
+ if (fs.existsSync(subApp)) walk(subApp);
954
+ const files = Array.from(new Set(found));
955
+ for (const rel of files) {
956
+ const segs = rel.split("/");
957
+ const file = segs.pop();
958
+ const name = file.replace(/\.ts$/, "");
959
+ const importName = `Subscriber${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
960
+ const metaName = `SubscriberMeta${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
961
+ const appFile = path.join(subApp, ...segs, `${name}.ts`);
962
+ const fromApp = fs.existsSync(appFile);
963
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/subscribers/${[...segs, name].join("/")}`;
964
+ imports.push(`import ${importName}, * as ${metaName} from '${importPath}'`);
965
+ const sid = [modId, ...segs, name].filter(Boolean).join(":");
966
+ subscribers.push(
967
+ `{ id: (((${metaName}.metadata) as any)?.id || '${sid}'), event: ((${metaName}.metadata) as any)?.event, persistent: ((${metaName}.metadata) as any)?.persistent, handler: ${importName} }`
968
+ );
969
+ }
970
+ }
971
+ {
972
+ const wrkApp = path.join(roots.appBase, "workers");
973
+ const wrkPkg = path.join(roots.pkgBase, "workers");
974
+ if (fs.existsSync(wrkApp) || fs.existsSync(wrkPkg)) {
975
+ const found = [];
976
+ const walk = (dir, rel = []) => {
977
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
978
+ if (e.isDirectory()) {
979
+ if (e.name === "__tests__" || e.name === "__mocks__") continue;
980
+ walk(path.join(dir, e.name), [...rel, e.name]);
981
+ } else if (e.isFile() && e.name.endsWith(".ts")) {
982
+ if (/\.(test|spec)\.ts$/.test(e.name)) continue;
983
+ found.push([...rel, e.name].join("/"));
984
+ }
985
+ }
986
+ };
987
+ if (fs.existsSync(wrkPkg)) walk(wrkPkg);
988
+ if (fs.existsSync(wrkApp)) walk(wrkApp);
989
+ const files = Array.from(new Set(found));
990
+ for (const rel of files) {
991
+ const segs = rel.split("/");
992
+ const file = segs.pop();
993
+ const name = file.replace(/\.ts$/, "");
994
+ const appFile = path.join(wrkApp, ...segs, `${name}.ts`);
995
+ const fromApp = fs.existsSync(appFile);
996
+ const importPath = `${fromApp ? appImportBase : imps.pkgBase}/workers/${[...segs, name].join("/")}`;
997
+ if (!await moduleHasExport(importPath, "metadata")) continue;
998
+ const importName = `Worker${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
999
+ const metaName = `WorkerMeta${importId++}_${toVar(modId)}_${toVar([...segs, name].join("_") || "index")}`;
1000
+ imports.push(`import ${importName}, * as ${metaName} from '${importPath}'`);
1001
+ const wid = [modId, "workers", ...segs, name].filter(Boolean).join(":");
1002
+ workers.push(
1003
+ `{ id: (${metaName}.metadata as { id?: string })?.id || '${wid}', queue: (${metaName}.metadata as { queue: string }).queue, concurrency: (${metaName}.metadata as { concurrency?: number })?.concurrency ?? 1, handler: ${importName} as (job: unknown, ctx: unknown) => Promise<void> }`
1004
+ );
1005
+ }
1006
+ }
1007
+ }
1008
+ {
1009
+ const parts = [];
1010
+ if (fieldsImportName)
1011
+ parts.push(`(( ${fieldsImportName}.default ?? ${fieldsImportName}.fieldSets) as any) || []`);
1012
+ if (customEntitiesImportName)
1013
+ parts.push(
1014
+ `((( ${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || []).filter((e: any) => Array.isArray(e.fields) && e.fields.length).map((e: any) => ({ entity: e.id, fields: e.fields, source: '${modId}' }))`
1015
+ );
1016
+ customFieldSetsExpr = parts.length ? `[...${parts.join(", ...")}]` : "[]";
1017
+ }
1018
+ moduleDecls.push(`{
1019
+ id: '${modId}',
1020
+ ${infoImportName ? `info: ${infoImportName}.metadata,` : ""}
1021
+ ${cliImportName ? `cli: ${cliImportName},` : ""}
1022
+ ${translations.length ? `translations: { ${translations.join(", ")} },` : ""}
1023
+ ${subscribers.length ? `subscribers: [${subscribers.join(", ")}],` : ""}
1024
+ ${workers.length ? `workers: [${workers.join(", ")}],` : ""}
1025
+ ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as any) || [],` : ""}
1026
+ customFieldSets: ${customFieldSetsExpr},
1027
+ ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ""}
1028
+ ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ""}
1029
+ ${vectorImportName ? `vector: (${vectorImportName}.default ?? ${vectorImportName}.vectorConfig ?? ${vectorImportName}.config ?? undefined),` : ""}
1030
+ }`);
1031
+ }
1032
+ const output = `// AUTO-GENERATED by mercato generate registry (CLI version)
1033
+ // This file excludes Next.js dependent code (routes, APIs, widgets)
1034
+ import type { Module } from '@open-mercato/shared/modules/registry'
1035
+ ${imports.join("\n")}
1036
+
1037
+ export const modules: Module[] = [
1038
+ ${moduleDecls.join(",\n ")}
1039
+ ]
1040
+ export const modulesInfo = modules.map(m => ({ id: m.id, ...(m.info || {}) }))
1041
+ export default modules
1042
+ `;
1043
+ {
1044
+ const enabledIds = new Set(enabled.map((e) => e.id));
1045
+ const problems = [];
1046
+ for (const [modId, reqs] of requiresByModule.entries()) {
1047
+ const missing = reqs.filter((r) => !enabledIds.has(r));
1048
+ if (missing.length) {
1049
+ problems.push(`- Module "${modId}" requires: ${missing.join(", ")}`);
1050
+ }
1051
+ }
1052
+ if (problems.length) {
1053
+ console.error("\nModule dependency check failed:");
1054
+ for (const p of problems) console.error(p);
1055
+ console.error("\nFix: Enable required module(s) in src/modules.ts. Example:");
1056
+ console.error(
1057
+ " export const enabledModules = [ { id: '" + Array.from(new Set(requiresByModule.values()).values()).join("' }, { id: '") + "' } ]"
1058
+ );
1059
+ process.exit(1);
1060
+ }
1061
+ }
1062
+ const structureChecksum = calculateStructureChecksum(Array.from(trackedRoots));
1063
+ const checksum = { content: calculateChecksum(output), structure: structureChecksum };
1064
+ const existingChecksum = readChecksumRecord(checksumFile);
1065
+ const shouldWrite = !existingChecksum || existingChecksum.content !== checksum.content || existingChecksum.structure !== checksum.structure;
1066
+ if (shouldWrite) {
1067
+ fs.mkdirSync(path.dirname(outFile), { recursive: true });
1068
+ fs.writeFileSync(outFile, output);
1069
+ writeChecksumRecord(checksumFile, checksum);
1070
+ result.filesWritten.push(outFile);
1071
+ } else {
1072
+ result.filesUnchanged.push(outFile);
1073
+ }
1074
+ if (!quiet) logGenerationResult(path.relative(process.cwd(), outFile), shouldWrite);
1075
+ return result;
1076
+ }
1077
+ export {
1078
+ generateModuleRegistry,
1079
+ generateModuleRegistryCli
1080
+ };
1081
+ //# sourceMappingURL=module-registry.js.map