mikro-orm-markdown 0.1.0-alpha.5 → 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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ 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.0] - 2026-06-30
9
+
10
+ First stable release. All alpha features are considered production-ready.
11
+
12
+ ### Added
13
+
14
+ - `AGENTS.md` with project overview, source layout, development commands, and conventions for AI agent context
15
+ - Planned Features section in README (en/ko) documenting `--check` mode, `--watch` mode, and `EntitySchema` support as upcoming work
16
+
8
17
  ## [0.1.0-alpha.5] - 2026-06-29
9
18
 
10
19
  ### Added
package/README.ko.md CHANGED
@@ -216,7 +216,7 @@ erDiagram
216
216
  Post {
217
217
  integer id PK
218
218
  string title
219
- text body
219
+ string body
220
220
  integer author_id FK
221
221
  }
222
222
  Author {
@@ -453,6 +453,40 @@ erDiagram
453
453
 
454
454
  > `elk`와 theme 지원 여부는 뷰어마다 다릅니다. `--mermaid-layout`이나 `--mermaid-theme`을 지정하지 않으면 frontmatter는 출력되지 않습니다.
455
455
 
456
+ ## 추후 구현 예정 기능
457
+
458
+ 다음 기능들은 향후 릴리즈에서 지원할 예정입니다.
459
+
460
+ ### `--check` 모드
461
+
462
+ 출력 파일이 현재 엔티티 메타데이터와 다를 경우 non-zero 코드로 종료하는 `--check` 플래그입니다. 파일을 직접 수정하지 않으며, CI 파이프라인에서 `ERD.md`가 엔티티 변경 사항과 함께 항상 커밋되었는지 검증하는 용도로 사용합니다.
463
+
464
+ ```bash
465
+ mikro-orm-markdown --config ./mikro-orm.config.ts --check
466
+ ```
467
+
468
+ ### `--watch` 모드
469
+
470
+ 엔티티 소스 파일을 감지하여 변경이 발생할 때마다 출력 문서를 자동으로 재생성하는 `--watch` 플래그입니다. CLI를 매번 수동으로 실행하지 않아도 ERD를 최신 상태로 유지할 수 있어 개발 중에 유용합니다.
471
+
472
+ ```bash
473
+ mikro-orm-markdown --config ./mikro-orm.config.ts --watch
474
+ ```
475
+
476
+ ### `EntitySchema` 지원
477
+
478
+ 현재는 decorator 기반 엔티티(`@Entity()` 클래스)만 지원합니다. 향후 릴리즈에서는 TypeScript decorator 없이 Plain Object로 스키마를 정의하는 [`EntitySchema`](https://mikro-orm.io/docs/entity-schema) 방식도 지원할 예정입니다.
479
+
480
+ ```typescript
481
+ const PostSchema = new EntitySchema({
482
+ name: 'Post',
483
+ properties: {
484
+ id: { primary: true, type: 'integer' },
485
+ title: { type: 'string' },
486
+ },
487
+ });
488
+ ```
489
+
456
490
  ## 라이선스
457
491
 
458
492
  MIT
package/README.md CHANGED
@@ -216,7 +216,7 @@ erDiagram
216
216
  Post {
217
217
  integer id PK
218
218
  string title
219
- text body
219
+ string body
220
220
  integer author_id FK
221
221
  }
222
222
  Author {
@@ -453,6 +453,43 @@ erDiagram
453
453
 
454
454
  > Viewer support for `elk` and theme values varies. If no `--mermaid-layout` or `--mermaid-theme` is provided, no frontmatter is emitted.
455
455
 
456
+ ## Planned Features
457
+
458
+ The following features are planned for future releases.
459
+
460
+ ### `--check` mode
461
+
462
+ 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.
463
+ Intended for CI pipelines to enforce that `ERD.md` is always committed alongside entity changes.
464
+
465
+ ```bash
466
+ mikro-orm-markdown --config ./mikro-orm.config.ts --check
467
+ ```
468
+
469
+ ### `--watch` mode
470
+
471
+ A `--watch` flag that monitors entity source files and regenerates the output document automatically on each change.
472
+ Useful during active development when you want the ERD to stay current without running the CLI manually.
473
+
474
+ ```bash
475
+ mikro-orm-markdown --config ./mikro-orm.config.ts --watch
476
+ ```
477
+
478
+ ### `EntitySchema` support
479
+
480
+ Currently only decorator-based entities (`@Entity()` classes) are supported.
481
+ 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.
482
+
483
+ ```typescript
484
+ const PostSchema = new EntitySchema({
485
+ name: 'Post',
486
+ properties: {
487
+ id: { primary: true, type: 'integer' },
488
+ title: { type: 'string' },
489
+ },
490
+ });
491
+ ```
492
+
456
493
  ## License
457
494
 
458
495
  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 = [];
@@ -776,7 +787,8 @@ function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
776
787
  if (isDefault || !jsDoc) {
777
788
  return false;
778
789
  }
779
- return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
790
+ const hasHomeNamespace = jsDoc.namespaces.length > 0 || jsDoc.describeNamespaces.length > 0;
791
+ return hasHomeNamespace && jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
780
792
  }
781
793
 
782
794
  // src/provider.ts
@@ -806,7 +818,7 @@ function renderMarkdown(docModel, mermaid) {
806
818
  sections.push(escapeMarkdownParagraph(docModel.description));
807
819
  }
808
820
  if (docModel.groups.length > 1) {
809
- sections.push(renderTableOfContents(docModel.groups));
821
+ sections.push(renderTableOfContents(docModel.groups, resolveGroupAnchors(docModel)));
810
822
  }
811
823
  for (const group of docModel.groups) {
812
824
  sections.push(renderGroupSection(group, mermaid));
@@ -820,12 +832,31 @@ function renderBulletSection(header, items, renderItem) {
820
832
  }
821
833
  return lines.join("\n");
822
834
  }
823
- function renderTableOfContents(groups) {
835
+ function renderTableOfContents(groups, anchors) {
824
836
  return renderBulletSection("## Contents", groups, (group) => {
825
837
  const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
826
- return `- [${label}](#${toMarkdownAnchor(group.name)})`;
838
+ return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
827
839
  });
828
840
  }
841
+ function resolveGroupAnchors(docModel) {
842
+ const seenAnchors = /* @__PURE__ */ new Map();
843
+ const groupAnchors = /* @__PURE__ */ new Map();
844
+ resolveHeadingAnchor(docModel.title, seenAnchors);
845
+ resolveHeadingAnchor("Contents", seenAnchors);
846
+ for (const group of docModel.groups) {
847
+ groupAnchors.set(group, resolveHeadingAnchor(group.name, seenAnchors));
848
+ for (const entity of group.textEntities) {
849
+ resolveHeadingAnchor(entity.model.className, seenAnchors);
850
+ }
851
+ }
852
+ return groupAnchors;
853
+ }
854
+ function resolveHeadingAnchor(value, seenAnchors) {
855
+ const baseAnchor = toMarkdownAnchor(value);
856
+ const previousCount = seenAnchors.get(baseAnchor) ?? 0;
857
+ seenAnchors.set(baseAnchor, previousCount + 1);
858
+ return previousCount === 0 ? baseAnchor : `${baseAnchor}-${previousCount}`;
859
+ }
829
860
  function renderGroupSection(group, mermaid) {
830
861
  const parts = [`## ${escapeMarkdownInline(group.name)}`];
831
862
  if (group.erdEntities.length > 0) {
@@ -992,7 +1023,7 @@ async function generateMarkdown(options) {
992
1023
  const { orm, title = "Database Schema", description, src, onWarn, mermaid } = options;
993
1024
  const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
994
1025
  const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
995
- const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
1026
+ const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn), onWarn);
996
1027
  if (src !== void 0 && src.length > 0) {
997
1028
  assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
998
1029
  }