mikro-orm-markdown 0.1.1 → 0.1.2
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/CHANGELOG.md +14 -0
- package/dist/cli.cjs +326 -197
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -2
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +329 -200
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +242 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +244 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.2] - 2026-07-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Hidden STI parent entity names no longer leak into child entity captions when the parent is excluded with `@hidden`
|
|
13
|
+
- JSDoc extraction now includes getter accessors and constructor parameter properties, including rich inline JSDoc text such as `{@link ...}`
|
|
14
|
+
- Markdown escaping now treats underscore emphasis and link-like labels consistently while preserving identifier-style names such as `author_id`
|
|
15
|
+
- CLI TypeScript config loading now unregisters the temporary `tsx` loader when it is no longer needed
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Mermaid layout and theme CLI options now use Commander's built-in choice validation
|
|
20
|
+
- Diagram model construction was moved into the model layer so Mermaid rendering remains focused on output formatting
|
|
21
|
+
|
|
8
22
|
## [0.1.1] - 2026-07-03
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/dist/cli.cjs
CHANGED
|
@@ -91,17 +91,7 @@ function loadJsDoc(filePaths, onWarn) {
|
|
|
91
91
|
if (classDocs.length > 0) {
|
|
92
92
|
entities.set(className, parseEntityJsDoc(classDocs));
|
|
93
93
|
}
|
|
94
|
-
const propMap =
|
|
95
|
-
for (const prop of cls.getProperties()) {
|
|
96
|
-
const propDocs = prop.getJsDocs();
|
|
97
|
-
if (propDocs.length === 0) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const info = parsePropJsDoc(propDocs);
|
|
101
|
-
if (info.description !== void 0 || info.atLeastOne) {
|
|
102
|
-
propMap.set(prop.getName(), info);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
94
|
+
const propMap = collectPropJsDocs(cls);
|
|
105
95
|
if (propMap.size > 0) {
|
|
106
96
|
props.set(className, propMap);
|
|
107
97
|
}
|
|
@@ -118,6 +108,28 @@ function formatUnknownError(err) {
|
|
|
118
108
|
function hasGlobPattern(filePath) {
|
|
119
109
|
return /[*?[\]{}]/.test(filePath);
|
|
120
110
|
}
|
|
111
|
+
function collectPropJsDocs(cls) {
|
|
112
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const prop of [...cls.getProperties(), ...cls.getGetAccessors()]) {
|
|
114
|
+
const info = parsePropJsDoc(prop.getJsDocs());
|
|
115
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
116
|
+
}
|
|
117
|
+
for (const prop of getConstructorParameterProperties(cls)) {
|
|
118
|
+
const info = parseCompilerPropJsDoc(import_ts_morph.ts.getJSDocCommentsAndTags(prop.compilerNode));
|
|
119
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
120
|
+
}
|
|
121
|
+
return propMap;
|
|
122
|
+
}
|
|
123
|
+
function getConstructorParameterProperties(cls) {
|
|
124
|
+
return cls.getConstructors().flatMap(
|
|
125
|
+
(constructorDeclaration) => constructorDeclaration.getParameters().filter((param) => param.isParameterProperty())
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function addPropInfo(propMap, propName, info) {
|
|
129
|
+
if (info.description !== void 0 || info.atLeastOne) {
|
|
130
|
+
propMap.set(propName, info);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
121
133
|
function parseEntityJsDoc(jsDocs) {
|
|
122
134
|
const namespaces = [];
|
|
123
135
|
const erdNamespaces = [];
|
|
@@ -167,6 +179,35 @@ function parsePropJsDoc(jsDocs) {
|
|
|
167
179
|
}
|
|
168
180
|
return { ...description !== void 0 && { description }, atLeastOne };
|
|
169
181
|
}
|
|
182
|
+
function parseCompilerPropJsDoc(jsDocs) {
|
|
183
|
+
let description;
|
|
184
|
+
let atLeastOne = false;
|
|
185
|
+
for (const doc of jsDocs) {
|
|
186
|
+
if (import_ts_morph.ts.isJSDoc(doc)) {
|
|
187
|
+
const desc = formatCompilerJsDocComment(doc.comment);
|
|
188
|
+
if (desc && description === void 0) {
|
|
189
|
+
description = desc;
|
|
190
|
+
}
|
|
191
|
+
for (const tag of doc.tags ?? []) {
|
|
192
|
+
if (tag.tagName.getText() === "atLeastOne") {
|
|
193
|
+
atLeastOne = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (doc.tagName.getText() === "atLeastOne") {
|
|
199
|
+
atLeastOne = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
203
|
+
}
|
|
204
|
+
function formatCompilerJsDocComment(comment) {
|
|
205
|
+
if (comment === void 0) {
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
const trimmed = import_ts_morph.ts.getTextOfJSDocComment(comment)?.trim();
|
|
209
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
210
|
+
}
|
|
170
211
|
|
|
171
212
|
// src/metadata/load.ts
|
|
172
213
|
var path = __toESM(require("path"), 1);
|
|
@@ -206,6 +247,44 @@ function assertNoEntitySchemaEntities(options) {
|
|
|
206
247
|
Use decorator-based @Entity() classes instead.`
|
|
207
248
|
);
|
|
208
249
|
}
|
|
250
|
+
function hasDecoratorMarker(target) {
|
|
251
|
+
const pathSymbol = import_core.MetadataStorage.PATH_SYMBOL;
|
|
252
|
+
if (typeof pathSymbol === "symbol" && pathSymbol in target) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return "__path" in target;
|
|
256
|
+
}
|
|
257
|
+
function isRenderableMeta(meta) {
|
|
258
|
+
return !meta.pivotTable && !meta.embeddable;
|
|
259
|
+
}
|
|
260
|
+
function assertDiscoveredEntitiesAreSupported(metas) {
|
|
261
|
+
const confirmed = [];
|
|
262
|
+
const unconfirmed = [];
|
|
263
|
+
for (const meta of metas) {
|
|
264
|
+
if (!isRenderableMeta(meta)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (import_core.EntitySchema.REGISTRY.has(meta.class)) {
|
|
268
|
+
confirmed.push(meta.className);
|
|
269
|
+
} else if (!hasDecoratorMarker(meta.class)) {
|
|
270
|
+
unconfirmed.push(meta.className);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (confirmed.length === 0 && unconfirmed.length === 0) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const lines = [];
|
|
277
|
+
if (confirmed.length > 0) {
|
|
278
|
+
lines.push(`EntitySchema-defined entities are not currently supported: ${confirmed.join(", ")}.`);
|
|
279
|
+
}
|
|
280
|
+
if (unconfirmed.length > 0) {
|
|
281
|
+
lines.push(
|
|
282
|
+
`Could not confirm these entities are decorator-based @Entity() classes: ${unconfirmed.join(", ")}. This usually means they are EntitySchema-defined entities (also not currently supported). If you're certain these are valid @Entity() classes, this may be a detection false positive in mikro-orm-markdown \u2014 please open an issue: https://github.com/iamkanguk97/mikro-orm-markdown/issues`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
lines.push("Use decorator-based @Entity() classes instead.");
|
|
286
|
+
throw new MetadataLoadError(lines.join("\n"));
|
|
287
|
+
}
|
|
209
288
|
async function loadEntityMetadata(options) {
|
|
210
289
|
assertNoEntitySchemaEntities(options);
|
|
211
290
|
let orm;
|
|
@@ -232,6 +311,7 @@ async function loadEntityMetadata(options) {
|
|
|
232
311
|
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
233
312
|
);
|
|
234
313
|
}
|
|
314
|
+
assertDiscoveredEntitiesAreSupported(all);
|
|
235
315
|
const baseDir = orm.config.get("baseDir");
|
|
236
316
|
const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
|
|
237
317
|
return { metas: all, sourcePaths };
|
|
@@ -243,57 +323,8 @@ async function loadEntityMetadata(options) {
|
|
|
243
323
|
// src/model/build.ts
|
|
244
324
|
var import_core3 = require("@mikro-orm/core");
|
|
245
325
|
|
|
246
|
-
// src/
|
|
326
|
+
// src/model/diagram.ts
|
|
247
327
|
var import_core2 = require("@mikro-orm/core");
|
|
248
|
-
|
|
249
|
-
// src/render/escape.ts
|
|
250
|
-
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
|
|
251
|
-
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
252
|
-
function normalizeInlineText(value) {
|
|
253
|
-
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
254
|
-
}
|
|
255
|
-
function splitNormalizedLines(value) {
|
|
256
|
-
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
257
|
-
}
|
|
258
|
-
function escapeHtmlText(value) {
|
|
259
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
260
|
-
}
|
|
261
|
-
function escapeMarkdownInline(value) {
|
|
262
|
-
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
|
|
263
|
-
}
|
|
264
|
-
function escapeMarkdownTableCell(value) {
|
|
265
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
266
|
-
}
|
|
267
|
-
function escapeMarkdownParagraph(value) {
|
|
268
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
269
|
-
}
|
|
270
|
-
function renderMarkdownBlockQuote(value) {
|
|
271
|
-
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
272
|
-
}
|
|
273
|
-
function renderMarkdownInlineCode(value) {
|
|
274
|
-
const normalized = normalizeInlineText(value);
|
|
275
|
-
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
276
|
-
const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
|
|
277
|
-
const fence = "`".repeat(longestRun + 1);
|
|
278
|
-
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
279
|
-
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
280
|
-
return `${fence}${content}${fence}`;
|
|
281
|
-
}
|
|
282
|
-
function toMermaidIdentifier(value) {
|
|
283
|
-
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
284
|
-
const identifier = normalized === "" ? "_" : normalized;
|
|
285
|
-
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
286
|
-
}
|
|
287
|
-
function escapeMermaidQuotedText(value) {
|
|
288
|
-
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
289
|
-
}
|
|
290
|
-
function toMarkdownAnchor(value) {
|
|
291
|
-
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// src/render/mermaid.ts
|
|
295
|
-
var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
|
|
296
|
-
var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
|
|
297
328
|
var FORMULA_DUMMY_TABLE = {
|
|
298
329
|
alias: "e0",
|
|
299
330
|
name: "",
|
|
@@ -564,75 +595,6 @@ function buildEdge(fromEntity, prop) {
|
|
|
564
595
|
}
|
|
565
596
|
return null;
|
|
566
597
|
}
|
|
567
|
-
function normalizeType(type) {
|
|
568
|
-
const t = type.toLowerCase().trim();
|
|
569
|
-
if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
|
|
570
|
-
return "string";
|
|
571
|
-
}
|
|
572
|
-
if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
|
|
573
|
-
return "datetime";
|
|
574
|
-
}
|
|
575
|
-
if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
|
|
576
|
-
return "integer";
|
|
577
|
-
}
|
|
578
|
-
if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
|
|
579
|
-
return "float";
|
|
580
|
-
}
|
|
581
|
-
if (t === "boolean" || t === "bool") {
|
|
582
|
-
return "boolean";
|
|
583
|
-
}
|
|
584
|
-
if (t === "jsonb") {
|
|
585
|
-
return "json";
|
|
586
|
-
}
|
|
587
|
-
return type;
|
|
588
|
-
}
|
|
589
|
-
function renderErDiagram(model, mermaid) {
|
|
590
|
-
const lines = [];
|
|
591
|
-
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
592
|
-
lines.push("---", "config:");
|
|
593
|
-
if (mermaid.layout !== void 0) {
|
|
594
|
-
lines.push(` layout: ${mermaid.layout}`);
|
|
595
|
-
}
|
|
596
|
-
if (mermaid.theme !== void 0) {
|
|
597
|
-
lines.push(` theme: ${mermaid.theme}`);
|
|
598
|
-
}
|
|
599
|
-
lines.push("---");
|
|
600
|
-
}
|
|
601
|
-
lines.push("erDiagram");
|
|
602
|
-
for (const entity of model.entities) {
|
|
603
|
-
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
604
|
-
for (const col of entity.columns) {
|
|
605
|
-
lines.push(` ${renderColumnLine(col)}`);
|
|
606
|
-
}
|
|
607
|
-
lines.push(" }");
|
|
608
|
-
}
|
|
609
|
-
for (const rel of model.relations) {
|
|
610
|
-
lines.push(
|
|
611
|
-
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
return lines.join("\n");
|
|
615
|
-
}
|
|
616
|
-
function renderColumnLine(col) {
|
|
617
|
-
let qualifier = "";
|
|
618
|
-
if (col.isPrimary) {
|
|
619
|
-
qualifier = " PK";
|
|
620
|
-
} else if (col.isUnique) {
|
|
621
|
-
qualifier = " UK";
|
|
622
|
-
}
|
|
623
|
-
let comment;
|
|
624
|
-
if (col.formula !== void 0) {
|
|
625
|
-
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
626
|
-
} else if (col.isDiscriminator) {
|
|
627
|
-
comment = "discriminator";
|
|
628
|
-
} else if (col.embeddedIn !== void 0) {
|
|
629
|
-
comment = `[${col.embeddedIn}]`;
|
|
630
|
-
} else if (col.isSelfReference) {
|
|
631
|
-
comment = "self-ref";
|
|
632
|
-
}
|
|
633
|
-
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
634
|
-
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
635
|
-
}
|
|
636
598
|
|
|
637
599
|
// src/model/build.ts
|
|
638
600
|
var FROM_ONE_OR_MORE = "}|";
|
|
@@ -655,7 +617,10 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
655
617
|
const columns = model.columns.filter(
|
|
656
618
|
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
657
619
|
);
|
|
658
|
-
const visibleModel =
|
|
620
|
+
const visibleModel = removeHiddenEntityReferences(
|
|
621
|
+
columns.length === model.columns.length ? model : { ...model, columns },
|
|
622
|
+
hiddenClasses
|
|
623
|
+
);
|
|
659
624
|
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
660
625
|
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
661
626
|
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
@@ -706,6 +671,14 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
706
671
|
});
|
|
707
672
|
return { title, groups, ...description !== void 0 && { description } };
|
|
708
673
|
}
|
|
674
|
+
function removeHiddenEntityReferences(model, hiddenClasses) {
|
|
675
|
+
if (model.extendsEntity === void 0 || !hiddenClasses.has(model.extendsEntity)) {
|
|
676
|
+
return model;
|
|
677
|
+
}
|
|
678
|
+
const visibleModel = { ...model };
|
|
679
|
+
delete visibleModel.extendsEntity;
|
|
680
|
+
return visibleModel;
|
|
681
|
+
}
|
|
709
682
|
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
710
683
|
const merged = new Map(ownPropDocs);
|
|
711
684
|
for (const col of columns) {
|
|
@@ -817,6 +790,144 @@ async function withTsMorphMetadataProvider(options, onWarn) {
|
|
|
817
790
|
}
|
|
818
791
|
}
|
|
819
792
|
|
|
793
|
+
// src/render/escape.ts
|
|
794
|
+
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#\[\]]/g;
|
|
795
|
+
var MARKDOWN_EMPHASIS_UNDERSCORE = /(?<![a-zA-Z0-9])_|_(?![a-zA-Z0-9])/g;
|
|
796
|
+
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
797
|
+
function normalizeInlineText(value) {
|
|
798
|
+
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
799
|
+
}
|
|
800
|
+
function splitNormalizedLines(value) {
|
|
801
|
+
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
802
|
+
}
|
|
803
|
+
function escapeHtmlText(value) {
|
|
804
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
805
|
+
}
|
|
806
|
+
function escapeMarkdownInline(value) {
|
|
807
|
+
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&").replace(MARKDOWN_EMPHASIS_UNDERSCORE, "\\_");
|
|
808
|
+
}
|
|
809
|
+
function escapeMarkdownTableCell(value) {
|
|
810
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
811
|
+
}
|
|
812
|
+
function escapeMarkdownParagraph(value) {
|
|
813
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
814
|
+
}
|
|
815
|
+
function renderMarkdownBlockQuote(value) {
|
|
816
|
+
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
817
|
+
}
|
|
818
|
+
function renderMarkdownInlineCode(value) {
|
|
819
|
+
const normalized = normalizeInlineText(value);
|
|
820
|
+
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
821
|
+
const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
|
|
822
|
+
const fence = "`".repeat(longestRun + 1);
|
|
823
|
+
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
824
|
+
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
825
|
+
return `${fence}${content}${fence}`;
|
|
826
|
+
}
|
|
827
|
+
function toMermaidIdentifier(value) {
|
|
828
|
+
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
829
|
+
const identifier = normalized === "" ? "_" : normalized;
|
|
830
|
+
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
831
|
+
}
|
|
832
|
+
function escapeMermaidQuotedText(value) {
|
|
833
|
+
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
834
|
+
}
|
|
835
|
+
function toMarkdownAnchor(value) {
|
|
836
|
+
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/render/mermaid.ts
|
|
840
|
+
var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
|
|
841
|
+
var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
|
|
842
|
+
var GENERIC_TYPE_BY_BASE_NAME = /* @__PURE__ */ new Map([
|
|
843
|
+
["uuid", "string"],
|
|
844
|
+
["text", "string"],
|
|
845
|
+
["string", "string"],
|
|
846
|
+
["varchar", "string"],
|
|
847
|
+
["character varying", "string"],
|
|
848
|
+
["character", "string"],
|
|
849
|
+
["char", "string"],
|
|
850
|
+
["tinytext", "string"],
|
|
851
|
+
["mediumtext", "string"],
|
|
852
|
+
["longtext", "string"],
|
|
853
|
+
["timestamptz", "datetime"],
|
|
854
|
+
["timestamp", "datetime"],
|
|
855
|
+
["datetime", "datetime"],
|
|
856
|
+
["integer", "integer"],
|
|
857
|
+
["int", "integer"],
|
|
858
|
+
["bigint", "integer"],
|
|
859
|
+
["smallint", "integer"],
|
|
860
|
+
["tinyint", "integer"],
|
|
861
|
+
["mediumint", "integer"],
|
|
862
|
+
["serial", "integer"],
|
|
863
|
+
["bigserial", "integer"],
|
|
864
|
+
["doubletype", "float"],
|
|
865
|
+
["double precision", "float"],
|
|
866
|
+
["double", "float"],
|
|
867
|
+
["float", "float"],
|
|
868
|
+
["decimal", "float"],
|
|
869
|
+
["numeric", "float"],
|
|
870
|
+
["real", "float"],
|
|
871
|
+
["boolean", "boolean"],
|
|
872
|
+
["bool", "boolean"],
|
|
873
|
+
["jsonb", "json"]
|
|
874
|
+
]);
|
|
875
|
+
function normalizeType(type) {
|
|
876
|
+
const t = type.toLowerCase().replace(/\s+/g, " ").trim();
|
|
877
|
+
if (/^tinyint\s*\(\s*1\s*\)$/.test(t)) {
|
|
878
|
+
return "boolean";
|
|
879
|
+
}
|
|
880
|
+
const baseName = t.replace(/\(.*\)/, "").replace(/\b(un)?signed\b/, "").trim();
|
|
881
|
+
return GENERIC_TYPE_BY_BASE_NAME.get(baseName) ?? type;
|
|
882
|
+
}
|
|
883
|
+
function renderErDiagram(model, mermaid) {
|
|
884
|
+
const lines = [];
|
|
885
|
+
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
886
|
+
lines.push("---", "config:");
|
|
887
|
+
if (mermaid.layout !== void 0) {
|
|
888
|
+
lines.push(` layout: ${mermaid.layout}`);
|
|
889
|
+
}
|
|
890
|
+
if (mermaid.theme !== void 0) {
|
|
891
|
+
lines.push(` theme: ${mermaid.theme}`);
|
|
892
|
+
}
|
|
893
|
+
lines.push("---");
|
|
894
|
+
}
|
|
895
|
+
lines.push("erDiagram");
|
|
896
|
+
for (const entity of model.entities) {
|
|
897
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
898
|
+
for (const col of entity.columns) {
|
|
899
|
+
lines.push(` ${renderColumnLine(col)}`);
|
|
900
|
+
}
|
|
901
|
+
lines.push(" }");
|
|
902
|
+
}
|
|
903
|
+
for (const rel of model.relations) {
|
|
904
|
+
lines.push(
|
|
905
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
return lines.join("\n");
|
|
909
|
+
}
|
|
910
|
+
function renderColumnLine(col) {
|
|
911
|
+
let qualifier = "";
|
|
912
|
+
if (col.isPrimary) {
|
|
913
|
+
qualifier = " PK";
|
|
914
|
+
} else if (col.isUnique) {
|
|
915
|
+
qualifier = " UK";
|
|
916
|
+
}
|
|
917
|
+
let comment;
|
|
918
|
+
if (col.formula !== void 0) {
|
|
919
|
+
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
920
|
+
} else if (col.isDiscriminator) {
|
|
921
|
+
comment = "discriminator";
|
|
922
|
+
} else if (col.embeddedIn !== void 0) {
|
|
923
|
+
comment = `[${col.embeddedIn}]`;
|
|
924
|
+
} else if (col.isSelfReference) {
|
|
925
|
+
comment = "self-ref";
|
|
926
|
+
}
|
|
927
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
928
|
+
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
929
|
+
}
|
|
930
|
+
|
|
820
931
|
// src/render/markdown.ts
|
|
821
932
|
function renderMarkdown(docModel, mermaid) {
|
|
822
933
|
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
@@ -840,7 +951,7 @@ function renderBulletSection(header, items, renderItem) {
|
|
|
840
951
|
}
|
|
841
952
|
function renderTableOfContents(groups, anchors) {
|
|
842
953
|
return renderBulletSection("## Contents", groups, (group) => {
|
|
843
|
-
const label = escapeMarkdownInline(group.name)
|
|
954
|
+
const label = escapeMarkdownInline(group.name);
|
|
844
955
|
return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
|
|
845
956
|
});
|
|
846
957
|
}
|
|
@@ -925,7 +1036,7 @@ function resolveColumnKey(col) {
|
|
|
925
1036
|
return "PK";
|
|
926
1037
|
}
|
|
927
1038
|
if (col.isForeignKey) {
|
|
928
|
-
return fkKey;
|
|
1039
|
+
return col.isUnique ? `${fkKey}, UK` : fkKey;
|
|
929
1040
|
}
|
|
930
1041
|
if (col.isUnique) {
|
|
931
1042
|
return "UK";
|
|
@@ -1038,6 +1149,12 @@ async function generateMarkdown(options) {
|
|
|
1038
1149
|
}
|
|
1039
1150
|
|
|
1040
1151
|
// src/cli.ts
|
|
1152
|
+
var activeTsxUnregister;
|
|
1153
|
+
async function unregisterActiveTsxLoader() {
|
|
1154
|
+
const unregister = activeTsxUnregister;
|
|
1155
|
+
activeTsxUnregister = void 0;
|
|
1156
|
+
await unregister?.();
|
|
1157
|
+
}
|
|
1041
1158
|
function toConfigImportSpecifier(configPath) {
|
|
1042
1159
|
return (0, import_node_url.pathToFileURL)(path2.resolve(configPath)).href;
|
|
1043
1160
|
}
|
|
@@ -1055,64 +1172,78 @@ function findNearestTsconfig(fromPath) {
|
|
|
1055
1172
|
dir = parent;
|
|
1056
1173
|
}
|
|
1057
1174
|
}
|
|
1058
|
-
async function loadOrmOptions(configPath, tsconfigPath) {
|
|
1175
|
+
async function loadOrmOptions(configPath, tsconfigPath, loadOptions = {}) {
|
|
1059
1176
|
const isTypeScriptConfig = configPath.endsWith(".ts");
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
try {
|
|
1063
|
-
({ register } = await import("tsx/esm/api"));
|
|
1064
|
-
} catch {
|
|
1065
|
-
throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
|
|
1066
|
-
}
|
|
1067
|
-
let tsconfig;
|
|
1068
|
-
if (tsconfigPath !== void 0) {
|
|
1069
|
-
tsconfig = path2.resolve(tsconfigPath);
|
|
1070
|
-
if (!syncFs.existsSync(tsconfig)) {
|
|
1071
|
-
throw new Error(`--tsconfig file not found: ${tsconfig}`);
|
|
1072
|
-
}
|
|
1073
|
-
} else {
|
|
1074
|
-
tsconfig = findNearestTsconfig(configPath);
|
|
1075
|
-
}
|
|
1076
|
-
register(tsconfig !== void 0 ? { tsconfig } : {});
|
|
1077
|
-
}
|
|
1078
|
-
const configUrl = toConfigImportSpecifier(configPath);
|
|
1079
|
-
let mod;
|
|
1177
|
+
let unregisterTsx;
|
|
1178
|
+
let shouldKeepTsxRegistered = false;
|
|
1080
1179
|
try {
|
|
1081
|
-
mod = await import(
|
|
1082
|
-
/* @vite-ignore */
|
|
1083
|
-
configUrl
|
|
1084
|
-
);
|
|
1085
|
-
} catch (cause) {
|
|
1086
1180
|
if (isTypeScriptConfig) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1181
|
+
await unregisterActiveTsxLoader();
|
|
1182
|
+
let register;
|
|
1183
|
+
try {
|
|
1184
|
+
({ register } = await import("tsx/esm/api"));
|
|
1185
|
+
} catch {
|
|
1186
|
+
throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
|
|
1187
|
+
}
|
|
1188
|
+
let tsconfig;
|
|
1189
|
+
if (tsconfigPath !== void 0) {
|
|
1190
|
+
tsconfig = path2.resolve(tsconfigPath);
|
|
1191
|
+
if (!syncFs.existsSync(tsconfig)) {
|
|
1192
|
+
throw new Error(`--tsconfig file not found: ${tsconfig}`);
|
|
1193
|
+
}
|
|
1194
|
+
} else {
|
|
1195
|
+
tsconfig = findNearestTsconfig(configPath);
|
|
1196
|
+
}
|
|
1197
|
+
unregisterTsx = register(tsconfig !== void 0 ? { tsconfig } : {});
|
|
1198
|
+
}
|
|
1199
|
+
const configUrl = toConfigImportSpecifier(configPath);
|
|
1200
|
+
let mod;
|
|
1201
|
+
try {
|
|
1202
|
+
mod = await import(
|
|
1203
|
+
/* @vite-ignore */
|
|
1204
|
+
configUrl
|
|
1205
|
+
);
|
|
1206
|
+
} catch (cause) {
|
|
1207
|
+
if (isTypeScriptConfig) {
|
|
1208
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
`Failed to load TypeScript config.
|
|
1090
1211
|
${detail}
|
|
1091
1212
|
|
|
1092
1213
|
If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
|
|
1093
1214
|
Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
|
|
1094
|
-
|
|
1215
|
+
{ cause }
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
throw cause;
|
|
1219
|
+
}
|
|
1220
|
+
if (mod.default === void 0) {
|
|
1221
|
+
throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
|
|
1222
|
+
}
|
|
1223
|
+
const config = mod.default;
|
|
1224
|
+
if (typeof config === "function" || config instanceof Promise) {
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
"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)."
|
|
1095
1227
|
);
|
|
1096
1228
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1229
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
"Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
const options = config;
|
|
1235
|
+
const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
|
|
1236
|
+
shouldKeepTsxRegistered = loadOptions.keepTsxRegistered === true;
|
|
1237
|
+
return withPreferTs;
|
|
1238
|
+
} finally {
|
|
1239
|
+
if (unregisterTsx !== void 0) {
|
|
1240
|
+
if (shouldKeepTsxRegistered) {
|
|
1241
|
+
activeTsxUnregister = unregisterTsx;
|
|
1242
|
+
} else {
|
|
1243
|
+
await unregisterTsx();
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1112
1246
|
}
|
|
1113
|
-
const options = config;
|
|
1114
|
-
const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
|
|
1115
|
-
return withPreferTs;
|
|
1116
1247
|
}
|
|
1117
1248
|
function formatErrorChain(err) {
|
|
1118
1249
|
const lines = [];
|
|
@@ -1151,20 +1282,6 @@ ${formatFileSystemError(cause)}`, { cause });
|
|
|
1151
1282
|
}
|
|
1152
1283
|
function parseMermaidOptions(opts) {
|
|
1153
1284
|
const { mermaidLayout, mermaidTheme } = opts;
|
|
1154
|
-
if (mermaidLayout !== void 0 && !MERMAID_LAYOUTS.includes(mermaidLayout)) {
|
|
1155
|
-
process.stderr.write(
|
|
1156
|
-
`Error: Invalid --mermaid-layout "${mermaidLayout}". Allowed values: ${MERMAID_LAYOUTS.join(", ")}
|
|
1157
|
-
`
|
|
1158
|
-
);
|
|
1159
|
-
process.exit(1);
|
|
1160
|
-
}
|
|
1161
|
-
if (mermaidTheme !== void 0 && !MERMAID_THEMES.includes(mermaidTheme)) {
|
|
1162
|
-
process.stderr.write(
|
|
1163
|
-
`Error: Invalid --mermaid-theme "${mermaidTheme}". Allowed values: ${MERMAID_THEMES.join(", ")}
|
|
1164
|
-
`
|
|
1165
|
-
);
|
|
1166
|
-
process.exit(1);
|
|
1167
|
-
}
|
|
1168
1285
|
if (mermaidLayout === void 0 && mermaidTheme === void 0) {
|
|
1169
1286
|
return void 0;
|
|
1170
1287
|
}
|
|
@@ -1179,7 +1296,7 @@ async function run(opts) {
|
|
|
1179
1296
|
const mermaid = parseMermaidOptions(opts);
|
|
1180
1297
|
let ormOptions;
|
|
1181
1298
|
try {
|
|
1182
|
-
ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
|
|
1299
|
+
ormOptions = await loadOrmOptions(configPath, opts.tsconfig, { keepTsxRegistered: true });
|
|
1183
1300
|
} catch (err) {
|
|
1184
1301
|
process.stderr.write(
|
|
1185
1302
|
`Error: Cannot load config: ${configPath}
|
|
@@ -1200,10 +1317,12 @@ ${err instanceof Error ? err.message : String(err)}
|
|
|
1200
1317
|
`)
|
|
1201
1318
|
});
|
|
1202
1319
|
} catch (err) {
|
|
1320
|
+
await unregisterActiveTsxLoader();
|
|
1203
1321
|
process.stderr.write(`Error: ${formatDiscoveryError(err)}
|
|
1204
1322
|
`);
|
|
1205
1323
|
process.exit(1);
|
|
1206
1324
|
}
|
|
1325
|
+
await unregisterActiveTsxLoader();
|
|
1207
1326
|
try {
|
|
1208
1327
|
await writeMarkdownFile(outPath, markdown);
|
|
1209
1328
|
} catch (err) {
|
|
@@ -1220,7 +1339,17 @@ var program = new import_commander.Command().name("mikro-orm-markdown").descript
|
|
|
1220
1339
|
).option(
|
|
1221
1340
|
"--src <paths...>",
|
|
1222
1341
|
"Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
|
|
1223
|
-
).
|
|
1342
|
+
).addOption(
|
|
1343
|
+
new import_commander.Option(
|
|
1344
|
+
"--mermaid-layout <layout>",
|
|
1345
|
+
`Mermaid layout engine injected as frontmatter (${MERMAID_LAYOUTS.join("|")})`
|
|
1346
|
+
).choices(MERMAID_LAYOUTS)
|
|
1347
|
+
).addOption(
|
|
1348
|
+
new import_commander.Option(
|
|
1349
|
+
"--mermaid-theme <theme>",
|
|
1350
|
+
`Mermaid theme injected as frontmatter (${MERMAID_THEMES.join("|")})`
|
|
1351
|
+
).choices(MERMAID_THEMES)
|
|
1352
|
+
).action(run);
|
|
1224
1353
|
function isDirectCliExecution() {
|
|
1225
1354
|
const entryPoint = process.argv[1];
|
|
1226
1355
|
if (entryPoint === void 0) {
|