mikro-orm-markdown 0.1.0-alpha.2 → 0.1.0-alpha.4

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/cli.cjs CHANGED
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
9
13
  var __copyProps = (to, from, except, desc) => {
10
14
  if (from && typeof from === "object" || typeof from === "function") {
11
15
  for (let key of __getOwnPropNames(from))
@@ -22,19 +26,39 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
26
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
27
  mod
24
28
  ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
30
 
26
31
  // src/cli.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ findNearestTsconfig: () => findNearestTsconfig,
35
+ formatDiscoveryError: () => formatDiscoveryError,
36
+ formatErrorChain: () => formatErrorChain,
37
+ loadOrmOptions: () => loadOrmOptions,
38
+ toConfigImportSpecifier: () => toConfigImportSpecifier,
39
+ writeMarkdownFile: () => writeMarkdownFile
40
+ });
41
+ module.exports = __toCommonJS(cli_exports);
42
+
43
+ // node_modules/tsup/assets/cjs_shims.js
44
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
45
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
46
+
47
+ // src/cli.ts
48
+ var syncFs = __toESM(require("fs"), 1);
27
49
  var fs = __toESM(require("fs/promises"), 1);
28
- var path = __toESM(require("path"), 1);
50
+ var path2 = __toESM(require("path"), 1);
51
+ var import_node_url = require("url");
29
52
  var import_commander = require("commander");
30
53
 
31
54
  // src/docs/jsdoc.ts
32
55
  var import_ts_morph = require("ts-morph");
