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

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);
30
+
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();
25
46
 
26
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,47 @@ 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) {
244
- return "integer";
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
+ return (pkProp ?? primaryProps[index] ?? primaryProps[0])?.type ?? "integer";
423
+ });
424
+ }
425
+ function getPrimaryProps(meta) {
426
+ const primaryKeys = meta.primaryKeys ?? [];
427
+ const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
428
+ if (orderedPrimaryProps.length > 0) {
429
+ return orderedPrimaryProps;
245
430
  }
246
- const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);
247
- return pkProp ? normalizeType(pkProp.type) : "integer";
431
+ return Object.values(meta.properties).filter((prop) => prop.primary === true);
248
432
  }
249
433
  function buildConstraints(meta) {
250
434
  const result = [];
@@ -252,7 +436,7 @@ function buildConstraints(meta) {
252
436
  const props = idx.properties;
253
437
  result.push({
254
438
  type: "index",
255
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
439
+ properties: resolveConstraintProperties(meta, props),
256
440
  ...idx.name !== void 0 && { name: idx.name }
257
441
  });
258
442
  }
@@ -260,7 +444,7 @@ function buildConstraints(meta) {
260
444
  const props = uniq.properties;
261
445
  result.push({
262
446
  type: "unique",
263
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
447
+ properties: resolveConstraintProperties(meta, props),
264
448
  ...uniq.name !== void 0 && { name: uniq.name }
265
449
  });
266
450
  }
@@ -277,6 +461,19 @@ function buildConstraints(meta) {
277
461
  }
278
462
  return result;
279
463
  }
