drizzle-docs-generator 0.4.0 → 0.5.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/README.ja.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # drizzle-docs-generator
2
2
 
3
- [![npm version](https://badge.fury.io/js/drizzle-docs-generator.svg)](https://www.npmjs.com/package/drizzle-docs-generator)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
3
+ [![NPM](https://nodei.co/npm/drizzle-docs-generator.svg?style=shields&data=v,d&color=brightgreen)](https://nodei.co/npm/drizzle-docs-generator/)
5
4
 
6
- Drizzle ORM スキーマから DBML を生成する CLI。JSDoc コメントを Note 句として出力できる。
5
+ Drizzle ORM スキーマから DBML Markdown ドキュメントを生成する CLI。JSDoc コメントを Note 句として出力できる。
7
6
 
8
7
  **機能:**
9
8
 
@@ -12,62 +11,97 @@ Drizzle ORM スキーマから DBML を生成する CLI。JSDoc コメントを
12
11
  - **JSDoc コメント**: 自動的に DBML の Note 句に変換
13
12
  - **リレーション対応**: `relations()` または `defineRelations()` から参照を生成
14
13
  - **Watch モード**: ファイル変更時に自動再生成
15
- - **複数の出力形式**: DBML (デフォルト) および ER 図付き Markdown
14
+ - **複数の出力形式**: Markdown (デフォルト) および ER 図付き DBML
16
15
 
17
16
  [English README](./README.md)
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
  ## 使い方
28
43
 
29
- ### DBML 出力 (デフォルト)
44
+ ### 基本的な使用方法
30
45
 
31
46
  ```bash
32
- # 基本 - 単一ファイル
33
- drizzle-docs generate ./src/db/schema.ts -d postgresql
47
+ # Markdown 出力 (デフォルト)
48
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -o ./docs
34
49
 
35
- # ディレクトリ - ディレクトリ内のすべてのスキーマファイルをインポート
36
- drizzle-docs generate ./src/db/schema/ -d postgresql
50
+ # DBML 出力
51
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -o schema.dbml
52
+ ```
53
+
54
+ ### 出力形式のオプション
55
+
56
+ #### Markdown 形式 (デフォルト)
57
+
58
+ デフォルトの出力形式は **Markdown** で、ER 図付きの複数ファイルを生成します。
59
+
60
+ **Markdown 形式固有のオプション:**
61
+
62
+ | オプション | 説明 |
63
+ | ----------------- | -------------------------------------- |
64
+ | `--single-file` | 複数ファイルではなく単一ファイルで出力 |
65
+ | `--no-er-diagram` | 出力から ER 図を除外 |
66
+
67
+ **例:**
37
68
 
38
- # ファイル出力
39
- drizzle-docs generate ./src/db/schema.ts -d postgresql -o schema.dbml
69
+ ```bash
70
+ # ER 図付き複数ファイル (デフォルト)
71
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -o ./docs
40
72
 
41
- # relations() を使う
42
- drizzle-docs generate ./src/db/schema.ts -d postgresql -r
73
+ # 単一ファイル Markdown
74
+ drizzle-docs generate ./src/db/schema.ts -d postgresql --single-file -o schema.md
43
75
 
44
- # watch モード
45
- drizzle-docs generate ./src/db/schema.ts -d postgresql -w
76
+ # ER 図なしの複数ファイル
77
+ drizzle-docs generate ./src/db/schema.ts -d postgresql --no-er-diagram -o ./docs
46
78
  ```
47
79
 
48
- ### Markdown 出力
80
+ #### DBML 形式
81
+
82
+ `-f dbml` または `--format dbml` オプションを使用して DBML 形式を生成します。
83
+
84
+ **例:**
49
85
 
50
86
  ```bash
51
- # Markdown 出力 (ER 図付き複数ファイル)
52
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown -o ./docs
87
+ # ファイルに出力
88
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -o schema.dbml
53
89
 
54
- # Markdown 出力 (単一ファイル)
55
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown --single-file -o schema.md
90
+ # ディレクトリ - ディレクトリ内のすべてのスキーマファイルをインポート
91
+ drizzle-docs generate ./src/db/schema/ -d postgresql -f dbml -o schema.dbml
56
92
 
57
- # ER 図なしの Markdown
58
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown --no-er-diagram -o ./docs
93
+ # Watch モード
94
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -w
59
95
  ```
60
96
 
61
- ### オプション
97
+ #### 共通オプション
62
98
 
63
99
  | オプション | 説明 |
64
100
  | ------------------------- | ----------------------------------------------------- |
65
101
  | `-o, --output <path>` | 出力ファイルまたはディレクトリパス |
66
102
  | `-d, --dialect <dialect>` | DB 種別: `postgresql` (デフォルト), `mysql`, `sqlite` |
67
- | `-f, --format <format>` | 出力形式: `dbml` (デフォルト), `markdown` |
103
+ | `-f, --format <format>` | 出力形式: `markdown` (デフォルト), `dbml` |
68
104
  | `-w, --watch` | ファイル変更時に自動再生成 |
69
- | `--single-file` | Markdown を単一ファイルで出力 (markdown のみ) |
70
- | `--no-er-diagram` | ER 図を Markdown 出力から除外 |
71
105
  | `--force` | 確認なしで既存ファイルを上書き |
72
106
 
73
107
  ### リレーション検出
@@ -119,37 +153,6 @@ Table users {
119
153
 
120
154
  詳細なサンプル出力は [examples/](./examples/) を参照してください。
121
155
 
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
156
  ## License
154
157
 
155
158
  MIT
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # drizzle-docs-generator
2
2
 
3
- [![npm version](https://badge.fury.io/js/drizzle-docs-generator.svg)](https://www.npmjs.com/package/drizzle-docs-generator)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
3
+ [![NPM](https://nodei.co/npm/drizzle-docs-generator.svg?style=shields&data=v,d&color=brightgreen)](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
 
@@ -12,62 +11,97 @@ CLI tool to generate DBML from Drizzle ORM schemas. Extracts JSDoc comments and
12
11
  - **JSDoc Comments**: Automatically extracts and converts to DBML Notes
13
12
  - **Relations Support**: Generate refs from `relations()` or `defineRelations()`
14
13
  - **Watch Mode**: Auto-regenerate on file changes
15
- - **Multiple Output Formats**: DBML (default) and Markdown with ER diagrams
14
+ - **Multiple Output Formats**: Markdown (default) and DBML with ER diagrams
16
15
 
17
16
  [日本語版READMEはこちら](./README.ja.md)
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
28
43
 
29
- ### DBML Output (Default)
44
+ ### Basic Usage
30
45
 
31
46
  ```bash
32
- # Basic - single file
33
- drizzle-docs generate ./src/db/schema.ts -d postgresql
47
+ # Markdown output (default)
48
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -o ./docs
34
49
 
35
- # Directory - import all schema files from directory
36
- drizzle-docs generate ./src/db/schema/ -d postgresql
50
+ # DBML output
51
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -o schema.dbml
52
+ ```
37
53
 
38
- # Output to file
39
- drizzle-docs generate ./src/db/schema.ts -d postgresql -o schema.dbml
54
+ ### Output Format Options
40
55
 
41
- # Use relations() definitions
42
- drizzle-docs generate ./src/db/schema.ts -d postgresql -r
56
+ #### Markdown Format (Default)
43
57
 
44
- # Watch mode
45
- drizzle-docs generate ./src/db/schema.ts -d postgresql -w
58
+ The default output format is **Markdown**, which generates multiple files with an ER diagram.
59
+
60
+ **Options specific to Markdown format:**
61
+
62
+ | Option | Description |
63
+ | ----------------- | ------------------------------------------- |
64
+ | `--single-file` | Output as a single file instead of multiple |
65
+ | `--no-er-diagram` | Exclude ER diagram from output |
66
+
67
+ **Examples:**
68
+
69
+ ```bash
70
+ # Multiple files with ER diagram (default)
71
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -o ./docs
72
+
73
+ # Single file Markdown
74
+ drizzle-docs generate ./src/db/schema.ts -d postgresql --single-file -o schema.md
75
+
76
+ # Multiple files without ER diagram
77
+ drizzle-docs generate ./src/db/schema.ts -d postgresql --no-er-diagram -o ./docs
46
78
  ```
47
79
 
48
- ### Markdown Output
80
+ #### DBML Format
81
+
82
+ Use the `-f dbml` or `--format dbml` option to generate DBML format.
83
+
84
+ **Examples:**
49
85
 
50
86
  ```bash
51
- # Markdown output (multiple files with ER diagram)
52
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown -o ./docs
87
+ # Output to file
88
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -o schema.dbml
53
89
 
54
- # Markdown output (single file)
55
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown --single-file -o schema.md
90
+ # Directory - import all schema files from directory
91
+ drizzle-docs generate ./src/db/schema/ -d postgresql -f dbml -o schema.dbml
56
92
 
57
- # Markdown without ER diagram
58
- drizzle-docs generate ./src/db/schema.ts -d postgresql -f markdown --no-er-diagram -o ./docs
93
+ # Watch mode
94
+ drizzle-docs generate ./src/db/schema.ts -d postgresql -f dbml -w
59
95
  ```
60
96
 
61
- ### Options
97
+ #### Common Options
62
98
 
63
99
  | Option | Description |
64
100
  | ------------------------- | --------------------------------------------------- |
65
101
  | `-o, --output <path>` | Output file or directory path |
66
102
  | `-d, --dialect <dialect>` | Database: `postgresql` (default), `mysql`, `sqlite` |
67
- | `-f, --format <format>` | Output format: `dbml` (default), `markdown` |
103
+ | `-f, --format <format>` | Output format: `markdown` (default), `dbml` |
68
104
  | `-w, --watch` | Regenerate on file changes |
69
- | `--single-file` | Output Markdown as a single file (markdown only) |
70
- | `--no-er-diagram` | Exclude ER diagram from Markdown output |
71
105
  | `--force` | Overwrite existing files without confirmation |
72
106
 
73
107
  ### Relation Detection
@@ -119,37 +153,6 @@ Users table
119
153
 
120
154
  See [examples/](./examples/) for more detailed output samples.
121
155
 
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
156
  ## License
154
157
 
155
158
  MIT
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI for drizzle-docs-generator\n *\n * Generates DBML files from Drizzle ORM schema definitions.\n */\n\nimport { Command } from \"commander\";\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n watch,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { pgGenerate } from \"../generator/pg\";\nimport { mysqlGenerate } from \"../generator/mysql\";\nimport { sqliteGenerate } from \"../generator/sqlite\";\nimport { PgGenerator } from \"../generator/pg\";\nimport { MySqlGenerator } from \"../generator/mysql\";\nimport { SqliteGenerator } from \"../generator/sqlite\";\nimport { MarkdownFormatter } from \"../formatter/markdown\";\nimport { MermaidErDiagramFormatter } from \"../formatter/mermaid\";\nimport { register } from \"tsx/esm/api\";\nimport type { IntermediateSchema } from \"../types\";\n\n// Register tsx loader to support TypeScript and extensionless imports\nconst unregister = register();\n\nconst program = new Command();\n\n// Use import.meta.dirname (Node 20.11+) to resolve package.json\n// This works correctly even after bundling\nconst packageJsonPath = join(import.meta.dirname, \"../../package.json\");\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n version: string;\n description: string;\n};\n\nprogram.name(\"drizzle-docs\").description(packageJson.description).version(packageJson.version);\n\ntype Dialect = \"postgresql\" | \"mysql\" | \"sqlite\";\ntype OutputFormat = \"dbml\" | \"markdown\";\n\ninterface GenerateCommandOptions {\n output?: string;\n dialect: Dialect;\n watch?: boolean;\n format: OutputFormat;\n singleFile?: boolean;\n erDiagram: boolean; // commander uses --no-er-diagram which sets erDiagram to false\n force?: boolean; // skip overwrite confirmation for existing files\n}\n\n/**\n * Get the generate function based on dialect\n */\nfunction getGenerateFunction(dialect: Dialect) {\n switch (dialect) {\n case \"mysql\":\n return mysqlGenerate;\n case \"sqlite\":\n return sqliteGenerate;\n case \"postgresql\":\n default:\n return pgGenerate;\n }\n}\n\n/**\n * Get the generator class based on dialect\n */\nfunction getGeneratorClass(dialect: Dialect) {\n switch (dialect) {\n case \"mysql\":\n return MySqlGenerator;\n case \"sqlite\":\n return SqliteGenerator;\n case \"postgresql\":\n default:\n return PgGenerator;\n }\n}\n\n/**\n * Check if output directory has existing files\n */\nfunction hasExistingFiles(outputDir: string, tableNames: string[]): string[] {\n const existingFiles: string[] = [];\n\n if (!existsSync(outputDir)) {\n return existingFiles;\n }\n\n const readmePath = join(outputDir, \"README.md\");\n if (existsSync(readmePath)) {\n existingFiles.push(readmePath);\n }\n\n for (const tableName of tableNames) {\n const tablePath = join(outputDir, `${tableName}.md`);\n if (existsSync(tablePath)) {\n existingFiles.push(tablePath);\n }\n }\n\n return existingFiles;\n}\n\n/**\n * Generate output from a schema file (for watch mode)\n * Returns the generated output string for single-file formats,\n * or writes multiple files directly for multi-file markdown\n */\nasync function generateFromSchema(\n schemaPath: string,\n options: GenerateCommandOptions,\n): Promise<string | undefined> {\n // Use file URL for dynamic import (required for ESM)\n const schemaUrl = pathToFileURL(schemaPath).href;\n\n // Dynamic import with cache busting for watch mode\n const cacheBuster = options.watch ? `?t=${Date.now()}` : \"\";\n const schemaModule = (await import(schemaUrl + cacheBuster)) as Record<string, unknown>;\n\n if (options.format === \"markdown\") {\n const GeneratorClass = getGeneratorClass(options.dialect);\n const generator = new GeneratorClass({\n schema: schemaModule,\n source: schemaPath,\n });\n const intermediateSchema = generator.toIntermediateSchema();\n\n // Handle multi-file output in watch mode\n if (!options.singleFile && options.output) {\n writeMarkdownMultipleFiles(intermediateSchema, options.output, options);\n return undefined;\n }\n\n return generateMarkdownOutput(intermediateSchema, options);\n } else {\n const generate = getGenerateFunction(options.dialect);\n return generate({\n schema: schemaModule,\n source: schemaPath,\n });\n }\n}\n\n/**\n * Find all TypeScript schema files in a directory\n */\nfunction findSchemaFiles(dirPath: string): string[] {\n const files: string[] = [];\n\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // Recursively search subdirectories\n files.push(...findSchemaFiles(fullPath));\n } else if (entry.isFile() && /\\.(ts|js|mts|mjs|cts|cjs)$/.test(entry.name)) {\n files.push(fullPath);\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error reading directory ${dirPath}: ${error.message}`);\n }\n }\n\n return files;\n}\n\n/**\n * Resolve schema path (file or directory)\n */\nfunction resolveSchemaPath(schema: string): string[] {\n const schemaPath = resolve(process.cwd(), schema);\n\n // Check if path exists\n if (!existsSync(schemaPath)) {\n console.error(`Error: Schema path not found: ${schemaPath}`);\n process.exit(1);\n }\n\n // Check if it's a directory\n const stats = lstatSync(schemaPath);\n if (stats.isDirectory()) {\n const schemaFiles = findSchemaFiles(schemaPath);\n if (schemaFiles.length === 0) {\n console.error(`Error: No schema files found in directory: ${schemaPath}`);\n process.exit(1);\n }\n return schemaFiles;\n }\n\n // Single file\n return [schemaPath];\n}\n\n/**\n * Generate DBML format output\n */\nfunction generateDbmlOutput(\n mergedSchema: Record<string, unknown>,\n schemaPaths: string[],\n options: GenerateCommandOptions,\n): string {\n const generate = getGenerateFunction(options.dialect);\n return (\n generate({\n schema: mergedSchema,\n source: schemaPaths[0],\n }) || \"\"\n );\n}\n\n/**\n * Generate Markdown format output\n */\nfunction generateMarkdownOutput(\n intermediateSchema: IntermediateSchema,\n options: GenerateCommandOptions,\n): string {\n const markdownFormatter = new MarkdownFormatter();\n const markdown = markdownFormatter.format(intermediateSchema);\n\n // Include ER diagram unless --no-er-diagram is specified\n if (options.erDiagram) {\n const mermaidFormatter = new MermaidErDiagramFormatter();\n const erDiagram = mermaidFormatter.format(intermediateSchema);\n\n return `${markdown}\\n\\n---\\n\\n## ER Diagram\\n\\n\\`\\`\\`mermaid\\n${erDiagram}\\n\\`\\`\\``;\n }\n\n return markdown;\n}\n\n/**\n * Write Markdown to multiple files (one per table)\n */\nfunction writeMarkdownMultipleFiles(\n intermediateSchema: IntermediateSchema,\n outputDir: string,\n options: GenerateCommandOptions,\n): void {\n // Ensure output directory exists\n mkdirSync(outputDir, { recursive: true });\n\n const markdownFormatter = new MarkdownFormatter({ linkFormat: \"file\" });\n\n // Write README.md with index\n const index = markdownFormatter.generateIndex(intermediateSchema);\n let readme = `${index}\\n`;\n\n // Add ER diagram to README unless disabled\n if (options.erDiagram) {\n const mermaidFormatter = new MermaidErDiagramFormatter();\n const erDiagram = mermaidFormatter.format(intermediateSchema);\n readme += `\\n---\\n\\n## ER Diagram\\n\\n\\`\\`\\`mermaid\\n${erDiagram}\\n\\`\\`\\`\\n`;\n }\n\n writeFileSync(join(outputDir, \"README.md\"), readme, \"utf-8\");\n\n // Write individual table files\n for (const table of intermediateSchema.tables) {\n const tableDoc = markdownFormatter.generateTableDoc(table, intermediateSchema);\n const fileName = `${table.name}.md`;\n writeFileSync(join(outputDir, fileName), `# ${table.name}\\n\\n${tableDoc}\\n`, \"utf-8\");\n }\n}\n\n/**\n * Write single Markdown file\n */\nfunction writeSingleMarkdownFile(content: string, outputPath: string): void {\n const dir = dirname(outputPath);\n mkdirSync(dir, { recursive: true });\n writeFileSync(outputPath, content.endsWith(\"\\n\") ? content : content + \"\\n\", \"utf-8\");\n}\n\n/**\n * Run the generate command\n */\nasync function runGenerate(schema: string, options: GenerateCommandOptions): Promise<void> {\n const schemaPaths = resolveSchemaPath(schema);\n\n try {\n // Merge all schema modules\n const mergedSchema: Record<string, unknown> = {};\n\n for (const schemaPath of schemaPaths) {\n const schemaUrl = pathToFileURL(schemaPath).href;\n const cacheBuster = options.watch ? `?t=${Date.now()}` : \"\";\n\n try {\n const schemaModule = (await import(schemaUrl + cacheBuster)) as Record<string, unknown>;\n Object.assign(mergedSchema, schemaModule);\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error importing ${schemaPath}: ${error.message}`);\n console.error(\"\\nPossible causes:\");\n console.error(\"- Syntax error in the schema file\");\n console.error(\"- Missing dependencies (make sure drizzle-orm is installed)\");\n console.error(\"- Circular dependencies between schema files\");\n }\n throw error;\n }\n }\n\n if (options.format === \"markdown\") {\n // Generate Markdown format\n const GeneratorClass = getGeneratorClass(options.dialect);\n const generator = new GeneratorClass({\n schema: mergedSchema,\n source: schemaPaths[0],\n });\n const intermediateSchema = generator.toIntermediateSchema();\n\n if (options.singleFile) {\n // Single file Markdown output\n const content = generateMarkdownOutput(intermediateSchema, options);\n\n if (options.output) {\n // Check for existing file if --force is not specified\n if (!options.force && existsSync(options.output)) {\n console.error(\n `Error: Output file already exists: ${options.output}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n writeSingleMarkdownFile(content, options.output);\n console.log(`Markdown generated: ${options.output}`);\n } else {\n console.log(content);\n }\n } else {\n // Multiple files Markdown output\n if (!options.output) {\n // If no output specified, default to stdout with single file format\n const content = generateMarkdownOutput(intermediateSchema, options);\n console.log(content);\n } else {\n // Check for existing files if --force is not specified\n if (!options.force) {\n const tableNames = intermediateSchema.tables.map((t) => t.name);\n const existingFiles = hasExistingFiles(options.output, tableNames);\n if (existingFiles.length > 0) {\n console.error(\n `Error: The following files already exist:\\n${existingFiles.map((f) => ` - ${f}`).join(\"\\n\")}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n }\n writeMarkdownMultipleFiles(intermediateSchema, options.output, options);\n console.log(`Markdown generated: ${options.output}/`);\n }\n }\n } else {\n // Generate DBML format (default)\n const dbml = generateDbmlOutput(mergedSchema, schemaPaths, options);\n\n if (options.output) {\n // Check for existing file if --force is not specified\n if (!options.force && existsSync(options.output)) {\n console.error(\n `Error: Output file already exists: ${options.output}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, dbml.endsWith(\"\\n\") ? dbml : dbml + \"\\n\", \"utf-8\");\n console.log(`DBML generated: ${options.output}`);\n } else {\n console.log(dbml);\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error generating output: ${error.message}`);\n } else {\n console.error(\"Error generating output:\", error);\n }\n process.exit(1);\n }\n}\n\n/**\n * Watch mode: regenerate on file changes\n */\nfunction watchSchema(schema: string, options: GenerateCommandOptions): void {\n const schemaPath = resolve(process.cwd(), schema);\n\n console.log(`Watching for changes: ${schemaPath}`);\n\n let debounceTimer: NodeJS.Timeout | null = null;\n\n watch(schemaPath, async (eventType) => {\n if (eventType === \"change\") {\n // Debounce to avoid multiple triggers\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(async () => {\n console.log(\"\\nFile changed, regenerating...\");\n try {\n const output = await generateFromSchema(schemaPath, options);\n const formatLabel = options.format === \"markdown\" ? \"Markdown\" : \"DBML\";\n const isMultiFile =\n options.format === \"markdown\" && !options.singleFile && options.output;\n\n if (!options.output && output) {\n console.log(output);\n } else if (options.output) {\n const outputLabel = isMultiFile ? `${options.output}/` : options.output;\n console.log(`${formatLabel} regenerated: ${outputLabel}`);\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error: ${error.message}`);\n } else {\n console.error(\"Error:\", error);\n }\n }\n }, 100);\n }\n });\n}\n\nprogram\n .command(\"generate\")\n .description(\"Generate documentation from Drizzle schema files\")\n .argument(\"<schema>\", \"Path to Drizzle schema file or directory\")\n .option(\"-o, --output <path>\", \"Output file or directory path\")\n .option(\"-d, --dialect <dialect>\", \"Database dialect (postgresql, mysql, sqlite)\", \"postgresql\")\n .option(\"-f, --format <format>\", \"Output format (dbml, markdown)\", \"dbml\")\n .option(\"-w, --watch\", \"Watch for file changes and regenerate\")\n .option(\"--single-file\", \"Output Markdown as a single file (for markdown format)\")\n .option(\"--no-er-diagram\", \"Exclude ER diagram from Markdown output\")\n .option(\"--force\", \"Overwrite existing files without confirmation\")\n .action(async (schema: string, options: GenerateCommandOptions) => {\n // Validate dialect\n const validDialects: Dialect[] = [\"postgresql\", \"mysql\", \"sqlite\"];\n if (!validDialects.includes(options.dialect)) {\n console.error(\n `Error: Invalid dialect \"${options.dialect}\". Valid options: ${validDialects.join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Validate format\n const validFormats: OutputFormat[] = [\"dbml\", \"markdown\"];\n if (!validFormats.includes(options.format)) {\n console.error(\n `Error: Invalid format \"${options.format}\". Valid options: ${validFormats.join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Warn if --single-file or --no-er-diagram used with non-markdown format\n if (options.format !== \"markdown\") {\n if (options.singleFile) {\n console.warn(\"Warning: --single-file is only applicable with --format markdown\");\n }\n if (!options.erDiagram) {\n console.warn(\"Warning: --no-er-diagram is only applicable with --format markdown\");\n }\n }\n\n // Initial generation\n await runGenerate(schema, options);\n\n // Start watch mode if requested\n if (options.watch) {\n watchSchema(schema, options);\n }\n });\n\nprogram.parse();\n\n// Cleanup: Unregister tsx loader when process exits\nprocess.on(\"exit\", () => {\n unregister();\n});\n"],"names":["unregister","register","program","Command","packageJsonPath","join","packageJson","readFileSync","getGenerateFunction","dialect","mysqlGenerate","sqliteGenerate","pgGenerate","getGeneratorClass","MySqlGenerator","SqliteGenerator","PgGenerator","hasExistingFiles","outputDir","tableNames","existingFiles","existsSync","readmePath","tableName","tablePath","generateFromSchema","schemaPath","options","schemaUrl","pathToFileURL","cacheBuster","schemaModule","GeneratorClass","intermediateSchema","writeMarkdownMultipleFiles","generateMarkdownOutput","findSchemaFiles","dirPath","files","entries","readdirSync","entry","fullPath","error","resolveSchemaPath","schema","resolve","lstatSync","schemaFiles","generateDbmlOutput","mergedSchema","schemaPaths","markdown","MarkdownFormatter","erDiagram","MermaidErDiagramFormatter","mkdirSync","markdownFormatter","readme","writeFileSync","table","tableDoc","fileName","writeSingleMarkdownFile","content","outputPath","dir","dirname","runGenerate","t","dbml","watchSchema","debounceTimer","watch","eventType","output","formatLabel","isMultiFile","outputLabel","validDialects","validFormats"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI for drizzle-docs-generator\n *\n * Generates DBML files from Drizzle ORM schema definitions.\n */\n\nimport { Command } from \"commander\";\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n watch,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { pgGenerate } from \"../generator/pg\";\nimport { mysqlGenerate } from \"../generator/mysql\";\nimport { sqliteGenerate } from \"../generator/sqlite\";\nimport { PgGenerator } from \"../generator/pg\";\nimport { MySqlGenerator } from \"../generator/mysql\";\nimport { SqliteGenerator } from \"../generator/sqlite\";\nimport { MarkdownFormatter } from \"../formatter/markdown\";\nimport { MermaidErDiagramFormatter } from \"../formatter/mermaid\";\nimport { register } from \"tsx/esm/api\";\nimport type { IntermediateSchema } from \"../types\";\n\n// Register tsx loader to support TypeScript and extensionless imports\nconst unregister = register();\n\nconst program = new Command();\n\n// Use import.meta.dirname (Node 20.11+) to resolve package.json\n// This works correctly even after bundling\nconst packageJsonPath = join(import.meta.dirname, \"../../package.json\");\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n version: string;\n description: string;\n};\n\nprogram.name(\"drizzle-docs\").description(packageJson.description).version(packageJson.version);\n\ntype Dialect = \"postgresql\" | \"mysql\" | \"sqlite\";\ntype OutputFormat = \"dbml\" | \"markdown\";\n\ninterface GenerateCommandOptions {\n output?: string;\n dialect: Dialect;\n watch?: boolean;\n format: OutputFormat;\n singleFile?: boolean;\n erDiagram: boolean; // commander uses --no-er-diagram which sets erDiagram to false\n force?: boolean; // skip overwrite confirmation for existing files\n}\n\n/**\n * Get the generate function based on dialect\n */\nfunction getGenerateFunction(dialect: Dialect) {\n switch (dialect) {\n case \"mysql\":\n return mysqlGenerate;\n case \"sqlite\":\n return sqliteGenerate;\n case \"postgresql\":\n default:\n return pgGenerate;\n }\n}\n\n/**\n * Get the generator class based on dialect\n */\nfunction getGeneratorClass(dialect: Dialect) {\n switch (dialect) {\n case \"mysql\":\n return MySqlGenerator;\n case \"sqlite\":\n return SqliteGenerator;\n case \"postgresql\":\n default:\n return PgGenerator;\n }\n}\n\n/**\n * Check if output directory has existing files\n */\nfunction hasExistingFiles(outputDir: string, tableNames: string[]): string[] {\n const existingFiles: string[] = [];\n\n if (!existsSync(outputDir)) {\n return existingFiles;\n }\n\n const readmePath = join(outputDir, \"README.md\");\n if (existsSync(readmePath)) {\n existingFiles.push(readmePath);\n }\n\n for (const tableName of tableNames) {\n const tablePath = join(outputDir, `${tableName}.md`);\n if (existsSync(tablePath)) {\n existingFiles.push(tablePath);\n }\n }\n\n return existingFiles;\n}\n\n/**\n * Generate output from a schema file (for watch mode)\n * Returns the generated output string for single-file formats,\n * or writes multiple files directly for multi-file markdown\n */\nasync function generateFromSchema(\n schemaPath: string,\n options: GenerateCommandOptions,\n): Promise<string | undefined> {\n // Use file URL for dynamic import (required for ESM)\n const schemaUrl = pathToFileURL(schemaPath).href;\n\n // Dynamic import with cache busting for watch mode\n const cacheBuster = options.watch ? `?t=${Date.now()}` : \"\";\n const schemaModule = (await import(schemaUrl + cacheBuster)) as Record<string, unknown>;\n\n if (options.format === \"markdown\") {\n const GeneratorClass = getGeneratorClass(options.dialect);\n const generator = new GeneratorClass({\n schema: schemaModule,\n source: schemaPath,\n });\n const intermediateSchema = generator.toIntermediateSchema();\n\n // Handle multi-file output in watch mode\n if (!options.singleFile && options.output) {\n writeMarkdownMultipleFiles(intermediateSchema, options.output, options);\n return undefined;\n }\n\n return generateMarkdownOutput(intermediateSchema, options);\n } else {\n const generate = getGenerateFunction(options.dialect);\n return generate({\n schema: schemaModule,\n source: schemaPath,\n });\n }\n}\n\n/**\n * Find all TypeScript schema files in a directory\n */\nfunction findSchemaFiles(dirPath: string): string[] {\n const files: string[] = [];\n\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // Recursively search subdirectories\n files.push(...findSchemaFiles(fullPath));\n } else if (entry.isFile() && /\\.(ts|js|mts|mjs|cts|cjs)$/.test(entry.name)) {\n files.push(fullPath);\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error reading directory ${dirPath}: ${error.message}`);\n }\n }\n\n return files;\n}\n\n/**\n * Resolve schema path (file or directory)\n */\nfunction resolveSchemaPath(schema: string): string[] {\n const schemaPath = resolve(process.cwd(), schema);\n\n // Check if path exists\n if (!existsSync(schemaPath)) {\n console.error(`Error: Schema path not found: ${schemaPath}`);\n process.exit(1);\n }\n\n // Check if it's a directory\n const stats = lstatSync(schemaPath);\n if (stats.isDirectory()) {\n const schemaFiles = findSchemaFiles(schemaPath);\n if (schemaFiles.length === 0) {\n console.error(`Error: No schema files found in directory: ${schemaPath}`);\n process.exit(1);\n }\n return schemaFiles;\n }\n\n // Single file\n return [schemaPath];\n}\n\n/**\n * Generate DBML format output\n */\nfunction generateDbmlOutput(\n mergedSchema: Record<string, unknown>,\n schemaPaths: string[],\n options: GenerateCommandOptions,\n): string {\n const generate = getGenerateFunction(options.dialect);\n return (\n generate({\n schema: mergedSchema,\n source: schemaPaths[0],\n }) || \"\"\n );\n}\n\n/**\n * Generate Markdown format output\n */\nfunction generateMarkdownOutput(\n intermediateSchema: IntermediateSchema,\n options: GenerateCommandOptions,\n): string {\n const markdownFormatter = new MarkdownFormatter();\n const markdown = markdownFormatter.format(intermediateSchema);\n\n // Include ER diagram unless --no-er-diagram is specified\n if (options.erDiagram) {\n const mermaidFormatter = new MermaidErDiagramFormatter();\n const erDiagram = mermaidFormatter.format(intermediateSchema);\n\n return `${markdown}\\n\\n---\\n\\n## ER Diagram\\n\\n\\`\\`\\`mermaid\\n${erDiagram}\\n\\`\\`\\``;\n }\n\n return markdown;\n}\n\n/**\n * Write Markdown to multiple files (one per table)\n */\nfunction writeMarkdownMultipleFiles(\n intermediateSchema: IntermediateSchema,\n outputDir: string,\n options: GenerateCommandOptions,\n): void {\n // Ensure output directory exists\n mkdirSync(outputDir, { recursive: true });\n\n const markdownFormatter = new MarkdownFormatter({ linkFormat: \"file\" });\n\n // Write README.md with index\n const index = markdownFormatter.generateIndex(intermediateSchema);\n let readme = `${index}\\n`;\n\n // Add ER diagram to README unless disabled\n if (options.erDiagram) {\n const mermaidFormatter = new MermaidErDiagramFormatter();\n const erDiagram = mermaidFormatter.format(intermediateSchema);\n readme += `\\n---\\n\\n## ER Diagram\\n\\n\\`\\`\\`mermaid\\n${erDiagram}\\n\\`\\`\\`\\n`;\n }\n\n writeFileSync(join(outputDir, \"README.md\"), readme, \"utf-8\");\n\n // Write individual table files\n for (const table of intermediateSchema.tables) {\n const tableDoc = markdownFormatter.generateTableDoc(table, intermediateSchema);\n const fileName = `${table.name}.md`;\n writeFileSync(join(outputDir, fileName), `# ${table.name}\\n\\n${tableDoc}\\n`, \"utf-8\");\n }\n}\n\n/**\n * Write single Markdown file\n */\nfunction writeSingleMarkdownFile(content: string, outputPath: string): void {\n const dir = dirname(outputPath);\n mkdirSync(dir, { recursive: true });\n writeFileSync(outputPath, content.endsWith(\"\\n\") ? content : content + \"\\n\", \"utf-8\");\n}\n\n/**\n * Run the generate command\n */\nasync function runGenerate(schema: string, options: GenerateCommandOptions): Promise<void> {\n const schemaPaths = resolveSchemaPath(schema);\n\n try {\n // Merge all schema modules\n const mergedSchema: Record<string, unknown> = {};\n\n for (const schemaPath of schemaPaths) {\n const schemaUrl = pathToFileURL(schemaPath).href;\n const cacheBuster = options.watch ? `?t=${Date.now()}` : \"\";\n\n try {\n const schemaModule = (await import(schemaUrl + cacheBuster)) as Record<string, unknown>;\n Object.assign(mergedSchema, schemaModule);\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error importing ${schemaPath}: ${error.message}`);\n console.error(\"\\nPossible causes:\");\n console.error(\"- Syntax error in the schema file\");\n console.error(\"- Missing dependencies (make sure drizzle-orm is installed)\");\n console.error(\"- Circular dependencies between schema files\");\n }\n throw error;\n }\n }\n\n if (options.format === \"markdown\") {\n // Generate Markdown format\n const GeneratorClass = getGeneratorClass(options.dialect);\n const generator = new GeneratorClass({\n schema: mergedSchema,\n source: schemaPaths[0],\n });\n const intermediateSchema = generator.toIntermediateSchema();\n\n if (options.singleFile) {\n // Single file Markdown output\n const content = generateMarkdownOutput(intermediateSchema, options);\n\n if (options.output) {\n // Check for existing file if --force is not specified\n if (!options.force && existsSync(options.output)) {\n console.error(\n `Error: Output file already exists: ${options.output}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n writeSingleMarkdownFile(content, options.output);\n console.log(`Markdown generated: ${options.output}`);\n } else {\n console.log(content);\n }\n } else {\n // Multiple files Markdown output\n if (!options.output) {\n // If no output specified, default to stdout with single file format\n const content = generateMarkdownOutput(intermediateSchema, options);\n console.log(content);\n } else {\n // Check for existing files if --force is not specified\n if (!options.force) {\n const tableNames = intermediateSchema.tables.map((t) => t.name);\n const existingFiles = hasExistingFiles(options.output, tableNames);\n if (existingFiles.length > 0) {\n console.error(\n `Error: The following files already exist:\\n${existingFiles.map((f) => ` - ${f}`).join(\"\\n\")}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n }\n writeMarkdownMultipleFiles(intermediateSchema, options.output, options);\n console.log(`Markdown generated: ${options.output}/`);\n }\n }\n } else {\n // Generate DBML format\n const dbml = generateDbmlOutput(mergedSchema, schemaPaths, options);\n\n if (options.output) {\n // Check for existing file if --force is not specified\n if (!options.force && existsSync(options.output)) {\n console.error(\n `Error: Output file already exists: ${options.output}\\nUse --force to overwrite existing files.`,\n );\n process.exit(1);\n }\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, dbml.endsWith(\"\\n\") ? dbml : dbml + \"\\n\", \"utf-8\");\n console.log(`DBML generated: ${options.output}`);\n } else {\n console.log(dbml);\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error generating output: ${error.message}`);\n } else {\n console.error(\"Error generating output:\", error);\n }\n process.exit(1);\n }\n}\n\n/**\n * Watch mode: regenerate on file changes\n */\nfunction watchSchema(schema: string, options: GenerateCommandOptions): void {\n const schemaPath = resolve(process.cwd(), schema);\n\n console.log(`Watching for changes: ${schemaPath}`);\n\n let debounceTimer: NodeJS.Timeout | null = null;\n\n watch(schemaPath, async (eventType) => {\n if (eventType === \"change\") {\n // Debounce to avoid multiple triggers\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(async () => {\n console.log(\"\\nFile changed, regenerating...\");\n try {\n const output = await generateFromSchema(schemaPath, options);\n const formatLabel = options.format === \"markdown\" ? \"Markdown\" : \"DBML\";\n const isMultiFile =\n options.format === \"markdown\" && !options.singleFile && options.output;\n\n if (!options.output && output) {\n console.log(output);\n } else if (options.output) {\n const outputLabel = isMultiFile ? `${options.output}/` : options.output;\n console.log(`${formatLabel} regenerated: ${outputLabel}`);\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error: ${error.message}`);\n } else {\n console.error(\"Error:\", error);\n }\n }\n }, 100);\n }\n });\n}\n\nprogram\n .command(\"generate\")\n .description(\"Generate documentation from Drizzle schema files\")\n .argument(\"<schema>\", \"Path to Drizzle schema file or directory\")\n .option(\"-o, --output <path>\", \"Output file or directory path\")\n .option(\"-d, --dialect <dialect>\", \"Database dialect (postgresql, mysql, sqlite)\", \"postgresql\")\n .option(\"-f, --format <format>\", \"Output format (dbml, markdown)\", \"dbml\")\n .option(\"-w, --watch\", \"Watch for file changes and regenerate\")\n .option(\"--single-file\", \"Output Markdown as a single file (for markdown format)\")\n .option(\"--no-er-diagram\", \"Exclude ER diagram from Markdown output\")\n .option(\"--force\", \"Overwrite existing files without confirmation\")\n .action(async (schema: string, options: GenerateCommandOptions) => {\n // Validate dialect\n const validDialects: Dialect[] = [\"postgresql\", \"mysql\", \"sqlite\"];\n if (!validDialects.includes(options.dialect)) {\n console.error(\n `Error: Invalid dialect \"${options.dialect}\". Valid options: ${validDialects.join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Validate format\n const validFormats: OutputFormat[] = [\"dbml\", \"markdown\"];\n if (!validFormats.includes(options.format)) {\n console.error(\n `Error: Invalid format \"${options.format}\". Valid options: ${validFormats.join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Warn if --single-file or --no-er-diagram used with non-markdown format\n if (options.format !== \"markdown\") {\n if (options.singleFile) {\n console.warn(\"Warning: --single-file is only applicable with --format markdown\");\n }\n if (!options.erDiagram) {\n console.warn(\"Warning: --no-er-diagram is only applicable with --format markdown\");\n }\n }\n\n // Initial generation\n await runGenerate(schema, options);\n\n // Start watch mode if requested\n if (options.watch) {\n watchSchema(schema, options);\n }\n });\n\nprogram.parse();\n\n// Cleanup: Unregister tsx loader when process exits\nprocess.on(\"exit\", () => {\n unregister();\n});\n"],"names":["unregister","register","program","Command","packageJsonPath","join","packageJson","readFileSync","getGenerateFunction","dialect","mysqlGenerate","sqliteGenerate","pgGenerate","getGeneratorClass","MySqlGenerator","SqliteGenerator","PgGenerator","hasExistingFiles","outputDir","tableNames","existingFiles","existsSync","readmePath","tableName","tablePath","generateFromSchema","schemaPath","options","schemaUrl","pathToFileURL","cacheBuster","schemaModule","GeneratorClass","intermediateSchema","writeMarkdownMultipleFiles","generateMarkdownOutput","findSchemaFiles","dirPath","files","entries","readdirSync","entry","fullPath","error","resolveSchemaPath","schema","resolve","lstatSync","schemaFiles","generateDbmlOutput","mergedSchema","schemaPaths","markdown","MarkdownFormatter","erDiagram","MermaidErDiagramFormatter","mkdirSync","markdownFormatter","readme","writeFileSync","table","tableDoc","fileName","writeSingleMarkdownFile","content","outputPath","dir","dirname","runGenerate","t","dbml","watchSchema","debounceTimer","watch","eventType","output","formatLabel","isMultiFile","outputLabel","validDialects","validFormats"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration-test-utils.d.ts","sourceRoot":"","sources":["../../src/cli/integration-test-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIrF,eAAO,MAAM,YAAY,QAA8C,CAAC;AACxE,eAAO,MAAM,YAAY,QAAqC,CAAC;AAC/D,eAAO,MAAM,YAAY,QAAwC,CAAC;AAClE,eAAO,MAAM,eAAe,QAAwC,CAAC;AACrE,eAAO,MAAM,eAAe,QAA2C,CAAC;AACxE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAA4C,CAAC;AAG1E,eAAO,MAAM,eAAe,QAA2D,CAAC;AAGxF,eAAO,MAAM,eAAe,UAAsD,CAAC;AAEnF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAa3C;AAGD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"integration-test-utils.d.ts","sourceRoot":"","sources":["../../src/cli/integration-test-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIrF,eAAO,MAAM,YAAY,QAA8C,CAAC;AACxE,eAAO,MAAM,YAAY,QAAwC,CAAC;AAClE,eAAO,MAAM,YAAY,QAAwC,CAAC;AAClE,eAAO,MAAM,eAAe,QAA2C,CAAC;AACxE,eAAO,MAAM,eAAe,QAA2C,CAAC;AACxE,eAAO,MAAM,gBAAgB,QAA4C,CAAC;AAC1E,eAAO,MAAM,gBAAgB,QAA4C,CAAC;AAG1E,eAAO,MAAM,eAAe,QAA2D,CAAC;AAGxF,eAAO,MAAM,eAAe,UAAsD,CAAC;AAEnF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAa3C;AAGD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,CAAC"}
@@ -8,7 +8,6 @@ import { OutputFormatter, FormatterOptions } from './types';
8
8
  */
9
9
  export declare class DbmlFormatter implements OutputFormatter {
10
10
  private options;
11
- private databaseType;
12
11
  /**
13
12
  * Create a new DbmlFormatter
14
13
  *
@@ -46,6 +45,11 @@ export declare class DbmlFormatter implements OutputFormatter {
46
45
  * - Strings: already wrapped with single quotes (e.g., "'user'")
47
46
  * - Numbers/booleans: as-is (e.g., "true", "42")
48
47
  * - null: "null"
48
+ *
49
+ * DBML syntax requires:
50
+ * - SQL functions/expressions: wrapped in backticks (e.g., `now()`)
51
+ * - Strings: single quotes (e.g., 'user')
52
+ * - Numbers/booleans/null: as-is
49
53
  */
50
54
  private formatDefaultValue;
51
55
  /**
@@ -67,10 +71,9 @@ export declare class DbmlFormatter implements OutputFormatter {
67
71
  */
68
72
  private getRelationType;
69
73
  /**
70
- * Escape a name for DBML based on database type
74
+ * Escape a name for DBML
71
75
  *
72
- * - PostgreSQL/SQLite: Use double quotes ("name")
73
- * - MySQL: Use backticks (`name`)
76
+ * DBML uses double quotes for all database types
74
77
  */
75
78
  private escapeName;
76
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"dbml.d.ts","sourceRoot":"","sources":["../../src/formatter/dbml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAQnB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAYjE;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,eAAe;IACnD,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,YAAY,CAA8B;IAElD;;;;OAIG;gBACS,OAAO,GAAE,gBAAqB;IAI1C;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IA0B1C;;OAEG;IACH,OAAO,CAAC,UAAU;IAWlB;;OAEG;IACH,OAAO,CAAC,WAAW;IA8BnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA6C1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAQlB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
1
+ {"version":3,"file":"dbml.d.ts","sourceRoot":"","sources":["../../src/formatter/dbml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAOnB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAYjE;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,eAAe;IACnD,OAAO,CAAC,OAAO,CAA6B;IAE5C;;;;OAIG;gBACS,OAAO,GAAE,gBAAqB;IAI1C;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAuB1C;;OAEG;IACH,OAAO,CAAC,UAAU;IAWlB;;OAEG;IACH,OAAO,CAAC,WAAW;IA8BnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,kBAAkB;IA4B1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA6C1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
@@ -1,19 +1,18 @@
1
1
  import { DbmlBuilder as u } from "./dbml-builder.js";
2
- const p = {
2
+ const m = {
3
3
  includeComments: !0,
4
4
  includeIndexes: !0,
5
5
  includeConstraints: !0
6
6
  };
7
7
  class l {
8
8
  options;
9
- databaseType = "postgresql";
10
9
  /**
11
10
  * Create a new DbmlFormatter
12
11
  *
13
12
  * @param options - Formatter options
14
13
  */
15
14
  constructor(e = {}) {
16
- this.options = { ...p, ...e };
15
+ this.options = { ...m, ...e };
17
16
  }
18
17
  /**
19
18
  * Format the intermediate schema into DBML
@@ -23,7 +22,6 @@ class l {
23
22
  */
24
23
  format(e) {
25
24
  const t = new u();
26
- this.databaseType = e.databaseType;
27
25
  for (const n of e.enums)
28
26
  this.formatEnum(t, n), t.line();
29
27
  for (const n of e.tables)
@@ -75,6 +73,11 @@ class l {
75
73
  * - Strings: already wrapped with single quotes (e.g., "'user'")
76
74
  * - Numbers/booleans: as-is (e.g., "true", "42")
77
75
  * - null: "null"
76
+ *
77
+ * DBML syntax requires:
78
+ * - SQL functions/expressions: wrapped in backticks (e.g., `now()`)
79
+ * - Strings: single quotes (e.g., 'user')
80
+ * - Numbers/booleans/null: as-is
78
81
  */
79
82
  formatDefaultValue(e) {
80
83
  return e.includes("(") || e.includes("::") || this.isKnownSqlFunction(e) ? `\`${e}\`` : e === "null" || e === "NULL" ? "null" : e === "true" || e === "false" || /^-?\d+(\.\d+)?$/.test(e) ? e : e.startsWith("'") && e.endsWith("'") ? `'${e.slice(1, -1).replace(/''/g, "\\'")}'` : `'${e.replace(/'/g, "\\'")}'`;
@@ -142,13 +145,12 @@ class l {
142
145
  }
143
146
  }
144
147
  /**
145
- * Escape a name for DBML based on database type
148
+ * Escape a name for DBML
146
149
  *
147
- * - PostgreSQL/SQLite: Use double quotes ("name")
148
- * - MySQL: Use backticks (`name`)
150
+ * DBML uses double quotes for all database types
149
151
  */
150
152
  escapeName(e) {
151
- return this.databaseType === "mysql" ? `\`${e}\`` : `"${e}"`;
153
+ return `"${e}"`;
152
154
  }
153
155
  /**
154
156
  * Escape a string for use in DBML single-quoted strings
@@ -1 +1 @@
1
- {"version":3,"file":"dbml.js","sources":["../../src/formatter/dbml.ts"],"sourcesContent":["import type {\n IntermediateSchema,\n TableDefinition,\n ColumnDefinition,\n IndexDefinition,\n ConstraintDefinition,\n RelationDefinition,\n EnumDefinition,\n DatabaseType,\n} from \"../types\";\nimport type { OutputFormatter, FormatterOptions } from \"./types\";\nimport { DbmlBuilder } from \"./dbml-builder\";\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<FormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n};\n\n/**\n * DbmlFormatter converts IntermediateSchema to DBML format\n *\n * This formatter wraps the existing DbmlBuilder to provide DBML output\n * from the database-agnostic IntermediateSchema representation.\n */\nexport class DbmlFormatter implements OutputFormatter {\n private options: Required<FormatterOptions>;\n private databaseType: DatabaseType = \"postgresql\";\n\n /**\n * Create a new DbmlFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: FormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into DBML\n *\n * @param schema - The intermediate schema to format\n * @returns DBML string\n */\n format(schema: IntermediateSchema): string {\n const dbml = new DbmlBuilder();\n\n // Store database type for name escaping\n this.databaseType = schema.databaseType;\n\n // Generate enums (PostgreSQL specific)\n for (const enumDef of schema.enums) {\n this.formatEnum(dbml, enumDef);\n dbml.line();\n }\n\n // Generate tables\n for (const table of schema.tables) {\n this.formatTable(dbml, table);\n dbml.line();\n }\n\n // Generate relations\n for (const relation of schema.relations) {\n this.formatRelation(dbml, relation);\n }\n\n return dbml.build().trim();\n }\n\n /**\n * Format an enum definition to DBML\n */\n private formatEnum(dbml: DbmlBuilder, enumDef: EnumDefinition): void {\n const name = this.escapeName(enumDef.name);\n dbml.line(`Enum ${name} {`);\n dbml.indent();\n for (const value of enumDef.values) {\n dbml.line(value);\n }\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a table definition to DBML\n */\n private formatTable(dbml: DbmlBuilder, table: TableDefinition): void {\n const tableName = this.escapeName(table.name);\n dbml.line(`Table ${tableName} {`);\n dbml.indent();\n\n // Generate columns\n for (const column of table.columns) {\n this.formatColumn(dbml, column);\n }\n\n // Generate indexes block if enabled and there are indexes or constraints to show\n const hasIndexes = table.indexes.length > 0;\n const pkConstraints = table.constraints.filter((c) => c.type === \"primary_key\");\n const ucConstraints = table.constraints.filter((c) => c.type === \"unique\");\n const hasConstraints = pkConstraints.length > 0 || ucConstraints.length > 0;\n\n if (this.options.includeIndexes && (hasIndexes || hasConstraints)) {\n this.formatIndexesBlock(dbml, table.indexes, pkConstraints, ucConstraints);\n }\n\n // Add table-level Note if comment exists and comments are enabled\n if (this.options.includeComments && table.comment) {\n dbml.line();\n dbml.line(`Note: '${this.escapeString(table.comment)}'`);\n }\n\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a column definition to DBML\n */\n private formatColumn(dbml: DbmlBuilder, column: ColumnDefinition): void {\n const name = this.escapeName(column.name);\n const type = column.type;\n const attrs = this.getColumnAttributes(column);\n const attrStr = attrs.join(\", \");\n\n if (attrStr) {\n dbml.line(`${name} ${type} [${attrStr}]`);\n } else {\n dbml.line(`${name} ${type}`);\n }\n }\n\n /**\n * Get column attributes for DBML\n */\n private getColumnAttributes(column: ColumnDefinition): string[] {\n const attrs: string[] = [];\n\n if (column.primaryKey) {\n attrs.push(\"primary key\");\n }\n if (!column.nullable) {\n attrs.push(\"not null\");\n }\n if (column.unique) {\n attrs.push(\"unique\");\n }\n if (column.autoIncrement) {\n attrs.push(\"increment\");\n }\n if (column.defaultValue !== undefined) {\n attrs.push(`default: ${this.formatDefaultValue(column.defaultValue)}`);\n }\n if (this.options.includeComments && column.comment) {\n attrs.push(`note: '${this.escapeString(column.comment)}'`);\n }\n\n return attrs;\n }\n\n /**\n * Format a default value for DBML\n *\n * The value comes from IntermediateSchema in \"raw\" format:\n * - SQL expressions: no wrapping (e.g., \"now()\", \"gen_random_uuid()\")\n * - Strings: already wrapped with single quotes (e.g., \"'user'\")\n * - Numbers/booleans: as-is (e.g., \"true\", \"42\")\n * - null: \"null\"\n */\n private formatDefaultValue(value: string): string {\n // If it looks like a SQL expression (contains parentheses or is a known function)\n if (value.includes(\"(\") || value.includes(\"::\") || this.isKnownSqlFunction(value)) {\n return `\\`${value}\\``;\n }\n // If it's null\n if (value === \"null\" || value === \"NULL\") {\n return \"null\";\n }\n // If it's a boolean\n if (value === \"true\" || value === \"false\") {\n return value;\n }\n // If it's a number\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return value;\n }\n // If already wrapped in single quotes (from IntermediateSchema)\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n // Convert '' escape (SQL standard) to \\' escape (DBML)\n const inner = value.slice(1, -1).replace(/''/g, \"\\\\'\");\n return `'${inner}'`;\n }\n // Otherwise treat as string (shouldn't happen with proper IntermediateSchema)\n return `'${value.replace(/'/g, \"\\\\'\")}'`;\n }\n\n /**\n * Check if a value is a known SQL function/expression\n */\n private isKnownSqlFunction(value: string): boolean {\n const sqlKeywords = [\n \"now\",\n \"current_timestamp\",\n \"current_date\",\n \"current_time\",\n \"gen_random_uuid\",\n \"uuid_generate_v4\",\n \"autoincrement\",\n \"auto_increment\",\n ];\n return sqlKeywords.some((kw) => value.toLowerCase().includes(kw));\n }\n\n /**\n * Format indexes block to DBML\n *\n * Includes primary key constraints, unique constraints, and regular indexes.\n */\n private formatIndexesBlock(\n dbml: DbmlBuilder,\n indexes: IndexDefinition[],\n pkConstraints: ConstraintDefinition[],\n ucConstraints: ConstraintDefinition[],\n ): void {\n dbml.line();\n dbml.line(\"indexes {\");\n dbml.indent();\n\n // Primary key constraints\n for (const pk of pkConstraints) {\n const columns = pk.columns.map((c) => this.escapeName(c)).join(\", \");\n dbml.line(`(${columns}) [pk]`);\n }\n\n // Unique constraints\n for (const uc of ucConstraints) {\n const columns = uc.columns.map((c) => this.escapeName(c)).join(\", \");\n dbml.line(`(${columns}) [unique]`);\n }\n\n // Regular indexes\n for (const index of indexes) {\n const columns = index.columns.map((c) => this.escapeName(c)).join(\", \");\n const attrs: string[] = [];\n\n if (index.unique) {\n attrs.push(\"unique\");\n }\n if (index.name) {\n attrs.push(`name: '${index.name}'`);\n }\n if (index.type) {\n attrs.push(`type: ${index.type}`);\n }\n\n const attrStr = attrs.length > 0 ? ` [${attrs.join(\", \")}]` : \"\";\n dbml.line(`(${columns})${attrStr}`);\n }\n\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a relation definition to DBML Ref\n */\n private formatRelation(dbml: DbmlBuilder, relation: RelationDefinition): void {\n const from = `${this.escapeName(relation.fromTable)}.${relation.fromColumns.map((c) => this.escapeName(c)).join(\", \")}`;\n const to = `${this.escapeName(relation.toTable)}.${relation.toColumns.map((c) => this.escapeName(c)).join(\", \")}`;\n const type = this.getRelationType(relation.type);\n\n let refLine = `Ref: ${from} ${type} ${to}`;\n\n const attrs: string[] = [];\n if (relation.onDelete && relation.onDelete.toLowerCase() !== \"no action\") {\n attrs.push(`delete: ${relation.onDelete.toLowerCase()}`);\n }\n if (relation.onUpdate && relation.onUpdate.toLowerCase() !== \"no action\") {\n attrs.push(`update: ${relation.onUpdate.toLowerCase()}`);\n }\n\n if (attrs.length > 0) {\n refLine += ` [${attrs.join(\", \")}]`;\n }\n\n dbml.line(refLine);\n }\n\n /**\n * Convert IntermediateRelationType to DBML relation symbol\n */\n private getRelationType(type: RelationDefinition[\"type\"]): \"<\" | \">\" | \"-\" | \"<>\" {\n switch (type) {\n case \"one-to-one\":\n return \"-\";\n case \"one-to-many\":\n return \"<\";\n case \"many-to-one\":\n return \">\";\n case \"many-to-many\":\n return \"<>\";\n }\n }\n\n /**\n * Escape a name for DBML based on database type\n *\n * - PostgreSQL/SQLite: Use double quotes (\"name\")\n * - MySQL: Use backticks (`name`)\n */\n private escapeName(name: string): string {\n if (this.databaseType === \"mysql\") {\n return `\\`${name}\\``;\n }\n // PostgreSQL and SQLite use double quotes\n return `\"${name}\"`;\n }\n\n /**\n * Escape a string for use in DBML single-quoted strings\n */\n private escapeString(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\").replace(/\\n/g, \"\\\\n\");\n }\n}\n"],"names":["DEFAULT_OPTIONS","DbmlFormatter","options","schema","dbml","DbmlBuilder","enumDef","table","relation","name","value","tableName","column","hasIndexes","pkConstraints","c","ucConstraints","hasConstraints","type","attrStr","attrs","kw","indexes","pk","columns","uc","index","from","to","refLine","str"],"mappings":"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"}
1
+ {"version":3,"file":"dbml.js","sources":["../../src/formatter/dbml.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\";\nimport { DbmlBuilder } from \"./dbml-builder\";\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<FormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n};\n\n/**\n * DbmlFormatter converts IntermediateSchema to DBML format\n *\n * This formatter wraps the existing DbmlBuilder to provide DBML output\n * from the database-agnostic IntermediateSchema representation.\n */\nexport class DbmlFormatter implements OutputFormatter {\n private options: Required<FormatterOptions>;\n\n /**\n * Create a new DbmlFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: FormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into DBML\n *\n * @param schema - The intermediate schema to format\n * @returns DBML string\n */\n format(schema: IntermediateSchema): string {\n const dbml = new DbmlBuilder();\n\n // Generate enums (PostgreSQL specific)\n for (const enumDef of schema.enums) {\n this.formatEnum(dbml, enumDef);\n dbml.line();\n }\n\n // Generate tables\n for (const table of schema.tables) {\n this.formatTable(dbml, table);\n dbml.line();\n }\n\n // Generate relations\n for (const relation of schema.relations) {\n this.formatRelation(dbml, relation);\n }\n\n return dbml.build().trim();\n }\n\n /**\n * Format an enum definition to DBML\n */\n private formatEnum(dbml: DbmlBuilder, enumDef: EnumDefinition): void {\n const name = this.escapeName(enumDef.name);\n dbml.line(`Enum ${name} {`);\n dbml.indent();\n for (const value of enumDef.values) {\n dbml.line(value);\n }\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a table definition to DBML\n */\n private formatTable(dbml: DbmlBuilder, table: TableDefinition): void {\n const tableName = this.escapeName(table.name);\n dbml.line(`Table ${tableName} {`);\n dbml.indent();\n\n // Generate columns\n for (const column of table.columns) {\n this.formatColumn(dbml, column);\n }\n\n // Generate indexes block if enabled and there are indexes or constraints to show\n const hasIndexes = table.indexes.length > 0;\n const pkConstraints = table.constraints.filter((c) => c.type === \"primary_key\");\n const ucConstraints = table.constraints.filter((c) => c.type === \"unique\");\n const hasConstraints = pkConstraints.length > 0 || ucConstraints.length > 0;\n\n if (this.options.includeIndexes && (hasIndexes || hasConstraints)) {\n this.formatIndexesBlock(dbml, table.indexes, pkConstraints, ucConstraints);\n }\n\n // Add table-level Note if comment exists and comments are enabled\n if (this.options.includeComments && table.comment) {\n dbml.line();\n dbml.line(`Note: '${this.escapeString(table.comment)}'`);\n }\n\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a column definition to DBML\n */\n private formatColumn(dbml: DbmlBuilder, column: ColumnDefinition): void {\n const name = this.escapeName(column.name);\n const type = column.type;\n const attrs = this.getColumnAttributes(column);\n const attrStr = attrs.join(\", \");\n\n if (attrStr) {\n dbml.line(`${name} ${type} [${attrStr}]`);\n } else {\n dbml.line(`${name} ${type}`);\n }\n }\n\n /**\n * Get column attributes for DBML\n */\n private getColumnAttributes(column: ColumnDefinition): string[] {\n const attrs: string[] = [];\n\n if (column.primaryKey) {\n attrs.push(\"primary key\");\n }\n if (!column.nullable) {\n attrs.push(\"not null\");\n }\n if (column.unique) {\n attrs.push(\"unique\");\n }\n if (column.autoIncrement) {\n attrs.push(\"increment\");\n }\n if (column.defaultValue !== undefined) {\n attrs.push(`default: ${this.formatDefaultValue(column.defaultValue)}`);\n }\n if (this.options.includeComments && column.comment) {\n attrs.push(`note: '${this.escapeString(column.comment)}'`);\n }\n\n return attrs;\n }\n\n /**\n * Format a default value for DBML\n *\n * The value comes from IntermediateSchema in \"raw\" format:\n * - SQL expressions: no wrapping (e.g., \"now()\", \"gen_random_uuid()\")\n * - Strings: already wrapped with single quotes (e.g., \"'user'\")\n * - Numbers/booleans: as-is (e.g., \"true\", \"42\")\n * - null: \"null\"\n *\n * DBML syntax requires:\n * - SQL functions/expressions: wrapped in backticks (e.g., `now()`)\n * - Strings: single quotes (e.g., 'user')\n * - Numbers/booleans/null: as-is\n */\n private formatDefaultValue(value: string): string {\n // If it looks like a SQL expression (contains parentheses or is a known function)\n // Wrap in backticks for dbdiagram.io compatibility\n if (value.includes(\"(\") || value.includes(\"::\") || this.isKnownSqlFunction(value)) {\n return `\\`${value}\\``;\n }\n // If it's null\n if (value === \"null\" || value === \"NULL\") {\n return \"null\";\n }\n // If it's a boolean\n if (value === \"true\" || value === \"false\") {\n return value;\n }\n // If it's a number\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return value;\n }\n // If already wrapped in single quotes (from IntermediateSchema)\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n // Convert '' escape (SQL standard) to \\' escape (DBML)\n const inner = value.slice(1, -1).replace(/''/g, \"\\\\'\");\n return `'${inner}'`;\n }\n // Otherwise treat as string (shouldn't happen with proper IntermediateSchema)\n return `'${value.replace(/'/g, \"\\\\'\")}'`;\n }\n\n /**\n * Check if a value is a known SQL function/expression\n */\n private isKnownSqlFunction(value: string): boolean {\n const sqlKeywords = [\n \"now\",\n \"current_timestamp\",\n \"current_date\",\n \"current_time\",\n \"gen_random_uuid\",\n \"uuid_generate_v4\",\n \"autoincrement\",\n \"auto_increment\",\n ];\n return sqlKeywords.some((kw) => value.toLowerCase().includes(kw));\n }\n\n /**\n * Format indexes block to DBML\n *\n * Includes primary key constraints, unique constraints, and regular indexes.\n */\n private formatIndexesBlock(\n dbml: DbmlBuilder,\n indexes: IndexDefinition[],\n pkConstraints: ConstraintDefinition[],\n ucConstraints: ConstraintDefinition[],\n ): void {\n dbml.line();\n dbml.line(\"indexes {\");\n dbml.indent();\n\n // Primary key constraints\n for (const pk of pkConstraints) {\n const columns = pk.columns.map((c) => this.escapeName(c)).join(\", \");\n dbml.line(`(${columns}) [pk]`);\n }\n\n // Unique constraints\n for (const uc of ucConstraints) {\n const columns = uc.columns.map((c) => this.escapeName(c)).join(\", \");\n dbml.line(`(${columns}) [unique]`);\n }\n\n // Regular indexes\n for (const index of indexes) {\n const columns = index.columns.map((c) => this.escapeName(c)).join(\", \");\n const attrs: string[] = [];\n\n if (index.unique) {\n attrs.push(\"unique\");\n }\n if (index.name) {\n attrs.push(`name: '${index.name}'`);\n }\n if (index.type) {\n attrs.push(`type: ${index.type}`);\n }\n\n const attrStr = attrs.length > 0 ? ` [${attrs.join(\", \")}]` : \"\";\n dbml.line(`(${columns})${attrStr}`);\n }\n\n dbml.dedent();\n dbml.line(\"}\");\n }\n\n /**\n * Format a relation definition to DBML Ref\n */\n private formatRelation(dbml: DbmlBuilder, relation: RelationDefinition): void {\n const from = `${this.escapeName(relation.fromTable)}.${relation.fromColumns.map((c) => this.escapeName(c)).join(\", \")}`;\n const to = `${this.escapeName(relation.toTable)}.${relation.toColumns.map((c) => this.escapeName(c)).join(\", \")}`;\n const type = this.getRelationType(relation.type);\n\n let refLine = `Ref: ${from} ${type} ${to}`;\n\n const attrs: string[] = [];\n if (relation.onDelete && relation.onDelete.toLowerCase() !== \"no action\") {\n attrs.push(`delete: ${relation.onDelete.toLowerCase()}`);\n }\n if (relation.onUpdate && relation.onUpdate.toLowerCase() !== \"no action\") {\n attrs.push(`update: ${relation.onUpdate.toLowerCase()}`);\n }\n\n if (attrs.length > 0) {\n refLine += ` [${attrs.join(\", \")}]`;\n }\n\n dbml.line(refLine);\n }\n\n /**\n * Convert IntermediateRelationType to DBML relation symbol\n */\n private getRelationType(type: RelationDefinition[\"type\"]): \"<\" | \">\" | \"-\" | \"<>\" {\n switch (type) {\n case \"one-to-one\":\n return \"-\";\n case \"one-to-many\":\n return \"<\";\n case \"many-to-one\":\n return \">\";\n case \"many-to-many\":\n return \"<>\";\n }\n }\n\n /**\n * Escape a name for DBML\n *\n * DBML uses double quotes for all database types\n */\n private escapeName(name: string): string {\n return `\"${name}\"`;\n }\n\n /**\n * Escape a string for use in DBML single-quoted strings\n */\n private escapeString(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\").replace(/\\n/g, \"\\\\n\");\n }\n}\n"],"names":["DEFAULT_OPTIONS","DbmlFormatter","options","schema","dbml","DbmlBuilder","enumDef","table","relation","name","value","tableName","column","hasIndexes","pkConstraints","c","ucConstraints","hasConstraints","type","attrStr","attrs","kw","indexes","pk","columns","uc","index","from","to","refLine","str"],"mappings":"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"}
@@ -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;;OAEG;IACH,OAAO,CAAC,cAAc;CAGvB"}
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.escapeMarkdown(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)));
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":"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"}
@@ -1,71 +1,71 @@
1
- import * as r from "typescript";
2
- import { readFileSync as g, statSync as b, readdirSync as C } from "node:fs";
3
- import { join as y } from "node:path";
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 = b(t);
5
+ const n = h(t);
6
6
  if (n.isFile())
7
7
  return t.endsWith(".ts") ? [t] : [];
8
8
  if (n.isDirectory()) {
9
- const i = [], o = C(t, { withFileTypes: !0 });
10
- for (const e of o) {
11
- const s = y(t, e.name);
12
- e.isDirectory() ? i.push(...p(s)) : e.isFile() && e.name.endsWith(".ts") && !e.name.endsWith(".test.ts") && i.push(s);
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 i;
14
+ return s;
15
15
  }
16
16
  return [];
17
17
  }
18
18
  function L(t) {
19
- const n = { tables: {} }, i = p(t);
20
- for (const o of i) {
21
- const e = g(o, "utf-8"), s = r.createSourceFile(o, e, r.ScriptTarget.Latest, !0);
22
- d(s, s, n);
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, i) {
27
- if (r.isVariableStatement(t)) {
28
- const o = x(t, n);
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 (r.isIdentifier(e.name) && e.initializer && r.isCallExpression(e.initializer)) {
31
- const s = h(
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
- o
35
+ i
36
36
  );
37
- s && (i.tables[s.tableName] = s.tableComment);
37
+ r && (s.tables[r.tableName] = r.tableComment);
38
38
  }
39
39
  }
40
- r.forEachChild(t, (o) => d(o, n, i));
40
+ o.forEachChild(t, (i) => d(i, n, s));
41
41
  }
42
- function h(t, n, i, o) {
42
+ function y(t, n, s, i) {
43
43
  const e = T(n);
44
44
  if (!S(e))
45
45
  return;
46
- const s = n.arguments[0];
47
- if (!s || !r.isStringLiteral(s))
46
+ const r = n.arguments[0];
47
+ if (!r || !o.isStringLiteral(r))
48
48
  return;
49
- const c = s.text, m = n.arguments[1], f = {};
50
- if (m && r.isObjectLiteralExpression(m)) {
51
- for (const a of m.properties)
52
- if (r.isPropertyAssignment(a) && r.isIdentifier(a.name)) {
53
- const l = v(a.initializer), u = x(a, i);
54
- l && u && (f[l] = { comment: u });
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: o,
61
- columns: f
60
+ comment: i,
61
+ columns: m
62
62
  }
63
63
  };
64
64
  }
65
65
  function T(t) {
66
- if (r.isIdentifier(t.expression))
66
+ if (o.isIdentifier(t.expression))
67
67
  return t.expression.text;
68
- if (r.isPropertyAccessExpression(t.expression))
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 (; r.isCallExpression(n); )
77
- if (r.isPropertyAccessExpression(n.expression))
76
+ for (; o.isCallExpression(n); )
77
+ if (o.isPropertyAccessExpression(n.expression))
78
78
  n = n.expression.expression;
79
- else if (r.isIdentifier(n.expression)) {
80
- const i = n.arguments[0];
81
- return i && r.isStringLiteral(i) ? i.text : void 0;
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 x(t, n) {
86
- const i = n.getFullText(), o = t.getFullStart(), e = r.getLeadingCommentRanges(i, o);
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 s of e) {
89
- const c = i.slice(s.pos, s.end);
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 s of e) {
94
- const c = i.slice(s.pos, s.end);
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 i = t.slice(3, -2).split(`
102
- `).map((e) => e.replace(/^\s*\*\s?/, "").trim()), o = [];
103
- for (const e of i) {
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
- o.push(e);
106
+ i.push(e);
107
107
  }
108
- return o.join(" ").trim();
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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drizzle-docs-generator",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A CLI tool that generates DBML files from Drizzle ORM schema definitions.",
5
5
  "keywords": [
6
6
  "cli",
@@ -61,11 +61,11 @@
61
61
  "dev": "vite build --watch",
62
62
  "test": "vitest",
63
63
  "test:run": "vitest run",
64
- "test:integration": "vitest run --config vitest.integration.config.ts",
64
+ "test:integration": "rm -rf src/cli/__integration_test_output__ && vitest run --config vitest.integration.config.ts",
65
65
  "lint": "oxlint",
66
66
  "format": "oxfmt --write",
67
67
  "format:check": "oxfmt --check",
68
68
  "typecheck": "tsc --noEmit",
69
- "generate:examples": "node dist/cli/index.js generate examples/pg/schema.ts -d postgresql -f dbml -o examples/pg/schema.dbml --force && node dist/cli/index.js generate examples/pg/schema.ts -d postgresql -f markdown -o examples/pg/markdown --force && node dist/cli/index.js generate examples/mysql/schema.ts -d mysql -f dbml -o examples/mysql/schema.dbml --force && node dist/cli/index.js generate examples/mysql/schema.ts -d mysql -f markdown -o examples/mysql/markdown --force && node dist/cli/index.js generate examples/sqlite/schema.ts -d sqlite -f dbml -o examples/sqlite/schema.dbml --force && node dist/cli/index.js generate examples/sqlite/schema.ts -d sqlite -f markdown -o examples/sqlite/markdown --force"
69
+ "generate:examples": "node dist/cli/index.js generate examples/pg/v0/schema.ts -d postgresql -f dbml -o examples/pg/v0/schema.dbml --force && node dist/cli/index.js generate examples/pg/v0/schema.ts -d postgresql -f markdown -o examples/pg/v0/markdown --force && node dist/cli/index.js generate examples/pg/v1/schema.ts -d postgresql -f dbml -o examples/pg/v1/schema.dbml --force && node dist/cli/index.js generate examples/pg/v1/schema.ts -d postgresql -f markdown -o examples/pg/v1/markdown --force && node dist/cli/index.js generate examples/mysql/v0/schema.ts -d mysql -f dbml -o examples/mysql/v0/schema.dbml --force && node dist/cli/index.js generate examples/mysql/v0/schema.ts -d mysql -f markdown -o examples/mysql/v0/markdown --force && node dist/cli/index.js generate examples/mysql/v1/schema.ts -d mysql -f dbml -o examples/mysql/v1/schema.dbml --force && node dist/cli/index.js generate examples/mysql/v1/schema.ts -d mysql -f markdown -o examples/mysql/v1/markdown --force && node dist/cli/index.js generate examples/sqlite/v0/schema.ts -d sqlite -f dbml -o examples/sqlite/v0/schema.dbml --force && node dist/cli/index.js generate examples/sqlite/v0/schema.ts -d sqlite -f markdown -o examples/sqlite/v0/markdown --force && node dist/cli/index.js generate examples/sqlite/v1/schema.ts -d sqlite -f dbml -o examples/sqlite/v1/schema.dbml --force && node dist/cli/index.js generate examples/sqlite/v1/schema.ts -d sqlite -f markdown -o examples/sqlite/v1/markdown --force"
70
70
  }
71
71
  }