@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 +94 -0
- package/package.json +1 -1
- package/src/formatter/formatter.ts +55 -38
- package/src/repro_alignment.ts +33 -0
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
|
@@ -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
|
|
548
|
-
|
|
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
|
+
}
|