mikro-orm-markdown 0.1.0-alpha.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
 
10
10
  // src/docs/jsdoc.ts
11
11
  import { Project } from "ts-morph";
12
- function loadJsDoc(filePaths) {
12
+ function loadJsDoc(filePaths, onWarn) {
13
13
  const entities = /* @__PURE__ */ new Map();
14
14
  const props = /* @__PURE__ */ new Map();
15
15
  const classNames = /* @__PURE__ */ new Set();
@@ -26,8 +26,12 @@ function loadJsDoc(filePaths) {
26
26
  });
27
27
  for (const filePath of filePaths) {
28
28
  try {
29
- project.addSourceFilesAtPaths(filePath);
30
- } catch {
29
+ const sourceFiles2 = project.addSourceFilesAtPaths(filePath);
30
+ if (sourceFiles2.length === 0 && !hasGlobPattern(filePath)) {
31
+ onWarn?.(`No JSDoc source file matched path: ${filePath}`);
32
+ }
33
+ } catch (err) {
34
+ onWarn?.(`Could not load JSDoc source path "${filePath}": ${formatUnknownError(err)}`);
31
35
  }
32
36
  }
33
37
  const sourceFiles = project.getSourceFiles();
@@ -58,11 +62,18 @@ function loadJsDoc(filePaths) {
58
62
  props.set(className, propMap);
59
63
  }
60
64
  }
61
- } catch {
65
+ } catch (err) {
66
+ onWarn?.(`Could not parse JSDoc source file "${sourceFile.getFilePath()}": ${formatUnknownError(err)}`);
62
67
  }
63
68
  }
64
69
  return { entities, props, sourceFileCount: sourceFiles.length, classNames };
65
70
  }
