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.js CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import * as syncFs from "fs";
4
5
  import * as fs from "fs/promises";
5
- import * as path from "path";
6
+ import * as path2 from "path";
7
+ import { fileURLToPath, pathToFileURL } from "url";
6
8
  import { Command } from "commander";
7
9
 
8
10
  // src/docs/jsdoc.ts
9
11
  import { Project } from "ts-morph";
10
- function loadJsDoc(srcGlobs) {
12
+ function loadJsDoc(filePaths) {
11
13
  const entities = /* @__PURE__ */ new Map();
12
14
  const props = /* @__PURE__ */ new Map();
13
- if (srcGlobs.length === 0) {
14
- return { entities, props };
15
+ const classNames = /* @__PURE__ */ new Set();
16
+ if (filePaths.length === 0) {
17
+ return { entities, props, sourceFileCount: 0, classNames };
15
18
  }
16
19
  const project = new Project({
17
20
  skipAddingFilesFromTsConfig: true,
@@ -21,34 +24,44 @@ function loadJsDoc(srcGlobs) {
21
24
  skipLibCheck: true
22
25
  }
23
26
  });
24
- project.addSourceFilesAtPaths(srcGlobs);
25
- for (const sourceFile of project.getSourceFiles()) {
26
- for (const cls of sourceFile.getClasses()) {
27
- const className = cls.getName();
28
- if (!className) {
29
- continue;
30
- }
31
- const classDocs = cls.getJsDocs();
32
- if (classDocs.length > 0) {
33
- entities.set(className, parseEntityJsDoc(classDocs));
34
- }
35
- const propMap = /* @__PURE__ */ new Map();
36
- for (const prop of cls.getProperties()) {
37
- const propDocs = prop.getJsDocs();
38
- if (propDocs.length === 0) {
27
+ for (const filePath of filePaths) {
28
+ try {
29
+ project.addSourceFilesAtPaths(filePath);
30
+ } catch {
31
+ }
32
+ }
33
+ const sourceFiles = project.getSourceFiles();
34
+ for (const sourceFile of sourceFiles) {
35
+ try {
36
+ for (const cls of sourceFile.getClasses()) {
37
+ const className = cls.getName();
38
+ if (!className) {
39
39
  continue;
40
40
  }
41
- const desc = extractDescription(propDocs);
42
- if (desc !== void 0) {
43
- propMap.set(prop.getName(), { description: desc });
41
+ classNames.add(className);
42
+ const classDocs = cls.getJsDocs();
43
+ if (classDocs.length > 0) {
44
+ entities.set(className, parseEntityJsDoc(classDocs));
45
+ }
46
+ const propMap = /* @__PURE__ */ new Map();
47
+ for (const prop of cls.getProperties()) {
48
+ const propDocs = prop.getJsDocs();
49
+ if (propDocs.length === 0) {
50
+ continue;
51
+ }
52
+ const info = parsePropJsDoc(propDocs);
53
+ if (info.description !== void 0 || info.atLeastOne) {
54
+ propMap.set(prop.getName(), info);
55
+ }
56
+ }
57
+ if (propMap.size > 0) {
58
+ props.set(className, propMap);
44
59
  }
45
60
  }
46
- if (propMap.size > 0) {
47
- props.set(className, propMap);
48
- }
61
+ } catch {
49
62
  }
50
63
  }
51
- return { entities, props };
64
+ return { entities, props, sourceFileCount: sourceFiles.length, classNames };
52
65
  }
53
66
  function parseEntityJsDoc(jsDocs) {
54
67
  const namespaces = [];
@@ -83,18 +96,26 @@ function parseEntityJsDoc(jsDocs) {
83
96
  hidden
84
97
  };
85
98
  }
86
- function extractDescription(jsDocs) {
99
+ function parsePropJsDoc(jsDocs) {
100
+ let description;
101
+ let atLeastOne = false;
87
102
  for (const doc of jsDocs) {
88
103
  const desc = doc.getDescription().trim();
89
- if (desc) {
90
- return desc;
104
+ if (desc && description === void 0) {
105
+ description = desc;
106
+ }
107
+ for (const tag of doc.getTags()) {
108
+ if (tag.getTagName() === "atLeastOne") {
109
+ atLeastOne = true;
110
+ }
91
111
  }
92
112
  }
93
- return void 0;
113
+ return { ...description !== void 0 && { description }, atLeastOne };
94
114
  }
95
115
 
96
116
  // src/metadata/load.ts
97
- import { MikroORM } from "@mikro-orm/core";
117
+ import * as path from "path";
118
+ import { EntitySchema, MikroORM } from "@mikro-orm/core";
98
119
  var MetadataLoadError = class extends Error {
99
120
  constructor(message, cause) {
100
121
  super(message);
@@ -102,13 +123,46 @@ var MetadataLoadError = class extends Error {
102
123
  this.name = "MetadataLoadError";
103
124
  }
104
125
  };
126
+ async function closeDiscoveryResources(orm) {
127
+ await orm.config.getMetadataCacheAdapter()?.close?.();
128
+ await orm.config.getResultCacheAdapter()?.close?.();
129
+ }
130
+ function collectEntitySchemaNames(options) {
131
+ const configuredEntities = [...options.entities ?? [], ...options.entitiesTs ?? []];
132
+ const names = [];
133
+ for (const entity of configuredEntities) {
134
+ if (entity instanceof EntitySchema) {
135
+ names.push(entity.meta.className);
136
+ continue;
137
+ }
138
+ if (entity !== null && typeof entity === "object" && "schema" in entity && entity.schema instanceof EntitySchema) {
139
+ names.push(entity.schema.meta.className);
140
+ }
141
+ }
142
+ return names;
143
+ }
144
+ function assertNoEntitySchemaEntities(options) {
145
+ const schemaNames = collectEntitySchemaNames(options);
146
+ if (schemaNames.length === 0) {
147
+ return;
148
+ }
149
+ throw new MetadataLoadError(
150
+ `EntitySchema-defined entities are not currently supported: ${schemaNames.join(", ")}.
151
+ Use decorator-based @Entity() classes instead.`
152
+ );
153
+ }
105
154
  async function loadEntityMetadata(options) {
155
+ assertNoEntitySchemaEntities(options);
106
156
  let orm;
107
157
  try {
108
158
  orm = await MikroORM.init({
109
159
  ...options,
110
160
  debug: false,
111
- connect: false
161
+ connect: false,
162
+ // Always disable the metadata cache for one-shot doc runs so the project
163
+ // is never littered with a temp/ folder, regardless of how metadataProvider
164
+ // was configured.
165
+ metadataCache: { ...options.metadataCache, enabled: false }
112
166
  });
113
167
  } catch (cause) {
114
168
  throw new MetadataLoadError(
@@ -116,23 +170,80 @@ async function loadEntityMetadata(options) {
116
170
  cause
117
171
  );
118
172
  }
119
- const all = Object.values(orm.getMetadata().getAll());
120
- if (all.length === 0) {
121
- throw new MetadataLoadError(
122
- "No entities were discovered. Check that your config specifies at least one entity path or class."
123
- );
173
+ try {
174
+ const all = Object.values(orm.getMetadata().getAll());
175
+ if (all.length === 0) {
176
+ throw new MetadataLoadError(
177
+ "No entities were discovered. Check that your config specifies at least one entity path or class."
178
+ );
179
+ }
180
+ const baseDir = orm.config.get("baseDir");
181
+ const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
182
+ return { metas: all, sourcePaths };
183
+ } finally {
184
+ await closeDiscoveryResources(orm);
124
185
  }
125
- return all;
126
186
  }
127
187
 
188
+ // src/model/build.ts
189
+ import { ReferenceKind as ReferenceKind2 } from "@mikro-orm/core";
190
+
128
191
  // src/render/mermaid.ts
129
192
  import { ReferenceKind } from "@mikro-orm/core";
193
+
194
+ // src/render/escape.ts
195
+ var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
196
+ var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
197
+ function normalizeInlineText(value) {
198
+ return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
199
+ }
200
+ function splitNormalizedLines(value) {
201
+ return value.replace(/\r\n?/g, "\n").split("\n");
202
+ }
203
+ function escapeHtmlText(value) {
204
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
205
+ }
206
+ function escapeMarkdownInline(value) {
207
+ return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
208
+ }
209
+ function escapeMarkdownTableCell(value) {
210
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
211
+ }
212
+ function escapeMarkdownParagraph(value) {
213
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
214
+ }
215
+ function renderMarkdownBlockQuote(value) {
216
+ return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
217
+ }
218
+ function renderMarkdownInlineCode(value) {
219
+ const normalized = normalizeInlineText(value);
220
+ const backtickRuns = normalized.match(/`+/g) ?? [];
221
+ const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
222
+ const fence = "`".repeat(longestRun + 1);
223
+ const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
224
+ const content = needsPadding ? ` ${normalized} ` : normalized;
225
+ return `${fence}${content}${fence}`;
226
+ }
227
+ function toMermaidIdentifier(value) {
228
+ const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
229
+ const identifier = normalized === "" ? "_" : normalized;
230
+ return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
231
+ }
232
+ function escapeMermaidQuotedText(value) {
233
+ return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
234
+ }
235
+ function toMarkdownAnchor(value) {
236
+ return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
237
+ }
238
+
239
+ // src/render/mermaid.ts
130
240
  var FORMULA_DUMMY_TABLE = {
131
241
  alias: "e0",
132
242
  name: "",
133
243
  qualifiedName: "",
134
244
  toString: () => "e0"
135
245
  };
246
+ var UNRESOLVED_FORMULA = "<unresolved>";
136
247
  function buildDiagramModel(metas) {
137
248
  const metaByClass = new Map(metas.map((m) => [m.className, m]));
138
249
  const entities = metas.filter((meta) => !meta.pivotTable && !meta.embeddable).map((meta) => buildEntityModel(meta, metaByClass));
@@ -140,17 +251,14 @@ function buildDiagramModel(metas) {
140
251
  return { entities, relations };
141
252
  }
142
253
  function buildEntityModel(meta, metaByClass) {
143
- const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.discriminatorValue;
254
+ const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.extends;
144
255
  const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
145
256
  const columns = [];
146
257
  for (const prop of Object.values(meta.properties)) {
147
258
  if (isStiRoot && prop.inherited === true) {
148
259
  continue;
149
260
  }
150
- const col = buildColumn(prop, metaByClass, meta);
151
- if (col !== null) {
152
- columns.push(col);
153
- }
261
+ columns.push(...buildColumns(prop, metaByClass, meta));
154
262
  }
155
263
  return {
156
264
  className: meta.className,
@@ -160,47 +268,73 @@ function buildEntityModel(meta, metaByClass) {
160
268
  isEmbeddable: meta.embeddable === true,
161
269
  ...isStiRoot && { discriminatorColumn: meta.discriminatorColumn },
162
270
  ...isStiChild && { extendsEntity: meta.extends },
271
+ ...isStiChild && meta.discriminatorValue !== void 0 && { discriminatorValue: String(meta.discriminatorValue) },
163
272
  constraints: buildConstraints(meta)
164
273
  };
165
274
  }
166
- function buildColumn(prop, metaByClass, owningMeta) {
275
+ function buildColumns(prop, metaByClass, owningMeta) {
167
276
  if (prop.kind === ReferenceKind.EMBEDDED) {
168
- return null;
277
+ if (prop.object === true || prop.array === true) {
278
+ return [
279
+ {
280
+ propName: prop.name,
281
+ fieldName: prop.fieldNames?.[0] ?? prop.name,
282
+ type: "json",
283
+ isPrimary: false,
284
+ isForeignKey: false,
285
+ isUnique: prop.unique === true,
286
+ isNullable: prop.nullable === true,
287
+ ...prop.comment !== void 0 && { comment: prop.comment },
288
+ embeddedIn: prop.array === true ? `${prop.type}[]` : prop.type
289
+ }
290
+ ];
291
+ }
292
+ return [];
169
293
  }
170
294
  if (prop.kind === ReferenceKind.SCALAR) {
295
+ if (prop.object === true && prop.embedded !== void 0) {
296
+ return [];
297
+ }
171
298
  const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
172
299
  let embeddedIn;
300
+ let embeddedPropName;
173
301
  if (prop.embedded !== void 0) {
174
302
  const parentPropName = prop.embedded[0];
175
303
  embeddedIn = owningMeta.properties[parentPropName]?.type;
304
+ embeddedPropName = prop.embedded[1];
176
305
  }
177
306
  const isDiscriminator = owningMeta.discriminatorColumn !== void 0 && prop.name === owningMeta.discriminatorColumn;
178
- return {
179
- propName: prop.name,
180
- fieldName: prop.fieldNames?.[0] ?? prop.name,
181
- type: normalizeType(prop.type),
182
- isPrimary: prop.primary === true,
183
- isForeignKey: false,
184
- isUnique: prop.unique === true,
185
- isNullable: prop.nullable === true,
186
- ...formulaExpr !== void 0 && { formula: formulaExpr },
187
- ...embeddedIn !== void 0 && { embeddedIn },
188
- ...isDiscriminator && { isDiscriminator: true }
189
- };
307
+ const enumItems = prop.enum === true && Array.isArray(prop.items) && prop.items.length > 0 ? prop.items.map((item) => String(item)) : void 0;
308
+ return [
309
+ {
310
+ propName: prop.name,
311
+ fieldName: prop.fieldNames?.[0] ?? prop.name,
312
+ // Store the original type (e.g. `varchar(255)`); the Mermaid renderer
313
+ // sanitizes it for diagram identifiers, while the markdown table shows
314
+ // it verbatim. Guard against a missing type so downstream string
315
+ // handling never sees undefined (matches the FK path's defaulting).
316
+ type: prop.type ?? "unknown",
317
+ isPrimary: prop.primary === true,
318
+ isForeignKey: false,
319
+ isUnique: prop.unique === true,
320
+ isNullable: prop.nullable === true,
321
+ ...prop.comment !== void 0 && { comment: prop.comment },
322
+ ...formulaExpr !== void 0 && { formula: formulaExpr },
323
+ ...embeddedIn !== void 0 && { embeddedIn },
324
+ ...embeddedPropName !== void 0 && { embeddedPropName },
325
+ ...isDiscriminator && { isDiscriminator: true },
326
+ ...enumItems !== void 0 && { enumItems }
327
+ }
328
+ ];
190
329
  }
191
330
  if (prop.kind === ReferenceKind.MANY_TO_ONE || prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {
192
- const fkType = resolveFkType(prop.type, metaByClass);
193
- return {
194
- propName: prop.name,
195
- fieldName: prop.fieldNames?.[0] ?? `${prop.name}_id`,
196
- type: fkType,
197
- isPrimary: false,
198
- isForeignKey: true,
199
- isUnique: prop.unique === true,
200
- isNullable: prop.nullable === true
201
- };
331
+ const cols = buildForeignKeyColumns(prop, metaByClass);
332
+ if (prop.type === owningMeta.className) {
333
+ return cols.map((col) => ({ ...col, isSelfReference: true }));
334
+ }
335
+ return cols;
202
336
  }
203
- return null;
337
+ return [];
204
338
  }
205
339
  function resolveFormulaExpr(cb) {
206
340
  try {
@@ -210,18 +344,47 @@ function resolveFormulaExpr(cb) {
210
344
  get: (_target, key) => typeof key === "string" ? key : ""
211
345
  }
212
346
  );
213
- return cb(FORMULA_DUMMY_TABLE, cols);
347
+ const result = cb(FORMULA_DUMMY_TABLE, cols);
348
+ return typeof result === "string" ? result : String(result);
214
349
  } catch {
215
- return "";
350
+ return UNRESOLVED_FORMULA;
216
351
  }
217
352
  }
218
- function resolveFkType(referencedClassName, metaByClass) {
219
- const refMeta = metaByClass.get(referencedClassName);
353
+ function buildForeignKeyColumns(prop, metaByClass) {
354
+ const fieldNames = prop.fieldNames && prop.fieldNames.length > 0 ? prop.fieldNames : [`${prop.name}_id`];
355
+ const fkTypes = resolveFkTypes(prop, metaByClass, fieldNames.length);
356
+ return fieldNames.map((fieldName, index) => ({
357
+ propName: prop.name,
358
+ fieldName,
359
+ type: fkTypes[index] ?? fkTypes[0] ?? "integer",
360
+ isPrimary: prop.primary === true,
361
+ isForeignKey: true,
362
+ isUnique: prop.unique === true,
363
+ isNullable: prop.nullable === true,
364
+ ...prop.comment !== void 0 && { comment: prop.comment },
365
+ referencedEntity: prop.type
366
+ }));
367
+ }
368
+ function resolveFkTypes(prop, metaByClass, fieldNameCount) {
369
+ const refMeta = metaByClass.get(prop.type);
220
370
  if (!refMeta) {
221
- return "integer";
371
+ return Array.from({ length: fieldNameCount }, () => "integer");
222
372
  }
223
- const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);
224
- return pkProp ? normalizeType(pkProp.type) : "integer";
373
+ const primaryProps = getPrimaryProps(refMeta);
374
+ const referencedColumnNames = prop.referencedColumnNames ?? [];
375
+ return Array.from({ length: fieldNameCount }, (_value, index) => {
376
+ const referencedColumnName = referencedColumnNames[index];
377
+ const pkProp = referencedColumnName !== void 0 ? primaryProps.find((candidate) => candidate.fieldNames.includes(referencedColumnName)) : void 0;
378
+ return (pkProp ?? primaryProps[index] ?? primaryProps[0])?.type ?? "integer";
379
+ });
380
+ }
381
+ function getPrimaryProps(meta) {
382
+ const primaryKeys = meta.primaryKeys ?? [];
383
+ const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
384
+ if (orderedPrimaryProps.length > 0) {
385
+ return orderedPrimaryProps;
386
+ }
387
+ return Object.values(meta.properties).filter((prop) => prop.primary === true);
225
388
  }
226
389
  function buildConstraints(meta) {
227
390
  const result = [];
@@ -229,7 +392,7 @@ function buildConstraints(meta) {
229
392
  const props = idx.properties;
230
393
  result.push({
231
394
  type: "index",
232
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
395
+ properties: resolveConstraintProperties(meta, props),
233
396
  ...idx.name !== void 0 && { name: idx.name }
234
397
  });
235
398
  }
@@ -237,7 +400,7 @@ function buildConstraints(meta) {
237
400
  const props = uniq.properties;
238
401
  result.push({
239
402
  type: "unique",
240
- properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],
403
+ properties: resolveConstraintProperties(meta, props),
241
404
  ...uniq.name !== void 0 && { name: uniq.name }
242
405
  });
243
406
  }
@@ -254,6 +417,19 @@ function buildConstraints(meta) {
254
417
  }
255
418
  return result;
256
419
  }
420
+ function resolveConstraintProperties(meta, props) {
421
+ const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
422
+ return propNames.flatMap((propName) => {
423
+ const prop = meta.properties[String(propName)];
424
+ if (prop === void 0) {
425
+ return [String(propName)];
426
+ }
427
+ if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
428
+ return prop.fieldNames;
429
+ }
430
+ return [prop.name];
431
+ });
432
+ }
257
433
  function buildRelationEdges(metas) {
258
434
  const edges = [];
259
435
  for (const meta of metas) {
@@ -272,6 +448,9 @@ function buildRelationEdges(metas) {
272
448
  function buildEdge(fromEntity, prop) {
273
449
  const isNullable = prop.nullable === true;
274
450
  if (prop.kind === ReferenceKind.MANY_TO_ONE) {
451
+ if (prop.type === fromEntity) {
452
+ return null;
453
+ }
275
454
  return {
276
455
  fromEntity,
277
456
  toEntity: prop.type,
@@ -303,14 +482,16 @@ function buildEdge(fromEntity, prop) {
303
482
  function renderErDiagram(model) {
304
483
  const lines = ["erDiagram"];
305
484
  for (const entity of model.entities) {
306
- lines.push(` ${entity.className} {`);
485
+ lines.push(` ${toMermaidIdentifier(entity.className)} {`);
307
486
  for (const col of entity.columns) {
308
487
  lines.push(` ${renderColumnLine(col)}`);
309
488
  }
310
489
  lines.push(" }");
311
490
  }
312
491
  for (const rel of model.relations) {
313
- lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`);
492
+ lines.push(
493
+ ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
494
+ );
314
495
  }
315
496
  return lines.join("\n");
316
497
  }
@@ -318,8 +499,6 @@ function renderColumnLine(col) {
318
499
  let qualifier = "";
319
500
  if (col.isPrimary) {
320
501
  qualifier = " PK";
321
- } else if (col.isForeignKey) {
322
- qualifier = " FK";
323
502
  } else if (col.isUnique) {
324
503
  qualifier = " UK";
325
504
  }
@@ -330,27 +509,38 @@ function renderColumnLine(col) {
330
509
  comment = "discriminator";
331
510
  } else if (col.embeddedIn !== void 0) {
332
511
  comment = `[${col.embeddedIn}]`;
333
- } else if (col.fieldName !== col.propName) {
334
- comment = col.propName;
512
+ } else if (col.isSelfReference) {
513
+ comment = "self-ref";
335
514
  }
336
- const commentStr = comment !== void 0 ? ` "${comment}"` : "";
337
- return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;
338
- }
339
- function normalizeType(type) {
340
- return type.replace(/[^a-zA-Z0-9_]/g, "_");
515
+ const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
516
+ return `${toMermaidIdentifier(col.type)} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
341
517
  }
342
518
 
343
519
  // src/model/build.ts
344
- function buildDocumentModel(metas, jsDocResult, title, description) {
345
- const { entities: diagramEntities, relations: allRelations } = buildDiagramModel(metas);
520
+ var FROM_ONE_OR_MORE = "}|";
521
+ var TO_ONE_OR_MORE = "|{";
522
+ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
523
+ const { entities: diagramEntities, relations } = buildDiagramModel(metas);
524
+ const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
525
+ const hiddenClasses = /* @__PURE__ */ new Set();
526
+ for (const model of diagramEntities) {
527
+ if (jsDocResult.entities.get(model.className)?.hidden) {
528
+ hiddenClasses.add(model.className);
529
+ }
530
+ }
346
531
  const enrichedByClass = /* @__PURE__ */ new Map();
347
532
  for (const model of diagramEntities) {
348
533
  const jsDoc = jsDocResult.entities.get(model.className);
349
534
  if (jsDoc?.hidden) {
350
535
  continue;
351
536
  }
352
- const propDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
353
- enrichedByClass.set(model.className, { model, jsDoc, propDocs });
537
+ const columns = model.columns.filter(
538
+ (col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
539
+ );
540
+ const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
541
+ const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
542
+ const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
543
+ enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
354
544
  }
355
545
  const groupNames = /* @__PURE__ */ new Set();
356
546
  let anyUntagged = false;
@@ -391,6 +581,65 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
391
581
  });
392
582
  return { title, groups, ...description !== void 0 && { description } };
393
583
  }
584
+ function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
585
+ const merged = new Map(ownPropDocs);
586
+ for (const col of columns) {
587
+ if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
588
+ continue;
589
+ }
590
+ const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
591
+ if (info) {
592
+ merged.set(col.propName, info);
593
+ }
594
+ }
595
+ return merged;
596
+ }
597
+ function applyAtLeastOne(relations, metas, props, onWarn) {
598
+ const adjusted = relations.map((edge) => ({ ...edge }));
599
+ const metaByClass = new Map(metas.map((m) => [m.className, m]));
600
+ for (const [className, propMap] of props) {
601
+ const meta = metaByClass.get(className);
602
+ if (!meta) {
603
+ continue;
604
+ }
605
+ for (const [propName, info] of propMap) {
606
+ if (!info.atLeastOne) {
607
+ continue;
608
+ }
609
+ const prop = meta.properties[propName];
610
+ if (!prop) {
611
+ continue;
612
+ }
613
+ let edge;
614
+ if (prop.kind === ReferenceKind2.ONE_TO_MANY && prop.mappedBy) {
615
+ edge = adjusted.find(
616
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
617
+ );
618
+ if (edge) {
619
+ edge.fromCardinality = FROM_ONE_OR_MORE;
620
+ }
621
+ } else if (prop.kind === ReferenceKind2.MANY_TO_MANY && prop.owner === true) {
622
+ edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
623
+ if (edge) {
624
+ edge.toCardinality = TO_ONE_OR_MORE;
625
+ }
626
+ } else if (prop.kind === ReferenceKind2.MANY_TO_MANY && prop.mappedBy) {
627
+ edge = adjusted.find(
628
+ (e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
629
+ );
630
+ if (edge) {
631
+ edge.fromCardinality = FROM_ONE_OR_MORE;
632
+ }
633
+ }
634
+ if (!edge) {
635
+ onWarn?.(
636
+ `@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.`
637
+ );
638
+ }
639
+ }
640
+ }
641
+ return adjusted;
642
+ }
394
643
  function hasNoNamespaceTags(jsDoc) {
395
644
  if (!jsDoc) {
396
645
  return true;
@@ -416,19 +665,55 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
416
665
  return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
417
666
  }
418
667
 
668
+ // src/provider.ts
669
+ async function withTsMorphMetadataProvider(options, onWarn) {
670
+ if (options.metadataProvider !== void 0) {
671
+ return options;
672
+ }
673
+ try {
674
+ const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
675
+ return { ...options, metadataProvider: TsMorphMetadataProvider };
676
+ } catch (err) {
677
+ const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
678
+ const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
679
+ if (!isNotInstalled && onWarn) {
680
+ onWarn(
681
+ `@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.`
682
+ );
683
+ }
684
+ return options;
685
+ }
686
+ }
687
+
419
688
  // src/render/markdown.ts
420
689
  function renderMarkdown(docModel) {
421
- const sections = [`# ${docModel.title}`];
690
+ const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
422
691
  if (docModel.description) {
423
- sections.push(docModel.description);
692
+ sections.push(escapeMarkdownParagraph(docModel.description));
693
+ }
694
+ if (docModel.groups.length > 1) {
695
+ sections.push(renderTableOfContents(docModel.groups));
424
696
  }
425
697
  for (const group of docModel.groups) {
426
698
  sections.push(renderGroupSection(group));
427
699
  }
428
700
  return sections.join("\n\n");
429
701
  }
702
+ function renderBulletSection(header, items, renderItem) {
703
+ const lines = [header, ""];
704
+ for (const item of items) {
705
+ lines.push(renderItem(item));
706
+ }
707
+ return lines.join("\n");
708
+ }
709
+ function renderTableOfContents(groups) {
710
+ return renderBulletSection("## Contents", groups, (group) => {
711
+ const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
712
+ return `- [${label}](#${toMarkdownAnchor(group.name)})`;
713
+ });
714
+ }
430
715
  function renderGroupSection(group) {
431
- const parts = [`## ${group.name}`];
716
+ const parts = [`## ${escapeMarkdownInline(group.name)}`];
432
717
  if (group.erdEntities.length > 0) {
433
718
  const diagramModel = {
434
719
  entities: group.erdEntities.map((e) => e.model),
@@ -442,14 +727,18 @@ function renderGroupSection(group) {
442
727
  return parts.join("\n\n");
443
728
  }
444
729
  function renderEntitySection(entity) {
445
- const parts = [`### ${entity.model.className}`];
730
+ const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
731
+ parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
446
732
  if (entity.jsDoc?.description) {
447
- parts.push(`> ${entity.jsDoc.description}`);
733
+ parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
448
734
  }
449
735
  if (entity.model.discriminatorColumn) {
450
- parts.push(`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`);
736
+ parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
451
737
  } else if (entity.model.extendsEntity) {
452
- parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
738
+ const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
739
+ parts.push(
740
+ `*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
741
+ );
453
742
  }
454
743
  if (entity.model.columns.length > 0) {
455
744
  parts.push(renderColumnTable(entity));
@@ -457,6 +746,10 @@ function renderEntitySection(entity) {
457
746
  if (entity.model.constraints.length > 0) {
458
747
  parts.push(renderConstraints(entity.model.constraints));
459
748
  }
749
+ const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
750
+ if (computedColumns.length > 0) {
751
+ parts.push(renderComputedColumns(computedColumns));
752
+ }
460
753
  return parts.join("\n\n");
461
754
  }
462
755
  function renderColumnTable(entity) {
@@ -465,24 +758,27 @@ function renderColumnTable(entity) {
465
758
  const rows = entity.model.columns.map((col) => {
466
759
  const key = resolveColumnKey(col);
467
760
  const nullable = col.isNullable && !col.isPrimary ? "Y" : "";
468
- const desc = entity.propDocs.get(col.propName)?.description ?? "";
469
- return `| ${col.fieldName} | ${col.type} | ${key} | ${nullable} | ${desc} |`;
761
+ const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
762
+ const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
763
+ const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
764
+ return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(col.type)} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
470
765
  });
471
766
  return [header, sep, ...rows].join("\n");
472
767
  }
473
768
  function resolveColumnKey(col) {
769
+ const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
770
+ if (col.isPrimary && col.isForeignKey) {
771
+ return `PK, ${fkKey}`;
772
+ }
474
773
  if (col.isPrimary) {
475
774
  return "PK";
476
775
  }
477
776
  if (col.isForeignKey) {
478
- return col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
777
+ return fkKey;
479
778
  }
480
779
  if (col.isUnique) {
481
780
  return "UK";
482
781
  }
483
- if (col.formula !== void 0) {
484
- return `formula: ${col.formula}`;
485
- }
486
782
  if (col.isDiscriminator) {
487
783
  return "discriminator";
488
784
  }
@@ -491,43 +787,227 @@ function resolveColumnKey(col) {
491
787
  }
492
788
  return "";
493
789
  }
790
+ function renderComputedColumns(columns) {
791
+ return renderBulletSection("**Computed columns:**", columns, (col) => {
792
+ const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
793
+ return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
794
+ });
795
+ }
494
796
  function renderConstraints(constraints) {
495
- const lines = ["**Constraints:**", ""];
496
- for (const c of constraints) {
497
- const name = c.name ? ` \`${c.name}\`` : "";
797
+ return renderBulletSection("**Constraints:**", constraints, (c) => {
798
+ const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
799
+ const properties = c.properties.map(escapeMarkdownInline).join(", ");
498
800
  if (c.type === "index") {
499
- lines.push(`- Index${name}: (${c.properties.join(", ")})`);
500
- } else if (c.type === "unique") {
501
- lines.push(`- Unique${name}: (${c.properties.join(", ")})`);
502
- } else if (c.type === "check") {
503
- lines.push(`- Check${name}: \`${c.expression ?? ""}\``);
801
+ return `- Index${name}: (${properties})`;
504
802
  }
505
- }
506
- return lines.join("\n");
803
+ if (c.type === "unique") {
804
+ return `- Unique${name}: (${properties})`;
805
+ }
806
+ return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
807
+ });
507
808
  }
508
809
 
509
810
  // src/index.ts
811
+ var COMPILED_JS = /\.(c|m)?js$/i;
812
+ function resolveJsDocSources(sourcePaths, src, onWarn) {
813
+ if (src !== void 0 && src.length > 0) {
814
+ return src;
815
+ }
816
+ if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
817
+ onWarn(
818
+ '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.'
819
+ );
820
+ }
821
+ return sourcePaths;
822
+ }
823
+ function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
824
+ if (jsDocResult.sourceFileCount === 0) {
825
+ throw new Error(
826
+ `No source files matched the explicit src paths: ${src.join(", ")}
827
+ Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
828
+ );
829
+ }
830
+ const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
831
+ const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
832
+ if (missingConcrete.length > 0) {
833
+ throw new Error(
834
+ `Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
835
+ 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.`
836
+ );
837
+ }
838
+ if (onWarn) {
839
+ const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
840
+ if (missingAbstract.length > 0) {
841
+ onWarn(
842
+ `Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
843
+ @hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
844
+ );
845
+ }
846
+ }
847
+ }
848
+ function errorMessages(err) {
849
+ const messages = [];
850
+ const seen = /* @__PURE__ */ new Set();
851
+ let current = err;
852
+ while (current instanceof Error && !seen.has(current)) {
853
+ seen.add(current);
854
+ messages.push(current.message);
855
+ current = current.cause;
856
+ }
857
+ return messages;
858
+ }
859
+ function isMissingTsMorphSourceFile(err) {
860
+ return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
861
+ }
862
+ async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
863
+ try {
864
+ return await loadEntityMetadata(effectiveOrm);
865
+ } catch (err) {
866
+ const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
867
+ if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
868
+ throw err;
869
+ }
870
+ try {
871
+ return await loadEntityMetadata(originalOrm);
872
+ } catch {
873
+ throw err;
874
+ }
875
+ }
876
+ }
510
877
  async function generateMarkdown(options) {
511
- const { orm, title = "Database Schema", description, src = [] } = options;
512
- const metas = await loadEntityMetadata(orm);
513
- const jsDocResult = loadJsDoc(src);
514
- const docModel = buildDocumentModel(metas, jsDocResult, title, description);
878
+ const { orm, title = "Database Schema", description, src, onWarn } = options;
879
+ const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
880
+ const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
881
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
882
+ if (src !== void 0 && src.length > 0) {
883
+ assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
884
+ }
885
+ const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
515
886
  return renderMarkdown(docModel);
516
887
  }
517
888
 
518
889
  // src/cli.ts
890
+ function toConfigImportSpecifier(configPath) {
891
+ return pathToFileURL(path2.resolve(configPath)).href;
892
+ }
893
+ function findNearestTsconfig(fromPath) {
894
+ let dir = path2.dirname(path2.resolve(fromPath));
895
+ for (; ; ) {
896
+ const candidate = path2.join(dir, "tsconfig.json");
897
+ if (syncFs.existsSync(candidate)) {
898
+ return candidate;
899
+ }
900
+ const parent = path2.dirname(dir);
901
+ if (parent === dir) {
902
+ return void 0;
903
+ }
904
+ dir = parent;
905
+ }
906
+ }
907
+ async function loadOrmOptions(configPath, tsconfigPath) {
908
+ const isTypeScriptConfig = configPath.endsWith(".ts");
909
+ if (isTypeScriptConfig) {
910
+ let register;
911
+ try {
912
+ ({ register } = await import("tsx/esm/api"));
913
+ } catch {
914
+ throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
915
+ }
916
+ let tsconfig;
917
+ if (tsconfigPath !== void 0) {
918
+ tsconfig = path2.resolve(tsconfigPath);
919
+ if (!syncFs.existsSync(tsconfig)) {
920
+ throw new Error(`--tsconfig file not found: ${tsconfig}`);
921
+ }
922
+ } else {
923
+ tsconfig = findNearestTsconfig(configPath);
924
+ }
925
+ register(tsconfig !== void 0 ? { tsconfig } : {});
926
+ }
927
+ const configUrl = toConfigImportSpecifier(configPath);
928
+ let mod;
929
+ try {
930
+ mod = await import(
931
+ /* @vite-ignore */
932
+ configUrl
933
+ );
934
+ } catch (cause) {
935
+ if (isTypeScriptConfig) {
936
+ const detail = cause instanceof Error ? cause.message : String(cause);
937
+ throw new Error(
938
+ `Failed to load TypeScript config.
939
+ ${detail}
940
+
941
+ If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
942
+ Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
943
+ { cause }
944
+ );
945
+ }
946
+ throw cause;
947
+ }
948
+ if (mod.default === void 0) {
949
+ throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
950
+ }
951
+ const config = mod.default;
952
+ if (typeof config === "function" || config instanceof Promise) {
953
+ throw new Error(
954
+ "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)."
955
+ );
956
+ }
957
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
958
+ throw new Error(
959
+ "Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
960
+ );
961
+ }
962
+ const options = config;
963
+ const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
964
+ return withPreferTs;
965
+ }
966
+ function formatErrorChain(err) {
967
+ const lines = [];
968
+ const seen = /* @__PURE__ */ new Set();
969
+ let current = err;
970
+ while (current instanceof Error && !seen.has(current)) {
971
+ seen.add(current);
972
+ lines.push(current.message);
973
+ current = current.cause;
974
+ }
975
+ if (current !== void 0 && !(current instanceof Error)) {
976
+ lines.push(String(current));
977
+ }
978
+ return lines.map((line, i) => i === 0 ? line : ` \u21B3 caused by: ${line}`).join("\n");
979
+ }
980
+ 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.";
981
+ function formatDiscoveryError(err) {
982
+ const chain = formatErrorChain(err);
983
+ return chain.includes("emitDecoratorMetadata") ? chain + REFLECTION_METADATA_HINT : chain;
984
+ }
985
+ function formatFileSystemError(cause) {
986
+ if (cause instanceof Error) {
987
+ const code = "code" in cause && typeof cause.code === "string" ? ` (${cause.code})` : "";
988
+ return `${cause.message}${code}`;
989
+ }
990
+ return String(cause);
991
+ }
992
+ async function writeMarkdownFile(outPath, markdown) {
993
+ try {
994
+ await fs.mkdir(path2.dirname(outPath), { recursive: true });
995
+ await fs.writeFile(outPath, markdown, "utf-8");
996
+ } catch (cause) {
997
+ throw new Error(`Cannot write output file: ${outPath}
998
+ ${formatFileSystemError(cause)}`, { cause });
999
+ }
1000
+ }
519
1001
  async function run(opts) {
520
- const configPath = path.resolve(opts.config);
521
- const outPath = path.resolve(opts.out);
1002
+ const configPath = path2.resolve(opts.config);
1003
+ const outPath = path2.resolve(opts.out);
522
1004
  let ormOptions;
523
1005
  try {
524
- const mod = await import(configPath);
525
- ormOptions = mod.default ?? mod;
1006
+ ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
526
1007
  } catch (err) {
527
- const hint = configPath.endsWith(".ts") ? "\nHint: TypeScript configs require tsx or ts-node:\n npx tsx ./node_modules/.bin/mikro-orm-markdown ..." : "";
528
1008
  process.stderr.write(
529
1009
  `Error: Cannot load config: ${configPath}
530
- ${err instanceof Error ? err.message : String(err)}${hint}
1010
+ ${err instanceof Error ? err.message : String(err)}
531
1011
  `
532
1012
  );
533
1013
  process.exit(1);
@@ -537,23 +1017,57 @@ ${err instanceof Error ? err.message : String(err)}${hint}
537
1017
  markdown = await generateMarkdown({
538
1018
  orm: ormOptions,
539
1019
  title: opts.title,
540
- src: opts.src ?? [],
541
- ...opts.description !== void 0 && { description: opts.description }
1020
+ ...opts.description !== void 0 && { description: opts.description },
1021
+ ...opts.src !== void 0 && { src: opts.src },
1022
+ onWarn: (message) => void process.stderr.write(`Warning: ${message}
1023
+ `)
542
1024
  });
543
1025
  } catch (err) {
544
- const msg = err instanceof MetadataLoadError ? err.message : err instanceof Error ? err.message : String(err);
545
- process.stderr.write(`Error: ${msg}
1026
+ process.stderr.write(`Error: ${formatDiscoveryError(err)}
1027
+ `);
1028
+ process.exit(1);
1029
+ }
1030
+ try {
1031
+ await writeMarkdownFile(outPath, markdown);
1032
+ } catch (err) {
1033
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
546
1034
  `);
547
1035
  process.exit(1);
548
1036
  }
549
- await fs.writeFile(outPath, markdown, "utf-8");
550
- process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
1037
+ process.stdout.write(`\u2713 Written to ${path2.relative(process.cwd(), outPath)}
551
1038
  `);
552
1039
  }
553
- var program = new 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);
554
- program.parseAsync(process.argv).catch((err) => {
555
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}
1040
+ var program = new 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(
1041
+ "--tsconfig <path>",
1042
+ "tsconfig.json to use when loading a .ts config (defaults to the nearest one beside the config file)"
1043
+ ).option(
1044
+ "--src <paths...>",
1045
+ "Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
1046
+ ).action(run);
1047
+ function isDirectCliExecution() {
1048
+ const entryPoint = process.argv[1];
1049
+ if (entryPoint === void 0) {
1050
+ return false;
1051
+ }
1052
+ try {
1053
+ return syncFs.realpathSync(path2.resolve(entryPoint)) === syncFs.realpathSync(fileURLToPath(import.meta.url));
1054
+ } catch {
1055
+ return false;
1056
+ }
1057
+ }
1058
+ if (isDirectCliExecution()) {
1059
+ program.parseAsync(process.argv).catch((err) => {
1060
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
556
1061
  `);
557
- process.exit(1);
558
- });
1062
+ process.exit(1);
1063
+ });
1064
+ }
1065
+ export {
1066
+ findNearestTsconfig,
1067
+ formatDiscoveryError,
1068
+ formatErrorChain,
1069
+ loadOrmOptions,
1070
+ toConfigImportSpecifier,
1071
+ writeMarkdownFile
1072
+ };
559
1073
  //# sourceMappingURL=cli.js.map