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