mikro-orm-markdown 0.1.0 → 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 +21 -0
- package/README.ko.md +10 -1
- package/README.md +10 -1
- package/dist/cli.cjs +332 -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 +335 -200
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +248 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +250 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,10 +5,10 @@ import * as syncFs from "fs";
|
|
|
5
5
|
import * as fs from "fs/promises";
|
|
6
6
|
import * as path2 from "path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
8
|
-
import { Command } from "commander";
|
|
8
|
+
import { Command, Option } from "commander";
|
|
9
9
|
|
|
10
10
|
// src/docs/jsdoc.ts
|
|
11
|
-
import { Project } from "ts-morph";
|
|
11
|
+
import { Project, ts } from "ts-morph";
|
|
12
12
|
function loadJsDoc(filePaths, onWarn) {
|
|
13
13
|
const entities = /* @__PURE__ */ new Map();
|
|
14
14
|
const props = /* @__PURE__ */ new Map();
|
|
@@ -47,17 +47,7 @@ function loadJsDoc(filePaths, onWarn) {
|
|
|
47
47
|
if (classDocs.length > 0) {
|
|
48
48
|
entities.set(className, parseEntityJsDoc(classDocs));
|
|
49
49
|
}
|
|
50
|
-
const propMap =
|
|
51
|
-
for (const prop of cls.getProperties()) {
|
|
52
|
-
const propDocs = prop.getJsDocs();
|
|
53
|
-
if (propDocs.length === 0) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const info = parsePropJsDoc(propDocs);
|
|
57
|
-
if (info.description !== void 0 || info.atLeastOne) {
|
|
58
|
-
propMap.set(prop.getName(), info);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
50
|
+
const propMap = collectPropJsDocs(cls);
|
|
61
51
|
if (propMap.size > 0) {
|
|
62
52
|
props.set(className, propMap);
|
|
63
53
|
}
|
|
@@ -74,6 +64,28 @@ function formatUnknownError(err) {
|
|
|
74
64
|
function hasGlobPattern(filePath) {
|
|
75
65
|
return /[*?[\]{}]/.test(filePath);
|
|
76
66
|
}
|
|
67
|
+
function collectPropJsDocs(cls) {
|
|
68
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const prop of [...cls.getProperties(), ...cls.getGetAccessors()]) {
|
|
70
|
+
const info = parsePropJsDoc(prop.getJsDocs());
|
|
71
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
72
|
+
}
|
|
73
|
+
for (const prop of getConstructorParameterProperties(cls)) {
|
|
74
|
+
const info = parseCompilerPropJsDoc(ts.getJSDocCommentsAndTags(prop.compilerNode));
|
|
75
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
76
|
+
}
|
|
77
|
+
return propMap;
|
|
78
|
+
}
|
|
79
|
+
function getConstructorParameterProperties(cls) {
|
|
80
|
+
return cls.getConstructors().flatMap(
|
|
81
|
+
(constructorDeclaration) => constructorDeclaration.getParameters().filter((param) => param.isParameterProperty())
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function addPropInfo(propMap, propName, info) {
|
|
85
|
+
if (info.description !== void 0 || info.atLeastOne) {
|
|
86
|
+
propMap.set(propName, info);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
77
89
|
function parseEntityJsDoc(jsDocs) {
|
|
78
90
|
const namespaces = [];
|
|
79
91
|
const erdNamespaces = [];
|
|
@@ -123,10 +135,39 @@ function parsePropJsDoc(jsDocs) {
|
|
|
123
135
|
}
|
|
124
136
|
return { ...description !== void 0 && { description }, atLeastOne };
|
|
125
137
|
}
|
|
138
|
+
function parseCompilerPropJsDoc(jsDocs) {
|
|
139
|
+
let description;
|
|
140
|
+
let atLeastOne = false;
|
|
141
|
+
for (const doc of jsDocs) {
|
|
142
|
+
if (ts.isJSDoc(doc)) {
|
|
143
|
+
const desc = formatCompilerJsDocComment(doc.comment);
|
|
144
|
+
if (desc && description === void 0) {
|
|
145
|
+
description = desc;
|
|
146
|
+
}
|
|
147
|
+
for (const tag of doc.tags ?? []) {
|
|
148
|
+
if (tag.tagName.getText() === "atLeastOne") {
|
|
149
|
+
atLeastOne = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (doc.tagName.getText() === "atLeastOne") {
|
|
155
|
+
atLeastOne = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
159
|
+
}
|
|
160
|
+
function formatCompilerJsDocComment(comment) {
|
|
161
|
+
if (comment === void 0) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
const trimmed = ts.getTextOfJSDocComment(comment)?.trim();
|
|
165
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
166
|
+
}
|
|
126
167
|
|
|
127
168
|
// src/metadata/load.ts
|
|
128
169
|
import * as path from "path";
|
|
129
|
-
import { EntitySchema, MikroORM } from "@mikro-orm/core";
|
|
170
|
+
import { EntitySchema, MetadataStorage, MikroORM } from "@mikro-orm/core";
|
|
130
171
|
var MetadataLoadError = class extends Error {
|
|
131
172
|
constructor(message, cause) {
|
|
132
173
|
super(message);
|
|
@@ -162,6 +203,44 @@ function assertNoEntitySchemaEntities(options) {
|
|
|
162
203
|
Use decorator-based @Entity() classes instead.`
|
|
163
204
|
);
|
|
164
205
|
}
|
|
206
|
+
function hasDecoratorMarker(target) {
|
|
207
|
+
const pathSymbol = MetadataStorage.PATH_SYMBOL;
|
|
208
|
+
if (typeof pathSymbol === "symbol" && pathSymbol in target) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return "__path" in target;
|
|
212
|
+
}
|
|
213
|
+
function isRenderableMeta(meta) {
|
|
214
|
+
return !meta.pivotTable && !meta.embeddable;
|
|
215
|
+
}
|
|
216
|
+
function assertDiscoveredEntitiesAreSupported(metas) {
|
|
217
|
+
const confirmed = [];
|
|
218
|
+
const unconfirmed = [];
|
|
219
|
+
for (const meta of metas) {
|
|
220
|
+
if (!isRenderableMeta(meta)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (EntitySchema.REGISTRY.has(meta.class)) {
|
|
224
|
+
confirmed.push(meta.className);
|
|
225
|
+
} else if (!hasDecoratorMarker(meta.class)) {
|
|
226
|
+
unconfirmed.push(meta.className);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (confirmed.length === 0 && unconfirmed.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const lines = [];
|
|
233
|
+
if (confirmed.length > 0) {
|
|
234
|
+
lines.push(`EntitySchema-defined entities are not currently supported: ${confirmed.join(", ")}.`);
|
|
235
|
+
}
|
|
236
|
+
if (unconfirmed.length > 0) {
|
|
237
|
+
lines.push(
|
|
238
|
+
`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`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
lines.push("Use decorator-based @Entity() classes instead.");
|
|
242
|
+
throw new MetadataLoadError(lines.join("\n"));
|
|
243
|
+
}
|
|
165
244
|
async function loadEntityMetadata(options) {
|
|
166
245
|
assertNoEntitySchemaEntities(options);
|
|
167
246
|
let orm;
|
|
@@ -188,6 +267,7 @@ async function loadEntityMetadata(options) {
|
|
|
188
267
|
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
189
268
|
);
|
|
190
269
|
}
|
|
270
|
+
assertDiscoveredEntitiesAreSupported(all);
|
|
191
271
|
const baseDir = orm.config.get("baseDir");
|
|
192
272
|
const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
|
|
193
273
|
return { metas: all, sourcePaths };
|
|
@@ -199,57 +279,8 @@ async function loadEntityMetadata(options) {
|
|
|
199
279
|
// src/model/build.ts
|
|
200
280
|
import { ReferenceKind as ReferenceKind2 } from "@mikro-orm/core";
|
|
201
281
|
|
|
202
|
-
// src/
|
|
282
|
+
// src/model/diagram.ts
|
|
203
283
|
import { ReferenceKind } from "@mikro-orm/core";
|
|
204
|
-
|
|
205
|
-
// src/render/escape.ts
|
|
206
|
-
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
|
|
207
|
-
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
208
|
-
function normalizeInlineText(value) {
|
|
209
|
-
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
210
|
-
}
|
|
211
|
-
function splitNormalizedLines(value) {
|
|
212
|
-
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
213
|
-
}
|
|
214
|
-
function escapeHtmlText(value) {
|
|
215
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
216
|
-
}
|
|
217
|
-
function escapeMarkdownInline(value) {
|
|
218
|
-
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
|
|
219
|
-
}
|
|
220
|
-
function escapeMarkdownTableCell(value) {
|
|
221
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
222
|
-
}
|
|
223
|
-
function escapeMarkdownParagraph(value) {
|
|
224
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
225
|
-
}
|
|
226
|
-
function renderMarkdownBlockQuote(value) {
|
|
227
|
-
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
228
|
-
}
|
|
229
|
-
function renderMarkdownInlineCode(value) {
|
|
230
|
-
const normalized = normalizeInlineText(value);
|
|
231
|
-
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
232
|
-
const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
|
|
233
|
-
const fence = "`".repeat(longestRun + 1);
|
|
234
|
-
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
235
|
-
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
236
|
-
return `${fence}${content}${fence}`;
|
|
237
|
-
}
|
|
238
|
-
function toMermaidIdentifier(value) {
|
|
239
|
-
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
240
|
-
const identifier = normalized === "" ? "_" : normalized;
|
|
241
|
-
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
242
|
-
}
|
|
243
|
-
function escapeMermaidQuotedText(value) {
|
|
244
|
-
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
245
|
-
}
|
|
246
|
-
function toMarkdownAnchor(value) {
|
|
247
|
-
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// src/render/mermaid.ts
|
|
251
|
-
var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
|
|
252
|
-
var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
|
|
253
284
|
var FORMULA_DUMMY_TABLE = {
|
|
254
285
|
alias: "e0",
|
|
255
286
|
name: "",
|
|
@@ -309,6 +340,9 @@ function buildColumns(prop, metaByClass, owningMeta) {
|
|
|
309
340
|
return [];
|
|
310
341
|
}
|
|
311
342
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
343
|
+
if (prop.persist === false && formulaExpr === void 0) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
312
346
|
let embeddedIn;
|
|
313
347
|
let embeddedPropName;
|
|
314
348
|
if (prop.embedded !== void 0) {
|
|
@@ -495,6 +529,9 @@ function buildEdge(fromEntity, prop) {
|
|
|
495
529
|
};
|
|
496
530
|
}
|
|
497
531
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {
|
|
532
|
+
if (prop.type === fromEntity) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
498
535
|
return {
|
|
499
536
|
fromEntity,
|
|
500
537
|
toEntity: prop.type,
|
|
@@ -514,75 +551,6 @@ function buildEdge(fromEntity, prop) {
|
|
|
514
551
|
}
|
|
515
552
|
return null;
|
|
516
553
|
}
|
|
517
|
-
function normalizeType(type) {
|
|
518
|
-
const t = type.toLowerCase().trim();
|
|
519
|
-
if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
|
|
520
|
-
return "string";
|
|
521
|
-
}
|
|
522
|
-
if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
|
|
523
|
-
return "datetime";
|
|
524
|
-
}
|
|
525
|
-
if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
|
|
526
|
-
return "integer";
|
|
527
|
-
}
|
|
528
|
-
if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
|
|
529
|
-
return "float";
|
|
530
|
-
}
|
|
531
|
-
if (t === "boolean" || t === "bool") {
|
|
532
|
-
return "boolean";
|
|
533
|
-
}
|
|
534
|
-
if (t === "jsonb") {
|
|
535
|
-
return "json";
|
|
536
|
-
}
|
|
537
|
-
return type;
|
|
538
|
-
}
|
|
539
|
-
function renderErDiagram(model, mermaid) {
|
|
540
|
-
const lines = [];
|
|
541
|
-
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
542
|
-
lines.push("---", "config:");
|
|
543
|
-
if (mermaid.layout !== void 0) {
|
|
544
|
-
lines.push(` layout: ${mermaid.layout}`);
|
|
545
|
-
}
|
|
546
|
-
if (mermaid.theme !== void 0) {
|
|
547
|
-
lines.push(` theme: ${mermaid.theme}`);
|
|
548
|
-
}
|
|
549
|
-
lines.push("---");
|
|
550
|
-
}
|
|
551
|
-
lines.push("erDiagram");
|
|
552
|
-
for (const entity of model.entities) {
|
|
553
|
-
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
554
|
-
for (const col of entity.columns) {
|
|
555
|
-
lines.push(` ${renderColumnLine(col)}`);
|
|
556
|
-
}
|
|
557
|
-
lines.push(" }");
|
|
558
|
-
}
|
|
559
|
-
for (const rel of model.relations) {
|
|
560
|
-
lines.push(
|
|
561
|
-
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
return lines.join("\n");
|
|
565
|
-
}
|
|
566
|
-
function renderColumnLine(col) {
|
|
567
|
-
let qualifier = "";
|
|
568
|
-
if (col.isPrimary) {
|
|
569
|
-
qualifier = " PK";
|
|
570
|
-
} else if (col.isUnique) {
|
|
571
|
-
qualifier = " UK";
|
|
572
|
-
}
|
|
573
|
-
let comment;
|
|
574
|
-
if (col.formula !== void 0) {
|
|
575
|
-
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
576
|
-
} else if (col.isDiscriminator) {
|
|
577
|
-
comment = "discriminator";
|
|
578
|
-
} else if (col.embeddedIn !== void 0) {
|
|
579
|
-
comment = `[${col.embeddedIn}]`;
|
|
580
|
-
} else if (col.isSelfReference) {
|
|
581
|
-
comment = "self-ref";
|
|
582
|
-
}
|
|
583
|
-
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
584
|
-
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
585
|
-
}
|
|
586
554
|
|
|
587
555
|
// src/model/build.ts
|
|
588
556
|
var FROM_ONE_OR_MORE = "}|";
|
|
@@ -605,7 +573,10 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
605
573
|
const columns = model.columns.filter(
|
|
606
574
|
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
607
575
|
);
|
|
608
|
-
const visibleModel =
|
|
576
|
+
const visibleModel = removeHiddenEntityReferences(
|
|
577
|
+
columns.length === model.columns.length ? model : { ...model, columns },
|
|
578
|
+
hiddenClasses
|
|
579
|
+
);
|
|
609
580
|
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
610
581
|
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
611
582
|
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
@@ -656,6 +627,14 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
656
627
|
});
|
|
657
628
|
return { title, groups, ...description !== void 0 && { description } };
|
|
658
629
|
}
|
|
630
|
+
function removeHiddenEntityReferences(model, hiddenClasses) {
|
|
631
|
+
if (model.extendsEntity === void 0 || !hiddenClasses.has(model.extendsEntity)) {
|
|
632
|
+
return model;
|
|
633
|
+
}
|
|
634
|
+
const visibleModel = { ...model };
|
|
635
|
+
delete visibleModel.extendsEntity;
|
|
636
|
+
return visibleModel;
|
|
637
|
+
}
|
|
659
638
|
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
660
639
|
const merged = new Map(ownPropDocs);
|
|
661
640
|
for (const col of columns) {
|
|
@@ -767,6 +746,144 @@ async function withTsMorphMetadataProvider(options, onWarn) {
|
|
|
767
746
|
}
|
|
768
747
|
}
|
|
769
748
|
|
|
749
|
+
// src/render/escape.ts
|
|
750
|
+
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#\[\]]/g;
|
|
751
|
+
var MARKDOWN_EMPHASIS_UNDERSCORE = /(?<![a-zA-Z0-9])_|_(?![a-zA-Z0-9])/g;
|
|
752
|
+
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
753
|
+
function normalizeInlineText(value) {
|
|
754
|
+
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
755
|
+
}
|
|
756
|
+
function splitNormalizedLines(value) {
|
|
757
|
+
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
758
|
+
}
|
|
759
|
+
function escapeHtmlText(value) {
|
|
760
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
761
|
+
}
|
|
762
|
+
function escapeMarkdownInline(value) {
|
|
763
|
+
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&").replace(MARKDOWN_EMPHASIS_UNDERSCORE, "\\_");
|
|
764
|
+
}
|
|
765
|
+
function escapeMarkdownTableCell(value) {
|
|
766
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
767
|
+
}
|
|
768
|
+
function escapeMarkdownParagraph(value) {
|
|
769
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
770
|
+
}
|
|
771
|
+
function renderMarkdownBlockQuote(value) {
|
|
772
|
+
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
773
|
+
}
|
|
774
|
+
function renderMarkdownInlineCode(value) {
|
|
775
|
+
const normalized = normalizeInlineText(value);
|
|
776
|
+
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
777
|
+
const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
|
|
778
|
+
const fence = "`".repeat(longestRun + 1);
|
|
779
|
+
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
780
|
+
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
781
|
+
return `${fence}${content}${fence}`;
|
|
782
|
+
}
|
|
783
|
+
function toMermaidIdentifier(value) {
|
|
784
|
+
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
785
|
+
const identifier = normalized === "" ? "_" : normalized;
|
|
786
|
+
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
787
|
+
}
|
|
788
|
+
function escapeMermaidQuotedText(value) {
|
|
789
|
+
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
790
|
+
}
|
|
791
|
+
function toMarkdownAnchor(value) {
|
|
792
|
+
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/render/mermaid.ts
|
|
796
|
+
var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
|
|
797
|
+
var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
|
|
798
|
+
var GENERIC_TYPE_BY_BASE_NAME = /* @__PURE__ */ new Map([
|
|
799
|
+
["uuid", "string"],
|
|
800
|
+
["text", "string"],
|
|
801
|
+
["string", "string"],
|
|
802
|
+
["varchar", "string"],
|
|
803
|
+
["character varying", "string"],
|
|
804
|
+
["character", "string"],
|
|
805
|
+
["char", "string"],
|
|
806
|
+
["tinytext", "string"],
|
|
807
|
+
["mediumtext", "string"],
|
|
808
|
+
["longtext", "string"],
|
|
809
|
+
["timestamptz", "datetime"],
|
|
810
|
+
["timestamp", "datetime"],
|
|
811
|
+
["datetime", "datetime"],
|
|
812
|
+
["integer", "integer"],
|
|
813
|
+
["int", "integer"],
|
|
814
|
+
["bigint", "integer"],
|
|
815
|
+
["smallint", "integer"],
|
|
816
|
+
["tinyint", "integer"],
|
|
817
|
+
["mediumint", "integer"],
|
|
818
|
+
["serial", "integer"],
|
|
819
|
+
["bigserial", "integer"],
|
|
820
|
+
["doubletype", "float"],
|
|
821
|
+
["double precision", "float"],
|
|
822
|
+
["double", "float"],
|
|
823
|
+
["float", "float"],
|
|
824
|
+
["decimal", "float"],
|
|
825
|
+
["numeric", "float"],
|
|
826
|
+
["real", "float"],
|
|
827
|
+
["boolean", "boolean"],
|
|
828
|
+
["bool", "boolean"],
|
|
829
|
+
["jsonb", "json"]
|
|
830
|
+
]);
|
|
831
|
+
function normalizeType(type) {
|
|
832
|
+
const t = type.toLowerCase().replace(/\s+/g, " ").trim();
|
|
833
|
+
if (/^tinyint\s*\(\s*1\s*\)$/.test(t)) {
|
|
834
|
+
return "boolean";
|
|
835
|
+
}
|
|
836
|
+
const baseName = t.replace(/\(.*\)/, "").replace(/\b(un)?signed\b/, "").trim();
|
|
837
|
+
return GENERIC_TYPE_BY_BASE_NAME.get(baseName) ?? type;
|
|
838
|
+
}
|
|
839
|
+
function renderErDiagram(model, mermaid) {
|
|
840
|
+
const lines = [];
|
|
841
|
+
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
842
|
+
lines.push("---", "config:");
|
|
843
|
+
if (mermaid.layout !== void 0) {
|
|
844
|
+
lines.push(` layout: ${mermaid.layout}`);
|
|
845
|
+
}
|
|
846
|
+
if (mermaid.theme !== void 0) {
|
|
847
|
+
lines.push(` theme: ${mermaid.theme}`);
|
|
848
|
+
}
|
|
849
|
+
lines.push("---");
|
|
850
|
+
}
|
|
851
|
+
lines.push("erDiagram");
|
|
852
|
+
for (const entity of model.entities) {
|
|
853
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
854
|
+
for (const col of entity.columns) {
|
|
855
|
+
lines.push(` ${renderColumnLine(col)}`);
|
|
856
|
+
}
|
|
857
|
+
lines.push(" }");
|
|
858
|
+
}
|
|
859
|
+
for (const rel of model.relations) {
|
|
860
|
+
lines.push(
|
|
861
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return lines.join("\n");
|
|
865
|
+
}
|
|
866
|
+
function renderColumnLine(col) {
|
|
867
|
+
let qualifier = "";
|
|
868
|
+
if (col.isPrimary) {
|
|
869
|
+
qualifier = " PK";
|
|
870
|
+
} else if (col.isUnique) {
|
|
871
|
+
qualifier = " UK";
|
|
872
|
+
}
|
|
873
|
+
let comment;
|
|
874
|
+
if (col.formula !== void 0) {
|
|
875
|
+
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
876
|
+
} else if (col.isDiscriminator) {
|
|
877
|
+
comment = "discriminator";
|
|
878
|
+
} else if (col.embeddedIn !== void 0) {
|
|
879
|
+
comment = `[${col.embeddedIn}]`;
|
|
880
|
+
} else if (col.isSelfReference) {
|
|
881
|
+
comment = "self-ref";
|
|
882
|
+
}
|
|
883
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
884
|
+
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
885
|
+
}
|
|
886
|
+
|
|
770
887
|
// src/render/markdown.ts
|
|
771
888
|
function renderMarkdown(docModel, mermaid) {
|
|
772
889
|
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
@@ -790,7 +907,7 @@ function renderBulletSection(header, items, renderItem) {
|
|
|
790
907
|
}
|
|
791
908
|
function renderTableOfContents(groups, anchors) {
|
|
792
909
|
return renderBulletSection("## Contents", groups, (group) => {
|
|
793
|
-
const label = escapeMarkdownInline(group.name)
|
|
910
|
+
const label = escapeMarkdownInline(group.name);
|
|
794
911
|
return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
|
|
795
912
|
});
|
|
796
913
|
}
|
|
@@ -875,7 +992,7 @@ function resolveColumnKey(col) {
|
|
|
875
992
|
return "PK";
|
|
876
993
|
}
|
|
877
994
|
if (col.isForeignKey) {
|
|
878
|
-
return fkKey;
|
|
995
|
+
return col.isUnique ? `${fkKey}, UK` : fkKey;
|
|
879
996
|
}
|
|
880
997
|
if (col.isUnique) {
|
|
881
998
|
return "UK";
|
|
@@ -988,6 +1105,12 @@ async function generateMarkdown(options) {
|
|
|
988
1105
|
}
|
|
989
1106
|
|
|
990
1107
|
// src/cli.ts
|
|
1108
|
+
var activeTsxUnregister;
|
|
1109
|
+
async function unregisterActiveTsxLoader() {
|
|
1110
|
+
const unregister = activeTsxUnregister;
|
|
1111
|
+
activeTsxUnregister = void 0;
|
|
1112
|
+
await unregister?.();
|
|
1113
|
+
}
|
|
991
1114
|
function toConfigImportSpecifier(configPath) {
|
|
992
1115
|
return pathToFileURL(path2.resolve(configPath)).href;
|
|
993
1116
|
}
|
|
@@ -1005,64 +1128,78 @@ function findNearestTsconfig(fromPath) {
|
|
|
1005
1128
|
dir = parent;
|
|
1006
1129
|
}
|
|
1007
1130
|
}
|
|
1008
|
-
async function loadOrmOptions(configPath, tsconfigPath) {
|
|
1131
|
+
async function loadOrmOptions(configPath, tsconfigPath, loadOptions = {}) {
|
|
1009
1132
|
const isTypeScriptConfig = configPath.endsWith(".ts");
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
try {
|
|
1013
|
-
({ register } = await import("tsx/esm/api"));
|
|
1014
|
-
} catch {
|
|
1015
|
-
throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
|
|
1016
|
-
}
|
|
1017
|
-
let tsconfig;
|
|
1018
|
-
if (tsconfigPath !== void 0) {
|
|
1019
|
-
tsconfig = path2.resolve(tsconfigPath);
|
|
1020
|
-
if (!syncFs.existsSync(tsconfig)) {
|
|
1021
|
-
throw new Error(`--tsconfig file not found: ${tsconfig}`);
|
|
1022
|
-
}
|
|
1023
|
-
} else {
|
|
1024
|
-
tsconfig = findNearestTsconfig(configPath);
|
|
1025
|
-
}
|
|
1026
|
-
register(tsconfig !== void 0 ? { tsconfig } : {});
|
|
1027
|
-
}
|
|
1028
|
-
const configUrl = toConfigImportSpecifier(configPath);
|
|
1029
|
-
let mod;
|
|
1133
|
+
let unregisterTsx;
|
|
1134
|
+
let shouldKeepTsxRegistered = false;
|
|
1030
1135
|
try {
|
|
1031
|
-
mod = await import(
|
|
1032
|
-
/* @vite-ignore */
|
|
1033
|
-
configUrl
|
|
1034
|
-
);
|
|
1035
|
-
} catch (cause) {
|
|
1036
1136
|
if (isTypeScriptConfig) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1137
|
+
await unregisterActiveTsxLoader();
|
|
1138
|
+
let register;
|
|
1139
|
+
try {
|
|
1140
|
+
({ register } = await import("tsx/esm/api"));
|
|
1141
|
+
} catch {
|
|
1142
|
+
throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
|
|
1143
|
+
}
|
|
1144
|
+
let tsconfig;
|
|
1145
|
+
if (tsconfigPath !== void 0) {
|
|
1146
|
+
tsconfig = path2.resolve(tsconfigPath);
|
|
1147
|
+
if (!syncFs.existsSync(tsconfig)) {
|
|
1148
|
+
throw new Error(`--tsconfig file not found: ${tsconfig}`);
|
|
1149
|
+
}
|
|
1150
|
+
} else {
|
|
1151
|
+
tsconfig = findNearestTsconfig(configPath);
|
|
1152
|
+
}
|
|
1153
|
+
unregisterTsx = register(tsconfig !== void 0 ? { tsconfig } : {});
|
|
1154
|
+
}
|
|
1155
|
+
const configUrl = toConfigImportSpecifier(configPath);
|
|
1156
|
+
let mod;
|
|
1157
|
+
try {
|
|
1158
|
+
mod = await import(
|
|
1159
|
+
/* @vite-ignore */
|
|
1160
|
+
configUrl
|
|
1161
|
+
);
|
|
1162
|
+
} catch (cause) {
|
|
1163
|
+
if (isTypeScriptConfig) {
|
|
1164
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1165
|
+
throw new Error(
|
|
1166
|
+
`Failed to load TypeScript config.
|
|
1040
1167
|
${detail}
|
|
1041
1168
|
|
|
1042
1169
|
If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
|
|
1043
1170
|
Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
|
|
1044
|
-
|
|
1171
|
+
{ cause }
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
throw cause;
|
|
1175
|
+
}
|
|
1176
|
+
if (mod.default === void 0) {
|
|
1177
|
+
throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
|
|
1178
|
+
}
|
|
1179
|
+
const config = mod.default;
|
|
1180
|
+
if (typeof config === "function" || config instanceof Promise) {
|
|
1181
|
+
throw new Error(
|
|
1182
|
+
"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)."
|
|
1045
1183
|
);
|
|
1046
1184
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1185
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
"Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
const options = config;
|
|
1191
|
+
const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
|
|
1192
|
+
shouldKeepTsxRegistered = loadOptions.keepTsxRegistered === true;
|
|
1193
|
+
return withPreferTs;
|
|
1194
|
+
} finally {
|
|
1195
|
+
if (unregisterTsx !== void 0) {
|
|
1196
|
+
if (shouldKeepTsxRegistered) {
|
|
1197
|
+
activeTsxUnregister = unregisterTsx;
|
|
1198
|
+
} else {
|
|
1199
|
+
await unregisterTsx();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1062
1202
|
}
|
|
1063
|
-
const options = config;
|
|
1064
|
-
const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
|
|
1065
|
-
return withPreferTs;
|
|
1066
1203
|
}
|
|
1067
1204
|
function formatErrorChain(err) {
|
|
1068
1205
|
const lines = [];
|
|
@@ -1101,20 +1238,6 @@ ${formatFileSystemError(cause)}`, { cause });
|
|
|
1101
1238
|
}
|
|
1102
1239
|
function parseMermaidOptions(opts) {
|
|
1103
1240
|
const { mermaidLayout, mermaidTheme } = opts;
|
|
1104
|
-
if (mermaidLayout !== void 0 && !MERMAID_LAYOUTS.includes(mermaidLayout)) {
|
|
1105
|
-
process.stderr.write(
|
|
1106
|
-
`Error: Invalid --mermaid-layout "${mermaidLayout}". Allowed values: ${MERMAID_LAYOUTS.join(", ")}
|
|
1107
|
-
`
|
|
1108
|
-
);
|
|
1109
|
-
process.exit(1);
|
|
1110
|
-
}
|
|
1111
|
-
if (mermaidTheme !== void 0 && !MERMAID_THEMES.includes(mermaidTheme)) {
|
|
1112
|
-
process.stderr.write(
|
|
1113
|
-
`Error: Invalid --mermaid-theme "${mermaidTheme}". Allowed values: ${MERMAID_THEMES.join(", ")}
|
|
1114
|
-
`
|
|
1115
|
-
);
|
|
1116
|
-
process.exit(1);
|
|
1117
|
-
}
|
|
1118
1241
|
if (mermaidLayout === void 0 && mermaidTheme === void 0) {
|
|
1119
1242
|
return void 0;
|
|
1120
1243
|
}
|
|
@@ -1129,7 +1252,7 @@ async function run(opts) {
|
|
|
1129
1252
|
const mermaid = parseMermaidOptions(opts);
|
|
1130
1253
|
let ormOptions;
|
|
1131
1254
|
try {
|
|
1132
|
-
ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
|
|
1255
|
+
ormOptions = await loadOrmOptions(configPath, opts.tsconfig, { keepTsxRegistered: true });
|
|
1133
1256
|
} catch (err) {
|
|
1134
1257
|
process.stderr.write(
|
|
1135
1258
|
`Error: Cannot load config: ${configPath}
|
|
@@ -1150,10 +1273,12 @@ ${err instanceof Error ? err.message : String(err)}
|
|
|
1150
1273
|
`)
|
|
1151
1274
|
});
|
|
1152
1275
|
} catch (err) {
|
|
1276
|
+
await unregisterActiveTsxLoader();
|
|
1153
1277
|
process.stderr.write(`Error: ${formatDiscoveryError(err)}
|
|
1154
1278
|
`);
|
|
1155
1279
|
process.exit(1);
|
|
1156
1280
|
}
|
|
1281
|
+
await unregisterActiveTsxLoader();
|
|
1157
1282
|
try {
|
|
1158
1283
|
await writeMarkdownFile(outPath, markdown);
|
|
1159
1284
|
} catch (err) {
|
|
@@ -1170,7 +1295,17 @@ var program = new Command().name("mikro-orm-markdown").description("Generate Mer
|
|
|
1170
1295
|
).option(
|
|
1171
1296
|
"--src <paths...>",
|
|
1172
1297
|
"Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
|
|
1173
|
-
).
|
|
1298
|
+
).addOption(
|
|
1299
|
+
new Option(
|
|
1300
|
+
"--mermaid-layout <layout>",
|
|
1301
|
+
`Mermaid layout engine injected as frontmatter (${MERMAID_LAYOUTS.join("|")})`
|
|
1302
|
+
).choices(MERMAID_LAYOUTS)
|
|
1303
|
+
).addOption(
|
|
1304
|
+
new Option(
|
|
1305
|
+
"--mermaid-theme <theme>",
|
|
1306
|
+
`Mermaid theme injected as frontmatter (${MERMAID_THEMES.join("|")})`
|
|
1307
|
+
).choices(MERMAID_THEMES)
|
|
1308
|
+
).action(run);
|
|
1174
1309
|
function isDirectCliExecution() {
|
|
1175
1310
|
const entryPoint = process.argv[1];
|
|
1176
1311
|
if (entryPoint === void 0) {
|