464
+ function resolveConstraintProperties(meta, props) {
465
+ const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
466
+ return propNames.flatMap((propName) => {
467
+ const prop = meta.properties[String(propName)];
468
+ if (prop === void 0) {
469
+ return [String(propName)];
470
+ }
471
+ if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
472
+ return prop.fieldNames;
473
+ }
474
+ return [prop.name];
475
+ });
476
+ }
280
477
  function buildRelationEdges(metas) {
281
478
  const edges = [];
282
479
  for (const meta of metas) {
@@ -295,6 +492,9 @@ function buildRelationEdges(metas) {
295
492
  function buildEdge(fromEntity, prop) {
296
493
  const isNullable = prop.nullable === true;
297
494
  if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE) {
495
+ if (prop.type === fromEntity) {
496
+ return null;
497
+ }
298
498
  return {
299
499
  fromEntity,
300
500
  toEntity: prop.type,
@@ -326,14 +526,16 @@ function buildEdge(fromEntity, prop) {
326
526
  function renderErDiagram(model) {
327
527
  const lines = ["erDiagram"];
328
528
  for (const entity of model.entities) {
329
- lines.push(` ${entity.className} {`);
529
+ lines.push(` ${toMermaidIdentifier(entity.className)} {`);
330
530
  for (const col of entity.columns) {
331
531
  lines.push(` ${renderColumnLine(col)}`);
332
532
  }
333
533
  lines.push(" }");
334
534
  }
335
535
  for (const rel of model.relations) {
336
- lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`);
536
+ lines.push(
537
+ ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
538
+ );
337
539
  }
338
540
  return lines.join("\n");
339
541
  }
@@ -341,8 +543,6 @@ function renderColumnLine(col) {
341
543
  let qualifier = "";
342
544
  if (col.isPrimary) {
343
545
  qualifier = " PK";
344
- } else if (col.isForeignKey) {
345
- qualifier = " FK";
346
546
  } else if (col.isUnique) {
347
547
  qualifier = " UK";
348
548
  }
@@ -353,27 +553,38 @@ function renderColumnLine(col) {
353
553
  comment = "discriminator";
354
554
  } else if (col.embeddedIn !== void 0) {
355
555
  comment = `[${col.embeddedIn}]`;
356
- } else if (col.fieldName !== col.propName) {
357
- comment = col.propName;
556
+ } else if (col.isSelfReference) {
557
+ comment = "self-ref";
358
558
  }
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, "_");
559
+ const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
560
+ return `${toMermaidIdentifier(col.type)} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
364
561
  }
365
562
 
366
563
  // src/model/build.ts
367
- function buildDocumentModel(metas, jsDocResult, title, description) {
368
- const { entities: diagramEntities, relations: allRelations } = buildDiagramModel(metas);
564
+ var FROM_ONE_OR_MORE = "}|";
565
+ var TO_ONE_OR_MORE = "|{";
566
+ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
567
+ const { entities: diagramEntities, relations } = buildDiagramModel(metas);
568
+ const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
569
+ const hiddenClasses = /* @__PURE__ */ new Set();
570
+ for (const model of diagramEntities) {
571
+ if (jsDocResult.entities.get(model.className)?.hidden) {
572
+ hiddenClasses.add(model.className);
573
+ }
574
+ }
369
575
  const enrichedByClass = /* @__PURE__ */ new Map();
370
576
  for (const model of diagramEntities) {
371
577
  const jsDoc = jsDocResult.entities.get(model.className);
372
578
  if (jsDoc?.hidden) {
373
579
  continue;
374
580
  }
375
- const propDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
376
- enrichedByClass.set(model.className, { model, jsDoc, propDocs });
581
+ const columns = model.columns.filter(
582
+ (col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
583
+ );
584
+ const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
585
+ const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
586
+ const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
587
+ enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
377
588
  }
378
589
  const groupNames = /* @__PURE__ */ new Set();
379
590
  let anyUntagged = false;
@@ -414,6 +625,65 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
414
625
  });
415
626
  return { title, groups, ...description !== void 0 && { description } };
416
627
  }
628
+ function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
629
+ const merged = new Map(ownPropDocs);
630
+ for (const col of columns) {
631
+ if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
632
+ continue;
633
+ }
634
+ const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
635
+ if (info) {
636
+ merged.set(col.propName, info);
637
+ }
638
+ }
639
+ return merged;
640
+ }
641
+ function applyAtLeastOne(relations, metas, props, onWarn) {
642
+ const adjusted = relations.map((edge) => ({ ...edge }));
643
+ const metaByClass = new Map(metas.map((m) => [m.className, m]));
644
+ for (const [className, propMap] of props) {
645
+ const meta = metaByClass.get(className);
646
+ if (!meta) {
647
+ continue;
648
+ }
649
+ for (const [propName, info] of propMap) {
650
+ if (!info.atLeastOne) {
651
+ continue;
652
+ }
653
+ const prop = meta.properties[propName];
654
+ if (!prop) {
655
+ continue;
656
+ }
657
+ let edge;
658
+ if (prop.kind === import_core3.ReferenceKind.ONE_TO_MANY && prop.mappedBy) {
659
+ edge = adjusted.find(
660
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
661
+ );
662
+ if (edge) {
663
+ edge.fromCardinality = FROM_ONE_OR_MORE;
664
+ }
665
+ } else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.owner === true) {
666
+ edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
667
+ if (edge) {
668
+ edge.toCardinality = TO_ONE_OR_MORE;
669
+ }
670
+ } else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.mappedBy) {
671
+ edge = adjusted.find(
672
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
673
+ );
674
+ if (edge) {
675
+ edge.fromCardinality = FROM_ONE_OR_MORE;
676
+ }
677
+ }
678
+ if (!edge) {
679
+ onWarn?.(
680
+ `@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.`
681
+ );
682
+ }
683
+ }
684
+ }
685
+ return adjusted;
686
+ }
417
687
  function hasNoNamespaceTags(jsDoc) {
418
688
  if (!jsDoc) {
419
689
  return true;
@@ -439,19 +709,55 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
439
709
  return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
440
710
  }
441
711
 
712
+ // src/provider.ts
713
+ async function withTsMorphMetadataProvider(options, onWarn) {
714
+ if (options.metadataProvider !== void 0) {
715
+ return options;
716
+ }
717
+ try {
718
+ const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
719
+ return { ...options, metadataProvider: TsMorphMetadataProvider };
720
+ } catch (err) {
721
+ const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
722
+ const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
723
+ if (!isNotInstalled && onWarn) {
724
+ onWarn(
725
+ `@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.`
726
+ );
727
+ }
728
+ return options;
729
+ }
730
+ }
731
+
442
732
  // src/render/markdown.ts
443
733
  function renderMarkdown(docModel) {
444
- const sections = [`# ${docModel.title}`];
734
+ const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
445
735
  if (docModel.description) {
446
- sections.push(docModel.description);
736
+ sections.push(escapeMarkdownParagraph(docModel.description));
737
+ }
738
+ if (docModel.groups.length > 1) {
739
+ sections.push(renderTableOfContents(docModel.groups));
447
740
  }
448
741
  for (const group of docModel.groups) {
449
742
  sections.push(renderGroupSection(group));
450
743
  }
451
744
  return sections.join("\n\n");
452
745
  }
746
+ function renderBulletSection(header, items, renderItem) {
747
+ const lines = [header, ""];
748
+ for (const item of items) {
749
+ lines.push(renderItem(item));
750
+ }
751
+ return lines.join("\n");
752
+ }
753
+ function renderTableOfContents(groups) {
754
+ return renderBulletSection("## Contents", groups, (group) => {
755
+ const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
756
+ return `- [${label}](#${toMarkdownAnchor(group.name)})`;
757
+ });
758
+ }
453
759
  function renderGroupSection(group) {
454
- const parts = [`## ${group.name}`];
760
+ const parts = [`## ${escapeMarkdownInline(group.name)}`];
455
761
  if (group.erdEntities.length > 0) {
456
762
  const diagramModel = {
457
763
  entities: group.erdEntities.map((e) => e.model),
@@ -465,14 +771,18 @@ function renderGroupSection(group) {
465
771
  return parts.join("\n\n");
466
772
  }
467
773
  function renderEntitySection(entity) {
468
- const parts = [`### ${entity.model.className}`];
774
+ const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
775
+ parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
469
776
  if (entity.jsDoc?.description) {
470
- parts.push(`> ${entity.jsDoc.description}`);
777
+ parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
471
778
  }
472
779
  if (entity.model.discriminatorColumn) {
473
- parts.push(`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`);
780
+ parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
474
781
  } else if (entity.model.extendsEntity) {
475
- parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
782
+ const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
783
+ parts.push(
784
+ `*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
785
+ );
476
786
  }
477
787
  if (entity.model.columns.length > 0) {
478
788
  parts.push(renderColumnTable(entity));
@@ -480,6 +790,10 @@ function renderEntitySection(entity) {
480
790
  if (entity.model.constraints.length > 0) {
481
791
  parts.push(renderConstraints(entity.model.constraints));
482
792
  }
793
+ const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
794
+ if (computedColumns.length > 0) {
795
+ parts.push(renderComputedColumns(computedColumns));
796
+ }
483
797
  return parts.join("\n\n");
484
798
  }
485
799
  function renderColumnTable(entity) {
@@ -488,24 +802,27 @@ function renderColumnTable(entity) {
488
802
  const rows = entity.model.columns.map((col) => {
489
803
  const key = resolveColumnKey(col);
490
804
  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} |`;
805
+ const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
806
+ const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
807
+ const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
808
+ return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(col.type)} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
493
809
  });
494
810
  return [header, sep, ...rows].join("\n");
495
811
  }
496
812
  function resolveColumnKey(col) {
813
+ const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
814
+ if (col.isPrimary && col.isForeignKey) {
815
+ return `PK, ${fkKey}`;
816
+ }
497
817
  if (col.isPrimary) {
498
818
  return "PK";
499
819
  }
500
820
  if (col.isForeignKey) {
501
- return col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
821
+ return fkKey;
502
822
  }
503
823
  if (col.isUnique) {
504
824
  return "UK";
505
825
  }
506
- if (col.formula !== void 0) {
507
- return `formula: ${col.formula}`;
508
- }
509
826
  if (col.isDiscriminator) {
510
827
  return "discriminator";
511
828
  }
@@ -514,43 +831,227 @@ function resolveColumnKey(col) {
514
831
  }
515
832
  return "";
516
833
  }
834
+ function renderComputedColumns(columns) {
835
+ return renderBulletSection("**Computed columns:**", columns, (col) => {
836
+ const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
837
+ return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
838
+ });
839
+ }
517
840
  function renderConstraints(constraints) {
518
- const lines = ["**Constraints:**", ""];
519
- for (const c of constraints) {
520
- const name = c.name ? ` \`${c.name}\`` : "";
841
+ return renderBulletSection("**Constraints:**", constraints, (c) => {
842
+ const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
843
+ const properties = c.properties.map(escapeMarkdownInline).join(", ");
521
844
  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 ?? ""}\``);
845
+ return `- Index${name}: (${properties})`;
527
846
  }
528
- }
529
- return lines.join("\n");
847
+ if (c.type === "unique") {
848
+ return `- Unique${name}: (${properties})`;
849
+ }
850
+ return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
851
+ });
530
852
  }
531
853
 
532
854
  // src/index.ts
855
+ var COMPILED_JS = /\.(c|m)?js$/i;
856
+ function resolveJsDocSources(sourcePaths, src, onWarn) {
857
+ if (src !== void 0 && src.length > 0) {
858
+ return src;
859
+ }
860
+ if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
861
+ onWarn(
862
+ '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.'
863
+ );
864
+ }
865
+ return sourcePaths;
866
+ }
867
+ function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
868
+ if (jsDocResult.sourceFileCount === 0) {
869
+ throw new Error(
870
+ `No source files matched the explicit src paths: ${src.join(", ")}
871
+ Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
872
+ );
873
+ }
874
+ const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
875
+ const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
876
+ if (missingConcrete.length > 0) {
877
+ throw new Error(
878
+ `Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
879
+ 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.`
880
+ );
881
+ }
882
+ if (onWarn) {
883
+ const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
884
+ if (missingAbstract.length > 0) {
885
+ onWarn(
886
+ `Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
887
+ @hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
888
+ );
889
+ }
890
+ }
891
+ }
892
+ function errorMessages(err) {
893
+ const messages = [];
894
+ const seen = /* @__PURE__ */ new Set();
895
+ let current = err;
896
+ while (current instanceof Error && !seen.has(current)) {
897
+ seen.add(current);
898
+ messages.push(current.message);
899
+ current = current.cause;
900
+ }
901
+ return messages;
902
+ }
903
+ function isMissingTsMorphSourceFile(err) {
904
+ return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
905
+ }
906
+ async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
907
+ try {
908
+ return await loadEntityMetadata(effectiveOrm);
909
+ } catch (err) {
910
+ const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
911
+ if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
912
+ throw err;
913
+ }
914
+ try {
915
+ return await loadEntityMetadata(originalOrm);
916
+ } catch {
917
+ throw err;
918
+ }
919
+ }
920
+ }
533
921
  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);
