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 +16 -0
- package/README.ko.md +45 -2
- package/README.md +48 -2
- package/dist/cli.cjs +46 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +46 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +46 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -9
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
-
[](https://www.npmjs.com/package/mikro-orm-markdown)
|
|
10
|
+
[](https://www.npmjs.com/package/mikro-orm-markdown)
|
|
6
11
|
[](https://github.com/iamkanguk97/mikro-orm-markdown/actions)
|
|
7
12
|
[](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
|
-
|
|
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
|
-
[](https://www.npmjs.com/package/mikro-orm-markdown)
|
|
10
|
+
[](https://www.npmjs.com/package/mikro-orm-markdown)
|
|
6
11
|
[](https://github.com/iamkanguk97/mikro-orm-markdown/actions)
|
|
7
12
|
[](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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|