drizzle-docs-generator 0.4.0 → 0.4.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/README.ja.md +18 -37
- package/README.md +18 -37
- package/dist/formatter/markdown.d.ts +6 -0
- package/dist/formatter/markdown.d.ts.map +1 -1
- package/dist/formatter/markdown.js +10 -1
- package/dist/formatter/markdown.js.map +1 -1
- package/dist/parser/comments.js +55 -50
- package/dist/parser/comments.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# drizzle-docs-generator
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://nodei.co/npm/drizzle-docs-generator/)
|
|
5
4
|
|
|
6
|
-
Drizzle ORM スキーマから DBML
|
|
5
|
+
Drizzle ORM スキーマから DBML と Markdown ドキュメントを生成する CLI。JSDoc コメントを Note 句として出力できる。
|
|
7
6
|
|
|
8
7
|
**機能:**
|
|
9
8
|
|
|
@@ -18,10 +17,26 @@ Drizzle ORM スキーマから DBML を生成する CLI。JSDoc コメントを
|
|
|
18
17
|
|
|
19
18
|
## インストール
|
|
20
19
|
|
|
20
|
+
### ローカルインストール(推奨)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 開発依存関係としてインストール
|
|
24
|
+
npm install --save-dev drizzle-docs-generator
|
|
25
|
+
# or
|
|
26
|
+
pnpm add -D drizzle-docs-generator
|
|
27
|
+
|
|
28
|
+
# npx で実行
|
|
29
|
+
npx drizzle-docs generate ./src/db/schema.ts -d postgresql
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### グローバルインストール
|
|
33
|
+
|
|
21
34
|
```bash
|
|
22
35
|
npm install -g drizzle-docs-generator
|
|
23
36
|
# or
|
|
24
37
|
pnpm add -g drizzle-docs-generator
|
|
38
|
+
|
|
39
|
+
drizzle-docs generate ./src/db/schema.ts -d postgresql
|
|
25
40
|
```
|
|
26
41
|
|
|
27
42
|
## 使い方
|
|
@@ -38,9 +53,6 @@ drizzle-docs generate ./src/db/schema/ -d postgresql
|
|
|
38
53
|
# ファイル出力
|
|
39
54
|
drizzle-docs generate ./src/db/schema.ts -d postgresql -o schema.dbml
|
|
40
55
|
|
|
41
|
-
# relations() を使う
|
|
42
|
-
drizzle-docs generate ./src/db/schema.ts -d postgresql -r
|
|
43
|
-
|
|
44
56
|
# watch モード
|
|
45
57
|
drizzle-docs generate ./src/db/schema.ts -d postgresql -w
|
|
46
58
|
```
|
|
@@ -119,37 +131,6 @@ Table users {
|
|
|
119
131
|
|
|
120
132
|
詳細なサンプル出力は [examples/](./examples/) を参照してください。
|
|
121
133
|
|
|
122
|
-
## API
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { pgGenerate } from "drizzle-docs-generator";
|
|
126
|
-
import * as schema from "./schema";
|
|
127
|
-
|
|
128
|
-
const dbml = pgGenerate({
|
|
129
|
-
schema,
|
|
130
|
-
source: "./schema.ts", // JSDoc コメントと v0 relations() 検出用
|
|
131
|
-
out: "./output.dbml", // optional
|
|
132
|
-
});
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
`mysqlGenerate`, `sqliteGenerate` も同様。
|
|
136
|
-
|
|
137
|
-
## 動作環境
|
|
138
|
-
|
|
139
|
-
- Node.js >= 24
|
|
140
|
-
- Drizzle ORM v1 beta (1.0.0-beta.10+)
|
|
141
|
-
- ES Modules (ESM): プロジェクトで ESM を使用していること (`package.json` に `"type": "module"`)
|
|
142
|
-
|
|
143
|
-
## 仕組み
|
|
144
|
-
|
|
145
|
-
このツールは [tsx](https://github.com/privatenumber/tsx) を使用してスキーマファイルを読み込むため:
|
|
146
|
-
|
|
147
|
-
✅ **拡張子なしのインポートが動作**: `import { users } from './users'`
|
|
148
|
-
✅ **TypeScript ファイルを直接読み込み**: コンパイル不要
|
|
149
|
-
✅ **ディレクトリインポート**: ディレクトリ内のすべてのスキーマファイルを自動読み込み
|
|
150
|
-
|
|
151
|
-
スキーマファイルでは、ファイル拡張子を気にせず標準的な TypeScript のモジュール解決を使用できます。
|
|
152
|
-
|
|
153
134
|
## License
|
|
154
135
|
|
|
155
136
|
MIT
|
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# drizzle-docs-generator
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://nodei.co/npm/drizzle-docs-generator/)
|
|
5
4
|
|
|
6
|
-
CLI tool to generate DBML from Drizzle ORM schemas. Extracts JSDoc comments and outputs them as Note clauses.
|
|
5
|
+
CLI tool to generate DBML and Markdown documentation from Drizzle ORM schemas. Extracts JSDoc comments and outputs them as Note clauses.
|
|
7
6
|
|
|
8
7
|
**Features:**
|
|
9
8
|
|
|
@@ -18,10 +17,26 @@ CLI tool to generate DBML from Drizzle ORM schemas. Extracts JSDoc comments and
|
|
|
18
17
|
|
|
19
18
|
## Install
|
|
20
19
|
|
|
20
|
+
### Local Install (recommended)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# As a dev dependency
|
|
24
|
+
npm install --save-dev drizzle-docs-generator
|
|
25
|
+
# or
|
|
26
|
+
pnpm add -D drizzle-docs-generator
|
|
27
|
+
|
|
28
|
+
# Then use with npx
|
|
29
|
+
npx drizzle-docs generate ./src/db/schema.ts -d postgresql
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Global Install
|
|
33
|
+
|
|
21
34
|
```bash
|
|
22
35
|
npm install -g drizzle-docs-generator
|
|
23
36
|
# or
|
|
24
37
|
pnpm add -g drizzle-docs-generator
|
|
38
|
+
|
|
39
|
+
drizzle-docs generate ./src/db/schema.ts -d postgresql
|
|
25
40
|
```
|
|
26
41
|
|
|
27
42
|
## Usage
|
|
@@ -38,9 +53,6 @@ drizzle-docs generate ./src/db/schema/ -d postgresql
|
|
|
38
53
|
# Output to file
|
|
39
54
|
drizzle-docs generate ./src/db/schema.ts -d postgresql -o schema.dbml
|
|
40
55
|
|
|
41
|
-
# Use relations() definitions
|
|
42
|
-
drizzle-docs generate ./src/db/schema.ts -d postgresql -r
|
|
43
|
-
|
|
44
56
|
# Watch mode
|
|
45
57
|
drizzle-docs generate ./src/db/schema.ts -d postgresql -w
|
|
46
58
|
```
|
|
@@ -119,37 +131,6 @@ Users table
|
|
|
119
131
|
|
|
120
132
|
See [examples/](./examples/) for more detailed output samples.
|
|
121
133
|
|
|
122
|
-
## API
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { pgGenerate } from "drizzle-docs-generator";
|
|
126
|
-
import * as schema from "./schema";
|
|
127
|
-
|
|
128
|
-
const dbml = pgGenerate({
|
|
129
|
-
schema,
|
|
130
|
-
source: "./schema.ts", // for JSDoc comments and v0 relations() detection
|
|
131
|
-
out: "./output.dbml", // optional
|
|
132
|
-
});
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
`mysqlGenerate`, `sqliteGenerate` are also available.
|
|
136
|
-
|
|
137
|
-
## Requirements
|
|
138
|
-
|
|
139
|
-
- Node.js >= 24
|
|
140
|
-
- Drizzle ORM v1 beta (1.0.0-beta.10+)
|
|
141
|
-
- ES Modules (ESM): Your project must use ESM (`"type": "module"` in package.json)
|
|
142
|
-
|
|
143
|
-
## How It Works
|
|
144
|
-
|
|
145
|
-
This tool uses [tsx](https://github.com/privatenumber/tsx) to load your schema files, which means:
|
|
146
|
-
|
|
147
|
-
✅ **Extensionless imports work**: `import { users } from './users'`
|
|
148
|
-
✅ **TypeScript files are loaded directly**: No need to compile first
|
|
149
|
-
✅ **Directory imports**: Load all schema files from a directory automatically
|
|
150
|
-
|
|
151
|
-
Your schema files can use standard TypeScript module resolution without worrying about file extensions.
|
|
152
|
-
|
|
153
134
|
## License
|
|
154
135
|
|
|
155
136
|
MIT
|
|
@@ -119,8 +119,14 @@ export declare class MarkdownFormatter implements OutputFormatter {
|
|
|
119
119
|
private createTableLink;
|
|
120
120
|
/**
|
|
121
121
|
* Escape special Markdown characters in a string
|
|
122
|
+
* Converts newlines to spaces for use in table cells
|
|
122
123
|
*/
|
|
123
124
|
private escapeMarkdown;
|
|
125
|
+
/**
|
|
126
|
+
* Escape special Markdown characters and convert newlines to <br> tags
|
|
127
|
+
* Used for better readability in text sections (not in tables)
|
|
128
|
+
*/
|
|
129
|
+
private escapeMarkdownWithBreaks;
|
|
124
130
|
}
|
|
125
131
|
export {};
|
|
126
132
|
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/formatter/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EAMhB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE;;GAEG;AACH,KAAK,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAChE;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAaD;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,eAAe;IACvD,OAAO,CAAC,OAAO,CAAqC;IAEpD;;;;OAIG;gBACS,OAAO,GAAE,wBAA6B;IAIlD;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAyB1C;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IA4BjD;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAsC5E;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA6C5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoBhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiC9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAazB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAelC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;OAEG;IACH,OAAO,CAAC,OAAO;IAOf;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/formatter/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EAMhB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE;;GAEG;AACH,KAAK,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAChE;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAaD;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,eAAe;IACvD,OAAO,CAAC,OAAO,CAAqC;IAEpD;;;;OAIG;gBACS,OAAO,GAAE,wBAA6B;IAIlD;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAyB1C;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IA4BjD;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAsC5E;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA6C5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoBhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiC9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAazB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAelC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;OAEG;IACH,OAAO,CAAC,OAAO;IAOf;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACH,OAAO,CAAC,wBAAwB;CAGjC"}
|
|
@@ -59,7 +59,7 @@ class T {
|
|
|
59
59
|
*/
|
|
60
60
|
generateTableDoc(e, n) {
|
|
61
61
|
const t = [];
|
|
62
|
-
t.push(`## ${e.name}`), t.push(""), this.options.includeComments && e.comment && (t.push(this.
|
|
62
|
+
t.push(`## ${e.name}`), t.push(""), this.options.includeComments && e.comment && (t.push(this.escapeMarkdownWithBreaks(e.comment)), t.push("")), t.push(this.generateColumnsTable(e.columns, e.name, n)), this.options.includeConstraints && e.constraints.length > 0 && (t.push(""), t.push(this.generateConstraintsTable(e.constraints))), this.options.includeIndexes && e.indexes.length > 0 && (t.push(""), t.push(this.generateIndexesTable(e.indexes)));
|
|
63
63
|
const s = this.getTableRelations(e.name, n.relations);
|
|
64
64
|
return s.length > 0 && (t.push(""), t.push(this.generateRelationsTable(s, e.name))), t.join(`
|
|
65
65
|
`);
|
|
@@ -224,10 +224,19 @@ class T {
|
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
226
226
|
* Escape special Markdown characters in a string
|
|
227
|
+
* Converts newlines to spaces for use in table cells
|
|
227
228
|
*/
|
|
228
229
|
escapeMarkdown(e) {
|
|
229
230
|
return e.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
230
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Escape special Markdown characters and convert newlines to <br> tags
|
|
234
|
+
* Used for better readability in text sections (not in tables)
|
|
235
|
+
*/
|
|
236
|
+
escapeMarkdownWithBreaks(e) {
|
|
237
|
+
return e.replace(/\|/g, "\\|").replace(/\n/g, `
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
231
240
|
}
|
|
232
241
|
export {
|
|
233
242
|
T as MarkdownFormatter
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.js","sources":["../../src/formatter/markdown.ts"],"sourcesContent":["import type {\n IntermediateSchema,\n TableDefinition,\n ColumnDefinition,\n IndexDefinition,\n ConstraintDefinition,\n RelationDefinition,\n EnumDefinition,\n} from \"../types\";\nimport type { OutputFormatter, FormatterOptions } from \"./types\";\n\n/**\n * Link format for table references\n */\ntype LinkFormat = \"anchor\" | \"file\";\n\n/**\n * Options for MarkdownFormatter\n */\nexport interface MarkdownFormatterOptions extends FormatterOptions {\n /**\n * Whether to use relative links for table references\n * @default true\n */\n useRelativeLinks?: boolean;\n /**\n * Link format: \"anchor\" for #table-name, \"file\" for ./table-name.md\n * @default \"anchor\"\n */\n linkFormat?: LinkFormat;\n}\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<MarkdownFormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n useRelativeLinks: true,\n linkFormat: \"anchor\",\n};\n\n/**\n * MarkdownFormatter converts IntermediateSchema to tbls-style Markdown format\n *\n * This formatter generates human-readable Markdown documentation from\n * the database-agnostic IntermediateSchema representation.\n *\n * Output includes:\n * - Table index (README.md style)\n * - Individual table documentation with columns, constraints, indexes, and relations\n */\nexport class MarkdownFormatter implements OutputFormatter {\n private options: Required<MarkdownFormatterOptions>;\n\n /**\n * Create a new MarkdownFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: MarkdownFormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into a single Markdown document\n *\n * This generates a complete document containing the index and all table docs.\n *\n * @param schema - The intermediate schema to format\n * @returns Markdown string\n */\n format(schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n // Generate index section\n lines.push(this.generateIndex(schema));\n\n // Generate enum documentation if any\n if (schema.enums.length > 0) {\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(this.generateEnumsSection(schema.enums));\n }\n\n // Generate table documentation\n for (const table of schema.tables) {\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(this.generateTableDoc(table, schema));\n }\n\n return lines.join(\"\\n\").trim();\n }\n\n /**\n * Generate the index section (README.md style)\n *\n * @param schema - The intermediate schema\n * @returns Markdown string for the index\n */\n generateIndex(schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n lines.push(\"# Tables\");\n lines.push(\"\");\n\n if (schema.tables.length === 0) {\n lines.push(\"No tables defined.\");\n return lines.join(\"\\n\");\n }\n\n // Table header\n lines.push(\"| Name | Columns | Comment |\");\n lines.push(\"|------|---------|---------|\");\n\n // Table rows\n for (const table of schema.tables) {\n const name = this.options.useRelativeLinks ? this.createTableLink(table.name) : table.name;\n const columnCount = table.columns.length;\n const comment =\n this.options.includeComments && table.comment ? this.escapeMarkdown(table.comment) : \"\";\n\n lines.push(`| ${name} | ${columnCount} | ${comment} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate documentation for a single table\n *\n * @param table - The table definition\n * @param schema - The full schema (for relation lookups)\n * @returns Markdown string for the table documentation\n */\n generateTableDoc(table: TableDefinition, schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n // Table heading with anchor\n lines.push(`## ${table.name}`);\n lines.push(\"\");\n\n // Table comment\n if (this.options.includeComments && table.comment) {\n lines.push(this.escapeMarkdown(table.comment));\n lines.push(\"\");\n }\n\n // Columns section\n lines.push(this.generateColumnsTable(table.columns, table.name, schema));\n\n // Constraints section\n if (this.options.includeConstraints && table.constraints.length > 0) {\n lines.push(\"\");\n lines.push(this.generateConstraintsTable(table.constraints));\n }\n\n // Indexes section\n if (this.options.includeIndexes && table.indexes.length > 0) {\n lines.push(\"\");\n lines.push(this.generateIndexesTable(table.indexes));\n }\n\n // Relations section\n const tableRelations = this.getTableRelations(table.name, schema.relations);\n if (tableRelations.length > 0) {\n lines.push(\"\");\n lines.push(this.generateRelationsTable(tableRelations, table.name));\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the columns table for a table\n */\n private generateColumnsTable(\n columns: ColumnDefinition[],\n tableName: string,\n schema: IntermediateSchema,\n ): string {\n const lines: string[] = [];\n\n lines.push(\"### Columns\");\n lines.push(\"\");\n\n if (columns.length === 0) {\n lines.push(\"No columns defined.\");\n return lines.join(\"\\n\");\n }\n\n // Build column info with relations\n const columnInfo = columns.map((col) => {\n const children = this.getChildRelations(tableName, col.name, schema.relations);\n const parents = this.getParentRelations(tableName, col.name, schema.relations);\n return { column: col, children, parents };\n });\n\n // Table header\n lines.push(\"| Name | Type | Default | Nullable | Children | Parents | Comment |\");\n lines.push(\"|------|------|---------|----------|----------|---------|---------|\");\n\n // Table rows\n for (const { column, children, parents } of columnInfo) {\n const name = column.primaryKey ? `**${column.name}**` : column.name;\n const type = this.escapeMarkdown(column.type);\n const defaultVal = column.defaultValue !== undefined ? `\\`${column.defaultValue}\\`` : \"-\";\n const nullable = column.nullable ? \"YES\" : \"NO\";\n const childrenStr = children.length > 0 ? this.formatRelationLinks(children) : \"-\";\n const parentsStr = parents.length > 0 ? this.formatRelationLinks(parents) : \"-\";\n const comment =\n this.options.includeComments && column.comment ? this.escapeMarkdown(column.comment) : \"-\";\n\n lines.push(\n `| ${name} | ${type} | ${defaultVal} | ${nullable} | ${childrenStr} | ${parentsStr} | ${comment} |`,\n );\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the constraints table\n */\n private generateConstraintsTable(constraints: ConstraintDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"### Constraints\");\n lines.push(\"\");\n\n lines.push(\"| Name | Type | Definition |\");\n lines.push(\"|------|------|------------|\");\n\n for (const constraint of constraints) {\n const name = constraint.name || \"-\";\n const type = this.formatConstraintType(constraint.type);\n const definition = this.formatConstraintDefinition(constraint);\n\n lines.push(`| ${name} | ${type} | ${definition} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the indexes table\n */\n private generateIndexesTable(indexes: IndexDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"### Indexes\");\n lines.push(\"\");\n\n lines.push(\"| Name | Columns | Unique | Type |\");\n lines.push(\"|------|---------|--------|------|\");\n\n for (const index of indexes) {\n const name = index.name || \"-\";\n const columns = index.columns.join(\", \");\n const unique = index.unique ? \"YES\" : \"NO\";\n const type = index.type || \"-\";\n\n lines.push(`| ${name} | ${columns} | ${unique} | ${type} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the relations table for a specific table\n */\n private generateRelationsTable(relations: RelationDefinition[], tableName: string): string {\n const lines: string[] = [];\n\n lines.push(\"### Relations\");\n lines.push(\"\");\n\n lines.push(\"| Parent | Child | Type |\");\n lines.push(\"|--------|-------|------|\");\n\n for (const relation of relations) {\n const isParent = relation.toTable === tableName;\n const parent = `${relation.toTable}.${relation.toColumns.join(\", \")}`;\n const child = `${relation.fromTable}.${relation.fromColumns.join(\", \")}`;\n const type = this.formatRelationType(relation.type);\n\n // Add links if enabled\n const parentLink = this.options.useRelativeLinks\n ? this.createTableLink(relation.toTable, parent)\n : parent;\n const childLink = this.options.useRelativeLinks\n ? this.createTableLink(relation.fromTable, child)\n : child;\n\n // Highlight the current table\n const parentDisplay = isParent ? `**${parentLink}**` : parentLink;\n const childDisplay = !isParent ? `**${childLink}**` : childLink;\n\n lines.push(`| ${parentDisplay} | ${childDisplay} | ${type} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate documentation for enums\n */\n private generateEnumsSection(enums: EnumDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"# Enums\");\n lines.push(\"\");\n\n for (const enumDef of enums) {\n lines.push(`## ${enumDef.name}`);\n lines.push(\"\");\n lines.push(\"| Value |\");\n lines.push(\"|-------|\");\n for (const value of enumDef.values) {\n lines.push(`| ${value} |`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trim();\n }\n\n /**\n * Get all relations involving a specific table\n */\n private getTableRelations(\n tableName: string,\n relations: RelationDefinition[],\n ): RelationDefinition[] {\n return relations.filter((r) => r.fromTable === tableName || r.toTable === tableName);\n }\n\n /**\n * Get child relations for a specific column (where this column is referenced)\n */\n private getChildRelations(\n tableName: string,\n columnName: string,\n relations: RelationDefinition[],\n ): Array<{ table: string; column: string }> {\n return relations\n .filter((r) => r.toTable === tableName && r.toColumns.includes(columnName))\n .map((r) => ({\n table: r.fromTable,\n column: r.fromColumns.join(\", \"),\n }));\n }\n\n /**\n * Get parent relations for a specific column (columns this column references)\n */\n private getParentRelations(\n tableName: string,\n columnName: string,\n relations: RelationDefinition[],\n ): Array<{ table: string; column: string }> {\n return relations\n .filter((r) => r.fromTable === tableName && r.fromColumns.includes(columnName))\n .map((r) => ({\n table: r.toTable,\n column: r.toColumns.join(\", \"),\n }));\n }\n\n /**\n * Format relation links for display in columns table\n */\n private formatRelationLinks(relations: Array<{ table: string; column: string }>): string {\n return relations\n .map((r) => {\n const text = `${r.table}.${r.column}`;\n return this.options.useRelativeLinks ? this.createTableLink(r.table, text) : text;\n })\n .join(\", \");\n }\n\n /**\n * Format constraint type for display\n */\n private formatConstraintType(type: ConstraintDefinition[\"type\"]): string {\n const typeMap: Record<ConstraintDefinition[\"type\"], string> = {\n primary_key: \"PRIMARY KEY\",\n foreign_key: \"FOREIGN KEY\",\n unique: \"UNIQUE\",\n check: \"CHECK\",\n not_null: \"NOT NULL\",\n };\n return typeMap[type] || type;\n }\n\n /**\n * Format constraint definition for display\n */\n private formatConstraintDefinition(constraint: ConstraintDefinition): string {\n if (constraint.definition) {\n return `\\`${constraint.definition}\\``;\n }\n\n const columns = constraint.columns.join(\", \");\n\n if (constraint.type === \"foreign_key\" && constraint.referencedTable) {\n const refColumns = constraint.referencedColumns?.join(\", \") || \"\";\n return `(${columns}) → ${constraint.referencedTable}(${refColumns})`;\n }\n\n return `(${columns})`;\n }\n\n /**\n * Format relation type for display\n */\n private formatRelationType(type: RelationDefinition[\"type\"]): string {\n const typeMap: Record<RelationDefinition[\"type\"], string> = {\n \"one-to-one\": \"One to One\",\n \"one-to-many\": \"One to Many\",\n \"many-to-one\": \"Many to One\",\n \"many-to-many\": \"Many to Many\",\n };\n return typeMap[type] || type;\n }\n\n /**\n * Create a URL-safe slug from a string\n */\n private slugify(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n }\n\n /**\n * Create a table link based on the configured link format\n */\n private createTableLink(tableName: string, displayText?: string): string {\n const text = displayText || tableName;\n if (this.options.linkFormat === \"file\") {\n return `[${text}](./${tableName}.md)`;\n }\n return `[${text}](#${this.slugify(tableName)})`;\n }\n\n /**\n * Escape special Markdown characters in a string\n */\n private escapeMarkdown(str: string): string {\n return str.replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \" \");\n }\n}\n"],"names":["DEFAULT_OPTIONS","MarkdownFormatter","options","schema","lines","table","name","columnCount","comment","tableRelations","columns","tableName","columnInfo","col","children","parents","column","type","defaultVal","nullable","childrenStr","parentsStr","constraints","constraint","definition","indexes","index","unique","relations","relation","isParent","parent","child","parentLink","childLink","parentDisplay","childDisplay","enums","enumDef","value","r","columnName","text","refColumns","str","displayText"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown.js","sources":["../../src/formatter/markdown.ts"],"sourcesContent":["import type {\n IntermediateSchema,\n TableDefinition,\n ColumnDefinition,\n IndexDefinition,\n ConstraintDefinition,\n RelationDefinition,\n EnumDefinition,\n} from \"../types\";\nimport type { OutputFormatter, FormatterOptions } from \"./types\";\n\n/**\n * Link format for table references\n */\ntype LinkFormat = \"anchor\" | \"file\";\n\n/**\n * Options for MarkdownFormatter\n */\nexport interface MarkdownFormatterOptions extends FormatterOptions {\n /**\n * Whether to use relative links for table references\n * @default true\n */\n useRelativeLinks?: boolean;\n /**\n * Link format: \"anchor\" for #table-name, \"file\" for ./table-name.md\n * @default \"anchor\"\n */\n linkFormat?: LinkFormat;\n}\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<MarkdownFormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n useRelativeLinks: true,\n linkFormat: \"anchor\",\n};\n\n/**\n * MarkdownFormatter converts IntermediateSchema to tbls-style Markdown format\n *\n * This formatter generates human-readable Markdown documentation from\n * the database-agnostic IntermediateSchema representation.\n *\n * Output includes:\n * - Table index (README.md style)\n * - Individual table documentation with columns, constraints, indexes, and relations\n */\nexport class MarkdownFormatter implements OutputFormatter {\n private options: Required<MarkdownFormatterOptions>;\n\n /**\n * Create a new MarkdownFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: MarkdownFormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into a single Markdown document\n *\n * This generates a complete document containing the index and all table docs.\n *\n * @param schema - The intermediate schema to format\n * @returns Markdown string\n */\n format(schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n // Generate index section\n lines.push(this.generateIndex(schema));\n\n // Generate enum documentation if any\n if (schema.enums.length > 0) {\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(this.generateEnumsSection(schema.enums));\n }\n\n // Generate table documentation\n for (const table of schema.tables) {\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(this.generateTableDoc(table, schema));\n }\n\n return lines.join(\"\\n\").trim();\n }\n\n /**\n * Generate the index section (README.md style)\n *\n * @param schema - The intermediate schema\n * @returns Markdown string for the index\n */\n generateIndex(schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n lines.push(\"# Tables\");\n lines.push(\"\");\n\n if (schema.tables.length === 0) {\n lines.push(\"No tables defined.\");\n return lines.join(\"\\n\");\n }\n\n // Table header\n lines.push(\"| Name | Columns | Comment |\");\n lines.push(\"|------|---------|---------|\");\n\n // Table rows\n for (const table of schema.tables) {\n const name = this.options.useRelativeLinks ? this.createTableLink(table.name) : table.name;\n const columnCount = table.columns.length;\n const comment =\n this.options.includeComments && table.comment ? this.escapeMarkdown(table.comment) : \"\";\n\n lines.push(`| ${name} | ${columnCount} | ${comment} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate documentation for a single table\n *\n * @param table - The table definition\n * @param schema - The full schema (for relation lookups)\n * @returns Markdown string for the table documentation\n */\n generateTableDoc(table: TableDefinition, schema: IntermediateSchema): string {\n const lines: string[] = [];\n\n // Table heading with anchor\n lines.push(`## ${table.name}`);\n lines.push(\"\");\n\n // Table comment (preserve newlines as <br> for better readability)\n if (this.options.includeComments && table.comment) {\n lines.push(this.escapeMarkdownWithBreaks(table.comment));\n lines.push(\"\");\n }\n\n // Columns section\n lines.push(this.generateColumnsTable(table.columns, table.name, schema));\n\n // Constraints section\n if (this.options.includeConstraints && table.constraints.length > 0) {\n lines.push(\"\");\n lines.push(this.generateConstraintsTable(table.constraints));\n }\n\n // Indexes section\n if (this.options.includeIndexes && table.indexes.length > 0) {\n lines.push(\"\");\n lines.push(this.generateIndexesTable(table.indexes));\n }\n\n // Relations section\n const tableRelations = this.getTableRelations(table.name, schema.relations);\n if (tableRelations.length > 0) {\n lines.push(\"\");\n lines.push(this.generateRelationsTable(tableRelations, table.name));\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the columns table for a table\n */\n private generateColumnsTable(\n columns: ColumnDefinition[],\n tableName: string,\n schema: IntermediateSchema,\n ): string {\n const lines: string[] = [];\n\n lines.push(\"### Columns\");\n lines.push(\"\");\n\n if (columns.length === 0) {\n lines.push(\"No columns defined.\");\n return lines.join(\"\\n\");\n }\n\n // Build column info with relations\n const columnInfo = columns.map((col) => {\n const children = this.getChildRelations(tableName, col.name, schema.relations);\n const parents = this.getParentRelations(tableName, col.name, schema.relations);\n return { column: col, children, parents };\n });\n\n // Table header\n lines.push(\"| Name | Type | Default | Nullable | Children | Parents | Comment |\");\n lines.push(\"|------|------|---------|----------|----------|---------|---------|\");\n\n // Table rows\n for (const { column, children, parents } of columnInfo) {\n const name = column.primaryKey ? `**${column.name}**` : column.name;\n const type = this.escapeMarkdown(column.type);\n const defaultVal = column.defaultValue !== undefined ? `\\`${column.defaultValue}\\`` : \"-\";\n const nullable = column.nullable ? \"YES\" : \"NO\";\n const childrenStr = children.length > 0 ? this.formatRelationLinks(children) : \"-\";\n const parentsStr = parents.length > 0 ? this.formatRelationLinks(parents) : \"-\";\n const comment =\n this.options.includeComments && column.comment ? this.escapeMarkdown(column.comment) : \"-\";\n\n lines.push(\n `| ${name} | ${type} | ${defaultVal} | ${nullable} | ${childrenStr} | ${parentsStr} | ${comment} |`,\n );\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the constraints table\n */\n private generateConstraintsTable(constraints: ConstraintDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"### Constraints\");\n lines.push(\"\");\n\n lines.push(\"| Name | Type | Definition |\");\n lines.push(\"|------|------|------------|\");\n\n for (const constraint of constraints) {\n const name = constraint.name || \"-\";\n const type = this.formatConstraintType(constraint.type);\n const definition = this.formatConstraintDefinition(constraint);\n\n lines.push(`| ${name} | ${type} | ${definition} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the indexes table\n */\n private generateIndexesTable(indexes: IndexDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"### Indexes\");\n lines.push(\"\");\n\n lines.push(\"| Name | Columns | Unique | Type |\");\n lines.push(\"|------|---------|--------|------|\");\n\n for (const index of indexes) {\n const name = index.name || \"-\";\n const columns = index.columns.join(\", \");\n const unique = index.unique ? \"YES\" : \"NO\";\n const type = index.type || \"-\";\n\n lines.push(`| ${name} | ${columns} | ${unique} | ${type} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate the relations table for a specific table\n */\n private generateRelationsTable(relations: RelationDefinition[], tableName: string): string {\n const lines: string[] = [];\n\n lines.push(\"### Relations\");\n lines.push(\"\");\n\n lines.push(\"| Parent | Child | Type |\");\n lines.push(\"|--------|-------|------|\");\n\n for (const relation of relations) {\n const isParent = relation.toTable === tableName;\n const parent = `${relation.toTable}.${relation.toColumns.join(\", \")}`;\n const child = `${relation.fromTable}.${relation.fromColumns.join(\", \")}`;\n const type = this.formatRelationType(relation.type);\n\n // Add links if enabled\n const parentLink = this.options.useRelativeLinks\n ? this.createTableLink(relation.toTable, parent)\n : parent;\n const childLink = this.options.useRelativeLinks\n ? this.createTableLink(relation.fromTable, child)\n : child;\n\n // Highlight the current table\n const parentDisplay = isParent ? `**${parentLink}**` : parentLink;\n const childDisplay = !isParent ? `**${childLink}**` : childLink;\n\n lines.push(`| ${parentDisplay} | ${childDisplay} | ${type} |`);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Generate documentation for enums\n */\n private generateEnumsSection(enums: EnumDefinition[]): string {\n const lines: string[] = [];\n\n lines.push(\"# Enums\");\n lines.push(\"\");\n\n for (const enumDef of enums) {\n lines.push(`## ${enumDef.name}`);\n lines.push(\"\");\n lines.push(\"| Value |\");\n lines.push(\"|-------|\");\n for (const value of enumDef.values) {\n lines.push(`| ${value} |`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trim();\n }\n\n /**\n * Get all relations involving a specific table\n */\n private getTableRelations(\n tableName: string,\n relations: RelationDefinition[],\n ): RelationDefinition[] {\n return relations.filter((r) => r.fromTable === tableName || r.toTable === tableName);\n }\n\n /**\n * Get child relations for a specific column (where this column is referenced)\n */\n private getChildRelations(\n tableName: string,\n columnName: string,\n relations: RelationDefinition[],\n ): Array<{ table: string; column: string }> {\n return relations\n .filter((r) => r.toTable === tableName && r.toColumns.includes(columnName))\n .map((r) => ({\n table: r.fromTable,\n column: r.fromColumns.join(\", \"),\n }));\n }\n\n /**\n * Get parent relations for a specific column (columns this column references)\n */\n private getParentRelations(\n tableName: string,\n columnName: string,\n relations: RelationDefinition[],\n ): Array<{ table: string; column: string }> {\n return relations\n .filter((r) => r.fromTable === tableName && r.fromColumns.includes(columnName))\n .map((r) => ({\n table: r.toTable,\n column: r.toColumns.join(\", \"),\n }));\n }\n\n /**\n * Format relation links for display in columns table\n */\n private formatRelationLinks(relations: Array<{ table: string; column: string }>): string {\n return relations\n .map((r) => {\n const text = `${r.table}.${r.column}`;\n return this.options.useRelativeLinks ? this.createTableLink(r.table, text) : text;\n })\n .join(\", \");\n }\n\n /**\n * Format constraint type for display\n */\n private formatConstraintType(type: ConstraintDefinition[\"type\"]): string {\n const typeMap: Record<ConstraintDefinition[\"type\"], string> = {\n primary_key: \"PRIMARY KEY\",\n foreign_key: \"FOREIGN KEY\",\n unique: \"UNIQUE\",\n check: \"CHECK\",\n not_null: \"NOT NULL\",\n };\n return typeMap[type] || type;\n }\n\n /**\n * Format constraint definition for display\n */\n private formatConstraintDefinition(constraint: ConstraintDefinition): string {\n if (constraint.definition) {\n return `\\`${constraint.definition}\\``;\n }\n\n const columns = constraint.columns.join(\", \");\n\n if (constraint.type === \"foreign_key\" && constraint.referencedTable) {\n const refColumns = constraint.referencedColumns?.join(\", \") || \"\";\n return `(${columns}) → ${constraint.referencedTable}(${refColumns})`;\n }\n\n return `(${columns})`;\n }\n\n /**\n * Format relation type for display\n */\n private formatRelationType(type: RelationDefinition[\"type\"]): string {\n const typeMap: Record<RelationDefinition[\"type\"], string> = {\n \"one-to-one\": \"One to One\",\n \"one-to-many\": \"One to Many\",\n \"many-to-one\": \"Many to One\",\n \"many-to-many\": \"Many to Many\",\n };\n return typeMap[type] || type;\n }\n\n /**\n * Create a URL-safe slug from a string\n */\n private slugify(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n }\n\n /**\n * Create a table link based on the configured link format\n */\n private createTableLink(tableName: string, displayText?: string): string {\n const text = displayText || tableName;\n if (this.options.linkFormat === \"file\") {\n return `[${text}](./${tableName}.md)`;\n }\n return `[${text}](#${this.slugify(tableName)})`;\n }\n\n /**\n * Escape special Markdown characters in a string\n * Converts newlines to spaces for use in table cells\n */\n private escapeMarkdown(str: string): string {\n return str.replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \" \");\n }\n\n /**\n * Escape special Markdown characters and convert newlines to <br> tags\n * Used for better readability in text sections (not in tables)\n */\n private escapeMarkdownWithBreaks(str: string): string {\n return str.replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \" \\n\");\n }\n}\n"],"names":["DEFAULT_OPTIONS","MarkdownFormatter","options","schema","lines","table","name","columnCount","comment","tableRelations","columns","tableName","columnInfo","col","children","parents","column","type","defaultVal","nullable","childrenStr","parentsStr","constraints","constraint","definition","indexes","index","unique","relations","relation","isParent","parent","child","parentLink","childLink","parentDisplay","childDisplay","enums","enumDef","value","r","columnName","text","refColumns","str","displayText"],"mappings":"AAmCA,MAAMA,IAAsD;AAAA,EAC1D,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,YAAY;AACd;AAYO,MAAMC,EAA6C;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOR,YAAYC,IAAoC,IAAI;AAClD,SAAK,UAAU,EAAE,GAAGF,GAAiB,GAAGE,EAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAOC,GAAoC;AACzC,UAAMC,IAAkB,CAAA;AAGxB,IAAAA,EAAM,KAAK,KAAK,cAAcD,CAAM,CAAC,GAGjCA,EAAO,MAAM,SAAS,MACxBC,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,GAChBA,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,qBAAqBD,EAAO,KAAK,CAAC;AAIpD,eAAWE,KAASF,EAAO;AACzB,MAAAC,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,GAChBA,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,iBAAiBC,GAAOF,CAAM,CAAC;AAGjD,WAAOC,EAAM,KAAK;AAAA,CAAI,EAAE,KAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAcD,GAAoC;AAChD,UAAMC,IAAkB,CAAA;AAKxB,QAHAA,EAAM,KAAK,UAAU,GACrBA,EAAM,KAAK,EAAE,GAETD,EAAO,OAAO,WAAW;AAC3B,aAAAC,EAAM,KAAK,oBAAoB,GACxBA,EAAM,KAAK;AAAA,CAAI;AAIxB,IAAAA,EAAM,KAAK,8BAA8B,GACzCA,EAAM,KAAK,8BAA8B;AAGzC,eAAWC,KAASF,EAAO,QAAQ;AACjC,YAAMG,IAAO,KAAK,QAAQ,mBAAmB,KAAK,gBAAgBD,EAAM,IAAI,IAAIA,EAAM,MAChFE,IAAcF,EAAM,QAAQ,QAC5BG,IACJ,KAAK,QAAQ,mBAAmBH,EAAM,UAAU,KAAK,eAAeA,EAAM,OAAO,IAAI;AAEvF,MAAAD,EAAM,KAAK,KAAKE,CAAI,MAAMC,CAAW,MAAMC,CAAO,IAAI;AAAA,IACxD;AAEA,WAAOJ,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiBC,GAAwBF,GAAoC;AAC3E,UAAMC,IAAkB,CAAA;AAGxB,IAAAA,EAAM,KAAK,MAAMC,EAAM,IAAI,EAAE,GAC7BD,EAAM,KAAK,EAAE,GAGT,KAAK,QAAQ,mBAAmBC,EAAM,YACxCD,EAAM,KAAK,KAAK,yBAAyBC,EAAM,OAAO,CAAC,GACvDD,EAAM,KAAK,EAAE,IAIfA,EAAM,KAAK,KAAK,qBAAqBC,EAAM,SAASA,EAAM,MAAMF,CAAM,CAAC,GAGnE,KAAK,QAAQ,sBAAsBE,EAAM,YAAY,SAAS,MAChED,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,yBAAyBC,EAAM,WAAW,CAAC,IAIzD,KAAK,QAAQ,kBAAkBA,EAAM,QAAQ,SAAS,MACxDD,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,qBAAqBC,EAAM,OAAO,CAAC;AAIrD,UAAMI,IAAiB,KAAK,kBAAkBJ,EAAM,MAAMF,EAAO,SAAS;AAC1E,WAAIM,EAAe,SAAS,MAC1BL,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,KAAK,uBAAuBK,GAAgBJ,EAAM,IAAI,CAAC,IAG7DD,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,qBACNM,GACAC,GACAR,GACQ;AACR,UAAMC,IAAkB,CAAA;AAKxB,QAHAA,EAAM,KAAK,aAAa,GACxBA,EAAM,KAAK,EAAE,GAETM,EAAQ,WAAW;AACrB,aAAAN,EAAM,KAAK,qBAAqB,GACzBA,EAAM,KAAK;AAAA,CAAI;AAIxB,UAAMQ,IAAaF,EAAQ,IAAI,CAACG,MAAQ;AACtC,YAAMC,IAAW,KAAK,kBAAkBH,GAAWE,EAAI,MAAMV,EAAO,SAAS,GACvEY,IAAU,KAAK,mBAAmBJ,GAAWE,EAAI,MAAMV,EAAO,SAAS;AAC7E,aAAO,EAAE,QAAQU,GAAK,UAAAC,GAAU,SAAAC,EAAA;AAAA,IAClC,CAAC;AAGD,IAAAX,EAAM,KAAK,qEAAqE,GAChFA,EAAM,KAAK,qEAAqE;AAGhF,eAAW,EAAE,QAAAY,GAAQ,UAAAF,GAAU,SAAAC,EAAA,KAAaH,GAAY;AACtD,YAAMN,IAAOU,EAAO,aAAa,KAAKA,EAAO,IAAI,OAAOA,EAAO,MACzDC,IAAO,KAAK,eAAeD,EAAO,IAAI,GACtCE,IAAaF,EAAO,iBAAiB,SAAY,KAAKA,EAAO,YAAY,OAAO,KAChFG,IAAWH,EAAO,WAAW,QAAQ,MACrCI,IAAcN,EAAS,SAAS,IAAI,KAAK,oBAAoBA,CAAQ,IAAI,KACzEO,IAAaN,EAAQ,SAAS,IAAI,KAAK,oBAAoBA,CAAO,IAAI,KACtEP,IACJ,KAAK,QAAQ,mBAAmBQ,EAAO,UAAU,KAAK,eAAeA,EAAO,OAAO,IAAI;AAEzF,MAAAZ,EAAM;AAAA,QACJ,KAAKE,CAAI,MAAMW,CAAI,MAAMC,CAAU,MAAMC,CAAQ,MAAMC,CAAW,MAAMC,CAAU,MAAMb,CAAO;AAAA,MAAA;AAAA,IAEnG;AAEA,WAAOJ,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAAyBkB,GAA6C;AAC5E,UAAMlB,IAAkB,CAAA;AAExB,IAAAA,EAAM,KAAK,iBAAiB,GAC5BA,EAAM,KAAK,EAAE,GAEbA,EAAM,KAAK,8BAA8B,GACzCA,EAAM,KAAK,8BAA8B;AAEzC,eAAWmB,KAAcD,GAAa;AACpC,YAAMhB,IAAOiB,EAAW,QAAQ,KAC1BN,IAAO,KAAK,qBAAqBM,EAAW,IAAI,GAChDC,IAAa,KAAK,2BAA2BD,CAAU;AAE7D,MAAAnB,EAAM,KAAK,KAAKE,CAAI,MAAMW,CAAI,MAAMO,CAAU,IAAI;AAAA,IACpD;AAEA,WAAOpB,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqBqB,GAAoC;AAC/D,UAAMrB,IAAkB,CAAA;AAExB,IAAAA,EAAM,KAAK,aAAa,GACxBA,EAAM,KAAK,EAAE,GAEbA,EAAM,KAAK,oCAAoC,GAC/CA,EAAM,KAAK,oCAAoC;AAE/C,eAAWsB,KAASD,GAAS;AAC3B,YAAMnB,IAAOoB,EAAM,QAAQ,KACrBhB,IAAUgB,EAAM,QAAQ,KAAK,IAAI,GACjCC,IAASD,EAAM,SAAS,QAAQ,MAChCT,IAAOS,EAAM,QAAQ;AAE3B,MAAAtB,EAAM,KAAK,KAAKE,CAAI,MAAMI,CAAO,MAAMiB,CAAM,MAAMV,CAAI,IAAI;AAAA,IAC7D;AAEA,WAAOb,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuBwB,GAAiCjB,GAA2B;AACzF,UAAMP,IAAkB,CAAA;AAExB,IAAAA,EAAM,KAAK,eAAe,GAC1BA,EAAM,KAAK,EAAE,GAEbA,EAAM,KAAK,2BAA2B,GACtCA,EAAM,KAAK,2BAA2B;AAEtC,eAAWyB,KAAYD,GAAW;AAChC,YAAME,IAAWD,EAAS,YAAYlB,GAChCoB,IAAS,GAAGF,EAAS,OAAO,IAAIA,EAAS,UAAU,KAAK,IAAI,CAAC,IAC7DG,IAAQ,GAAGH,EAAS,SAAS,IAAIA,EAAS,YAAY,KAAK,IAAI,CAAC,IAChEZ,IAAO,KAAK,mBAAmBY,EAAS,IAAI,GAG5CI,IAAa,KAAK,QAAQ,mBAC5B,KAAK,gBAAgBJ,EAAS,SAASE,CAAM,IAC7CA,GACEG,IAAY,KAAK,QAAQ,mBAC3B,KAAK,gBAAgBL,EAAS,WAAWG,CAAK,IAC9CA,GAGEG,IAAgBL,IAAW,KAAKG,CAAU,OAAOA,GACjDG,IAAgBN,IAAgCI,IAArB,KAAKA,CAAS;AAE/C,MAAA9B,EAAM,KAAK,KAAK+B,CAAa,MAAMC,CAAY,MAAMnB,CAAI,IAAI;AAAA,IAC/D;AAEA,WAAOb,EAAM,KAAK;AAAA,CAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqBiC,GAAiC;AAC5D,UAAMjC,IAAkB,CAAA;AAExB,IAAAA,EAAM,KAAK,SAAS,GACpBA,EAAM,KAAK,EAAE;AAEb,eAAWkC,KAAWD,GAAO;AAC3B,MAAAjC,EAAM,KAAK,MAAMkC,EAAQ,IAAI,EAAE,GAC/BlC,EAAM,KAAK,EAAE,GACbA,EAAM,KAAK,WAAW,GACtBA,EAAM,KAAK,WAAW;AACtB,iBAAWmC,KAASD,EAAQ;AAC1B,QAAAlC,EAAM,KAAK,KAAKmC,CAAK,IAAI;AAE3B,MAAAnC,EAAM,KAAK,EAAE;AAAA,IACf;AAEA,WAAOA,EAAM,KAAK;AAAA,CAAI,EAAE,KAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACNO,GACAiB,GACsB;AACtB,WAAOA,EAAU,OAAO,CAACY,MAAMA,EAAE,cAAc7B,KAAa6B,EAAE,YAAY7B,CAAS;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACNA,GACA8B,GACAb,GAC0C;AAC1C,WAAOA,EACJ,OAAO,CAACY,MAAMA,EAAE,YAAY7B,KAAa6B,EAAE,UAAU,SAASC,CAAU,CAAC,EACzE,IAAI,CAACD,OAAO;AAAA,MACX,OAAOA,EAAE;AAAA,MACT,QAAQA,EAAE,YAAY,KAAK,IAAI;AAAA,IAAA,EAC/B;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,mBACN7B,GACA8B,GACAb,GAC0C;AAC1C,WAAOA,EACJ,OAAO,CAACY,MAAMA,EAAE,cAAc7B,KAAa6B,EAAE,YAAY,SAASC,CAAU,CAAC,EAC7E,IAAI,CAACD,OAAO;AAAA,MACX,OAAOA,EAAE;AAAA,MACT,QAAQA,EAAE,UAAU,KAAK,IAAI;AAAA,IAAA,EAC7B;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoBZ,GAA6D;AACvF,WAAOA,EACJ,IAAI,CAACY,MAAM;AACV,YAAME,IAAO,GAAGF,EAAE,KAAK,IAAIA,EAAE,MAAM;AACnC,aAAO,KAAK,QAAQ,mBAAmB,KAAK,gBAAgBA,EAAE,OAAOE,CAAI,IAAIA;AAAA,IAC/E,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqBzB,GAA4C;AAQvE,WAP8D;AAAA,MAC5D,aAAa;AAAA,MACb,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IAAA,EAEGA,CAAI,KAAKA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKQ,2BAA2BM,GAA0C;AAC3E,QAAIA,EAAW;AACb,aAAO,KAAKA,EAAW,UAAU;AAGnC,UAAMb,IAAUa,EAAW,QAAQ,KAAK,IAAI;AAE5C,QAAIA,EAAW,SAAS,iBAAiBA,EAAW,iBAAiB;AACnE,YAAMoB,IAAapB,EAAW,mBAAmB,KAAK,IAAI,KAAK;AAC/D,aAAO,IAAIb,CAAO,OAAOa,EAAW,eAAe,IAAIoB,CAAU;AAAA,IACnE;AAEA,WAAO,IAAIjC,CAAO;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmBO,GAA0C;AAOnE,WAN4D;AAAA,MAC1D,cAAc;AAAA,MACd,eAAe;AAAA,MACf,eAAe;AAAA,MACf,gBAAgB;AAAA,IAAA,EAEHA,CAAI,KAAKA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKQ,QAAQ2B,GAAqB;AACnC,WAAOA,EACJ,cACA,QAAQ,eAAe,GAAG,EAC1B,QAAQ,UAAU,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgBjC,GAAmBkC,GAA8B;AACvE,UAAMH,IAAOG,KAAelC;AAC5B,WAAI,KAAK,QAAQ,eAAe,SACvB,IAAI+B,CAAI,OAAO/B,CAAS,SAE1B,IAAI+B,CAAI,MAAM,KAAK,QAAQ/B,CAAS,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAeiC,GAAqB;AAC1C,WAAOA,EAAI,QAAQ,OAAO,KAAK,EAAE,QAAQ,OAAO,GAAG;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,yBAAyBA,GAAqB;AACpD,WAAOA,EAAI,QAAQ,OAAO,KAAK,EAAE,QAAQ,OAAO;AAAA,CAAM;AAAA,EACxD;AACF;"}
|
package/dist/parser/comments.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { readFileSync as
|
|
3
|
-
import { join as
|
|
1
|
+
import * as o from "typescript";
|
|
2
|
+
import { readFileSync as x, statSync as h, readdirSync as b } from "node:fs";
|
|
3
|
+
import { join as C } from "node:path";
|
|
4
4
|
function p(t) {
|
|
5
|
-
const n =
|
|
5
|
+
const n = h(t);
|
|
6
6
|
if (n.isFile())
|
|
7
7
|
return t.endsWith(".ts") ? [t] : [];
|
|
8
8
|
if (n.isDirectory()) {
|
|
9
|
-
const
|
|
10
|
-
for (const e of
|
|
11
|
-
const
|
|
12
|
-
e.isDirectory() ?
|
|
9
|
+
const s = [], i = b(t, { withFileTypes: !0 });
|
|
10
|
+
for (const e of i) {
|
|
11
|
+
const r = C(t, e.name);
|
|
12
|
+
e.isDirectory() ? s.push(...p(r)) : e.isFile() && e.name.endsWith(".ts") && !e.name.endsWith(".test.ts") && s.push(r);
|
|
13
13
|
}
|
|
14
|
-
return
|
|
14
|
+
return s;
|
|
15
15
|
}
|
|
16
16
|
return [];
|
|
17
17
|
}
|
|
18
18
|
function L(t) {
|
|
19
|
-
const n = { tables: {} },
|
|
20
|
-
for (const
|
|
21
|
-
const e =
|
|
22
|
-
d(
|
|
19
|
+
const n = { tables: {} }, s = p(t);
|
|
20
|
+
for (const i of s) {
|
|
21
|
+
const e = x(i, "utf-8"), r = o.createSourceFile(i, e, o.ScriptTarget.Latest, !0);
|
|
22
|
+
d(r, r, n);
|
|
23
23
|
}
|
|
24
24
|
return n;
|
|
25
25
|
}
|
|
26
|
-
function d(t, n,
|
|
27
|
-
if (
|
|
28
|
-
const
|
|
26
|
+
function d(t, n, s) {
|
|
27
|
+
if (o.isVariableStatement(t)) {
|
|
28
|
+
const i = g(t, n);
|
|
29
29
|
for (const e of t.declarationList.declarations)
|
|
30
|
-
if (
|
|
31
|
-
const
|
|
30
|
+
if (o.isIdentifier(e.name) && e.initializer && o.isCallExpression(e.initializer)) {
|
|
31
|
+
const r = y(
|
|
32
32
|
e.name.text,
|
|
33
33
|
e.initializer,
|
|
34
34
|
n,
|
|
35
|
-
|
|
35
|
+
i
|
|
36
36
|
);
|
|
37
|
-
|
|
37
|
+
r && (s.tables[r.tableName] = r.tableComment);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
o.forEachChild(t, (i) => d(i, n, s));
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function y(t, n, s, i) {
|
|
43
43
|
const e = T(n);
|
|
44
44
|
if (!S(e))
|
|
45
45
|
return;
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
46
|
+
const r = n.arguments[0];
|
|
47
|
+
if (!r || !o.isStringLiteral(r))
|
|
48
48
|
return;
|
|
49
|
-
const c =
|
|
50
|
-
if (
|
|
51
|
-
for (const a of
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
|
|
49
|
+
const c = r.text, l = n.arguments[1], m = {};
|
|
50
|
+
if (l && o.isObjectLiteralExpression(l)) {
|
|
51
|
+
for (const a of l.properties)
|
|
52
|
+
if (o.isPropertyAssignment(a) && o.isIdentifier(a.name)) {
|
|
53
|
+
const f = v(a.initializer), u = g(a, s);
|
|
54
|
+
f && u && (m[f] = { comment: u });
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
return {
|
|
58
58
|
tableName: c,
|
|
59
59
|
tableComment: {
|
|
60
|
-
comment:
|
|
61
|
-
columns:
|
|
60
|
+
comment: i,
|
|
61
|
+
columns: m
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
function T(t) {
|
|
66
|
-
if (
|
|
66
|
+
if (o.isIdentifier(t.expression))
|
|
67
67
|
return t.expression.text;
|
|
68
|
-
if (
|
|
68
|
+
if (o.isPropertyAccessExpression(t.expression))
|
|
69
69
|
return t.expression.name.text;
|
|
70
70
|
}
|
|
71
71
|
function S(t) {
|
|
@@ -73,39 +73,44 @@ function S(t) {
|
|
|
73
73
|
}
|
|
74
74
|
function v(t) {
|
|
75
75
|
let n = t;
|
|
76
|
-
for (;
|
|
77
|
-
if (
|
|
76
|
+
for (; o.isCallExpression(n); )
|
|
77
|
+
if (o.isPropertyAccessExpression(n.expression))
|
|
78
78
|
n = n.expression.expression;
|
|
79
|
-
else if (
|
|
80
|
-
const
|
|
81
|
-
return
|
|
79
|
+
else if (o.isIdentifier(n.expression)) {
|
|
80
|
+
const s = n.arguments[0];
|
|
81
|
+
return s && o.isStringLiteral(s) ? s.text : void 0;
|
|
82
82
|
} else
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
function
|
|
86
|
-
const
|
|
85
|
+
function g(t, n) {
|
|
86
|
+
const s = n.getFullText(), i = t.getFullStart(), e = o.getLeadingCommentRanges(s, i);
|
|
87
87
|
if (!(!e || e.length === 0)) {
|
|
88
|
-
for (const
|
|
89
|
-
const c =
|
|
88
|
+
for (const r of e) {
|
|
89
|
+
const c = s.slice(r.pos, r.end);
|
|
90
90
|
if (c.startsWith("/**"))
|
|
91
91
|
return D(c);
|
|
92
92
|
}
|
|
93
|
-
for (const
|
|
94
|
-
const c =
|
|
93
|
+
for (const r of e) {
|
|
94
|
+
const c = s.slice(r.pos, r.end);
|
|
95
95
|
if (c.startsWith("//"))
|
|
96
96
|
return c.slice(2).trim();
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
function D(t) {
|
|
101
|
-
const
|
|
102
|
-
`).map((e) => e.replace(/^\s*\*\s?/, "").trim()),
|
|
103
|
-
for (const e of
|
|
101
|
+
const s = t.slice(3, -2).split(`
|
|
102
|
+
`).map((e) => e.replace(/^\s*\*\s?/, "").trim()), i = [];
|
|
103
|
+
for (const e of s) {
|
|
104
104
|
if (e.startsWith("@"))
|
|
105
105
|
break;
|
|
106
|
-
|
|
106
|
+
i.push(e);
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
for (; i.length > 0 && i[i.length - 1] === ""; )
|
|
109
|
+
i.pop();
|
|
110
|
+
for (; i.length > 0 && i[0] === ""; )
|
|
111
|
+
i.shift();
|
|
112
|
+
return i.join(`
|
|
113
|
+
`).trim();
|
|
109
114
|
}
|
|
110
115
|
export {
|
|
111
116
|
L as extractComments
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.js","sources":["../../src/parser/comments.ts"],"sourcesContent":["import * as ts from \"typescript\";\nimport { readFileSync, statSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Comments for a single column\n */\nexport interface ColumnComment {\n comment: string;\n}\n\n/**\n * Comments for a single table\n */\nexport interface TableComment {\n comment?: string;\n columns: Record<string, ColumnComment>;\n}\n\n/**\n * All extracted comments from a schema file\n */\nexport interface SchemaComments {\n tables: Record<string, TableComment>;\n}\n\n/**\n * Get all TypeScript files from a path (file or directory)\n */\nfunction getTypeScriptFiles(sourcePath: string): string[] {\n const stat = statSync(sourcePath);\n\n if (stat.isFile()) {\n return sourcePath.endsWith(\".ts\") ? [sourcePath] : [];\n }\n\n if (stat.isDirectory()) {\n const files: string[] = [];\n const entries = readdirSync(sourcePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(sourcePath, entry.name);\n if (entry.isDirectory()) {\n files.push(...getTypeScriptFiles(fullPath));\n } else if (entry.isFile() && entry.name.endsWith(\".ts\") && !entry.name.endsWith(\".test.ts\")) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n\n return [];\n}\n\n/**\n * Extract JSDoc comments from a Drizzle schema source file or directory\n *\n * Parses TypeScript source files and extracts:\n * - JSDoc comments on table definitions (e.g., pgTable, mysqlTable, sqliteTable)\n * - JSDoc comments on column definitions within tables\n *\n * @param sourcePath - Path to the TypeScript schema file or directory\n * @returns Extracted comments organized by table and column\n */\nexport function extractComments(sourcePath: string): SchemaComments {\n const comments: SchemaComments = { tables: {} };\n const files = getTypeScriptFiles(sourcePath);\n\n for (const filePath of files) {\n const sourceCode = readFileSync(filePath, \"utf-8\");\n const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);\n\n // Visit all nodes in the source file\n visitNode(sourceFile, sourceFile, comments);\n }\n\n return comments;\n}\n\n/**\n * Recursively visit AST nodes to find table and column definitions\n */\nfunction visitNode(node: ts.Node, sourceFile: ts.SourceFile, comments: SchemaComments): void {\n // Look for variable declarations that define tables\n if (ts.isVariableStatement(node)) {\n const jsDocComment = getJsDocComment(node, sourceFile);\n\n for (const declaration of node.declarationList.declarations) {\n if (\n ts.isIdentifier(declaration.name) &&\n declaration.initializer &&\n ts.isCallExpression(declaration.initializer)\n ) {\n const tableInfo = parseTableDefinition(\n declaration.name.text,\n declaration.initializer,\n sourceFile,\n jsDocComment,\n );\n if (tableInfo) {\n comments.tables[tableInfo.tableName] = tableInfo.tableComment;\n }\n }\n }\n }\n\n ts.forEachChild(node, (child) => visitNode(child, sourceFile, comments));\n}\n\n/**\n * Parse a table definition call expression (e.g., pgTable(\"users\", { ... }))\n */\nfunction parseTableDefinition(\n _variableName: string,\n callExpr: ts.CallExpression,\n sourceFile: ts.SourceFile,\n tableJsDoc: string | undefined,\n): { tableName: string; tableComment: TableComment } | undefined {\n const funcName = getCallExpressionName(callExpr);\n\n // Check if this is a table definition function\n if (!isTableDefinitionFunction(funcName)) {\n return undefined;\n }\n\n // Get table name from first argument\n const tableNameArg = callExpr.arguments[0];\n if (!tableNameArg || !ts.isStringLiteral(tableNameArg)) {\n return undefined;\n }\n const tableName = tableNameArg.text;\n\n // Get column definitions from second argument\n const columnsArg = callExpr.arguments[1];\n const columnComments: Record<string, ColumnComment> = {};\n\n if (columnsArg && ts.isObjectLiteralExpression(columnsArg)) {\n for (const property of columnsArg.properties) {\n if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {\n const columnName = extractColumnName(property.initializer);\n const columnJsDoc = getJsDocComment(property, sourceFile);\n\n if (columnName && columnJsDoc) {\n columnComments[columnName] = { comment: columnJsDoc };\n }\n }\n }\n }\n\n return {\n tableName,\n tableComment: {\n comment: tableJsDoc,\n columns: columnComments,\n },\n };\n}\n\n/**\n * Get the function name from a call expression\n */\nfunction getCallExpressionName(callExpr: ts.CallExpression): string | undefined {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n if (ts.isPropertyAccessExpression(callExpr.expression)) {\n return callExpr.expression.name.text;\n }\n return undefined;\n}\n\n/**\n * Check if a function name is a table definition function\n */\nfunction isTableDefinitionFunction(funcName: string | undefined): boolean {\n if (!funcName) return false;\n return [\"pgTable\", \"mysqlTable\", \"sqliteTable\"].includes(funcName);\n}\n\n/**\n * Extract the actual column name from a column definition\n * e.g., serial(\"id\") -> \"id\", text(\"name\") -> \"name\"\n */\nfunction extractColumnName(expr: ts.Expression): string | undefined {\n // Handle chained calls like serial(\"id\").primaryKey()\n let current = expr;\n\n while (ts.isCallExpression(current)) {\n if (ts.isPropertyAccessExpression(current.expression)) {\n // This is a method call like .primaryKey(), go deeper\n current = current.expression.expression;\n } else if (ts.isIdentifier(current.expression)) {\n // This is the base call like serial(\"id\")\n const firstArg = current.arguments[0];\n if (firstArg && ts.isStringLiteral(firstArg)) {\n return firstArg.text;\n }\n return undefined;\n } else {\n return undefined;\n }\n }\n\n return undefined;\n}\n\n/**\n * Get JSDoc comment from a node\n */\nfunction getJsDocComment(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {\n const fullText = sourceFile.getFullText();\n const nodeStart = node.getFullStart();\n const leadingComments = ts.getLeadingCommentRanges(fullText, nodeStart);\n\n if (!leadingComments || leadingComments.length === 0) {\n return undefined;\n }\n\n // Find JSDoc comment (starts with /**)\n for (const comment of leadingComments) {\n const commentText = fullText.slice(comment.pos, comment.end);\n if (commentText.startsWith(\"/**\")) {\n return parseJsDocComment(commentText);\n }\n }\n\n // Fall back to single-line comments\n for (const comment of leadingComments) {\n const commentText = fullText.slice(comment.pos, comment.end);\n if (commentText.startsWith(\"//\")) {\n return commentText.slice(2).trim();\n }\n }\n\n return undefined;\n}\n\n/**\n * Parse JSDoc comment text to extract the description\n */\nfunction parseJsDocComment(commentText: string): string {\n // Remove /** and */\n let text = commentText.slice(3, -2);\n\n // Split into lines and process\n const lines = text.split(\"\\n\").map((line) => {\n // Remove leading * and whitespace\n return line.replace(/^\\s*\\*\\s?/, \"\").trim();\n });\n\n // Filter out @tags and empty lines at start/end\n const contentLines: string[] = [];\n for (const line of lines) {\n // Stop at first @tag\n if (line.startsWith(\"@\")) {\n break;\n }\n contentLines.push(line);\n }\n\n // Join and trim\n return contentLines.join(\" \").trim();\n}\n"],"names":["getTypeScriptFiles","sourcePath","stat","statSync","files","entries","readdirSync","entry","fullPath","join","extractComments","comments","filePath","sourceCode","readFileSync","sourceFile","ts","visitNode","node","jsDocComment","getJsDocComment","declaration","tableInfo","parseTableDefinition","child","_variableName","callExpr","tableJsDoc","funcName","getCallExpressionName","isTableDefinitionFunction","tableNameArg","tableName","columnsArg","columnComments","property","columnName","extractColumnName","columnJsDoc","expr","current","firstArg","fullText","nodeStart","leadingComments","comment","commentText","parseJsDocComment","lines","line","contentLines"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"comments.js","sources":["../../src/parser/comments.ts"],"sourcesContent":["import * as ts from \"typescript\";\nimport { readFileSync, statSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Comments for a single column\n */\nexport interface ColumnComment {\n comment: string;\n}\n\n/**\n * Comments for a single table\n */\nexport interface TableComment {\n comment?: string;\n columns: Record<string, ColumnComment>;\n}\n\n/**\n * All extracted comments from a schema file\n */\nexport interface SchemaComments {\n tables: Record<string, TableComment>;\n}\n\n/**\n * Get all TypeScript files from a path (file or directory)\n */\nfunction getTypeScriptFiles(sourcePath: string): string[] {\n const stat = statSync(sourcePath);\n\n if (stat.isFile()) {\n return sourcePath.endsWith(\".ts\") ? [sourcePath] : [];\n }\n\n if (stat.isDirectory()) {\n const files: string[] = [];\n const entries = readdirSync(sourcePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(sourcePath, entry.name);\n if (entry.isDirectory()) {\n files.push(...getTypeScriptFiles(fullPath));\n } else if (entry.isFile() && entry.name.endsWith(\".ts\") && !entry.name.endsWith(\".test.ts\")) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n\n return [];\n}\n\n/**\n * Extract JSDoc comments from a Drizzle schema source file or directory\n *\n * Parses TypeScript source files and extracts:\n * - JSDoc comments on table definitions (e.g., pgTable, mysqlTable, sqliteTable)\n * - JSDoc comments on column definitions within tables\n *\n * @param sourcePath - Path to the TypeScript schema file or directory\n * @returns Extracted comments organized by table and column\n */\nexport function extractComments(sourcePath: string): SchemaComments {\n const comments: SchemaComments = { tables: {} };\n const files = getTypeScriptFiles(sourcePath);\n\n for (const filePath of files) {\n const sourceCode = readFileSync(filePath, \"utf-8\");\n const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);\n\n // Visit all nodes in the source file\n visitNode(sourceFile, sourceFile, comments);\n }\n\n return comments;\n}\n\n/**\n * Recursively visit AST nodes to find table and column definitions\n */\nfunction visitNode(node: ts.Node, sourceFile: ts.SourceFile, comments: SchemaComments): void {\n // Look for variable declarations that define tables\n if (ts.isVariableStatement(node)) {\n const jsDocComment = getJsDocComment(node, sourceFile);\n\n for (const declaration of node.declarationList.declarations) {\n if (\n ts.isIdentifier(declaration.name) &&\n declaration.initializer &&\n ts.isCallExpression(declaration.initializer)\n ) {\n const tableInfo = parseTableDefinition(\n declaration.name.text,\n declaration.initializer,\n sourceFile,\n jsDocComment,\n );\n if (tableInfo) {\n comments.tables[tableInfo.tableName] = tableInfo.tableComment;\n }\n }\n }\n }\n\n ts.forEachChild(node, (child) => visitNode(child, sourceFile, comments));\n}\n\n/**\n * Parse a table definition call expression (e.g., pgTable(\"users\", { ... }))\n */\nfunction parseTableDefinition(\n _variableName: string,\n callExpr: ts.CallExpression,\n sourceFile: ts.SourceFile,\n tableJsDoc: string | undefined,\n): { tableName: string; tableComment: TableComment } | undefined {\n const funcName = getCallExpressionName(callExpr);\n\n // Check if this is a table definition function\n if (!isTableDefinitionFunction(funcName)) {\n return undefined;\n }\n\n // Get table name from first argument\n const tableNameArg = callExpr.arguments[0];\n if (!tableNameArg || !ts.isStringLiteral(tableNameArg)) {\n return undefined;\n }\n const tableName = tableNameArg.text;\n\n // Get column definitions from second argument\n const columnsArg = callExpr.arguments[1];\n const columnComments: Record<string, ColumnComment> = {};\n\n if (columnsArg && ts.isObjectLiteralExpression(columnsArg)) {\n for (const property of columnsArg.properties) {\n if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {\n const columnName = extractColumnName(property.initializer);\n const columnJsDoc = getJsDocComment(property, sourceFile);\n\n if (columnName && columnJsDoc) {\n columnComments[columnName] = { comment: columnJsDoc };\n }\n }\n }\n }\n\n return {\n tableName,\n tableComment: {\n comment: tableJsDoc,\n columns: columnComments,\n },\n };\n}\n\n/**\n * Get the function name from a call expression\n */\nfunction getCallExpressionName(callExpr: ts.CallExpression): string | undefined {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n if (ts.isPropertyAccessExpression(callExpr.expression)) {\n return callExpr.expression.name.text;\n }\n return undefined;\n}\n\n/**\n * Check if a function name is a table definition function\n */\nfunction isTableDefinitionFunction(funcName: string | undefined): boolean {\n if (!funcName) return false;\n return [\"pgTable\", \"mysqlTable\", \"sqliteTable\"].includes(funcName);\n}\n\n/**\n * Extract the actual column name from a column definition\n * e.g., serial(\"id\") -> \"id\", text(\"name\") -> \"name\"\n */\nfunction extractColumnName(expr: ts.Expression): string | undefined {\n // Handle chained calls like serial(\"id\").primaryKey()\n let current = expr;\n\n while (ts.isCallExpression(current)) {\n if (ts.isPropertyAccessExpression(current.expression)) {\n // This is a method call like .primaryKey(), go deeper\n current = current.expression.expression;\n } else if (ts.isIdentifier(current.expression)) {\n // This is the base call like serial(\"id\")\n const firstArg = current.arguments[0];\n if (firstArg && ts.isStringLiteral(firstArg)) {\n return firstArg.text;\n }\n return undefined;\n } else {\n return undefined;\n }\n }\n\n return undefined;\n}\n\n/**\n * Get JSDoc comment from a node\n */\nfunction getJsDocComment(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {\n const fullText = sourceFile.getFullText();\n const nodeStart = node.getFullStart();\n const leadingComments = ts.getLeadingCommentRanges(fullText, nodeStart);\n\n if (!leadingComments || leadingComments.length === 0) {\n return undefined;\n }\n\n // Find JSDoc comment (starts with /**)\n for (const comment of leadingComments) {\n const commentText = fullText.slice(comment.pos, comment.end);\n if (commentText.startsWith(\"/**\")) {\n return parseJsDocComment(commentText);\n }\n }\n\n // Fall back to single-line comments\n for (const comment of leadingComments) {\n const commentText = fullText.slice(comment.pos, comment.end);\n if (commentText.startsWith(\"//\")) {\n return commentText.slice(2).trim();\n }\n }\n\n return undefined;\n}\n\n/**\n * Parse JSDoc comment text to extract the description\n *\n * Preserves newlines in the output for proper formatting in DBML and Markdown.\n */\nfunction parseJsDocComment(commentText: string): string {\n // Remove /** and */\n let text = commentText.slice(3, -2);\n\n // Split into lines and process\n const lines = text.split(\"\\n\").map((line) => {\n // Remove leading * and whitespace\n return line.replace(/^\\s*\\*\\s?/, \"\").trim();\n });\n\n // Filter out @tags and empty lines at start/end\n const contentLines: string[] = [];\n for (const line of lines) {\n // Stop at first @tag\n if (line.startsWith(\"@\")) {\n break;\n }\n contentLines.push(line);\n }\n\n // Remove trailing empty lines\n while (contentLines.length > 0 && contentLines[contentLines.length - 1] === \"\") {\n contentLines.pop();\n }\n\n // Remove leading empty lines\n while (contentLines.length > 0 && contentLines[0] === \"\") {\n contentLines.shift();\n }\n\n // Join with newlines to preserve formatting\n return contentLines.join(\"\\n\").trim();\n}\n"],"names":["getTypeScriptFiles","sourcePath","stat","statSync","files","entries","readdirSync","entry","fullPath","join","extractComments","comments","filePath","sourceCode","readFileSync","sourceFile","ts","visitNode","node","jsDocComment","getJsDocComment","declaration","tableInfo","parseTableDefinition","child","_variableName","callExpr","tableJsDoc","funcName","getCallExpressionName","isTableDefinitionFunction","tableNameArg","tableName","columnsArg","columnComments","property","columnName","extractColumnName","columnJsDoc","expr","current","firstArg","fullText","nodeStart","leadingComments","comment","commentText","parseJsDocComment","lines","line","contentLines"],"mappings":";;;AA6BA,SAASA,EAAmBC,GAA8B;AACxD,QAAMC,IAAOC,EAASF,CAAU;AAEhC,MAAIC,EAAK;AACP,WAAOD,EAAW,SAAS,KAAK,IAAI,CAACA,CAAU,IAAI,CAAA;AAGrD,MAAIC,EAAK,eAAe;AACtB,UAAME,IAAkB,CAAA,GAClBC,IAAUC,EAAYL,GAAY,EAAE,eAAe,IAAM;AAE/D,eAAWM,KAASF,GAAS;AAC3B,YAAMG,IAAWC,EAAKR,GAAYM,EAAM,IAAI;AAC5C,MAAIA,EAAM,gBACRH,EAAM,KAAK,GAAGJ,EAAmBQ,CAAQ,CAAC,IACjCD,EAAM,OAAA,KAAYA,EAAM,KAAK,SAAS,KAAK,KAAK,CAACA,EAAM,KAAK,SAAS,UAAU,KACxFH,EAAM,KAAKI,CAAQ;AAAA,IAEvB;AAEA,WAAOJ;AAAA,EACT;AAEA,SAAO,CAAA;AACT;AAYO,SAASM,EAAgBT,GAAoC;AAClE,QAAMU,IAA2B,EAAE,QAAQ,GAAC,GACtCP,IAAQJ,EAAmBC,CAAU;AAE3C,aAAWW,KAAYR,GAAO;AAC5B,UAAMS,IAAaC,EAAaF,GAAU,OAAO,GAC3CG,IAAaC,EAAG,iBAAiBJ,GAAUC,GAAYG,EAAG,aAAa,QAAQ,EAAI;AAGzF,IAAAC,EAAUF,GAAYA,GAAYJ,CAAQ;AAAA,EAC5C;AAEA,SAAOA;AACT;AAKA,SAASM,EAAUC,GAAeH,GAA2BJ,GAAgC;AAE3F,MAAIK,EAAG,oBAAoBE,CAAI,GAAG;AAChC,UAAMC,IAAeC,EAAgBF,GAAMH,CAAU;AAErD,eAAWM,KAAeH,EAAK,gBAAgB;AAC7C,UACEF,EAAG,aAAaK,EAAY,IAAI,KAChCA,EAAY,eACZL,EAAG,iBAAiBK,EAAY,WAAW,GAC3C;AACA,cAAMC,IAAYC;AAAA,UAChBF,EAAY,KAAK;AAAA,UACjBA,EAAY;AAAA,UACZN;AAAA,UACAI;AAAA,QAAA;AAEF,QAAIG,MACFX,EAAS,OAAOW,EAAU,SAAS,IAAIA,EAAU;AAAA,MAErD;AAAA,EAEJ;AAEA,EAAAN,EAAG,aAAaE,GAAM,CAACM,MAAUP,EAAUO,GAAOT,GAAYJ,CAAQ,CAAC;AACzE;AAKA,SAASY,EACPE,GACAC,GACAX,GACAY,GAC+D;AAC/D,QAAMC,IAAWC,EAAsBH,CAAQ;AAG/C,MAAI,CAACI,EAA0BF,CAAQ;AACrC;AAIF,QAAMG,IAAeL,EAAS,UAAU,CAAC;AACzC,MAAI,CAACK,KAAgB,CAACf,EAAG,gBAAgBe,CAAY;AACnD;AAEF,QAAMC,IAAYD,EAAa,MAGzBE,IAAaP,EAAS,UAAU,CAAC,GACjCQ,IAAgD,CAAA;AAEtD,MAAID,KAAcjB,EAAG,0BAA0BiB,CAAU;AACvD,eAAWE,KAAYF,EAAW;AAChC,UAAIjB,EAAG,qBAAqBmB,CAAQ,KAAKnB,EAAG,aAAamB,EAAS,IAAI,GAAG;AACvE,cAAMC,IAAaC,EAAkBF,EAAS,WAAW,GACnDG,IAAclB,EAAgBe,GAAUpB,CAAU;AAExD,QAAIqB,KAAcE,MAChBJ,EAAeE,CAAU,IAAI,EAAE,SAASE,EAAA;AAAA,MAE5C;AAAA;AAIJ,SAAO;AAAA,IACL,WAAAN;AAAA,IACA,cAAc;AAAA,MACZ,SAASL;AAAA,MACT,SAASO;AAAA,IAAA;AAAA,EACX;AAEJ;AAKA,SAASL,EAAsBH,GAAiD;AAC9E,MAAIV,EAAG,aAAaU,EAAS,UAAU;AACrC,WAAOA,EAAS,WAAW;AAE7B,MAAIV,EAAG,2BAA2BU,EAAS,UAAU;AACnD,WAAOA,EAAS,WAAW,KAAK;AAGpC;AAKA,SAASI,EAA0BF,GAAuC;AACxE,SAAKA,IACE,CAAC,WAAW,cAAc,aAAa,EAAE,SAASA,CAAQ,IAD3C;AAExB;AAMA,SAASS,EAAkBE,GAAyC;AAElE,MAAIC,IAAUD;AAEd,SAAOvB,EAAG,iBAAiBwB,CAAO;AAChC,QAAIxB,EAAG,2BAA2BwB,EAAQ,UAAU;AAElD,MAAAA,IAAUA,EAAQ,WAAW;AAAA,aACpBxB,EAAG,aAAawB,EAAQ,UAAU,GAAG;AAE9C,YAAMC,IAAWD,EAAQ,UAAU,CAAC;AACpC,aAAIC,KAAYzB,EAAG,gBAAgByB,CAAQ,IAClCA,EAAS,OAElB;AAAA,IACF;AACE;AAKN;AAKA,SAASrB,EAAgBF,GAAeH,GAA+C;AACrF,QAAM2B,IAAW3B,EAAW,YAAA,GACtB4B,IAAYzB,EAAK,aAAA,GACjB0B,IAAkB5B,EAAG,wBAAwB0B,GAAUC,CAAS;AAEtE,MAAI,GAACC,KAAmBA,EAAgB,WAAW,IAKnD;AAAA,eAAWC,KAAWD,GAAiB;AACrC,YAAME,IAAcJ,EAAS,MAAMG,EAAQ,KAAKA,EAAQ,GAAG;AAC3D,UAAIC,EAAY,WAAW,KAAK;AAC9B,eAAOC,EAAkBD,CAAW;AAAA,IAExC;AAGA,eAAWD,KAAWD,GAAiB;AACrC,YAAME,IAAcJ,EAAS,MAAMG,EAAQ,KAAKA,EAAQ,GAAG;AAC3D,UAAIC,EAAY,WAAW,IAAI;AAC7B,eAAOA,EAAY,MAAM,CAAC,EAAE,KAAA;AAAA,IAEhC;AAAA;AAGF;AAOA,SAASC,EAAkBD,GAA6B;AAKtD,QAAME,IAHKF,EAAY,MAAM,GAAG,EAAE,EAGf,MAAM;AAAA,CAAI,EAAE,IAAI,CAACG,MAE3BA,EAAK,QAAQ,aAAa,EAAE,EAAE,KAAA,CACtC,GAGKC,IAAyB,CAAA;AAC/B,aAAWD,KAAQD,GAAO;AAExB,QAAIC,EAAK,WAAW,GAAG;AACrB;AAEF,IAAAC,EAAa,KAAKD,CAAI;AAAA,EACxB;AAGA,SAAOC,EAAa,SAAS,KAAKA,EAAaA,EAAa,SAAS,CAAC,MAAM;AAC1E,IAAAA,EAAa,IAAA;AAIf,SAAOA,EAAa,SAAS,KAAKA,EAAa,CAAC,MAAM;AACpD,IAAAA,EAAa,MAAA;AAIf,SAAOA,EAAa,KAAK;AAAA,CAAI,EAAE,KAAA;AACjC;"}
|