922
+ const { orm, title = "Database Schema", description, src, onWarn } = options;
923
+ const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
924
+ const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
925
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
926
+ if (src !== void 0 && src.length > 0) {
927
+ assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
928
+ }
929
+ const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
538
930
  return renderMarkdown(docModel);
539
931
  }
540
932
 
541
933
  // src/cli.ts
934
+ function toConfigImportSpecifier(configPath) {
935
+ return (0, import_node_url.pathToFileURL)(path2.resolve(configPath)).href;
936
+ }
937
+ function findNearestTsconfig(fromPath) {
938
+ let dir = path2.dirname(path2.resolve(fromPath));
939
+ for (; ; ) {
940
+ const candidate = path2.join(dir, "tsconfig.json");
941
+ if (syncFs.existsSync(candidate)) {
942
+ return candidate;
943
+ }
944
+ const parent = path2.dirname(dir);
945
+ if (parent === dir) {
946
+ return void 0;
947
+ }
948
+ dir = parent;
949
+ }
950
+ }
951
+ async function loadOrmOptions(configPath, tsconfigPath) {
952
+ const isTypeScriptConfig = configPath.endsWith(".ts");
953
+ if (isTypeScriptConfig) {
954
+ let register;
955
+ try {
956
+ ({ register } = await import("tsx/esm/api"));
957
+ } catch {
958
+ throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
959
+ }
960
+ let tsconfig;
961
+ if (tsconfigPath !== void 0) {
962
+ tsconfig = path2.resolve(tsconfigPath);
963
+ if (!syncFs.existsSync(tsconfig)) {
964
+ throw new Error(`--tsconfig file not found: ${tsconfig}`);
965
+ }
966
+ } else {
967
+ tsconfig = findNearestTsconfig(configPath);
968
+ }
969
+ register(tsconfig !== void 0 ? { tsconfig } : {});
970
+ }
971
+ const configUrl = toConfigImportSpecifier(configPath);
972
+ let mod;
973
+ try {
974
+ mod = await import(
975
+ /* @vite-ignore */
976
+ configUrl
977
+ );
978
+ } catch (cause) {
979
+ if (isTypeScriptConfig) {
980
+ const detail = cause instanceof Error ? cause.message : String(cause);
981
+ throw new Error(
982
+ `Failed to load TypeScript config.
983
+ ${detail}
984
+
985
+ If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
986
+ Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
987
+ { cause }
988
+ );
989
+ }
990
+ throw cause;
991
+ }
992
+ if (mod.default === void 0) {
993
+ throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
994
+ }
995
+ const config = mod.default;
996
+ if (typeof config === "function" || config instanceof Promise) {
997
+ throw new Error(
998
+ "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)."
999
+ );
1000
+ }
1001
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
1002
+ throw new Error(
1003
+ "Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
1004
+ );
1005
+ }
1006
+ const options = config;
1007
+ const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
1008
+ return withPreferTs;
1009
+ }
1010
+ function formatErrorChain(err) {
1011
+ const lines = [];
1012
+ const seen = /* @__PURE__ */ new Set();
1013
+ let current = err;
1014
+ while (current instanceof Error && !seen.has(current)) {
1015
+ seen.add(current);
1016
+ lines.push(current.message);
1017
+ current = current.cause;
1018
+ }
1019
+ if (current !== void 0 && !(current instanceof Error)) {
1020
+ lines.push(String(current));
1021
+ }
1022
+ return lines.map((line, i) => i === 0 ? line : ` \u21B3 caused by: ${line}`).join("\n");
1023
+ }
1024
+ 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.";
1025
+ function formatDiscoveryError(err) {
1026
+ const chain = formatErrorChain(err);
1027
+ return chain.includes("emitDecoratorMetadata") ? chain + REFLECTION_METADATA_HINT : chain;
1028
+ }
1029
+ function formatFileSystemError(cause) {
1030
+ if (cause instanceof Error) {
1031
+ const code = "code" in cause && typeof cause.code === "string" ? ` (${cause.code})` : "";
1032
+ return `${cause.message}${code}`;
1033
+ }
1034
+ return String(cause);
1035
+ }
1036
+ async function writeMarkdownFile(outPath, markdown) {
1037
+ try {
1038
+ await fs.mkdir(path2.dirname(outPath), { recursive: true });
1039
+ await fs.writeFile(outPath, markdown, "utf-8");
1040
+ } catch (cause) {
1041
+ throw new Error(`Cannot write output file: ${outPath}
1042
+ ${formatFileSystemError(cause)}`, { cause });
1043
+ }
1044
+ }
542
1045
  async function run(opts) {
543
- const configPath = path.resolve(opts.config);
544
- const outPath = path.resolve(opts.out);
1046
+ const configPath = path2.resolve(opts.config);
1047
+ const outPath = path2.resolve(opts.out);
545
1048
  let ormOptions;
546
1049
  try {
547
- const mod = await import(configPath);
548
- ormOptions = mod.default ?? mod;
1050
+ ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
549
1051
  } 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
1052
  process.stderr.write(
552
1053
  `Error: Cannot load config: ${configPath}
553
- ${err instanceof Error ? err.message : String(err)}${hint}
1054
+ ${err instanceof Error ? err.message : String(err)}
554
1055
  `
555
1056
  );
556
1057
  process.exit(1);
@@ -560,23 +1061,58 @@ ${err instanceof Error ? err.message : String(err)}${hint}
560
1061
  markdown = await generateMarkdown({
561
1062
  orm: ormOptions,
562
1063
  title: opts.title,
563
- src: opts.src ?? [],
564
- ...opts.description !== void 0 && { description: opts.description }
1064
+ ...opts.description !== void 0 && { description: opts.description },
1065
+ ...opts.src !== void 0 && { src: opts.src },
1066
+ onWarn: (message) => void process.stderr.write(`Warning: ${message}
1067
+ `)
565
1068
  });
566
1069
  } catch (err) {
567
- const msg = err instanceof MetadataLoadError ? err.message : err instanceof Error ? err.message : String(err);
568
- process.stderr.write(`Error: ${msg}
1070
+ process.stderr.write(`Error: ${formatDiscoveryError(err)}
569
1071
  `);
570
1072
  process.exit(1);
571
1073
  }
572
- await fs.writeFile(outPath, markdown, "utf-8");
573
- process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
1074
+ try {
1075
+ await writeMarkdownFile(outPath, markdown);
1076
+ } catch (err) {
1077
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1078
+ `);
1079
+ process.exit(1);
1080
+ }
1081
+ process.stdout.write(`\u2713 Written to ${path2.relative(process.cwd(), outPath)}
574
1082
  `);
575
1083
  }
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)}
1084
+ 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(
1085
+ "--tsconfig <path>",
1086
+ "tsconfig.json to use when loading a .ts config (defaults to the nearest one beside the config file)"
1087
+ ).option(
1088
+ "--src <paths...>",
1089
+ "Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
1090
+ ).action(run);
1091
+ function isDirectCliExecution() {
1092
+ const entryPoint = process.argv[1];
1093
+ if (entryPoint === void 0) {
1094
+ return false;
1095
+ }
1096
+ try {
1097
+ return syncFs.realpathSync(path2.resolve(entryPoint)) === syncFs.realpathSync((0, import_node_url.fileURLToPath)(importMetaUrl));
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ }
1102
+ if (isDirectCliExecution()) {
1103
+ program.parseAsync(process.argv).catch((err) => {
1104
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
579
1105
  `);
580
- process.exit(1);
1106
+ process.exit(1);
1107
+ });
1108
+ }
1109
+ // Annotate the CommonJS export names for ESM import in node:
1110
+ 0 && (module.exports = {
1111
+ findNearestTsconfig,
1112
+ formatDiscoveryError,
1113
+ formatErrorChain,
1114
+ loadOrmOptions,
1115
+ toConfigImportSpecifier,
1116
+ writeMarkdownFile
581
1117
  });
582
1118
  //# sourceMappingURL=cli.cjs.map