@zxsylph/dbml-formatter 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @zxsylph/dbml-formatter
2
+
3
+ A powerful and flexible formatter for [DBML (Database Markup Language)](https://dbml.dbdiagram.io/home/) files. This tool helps keep your DBML schemas clean, consistent, and readable by applying standard formatting rules, alignment, and optional enhancements.
4
+
5
+ ## Features
6
+
7
+ - **Standard Formatting**: Applies consistent spacing and indentation to tables, fields, indexes, and relationships.
8
+ - **Alignment**:
9
+ - Automatically aligns field data types for better readability.
10
+ - Aligns field settings (e.g., `[pk, increment]`) for a clean columnar look.
11
+ - **Field Sorting** (Optional): Sorts fields within groups alphabetically.
12
+ - **Note Management** (Optional): Automatically adds empty notes to fields and tables if they are missing.
13
+ - **Batch Processing**: Supports formatting individual files or entire directories recursively.
14
+ - **Dry Run**: Preview changes without modifying files.
15
+
16
+ ## Installation
17
+
18
+ You can use the formatter directly via `npx` without installation:
19
+
20
+ ```bash
21
+ npx @zxsylph/dbml-formatter <file-or-options>
22
+ ```
23
+
24
+ Or install it globally:
25
+
26
+ ```bash
27
+ npm install -g @zxsylph/dbml-formatter
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Format a Single File
33
+
34
+ Prints the formatted result to `stdout`.
35
+
36
+ ```bash
37
+ npx @zxsylph/dbml-formatter schema.dbml
38
+ ```
39
+
40
+ ### Format a Directory
41
+
42
+ Formats all `.dbml` files in the specified directory (and subdirectories) **in place**.
43
+
44
+ ```bash
45
+ npx @zxsylph/dbml-formatter --folder ./database
46
+ ```
47
+
48
+ ### Options & Flags
49
+
50
+ | Flag | Description |
51
+ | ----------------- | ---------------------------------------------------------------------------------------------- |
52
+ | `--folder <path>` | Specifies a directory to recursively find and format `.dbml` files. Updates files in place. |
53
+ | `--dry-run` | Use with `--folder` to preview formatting changes in the console instead of overwriting files. |
54
+ | `--order-field` | Sorts fields within their logical groups (separated by blank lines) in ascending order. |
55
+ | `--add-note` | Automatically adds `Note: ''` to tables and fields that don't have one. |
56
+
57
+ ### Examples
58
+
59
+ **1. Basic file formatting (output to console):**
60
+
61
+ ```bash
62
+ npx @zxsylph/dbml-formatter schema.dbml > formatted_schema.dbml
63
+ ```
64
+
65
+ **2. Format an entire project directory:**
66
+
67
+ ```bash
68
+ npx @zxsylph/dbml-formatter --folder ./src/db
69
+ ```
70
+
71
+ **3. Format with field sorting and extra notes:**
72
+
73
+ ```bash
74
+ npx @zxsylph/dbml-formatter --folder ./src/db --order-field --add-note
75
+ ```
76
+
77
+ **4. Preview changes for a folder:**
78
+
79
+ ```bash
80
+ npx @zxsylph/dbml-formatter --folder ./src/db --dry-run
81
+ ```
82
+
83
+ ## Formatting Rules
84
+
85
+ The formatter applies the following styles:
86
+
87
+ - **Indentation**: 2 spaces.
88
+ - **Spacing**: Removes extra empty lines inside table bodies.
89
+ - **Alignment**: Vertically aligns data types and column settings `[...]`.
90
+ - **Hoisting**: Moves table-level notes to the top of the table definition.
91
+
92
+ ## License
93
+
94
+ ISC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zxsylph/dbml-formatter",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "files": [
6
6
  "bin",
@@ -185,44 +185,46 @@ export function format(input: string, options: FormatterOptions = {}): string {
185
185
  }
186
186
  }
187
187
 
188
+ // OPTIONAL: Sort Fields within groups
189
+ // 1. Normalize groups: Detach "Gap" (extra newlines) from content lines.
190
+ {
191
+ const normalized: Token[][] = [];
192
+
193
+ for (const line of otherLinesGroups) {
194
+ const last = line[line.length - 1];
195
+ let hasExtraNewline = false;
196
+
197
+ if (last && last.type === TokenType.Whitespace) {
198
+ const newlineCount = (last.value.match(/\n/g) || []).length;
199
+ if (newlineCount > 1) {
200
+ hasExtraNewline = true;
201
+
202
+ // Create stripped line (1 newline)
203
+ const newLineTokens = [...line];
204
+ newLineTokens[newLineTokens.length - 1] = {
205
+ ...last,
206
+ value: last.value.replace(/\n+/g, '\n')
207
+ };
208
+ normalized.push(newLineTokens);
209
+
210
+ // Add spacer lines
211
+ for(let k=1; k < newlineCount; k++) {
212
+ normalized.push([{ type: TokenType.Whitespace, value: '\n', line: 0, column: 0 }]);
213
+ }
214
+ }
215
+ }
216
+
217
+ if (!hasExtraNewline) {
218
+ normalized.push(line);
219
+ }
220
+ }
221
+
222
+ // Replace otherLinesGroups with normalized version
223
+ otherLinesGroups.splice(0, otherLinesGroups.length, ...normalized);
224
+ }
225
+
188
226
  // OPTIONAL: Sort Fields within groups
189
227
  if (options.orderField) {
190
- // 1. Normalize groups: Detach "Gap" (extra newlines) from content lines.
191
- // If a line ends with > 1 newline, split it into [ContentLine] + [EmptyLine]s.
192
-
193
- const normalized: Token[][] = [];
194
-
195
- for (const line of otherLinesGroups) {
196
- const last = line[line.length - 1];
197
- let hasExtraNewline = false;
198
-
199
- if (last && last.type === TokenType.Whitespace) {
200
- const newlineCount = (last.value.match(/\n/g) || []).length;
201
- if (newlineCount > 1) {
202
- hasExtraNewline = true;
203
-
204
- // Create stripped line (1 newline)
205
- const newLineTokens = [...line];
206
- newLineTokens[newLineTokens.length - 1] = {
207
- ...last,
208
- value: last.value.replace(/\n+/g, '\n')
209
- };
210
- normalized.push(newLineTokens);
211
-
212
- // Add spacer lines
213
- for(let k=1; k < newlineCount; k++) {
214
- normalized.push([{ type: TokenType.Whitespace, value: '\n', line: 0, column: 0 }]);
215
- }
216
- }
217
- }
218
-
219
- if (!hasExtraNewline) {
220
- normalized.push(line);
221
- }
222
- }
223
-
224
- // Replace otherLinesGroups with normalized version
225
- otherLinesGroups.splice(0, otherLinesGroups.length, ...normalized);
226
228
 
227
229
  // 2. Group lines by "is content" and Sort
228
230
 
@@ -544,8 +546,23 @@ export function format(input: string, options: FormatterOptions = {}): string {
544
546
  }
545
547
  };
546
548
 
547
- // Align globally across all lines in the table (ignoring inter-field grouping)
548
- alignFieldBlock(otherLinesGroups);
549
+ // Align by grouping (split by empty lines)
550
+ let currentBlock: Token[][] = [];
551
+ for (const line of otherLinesGroups) {
552
+ const isWhitespace = line.every(t => t.type === TokenType.Whitespace);
553
+ // console.log('Line tokens:', line.map(t => t.type + ':' + JSON.stringify(t.value)), 'isWhitespace:', isWhitespace);
554
+ if (isWhitespace) {
555
+ if (currentBlock.length > 0) {
556
+ alignFieldBlock(currentBlock);
557
+ currentBlock = [];
558
+ }
559
+ } else {
560
+ currentBlock.push(line);
561
+ }
562
+ }
563
+ if (currentBlock.length > 0) {
564
+ alignFieldBlock(currentBlock);
565
+ }
549
566
 
550
567
  // 5c. Print Pass
551
568
  for (let lgIdx = 0; lgIdx < otherLinesGroups.length; lgIdx++) {
@@ -0,0 +1,33 @@
1
+ import { format } from "./formatter/formatter";
2
+ import * as assert from "assert";
3
+
4
+ const input = `Table users {
5
+ id integer
6
+ username varchar
7
+
8
+ email varchar
9
+ created_at timestamp
10
+ }`;
11
+
12
+ const expected = `Table users {
13
+ id "integer"
14
+ username "varchar"
15
+
16
+ email "varchar"
17
+ created_at "timestamp"
18
+ }
19
+ `;
20
+
21
+ const actual = format(input);
22
+
23
+ console.log("Expected:");
24
+ console.log(expected);
25
+ console.log("Actual:");
26
+ console.log(actual);
27
+
28
+ if (actual === expected) {
29
+ console.log("Test Passed");
30
+ } else {
31
+ console.error("Test Failed");
32
+ process.exit(1);
33
+ }