33
- function loadJsDoc(srcGlobs) {
56
+ function loadJsDoc(filePaths) {
34
57
  const entities = /* @__PURE__ */ new Map();
35
58
  const props = /* @__PURE__ */ new Map();
36
- if (srcGlobs.length === 0) {
37
- return { entities, props };
59
+ const classNames = /* @__PURE__ */ new Set();
60
+ if (filePaths.length === 0) {
61
+ return { entities, props, sourceFileCount: 0, classNames };
38
62
  }
39
63
  const project = new import_ts_morph.Project({
40
64
  skipAddingFilesFromTsConfig: true,
@@ -44,34 +68,44 @@ function loadJsDoc(srcGlobs) {
44
68
  skipLibCheck: true
45
69
  }
46
70
  });
47
- project.addSourceFilesAtPaths(srcGlobs);
48
- for (const sourceFile of project.getSourceFiles()) {
49
- for (const cls of sourceFile.getClasses()) {
50
- const className = cls.getName();
51
- if (!className) {
52
- continue;
53
- }
54
- const classDocs = cls.getJsDocs();
55
- if (classDocs.length > 0) {
56
- entities.set(className, parseEntityJsDoc(classDocs));
57
- }
58
- const propMap = /* @__PURE__ */ new Map();
59
- for (const prop of cls.getProperties()) {
60
- const propDocs = prop.getJsDocs();
61
- if (propDocs.length === 0) {
71
+ for (const filePath of filePaths) {
72
+ try {
73
+ project.addSourceFilesAtPaths(filePath);
74
+ } catch {
75
+ }
76
+ }
77
+ const sourceFiles = project.getSourceFiles();
78
+ for (const sourceFile of sourceFiles) {
79
+ try {
80
+ for (const cls of sourceFile.getClasses()) {
81
+ const className = cls.getName();
82
+ if (!className) {
62
83
  continue;
63
84
  }
64
- const desc = extractDescription(propDocs);
65
- if (desc !== void 0) {
66
- propMap.set(prop.getName(), { description: desc });
85
+ classNames.add(className);
86
+ const classDocs = cls.getJsDocs();
87
+ if (classDocs.length > 0) {
88
+ entities.set(className, parseEntityJsDoc(classDocs));
89
+ }
90
+ const propMap = /* @__PURE__ */ new Map();
91
+ for (const prop of cls.getProperties()) {
92
+ const propDocs = prop.getJsDocs();
93
+ if (propDocs.length === 0) {
94
+ continue;
95
+ }
96
+ const info = parsePropJsDoc(propDocs);
97
+ if (info.description !== void 0 || info.atLeastOne) {
98
+ propMap.set(prop.getName(), info);
99
+ }
100
+ }
101
+ if (propMap.size > 0) {
102
+ props.set(className, propMap);
67
103
  }
68
104
  }
69
- if (propMap.size > 0) {
70
- props.set(className, propMap);
71
- }
105
+ } catch {
72
106
  }
73
107
  }
74
- return { entities, props };
108
+ return { entities, props, sourceFileCount: sourceFiles.length, classNames };
75
109
  }
76
110
  function parseEntityJsDoc(jsDocs) {
77
111
  const namespaces = [];
@@ -106,17 +140,25 @@ function parseEntityJsDoc(jsDocs) {
106
140
  hidden
107
141
  };
108
142
  }
109
- function extractDescription(jsDocs) {
143
+ function parsePropJsDoc(jsDocs) {
144
+ let description;
145
+ let atLeastOne = false;
110
146
  for (const doc of jsDocs) {
111
147
  const desc = doc.getDescription().trim();
112
- if (desc) {
113
- return desc;
148
+ if (desc && description === void 0) {
149
+ description = desc;
150
+ }
151
+ for (const tag of doc.getTags()) {
152
+ if (tag.getTagName() === "atLeastOne") {
153
+ atLeastOne = true;
154
+ }
114
155
  }
115
156
  }
116
- return void 0;
157
+ return { ...description !== void 0 && { description }, atLeastOne };
117
158
  }
118
159
 
119
160
  // src/metadata/load.ts
161
+ var path = __toESM(require("path"), 1);
120
162
  var import_core = require("@mikro-orm/core");
121
163
  var MetadataLoadError = class extends Error {
122
164
  constructor(message, cause) {
@@ -125,13 +167,46 @@ var MetadataLoadError = class extends Error {
125
167
  this.name = "MetadataLoadError";
126
168
  }
127
169
  };
170
+ async function closeDiscoveryResources(orm) {
171
+ await orm.config.getMetadataCacheAdapter()?.close?.();
172
+ await orm.config.getResultCacheAdapter()?.close?.();
173
+ }
174
+ function collectEntitySchemaNames(options) {
175
+ const configuredEntities = [...options.entities ?? [], ...options.entitiesTs ?? []];
176
+ const names = [];
177
+ for (const entity of configuredEntities) {
178
+ if (entity instanceof import_core.EntitySchema) {
179
+ names.push(entity.meta.className);
180
+ continue;
181
+ }
182
+ if (entity !== null && typeof entity === "object" && "schema" in entity && entity.schema instanceof import_core.EntitySchema) {
183
+ names.push(entity.schema.meta.className);
184
+ }
185
+ }
186
+ return names;
187
+ }
188
+ function assertNoEntitySchemaEntities(options) {
189
+ const schemaNames = collectEntitySchemaNames(options);
190
+ if (schemaNames.length === 0) {
191
+ return;
192
+ }
193
+ throw new MetadataLoadError(
194
+ `EntitySchema-defined entities are not currently supported: ${schemaNames.join(", ")}.
195
+ Use decorator-based @Entity() classes instead.`
196
+ );
197
+ }
128
198
  async function loadEntityMetadata(options) {
199
+ assertNoEntitySchemaEntities(options);
129
200
  let orm;
130
201
  try {
131
202
  orm = await import_core.MikroORM.init({
132
203
  ...options,
133
204
  debug: false,
134
- connect: false
205
+ connect: false,
206
+ // Always disable the metadata cache for one-shot doc runs so the project
207
+ // is never littered with a temp/ folder, regardless of how metadataProvider
208
+ // was configured.
209
+ metadataCache: { ...options.metadataCache, enabled: false }
135
210
  });
136
211
  } catch (cause) {
137
212
  throw new MetadataLoadError(
@@ -139,23 +214,80 @@ async function loadEntityMetadata(options) {
139
214
  cause
140
215
  );
141
216
  }
142
- const all = Object.values(orm.getMetadata().getAll());
143
- if (all.length === 0) {
144
- throw new MetadataLoadError(
145
- "No entities were discovered. Check that your config specifies at least one entity path or class."
146
- );
217
+ try {
218
+ const all = Object.values(orm.getMetadata().getAll());
219
+ if (all.length === 0) {
220
+ throw new MetadataLoadError(
221
+ "No entities were discovered. Check that your config specifies at least one entity path or class."
222
+ );
223
+ }
224
+ const baseDir = orm.config.get("baseDir");
225
+ const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
226
+ return { metas: all, sourcePaths };
227
+ } finally {
228
+ await closeDiscoveryResources(orm);
147
229
  }
148
- return all;
149
230
  }
150
231
 
232
+ // src/model/build.ts
233
+ var import_core3 = require("@mikro-orm/core");
234
+
151
235
  // src/render/mermaid.ts
152
236
  var import_core2 = require("@mikro-orm/core");
237
+
238
+ // src/render/escape.ts
239
+ var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
240
+ var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
241
+ function normalizeInlineText(value) {
242
+ return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
243
+ }
244
+ function splitNormalizedLines(value) {
245
+ return value.replace(/\r\n?/g, "\n").split("\n");
246
+ }
247
+ function escapeHtmlText(value) {
248
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
249
+ }
250
+ function escapeMarkdownInline(value) {
251
+ return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
252
+ }
253
+ function escapeMarkdownTableCell(value) {
254
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
255
+ }
256
+ function escapeMarkdownParagraph(value) {
257
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
258
+ }
259
+ function renderMarkdownBlockQuote(value) {
260
+ return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
261
+ }
262
+ function renderMarkdownInlineCode(value) {
263
+ const normalized = normalizeInlineText(value);
264
+ const backtickRuns = normalized.match(/`+/g) ?? [];
265
+ const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
266
+ const fence = "`".repeat(longestRun + 1);
267
+ const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
268
+ const content = needsPadding ? ` ${normalized} ` : normalized;
269
+ return `${fence}${content}${fence}`;
270
+ }
271
+ function toMermaidIdentifier(value) {
272
+ const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
273
+ const identifier = normalized === "" ? "_" : normalized;
274
+ return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
275
+ }
276
+ function escapeMermaidQuotedText(value) {
277
+ return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
278
+ }
279
+ function toMarkdownAnchor(value) {
280
+ return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
281
+ }
282
+
283
+ // src/render/mermaid.ts
153
284
  var FORMULA_DUMMY_TABLE = {
154
285
  alias: "e0",
155
286
  name: "",
156
287
  qualifiedName: "",
157
288
  toString: () => "e0"
158
289
  };
290
+ var UNRESOLVED_FORMULA = "<unresolved>";
159
291
  function buildDiagramModel(metas) {
160
292
  const metaByClass = new Map(metas.map((m) => [m.className, m]));
161
293
  const entities = metas.filter((meta) => !meta.pivotTable && !meta.embeddable).map((meta) => buildEntityModel(meta, metaByClass));
@@ -163,17 +295,14 @@ function buildDiagramModel(metas) {
163
295
  return { entities, relations };
164
296
  }
165
297
  function buildEntityModel(meta, metaByClass) {
166
- const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.discriminatorValue;
298
+ const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.extends;
167
299
  const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
168
300
  const columns = [];
169
301
  for (const prop of Object.values(meta.properties)) {
170
302
  if (isStiRoot && prop.inherited === true) {
171
303
  continue;
172
304
  }
173
- const col = buildColumn(prop, metaByClass, meta);
174
- if (col !== null) {
175
- columns.push(col);
176
- }
305
+ columns.push(...buildColumns(prop, metaByClass, meta));
177
306
  }
178
307
  return {
179
308
  className: meta.className,
@@ -183,47 +312,73 @@ function buildEntityModel(meta, metaByClass) {
183
312
  isEmbeddable: meta.embeddable === true,
184
313
  ...isStiRoot && { discriminatorColumn: meta.discriminatorColumn },
185
314
  ...isStiChild && { extendsEntity: meta.extends },
315
+ ...isStiChild && meta.discriminatorValue !== void 0 && { discriminatorValue: String(meta.discriminatorValue) },
186
316
  constraints: buildConstraints(meta)
187
317
  };
188
318
  }
189
- function buildColumn(prop, metaByClass, owningMeta) {
319
+ function buildColumns(prop, metaByClass, owningMeta) {
190
320
  if (prop.kind === import_core2.ReferenceKind.EMBEDDED) {
191
- return null;
321
+ if (prop.object === true || prop.array === true) {
322
+ return [
323
+ {
324
+ propName: prop.name,
325
+ fieldName: prop.fieldNames?.[0] ?? prop.name,
326
+ type: "json",
327
+ isPrimary: false,
328
+ isForeignKey: false,
329
+ isUnique: prop.unique === true,
330
+ isNullable: prop.nullable === true,
331
+ ...prop.comment !== void 0 && { comment: prop.comment },
332
+ embeddedIn: prop.array === true ? `${prop.type}[]` : prop.type
333
+ }
334
+ ];
335
+ }
336
+ return [];
192
337
  }
193
338
  if (prop.kind === import_core2.ReferenceKind.SCALAR) {
339
+ if (prop.object === true && prop.embedded !== void 0) {
340
+ return [];
341
+ }
194
342
  const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
195
343
  let embeddedIn;
344
+ let embeddedPropName;
196
345
  if (prop.embedded !== void 0) {
197
346
  const parentPropName = prop.embedded[0];
198
347
  embeddedIn = owningMeta.properties[parentPropName]?.type;
348
+ embeddedPropName = prop.embedded[1];
199
349
  }
200
350
  const isDiscriminator = owningMeta.discriminatorColumn !== void 0 && prop.name === owningMeta.discriminatorColumn;
201
- return {
202
- propName: prop.name,
203
- fieldName: prop.fieldNames?.[0] ?? prop.name,
204
- type: normalizeType(prop.type),
205
- isPrimary: prop.primary === true,
206
- isForeignKey: false,
207
- isUnique: prop.unique === true,
208
- isNullable: prop.nullable === true,
209
- ...formulaExpr !== void 0 && { formula: formulaExpr },
210
- ...embeddedIn !== void 0 && { embeddedIn },
211
- ...isDiscriminator && { isDiscriminator: true }
212
- };
351
+ const enumItems = prop.enum === true && Array.isArray(prop.items) && prop.items.length > 0 ? prop.items.map((item) => String(item)) : void 0;
352
+ return [
353
+ {
354
+ propName: prop.name,
355
+ fieldName: prop.fieldNames?.[0] ?? prop.name,
356
+ // Store the original type (e.g. `varchar(255)`); the Mermaid renderer
357
+ // sanitizes it for diagram identifiers, while the markdown table shows
358
+ // it verbatim. Guard against a missing type so downstream string
359
+ // handling never sees undefined (matches the FK path's defaulting).
360
+ type: prop.type ?? "unknown",
361
+ isPrimary: prop.primary === true,
362
+ isForeignKey: false,
363
+ isUnique: prop.unique === true,
364
+ isNullable: prop.nullable === true,
365
+ ...prop.comment !== void 0 && { comment: prop.comment },
366
+ ...formulaExpr !== void 0 && { formula: formulaExpr },
367
+ ...embeddedIn !== void 0 && { embeddedIn },
368
+ ...embeddedPropName !== void 0 && { embeddedPropName },
369
+ ...isDiscriminator && { isDiscriminator: true },
370
+ ...enumItems !== void 0 && { enumItems }
371
+ }
372
+ ];
213
373
  }
214
374
  if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE || prop.kind === import_core2.ReferenceKind.ONE_TO_ONE && prop.owner === true) {
215
- const fkType = resolveFkType(prop.type, metaByClass);
216
- return {
217
- propName: prop.name,
218
- fieldName: prop.fieldNames?.[0] ?? `${prop.name}_id`,
219
- type: fkType,
220
- isPrimary: false,
221
- isForeignKey: true,
222
- isUnique: prop.unique === true,
223
- isNullable: prop.nullable === true
224
- };
375
+ const cols = buildForeignKeyColumns(prop, metaByClass);
376
+ if (prop.type === owningMeta.className) {
377
+ return cols.map((col) => ({ ...col, isSelfReference: true }));
378
+ }
379
+ return cols;
225
380
  }
226
- return null;
381
+ return [];
227
382
  }
228
383
  function resolveFormulaExpr(cb) {
229
384
  try {
@@ -233,18 +388,69 @@ function resolveFormulaExpr(cb) {
233
388
  get: (_target, key) => typeof key === "string" ? key : ""
234
389
  }
235
390
  );
236
- return cb(FORMULA_DUMMY_TABLE, cols);
391
+ const result = cb(FORMULA_DUMMY_TABLE, cols);
392
+ return typeof result === "string" ? result : String(result);
237
393
  } catch {
238
- return "";
394
+ return UNRESOLVED_FORMULA;
239
395
  }
240
396
  }
241
- function resolveFkType(referencedClassName, metaByClass) {
242
- const refMeta = metaByClass.get(referencedClassName);
397
+ function buildForeignKeyColumns(prop, metaByClass) {
398
+ const fieldNames = prop.fieldNames && prop.fieldNames.length > 0 ? prop.fieldNames : [`${prop.name}_id`];
399
+ const fkTypes = resolveFkTypes(prop, metaByClass, fieldNames.length);
400
+ return fieldNames.map((fieldName, index) => ({
401
+ propName: prop.name,
402
+ fieldName,
403
+ type: fkTypes[index] ?? fkTypes[0] ?? "integer",
404
+ isPrimary: prop.primary === true,
405
+ isForeignKey: true,
406
+ isUnique: prop.unique === true,
407
+ isNullable: prop.nullable === true,
408
+ ...prop.comment !== void 0 && { comment: prop.comment },
409
+ referencedEntity: prop.type
410
+ }));
411
+ }
412
+ function resolveFkTypes(prop, metaByClass, fieldNameCount) {
413
+ const refMeta = metaByClass.get(prop.type);
243
414
  if (!refMeta) {
415
+ return Array.from({ length: fieldNameCount }, () => "integer");
416
+ }
417
+ const primaryProps = getPrimaryProps(refMeta);
418
+ const referencedColumnNames = prop.referencedColumnNames ?? [];
419
+ return Array.from({ length: fieldNameCount }, (_value, index) => {
420
+ const referencedColumnName = referencedColumnNames[index];
421
+ const pkProp = referencedColumnName !== void 0 ? primaryProps.find((candidate) => candidate.fieldNames.includes(referencedColumnName)) : void 0;
422
+ const resolvedProp = pkProp ?? primaryProps[index] ?? primaryProps[0];
423
+ const rawType = resolvedProp?.type ?? "integer";
424
+ const pkIndex = resolvedProp !== void 0 ? primaryProps.indexOf(resolvedProp) : 0;
425
+ return resolveScalarType(rawType, metaByClass, pkIndex);
426
+ });
427
+ }
428
+ function resolveScalarType(type, metaByClass, pkIndex = 0, depth = 0) {
429
+ if (depth >= 5) {
244
430
  return "integer";
245
431
  }
246
- const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);
247
- return pkProp ? normalizeType(pkProp.type) : "integer";
432
+ const refMeta = metaByClass.get(type);
433
+ if (!refMeta) {
434
+ return type;
435
+ }
436
+ const primaryProps = getPrimaryProps(refMeta);
437
+ if (primaryProps.length === 0) {
438
+ return type;
439
+ }
440
+ const targetProp = primaryProps[pkIndex] ?? primaryProps[0];
441
+ const nextType = targetProp?.type ?? type;
442
+ if (nextType === type) {
443
+ return type;
444
+ }
445
+ return resolveScalarType(nextType, metaByClass, pkIndex, depth + 1);
446
+ }
447
+ function getPrimaryProps(meta) {
448
+ const primaryKeys = meta.primaryKeys ?? [];
449
+ const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
450
+ if (orderedPrimaryProps.length > 0) {
451
+ return orderedPrimaryProps;
452
+ }
453
+ return Object.values(meta.properties).filter((prop) => prop.primary === true);
248
454
  }
249
455
  function buildConstraints(meta) {
250
456
  const result = [];
@@ -252,7 +458,7 @@ function buildConstraints(meta) {
252
458
  const props = idx.properties;
253
459
  result.push({
254
460
  type: "index",
255
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
461
+ properties: resolveConstraintProperties(meta, props),
256
462
  ...idx.name !== void 0 && { name: idx.name }
257
463
  });
258
464
  }
@@ -260,7 +466,7 @@ function buildConstraints(meta) {
260
466
  const props = uniq.properties;
261
467
  result.push({
262
468
  type: "unique",
263
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
469
+ properties: resolveConstraintProperties(meta, props),
264
470
  ...uniq.name !== void 0 && { name: uniq.name }
265
471
  });
266
472
  }
@@ -277,6 +483,19 @@ function buildConstraints(meta) {
277
483
  }
278
484
  return result;
279
485
  }
486
+ function resolveConstraintProperties(meta, props) {
487
+ const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
488
+ return propNames.flatMap((propName) => {
489
+ const prop = meta.properties[String(propName)];
490
+ if (prop === void 0) {
491
+ return [String(propName)];
492
+ }
493
+ if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
494
+ return prop.fieldNames;
495
+ }
496
+ return [prop.name];
497
+ });
498
+ }
280
499
  function buildRelationEdges(metas) {
281
500
  const edges = [];
282
501
  for (const meta of metas) {
@@ -295,6 +514,9 @@ function buildRelationEdges(metas) {
295
514
  function buildEdge(fromEntity, prop) {
296
515
  const isNullable = prop.nullable === true;
297
516
  if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE) {
517
+ if (prop.type === fromEntity) {
518
+ return null;
519
+ }
298
520
  return {
299
521
  fromEntity,
300
522
  toEntity: prop.type,
@@ -323,17 +545,41 @@ function buildEdge(fromEntity, prop) {
323
545
  }
324
546
  return null;
325
547
  }
548
+ function normalizeType(type) {
549
+ const t = type.toLowerCase().trim();
550
+ if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
551
+ return "string";
552
+ }
553
+ if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
554
+ return "datetime";
555
+ }
556
+ if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
557
+ return "integer";
558
+ }
559
+ if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
560
+ return "float";
561
+ }
562
+ if (t === "boolean" || t === "bool") {
563
+ return "boolean";
564
+ }
565
+ if (t === "jsonb") {
566
+ return "json";
567
+ }
568
+ return type;
569
+ }
326
570
  function renderErDiagram(model) {
327
571
  const lines = ["erDiagram"];
328
572
  for (const entity of model.entities) {
329
- lines.push(` ${entity.className} {`);
573
+ lines.push(` ${toMermaidIdentifier(entity.className)} {`);
330
574
  for (const col of entity.columns) {
331
575
  lines.push(` ${renderColumnLine(col)}`);
332
576
  }
333
577
  lines.push(" }");
334
578
  }
335
579
  for (const rel of model.relations) {
336
- lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`);
580
+ lines.push(
581
+ ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
582
+ );
337
583
  }
338
584
  return lines.join("\n");
339
585
  }
@@ -341,8 +587,6 @@ function renderColumnLine(col) {
341
587
  let qualifier = "";
342
588
  if (col.isPrimary) {
343
589
  qualifier = " PK";
344
- } else if (col.isForeignKey) {
345
- qualifier = " FK";
346
590
  } else if (col.isUnique) {
347
591
  qualifier = " UK";
348
592
  }
@@ -353,27 +597,38 @@ function renderColumnLine(col) {
353
597
  comment = "discriminator";
354
598
  } else if (col.embeddedIn !== void 0) {
355
599
  comment = `[${col.embeddedIn}]`;
356
- } else if (col.fieldName !== col.propName) {
357
- comment = col.propName;
600
+ } else if (col.isSelfReference) {
601
+ comment = "self-ref";
358
602
  }
359
- const commentStr = comment !== void 0 ? ` "${comment}"` : "";
360
- return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;
361
- }
362
- function normalizeType(type) {
363
- return type.replace(/[^a-zA-Z0-9_]/g, "_");
603
+ const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
604
+ return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
364
605
  }
365
606
 
366
607
  // src/model/build.ts
367
- function buildDocumentModel(metas, jsDocResult, title, description) {
368
- const { entities: diagramEntities, relations: allRelations } = buildDiagramModel(metas);
608
+ var FROM_ONE_OR_MORE = "}|";
609
+ var TO_ONE_OR_MORE = "|{";
610
+ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
611
+ const { entities: diagramEntities, relations } = buildDiagramModel(metas);
612
+ const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
613
+ const hiddenClasses = /* @__PURE__ */ new Set();
614
+ for (const model of diagramEntities) {
615
+ if (jsDocResult.entities.get(model.className)?.hidden) {
616
+ hiddenClasses.add(model.className);
617
+ }
618
+ }
369
619
  const enrichedByClass = /* @__PURE__ */ new Map();
370
620
  for (const model of diagramEntities) {
371
621
  const jsDoc = jsDocResult.entities.get(model.className);
372
622
  if (jsDoc?.hidden) {
373
623
  continue;
374
624
  }
375
- const propDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
376
- enrichedByClass.set(model.className, { model, jsDoc, propDocs });
625
+ const columns = model.columns.filter(
626
+ (col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
627
+ );
628
+ const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
629
+ const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
630
+ const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
631
+ enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
377
632
  }
378
633
  const groupNames = /* @__PURE__ */ new Set();
379
634
  let anyUntagged = false;
@@ -393,9 +648,16 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
393
648
  const groups = [];
394
649
  for (const groupName of groupNames) {
395
650
  const isDefault = groupName === "default";
396
- const erdEntities = [...enrichedByClass.values()].filter(
397
- ({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)
398
- );
651
+ const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)).map((entity) => {
652
+ if (isCrossNamespaceInGroup(entity.jsDoc, groupName, isDefault)) {
653
+ const pkColumns = entity.model.columns.filter((col) => col.isPrimary);
654
+ if (pkColumns.length === 0) {
655
+ return null;
656
+ }
657
+ return { ...entity, model: { ...entity.model, columns: pkColumns } };
658
+ }
659
+ return entity;
660
+ }).filter((entity) => entity !== null);
399
661
  const textEntities = [...enrichedByClass.values()].filter(
400
662
  ({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
401
663
  );
@@ -414,6 +676,65 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
414
676
  });
415
677
  return { title, groups, ...description !== void 0 && { description } };
416
678
  }
679
+ function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
680
+ const merged = new Map(ownPropDocs);
681
+ for (const col of columns) {
682
+ if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
683
+ continue;
684
+ }
685
+ const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
686
+ if (info) {
687
+ merged.set(col.propName, info);
688
+ }
689
+ }
690
+ return merged;
691
+ }
692
+ function applyAtLeastOne(relations, metas, props, onWarn) {
693
+ const adjusted = relations.map((edge) => ({ ...edge }));
694
+ const metaByClass = new Map(metas.map((m) => [m.className, m]));
695
+ for (const [className, propMap] of props) {
696
+ const meta = metaByClass.get(className);
697
+ if (!meta) {
698
+ continue;
699
+ }
700
+ for (const [propName, info] of propMap) {
701
+ if (!info.atLeastOne) {
702
+ continue;
703
+ }
704
+ const prop = meta.properties[propName];
705
+ if (!prop) {
706
+ continue;
707
+ }
708
+ let edge;
709
+ if (prop.kind === import_core3.ReferenceKind.ONE_TO_MANY && prop.mappedBy) {
710
+ edge = adjusted.find(
711
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
712
+ );
713
+ if (edge) {
714
+ edge.fromCardinality = FROM_ONE_OR_MORE;
715
+ }
716
+ } else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.owner === true) {
717
+ edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
718
+ if (edge) {
719
+ edge.toCardinality = TO_ONE_OR_MORE;
720
+ }
721
+ } else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.mappedBy) {
722
+ edge = adjusted.find(
723
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
724
+ );
725
+ if (edge) {
726
+ edge.fromCardinality = FROM_ONE_OR_MORE;
727
+ }
728
+ }
729
+ if (!edge) {
730
+ onWarn?.(
731
+ `@atLeastOne on ${className}.${propName} had no effect: no matching relation edge was found. It applies only to collection relations that can be matched to a rendered edge: @OneToMany with mappedBy, or @ManyToMany on either the owning side or an inverse mappedBy side.`
732
+ );
733
+ }
734
+ }
735
+ }
736
+ return adjusted;
737
+ }
417
738
  function hasNoNamespaceTags(jsDoc) {
418
739
  if (!jsDoc) {
419
740
  return true;
@@ -438,20 +759,62 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
438
759
  }
439
760
  return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
440
761
  }
762
+ function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
763
+ if (isDefault || !jsDoc) {
764
+ return false;
765
+ }
766
+ return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
767
+ }
768
+
769
+ // src/provider.ts
770
+ async function withTsMorphMetadataProvider(options, onWarn) {
771
+ if (options.metadataProvider !== void 0) {
772
+ return options;
773
+ }
774
+ try {
775
+ const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
776
+ return { ...options, metadataProvider: TsMorphMetadataProvider };
777
+ } catch (err) {
778
+ const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
779
+ const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
780
+ if (!isNotInstalled && onWarn) {
781
+ onWarn(
782
+ `@mikro-orm/reflection is installed but failed to load: ${err instanceof Error ? err.message : String(err)}. Ensure all @mikro-orm/* packages are installed at the same version.`
783
+ );
784
+ }
785
+ return options;
786
+ }
787
+ }
441
788
 
442
789
  // src/render/markdown.ts
443
790
  function renderMarkdown(docModel) {
444
- const sections = [`# ${docModel.title}`];
791
+ const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
445
792
  if (docModel.description) {
446
- sections.push(docModel.description);
793
+ sections.push(escapeMarkdownParagraph(docModel.description));
794
+ }
795
+ if (docModel.groups.length > 1) {
796
+ sections.push(renderTableOfContents(docModel.groups));
447
797
  }
448
798
  for (const group of docModel.groups) {
449
799
  sections.push(renderGroupSection(group));
450
800
  }
451
801
  return sections.join("\n\n");
452
802
  }
803
+ function renderBulletSection(header, items, renderItem) {
804
+ const lines = [header, ""];
805
+ for (const item of items) {
806
+ lines.push(renderItem(item));
807
+ }
808
+ return lines.join("\n");
809
+ }
810
+ function renderTableOfContents(groups) {
811
+ return renderBulletSection("## Contents", groups, (group) => {
812
+ const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
813
+ return `- [${label}](#${toMarkdownAnchor(group.name)})`;
814
+ });
815
+ }
453
816
  function renderGroupSection(group) {
454
- const parts = [`## ${group.name}`];
817
+ const parts = [`## ${escapeMarkdownInline(group.name)}`];
455
818
  if (group.erdEntities.length > 0) {
456
819
  const diagramModel = {
457
820
  entities: group.erdEntities.map((e) => e.model),
@@ -465,14 +828,18 @@ function renderGroupSection(group) {
465
828
  return parts.join("\n\n");
466
829
  }
467
830
  function renderEntitySection(entity) {
468
- const parts = [`### ${entity.model.className}`];
831
+ const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
832
+ parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
469
833
  if (entity.jsDoc?.description) {
470
- parts.push(`> ${entity.jsDoc.description}`);
834
+ parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
471
835
  }
472
836
  if (entity.model.discriminatorColumn) {
473
- parts.push(`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`);
837
+ parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
474
838
  } else if (entity.model.extendsEntity) {
475
- parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
839
+ const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
840
+ parts.push(
841
+ `*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
842
+ );
476
843
  }
477
844
  if (entity.model.columns.length > 0) {
478
845
  parts.push(renderColumnTable(entity));
@@ -480,6 +847,10 @@ function renderEntitySection(entity) {
480
847
  if (entity.model.constraints.length > 0) {
481
848
  parts.push(renderConstraints(entity.model.constraints));
482
849
  }
850
+ const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
851
+ if (computedColumns.length > 0) {
852
+ parts.push(renderComputedColumns(computedColumns));
853
+ }
483
854
  return parts.join("\n\n");
484
855
  }
485
856
  function renderColumnTable(entity) {
@@ -488,24 +859,27 @@ function renderColumnTable(entity) {
488
859
  const rows = entity.model.columns.map((col) => {
489
860
  const key = resolveColumnKey(col);
490
861
  const nullable = col.isNullable && !col.isPrimary ? "Y" : "";
491
- const desc = entity.propDocs.get(col.propName)?.description ?? "";
492
- return `| ${col.fieldName} | ${col.type} | ${key} | ${nullable} | ${desc} |`;
862
+ const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
863
+ const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
864
+ const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
865
+ return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(normalizeType(col.type))} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
493
866
  });
494
867
  return [header, sep, ...rows].join("\n");
495
868
  }
496
869
  function resolveColumnKey(col) {
870
+ const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
871
+ if (col.isPrimary && col.isForeignKey) {
872
+ return `PK, ${fkKey}`;
873
+ }
497
874
  if (col.isPrimary) {
498
875
  return "PK";
499
876
  }
500
877
  if (col.isForeignKey) {
501
- return col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
878
+ return fkKey;
502
879
  }
503
880
  if (col.isUnique) {
504
881
  return "UK";
505
882
  }
506
- if (col.formula !== void 0) {
507
- return `formula: ${col.formula}`;
508
- }
509
883
  if (col.isDiscriminator) {
510
884
  return "discriminator";
511
885
  }
@@ -514,43 +888,227 @@ function resolveColumnKey(col) {
514
888
  }
515
889
  return "";
516
890
  }
891
+ function renderComputedColumns(columns) {
892
+ return renderBulletSection("**Computed columns:**", columns, (col) => {
893
+ const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
894
+ return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
895
+ });
896
+ }
517
897
  function renderConstraints(constraints) {
518
- const lines = ["**Constraints:**", ""];
519
- for (const c of constraints) {
520
- const name = c.name ? ` \`${c.name}\`` : "";
898
+ return renderBulletSection("**Constraints:**", constraints, (c) => {
899
+ const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
900
+ const properties = c.properties.map(escapeMarkdownInline).join(", ");
521
901
  if (c.type === "index") {
522
- lines.push(`- Index${name}: (${c.properties.join(", ")})`);
523
- } else if (c.type === "unique") {
524
- lines.push(`- Unique${name}: (${c.properties.join(", ")})`);
525
- } else if (c.type === "check") {
526
- lines.push(`- Check${name}: \`${c.expression ?? ""}\``);
902
+ return `- Index${name}: (${properties})`;
527
903
  }
528
- }
529
- return lines.join("\n");
904
+ if (c.type === "unique") {
905
+ return `- Unique${name}: (${properties})`;
906
+ }
907
+ return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
908
+ });
530
909
  }
531
910
 
532
911
  // src/index.ts
912
+ var COMPILED_JS = /\.(c|m)?js$/i;
913
+ function resolveJsDocSources(sourcePaths, src, onWarn) {
914
+ if (src !== void 0 && src.length > 0) {
915
+ return src;
916
+ }
917
+ if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
918
+ onWarn(
919
+ 'Entities were discovered from compiled JavaScript, so JSDoc descriptions and @namespace/@hidden tags cannot be read (build tools strip comments). Hidden entities may be exposed. Pass --src "<glob to your .ts sources>" (or the `src` option) to read JSDoc from the original TypeScript files.'
920
+ );
921
+ }
922
+ return sourcePaths;
923
+ }
924
+ function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
925
+ if (jsDocResult.sourceFileCount === 0) {
926
+ throw new Error(
927
+ `No source files matched the explicit src paths: ${src.join(", ")}
928
+ Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
929
+ );
930
+ }
931
+ const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
932
+ const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
933
+ if (missingConcrete.length > 0) {
934
+ throw new Error(
935
+ `Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
936
+ Check that --src (or the \`src\` option) points at all TypeScript entity files. JSDoc tags such as @namespace and @hidden for missing entities cannot be read.`
937
+ );
938
+ }
939
+ if (onWarn) {
940
+ const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
941
+ if (missingAbstract.length > 0) {
942
+ onWarn(
943
+ `Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
944
+ @hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
945
+ );
946
+ }
947
+ }
948
+ }
949
+ function errorMessages(err) {
950
+ const messages = [];
951
+ const seen = /* @__PURE__ */ new Set();
952
+ let current = err;
953
+ while (current instanceof Error && !seen.has(current)) {
954
+ seen.add(current);
955
+ messages.push(current.message);
956
+ current = current.cause;
957
+ }
958
+ return messages;
959
+ }
960
+ function isMissingTsMorphSourceFile(err) {
961
+ return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
962
+ }
963
+ async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
964
+ try {
965
+ return await loadEntityMetadata(effectiveOrm);
966
+ } catch (err) {
967
+ const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
968
+ if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
969
+ throw err;
970
+ }
971
+ try {
972
+ return await loadEntityMetadata(originalOrm);
973
+ } catch {
974
+ throw err;
975
+ }
976
+ }
977
+ }
533
978
  async function generateMarkdown(options) {
534
- const { orm, title = "Database Schema", description, src = [] } = options;
535
- const metas = await loadEntityMetadata(orm);
536
- const jsDocResult = loadJsDoc(src);
537
- const docModel = buildDocumentModel(metas, jsDocResult, title, description);
979
+ const { orm, title = "Database Schema", description, src, onWarn } = options;
980
+ const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
981
+ const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
982
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
983
+ if (src !== void 0 && src.length > 0) {
984
+ assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
985
+ }
986
+ const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
538
987
  return renderMarkdown(docModel);
539
988
  }
540
989
 
541
990
  // src/cli.ts
991
+ function toConfigImportSpecifier(configPath) {
992
+ return (0, import_node_url.pathToFileURL)(path2.resolve(configPath)).href;
993
+ }
994
+ function findNearestTsconfig(fromPath) {
995
+ let dir = path2.dirname(path2.resolve(fromPath));
996
+ for (; ; ) {
997
+ const candidate = path2.join(dir, "tsconfig.json");
998
+ if (syncFs.existsSync(candidate)) {
999
+ return candidate;
1000
+ }
1001
+ const parent = path2.dirname(dir);
1002
+ if (parent === dir) {
1003
+ return void 0;
1004
+ }
1005
+ dir = parent;
1006
+ }
1007
+ }
1008
+ async function loadOrmOptions(configPath, tsconfigPath) {
1009
+ const isTypeScriptConfig = configPath.endsWith(".ts");
1010
+ if (isTypeScriptConfig) {
1011
+ let register;
1012
+ try {
1013
+ ({ register } = await import("tsx/esm/api"));
1014
+ } catch {
1015
+ throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
1016
+ }
1017
+ let tsconfig;
1018
+ if (tsconfigPath !== void 0) {
1019
+ tsconfig = path2.resolve(tsconfigPath);
1020
+ if (!syncFs.existsSync(tsconfig)) {
1021
+ throw new Error(`--tsconfig file not found: ${tsconfig}`);
1022
+ }
1023
+ } else {
1024
+ tsconfig = findNearestTsconfig(configPath);
1025
+ }
1026
+ register(tsconfig !== void 0 ? { tsconfig } : {});
1027
+ }
1028
+ const configUrl = toConfigImportSpecifier(configPath);
1029
+ let mod;
1030
+ try {
1031
+ mod = await import(
1032
+ /* @vite-ignore */
1033
+ configUrl
1034
+ );
1035
+ } catch (cause) {
1036
+ if (isTypeScriptConfig) {
1037
+ const detail = cause instanceof Error ? cause.message : String(cause);
1038
+ throw new Error(
1039
+ `Failed to load TypeScript config.
1040
+ ${detail}
1041
+
1042
+ If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
1043
+ Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
1044
+ { cause }
1045
+ );
1046
+ }
1047
+ throw cause;
1048
+ }
1049
+ if (mod.default === void 0) {
1050
+ throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
1051
+ }
1052
+ const config = mod.default;
1053
+ if (typeof config === "function" || config instanceof Promise) {
1054
+ throw new Error(
1055
+ "Config file default export must be a configuration object, not a function or Promise.\nResolve it first, or use the programmatic API instead (see README)."
1056
+ );
1057
+ }
1058
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
1059
+ throw new Error(
1060
+ "Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
1061
+ );
1062
+ }
1063
+ const options = config;
1064
+ const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
1065
+ return withPreferTs;
1066
+ }
1067
+ function formatErrorChain(err) {
1068
+ const lines = [];
1069
+ const seen = /* @__PURE__ */ new Set();
1070
+ let current = err;
1071
+ while (current instanceof Error && !seen.has(current)) {
1072
+ seen.add(current);
1073
+ lines.push(current.message);
1074
+ current = current.cause;
1075
+ }
1076
+ if (current !== void 0 && !(current instanceof Error)) {
1077
+ lines.push(String(current));
1078
+ }
1079
+ return lines.map((line, i) => i === 0 ? line : ` \u21B3 caused by: ${line}`).join("\n");
1080
+ }
1081
+ var REFLECTION_METADATA_HINT = "\n\nNote: this tool loads configs via tsx (esbuild), which does not emit 'emitDecoratorMetadata' reflection data, so enabling it will not help here.\nEither give each entity property an explicit 'type:'/'entity:' attribute, or install '@mikro-orm/reflection' \u2014 this tool then reads types from your TypeScript sources automatically.";
1082
+ function formatDiscoveryError(err) {
1083
+ const chain = formatErrorChain(err);
1084
+ return chain.includes("emitDecoratorMetadata") ? chain + REFLECTION_METADATA_HINT : chain;
1085
+ }
1086
+ function formatFileSystemError(cause) {
1087
+ if (cause instanceof Error) {
1088
+ const code = "code" in cause && typeof cause.code === "string" ? ` (${cause.code})` : "";
1089
+ return `${cause.message}${code}`;
1090
+ }
1091
+ return String(cause);
1092
+ }
1093
+ async function writeMarkdownFile(outPath, markdown) {
1094
+ try {
1095
+ await fs.mkdir(path2.dirname(outPath), { recursive: true });
1096
+ await fs.writeFile(outPath, markdown, "utf-8");
1097
+ } catch (cause) {
1098
+ throw new Error(`Cannot write output file: ${outPath}
1099
+ ${formatFileSystemError(cause)}`, { cause });
1100
+ }
1101
+ }
542
1102
  async function run(opts) {
543
- const configPath = path.resolve(opts.config);
544
- const outPath = path.resolve(opts.out);
1103
+ const configPath = path2.resolve(opts.config);
1104
+ const outPath = path2.resolve(opts.out);
545
1105
  let ormOptions;
546
1106
  try {
547
- const mod = await import(configPath);
548
- ormOptions = mod.default ?? mod;
1107
+ ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
549
1108
  } catch (err) {
550
- const hint = configPath.endsWith(".ts") ? "\nHint: TypeScript configs require tsx or ts-node:\n npx tsx ./node_modules/.bin/mikro-orm-markdown ..." : "";
551
1109
  process.stderr.write(
552
1110
  `Error: Cannot load config: ${configPath}
553
- ${err instanceof Error ? err.message : String(err)}${hint}
1111
+ ${err instanceof Error ? err.message : String(err)}
554
1112
  `
555
1113
  );
556
1114
  process.exit(1);
@@ -560,23 +1118,58 @@ ${err instanceof Error ? err.message : String(err)}${hint}
560
1118
  markdown = await generateMarkdown({
561
1119
  orm: ormOptions,
562
1120
  title: opts.title,
563
- src: opts.src ?? [],
564
- ...opts.description !== void 0 && { description: opts.description }
1121
+ ...opts.description !== void 0 && { description: opts.description },
1122
+ ...opts.src !== void 0 && { src: opts.src },
1123
+ onWarn: (message) => void process.stderr.write(`Warning: ${message}
1124
+ `)
565
1125
  });
566
1126
  } catch (err) {
567
- const msg = err instanceof MetadataLoadError ? err.message : err instanceof Error ? err.message : String(err);
568
- process.stderr.write(`Error: ${msg}
1127
+ process.stderr.write(`Error: ${formatDiscoveryError(err)}
1128
+ `);
1129
+ process.exit(1);
1130
+ }
1131
+ try {
1132
+ await writeMarkdownFile(outPath, markdown);
1133
+ } catch (err) {
1134
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
569
1135
  `);
570
1136
  process.exit(1);
571
1137
  }
572
- await fs.writeFile(outPath, markdown, "utf-8");
573
- process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
1138
+ process.stdout.write(`\u2713 Written to ${path2.relative(process.cwd(), outPath)}
574
1139
  `);
575
1140
  }
576
- var program = new import_commander.Command().name("mikro-orm-markdown").description("Generate Mermaid ERD + markdown docs from MikroORM entities").requiredOption("-c, --config <path>", "MikroORM config file path").option("-o, --out <path>", "Output markdown file path", "./ERD.md").option("-t, --title <string>", "Document title", "Database Schema").option("-d, --description <string>", "Optional description paragraph shown below the title").option("-s, --src <glob>", "JSDoc source glob (repeatable)", (val, prev = []) => [...prev, val]).action(run);
577
- program.parseAsync(process.argv).catch((err) => {
578
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}
1141
+ var program = new import_commander.Command().name("mikro-orm-markdown").description("Generate Mermaid ERD + markdown docs from MikroORM entities").requiredOption("-c, --config <path>", "MikroORM config file path").option("-o, --out <path>", "Output markdown file path", "./ERD.md").option("-t, --title <string>", "Document title", "Database Schema").option("-d, --description <string>", "Optional description paragraph shown below the title").option(
1142
+ "--tsconfig <path>",
1143
+ "tsconfig.json to use when loading a .ts config (defaults to the nearest one beside the config file)"
1144
+ ).option(
1145
+ "--src <paths...>",
1146
+ "Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
1147
+ ).action(run);
1148
+ function isDirectCliExecution() {
1149
+ const entryPoint = process.argv[1];
1150
+ if (entryPoint === void 0) {
1151
+ return false;
1152
+ }
1153
+ try {
1154
+ return syncFs.realpathSync(path2.resolve(entryPoint)) === syncFs.realpathSync((0, import_node_url.fileURLToPath)(importMetaUrl));
1155
+ } catch {
1156
+ return false;
1157
+ }
1158
+ }
1159
+ if (isDirectCliExecution()) {
1160
+ program.parseAsync(process.argv).catch((err) => {
1161
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
579
1162
  `);
580
- process.exit(1);
1163
+ process.exit(1);
1164
+ });
1165
+ }
1166
+ // Annotate the CommonJS export names for ESM import in node:
1167
+ 0 && (module.exports = {
1168
+ findNearestTsconfig,
1169
+ formatDiscoveryError,
1170
+ formatErrorChain,
1171
+ loadOrmOptions,
1172
+ toConfigImportSpecifier,
1173
+ writeMarkdownFile
581
1174
  });
582
1175
  //# sourceMappingURL=cli.cjs.map