mikro-orm-markdown 0.1.0-alpha.5 → 0.1.1

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 CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.1] - 2026-07-03
9
+
10
+ ### Fixed
11
+
12
+ - Scalar properties with `persist: false` (MikroORM shadow properties) are no longer rendered as if they were real database columns in the ERD or the Markdown column table; `@Formula` columns (also `persist: false` internally) are unaffected and continue to render
13
+ - A self-referencing owning 1:1 relation (e.g. `Node.twin: Node`) no longer renders a redundant self-loop relation edge in addition to the existing `"self-ref"` column comment — now matches the existing self-referencing m:1 behavior
14
+
15
+ ## [0.1.0] - 2026-06-30
16
+
17
+ First stable release. All alpha features are considered production-ready.
18
+
19
+ ### Added
20
+
21
+ - `AGENTS.md` with project overview, source layout, development commands, and conventions for AI agent context
22
+ - Planned Features section in README (en/ko) documenting `--check` mode, `--watch` mode, and `EntitySchema` support as upcoming work
23
+
8
24
  ## [0.1.0-alpha.5] - 2026-06-29
9
25
 
10
26
  ### Added
package/README.ko.md CHANGED
@@ -1,13 +1,22 @@
1
1
  # mikro-orm-markdown
2
2
 
3
+ <p align="center">
4
+ <img src="./assets/logo.png" alt="mikro-orm-markdown logo" width="400" />
5
+ </p>
6
+
3
7
  [MikroORM](https://mikro-orm.io) 엔티티에서 **Mermaid ERD + Markdown 문서**를 자동으로 생성합니다.
4
8
 
5
- [![npm version](https://badge.fury.io/js/mikro-orm-markdown.svg)](https://badge.fury.io/js/mikro-orm-markdown)
9
+ [![npm version](https://img.shields.io/npm/v/mikro-orm-markdown)](https://www.npmjs.com/package/mikro-orm-markdown)
10
+ [![npm downloads](https://img.shields.io/npm/dm/mikro-orm-markdown)](https://www.npmjs.com/package/mikro-orm-markdown)
6
11
  [![CI](https://github.com/iamkanguk97/mikro-orm-markdown/actions/workflows/ci.yml/badge.svg)](https://github.com/iamkanguk97/mikro-orm-markdown/actions)
7
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
13
 
9
14
  [English](./README.md)
10
15
 
16
+ 예시 출력은 [여기](./examples/ERD.md)에서 확인하실 수 있습니다.
17
+
18
+ Claude Code 또는 기타 Agent Skills 호환 도구를 사용한다면 `skills/mikro-orm-markdown/`의 선택적 Agent Skill로 MikroORM 프로젝트에 `mikro-orm-markdown`을 설정하고 문제를 진단할 수 있습니다. 이 Skill은 필요한 사용자가 저장소에서 가져가 쓰도록 제공되며, 일반적인 npm 사용에는 필요하지 않고 npm 패키지에는 포함되지 않습니다.
19
+
11
20
  > [@samchon](https://github.com/samchon)의 [prisma-markdown](https://github.com/samchon/prisma-markdown)에서 큰 영감을 받았습니다. 좋은 아이디어에 감사드립니다.
12
21
 
13
22
  ## 주요 기능
@@ -216,7 +225,7 @@ erDiagram
216
225
  Post {
217
226
  integer id PK
218
227
  string title
219
- text body
228
+ string body
220
229
  integer author_id FK
221
230
  }
222
231
  Author {
@@ -453,6 +462,40 @@ erDiagram
453
462
 
454
463
  > `elk`와 theme 지원 여부는 뷰어마다 다릅니다. `--mermaid-layout`이나 `--mermaid-theme`을 지정하지 않으면 frontmatter는 출력되지 않습니다.
455
464
 
465
+ ## 추후 구현 예정 기능
466
+
467
+ 다음 기능들은 향후 릴리즈에서 지원할 예정입니다.
468
+
469
+ ### `--check` 모드
470
+
471
+ 출력 파일이 현재 엔티티 메타데이터와 다를 경우 non-zero 코드로 종료하는 `--check` 플래그입니다. 파일을 직접 수정하지 않으며, CI 파이프라인에서 `ERD.md`가 엔티티 변경 사항과 함께 항상 커밋되었는지 검증하는 용도로 사용합니다.
472
+
473
+ ```bash
474
+ mikro-orm-markdown --config ./mikro-orm.config.ts --check
475
+ ```
476
+
477
+ ### `--watch` 모드
478
+
479
+ 엔티티 소스 파일을 감지하여 변경이 발생할 때마다 출력 문서를 자동으로 재생성하는 `--watch` 플래그입니다. CLI를 매번 수동으로 실행하지 않아도 ERD를 최신 상태로 유지할 수 있어 개발 중에 유용합니다.
480
+
481
+ ```bash
482
+ mikro-orm-markdown --config ./mikro-orm.config.ts --watch
483
+ ```
484
+
485
+ ### `EntitySchema` 지원
486
+
487
+ 현재는 decorator 기반 엔티티(`@Entity()` 클래스)만 지원합니다. 향후 릴리즈에서는 TypeScript decorator 없이 Plain Object로 스키마를 정의하는 [`EntitySchema`](https://mikro-orm.io/docs/entity-schema) 방식도 지원할 예정입니다.
488
+
489
+ ```typescript
490
+ const PostSchema = new EntitySchema({
491
+ name: 'Post',
492
+ properties: {
493
+ id: { primary: true, type: 'integer' },
494
+ title: { type: 'string' },
495
+ },
496
+ });
497
+ ```
498
+
456
499
  ## 라이선스
457
500
 
458
501
  MIT
package/README.md CHANGED
@@ -1,13 +1,22 @@
1
1
  # mikro-orm-markdown
2
2
 
3
+ <p align="center">
4
+ <img src="./assets/logo.png" alt="mikro-orm-markdown logo" width="400" />
5
+ </p>
6
+
3
7
  Generate **Mermaid ERD + Markdown documentation** from your [MikroORM](https://mikro-orm.io) entities.
4
8
 
5
- [![npm version](https://badge.fury.io/js/mikro-orm-markdown.svg)](https://badge.fury.io/js/mikro-orm-markdown)
9
+ [![npm version](https://img.shields.io/npm/v/mikro-orm-markdown)](https://www.npmjs.com/package/mikro-orm-markdown)
10
+ [![npm downloads](https://img.shields.io/npm/dm/mikro-orm-markdown)](https://www.npmjs.com/package/mikro-orm-markdown)
6
11
  [![CI](https://github.com/iamkanguk97/mikro-orm-markdown/actions/workflows/ci.yml/badge.svg)](https://github.com/iamkanguk97/mikro-orm-markdown/actions)
7
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
13
 
9
14
  [한국어 문서](./README.ko.md)
10
15
 
16
+ You can see the example output [here](./examples/ERD.md).
17
+
18
+ Agent users can also use the optional Agent Skill at `skills/mikro-orm-markdown/` with Claude Code or other Agent Skills-compatible tools to set up and troubleshoot `mikro-orm-markdown` in a MikroORM project. The skill is provided in this repository for users who want it; it is not required for normal npm usage and is not shipped in the npm package.
19
+
11
20
  > Heavily inspired by [prisma-markdown](https://github.com/samchon/prisma-markdown) by [@samchon](https://github.com/samchon). Thank you for the great idea.
12
21
 
13
22
  ## Features
@@ -216,7 +225,7 @@ erDiagram
216
225
  Post {
217
226
  integer id PK
218
227
  string title
219
- text body
228
+ string body
220
229
  integer author_id FK
221
230
  }
222
231
  Author {
@@ -453,6 +462,43 @@ erDiagram
453
462
 
454
463
  > Viewer support for `elk` and theme values varies. If no `--mermaid-layout` or `--mermaid-theme` is provided, no frontmatter is emitted.
455
464
 
465
+ ## Planned Features
466
+
467
+ The following features are planned for future releases.
468
+
469
+ ### `--check` mode
470
+
471
+ A `--check` flag that exits with a non-zero code when the output file is out of sync with the current entity metadata — without writing any files.
472
+ Intended for CI pipelines to enforce that `ERD.md` is always committed alongside entity changes.
473
+
474
+ ```bash
475
+ mikro-orm-markdown --config ./mikro-orm.config.ts --check
476
+ ```
477
+
478
+ ### `--watch` mode
479
+
480
+ A `--watch` flag that monitors entity source files and regenerates the output document automatically on each change.
481
+ Useful during active development when you want the ERD to stay current without running the CLI manually.
482
+
483
+ ```bash
484
+ mikro-orm-markdown --config ./mikro-orm.config.ts --watch
485
+ ```
486
+
487
+ ### `EntitySchema` support
488
+
489
+ Currently only decorator-based entities (`@Entity()` classes) are supported.
490
+ A future release will add support for [`EntitySchema`](https://mikro-orm.io/docs/entity-schema)-defined entities, which describe the schema as a plain object without TypeScript decorators.
491
+
492
+ ```typescript
493
+ const PostSchema = new EntitySchema({
494
+ name: 'Post',
495
+ properties: {
496
+ id: { primary: true, type: 'integer' },
497
+ title: { type: 'string' },
498
+ },
499
+ });
500
+ ```
501
+
456
502
  ## License
457
503
 
458
504
  MIT
package/dist/cli.cjs CHANGED
@@ -53,7 +53,7 @@ var import_commander = require("commander");
53
53
 
54
54
  // src/docs/jsdoc.ts
55
55
  var import_ts_morph = require("ts-morph");
56
- function loadJsDoc(filePaths) {
56
+ function loadJsDoc(filePaths, onWarn) {
57
57
  const entities = /* @__PURE__ */ new Map();
58
58
  const props = /* @__PURE__ */ new Map();
59
59
  const classNames = /* @__PURE__ */ new Set();
@@ -70,8 +70,12 @@ function loadJsDoc(filePaths) {
70
70
  });
71
71
  for (const filePath of filePaths) {
72
72
  try {
73
- project.addSourceFilesAtPaths(filePath);
74
- } catch {
73
+ const sourceFiles2 = project.addSourceFilesAtPaths(filePath);
74
+ if (sourceFiles2.length === 0 && !hasGlobPattern(filePath)) {
75
+ onWarn?.(`No JSDoc source file matched path: ${filePath}`);
76
+ }
77
+ } catch (err) {
78
+ onWarn?.(`Could not load JSDoc source path "${filePath}": ${formatUnknownError(err)}`);
75
79
  }
76
80
  }
77
81
  const sourceFiles = project.getSourceFiles();
@@ -102,11 +106,18 @@ function loadJsDoc(filePaths) {
102
106
  props.set(className, propMap);
103
107
  }
104
108
  }
105
- } catch {
109
+ } catch (err) {
110
+ onWarn?.(`Could not parse JSDoc source file "${sourceFile.getFilePath()}": ${formatUnknownError(err)}`);
106
111
  }
107
112
  }
108
113
  return { entities, props, sourceFileCount: sourceFiles.length, classNames };
109
114
  }
115
+ function formatUnknownError(err) {
116
+ return err instanceof Error ? err.message : String(err);
117
+ }
118
+ function hasGlobPattern(filePath) {
119
+ return /[*?[\]{}]/.test(filePath);
120
+ }
110
121
  function parseEntityJsDoc(jsDocs) {
111
122
  const namespaces = [];
112
123
  const erdNamespaces = [];
@@ -342,6 +353,9 @@ function buildColumns(prop, metaByClass, owningMeta) {
342
353
  return [];
343
354
  }
344
355
  const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
356
+ if (prop.persist === false && formulaExpr === void 0) {
357
+ return [];
358
+ }
345
359
  let embeddedIn;
346
360
  let embeddedPropName;
347
361
  if (prop.embedded !== void 0) {
@@ -528,6 +542,9 @@ function buildEdge(fromEntity, prop) {
528
542
  };
529
543
  }
530
544
  if (prop.kind === import_core2.ReferenceKind.ONE_TO_ONE && prop.owner === true) {
545
+ if (prop.type === fromEntity) {
546
+ return null;
547
+ }
531
548
  return {
532
549
  fromEntity,
533
550
  toEntity: prop.type,
@@ -776,7 +793,8 @@ function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
776
793
  if (isDefault || !jsDoc) {
777
794
  return false;
778
795
  }
779
- return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
796
+ const hasHomeNamespace = jsDoc.namespaces.length > 0 || jsDoc.describeNamespaces.length > 0;
797
+ return hasHomeNamespace && jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
780
798
  }
781
799
 
782
800
  // src/provider.ts
@@ -806,7 +824,7 @@ function renderMarkdown(docModel, mermaid) {
806
824
  sections.push(escapeMarkdownParagraph(docModel.description));
807
825
  }
808
826
  if (docModel.groups.length > 1) {
809
- sections.push(renderTableOfContents(docModel.groups));
827
+ sections.push(renderTableOfContents(docModel.groups, resolveGroupAnchors(docModel)));
810
828
  }
811
829
  for (const group of docModel.groups) {
812
830
  sections.push(renderGroupSection(group, mermaid));
@@ -820,12 +838,31 @@ function renderBulletSection(header, items, renderItem) {
820
838
  }
821
839
  return lines.join("\n");
822
840
  }
823
- function renderTableOfContents(groups) {
841
+ function renderTableOfContents(groups, anchors) {
824
842
  return renderBulletSection("## Contents", groups, (group) => {
825
843
  const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
826
- return `- [${label}](#${toMarkdownAnchor(group.name)})`;
844
+ return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
827
845
  });
828
846
  }
847
+ function resolveGroupAnchors(docModel) {
848
+ const seenAnchors = /* @__PURE__ */ new Map();
849
+ const groupAnchors = /* @__PURE__ */ new Map();
850
+ resolveHeadingAnchor(docModel.title, seenAnchors);
851
+ resolveHeadingAnchor("Contents", seenAnchors);
852
+ for (const group of docModel.groups) {
853
+ groupAnchors.set(group, resolveHeadingAnchor(group.name, seenAnchors));
854
+ for (const entity of group.textEntities) {
855
+ resolveHeadingAnchor(entity.model.className, seenAnchors);
856
+ }
857
+ }
858
+ return groupAnchors;
859
+ }
860
+ function resolveHeadingAnchor(value, seenAnchors) {
861
+ const baseAnchor = toMarkdownAnchor(value);
862
+ const previousCount = seenAnchors.get(baseAnchor) ?? 0;
863
+ seenAnchors.set(baseAnchor, previousCount + 1);
864
+ return previousCount === 0 ? baseAnchor : `${baseAnchor}-${previousCount}`;
865
+ }
829
866
  function renderGroupSection(group, mermaid) {
830
867
  const parts = [`## ${escapeMarkdownInline(group.name)}`];
831
868
  if (group.erdEntities.length > 0) {
@@ -992,7 +1029,7 @@ async function generateMarkdown(options) {
992
1029
  const { orm, title = "Database Schema", description, src, onWarn, mermaid } = options;
993
1030
  const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
994
1031
  const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
995
- const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
1032
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn), onWarn);
996
1033
  if (src !== void 0 && src.length > 0) {
997
1034
  assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
998
1035
  }