71
+ function formatUnknownError(err) {
72
+ return err instanceof Error ? err.message : String(err);
73
+ }
74
+ function hasGlobPattern(filePath) {
75
+ return /[*?[\]{}]/.test(filePath);
76
+ }
66
77
  function parseEntityJsDoc(jsDocs) {
67
78
  const namespaces = [];
68
79
  const erdNamespaces = [];
@@ -237,6 +248,8 @@ function toMarkdownAnchor(value) {
237
248
  }
238
249
 
239
250
  // src/render/mermaid.ts
251
+ var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
252
+ var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
240
253
  var FORMULA_DUMMY_TABLE = {
241
254
  alias: "e0",
242
255
  name: "",
@@ -523,8 +536,19 @@ function normalizeType(type) {
523
536
  }
524
537
  return type;
525
538
  }
526
- function renderErDiagram(model) {
527
- const lines = ["erDiagram"];
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");
528
552
  for (const entity of model.entities) {
529
553
  lines.push(` ${toMermaidIdentifier(entity.className)} {`);
530
554
  for (const col of entity.columns) {
@@ -719,7 +743,8 @@ function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
719
743
  if (isDefault || !jsDoc) {
720
744
  return false;
721
745
  }
722
- return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
746
+ const hasHomeNamespace = jsDoc.namespaces.length > 0 || jsDoc.describeNamespaces.length > 0;
747
+ return hasHomeNamespace && jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
723
748
  }
724
749
 
725
750
  // src/provider.ts
@@ -743,16 +768,16 @@ async function withTsMorphMetadataProvider(options, onWarn) {
743
768
  }
744
769
 
745
770
  // src/render/markdown.ts
746
- function renderMarkdown(docModel) {
771
+ function renderMarkdown(docModel, mermaid) {
747
772
  const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
748
773
  if (docModel.description) {
749
774
  sections.push(escapeMarkdownParagraph(docModel.description));
750
775
  }
751
776
  if (docModel.groups.length > 1) {
752
- sections.push(renderTableOfContents(docModel.groups));
777
+ sections.push(renderTableOfContents(docModel.groups, resolveGroupAnchors(docModel)));
753
778
  }
754
779
  for (const group of docModel.groups) {
755
- sections.push(renderGroupSection(group));
780
+ sections.push(renderGroupSection(group, mermaid));
756
781
  }
757
782
  return sections.join("\n\n");
758
783
  }
@@ -763,20 +788,39 @@ function renderBulletSection(header, items, renderItem) {
763
788
  }
764
789
  return lines.join("\n");
765
790
  }
766
- function renderTableOfContents(groups) {
791
+ function renderTableOfContents(groups, anchors) {
767
792
  return renderBulletSection("## Contents", groups, (group) => {
768
793
  const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
769
- return `- [${label}](#${toMarkdownAnchor(group.name)})`;
794
+ return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
770
795
  });
771
796
  }
772
- function renderGroupSection(group) {
797
+ function resolveGroupAnchors(docModel) {
798
+ const seenAnchors = /* @__PURE__ */ new Map();
799
+ const groupAnchors = /* @__PURE__ */ new Map();
800
+ resolveHeadingAnchor(docModel.title, seenAnchors);
801
+ resolveHeadingAnchor("Contents", seenAnchors);
802
+ for (const group of docModel.groups) {
803
+ groupAnchors.set(group, resolveHeadingAnchor(group.name, seenAnchors));
804
+ for (const entity of group.textEntities) {
805
+ resolveHeadingAnchor(entity.model.className, seenAnchors);
806
+ }
807
+ }
808
+ return groupAnchors;
809
+ }
810
+ function resolveHeadingAnchor(value, seenAnchors) {
811
+ const baseAnchor = toMarkdownAnchor(value);
812
+ const previousCount = seenAnchors.get(baseAnchor) ?? 0;
813
+ seenAnchors.set(baseAnchor, previousCount + 1);
814
+ return previousCount === 0 ? baseAnchor : `${baseAnchor}-${previousCount}`;
815
+ }
816
+ function renderGroupSection(group, mermaid) {
773
817
  const parts = [`## ${escapeMarkdownInline(group.name)}`];
774
818
  if (group.erdEntities.length > 0) {
775
819
  const diagramModel = {
776
820
  entities: group.erdEntities.map((e) => e.model),
777
821
  relations: group.erdRelations
778
822
  };
779
- parts.push("```mermaid\n" + renderErDiagram(diagramModel) + "\n```");
823
+ parts.push("```mermaid\n" + renderErDiagram(diagramModel, mermaid) + "\n```");
780
824
  }
781
825
  for (const entity of group.textEntities) {
782
826
  parts.push(renderEntitySection(entity));
@@ -932,15 +976,15 @@ async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm)
932
976
  }
933
977
  }
934
978
  async function generateMarkdown(options) {
935
- const { orm, title = "Database Schema", description, src, onWarn } = options;
979
+ const { orm, title = "Database Schema", description, src, onWarn, mermaid } = options;
936
980
  const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
937
981
  const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
938
- const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
982
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn), onWarn);
939
983
  if (src !== void 0 && src.length > 0) {
940
984
  assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
941
985
  }
942
986
  const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
943
- return renderMarkdown(docModel);
987
+ return renderMarkdown(docModel, mermaid);
944
988
  }
945
989
 
946
990
  // src/cli.ts
@@ -1055,9 +1099,34 @@ async function writeMarkdownFile(outPath, markdown) {
1055
1099
  ${formatFileSystemError(cause)}`, { cause });
1056
1100
  }
1057
1101
  }
1102
+ function parseMermaidOptions(opts) {
1103
+ 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
+ if (mermaidLayout === void 0 && mermaidTheme === void 0) {
1119
+ return void 0;
1120
+ }
1121
+ return {
1122
+ ...mermaidLayout !== void 0 && { layout: mermaidLayout },
1123
+ ...mermaidTheme !== void 0 && { theme: mermaidTheme }
1124
+ };
1125
+ }
1058
1126
  async function run(opts) {
1059
1127
  const configPath = path2.resolve(opts.config);
1060
1128
  const outPath = path2.resolve(opts.out);
1129
+ const mermaid = parseMermaidOptions(opts);
1061
1130
  let ormOptions;
1062
1131
  try {
1063
1132
  ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
@@ -1076,6 +1145,7 @@ ${err instanceof Error ? err.message : String(err)}
1076
1145
  title: opts.title,
1077
1146
  ...opts.description !== void 0 && { description: opts.description },
1078
1147
  ...opts.src !== void 0 && { src: opts.src },
1148
+ ...mermaid !== void 0 && { mermaid },
1079
1149
  onWarn: (message) => void process.stderr.write(`Warning: ${message}
1080
1150
  `)
1081
1151
  });
@@ -1100,7 +1170,7 @@ var program = new Command().name("mikro-orm-markdown").description("Generate Mer
1100
1170
  ).option(
1101
1171
  "--src <paths...>",
1102
1172
  "Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
1103
- ).action(run);
1173
+ ).option("--mermaid-layout <layout>", `Mermaid layout engine injected as frontmatter (${MERMAID_LAYOUTS.join("|")})`).option("--mermaid-theme <theme>", `Mermaid theme injected as frontmatter (${MERMAID_THEMES.join("|")})`).action(run);
1104
1174
  function isDirectCliExecution() {
1105
1175
  const entryPoint = process.argv[1];
1106
1176
  if (entryPoint === void 0) {