@cuongph.dev/dbdocgen 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1830 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+ import { readFile as readFile2, writeFile as writeFile5, rm } from "fs/promises";
6
+ import { existsSync } from "fs";
7
+ import { resolve } from "path";
8
+
9
+ // src/core/config/loader.ts
10
+ import { cosmiconfig } from "cosmiconfig";
11
+
12
+ // src/core/config/schema.ts
13
+ import { z } from "zod";
14
+ var dialectSchema = z.enum([
15
+ "postgres",
16
+ "mysql",
17
+ "mariadb",
18
+ "sqlite",
19
+ "mssql",
20
+ "unknown"
21
+ ]);
22
+ var outputFormatSchema = z.enum([
23
+ "excel",
24
+ "markdown",
25
+ "html",
26
+ "diagram",
27
+ "word"
28
+ ]);
29
+ var outputLanguageSchema = z.enum(["en", "jp"]);
30
+ var dbdocgenConfigSchema = z.object({
31
+ schema: z.string().default("./schema.sql"),
32
+ dialect: dialectSchema.optional(),
33
+ outDir: z.string().default("./docs/db"),
34
+ output: z.object({
35
+ formats: z.array(outputFormatSchema).default(["excel", "markdown", "html", "diagram", "word"]),
36
+ language: outputLanguageSchema.default("en")
37
+ }).default({})
38
+ });
39
+
40
+ // src/core/config/loader.ts
41
+ async function loadConfig(input) {
42
+ const explorer = cosmiconfig("dbdocgen", {
43
+ searchPlaces: ["dbdocgen.config.js", "dbdocgen.config.json", ".dbdocgenrc"]
44
+ });
45
+ const result = input.cliOptions.configPath ? await explorer.load(input.cliOptions.configPath) : await explorer.search(input.cwd);
46
+ const fileConfig = result?.config ?? {};
47
+ const merged = mergeConfig(fileConfig, input.cliOptions);
48
+ return dbdocgenConfigSchema.parse(merged);
49
+ }
50
+ function mergeConfig(fileConfig, cli) {
51
+ return {
52
+ ...fileConfig,
53
+ schema: cli.schema ?? fileConfig.schema,
54
+ dialect: cli.dialect ?? fileConfig.dialect,
55
+ outDir: cli.outDir ?? fileConfig.outDir,
56
+ output: {
57
+ ...fileConfig.output,
58
+ formats: cli.formats ?? fileConfig.output?.formats
59
+ }
60
+ };
61
+ }
62
+
63
+ // src/core/pipeline/generate-db-docs.ts
64
+ import { readFile } from "fs/promises";
65
+
66
+ // src/exporters/diagram/mermaid-exporter.ts
67
+ import { mkdir, writeFile } from "fs/promises";
68
+ import { join } from "path";
69
+ async function exportMermaidDiagram(doc, options) {
70
+ await mkdir(options.outDir, { recursive: true });
71
+ await writeFile(
72
+ join(options.outDir, "er_diagram.mmd"),
73
+ renderMermaid(doc),
74
+ "utf8"
75
+ );
76
+ }
77
+ function renderMermaid(doc) {
78
+ const lines = ["erDiagram"];
79
+ for (const warning of doc.warnings) {
80
+ const target = warning.target ? ` (${warning.target})` : "";
81
+ lines.push(
82
+ ` %% WARNING [${warning.severity}] ${warning.code}${target}: ${warning.message}`
83
+ );
84
+ }
85
+ for (const table of doc.tables) {
86
+ for (const todo of table.reviewTodos) {
87
+ lines.push(` %% TODO [${todo.type}] ${todo.target}: ${todo.issue}`);
88
+ }
89
+ lines.push(` ${table.name} {`);
90
+ for (const column of table.columns) {
91
+ const markers = [
92
+ column.isPrimaryKey ? "PK" : "",
93
+ column.isForeignKey ? "FK" : ""
94
+ ].filter(Boolean).join(" ");
95
+ lines.push(
96
+ ` ${sanitizeType(column.type)} ${column.name}${markers ? ` "${markers}"` : ""}`
97
+ );
98
+ }
99
+ lines.push(" }");
100
+ }
101
+ for (const relationship of doc.relationships.filter(
102
+ (item) => item.source === "schema"
103
+ )) {
104
+ lines.push(
105
+ ` ${relationship.toTable} ||--o{ ${relationship.fromTable} : "${relationship.constraintName ?? relationship.fromColumn}"`
106
+ );
107
+ }
108
+ return `${lines.join("\n")}
109
+ `;
110
+ }
111
+ function sanitizeType(type) {
112
+ return type.replace(/[^a-zA-Z0-9_]/g, "_");
113
+ }
114
+
115
+ // src/exporters/excel/excel-exporter.ts
116
+ import { mkdir as mkdir2 } from "fs/promises";
117
+ import { join as join2 } from "path";
118
+ import ExcelJS from "exceljs";
119
+
120
+ // src/exporters/shared/output-labels.ts
121
+ var LABELS = {
122
+ en: {
123
+ docTitle: "Database Documentation",
124
+ tableInfoHeading: "Table Info",
125
+ columnsHeading: "Columns",
126
+ metaField: "Field",
127
+ metaValue: "Value",
128
+ tablePhysicalName: "Table Physical Name",
129
+ tableLogicalName: "Table Logical Name",
130
+ schema: "Schema",
131
+ primaryKey: "Primary Key",
132
+ foreignKeys: "Foreign Keys",
133
+ indexes: "Indexes",
134
+ none: "(none)",
135
+ physicalName: "Physical Name",
136
+ logicalName: "Logical Name",
137
+ type: "Type",
138
+ required: "Required",
139
+ defaultValue: "Default Value",
140
+ notes: "Notes",
141
+ yes: "Yes",
142
+ no: "No",
143
+ generatedNote: "Generated by dbdocgen in A5:SQL-style layout.",
144
+ overviewHeading: "Overview",
145
+ tableListHeading: "Table List",
146
+ relationshipsHeading: "Relationships",
147
+ warningsHeading: "Warnings",
148
+ tableLabel: "Table",
149
+ descriptionLabel: "Description",
150
+ dialectLabel: "Dialect",
151
+ tablesLabel: "Tables",
152
+ relationshipsLabel: "Relationships",
153
+ severity: "Severity",
154
+ code: "Code",
155
+ target: "Target",
156
+ message: "Message",
157
+ fromTable: "From Table",
158
+ fromColumn: "From Column",
159
+ toTable: "To Table",
160
+ toColumn: "To Column",
161
+ constraint: "Constraint",
162
+ source: "Source",
163
+ needsReview: "Needs Review",
164
+ columnsCount: "Columns",
165
+ rowNo: "#",
166
+ backToOverview: "\u2190 Overview",
167
+ pkMarker: "PK",
168
+ fkMarker: "FK"
169
+ },
170
+ jp: {
171
+ docTitle: "Database Documentation",
172
+ tableInfoHeading: "Table Info",
173
+ columnsHeading: "Columns",
174
+ metaField: "\u9805\u76EE",
175
+ metaValue: "\u5024",
176
+ tablePhysicalName: "\u30C6\u30FC\u30D6\u30EB\u7269\u7406\u540D",
177
+ tableLogicalName: "\u30C6\u30FC\u30D6\u30EB\u8AD6\u7406\u540D",
178
+ schema: "\u30B9\u30AD\u30FC\u30DE",
179
+ primaryKey: "\u4E3B\u30AD\u30FC",
180
+ foreignKeys: "\u5916\u90E8\u30AD\u30FC",
181
+ indexes: "\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9",
182
+ none: "(none)",
183
+ physicalName: "\u7269\u7406\u540D",
184
+ logicalName: "\u8AD6\u7406\u540D",
185
+ type: "\u578B",
186
+ required: "\u5FC5\u9808",
187
+ defaultValue: "\u30C7\u30D5\u30A9\u30EB\u30C8\u5024",
188
+ notes: "\u5099\u8003",
189
+ yes: "Yes",
190
+ no: "No",
191
+ generatedNote: "Generated by dbdocgen in A5:SQL-style layout.",
192
+ overviewHeading: "Overview",
193
+ tableListHeading: "Table List",
194
+ relationshipsHeading: "Relationships",
195
+ warningsHeading: "Warnings",
196
+ tableLabel: "Table",
197
+ descriptionLabel: "Description",
198
+ dialectLabel: "Dialect",
199
+ tablesLabel: "Tables",
200
+ relationshipsLabel: "Relationships",
201
+ severity: "Severity",
202
+ code: "Code",
203
+ target: "Target",
204
+ message: "Message",
205
+ fromTable: "From Table",
206
+ fromColumn: "From Column",
207
+ toTable: "To Table",
208
+ toColumn: "To Column",
209
+ constraint: "Constraint",
210
+ source: "Source",
211
+ needsReview: "Needs Review",
212
+ columnsCount: "\u5217\u6570",
213
+ rowNo: "No.",
214
+ backToOverview: "\u2190 \u4E00\u89A7",
215
+ pkMarker: "PK",
216
+ fkMarker: "FK"
217
+ }
218
+ };
219
+ function getOutputLabels(language = "en") {
220
+ return LABELS[language];
221
+ }
222
+
223
+ // src/exporters/excel/excel-exporter.ts
224
+ var COLOR = {
225
+ headerBg: "FF4472C4",
226
+ headerFg: "FFFFFFFF",
227
+ metaBg: "FFD9E1F2",
228
+ metaFg: "FF1F3864",
229
+ overviewBg: "FF4472C4",
230
+ overviewFg: "FFFFFFFF",
231
+ altRow: "FFF2F7FF",
232
+ pkBg: "FFFFF3CD",
233
+ fkBg: "FFE8F4FD",
234
+ link: "FF0563C1",
235
+ border: "FFB8CCE4",
236
+ valueBg: "FFFAFBFE"
237
+ };
238
+ var COL_COUNT = 7;
239
+ async function exportExcelDictionary(doc, options) {
240
+ await mkdir2(options.outDir, { recursive: true });
241
+ const workbook = new ExcelJS.Workbook();
242
+ const labels = getOutputLabels(options.language);
243
+ const sheetNames = /* @__PURE__ */ new Map();
244
+ for (const table of doc.tables) {
245
+ sheetNames.set(table.name, buildSheetName(table.name, sheetNames));
246
+ }
247
+ addOverviewSheet(workbook, doc, labels, sheetNames);
248
+ for (const table of doc.tables) {
249
+ const sheetName = sheetNames.get(table.name);
250
+ const sheet = workbook.addWorksheet(sheetName);
251
+ populateTableSheet(sheet, table, doc, labels);
252
+ }
253
+ await workbook.xlsx.writeFile(
254
+ join2(options.outDir, "database_dictionary.xlsx")
255
+ );
256
+ }
257
+ function addOverviewSheet(workbook, doc, labels, sheetNames) {
258
+ const sheet = workbook.addWorksheet("Overview");
259
+ sheet.mergeCells(1, 1, 1, COL_COUNT);
260
+ const titleCell = sheet.getCell(1, 1);
261
+ titleCell.value = labels.docTitle;
262
+ titleCell.font = { bold: true, size: 14, color: { argb: COLOR.overviewFg } };
263
+ titleCell.fill = solidFill(COLOR.overviewBg);
264
+ titleCell.alignment = { horizontal: "center", vertical: "middle" };
265
+ sheet.getRow(1).height = 30;
266
+ sheet.addRow([]);
267
+ const summary = [
268
+ [labels.dialectLabel, doc.dialect],
269
+ [labels.tablesLabel, doc.tables.length],
270
+ [labels.relationshipsLabel, doc.relationships.length]
271
+ ];
272
+ for (const [field, value] of summary) {
273
+ const row = sheet.addRow([field, value]);
274
+ styleMetaRow(row);
275
+ applyBorderToRow(row, 2);
276
+ row.getCell(1).alignment = { vertical: "middle", indent: 1 };
277
+ row.getCell(2).alignment = { vertical: "middle", indent: 1 };
278
+ }
279
+ sheet.addRow([]);
280
+ const sectionRow = sheet.addRow([labels.tableListHeading]);
281
+ sheet.mergeCells(sectionRow.number, 1, sectionRow.number, COL_COUNT);
282
+ const sectionCell = sectionRow.getCell(1);
283
+ sectionCell.font = { bold: true, size: 11, color: { argb: COLOR.metaFg } };
284
+ sectionCell.fill = solidFill(COLOR.metaBg);
285
+ sectionCell.alignment = { vertical: "middle" };
286
+ sectionRow.height = 22;
287
+ const headerRowNum = sectionRow.number + 1;
288
+ const headerRow = sheet.getRow(headerRowNum);
289
+ headerRow.values = [
290
+ labels.rowNo,
291
+ labels.tableLabel,
292
+ labels.tableLogicalName,
293
+ labels.columnsCount,
294
+ labels.primaryKey,
295
+ labels.foreignKeys,
296
+ labels.indexes
297
+ ];
298
+ styleColorRow(headerRow, COLOR.overviewBg, COLOR.overviewFg);
299
+ applyBorderToRow(headerRow, COL_COUNT);
300
+ for (const [i, table] of doc.tables.entries()) {
301
+ const indexes = collectTableIndexes(table, doc);
302
+ const targetSheet = sheetNames.get(table.name);
303
+ const row = sheet.addRow([
304
+ i + 1,
305
+ table.name,
306
+ displayValue(table.comment, labels),
307
+ table.columns.length,
308
+ displayValue(table.primaryKeys.join(", "), labels),
309
+ table.foreignKeys.length > 0 ? table.foreignKeys.map((fk) => `${fk.columns.join(",")} \u2192 ${fk.referencedTable}`).join("; ") : labels.none,
310
+ indexes.length > 0 ? indexes.map((idx) => idx.name).join("; ") : labels.none
311
+ ]);
312
+ setHyperlink(row.getCell(2), table.name, targetSheet);
313
+ row.getCell(2).font = { bold: true, color: { argb: COLOR.link }, underline: true };
314
+ row.getCell(4).alignment = { horizontal: "center" };
315
+ if (i % 2 === 1) {
316
+ shadeRow(row, COL_COUNT, COLOR.altRow);
317
+ }
318
+ applyBorderToRow(row, COL_COUNT);
319
+ }
320
+ sheet.columns = [
321
+ { width: 20 },
322
+ { width: 28 },
323
+ { width: 30 },
324
+ { width: 9 },
325
+ { width: 20 },
326
+ { width: 38 },
327
+ { width: 38 }
328
+ ];
329
+ sheet.autoFilter = {
330
+ from: { row: headerRowNum, column: 1 },
331
+ to: { row: headerRowNum + doc.tables.length, column: COL_COUNT }
332
+ };
333
+ sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
334
+ }
335
+ function populateTableSheet(sheet, table, doc, labels) {
336
+ const indexes = collectTableIndexes(table, doc);
337
+ sheet.mergeCells(1, 1, 1, 6);
338
+ const titleCell = sheet.getCell(1, 1);
339
+ titleCell.value = table.name;
340
+ titleCell.font = { bold: true, size: 13, color: { argb: COLOR.overviewFg } };
341
+ titleCell.fill = solidFill(COLOR.headerBg);
342
+ titleCell.alignment = { horizontal: "left", vertical: "middle", indent: 1 };
343
+ sheet.getRow(1).height = 26;
344
+ const backRow = sheet.addRow([labels.backToOverview]);
345
+ setHyperlink(backRow.getCell(1), labels.backToOverview, "Overview");
346
+ backRow.getCell(1).font = { color: { argb: COLOR.link }, underline: true, size: 10 };
347
+ sheet.addRow([]);
348
+ const metaData = [
349
+ [labels.tablePhysicalName, table.name],
350
+ [labels.tableLogicalName, displayValue(table.comment, labels)],
351
+ ...table.schema ? [[labels.schema, table.schema]] : [],
352
+ [labels.columnsCount, String(table.columns.length)],
353
+ [labels.primaryKey, displayValue(table.primaryKeys.join(", "), labels)],
354
+ [
355
+ labels.foreignKeys,
356
+ table.foreignKeys.length > 0 ? table.foreignKeys.map((fk) => {
357
+ const name = fk.name ? ` (${fk.name})` : "";
358
+ return `${fk.columns.join(", ")} \u2192 ${fk.referencedTable}.${fk.referencedColumns.join(", ")}${name}`;
359
+ }).join("; ") : labels.none
360
+ ],
361
+ [
362
+ labels.indexes,
363
+ indexes.length > 0 ? indexes.map(
364
+ (idx) => `${idx.name} (${idx.columns.join(", ")})${idx.unique ? " UNIQUE" : ""}`
365
+ ).join("; ") : labels.none
366
+ ]
367
+ ];
368
+ for (const [field, value] of metaData) {
369
+ const row = sheet.addRow([field, value]);
370
+ styleMetaRow(row);
371
+ applyBorderToRow(row, 2);
372
+ row.getCell(2).alignment = { wrapText: true, vertical: "top" };
373
+ }
374
+ sheet.addRow([]);
375
+ const headerRow = sheet.addRow([
376
+ labels.physicalName,
377
+ labels.logicalName,
378
+ labels.type,
379
+ labels.required,
380
+ labels.defaultValue,
381
+ labels.notes
382
+ ]);
383
+ styleColorRow(headerRow, COLOR.headerBg, COLOR.headerFg);
384
+ applyBorderToRow(headerRow, 6);
385
+ const headerRowNum = headerRow.number;
386
+ for (const [i, column] of table.columns.entries()) {
387
+ const markers = [];
388
+ if (column.isPrimaryKey) markers.push(labels.pkMarker);
389
+ if (column.isForeignKey) markers.push(labels.fkMarker);
390
+ const notes = [markers.join(", "), column.description?.value ?? ""].filter(Boolean).join(" | ");
391
+ const row = sheet.addRow([
392
+ column.name,
393
+ displayValue(column.comment, labels),
394
+ column.type,
395
+ column.nullable ? labels.no : labels.yes,
396
+ column.defaultValue ?? "-",
397
+ notes || "-"
398
+ ]);
399
+ if (column.isPrimaryKey) {
400
+ shadeRow(row, 6, COLOR.pkBg);
401
+ row.getCell(1).font = { bold: true };
402
+ } else if (column.isForeignKey) {
403
+ shadeRow(row, 6, COLOR.fkBg);
404
+ } else if (i % 2 === 1) {
405
+ shadeRow(row, 6, COLOR.altRow);
406
+ }
407
+ row.getCell(4).alignment = { horizontal: "center" };
408
+ applyBorderToRow(row, 6);
409
+ }
410
+ sheet.columns = [
411
+ { width: 24 },
412
+ { width: 28 },
413
+ { width: 18 },
414
+ { width: 10 },
415
+ { width: 18 },
416
+ { width: 36 }
417
+ ];
418
+ sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
419
+ }
420
+ function displayValue(value, labels) {
421
+ const trimmed = value?.trim();
422
+ return trimmed ? trimmed : labels.none;
423
+ }
424
+ function buildSheetName(tableName, existing) {
425
+ const base = tableName.slice(0, 31);
426
+ if (![...existing.values()].includes(base)) return base;
427
+ let suffix = 2;
428
+ while (suffix < 100) {
429
+ const candidate = `${tableName.slice(0, 28)}_${suffix}`;
430
+ if (![...existing.values()].includes(candidate)) return candidate;
431
+ suffix++;
432
+ }
433
+ return base;
434
+ }
435
+ function sheetLocation(sheetName, cellRef = "A1") {
436
+ if (/^[A-Za-z0-9_]+$/.test(sheetName)) {
437
+ return `${sheetName}!${cellRef}`;
438
+ }
439
+ const escaped = sheetName.replace(/'/g, "''");
440
+ return `'${escaped}'!${cellRef}`;
441
+ }
442
+ function setHyperlink(cell, text, sheetName) {
443
+ cell.value = { text, hyperlink: sheetLocation(sheetName) };
444
+ }
445
+ function solidFill(argb) {
446
+ return { type: "pattern", pattern: "solid", fgColor: { argb } };
447
+ }
448
+ function styleMetaRow(row) {
449
+ row.getCell(1).font = { bold: true, color: { argb: COLOR.metaFg } };
450
+ row.getCell(1).fill = solidFill(COLOR.metaBg);
451
+ row.getCell(2).fill = solidFill(COLOR.valueBg);
452
+ }
453
+ function styleColorRow(row, bgArgb, fgArgb) {
454
+ row.eachCell((cell) => {
455
+ cell.font = { bold: true, color: { argb: fgArgb } };
456
+ cell.fill = solidFill(bgArgb);
457
+ cell.alignment = { vertical: "middle", horizontal: "center" };
458
+ });
459
+ row.height = 22;
460
+ }
461
+ function shadeRow(row, colCount, argb) {
462
+ for (let c = 1; c <= colCount; c++) {
463
+ row.getCell(c).fill = solidFill(argb);
464
+ }
465
+ }
466
+ function applyBorderToRow(row, colCount) {
467
+ const border = { style: "thin", color: { argb: COLOR.border } };
468
+ for (let c = 1; c <= colCount; c++) {
469
+ row.getCell(c).border = {
470
+ top: border,
471
+ left: border,
472
+ bottom: border,
473
+ right: border
474
+ };
475
+ }
476
+ }
477
+ function collectTableIndexes(table, doc) {
478
+ return [
479
+ ...table.indexes,
480
+ ...doc.indexes.filter(
481
+ (idx) => idx.table === table.name && !table.indexes.some((tableIdx) => tableIdx.name === idx.name)
482
+ )
483
+ ];
484
+ }
485
+
486
+ // src/exporters/markdown/markdown-exporter.ts
487
+ import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
488
+ import { join as join3 } from "path";
489
+
490
+ // src/core/sanitize.ts
491
+ function sanitizeFilename(name) {
492
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
493
+ }
494
+
495
+ // src/exporters/markdown/markdown-exporter.ts
496
+ async function exportMarkdownDocs(doc, options) {
497
+ try {
498
+ const tablesDir = join3(options.outDir, "tables");
499
+ await mkdir3(tablesDir, { recursive: true });
500
+ const labels = getOutputLabels(options.language);
501
+ for (const table of doc.tables) {
502
+ await writeFile2(
503
+ join3(tablesDir, `${sanitizeFilename(table.name)}.md`),
504
+ renderTableDoc(table, doc, labels),
505
+ "utf8"
506
+ );
507
+ }
508
+ } catch (err) {
509
+ throw new Error(
510
+ `Failed to export Markdown docs: ${err instanceof Error ? err.message : String(err)}`,
511
+ { cause: err }
512
+ );
513
+ }
514
+ }
515
+ function renderTableDoc(table, doc, labels) {
516
+ const lines = [];
517
+ const tableIndexes = collectTableIndexes2(table, doc);
518
+ lines.push(`# ${escapeMd(table.name)}`);
519
+ lines.push("");
520
+ lines.push(`## ${labels.tableInfoHeading}`);
521
+ lines.push("");
522
+ lines.push(`| ${labels.metaField} | ${labels.metaValue} |`);
523
+ lines.push("|------|----|");
524
+ lines.push(`| ${labels.tablePhysicalName} | ${escapeMd(table.name)} |`);
525
+ lines.push(`| ${labels.tableLogicalName} | ${escapeMd(table.comment ?? "")} |`);
526
+ lines.push(`| ${labels.schema} | ${escapeMd(table.schema ?? "")} |`);
527
+ lines.push(
528
+ `| ${labels.primaryKey} | ${escapeMd(table.primaryKeys.join(", ") || labels.none)} |`
529
+ );
530
+ lines.push(
531
+ `| ${labels.foreignKeys} | ${escapeMd(
532
+ table.foreignKeys.length > 0 ? table.foreignKeys.map((fk) => {
533
+ const name = fk.name ? ` (${fk.name})` : "";
534
+ return `${fk.columns.join(", ")} -> ${fk.referencedTable}.${fk.referencedColumns.join(", ")}${name}`;
535
+ }).join("; ") : labels.none
536
+ )} |`
537
+ );
538
+ lines.push(
539
+ `| ${labels.indexes} | ${escapeMd(
540
+ tableIndexes.length > 0 ? tableIndexes.map(
541
+ (idx) => `${idx.name} (${idx.columns.join(", ")})${idx.unique ? " UNIQUE" : ""}`
542
+ ).join("; ") : labels.none
543
+ )} |`
544
+ );
545
+ lines.push("");
546
+ lines.push(`## ${labels.columnsHeading}`);
547
+ lines.push("");
548
+ lines.push(
549
+ `| ${labels.physicalName} | ${labels.logicalName} | ${labels.type} | ${labels.required} | ${labels.defaultValue} | ${labels.notes} |`
550
+ );
551
+ lines.push("|--------|--------|----|------|--------------|------|");
552
+ for (const col of table.columns) {
553
+ lines.push(
554
+ `| ${escapeMd(col.name)} | ${escapeMd(col.comment ?? "")} | ${escapeMd(col.type)} | ${col.nullable ? labels.no : labels.yes} | ${escapeMd(col.defaultValue ?? "-")} | ${escapeMd(col.description?.value ?? "")} |`
555
+ );
556
+ }
557
+ lines.push("");
558
+ return lines.join("\n");
559
+ }
560
+ function collectTableIndexes2(table, doc) {
561
+ return [
562
+ ...table.indexes,
563
+ ...doc.indexes.filter(
564
+ (idx) => idx.table === table.name && !table.indexes.some((tableIdx) => tableIdx.name === idx.name)
565
+ )
566
+ ];
567
+ }
568
+ function escapeMd(text) {
569
+ return text.replace(/([|*_`\[\]<>#~\\])/g, "\\$1");
570
+ }
571
+
572
+ // src/exporters/html/html-exporter.ts
573
+ import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
574
+ import { join as join4 } from "path";
575
+ async function exportHtmlDocs(doc, options) {
576
+ try {
577
+ const htmlDir = join4(options.outDir, "html");
578
+ const tablesDir = join4(htmlDir, "tables");
579
+ await mkdir4(tablesDir, { recursive: true });
580
+ const labels = getOutputLabels(options.language);
581
+ await writeFile3(
582
+ join4(htmlDir, "index.html"),
583
+ renderIndexPage(doc, labels),
584
+ "utf8"
585
+ );
586
+ for (const table of doc.tables) {
587
+ await writeFile3(
588
+ join4(tablesDir, `${sanitizeFilename(table.name)}.html`),
589
+ renderTablePage(table, doc, labels),
590
+ "utf8"
591
+ );
592
+ }
593
+ } catch (err) {
594
+ throw new Error(
595
+ `Failed to export HTML docs: ${err instanceof Error ? err.message : String(err)}`,
596
+ { cause: err }
597
+ );
598
+ }
599
+ }
600
+ var CSS = `
601
+ :root {
602
+ --bg: #f3f4f6;
603
+ --paper: #ffffff;
604
+ --text: #111827;
605
+ --muted: #4b5563;
606
+ --border: #bfc7d4;
607
+ --accent: #4472c4;
608
+ --accent-light: #dbe5f1;
609
+ --accent-mid: #eef3f8;
610
+ --pk-bg: #fff3cd;
611
+ --fk-bg: #e8f4fd;
612
+ }
613
+ * { box-sizing: border-box; margin: 0; padding: 0; }
614
+ body {
615
+ font-family: "Yu Gothic UI", "Meiryo", Arial, sans-serif;
616
+ background: var(--bg); color: var(--text); line-height: 1.5;
617
+ padding: 24px;
618
+ }
619
+ a { color: var(--accent); text-decoration: none; }
620
+ a:hover { text-decoration: underline; }
621
+ .sheet {
622
+ max-width: 1200px;
623
+ margin: 0 auto;
624
+ background: var(--paper);
625
+ border: 1px solid var(--border);
626
+ padding: 24px;
627
+ }
628
+ h1 { font-size: 22px; margin-bottom: 16px; color: var(--accent); border-bottom: 2px solid var(--accent-light); padding-bottom: 8px; }
629
+ h2 { font-size: 15px; margin: 20px 0 8px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
630
+ table { width: 100%; border-collapse: collapse; table-layout: fixed; margin-bottom: 16px; }
631
+ th, td {
632
+ border: 1px solid var(--border);
633
+ padding: 7px 10px;
634
+ text-align: left;
635
+ vertical-align: top;
636
+ word-break: break-word;
637
+ font-size: 13px;
638
+ }
639
+ thead th { background: var(--accent); color: #fff; font-weight: 700; }
640
+ .meta th { background: var(--accent-light); font-weight: 700; color: #1f3864; width: 180px; }
641
+ .meta td { background: #fafbfe; }
642
+ .pk td:first-child { font-weight: 700; }
643
+ .pk { background: var(--pk-bg); }
644
+ .fk { background: var(--fk-bg); }
645
+ .badge {
646
+ display: inline-block; font-size: 10px; font-weight: 700;
647
+ padding: 1px 5px; border-radius: 3px; margin-left: 4px; vertical-align: middle;
648
+ }
649
+ .badge-pk { background: #f59e0b; color: #fff; }
650
+ .badge-fk { background: var(--accent); color: #fff; }
651
+ .note { color: var(--muted); font-size: 12px; margin-top: 10px; }
652
+ .back { margin-bottom: 16px; font-size: 13px; }
653
+ .summary { display: flex; gap: 24px; margin-bottom: 20px; }
654
+ .summary-item { background: var(--accent-light); border-radius: 6px; padding: 10px 18px; }
655
+ .summary-item .num { font-size: 24px; font-weight: 700; color: var(--accent); }
656
+ .summary-item .lbl { font-size: 12px; color: var(--muted); }
657
+ .table-list { width: 100%; border-collapse: collapse; margin-top: 8px; }
658
+ .table-list th { background: var(--accent); color: #fff; }
659
+ .table-list tr:nth-child(even) td { background: var(--accent-mid); }
660
+ .table-list td:first-child a { font-weight: 600; }
661
+ `;
662
+ function pageShell(title, body, fromSubdir = false) {
663
+ const base = fromSubdir ? "../" : "";
664
+ return `<!DOCTYPE html>
665
+ <html lang="en">
666
+ <head>
667
+ <meta charset="UTF-8">
668
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
669
+ <title>${esc(title)}</title>
670
+ <style>${CSS} </style>
671
+ </head>
672
+ <body>
673
+ <div class="sheet">
674
+ ${body}
675
+ </div>
676
+ </body>
677
+ </html>`;
678
+ }
679
+ function renderIndexPage(doc, labels) {
680
+ let tableRows = "";
681
+ for (const table of doc.tables) {
682
+ const pkCols = table.primaryKeys.join(", ") || labels.none;
683
+ const fkCount = table.foreignKeys.length;
684
+ const fileName = sanitizeFilename(table.name);
685
+ tableRows += ` <tr>
686
+ <td><a href="tables/${fileName}.html">${esc(table.name)}</a></td>
687
+ <td>${esc(table.comment ?? "")}</td>
688
+ <td style="text-align:center">${table.columns.length}</td>
689
+ <td>${esc(pkCols)}</td>
690
+ <td style="text-align:center">${fkCount}</td>
691
+ </tr>
692
+ `;
693
+ }
694
+ const body = `
695
+ <h1>${esc(labels.docTitle)}</h1>
696
+ <div class="summary">
697
+ <div class="summary-item"><div class="num">${doc.tables.length}</div><div class="lbl">${esc(labels.tablesLabel)}</div></div>
698
+ <div class="summary-item"><div class="num">${doc.relationships.length}</div><div class="lbl">${esc(labels.relationshipsLabel)}</div></div>
699
+ <div class="summary-item"><div class="num">${doc.dialect}</div><div class="lbl">${esc(labels.dialectLabel)}</div></div>
700
+ </div>
701
+ <h2>${esc(labels.tableListHeading)}</h2>
702
+ <table class="table-list">
703
+ <thead><tr>
704
+ <th>${esc(labels.tableLabel)}</th>
705
+ <th>${esc(labels.tableLogicalName)}</th>
706
+ <th style="width:70px;text-align:center">Cols</th>
707
+ <th>${esc(labels.primaryKey)}</th>
708
+ <th style="width:50px;text-align:center">FK</th>
709
+ </tr></thead>
710
+ <tbody>
711
+ ${tableRows} </tbody>
712
+ </table>
713
+ <p class="note">${esc(labels.generatedNote)}</p>
714
+ `;
715
+ return pageShell(labels.docTitle, body);
716
+ }
717
+ function renderTablePage(table, doc, labels) {
718
+ const indexes = collectTableIndexes3(table, doc);
719
+ const foreignKeys = table.foreignKeys.length ? table.foreignKeys.map((fk) => {
720
+ const name = fk.name ? ` (${fk.name})` : "";
721
+ return `${fk.columns.join(", ")} \u2192 ${fk.referencedTable}.${fk.referencedColumns.join(", ")}${name}`;
722
+ }).join("<br>") : labels.none;
723
+ const indexText = indexes.length ? indexes.map(
724
+ (idx) => `${idx.name} (${idx.columns.join(", ")})${idx.unique ? " UNIQUE" : ""}`
725
+ ).join("<br>") : labels.none;
726
+ let colRows = "";
727
+ for (const col of table.columns) {
728
+ const pkBadge = col.isPrimaryKey ? `<span class="badge badge-pk">PK</span>` : "";
729
+ const fkBadge = col.isForeignKey ? `<span class="badge badge-fk">FK</span>` : "";
730
+ const rowClass = col.isPrimaryKey ? "pk" : col.isForeignKey ? "fk" : "";
731
+ const required = col.nullable ? labels.no : labels.yes;
732
+ colRows += ` <tr${rowClass ? ` class="${rowClass}"` : ""}><td>${esc(col.name)}${pkBadge}${fkBadge}</td><td>${esc(col.comment ?? "")}</td><td>${esc(col.type)}</td><td>${required}</td><td>${esc(col.defaultValue ?? "-")}</td><td>${esc(col.description?.value ?? "")}</td></tr>
733
+ `;
734
+ }
735
+ const body = `
736
+ <p class="back"><a href="../index.html">\u2190 ${esc(labels.tableListHeading)}</a></p>
737
+ <h1>${esc(table.name)}</h1>
738
+ <h2>${esc(labels.tableInfoHeading)}</h2>
739
+ <table class="meta">
740
+ <tbody>
741
+ <tr><th>${esc(labels.tablePhysicalName)}</th><td>${esc(table.name)}</td></tr>
742
+ <tr><th>${esc(labels.tableLogicalName)}</th><td>${esc(table.comment ?? "")}</td></tr>
743
+ <tr><th>${esc(labels.schema)}</th><td>${esc(table.schema ?? "")}</td></tr>
744
+ <tr><th>${esc(labels.primaryKey)}</th><td>${esc(table.primaryKeys.join(", ") || labels.none)}</td></tr>
745
+ <tr><th>${esc(labels.foreignKeys)}</th><td>${foreignKeys}</td></tr>
746
+ <tr><th>${esc(labels.indexes)}</th><td>${indexText}</td></tr>
747
+ </tbody>
748
+ </table>
749
+
750
+ <h2>${esc(labels.columnsHeading)}</h2>
751
+ <table class="columns">
752
+ <thead><tr>
753
+ <th>${esc(labels.physicalName)}</th>
754
+ <th>${esc(labels.logicalName)}</th>
755
+ <th>${esc(labels.type)}</th>
756
+ <th>${esc(labels.required)}</th>
757
+ <th>${esc(labels.defaultValue)}</th>
758
+ <th>${esc(labels.notes)}</th>
759
+ </tr></thead>
760
+ <tbody>
761
+ ${colRows} </tbody>
762
+ </table>
763
+ <p class="note">${esc(labels.generatedNote)}</p>
764
+ `;
765
+ return pageShell(table.name, body);
766
+ }
767
+ function collectTableIndexes3(table, doc) {
768
+ return [
769
+ ...table.indexes,
770
+ ...doc.indexes.filter(
771
+ (idx) => idx.table === table.name && !table.indexes.some((tableIdx) => tableIdx.name === idx.name)
772
+ )
773
+ ];
774
+ }
775
+ function esc(text) {
776
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
777
+ }
778
+
779
+ // src/exporters/word/word-exporter.ts
780
+ import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
781
+ import { join as join5 } from "path";
782
+ import {
783
+ Document,
784
+ Packer,
785
+ Paragraph,
786
+ Table,
787
+ TableCell,
788
+ TableRow,
789
+ TextRun,
790
+ HeadingLevel
791
+ } from "docx";
792
+ async function exportWordDocument(doc, options) {
793
+ try {
794
+ await mkdir5(options.outDir, { recursive: true });
795
+ const labels = getOutputLabels(options.language);
796
+ const children = [];
797
+ children.push(
798
+ new Paragraph({
799
+ heading: HeadingLevel.HEADING_1,
800
+ children: [new TextRun(labels.docTitle)]
801
+ })
802
+ );
803
+ children.push(
804
+ new Paragraph({
805
+ heading: HeadingLevel.HEADING_2,
806
+ children: [new TextRun(labels.overviewHeading)]
807
+ })
808
+ );
809
+ children.push(
810
+ new Paragraph({
811
+ children: [new TextRun(`${labels.dialectLabel}: ${doc.dialect}`)]
812
+ })
813
+ );
814
+ children.push(
815
+ new Paragraph({
816
+ children: [new TextRun(`${labels.tablesLabel}: ${doc.tables.length}`)]
817
+ })
818
+ );
819
+ children.push(
820
+ new Paragraph({
821
+ children: [new TextRun(`${labels.relationshipsLabel}: ${doc.relationships.length}`)]
822
+ })
823
+ );
824
+ children.push(
825
+ new Paragraph({
826
+ heading: HeadingLevel.HEADING_2,
827
+ children: [new TextRun(labels.tableListHeading)]
828
+ })
829
+ );
830
+ if (doc.tables.length > 0) {
831
+ const tableListRows = [
832
+ new TableRow({
833
+ children: [
834
+ new TableCell({
835
+ children: [
836
+ new Paragraph({
837
+ children: [new TextRun({ text: labels.tableLabel, bold: true })]
838
+ })
839
+ ]
840
+ }),
841
+ new TableCell({
842
+ children: [
843
+ new Paragraph({
844
+ children: [new TextRun({ text: labels.descriptionLabel, bold: true })]
845
+ })
846
+ ]
847
+ })
848
+ ]
849
+ })
850
+ ];
851
+ for (const table of doc.tables) {
852
+ tableListRows.push(
853
+ new TableRow({
854
+ children: [
855
+ new TableCell({
856
+ children: [
857
+ new Paragraph({ children: [new TextRun(table.name)] })
858
+ ]
859
+ }),
860
+ new TableCell({
861
+ children: [
862
+ new Paragraph({
863
+ children: [
864
+ new TextRun(
865
+ table.description?.value ?? table.comment ?? ""
866
+ )
867
+ ]
868
+ })
869
+ ]
870
+ })
871
+ ]
872
+ })
873
+ );
874
+ }
875
+ children.push(new Table({ rows: tableListRows }));
876
+ }
877
+ for (const table of doc.tables) {
878
+ children.push(...renderTableDetail(table, doc, labels));
879
+ }
880
+ children.push(
881
+ new Paragraph({
882
+ heading: HeadingLevel.HEADING_2,
883
+ children: [new TextRun(labels.relationshipsHeading)]
884
+ })
885
+ );
886
+ if (doc.relationships.length > 0) {
887
+ const relHeaderCells = [
888
+ labels.fromTable,
889
+ labels.fromColumn,
890
+ labels.toTable,
891
+ labels.toColumn,
892
+ labels.constraint,
893
+ labels.source,
894
+ labels.needsReview
895
+ ].map(
896
+ (h) => new TableCell({
897
+ children: [
898
+ new Paragraph({
899
+ children: [new TextRun({ text: h, bold: true })]
900
+ })
901
+ ]
902
+ })
903
+ );
904
+ const relRows = [new TableRow({ children: relHeaderCells })];
905
+ for (const rel of doc.relationships) {
906
+ relRows.push(
907
+ new TableRow({
908
+ children: [
909
+ new TableCell({
910
+ children: [
911
+ new Paragraph({ children: [new TextRun(rel.fromTable)] })
912
+ ]
913
+ }),
914
+ new TableCell({
915
+ children: [
916
+ new Paragraph({ children: [new TextRun(rel.fromColumn)] })
917
+ ]
918
+ }),
919
+ new TableCell({
920
+ children: [
921
+ new Paragraph({ children: [new TextRun(rel.toTable)] })
922
+ ]
923
+ }),
924
+ new TableCell({
925
+ children: [
926
+ new Paragraph({ children: [new TextRun(rel.toColumn)] })
927
+ ]
928
+ }),
929
+ new TableCell({
930
+ children: [
931
+ new Paragraph({
932
+ children: [new TextRun(rel.constraintName ?? "")]
933
+ })
934
+ ]
935
+ }),
936
+ new TableCell({
937
+ children: [
938
+ new Paragraph({ children: [new TextRun(rel.source)] })
939
+ ]
940
+ }),
941
+ new TableCell({
942
+ children: [
943
+ new Paragraph({
944
+ children: [new TextRun(rel.needsReview ? labels.yes : labels.no)]
945
+ })
946
+ ]
947
+ })
948
+ ]
949
+ })
950
+ );
951
+ }
952
+ children.push(new Table({ rows: relRows }));
953
+ } else {
954
+ children.push(
955
+ new Paragraph({
956
+ children: [new TextRun(labels.none)]
957
+ })
958
+ );
959
+ }
960
+ children.push(
961
+ new Paragraph({
962
+ heading: HeadingLevel.HEADING_2,
963
+ children: [new TextRun(labels.warningsHeading)]
964
+ })
965
+ );
966
+ if (doc.warnings.length > 0) {
967
+ const warnHeaderCells = [
968
+ labels.severity,
969
+ labels.code,
970
+ labels.target,
971
+ labels.message
972
+ ].map(
973
+ (h) => new TableCell({
974
+ children: [
975
+ new Paragraph({
976
+ children: [new TextRun({ text: h, bold: true })]
977
+ })
978
+ ]
979
+ })
980
+ );
981
+ const warnRows = [new TableRow({ children: warnHeaderCells })];
982
+ for (const warning of doc.warnings) {
983
+ warnRows.push(
984
+ new TableRow({
985
+ children: [
986
+ new TableCell({
987
+ children: [
988
+ new Paragraph({ children: [new TextRun(warning.severity)] })
989
+ ]
990
+ }),
991
+ new TableCell({
992
+ children: [
993
+ new Paragraph({ children: [new TextRun(warning.code)] })
994
+ ]
995
+ }),
996
+ new TableCell({
997
+ children: [
998
+ new Paragraph({
999
+ children: [new TextRun(warning.target ?? "")]
1000
+ })
1001
+ ]
1002
+ }),
1003
+ new TableCell({
1004
+ children: [
1005
+ new Paragraph({ children: [new TextRun(warning.message)] })
1006
+ ]
1007
+ })
1008
+ ]
1009
+ })
1010
+ );
1011
+ }
1012
+ children.push(new Table({ rows: warnRows }));
1013
+ } else {
1014
+ children.push(
1015
+ new Paragraph({
1016
+ children: [new TextRun(labels.none)]
1017
+ })
1018
+ );
1019
+ }
1020
+ const wordDoc = new Document({
1021
+ sections: [{ children }]
1022
+ });
1023
+ const buffer = await Packer.toBuffer(wordDoc);
1024
+ await writeFile4(join5(options.outDir, "database_document.docx"), buffer);
1025
+ } catch (err) {
1026
+ throw new Error(
1027
+ `Failed to export Word document: ${err instanceof Error ? err.message : String(err)}`,
1028
+ { cause: err }
1029
+ );
1030
+ }
1031
+ }
1032
+ function renderTableDetail(table, doc, labels) {
1033
+ const items = [];
1034
+ const indexes = collectTableIndexes4(table, doc);
1035
+ items.push(
1036
+ new Paragraph({
1037
+ heading: HeadingLevel.HEADING_2,
1038
+ children: [new TextRun(table.name)]
1039
+ })
1040
+ );
1041
+ items.push(
1042
+ new Paragraph({
1043
+ heading: HeadingLevel.HEADING_3,
1044
+ children: [new TextRun(labels.tableInfoHeading)]
1045
+ })
1046
+ );
1047
+ items.push(
1048
+ renderMetaTable([
1049
+ [labels.tablePhysicalName, table.name],
1050
+ [labels.tableLogicalName, table.comment ?? ""],
1051
+ [labels.schema, table.schema ?? ""],
1052
+ [labels.primaryKey, table.primaryKeys.join(", ") || labels.none],
1053
+ [
1054
+ labels.foreignKeys,
1055
+ table.foreignKeys.length > 0 ? table.foreignKeys.map((fk) => {
1056
+ const name = fk.name ? ` (${fk.name})` : "";
1057
+ return `${fk.columns.join(", ")} -> ${fk.referencedTable}.${fk.referencedColumns.join(", ")}${name}`;
1058
+ }).join("; ") : labels.none
1059
+ ],
1060
+ [
1061
+ labels.indexes,
1062
+ indexes.length > 0 ? indexes.map(
1063
+ (idx) => `${idx.name} (${idx.columns.join(", ")})${idx.unique ? " UNIQUE" : ""}`
1064
+ ).join("; ") : labels.none
1065
+ ]
1066
+ ])
1067
+ );
1068
+ items.push(
1069
+ new Paragraph({
1070
+ heading: HeadingLevel.HEADING_3,
1071
+ children: [new TextRun(labels.columnsHeading)]
1072
+ })
1073
+ );
1074
+ items.push(renderColumnsTable(table, labels));
1075
+ items.push(
1076
+ new Paragraph({
1077
+ children: [new TextRun("")]
1078
+ })
1079
+ );
1080
+ return items;
1081
+ }
1082
+ function renderColumnsTable(table, labels) {
1083
+ const headerCells = [
1084
+ labels.physicalName,
1085
+ labels.logicalName,
1086
+ labels.type,
1087
+ labels.required,
1088
+ labels.defaultValue,
1089
+ labels.notes
1090
+ ].map(
1091
+ (h) => new TableCell({
1092
+ children: [
1093
+ new Paragraph({ children: [new TextRun({ text: h, bold: true })] })
1094
+ ]
1095
+ })
1096
+ );
1097
+ const colRows = [new TableRow({ children: headerCells })];
1098
+ for (const col of table.columns) {
1099
+ colRows.push(
1100
+ new TableRow({
1101
+ children: [
1102
+ new TableCell({
1103
+ children: [new Paragraph({ children: [new TextRun(col.name)] })]
1104
+ }),
1105
+ new TableCell({
1106
+ children: [
1107
+ new Paragraph({ children: [new TextRun(col.comment ?? "")] })
1108
+ ]
1109
+ }),
1110
+ new TableCell({
1111
+ children: [new Paragraph({ children: [new TextRun(col.type)] })]
1112
+ }),
1113
+ new TableCell({
1114
+ children: [
1115
+ new Paragraph({
1116
+ children: [new TextRun(col.nullable ? labels.no : labels.yes)]
1117
+ })
1118
+ ]
1119
+ }),
1120
+ new TableCell({
1121
+ children: [
1122
+ new Paragraph({
1123
+ children: [new TextRun(col.defaultValue ?? "-")]
1124
+ })
1125
+ ]
1126
+ }),
1127
+ new TableCell({
1128
+ children: [
1129
+ new Paragraph({
1130
+ children: [new TextRun(col.description?.value ?? "")]
1131
+ })
1132
+ ]
1133
+ })
1134
+ ]
1135
+ })
1136
+ );
1137
+ }
1138
+ return new Table({ rows: colRows });
1139
+ }
1140
+ function renderMetaTable(rows) {
1141
+ return new Table({
1142
+ rows: rows.map(
1143
+ ([label, value]) => new TableRow({
1144
+ children: [
1145
+ new TableCell({
1146
+ children: [
1147
+ new Paragraph({
1148
+ children: [new TextRun({ text: label, bold: true })]
1149
+ })
1150
+ ]
1151
+ }),
1152
+ new TableCell({
1153
+ children: [new Paragraph({ children: [new TextRun(value)] })]
1154
+ })
1155
+ ]
1156
+ })
1157
+ )
1158
+ });
1159
+ }
1160
+ function collectTableIndexes4(table, doc) {
1161
+ return [
1162
+ ...table.indexes,
1163
+ ...doc.indexes.filter(
1164
+ (idx) => idx.table === table.name && !table.indexes.some((tableIdx) => tableIdx.name === idx.name)
1165
+ )
1166
+ ];
1167
+ }
1168
+
1169
+ // src/parsers/sql/sql-parser.ts
1170
+ import nodeSqlParser from "node-sql-parser";
1171
+
1172
+ // src/core/warnings.ts
1173
+ function createWarning(code, message, target) {
1174
+ return {
1175
+ code,
1176
+ message,
1177
+ target,
1178
+ severity: "warning"
1179
+ };
1180
+ }
1181
+
1182
+ // src/parsers/sql/sql-normalizer.ts
1183
+ function normalizeSqlAst(ast, dialect) {
1184
+ const statements = Array.isArray(ast) ? ast : [ast];
1185
+ const tables = [];
1186
+ const indexes = [];
1187
+ const relationships = [];
1188
+ const warnings = [];
1189
+ for (const statement of statements) {
1190
+ if (statement.type === "create" && statement.keyword === "table") {
1191
+ const table = normalizeCreateTable(statement);
1192
+ tables.push(table);
1193
+ const result = relationshipsFromTable(table);
1194
+ relationships.push(...result.relationships);
1195
+ warnings.push(...result.warnings);
1196
+ }
1197
+ if (statement.type === "create" && statement.keyword === "index") {
1198
+ indexes.push(normalizeCreateIndex(statement));
1199
+ }
1200
+ }
1201
+ for (const index of indexes) {
1202
+ const table = tables.find((candidate) => candidate.name === index.table);
1203
+ table?.indexes.push(index);
1204
+ }
1205
+ return {
1206
+ dialect,
1207
+ tables,
1208
+ relationships,
1209
+ indexes,
1210
+ warnings
1211
+ };
1212
+ }
1213
+ function normalizeCreateTable(statement) {
1214
+ const tableName = extractTableName(statement.table);
1215
+ const createDefinitions = Array.isArray(statement.create_definitions) ? statement.create_definitions : [];
1216
+ const table = {
1217
+ name: tableName,
1218
+ columns: [],
1219
+ primaryKeys: [],
1220
+ foreignKeys: [],
1221
+ indexes: [],
1222
+ reviewTodos: []
1223
+ };
1224
+ for (const definition of createDefinitions) {
1225
+ if (definition.resource === "column") {
1226
+ const columnName = extractDeepColumnName(definition.column);
1227
+ const isPrimaryKey = hasPrimaryKey(definition);
1228
+ const isNotNull = hasNotNull(definition);
1229
+ table.columns.push({
1230
+ name: columnName,
1231
+ type: normalizeType(definition.definition),
1232
+ nullable: !isNotNull && !isPrimaryKey,
1233
+ defaultValue: extractDefaultFromDef(definition),
1234
+ isPrimaryKey,
1235
+ isForeignKey: false
1236
+ });
1237
+ if (isPrimaryKey) table.primaryKeys.push(columnName);
1238
+ }
1239
+ if (definition.resource === "constraint" && isConstraintType(definition.constraint_type, "PRIMARY KEY")) {
1240
+ table.primaryKeys = extractDeepColumnNames(definition.definition);
1241
+ for (const column of table.columns) {
1242
+ if (table.primaryKeys.includes(column.name)) column.isPrimaryKey = true;
1243
+ }
1244
+ }
1245
+ if (definition.resource === "constraint" && isConstraintType(definition.constraint_type, "FOREIGN KEY")) {
1246
+ const columns = extractDeepColumnNames(definition.definition);
1247
+ const refDef = definition.reference_definition;
1248
+ const referencedTable = extractTableName(refDef?.table);
1249
+ const referencedColumns = extractDeepColumnNames(refDef?.definition);
1250
+ table.foreignKeys.push({
1251
+ name: typeof definition.constraint === "string" ? definition.constraint : void 0,
1252
+ columns,
1253
+ referencedTable,
1254
+ referencedColumns
1255
+ });
1256
+ for (const column of table.columns) {
1257
+ if (columns.includes(column.name)) column.isForeignKey = true;
1258
+ }
1259
+ }
1260
+ }
1261
+ return table;
1262
+ }
1263
+ function normalizeCreateIndex(statement) {
1264
+ return {
1265
+ name: String(statement.index ?? statement.index_name ?? "unnamed_index"),
1266
+ table: extractTableName(statement.table),
1267
+ columns: extractDeepColumnNames(
1268
+ statement.index_columns ?? statement.columns
1269
+ ),
1270
+ unique: Boolean(statement.unique)
1271
+ };
1272
+ }
1273
+ function relationshipsFromTable(table) {
1274
+ const relationships = [];
1275
+ const warnings = [];
1276
+ for (const foreignKey of table.foreignKeys) {
1277
+ for (let index = 0; index < foreignKey.columns.length; index++) {
1278
+ const column = foreignKey.columns[index];
1279
+ let toColumn;
1280
+ let needsReview = false;
1281
+ if (foreignKey.referencedColumns[index]) {
1282
+ toColumn = foreignKey.referencedColumns[index];
1283
+ } else {
1284
+ toColumn = foreignKey.referencedColumns[0] ?? column;
1285
+ needsReview = true;
1286
+ table.reviewTodos.push({
1287
+ type: "relationship",
1288
+ target: `${table.name}.${column} \u2192 ${foreignKey.referencedTable}`,
1289
+ issue: `Foreign key column "${column}" references table "${foreignKey.referencedTable}" but the referenced column at position ${index} is missing from the schema. Using "${toColumn}" as a best-guess fallback.`,
1290
+ suggestion: `Verify the referenced column name in table "${foreignKey.referencedTable}" and update manually.`,
1291
+ source: "schema"
1292
+ });
1293
+ warnings.push({
1294
+ code: "FK_REFERENCED_COLUMN_GUESS",
1295
+ message: `In table "${table.name}", foreign key column "${column}" references "${foreignKey.referencedTable}" but the referenced column at index ${index} is missing. Falling back to "${toColumn}".`,
1296
+ target: `${table.name}.${column}`,
1297
+ severity: "warning"
1298
+ });
1299
+ }
1300
+ relationships.push({
1301
+ fromTable: table.name,
1302
+ fromColumn: column,
1303
+ toTable: foreignKey.referencedTable,
1304
+ toColumn,
1305
+ constraintName: foreignKey.name,
1306
+ source: "schema",
1307
+ needsReview
1308
+ });
1309
+ }
1310
+ }
1311
+ return { relationships, warnings };
1312
+ }
1313
+ function extractTableName(value) {
1314
+ if (Array.isArray(value)) return extractTableName(value[0]);
1315
+ if (typeof value === "object" && value !== null) {
1316
+ const object = value;
1317
+ return String(object.table ?? object.tableName ?? object.name ?? "unknown");
1318
+ }
1319
+ return String(value ?? "unknown");
1320
+ }
1321
+ function extractDeepColumnName(value) {
1322
+ if (typeof value !== "object" || value === null)
1323
+ return String(value ?? "unknown");
1324
+ const object = value;
1325
+ if (object.expr && typeof object.expr === "object") {
1326
+ return extractDeepColumnName(object.expr);
1327
+ }
1328
+ if (object.column && typeof object.column === "object") {
1329
+ return extractDeepColumnName(object.column);
1330
+ }
1331
+ if (object.value !== void 0) {
1332
+ return String(object.value);
1333
+ }
1334
+ if (object.column !== void 0) {
1335
+ return String(object.column);
1336
+ }
1337
+ return String(object.name ?? object.tableName ?? "unknown");
1338
+ }
1339
+ function extractDeepColumnNames(value) {
1340
+ if (!Array.isArray(value)) return [];
1341
+ return value.map((item) => extractDeepColumnName(item));
1342
+ }
1343
+ function normalizeType(value) {
1344
+ if (typeof value === "object" && value !== null) {
1345
+ const object = value;
1346
+ return String(
1347
+ object.dataType ?? object.type ?? object.name ?? "unknown"
1348
+ ).toLowerCase();
1349
+ }
1350
+ return String(value ?? "unknown").toLowerCase();
1351
+ }
1352
+ function hasPrimaryKey(def) {
1353
+ if (def.primary_key) return true;
1354
+ if (def.constraint_type && isConstraintType(def.constraint_type, "PRIMARY KEY"))
1355
+ return true;
1356
+ return false;
1357
+ }
1358
+ function hasNotNull(def) {
1359
+ if (!def.nullable) return false;
1360
+ if (typeof def.nullable === "object" && def.nullable.type === "not null")
1361
+ return true;
1362
+ if (String(def.nullable) === "not null") return true;
1363
+ return false;
1364
+ }
1365
+ function isConstraintType(value, expected) {
1366
+ if (typeof value !== "string") return false;
1367
+ return value.toUpperCase() === expected.toUpperCase();
1368
+ }
1369
+ function extractDefaultFromDef(def) {
1370
+ if (!def.default_val) return void 0;
1371
+ const defaultVal = def.default_val;
1372
+ if (defaultVal.type === "default" && defaultVal.value) {
1373
+ if (typeof defaultVal.value === "object" && defaultVal.value !== null) {
1374
+ const val = defaultVal.value;
1375
+ if (val.type === "function" && val.name) {
1376
+ const name = val.name;
1377
+ const parts = Array.isArray(name.name) ? name.name : [];
1378
+ return parts.map((p) => String(p.value ?? "")).join("");
1379
+ }
1380
+ if (val.type === "single_quote_string") {
1381
+ return `'${String(val.value)}'`;
1382
+ }
1383
+ if (val.value !== void 0) return String(val.value);
1384
+ }
1385
+ return String(defaultVal.value);
1386
+ }
1387
+ if (typeof defaultVal.value === "string") return defaultVal.value;
1388
+ return void 0;
1389
+ }
1390
+
1391
+ // src/parsers/sql/sql-parser.ts
1392
+ var { Parser } = nodeSqlParser;
1393
+ async function parseSqlSchema(sql, options = {}) {
1394
+ const requestedDialect = options.dialect;
1395
+ const parser = new Parser();
1396
+ const detectedDialect = detectDialect(sql);
1397
+ const attempts = buildDialectAttempts(requestedDialect, detectedDialect);
1398
+ const failures = [];
1399
+ const successes = [];
1400
+ for (const dialect of attempts) {
1401
+ try {
1402
+ const ast = parser.astify(sql, buildAstifyOptions(dialect));
1403
+ const doc = normalizeSqlAst(ast, dialect);
1404
+ successes.push({
1405
+ dialect,
1406
+ doc,
1407
+ score: scoreParsedDoc(doc)
1408
+ });
1409
+ } catch (error) {
1410
+ failures.push({
1411
+ dialect,
1412
+ message: error instanceof Error ? error.message : "Unsupported SQL syntax"
1413
+ });
1414
+ }
1415
+ }
1416
+ const best = pickBestResult(successes, requestedDialect, detectedDialect);
1417
+ if (best) {
1418
+ if (!requestedDialect && best.dialect !== "unknown") {
1419
+ best.doc.warnings.unshift(
1420
+ createWarning(
1421
+ "DIALECT_AUTO_DETECTED",
1422
+ `SQL dialect auto-detected as "${best.dialect}".`
1423
+ )
1424
+ );
1425
+ } else if (requestedDialect && requestedDialect !== best.dialect) {
1426
+ best.doc.warnings.unshift(
1427
+ createWarning(
1428
+ "DIALECT_FALLBACK",
1429
+ `Requested dialect "${requestedDialect}" produced a lower-quality parse, reparsed successfully as "${best.dialect}".`
1430
+ )
1431
+ );
1432
+ }
1433
+ return best.doc;
1434
+ }
1435
+ return {
1436
+ dialect: requestedDialect ?? "unknown",
1437
+ tables: [],
1438
+ relationships: [],
1439
+ indexes: [],
1440
+ warnings: [
1441
+ createWarning(
1442
+ "UNSUPPORTED_SQL",
1443
+ failures[0]?.message ?? "Unsupported SQL syntax"
1444
+ )
1445
+ ]
1446
+ };
1447
+ }
1448
+ function mapDialect(dialect) {
1449
+ if (dialect === "postgres") return "postgresql";
1450
+ if (dialect === "mysql" || dialect === "mariadb") return "mysql";
1451
+ return void 0;
1452
+ }
1453
+ function buildAstifyOptions(dialect) {
1454
+ const database = mapDialect(dialect);
1455
+ return database ? { database } : void 0;
1456
+ }
1457
+ function buildDialectAttempts(requestedDialect, detectedDialect) {
1458
+ const attempts = [];
1459
+ if (requestedDialect) {
1460
+ attempts.push(requestedDialect);
1461
+ }
1462
+ if (detectedDialect) {
1463
+ attempts.push(detectedDialect);
1464
+ }
1465
+ attempts.push("mysql", "mariadb", "postgres", "unknown");
1466
+ return dedupeDialects(attempts);
1467
+ }
1468
+ function dedupeDialects(dialects) {
1469
+ const seen = /* @__PURE__ */ new Set();
1470
+ const ordered = [];
1471
+ for (const dialect of dialects) {
1472
+ if (seen.has(dialect)) continue;
1473
+ seen.add(dialect);
1474
+ ordered.push(dialect);
1475
+ }
1476
+ return ordered;
1477
+ }
1478
+ function detectDialect(sql) {
1479
+ const source = sql.toUpperCase();
1480
+ const mysqlSignals = [
1481
+ "AUTO_INCREMENT",
1482
+ "ENGINE=",
1483
+ "TINYINT",
1484
+ "UNSIGNED",
1485
+ "ZEROFILL",
1486
+ "CHARACTER SET",
1487
+ "COLLATE "
1488
+ ];
1489
+ if (mysqlSignals.some((signal) => source.includes(signal))) {
1490
+ return "mysql";
1491
+ }
1492
+ const postgresSignals = [
1493
+ "SERIAL",
1494
+ "BIGSERIAL",
1495
+ "GENERATED ALWAYS AS IDENTITY",
1496
+ "JSONB",
1497
+ "ILIKE",
1498
+ "CREATE EXTENSION"
1499
+ ];
1500
+ if (postgresSignals.some((signal) => source.includes(signal))) {
1501
+ return "postgres";
1502
+ }
1503
+ return void 0;
1504
+ }
1505
+ function pickBestResult(successes, requestedDialect, detectedDialect) {
1506
+ const ranked = [...successes].sort((left, right) => {
1507
+ if (right.score !== left.score) return right.score - left.score;
1508
+ const leftBias = dialectBias(left.dialect, requestedDialect, detectedDialect);
1509
+ const rightBias = dialectBias(
1510
+ right.dialect,
1511
+ requestedDialect,
1512
+ detectedDialect
1513
+ );
1514
+ return rightBias - leftBias;
1515
+ });
1516
+ return ranked[0];
1517
+ }
1518
+ function scoreParsedDoc(doc) {
1519
+ let score = 0;
1520
+ score += doc.tables.length * 100;
1521
+ score += doc.indexes.length * 15;
1522
+ score += doc.relationships.length * 20;
1523
+ score -= doc.warnings.length * 25;
1524
+ for (const table of doc.tables) {
1525
+ score += table.columns.length * 10;
1526
+ score += table.primaryKeys.length * 5;
1527
+ for (const column of table.columns) {
1528
+ if (column.name && column.name !== "unknown") score += 3;
1529
+ if (column.type && column.type !== "unknown") score += 2;
1530
+ if (column.name.includes("[object Object]")) score -= 50;
1531
+ if (column.type.includes("[object Object]")) score -= 30;
1532
+ }
1533
+ }
1534
+ return score;
1535
+ }
1536
+ function dialectBias(dialect, requestedDialect, detectedDialect) {
1537
+ let bias = 0;
1538
+ if (detectedDialect && dialect === detectedDialect) bias += 20;
1539
+ if (requestedDialect && dialect === requestedDialect) bias += 5;
1540
+ if (dialect === "unknown") bias -= 10;
1541
+ return bias;
1542
+ }
1543
+
1544
+ // src/core/pipeline/generate-db-docs.ts
1545
+ async function generateDbDocs(options) {
1546
+ const progress = (step, message, detail) => {
1547
+ options.onProgress?.({ step, message, detail });
1548
+ };
1549
+ progress("read_schema", "Reading schema file", { schema: options.schema });
1550
+ const sql = await readFile(options.schema, "utf8");
1551
+ progress("parse_schema", "Parsing schema", { dialect: options.dialect ?? "auto-detect" });
1552
+ let doc = await parseSqlSchema(sql, {
1553
+ dialect: options.dialect
1554
+ });
1555
+ progress("schema_parsed", "Schema parsed", {
1556
+ tables: doc.tables.length,
1557
+ warnings: doc.warnings.length,
1558
+ dialect: doc.dialect
1559
+ });
1560
+ const exporters = [];
1561
+ if (options.output.formats.includes("excel")) {
1562
+ exporters.push({
1563
+ format: "excel",
1564
+ fn: () => exportExcelDictionary(doc, {
1565
+ outDir: options.outDir,
1566
+ language: options.output.language
1567
+ })
1568
+ });
1569
+ }
1570
+ if (options.output.formats.includes("diagram")) {
1571
+ exporters.push({
1572
+ format: "diagram",
1573
+ fn: () => exportMermaidDiagram(doc, { outDir: options.outDir })
1574
+ });
1575
+ }
1576
+ if (options.output.formats.includes("markdown")) {
1577
+ exporters.push({
1578
+ format: "markdown",
1579
+ fn: () => exportMarkdownDocs(doc, {
1580
+ outDir: options.outDir,
1581
+ language: options.output.language
1582
+ })
1583
+ });
1584
+ }
1585
+ if (options.output.formats.includes("html")) {
1586
+ exporters.push({
1587
+ format: "html",
1588
+ fn: () => exportHtmlDocs(doc, {
1589
+ outDir: options.outDir,
1590
+ language: options.output.language
1591
+ })
1592
+ });
1593
+ }
1594
+ if (options.output.formats.includes("word")) {
1595
+ exporters.push({
1596
+ format: "word",
1597
+ fn: () => exportWordDocument(doc, {
1598
+ outDir: options.outDir,
1599
+ language: options.output.language
1600
+ })
1601
+ });
1602
+ }
1603
+ for (const { format, fn } of exporters) {
1604
+ try {
1605
+ progress(`export_${format}`, `Exporting ${format} output`, {
1606
+ outDir: options.outDir
1607
+ });
1608
+ await fn();
1609
+ progress(`export_${format}_done`, `Exported ${format} output`, {
1610
+ outDir: options.outDir
1611
+ });
1612
+ } catch (err) {
1613
+ console.error(
1614
+ `[dbdocgen] Failed to export ${format} docs:`,
1615
+ err instanceof Error ? err.message : String(err)
1616
+ );
1617
+ progress(`export_${format}_failed`, `Failed to export ${format}`, {
1618
+ error: err instanceof Error ? err.message : String(err)
1619
+ });
1620
+ }
1621
+ }
1622
+ progress("complete", "Generation complete", {
1623
+ tables: doc.tables.length,
1624
+ warnings: doc.warnings.length,
1625
+ outDir: options.outDir
1626
+ });
1627
+ return doc;
1628
+ }
1629
+
1630
+ // src/cli/index.ts
1631
+ var DEFAULT_CONFIG_PATH = "dbdocgen.config.json";
1632
+ var program = new Command();
1633
+ program.name("dbdocgen").description("Generate database documentation from SQL schema files.").version("0.1.0");
1634
+ program.command("init").description("Create a default config file").option("-f, --force", "Overwrite existing config file").action(async (rawOptions) => {
1635
+ const configPath = resolve(process.cwd(), DEFAULT_CONFIG_PATH);
1636
+ if (existsSync(configPath) && !rawOptions.force) {
1637
+ console.log(`Config already exists at ${configPath}. Use --force to overwrite.`);
1638
+ return;
1639
+ }
1640
+ const defaultConfig = {
1641
+ schema: "./database/schema.sql",
1642
+ output: {
1643
+ formats: ["excel", "markdown", "html", "diagram", "word"],
1644
+ language: "en"
1645
+ }
1646
+ };
1647
+ await writeFile5(configPath, JSON.stringify(defaultConfig, null, 2), "utf8");
1648
+ console.log(`Created config at ${configPath}`);
1649
+ console.log("Default generate output directory is ./output/db_doc_gen_{yymmddhhmm} unless you pass --out.");
1650
+ console.log("Edit the file to configure your database schema path and output formats.");
1651
+ });
1652
+ var configCommand = program.command("config").description("Manage configuration");
1653
+ configCommand.command("show").description("Show resolved configuration").option("--config <path>", "Config file path").action(async (rawOptions) => {
1654
+ const config = await loadConfig({
1655
+ cwd: process.cwd(),
1656
+ cliOptions: { configPath: rawOptions.config }
1657
+ });
1658
+ console.log(JSON.stringify(config, null, 2));
1659
+ });
1660
+ configCommand.command("validate").description("Validate config file").option("--config <path>", "Config file path").action(async (rawOptions) => {
1661
+ try {
1662
+ const config = await loadConfig({
1663
+ cwd: process.cwd(),
1664
+ cliOptions: { configPath: rawOptions.config }
1665
+ });
1666
+ const result = dbdocgenConfigSchema.safeParse(config);
1667
+ if (result.success) {
1668
+ console.log("Config is valid.");
1669
+ } else {
1670
+ console.error("Config validation failed:");
1671
+ console.error(result.error.format());
1672
+ process.exitCode = 1;
1673
+ }
1674
+ } catch (err) {
1675
+ console.error("Failed to load config:", err instanceof Error ? err.message : err);
1676
+ process.exitCode = 1;
1677
+ }
1678
+ });
1679
+ program.command("generate").description("Generate database documentation").option("--schema <path>", "Path to schema.sql").option("--out <path>", "Output directory").option("--format <formats>", "Comma-separated output formats").option("--config <path>", "Config file path").action(async (rawOptions) => {
1680
+ console.log("[dbdocgen] Loading configuration...");
1681
+ const config = await loadConfig({
1682
+ cwd: process.cwd(),
1683
+ cliOptions: {
1684
+ schema: rawOptions.schema,
1685
+ outDir: rawOptions.out,
1686
+ formats: parseFormats(rawOptions.format),
1687
+ configPath: rawOptions.config
1688
+ }
1689
+ });
1690
+ const outDir = rawOptions.out ?? createTimestampedOutputDir();
1691
+ console.log("[dbdocgen] Configuration loaded");
1692
+ console.log(` schema: ${config.schema}`);
1693
+ console.log(` outDir: ${outDir}`);
1694
+ console.log(` formats: ${config.output.formats.join(", ")}`);
1695
+ console.log(` language: ${config.output.language}`);
1696
+ const doc = await generateDbDocs({
1697
+ schema: config.schema,
1698
+ outDir,
1699
+ dialect: config.dialect,
1700
+ output: {
1701
+ formats: config.output.formats,
1702
+ language: config.output.language
1703
+ },
1704
+ onProgress: (event) => {
1705
+ console.log(`[dbdocgen] ${event.message}`);
1706
+ if (event.detail) {
1707
+ for (const [key, value] of Object.entries(event.detail)) {
1708
+ console.log(` ${key}: ${String(value)}`);
1709
+ }
1710
+ }
1711
+ }
1712
+ });
1713
+ if (doc.warnings.length > 0) {
1714
+ console.log(`[dbdocgen] Completed with ${doc.warnings.length} warning(s)`);
1715
+ for (const warning of doc.warnings) {
1716
+ console.log(
1717
+ ` [${warning.severity}] ${warning.code}: ${warning.message}`
1718
+ );
1719
+ }
1720
+ }
1721
+ console.log(`Generated database documentation in ${outDir}`);
1722
+ });
1723
+ program.command("validate").description("Validate a SQL schema file without generating docs").option("--schema <path>", "Path to schema.sql").option("--config <path>", "Config file path").action(async (rawOptions) => {
1724
+ const config = await loadConfig({
1725
+ cwd: process.cwd(),
1726
+ cliOptions: {
1727
+ schema: rawOptions.schema,
1728
+ configPath: rawOptions.config
1729
+ }
1730
+ });
1731
+ console.log(`Validating ${config.schema}...`);
1732
+ const sql = await readFile2(config.schema, "utf8");
1733
+ const doc = await parseSqlSchema(sql, { dialect: "postgres" });
1734
+ if (doc.tables.length === 0) {
1735
+ console.log("No tables found in schema.");
1736
+ if (doc.warnings.length > 0) {
1737
+ console.log("\nWarnings:");
1738
+ for (const w of doc.warnings) {
1739
+ console.log(` [${w.code}] ${w.message}`);
1740
+ }
1741
+ }
1742
+ return;
1743
+ }
1744
+ console.log(`
1745
+ Found ${doc.tables.length} table(s):
1746
+ `);
1747
+ for (const table of doc.tables) {
1748
+ console.log(` ${table.name}`);
1749
+ console.log(` Columns: ${table.columns.length}`);
1750
+ console.log(` Primary Keys: ${table.primaryKeys.join(", ") || "(none)"}`);
1751
+ console.log(` Foreign Keys: ${table.foreignKeys.length}`);
1752
+ console.log("");
1753
+ }
1754
+ if (doc.warnings.length > 0) {
1755
+ console.log(`Warnings (${doc.warnings.length}):`);
1756
+ for (const w of doc.warnings) {
1757
+ console.log(` [${w.severity}] ${w.code}: ${w.message}`);
1758
+ }
1759
+ }
1760
+ console.log("Schema validation passed.");
1761
+ });
1762
+ program.command("clean").description("Clean output directory").option("--out <path>", "Output directory to clean").option("--config <path>", "Config file path").action(async (rawOptions) => {
1763
+ const config = await loadConfig({
1764
+ cwd: process.cwd(),
1765
+ cliOptions: {
1766
+ outDir: rawOptions.out,
1767
+ configPath: rawOptions.config
1768
+ }
1769
+ });
1770
+ const outDir = resolve(config.outDir);
1771
+ if (!existsSync(outDir)) {
1772
+ console.log(`Output directory ${outDir} does not exist. Nothing to clean.`);
1773
+ return;
1774
+ }
1775
+ console.log(`Cleaning ${outDir}...`);
1776
+ await rm(outDir, { recursive: true, force: true });
1777
+ console.log("Done.");
1778
+ });
1779
+ program.command("info").description("Show project info and supported features").action(() => {
1780
+ console.log("dbdocgen v0.1.0");
1781
+ console.log("");
1782
+ console.log("Generate database documentation from SQL schema files.");
1783
+ console.log("");
1784
+ console.log("Supported input:");
1785
+ console.log(" - PostgreSQL schema.sql");
1786
+ console.log(" - MySQL / MariaDB schema.sql");
1787
+ console.log("");
1788
+ console.log("Supported output formats:");
1789
+ console.log(" - excel Data Dictionary (.xlsx)");
1790
+ console.log(" - markdown Per-table .md files");
1791
+ console.log(" - html Static HTML documentation");
1792
+ console.log(" - diagram Mermaid ER Diagram (.mmd)");
1793
+ console.log(" - word Word document (.docx)");
1794
+ console.log("");
1795
+ console.log("Commands:");
1796
+ console.log(" init Create a default config file");
1797
+ console.log(" generate Generate documentation");
1798
+ console.log(" validate Validate SQL schema");
1799
+ console.log(" clean Clean output directory");
1800
+ console.log(" config show Show current config");
1801
+ console.log(" config validate Validate config");
1802
+ console.log(" info Show this info");
1803
+ });
1804
+ program.parseAsync().catch((error) => {
1805
+ console.error(error instanceof Error ? error.message : error);
1806
+ process.exitCode = 1;
1807
+ });
1808
+ function parseFormats(value) {
1809
+ if (!value) return void 0;
1810
+ const items = value.split(",").map((item) => item.trim());
1811
+ const valid = [];
1812
+ for (const item of items) {
1813
+ const parsed = outputFormatSchema.safeParse(item);
1814
+ if (parsed.success) {
1815
+ valid.push(parsed.data);
1816
+ } else {
1817
+ console.warn(`Warning: Unrecognized format "${item}" \u2014 must be one of: ${outputFormatSchema.options.join(", ")}`);
1818
+ }
1819
+ }
1820
+ return valid.length > 0 ? valid : void 0;
1821
+ }
1822
+ function createTimestampedOutputDir(date = /* @__PURE__ */ new Date()) {
1823
+ const year = String(date.getFullYear()).slice(-2);
1824
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1825
+ const day = String(date.getDate()).padStart(2, "0");
1826
+ const hours = String(date.getHours()).padStart(2, "0");
1827
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1828
+ return `./output/db_doc_gen_${year}${month}${day}${hours}${minutes}`;
1829
+ }
1830
+ //# sourceMappingURL=index.js.map