drizzle-docs-generator 0.5.3 → 0.5.5
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/dist/cli/index.js
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command as
|
|
3
|
-
import { readFileSync as
|
|
4
|
-
import { join as
|
|
5
|
-
import { pathToFileURL as
|
|
6
|
-
import { PgGenerator as
|
|
7
|
-
import { MySqlGenerator as
|
|
8
|
-
import { SqliteGenerator as
|
|
9
|
-
import { MarkdownFormatter as
|
|
10
|
-
import { MermaidErDiagramFormatter as
|
|
11
|
-
import { register as
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
function
|
|
2
|
+
import { Command as v } from "commander";
|
|
3
|
+
import { readFileSync as S, existsSync as m, mkdirSync as w, writeFileSync as u, watch as G, lstatSync as O, readdirSync as j } from "node:fs";
|
|
4
|
+
import { join as f, dirname as g, resolve as F } from "node:path";
|
|
5
|
+
import { pathToFileURL as k } from "node:url";
|
|
6
|
+
import { PgGenerator as P, pgGenerate as z } from "../generator/pg.js";
|
|
7
|
+
import { MySqlGenerator as T, mysqlGenerate as N } from "../generator/mysql.js";
|
|
8
|
+
import { SqliteGenerator as R, sqliteGenerate as U } from "../generator/sqlite.js";
|
|
9
|
+
import { MarkdownFormatter as E } from "../formatter/markdown.js";
|
|
10
|
+
import { MermaidErDiagramFormatter as x } from "../formatter/mermaid.js";
|
|
11
|
+
import { register as W } from "tsx/esm/api";
|
|
12
|
+
const C = W(), p = new v(), I = f(import.meta.dirname, "../../package.json"), $ = JSON.parse(S(I, "utf-8"));
|
|
13
|
+
p.name("drizzle-docs").description($.description).version($.version);
|
|
14
|
+
function b(r) {
|
|
15
15
|
switch (r) {
|
|
16
16
|
case "mysql":
|
|
17
|
-
return
|
|
17
|
+
return N;
|
|
18
18
|
case "sqlite":
|
|
19
|
-
return
|
|
19
|
+
return U;
|
|
20
20
|
default:
|
|
21
|
-
return
|
|
21
|
+
return z;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function M(r) {
|
|
25
25
|
switch (r) {
|
|
26
26
|
case "mysql":
|
|
27
|
-
return z;
|
|
28
|
-
case "sqlite":
|
|
29
27
|
return T;
|
|
28
|
+
case "sqlite":
|
|
29
|
+
return R;
|
|
30
30
|
default:
|
|
31
|
-
return
|
|
31
|
+
return P;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function L(r, e) {
|
|
35
35
|
const o = [];
|
|
36
|
-
if (!
|
|
36
|
+
if (!m(r))
|
|
37
37
|
return o;
|
|
38
|
-
const t =
|
|
39
|
-
|
|
38
|
+
const t = f(r, "README.md");
|
|
39
|
+
m(t) && o.push(t);
|
|
40
40
|
for (const n of e) {
|
|
41
|
-
const a =
|
|
42
|
-
|
|
41
|
+
const a = f(r, `${n}.md`);
|
|
42
|
+
m(a) && o.push(a);
|
|
43
43
|
}
|
|
44
44
|
return o;
|
|
45
45
|
}
|
|
46
|
-
async function
|
|
47
|
-
const o =
|
|
46
|
+
async function B(r, e) {
|
|
47
|
+
const o = k(r).href, t = e.watch ? `?t=${Date.now()}` : "", n = await import(o + t);
|
|
48
48
|
if (e.format === "markdown") {
|
|
49
|
-
const a =
|
|
49
|
+
const a = M(e.dialect), s = new a({
|
|
50
50
|
schema: n,
|
|
51
51
|
source: r
|
|
52
52
|
}).toIntermediateSchema();
|
|
53
53
|
if (!e.singleFile && e.output) {
|
|
54
|
-
|
|
54
|
+
q(s, e.output, e);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
return
|
|
57
|
+
return h(s, e);
|
|
58
58
|
} else
|
|
59
|
-
return
|
|
59
|
+
return b(e.dialect)({
|
|
60
60
|
schema: n,
|
|
61
61
|
source: r
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
function
|
|
64
|
+
function D(r) {
|
|
65
65
|
const e = [];
|
|
66
66
|
try {
|
|
67
|
-
const o =
|
|
67
|
+
const o = j(r, { withFileTypes: !0 });
|
|
68
68
|
for (const t of o) {
|
|
69
|
-
const n =
|
|
70
|
-
t.isDirectory() ? e.push(...
|
|
69
|
+
const n = f(r, t.name);
|
|
70
|
+
t.isDirectory() ? e.push(...D(n)) : t.isFile() && /\.(ts|js|mts|mjs|cts|cjs)$/.test(t.name) && e.push(n);
|
|
71
71
|
}
|
|
72
72
|
} catch (o) {
|
|
73
73
|
o instanceof Error && console.error(`Error reading directory ${r}: ${o.message}`);
|
|
74
74
|
}
|
|
75
75
|
return e;
|
|
76
76
|
}
|
|
77
|
-
function
|
|
78
|
-
const e =
|
|
79
|
-
if (
|
|
80
|
-
const t =
|
|
77
|
+
function J(r) {
|
|
78
|
+
const e = F(process.cwd(), r);
|
|
79
|
+
if (m(e) || (console.error(`Error: Schema path not found: ${e}`), process.exit(1)), O(e).isDirectory()) {
|
|
80
|
+
const t = D(e);
|
|
81
81
|
return t.length === 0 && (console.error(`Error: No schema files found in directory: ${e}`), process.exit(1)), t;
|
|
82
82
|
}
|
|
83
83
|
return [e];
|
|
84
84
|
}
|
|
85
|
-
function
|
|
86
|
-
return
|
|
85
|
+
function A(r, e, o) {
|
|
86
|
+
return b(o.dialect)({
|
|
87
87
|
schema: r,
|
|
88
88
|
source: e[0]
|
|
89
89
|
}) || "";
|
|
90
90
|
}
|
|
91
|
-
function
|
|
92
|
-
const t = new
|
|
91
|
+
function h(r, e) {
|
|
92
|
+
const t = new E().format(r);
|
|
93
93
|
if (e.erDiagram) {
|
|
94
|
-
const a = new
|
|
94
|
+
const a = new x().format(r);
|
|
95
95
|
return `${t}
|
|
96
96
|
|
|
97
97
|
---
|
|
@@ -104,13 +104,13 @@ ${a}
|
|
|
104
104
|
}
|
|
105
105
|
return t;
|
|
106
106
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
const t = new
|
|
107
|
+
function q(r, e, o) {
|
|
108
|
+
w(e, { recursive: !0 });
|
|
109
|
+
const t = new E({ linkFormat: "file" });
|
|
110
110
|
let a = `${t.generateIndex(r)}
|
|
111
111
|
`;
|
|
112
112
|
if (o.erDiagram) {
|
|
113
|
-
const s = new
|
|
113
|
+
const s = new x().format(r);
|
|
114
114
|
a += `
|
|
115
115
|
---
|
|
116
116
|
|
|
@@ -121,27 +121,27 @@ ${s}
|
|
|
121
121
|
\`\`\`
|
|
122
122
|
`;
|
|
123
123
|
}
|
|
124
|
-
u(
|
|
124
|
+
u(f(e, "README.md"), a, "utf-8");
|
|
125
125
|
for (const c of r.tables) {
|
|
126
126
|
const s = t.generateTableDoc(c, r), i = `${c.name}.md`;
|
|
127
|
-
u(
|
|
127
|
+
u(f(e, i), `# ${c.name}
|
|
128
128
|
|
|
129
129
|
${s}
|
|
130
130
|
`, "utf-8");
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
function
|
|
134
|
-
const o =
|
|
135
|
-
|
|
133
|
+
function V(r, e) {
|
|
134
|
+
const o = g(e);
|
|
135
|
+
w(o, { recursive: !0 }), u(e, r.endsWith(`
|
|
136
136
|
`) ? r : r + `
|
|
137
137
|
`, "utf-8");
|
|
138
138
|
}
|
|
139
|
-
async function
|
|
140
|
-
const o =
|
|
139
|
+
async function H(r, e) {
|
|
140
|
+
const o = J(r);
|
|
141
141
|
try {
|
|
142
142
|
const t = {};
|
|
143
143
|
for (const n of o) {
|
|
144
|
-
const a =
|
|
144
|
+
const a = k(n).href, c = e.watch ? `?t=${Date.now()}` : "";
|
|
145
145
|
try {
|
|
146
146
|
const s = await import(a + c);
|
|
147
147
|
Object.assign(t, s);
|
|
@@ -151,40 +151,43 @@ Possible causes:`), console.error("- Syntax error in the schema file"), console.
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
if (e.format === "markdown") {
|
|
154
|
-
const n =
|
|
154
|
+
const n = M(e.dialect), a = o[0];
|
|
155
|
+
if (!a)
|
|
156
|
+
throw new Error("No schema files found");
|
|
157
|
+
const c = o.length === 1 ? a : g(a), i = new n({
|
|
155
158
|
schema: t,
|
|
156
|
-
source:
|
|
159
|
+
source: c
|
|
157
160
|
}).toIntermediateSchema();
|
|
158
161
|
if (e.singleFile) {
|
|
159
|
-
const
|
|
160
|
-
e.output ? (!e.force &&
|
|
162
|
+
const l = h(i, e);
|
|
163
|
+
e.output ? (!e.force && m(e.output) && (console.error(
|
|
161
164
|
`Error: Output file already exists: ${e.output}
|
|
162
165
|
Use --force to overwrite existing files.`
|
|
163
|
-
), process.exit(1)),
|
|
166
|
+
), process.exit(1)), V(l, e.output), console.log(`Markdown generated: ${e.output}`)) : console.log(l);
|
|
164
167
|
} else if (e.output) {
|
|
165
168
|
if (!e.force) {
|
|
166
|
-
const
|
|
167
|
-
|
|
169
|
+
const l = i.tables.map((d) => d.name), y = L(e.output, l);
|
|
170
|
+
y.length > 0 && (console.error(
|
|
168
171
|
`Error: The following files already exist:
|
|
169
|
-
${
|
|
172
|
+
${y.map((d) => ` - ${d}`).join(`
|
|
170
173
|
`)}
|
|
171
174
|
Use --force to overwrite existing files.`
|
|
172
175
|
), process.exit(1));
|
|
173
176
|
}
|
|
174
|
-
|
|
177
|
+
q(i, e.output, e), console.log(`Markdown generated: ${e.output}/`);
|
|
175
178
|
} else {
|
|
176
|
-
const
|
|
177
|
-
console.log(
|
|
179
|
+
const l = h(i, e);
|
|
180
|
+
console.log(l);
|
|
178
181
|
}
|
|
179
182
|
} else {
|
|
180
|
-
const n =
|
|
183
|
+
const n = A(t, o, e);
|
|
181
184
|
if (e.output) {
|
|
182
|
-
!e.force &&
|
|
185
|
+
!e.force && m(e.output) && (console.error(
|
|
183
186
|
`Error: Output file already exists: ${e.output}
|
|
184
187
|
Use --force to overwrite existing files.`
|
|
185
188
|
), process.exit(1));
|
|
186
|
-
const a =
|
|
187
|
-
|
|
189
|
+
const a = g(e.output);
|
|
190
|
+
w(a, { recursive: !0 }), u(e.output, n.endsWith(`
|
|
188
191
|
`) ? n : n + `
|
|
189
192
|
`, "utf-8"), console.log(`DBML generated: ${e.output}`);
|
|
190
193
|
} else
|
|
@@ -194,16 +197,16 @@ Use --force to overwrite existing files.`
|
|
|
194
197
|
t instanceof Error ? console.error(`Error generating output: ${t.message}`) : console.error("Error generating output:", t), process.exit(1);
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
|
-
function
|
|
198
|
-
const o =
|
|
200
|
+
function K(r, e) {
|
|
201
|
+
const o = F(process.cwd(), r);
|
|
199
202
|
console.log(`Watching for changes: ${o}`);
|
|
200
203
|
let t = null;
|
|
201
|
-
|
|
204
|
+
G(o, async (n) => {
|
|
202
205
|
n === "change" && (t && clearTimeout(t), t = setTimeout(async () => {
|
|
203
206
|
console.log(`
|
|
204
207
|
File changed, regenerating...`);
|
|
205
208
|
try {
|
|
206
|
-
const a = await
|
|
209
|
+
const a = await B(o, e), c = e.format === "markdown" ? "Markdown" : "DBML", s = e.format === "markdown" && !e.singleFile && e.output;
|
|
207
210
|
if (!e.output && a)
|
|
208
211
|
console.log(a);
|
|
209
212
|
else if (e.output) {
|
|
@@ -216,7 +219,7 @@ File changed, regenerating...`);
|
|
|
216
219
|
}, 100));
|
|
217
220
|
});
|
|
218
221
|
}
|
|
219
|
-
|
|
222
|
+
p.command("generate").description("Generate documentation from Drizzle schema files").argument("<schema>", "Path to Drizzle schema file or directory").option("-o, --output <path>", "Output file or directory path").option("-d, --dialect <dialect>", "Database dialect (postgresql, mysql, sqlite)", "postgresql").option("-f, --format <format>", "Output format (dbml, markdown)", "markdown").option("-w, --watch", "Watch for file changes and regenerate").option("--single-file", "Output Markdown as a single file (for markdown format)").option("--no-er-diagram", "Exclude ER diagram from Markdown output").option("--force", "Overwrite existing files without confirmation").action(async (r, e) => {
|
|
220
223
|
const o = ["postgresql", "mysql", "sqlite"];
|
|
221
224
|
o.includes(e.dialect) || (console.error(
|
|
222
225
|
`Error: Invalid dialect "${e.dialect}". Valid options: ${o.join(", ")}`
|
|
@@ -224,10 +227,10 @@ h.command("generate").description("Generate documentation from Drizzle schema fi
|
|
|
224
227
|
const t = ["dbml", "markdown"];
|
|
225
228
|
t.includes(e.format) || (console.error(
|
|
226
229
|
`Error: Invalid format "${e.format}". Valid options: ${t.join(", ")}`
|
|
227
|
-
), process.exit(1)), e.format !== "markdown" && (e.singleFile && console.warn("Warning: --single-file is only applicable with --format markdown"), e.erDiagram || console.warn("Warning: --no-er-diagram is only applicable with --format markdown")), await
|
|
230
|
+
), process.exit(1)), e.format !== "markdown" && (e.singleFile && console.warn("Warning: --single-file is only applicable with --format markdown"), e.erDiagram || console.warn("Warning: --no-er-diagram is only applicable with --format markdown")), await H(r, e), e.watch && K(r, e);
|
|
228
231
|
});
|
|
229
|
-
|
|
232
|
+
p.parse();
|
|
230
233
|
process.on("exit", () => {
|
|
231
|
-
|
|
234
|
+
C();
|
|
232
235
|
});
|
|
233
236
|
//# sourceMappingURL=index.js.map
|
package/dist/cli/index.js.map
CHANGED
|
@@ -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\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)\", \"markdown\")\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 // For multiple files, pass the directory path to extract comments from all files\n const firstFilePath = schemaPaths[0];\n if (!firstFilePath) {\n throw new Error(\"No schema files found\");\n }\n const sourcePath = schemaPaths.length === 1 ? firstFilePath : dirname(firstFilePath);\n const generator = new GeneratorClass({\n schema: mergedSchema,\n source: sourcePath,\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)\", \"markdown\")\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","firstFilePath","sourcePath","t","f","dbml","watchSchema","debounceTimer","watch","eventType","output","formatLabel","isMultiFile","outputLabel","validDialects","validFormats"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../src/formatter/mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAInB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAYD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,yBAA0B,YAAW,eAAe;IAC/D,OAAO,CAAC,OAAO,CAAoC;IAEnD;;;;OAIG;gBACS,OAAO,GAAE,uBAA4B;IAIjD;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IA4B1C;;;;;;OAMG;IACH,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAiDpE;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAgBhC;;OAEG;IACH,OAAO,CAAC,WAAW;IAiBnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAmCpB;;;;OAIG;IACH,OAAO,CAAC,YAAY;
|
|
1
|
+
{"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../src/formatter/mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAInB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAYD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,yBAA0B,YAAW,eAAe;IAC/D,OAAO,CAAC,OAAO,CAAoC;IAEnD;;;;OAIG;gBACS,OAAO,GAAE,uBAA4B;IAIjD;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM;IA4B1C;;;;;;OAMG;IACH,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAiDpE;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAgBhC;;OAEG;IACH,OAAO,CAAC,WAAW;IAiBnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAmCpB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAiDpB;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAStB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const u = {
|
|
2
2
|
includeComments: !0,
|
|
3
3
|
includeIndexes: !0,
|
|
4
4
|
includeConstraints: !0,
|
|
@@ -12,7 +12,7 @@ class f {
|
|
|
12
12
|
* @param options - Formatter options
|
|
13
13
|
*/
|
|
14
14
|
constructor(e = {}) {
|
|
15
|
-
this.options = { ...
|
|
15
|
+
this.options = { ...u, ...e };
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Format the intermediate schema into a Mermaid ER diagram
|
|
@@ -21,15 +21,15 @@ class f {
|
|
|
21
21
|
* @returns Mermaid ER diagram string
|
|
22
22
|
*/
|
|
23
23
|
format(e) {
|
|
24
|
-
const t = ["erDiagram"],
|
|
25
|
-
for (const
|
|
26
|
-
const
|
|
27
|
-
|
|
24
|
+
const t = ["erDiagram"], n = this.collectForeignKeyColumns(e.relations);
|
|
25
|
+
for (const o of e.relations) {
|
|
26
|
+
const i = this.formatRelation(o);
|
|
27
|
+
i && t.push(` ${i}`);
|
|
28
28
|
}
|
|
29
29
|
e.relations.length > 0 && e.tables.length > 0 && t.push("");
|
|
30
|
-
for (const
|
|
31
|
-
const
|
|
32
|
-
t.push(...
|
|
30
|
+
for (const o of e.tables) {
|
|
31
|
+
const i = this.formatTable(o, n);
|
|
32
|
+
t.push(...i);
|
|
33
33
|
}
|
|
34
34
|
return t.join(`
|
|
35
35
|
`);
|
|
@@ -42,22 +42,22 @@ class f {
|
|
|
42
42
|
* @returns Mermaid ER diagram string showing only the focused table and its relations
|
|
43
43
|
*/
|
|
44
44
|
formatFocused(e, t) {
|
|
45
|
-
if (!e.tables.find((
|
|
45
|
+
if (!e.tables.find((s) => s.name === t))
|
|
46
46
|
return "erDiagram";
|
|
47
|
-
const
|
|
48
|
-
for (const
|
|
49
|
-
(
|
|
50
|
-
const
|
|
51
|
-
for (const
|
|
52
|
-
const l = this.formatRelation(
|
|
53
|
-
l &&
|
|
47
|
+
const o = /* @__PURE__ */ new Set([t]), i = [];
|
|
48
|
+
for (const s of e.relations)
|
|
49
|
+
(s.fromTable === t || s.toTable === t) && (o.add(s.fromTable), o.add(s.toTable), i.push(s));
|
|
50
|
+
const a = e.tables.filter((s) => o.has(s.name)), m = this.collectForeignKeyColumns(i), r = ["erDiagram"];
|
|
51
|
+
for (const s of i) {
|
|
52
|
+
const l = this.formatRelation(s);
|
|
53
|
+
l && r.push(` ${l}`);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
for (const
|
|
57
|
-
const l = this.formatTable(
|
|
58
|
-
|
|
55
|
+
i.length > 0 && a.length > 0 && r.push("");
|
|
56
|
+
for (const s of a) {
|
|
57
|
+
const l = this.formatTable(s, m);
|
|
58
|
+
r.push(...l);
|
|
59
59
|
}
|
|
60
|
-
return
|
|
60
|
+
return r.join(`
|
|
61
61
|
`);
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
@@ -65,10 +65,10 @@ class f {
|
|
|
65
65
|
*/
|
|
66
66
|
collectForeignKeyColumns(e) {
|
|
67
67
|
const t = /* @__PURE__ */ new Map();
|
|
68
|
-
for (const
|
|
69
|
-
t.has(
|
|
70
|
-
for (const
|
|
71
|
-
t.get(
|
|
68
|
+
for (const n of e) {
|
|
69
|
+
t.has(n.fromTable) || t.set(n.fromTable, /* @__PURE__ */ new Set());
|
|
70
|
+
for (const o of n.fromColumns)
|
|
71
|
+
t.get(n.fromTable).add(o);
|
|
72
72
|
}
|
|
73
73
|
return t;
|
|
74
74
|
}
|
|
@@ -76,22 +76,22 @@ class f {
|
|
|
76
76
|
* Format a table definition to Mermaid syntax
|
|
77
77
|
*/
|
|
78
78
|
formatTable(e, t) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
for (const
|
|
82
|
-
const m = this.formatColumn(
|
|
83
|
-
|
|
79
|
+
const n = [], o = this.escapeName(e.name), i = t.get(e.name) || /* @__PURE__ */ new Set();
|
|
80
|
+
n.push(` ${o} {`);
|
|
81
|
+
for (const a of e.columns) {
|
|
82
|
+
const m = this.formatColumn(a, i);
|
|
83
|
+
n.push(` ${m}`);
|
|
84
84
|
}
|
|
85
|
-
return
|
|
85
|
+
return n.push(" }"), n;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Format a column definition to Mermaid syntax
|
|
89
89
|
*/
|
|
90
90
|
formatColumn(e, t) {
|
|
91
|
-
const o = [];
|
|
92
|
-
this.options.includeColumnTypes && o.push(this.simplifyType(e.type)), o.push(this.escapeName(e.name));
|
|
93
91
|
const n = [];
|
|
94
|
-
|
|
92
|
+
this.options.includeColumnTypes && n.push(this.simplifyType(e.type)), n.push(this.escapeName(e.name));
|
|
93
|
+
const o = [];
|
|
94
|
+
return e.primaryKey && o.push("PK"), t.has(e.name) && o.push("FK"), e.unique && !e.primaryKey && o.push("UK"), o.length > 0 && n.push(o.join(",")), this.options.includeComments && e.comment && n.push(`"${this.escapeString(e.comment)}"`), n.join(" ");
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Simplify SQL type for Mermaid display
|
|
@@ -112,18 +112,33 @@ class f {
|
|
|
112
112
|
boolean: "boolean",
|
|
113
113
|
timestamp: "timestamp",
|
|
114
114
|
timestamptz: "timestamptz",
|
|
115
|
+
"timestamp with time zone": "timestamptz",
|
|
116
|
+
"time with time zone": "timetz",
|
|
115
117
|
date: "date",
|
|
116
118
|
time: "time",
|
|
119
|
+
timetz: "timetz",
|
|
117
120
|
json: "json",
|
|
118
121
|
jsonb: "jsonb",
|
|
119
122
|
uuid: "uuid",
|
|
120
123
|
real: "real",
|
|
121
124
|
float: "float",
|
|
122
125
|
double: "double",
|
|
126
|
+
"double precision": "double",
|
|
123
127
|
decimal: "decimal",
|
|
124
128
|
numeric: "numeric",
|
|
125
129
|
blob: "blob",
|
|
126
|
-
bytea: "bytea"
|
|
130
|
+
bytea: "bytea",
|
|
131
|
+
// MySQL types
|
|
132
|
+
tinyint: "tinyint",
|
|
133
|
+
mediumint: "mediumint",
|
|
134
|
+
// MySQL unsigned types (u-prefix for distinction)
|
|
135
|
+
"tinyint unsigned": "utinyint",
|
|
136
|
+
"smallint unsigned": "usmallint",
|
|
137
|
+
"mediumint unsigned": "umediumint",
|
|
138
|
+
"int unsigned": "uint",
|
|
139
|
+
"bigint unsigned": "ubigint",
|
|
140
|
+
"float unsigned": "ufloat",
|
|
141
|
+
"double unsigned": "udouble"
|
|
127
142
|
}[t] || t;
|
|
128
143
|
}
|
|
129
144
|
/**
|
|
@@ -136,8 +151,8 @@ class f {
|
|
|
136
151
|
* - }o--o{ : many-to-many
|
|
137
152
|
*/
|
|
138
153
|
formatRelation(e) {
|
|
139
|
-
const t = this.escapeName(e.fromTable),
|
|
140
|
-
return `${t} ${
|
|
154
|
+
const t = this.escapeName(e.fromTable), n = this.escapeName(e.toTable), o = this.getRelationSymbol(e.type), i = e.fromColumns.join(", ");
|
|
155
|
+
return `${t} ${o} ${n} : "${i}"`;
|
|
141
156
|
}
|
|
142
157
|
/**
|
|
143
158
|
* Convert IntermediateRelationType to Mermaid relation symbol
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.js","sources":["../../src/formatter/mermaid.ts"],"sourcesContent":["import type {\n IntermediateSchema,\n TableDefinition,\n ColumnDefinition,\n RelationDefinition,\n} from \"../types\";\nimport type { OutputFormatter, FormatterOptions } from \"./types\";\n\n/**\n * Options for MermaidErDiagramFormatter\n */\nexport interface MermaidFormatterOptions extends FormatterOptions {\n /**\n * Whether to include column types in the diagram\n * @default true\n */\n includeColumnTypes?: boolean;\n}\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<MermaidFormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n includeColumnTypes: true,\n};\n\n/**\n * MermaidErDiagramFormatter generates Mermaid ER diagram syntax from IntermediateSchema\n *\n * This formatter creates Mermaid-compatible ER diagrams that can be rendered\n * in GitHub, GitLab, or any Mermaid-supporting platform.\n *\n * @example\n * ```typescript\n * const formatter = new MermaidErDiagramFormatter();\n * const mermaid = formatter.format(schema);\n * // Output:\n * // erDiagram\n * // users ||--o{ posts : \"author_id\"\n * // users {\n * // int id PK\n * // varchar username\n * // }\n * ```\n *\n * @see https://mermaid.js.org/syntax/entityRelationshipDiagram.html\n */\nexport class MermaidErDiagramFormatter implements OutputFormatter {\n private options: Required<MermaidFormatterOptions>;\n\n /**\n * Create a new MermaidErDiagramFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: MermaidFormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into a Mermaid ER diagram\n *\n * @param schema - The intermediate schema to format\n * @returns Mermaid ER diagram string\n */\n format(schema: IntermediateSchema): string {\n const lines: string[] = [\"erDiagram\"];\n\n // Collect foreign key columns for FK markers\n const fkColumns = this.collectForeignKeyColumns(schema.relations);\n\n // Generate relations first (at the top of the diagram)\n for (const relation of schema.relations) {\n const relationLine = this.formatRelation(relation);\n if (relationLine) {\n lines.push(` ${relationLine}`);\n }\n }\n\n // Add blank line between relations and tables if there are both\n if (schema.relations.length > 0 && schema.tables.length > 0) {\n lines.push(\"\");\n }\n\n // Generate tables\n for (const table of schema.tables) {\n const tableLines = this.formatTable(table, fkColumns);\n lines.push(...tableLines);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Format a focused ER diagram showing only a specific table and its related tables\n *\n * @param schema - The intermediate schema\n * @param tableName - The name of the table to focus on\n * @returns Mermaid ER diagram string showing only the focused table and its relations\n */\n formatFocused(schema: IntermediateSchema, tableName: string): string {\n // Find the target table\n const targetTable = schema.tables.find((t) => t.name === tableName);\n if (!targetTable) {\n return \"erDiagram\";\n }\n\n // Find related tables through relations\n const relatedTableNames = new Set<string>([tableName]);\n const relevantRelations: RelationDefinition[] = [];\n\n for (const relation of schema.relations) {\n if (relation.fromTable === tableName || relation.toTable === tableName) {\n relatedTableNames.add(relation.fromTable);\n relatedTableNames.add(relation.toTable);\n relevantRelations.push(relation);\n }\n }\n\n // Filter tables to only include related ones\n const relevantTables = schema.tables.filter((t) => relatedTableNames.has(t.name));\n\n // Collect foreign key columns for FK markers\n const fkColumns = this.collectForeignKeyColumns(relevantRelations);\n\n const lines: string[] = [\"erDiagram\"];\n\n // Generate relations\n for (const relation of relevantRelations) {\n const relationLine = this.formatRelation(relation);\n if (relationLine) {\n lines.push(` ${relationLine}`);\n }\n }\n\n // Add blank line between relations and tables if there are both\n if (relevantRelations.length > 0 && relevantTables.length > 0) {\n lines.push(\"\");\n }\n\n // Generate tables\n for (const table of relevantTables) {\n const tableLines = this.formatTable(table, fkColumns);\n lines.push(...tableLines);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Collect foreign key columns from relations for FK marker assignment\n */\n private collectForeignKeyColumns(relations: RelationDefinition[]): Map<string, Set<string>> {\n const fkColumns = new Map<string, Set<string>>();\n\n for (const relation of relations) {\n // The \"from\" side of the relation has the FK column\n if (!fkColumns.has(relation.fromTable)) {\n fkColumns.set(relation.fromTable, new Set());\n }\n for (const col of relation.fromColumns) {\n fkColumns.get(relation.fromTable)!.add(col);\n }\n }\n\n return fkColumns;\n }\n\n /**\n * Format a table definition to Mermaid syntax\n */\n private formatTable(table: TableDefinition, fkColumns: Map<string, Set<string>>): string[] {\n const lines: string[] = [];\n const tableName = this.escapeName(table.name);\n const tableFkColumns = fkColumns.get(table.name) || new Set();\n\n lines.push(` ${tableName} {`);\n\n for (const column of table.columns) {\n const columnLine = this.formatColumn(column, tableFkColumns);\n lines.push(` ${columnLine}`);\n }\n\n lines.push(\" }\");\n\n return lines;\n }\n\n /**\n * Format a column definition to Mermaid syntax\n */\n private formatColumn(column: ColumnDefinition, fkColumns: Set<string>): string {\n const parts: string[] = [];\n\n // Column type (simplified for Mermaid)\n if (this.options.includeColumnTypes) {\n parts.push(this.simplifyType(column.type));\n }\n\n // Column name\n parts.push(this.escapeName(column.name));\n\n // PK/FK markers\n const markers: string[] = [];\n if (column.primaryKey) {\n markers.push(\"PK\");\n }\n if (fkColumns.has(column.name)) {\n markers.push(\"FK\");\n }\n if (column.unique && !column.primaryKey) {\n markers.push(\"UK\");\n }\n\n if (markers.length > 0) {\n parts.push(markers.join(\",\"));\n }\n\n // Add comment as Mermaid comment (quoted string after markers)\n if (this.options.includeComments && column.comment) {\n parts.push(`\"${this.escapeString(column.comment)}\"`);\n }\n\n return parts.join(\" \");\n }\n\n /**\n * Simplify SQL type for Mermaid display\n *\n * Mermaid ER diagrams work best with simple type names\n */\n private simplifyType(type: string): string {\n // Remove parentheses content for cleaner display (e.g., varchar(255) -> varchar)\n const simplified = type.replace(/\\([^)]*\\)/g, \"\").toLowerCase();\n\n // Map common types to shorter versions\n const typeMap: Record<string, string> = {\n integer: \"int\",\n bigint: \"bigint\",\n smallint: \"smallint\",\n serial: \"serial\",\n bigserial: \"bigserial\",\n text: \"text\",\n varchar: \"varchar\",\n char: \"char\",\n boolean: \"boolean\",\n timestamp: \"timestamp\",\n timestamptz: \"timestamptz\",\n date: \"date\",\n time: \"time\",\n json: \"json\",\n jsonb: \"jsonb\",\n uuid: \"uuid\",\n real: \"real\",\n float: \"float\",\n double: \"double\",\n decimal: \"decimal\",\n numeric: \"numeric\",\n blob: \"blob\",\n bytea: \"bytea\",\n };\n\n return typeMap[simplified] || simplified;\n }\n\n /**\n * Format a relation definition to Mermaid syntax\n *\n * Mermaid ER diagram relation syntax:\n * - ||--|| : one-to-one\n * - ||--o{ : one-to-many\n * - }o--|| : many-to-one\n * - }o--o{ : many-to-many\n */\n private formatRelation(relation: RelationDefinition): string {\n const fromTable = this.escapeName(relation.fromTable);\n const toTable = this.escapeName(relation.toTable);\n const symbol = this.getRelationSymbol(relation.type);\n const label = relation.fromColumns.join(\", \");\n\n return `${fromTable} ${symbol} ${toTable} : \"${label}\"`;\n }\n\n /**\n * Convert IntermediateRelationType to Mermaid relation symbol\n *\n * @see https://mermaid.js.org/syntax/entityRelationshipDiagram.html#relationship-syntax\n */\n private getRelationSymbol(type: RelationDefinition[\"type\"]): string {\n switch (type) {\n case \"one-to-one\":\n return \"||--||\";\n case \"one-to-many\":\n return \"||--o{\";\n case \"many-to-one\":\n return \"}o--||\";\n case \"many-to-many\":\n return \"}o--o{\";\n }\n }\n\n /**\n * Escape a name for Mermaid if it contains special characters\n *\n * Mermaid entity names should be alphanumeric with underscores\n */\n private escapeName(name: string): string {\n // Mermaid accepts alphanumeric and underscores\n // For names with special characters, we need to quote them\n if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n return name;\n }\n // Replace special characters with underscores for Mermaid compatibility\n return name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n }\n\n /**\n * Escape a string for use in Mermaid quoted strings\n */\n private escapeString(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \" \");\n }\n}\n"],"names":["DEFAULT_OPTIONS","MermaidErDiagramFormatter","options","schema","lines","fkColumns","relation","relationLine","table","tableLines","tableName","t","relatedTableNames","relevantRelations","relevantTables","relations","col","tableFkColumns","column","columnLine","parts","markers","type","simplified","fromTable","toTable","symbol","label","name","str"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mermaid.js","sources":["../../src/formatter/mermaid.ts"],"sourcesContent":["import type {\n IntermediateSchema,\n TableDefinition,\n ColumnDefinition,\n RelationDefinition,\n} from \"../types\";\nimport type { OutputFormatter, FormatterOptions } from \"./types\";\n\n/**\n * Options for MermaidErDiagramFormatter\n */\nexport interface MermaidFormatterOptions extends FormatterOptions {\n /**\n * Whether to include column types in the diagram\n * @default true\n */\n includeColumnTypes?: boolean;\n}\n\n/**\n * Default formatter options\n */\nconst DEFAULT_OPTIONS: Required<MermaidFormatterOptions> = {\n includeComments: true,\n includeIndexes: true,\n includeConstraints: true,\n includeColumnTypes: true,\n};\n\n/**\n * MermaidErDiagramFormatter generates Mermaid ER diagram syntax from IntermediateSchema\n *\n * This formatter creates Mermaid-compatible ER diagrams that can be rendered\n * in GitHub, GitLab, or any Mermaid-supporting platform.\n *\n * @example\n * ```typescript\n * const formatter = new MermaidErDiagramFormatter();\n * const mermaid = formatter.format(schema);\n * // Output:\n * // erDiagram\n * // users ||--o{ posts : \"author_id\"\n * // users {\n * // int id PK\n * // varchar username\n * // }\n * ```\n *\n * @see https://mermaid.js.org/syntax/entityRelationshipDiagram.html\n */\nexport class MermaidErDiagramFormatter implements OutputFormatter {\n private options: Required<MermaidFormatterOptions>;\n\n /**\n * Create a new MermaidErDiagramFormatter\n *\n * @param options - Formatter options\n */\n constructor(options: MermaidFormatterOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Format the intermediate schema into a Mermaid ER diagram\n *\n * @param schema - The intermediate schema to format\n * @returns Mermaid ER diagram string\n */\n format(schema: IntermediateSchema): string {\n const lines: string[] = [\"erDiagram\"];\n\n // Collect foreign key columns for FK markers\n const fkColumns = this.collectForeignKeyColumns(schema.relations);\n\n // Generate relations first (at the top of the diagram)\n for (const relation of schema.relations) {\n const relationLine = this.formatRelation(relation);\n if (relationLine) {\n lines.push(` ${relationLine}`);\n }\n }\n\n // Add blank line between relations and tables if there are both\n if (schema.relations.length > 0 && schema.tables.length > 0) {\n lines.push(\"\");\n }\n\n // Generate tables\n for (const table of schema.tables) {\n const tableLines = this.formatTable(table, fkColumns);\n lines.push(...tableLines);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Format a focused ER diagram showing only a specific table and its related tables\n *\n * @param schema - The intermediate schema\n * @param tableName - The name of the table to focus on\n * @returns Mermaid ER diagram string showing only the focused table and its relations\n */\n formatFocused(schema: IntermediateSchema, tableName: string): string {\n // Find the target table\n const targetTable = schema.tables.find((t) => t.name === tableName);\n if (!targetTable) {\n return \"erDiagram\";\n }\n\n // Find related tables through relations\n const relatedTableNames = new Set<string>([tableName]);\n const relevantRelations: RelationDefinition[] = [];\n\n for (const relation of schema.relations) {\n if (relation.fromTable === tableName || relation.toTable === tableName) {\n relatedTableNames.add(relation.fromTable);\n relatedTableNames.add(relation.toTable);\n relevantRelations.push(relation);\n }\n }\n\n // Filter tables to only include related ones\n const relevantTables = schema.tables.filter((t) => relatedTableNames.has(t.name));\n\n // Collect foreign key columns for FK markers\n const fkColumns = this.collectForeignKeyColumns(relevantRelations);\n\n const lines: string[] = [\"erDiagram\"];\n\n // Generate relations\n for (const relation of relevantRelations) {\n const relationLine = this.formatRelation(relation);\n if (relationLine) {\n lines.push(` ${relationLine}`);\n }\n }\n\n // Add blank line between relations and tables if there are both\n if (relevantRelations.length > 0 && relevantTables.length > 0) {\n lines.push(\"\");\n }\n\n // Generate tables\n for (const table of relevantTables) {\n const tableLines = this.formatTable(table, fkColumns);\n lines.push(...tableLines);\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Collect foreign key columns from relations for FK marker assignment\n */\n private collectForeignKeyColumns(relations: RelationDefinition[]): Map<string, Set<string>> {\n const fkColumns = new Map<string, Set<string>>();\n\n for (const relation of relations) {\n // The \"from\" side of the relation has the FK column\n if (!fkColumns.has(relation.fromTable)) {\n fkColumns.set(relation.fromTable, new Set());\n }\n for (const col of relation.fromColumns) {\n fkColumns.get(relation.fromTable)!.add(col);\n }\n }\n\n return fkColumns;\n }\n\n /**\n * Format a table definition to Mermaid syntax\n */\n private formatTable(table: TableDefinition, fkColumns: Map<string, Set<string>>): string[] {\n const lines: string[] = [];\n const tableName = this.escapeName(table.name);\n const tableFkColumns = fkColumns.get(table.name) || new Set();\n\n lines.push(` ${tableName} {`);\n\n for (const column of table.columns) {\n const columnLine = this.formatColumn(column, tableFkColumns);\n lines.push(` ${columnLine}`);\n }\n\n lines.push(\" }\");\n\n return lines;\n }\n\n /**\n * Format a column definition to Mermaid syntax\n */\n private formatColumn(column: ColumnDefinition, fkColumns: Set<string>): string {\n const parts: string[] = [];\n\n // Column type (simplified for Mermaid)\n if (this.options.includeColumnTypes) {\n parts.push(this.simplifyType(column.type));\n }\n\n // Column name\n parts.push(this.escapeName(column.name));\n\n // PK/FK markers\n const markers: string[] = [];\n if (column.primaryKey) {\n markers.push(\"PK\");\n }\n if (fkColumns.has(column.name)) {\n markers.push(\"FK\");\n }\n if (column.unique && !column.primaryKey) {\n markers.push(\"UK\");\n }\n\n if (markers.length > 0) {\n parts.push(markers.join(\",\"));\n }\n\n // Add comment as Mermaid comment (quoted string after markers)\n if (this.options.includeComments && column.comment) {\n parts.push(`\"${this.escapeString(column.comment)}\"`);\n }\n\n return parts.join(\" \");\n }\n\n /**\n * Simplify SQL type for Mermaid display\n *\n * Mermaid ER diagrams work best with simple type names\n */\n private simplifyType(type: string): string {\n // Remove parentheses content for cleaner display (e.g., varchar(255) -> varchar)\n const simplified = type.replace(/\\([^)]*\\)/g, \"\").toLowerCase();\n\n // Map common types to shorter versions\n const typeMap: Record<string, string> = {\n integer: \"int\",\n bigint: \"bigint\",\n smallint: \"smallint\",\n serial: \"serial\",\n bigserial: \"bigserial\",\n text: \"text\",\n varchar: \"varchar\",\n char: \"char\",\n boolean: \"boolean\",\n timestamp: \"timestamp\",\n timestamptz: \"timestamptz\",\n \"timestamp with time zone\": \"timestamptz\",\n \"time with time zone\": \"timetz\",\n date: \"date\",\n time: \"time\",\n timetz: \"timetz\",\n json: \"json\",\n jsonb: \"jsonb\",\n uuid: \"uuid\",\n real: \"real\",\n float: \"float\",\n double: \"double\",\n \"double precision\": \"double\",\n decimal: \"decimal\",\n numeric: \"numeric\",\n blob: \"blob\",\n bytea: \"bytea\",\n // MySQL types\n tinyint: \"tinyint\",\n mediumint: \"mediumint\",\n // MySQL unsigned types (u-prefix for distinction)\n \"tinyint unsigned\": \"utinyint\",\n \"smallint unsigned\": \"usmallint\",\n \"mediumint unsigned\": \"umediumint\",\n \"int unsigned\": \"uint\",\n \"bigint unsigned\": \"ubigint\",\n \"float unsigned\": \"ufloat\",\n \"double unsigned\": \"udouble\",\n };\n\n return typeMap[simplified] || simplified;\n }\n\n /**\n * Format a relation definition to Mermaid syntax\n *\n * Mermaid ER diagram relation syntax:\n * - ||--|| : one-to-one\n * - ||--o{ : one-to-many\n * - }o--|| : many-to-one\n * - }o--o{ : many-to-many\n */\n private formatRelation(relation: RelationDefinition): string {\n const fromTable = this.escapeName(relation.fromTable);\n const toTable = this.escapeName(relation.toTable);\n const symbol = this.getRelationSymbol(relation.type);\n const label = relation.fromColumns.join(\", \");\n\n return `${fromTable} ${symbol} ${toTable} : \"${label}\"`;\n }\n\n /**\n * Convert IntermediateRelationType to Mermaid relation symbol\n *\n * @see https://mermaid.js.org/syntax/entityRelationshipDiagram.html#relationship-syntax\n */\n private getRelationSymbol(type: RelationDefinition[\"type\"]): string {\n switch (type) {\n case \"one-to-one\":\n return \"||--||\";\n case \"one-to-many\":\n return \"||--o{\";\n case \"many-to-one\":\n return \"}o--||\";\n case \"many-to-many\":\n return \"}o--o{\";\n }\n }\n\n /**\n * Escape a name for Mermaid if it contains special characters\n *\n * Mermaid entity names should be alphanumeric with underscores\n */\n private escapeName(name: string): string {\n // Mermaid accepts alphanumeric and underscores\n // For names with special characters, we need to quote them\n if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n return name;\n }\n // Replace special characters with underscores for Mermaid compatibility\n return name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n }\n\n /**\n * Escape a string for use in Mermaid quoted strings\n */\n private escapeString(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \" \");\n }\n}\n"],"names":["DEFAULT_OPTIONS","MermaidErDiagramFormatter","options","schema","lines","fkColumns","relation","relationLine","table","tableLines","tableName","t","relatedTableNames","relevantRelations","relevantTables","relations","col","tableFkColumns","column","columnLine","parts","markers","type","simplified","fromTable","toTable","symbol","label","name","str"],"mappings":"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"}
|