fix-md-tables 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bin/fix-md-tables.mjs +13 -0
- package/lib/index.mjs +300 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JoobyPM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# fix-md-tables
|
|
2
|
+
|
|
3
|
+
Fix markdown table alignment for emoji using ideographic spaces (U+3000).
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Emoji display as 2 columns in terminals but count as 1 character. When Prettier formats markdown tables, it aligns by character count, so headers (no emoji) appear shorter than data rows (with emoji):
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
| Status | Description | Comments |
|
|
11
|
+
| ------ | -------------- | -------- |
|
|
12
|
+
| ✅ | ✅ Complete | ❌ |
|
|
13
|
+
| 🚧 | 🚧 In Progress | ⚠️ |
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Renders misaligned:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
| Status | Description | Comments |
|
|
20
|
+
| ------ | -------------- | -------- |
|
|
21
|
+
| ✅ | ✅ Complete | ❌ | ← emoji takes 2 cols but counts as 1 char
|
|
22
|
+
| 🚧 | 🚧 In Progress | ⚠️ |
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## The Solution
|
|
26
|
+
|
|
27
|
+
Add ideographic spaces (U+3000) to cells with fewer emoji. Ideographic spaces also display as 2 columns, compensating for the width difference:
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
| Status | Description | Comments |
|
|
31
|
+
| -------- | -------------- | ---------- |
|
|
32
|
+
| ✅ | ✅ Complete | ❌ |
|
|
33
|
+
| 🚧 | 🚧 In Progress | ⚠️ |
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Now renders aligned:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
| Status | Description | Comments |
|
|
40
|
+
| ------ | -------------- | -------- |
|
|
41
|
+
| ✅ | ✅ Complete | ❌ |
|
|
42
|
+
| 🚧 | 🚧 In Progress | ⚠️ |
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# One-off via npx (no install)
|
|
49
|
+
npx fix-md-tables
|
|
50
|
+
|
|
51
|
+
# Or with bun
|
|
52
|
+
bunx fix-md-tables
|
|
53
|
+
|
|
54
|
+
# Install globally
|
|
55
|
+
npm install -g fix-md-tables
|
|
56
|
+
fix-md-tables
|
|
57
|
+
|
|
58
|
+
# As dev dependency in project
|
|
59
|
+
npm install -D fix-md-tables
|
|
60
|
+
pnpm add -D fix-md-tables
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### CLI
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Process default files (*.md, *.mdx in root + docs/)
|
|
69
|
+
fix-md-tables
|
|
70
|
+
|
|
71
|
+
# Process specific files
|
|
72
|
+
fix-md-tables README.md docs/guide.mdx
|
|
73
|
+
|
|
74
|
+
# Via npx
|
|
75
|
+
npx fix-md-tables
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Programmatic
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import { fixTableAlignment } from "fix-md-tables";
|
|
82
|
+
|
|
83
|
+
const markdown = `| Status | Description | Comments |
|
|
84
|
+
| ------ | -------------- | -------- |
|
|
85
|
+
| ✅ | ✅ Complete | ❌ |
|
|
86
|
+
| 🚧 | 🚧 In Progress | ⚠️ |`;
|
|
87
|
+
|
|
88
|
+
const fixed = fixTableAlignment(markdown);
|
|
89
|
+
console.log(fixed);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Prettier (recommended)
|
|
93
|
+
|
|
94
|
+
Run after Prettier to fix table alignment:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
prettier --write "*.md" "docs/**/*.md" && npx fix-md-tables
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or in your `package.json`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"scripts": {
|
|
105
|
+
"format": "prettier --write . && fix-md-tables"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or in a Makefile:
|
|
111
|
+
|
|
112
|
+
```makefile
|
|
113
|
+
docs-format:
|
|
114
|
+
@prettier --write "*.md" "docs/**/*.md"
|
|
115
|
+
@npx fix-md-tables
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## How It Works
|
|
119
|
+
|
|
120
|
+
1. Finds all markdown tables in content
|
|
121
|
+
2. For each table, calculates max emoji count per column (from data rows only)
|
|
122
|
+
3. Adds ideographic spaces to compensate:
|
|
123
|
+
- Header cells: adds spaces after text
|
|
124
|
+
- Separator cells: removes dashes, adds ideographic spaces
|
|
125
|
+
- Data cells with fewer emoji: adds compensating spaces
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
### `fixTableAlignment(content: string): string`
|
|
130
|
+
|
|
131
|
+
Main function to fix table alignment in markdown content.
|
|
132
|
+
|
|
133
|
+
### `countEmoji(str: string): number`
|
|
134
|
+
|
|
135
|
+
Count emoji characters in a string.
|
|
136
|
+
|
|
137
|
+
### `stripIdeographicSpaces(str: string): string`
|
|
138
|
+
|
|
139
|
+
Remove all ideographic spaces from a string.
|
|
140
|
+
|
|
141
|
+
### `processTable(tableRows: string[]): string[]`
|
|
142
|
+
|
|
143
|
+
Process a complete table, applying emoji compensation to all rows.
|
|
144
|
+
|
|
145
|
+
### `run(args?: string[]): number`
|
|
146
|
+
|
|
147
|
+
CLI runner. Returns count of fixed files.
|
|
148
|
+
|
|
149
|
+
## Supported Emoji Ranges
|
|
150
|
+
|
|
151
|
+
- U+1F300-U+1F9FF (Miscellaneous Symbols and Pictographs, Emoticons, etc.)
|
|
152
|
+
- U+2600-U+26FF (Miscellaneous Symbols)
|
|
153
|
+
- U+2700-U+27BF (Dingbats)
|
|
154
|
+
- U+231A-U+23FA (Miscellaneous Technical)
|
|
155
|
+
- U+2B50-U+2B55 (Miscellaneous Symbols and Arrows)
|
|
156
|
+
|
|
157
|
+
## File Types
|
|
158
|
+
|
|
159
|
+
Supports `.md` and `.mdx` files.
|
|
160
|
+
|
|
161
|
+
## Requirements
|
|
162
|
+
|
|
163
|
+
- Node.js >= 18
|
|
164
|
+
- Also works with Bun
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix markdown/MDX table alignment for emoji by adding ideographic spaces (U+3000).
|
|
3
|
+
*
|
|
4
|
+
* Problem: Emoji display as 2 columns but count as 1 char. Prettier aligns by
|
|
5
|
+
* char count, so headers (no emoji) appear shorter than data rows (with emoji).
|
|
6
|
+
*
|
|
7
|
+
* Solution: Add ideographic spaces (U+3000, also 2 cols wide) to cells with
|
|
8
|
+
* fewer emoji to compensate. 1 ideographic space = 1 emoji worth of width.
|
|
9
|
+
*
|
|
10
|
+
* Compatible with Node.js and Bun. Supports both .md and .mdx files.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
// === Constants ===
|
|
17
|
+
|
|
18
|
+
export const IDEOGRAPHIC_SPACE = "\u3000"; // Displays as 2 columns, like emoji
|
|
19
|
+
export const EMOJI_REGEX = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{231A}-\u{23FA}]|[\u{2B50}-\u{2B55}]/gu;
|
|
20
|
+
export const SEPARATOR_REGEX = /^(\s*)(:*)(-+)(:*)(\s*)$/;
|
|
21
|
+
export const TABLE_SEPARATOR_LINE_REGEX = /^\s*\|[\s:|\-\u3000]+\|\s*$/;
|
|
22
|
+
export const MARKDOWN_EXTENSIONS = [".md", ".mdx"];
|
|
23
|
+
|
|
24
|
+
// === Pure Helper Functions ===
|
|
25
|
+
|
|
26
|
+
/** Count emoji characters in a string */
|
|
27
|
+
export function countEmoji(str) {
|
|
28
|
+
return (str.match(EMOJI_REGEX) || []).length;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Remove all ideographic spaces from a string */
|
|
32
|
+
export function stripIdeographicSpaces(str) {
|
|
33
|
+
return str.replaceAll("\u3000", "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Check if a line is a markdown table separator */
|
|
37
|
+
export function isTableSeparatorLine(line) {
|
|
38
|
+
return TABLE_SEPARATOR_LINE_REGEX.test(line);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Check if a line is a table row (starts and ends with |) */
|
|
42
|
+
export function isTableRow(line) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Parse a table row into cells (preserving internal spacing) */
|
|
48
|
+
export function parseTableRow(row) {
|
|
49
|
+
const trimmed = row.trim();
|
|
50
|
+
const inner = trimmed.slice(1, -1); // Remove outer |
|
|
51
|
+
return inner.split("|");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Calculate max emoji count per column from data rows */
|
|
55
|
+
export function calculateMaxEmojiPerColumn(parsedRows, numCols) {
|
|
56
|
+
const maxEmojiPerCol = new Array(numCols).fill(0);
|
|
57
|
+
|
|
58
|
+
// Skip header (row 0) and separator (row 1), only look at data rows
|
|
59
|
+
for (let rowIdx = 2; rowIdx < parsedRows.length; rowIdx++) {
|
|
60
|
+
const row = parsedRows[rowIdx];
|
|
61
|
+
for (let col = 0; col < row.length; col++) {
|
|
62
|
+
const emojiCount = countEmoji(row[col] || "");
|
|
63
|
+
maxEmojiPerCol[col] = Math.max(maxEmojiPerCol[col], emojiCount);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return maxEmojiPerCol;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Compensate a separator cell by removing dashes and adding ideographic spaces */
|
|
71
|
+
export function compensateSeparatorCell(cell, compensation) {
|
|
72
|
+
const match = cell.match(SEPARATOR_REGEX);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return cell;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [, leadingSpace, leftColon, dashes, rightColon, trailingSpace] = match;
|
|
78
|
+
// Remove 2 dashes per ideographic space (both are 2 cols visually)
|
|
79
|
+
const dashesToRemove = compensation * 2;
|
|
80
|
+
const newDashes = dashes.length > dashesToRemove ? dashes.slice(0, -dashesToRemove) : dashes;
|
|
81
|
+
|
|
82
|
+
return leadingSpace + leftColon + newDashes + IDEOGRAPHIC_SPACE.repeat(compensation) + rightColon + trailingSpace;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Split cell into [leadingSpace, content, trailingSpace] without regex (ReDoS-safe) */
|
|
86
|
+
export function splitCellContent(cell) {
|
|
87
|
+
let leadingEnd = 0;
|
|
88
|
+
while (leadingEnd < cell.length && /\s/.test(cell[leadingEnd])) {
|
|
89
|
+
leadingEnd++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let trailingStart = cell.length;
|
|
93
|
+
while (trailingStart > leadingEnd && /\s/.test(cell[trailingStart - 1])) {
|
|
94
|
+
trailingStart--;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return [cell.slice(0, leadingEnd), cell.slice(leadingEnd, trailingStart), cell.slice(trailingStart)];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Compensate a regular cell by adding ideographic spaces before trailing whitespace */
|
|
101
|
+
export function compensateRegularCell(cell, compensation) {
|
|
102
|
+
const [leadingSpace, content, trailingSpace] = splitCellContent(cell);
|
|
103
|
+
return leadingSpace + content + IDEOGRAPHIC_SPACE.repeat(compensation) + trailingSpace;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Process a single cell, applying emoji compensation */
|
|
107
|
+
export function processCell(cell, col, isSeparatorRow, maxEmojiPerCol) {
|
|
108
|
+
const maxEmoji = maxEmojiPerCol[col] || 0;
|
|
109
|
+
const cellEmoji = countEmoji(cell);
|
|
110
|
+
const compensation = maxEmoji - cellEmoji;
|
|
111
|
+
|
|
112
|
+
if (compensation <= 0) {
|
|
113
|
+
return cell;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return isSeparatorRow ? compensateSeparatorCell(cell, compensation) : compensateRegularCell(cell, compensation);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Build a table row string from cells */
|
|
120
|
+
export function buildTableRow(cells) {
|
|
121
|
+
return "|" + cells.join("|") + "|";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Process a complete table, applying emoji compensation to all rows */
|
|
125
|
+
export function processTable(tableRows) {
|
|
126
|
+
// Strip existing ideographic spaces to start fresh
|
|
127
|
+
const cleanedRows = tableRows.map(stripIdeographicSpaces);
|
|
128
|
+
const parsedRows = cleanedRows.map(parseTableRow);
|
|
129
|
+
|
|
130
|
+
// Check if table has any emoji
|
|
131
|
+
const hasEmoji = cleanedRows.some((row) => countEmoji(row) > 0);
|
|
132
|
+
if (!hasEmoji || parsedRows.length < 2) {
|
|
133
|
+
return cleanedRows;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const numCols = Math.max(...parsedRows.map((r) => r.length));
|
|
137
|
+
const maxEmojiPerCol = calculateMaxEmojiPerColumn(parsedRows, numCols);
|
|
138
|
+
|
|
139
|
+
// Rebuild all rows with compensation
|
|
140
|
+
return parsedRows.map((row, rowIdx) => {
|
|
141
|
+
const isSeparatorRow = rowIdx === 1;
|
|
142
|
+
const processedCells = row.map((cell, col) => processCell(cell, col, isSeparatorRow, maxEmojiPerCol));
|
|
143
|
+
return buildTableRow(processedCells);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Check if a line starts a fenced code block */
|
|
148
|
+
function isCodeFenceStart(line) {
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
return trimmed.startsWith("```") || trimmed.startsWith("~~~");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Check if a line ends a fenced code block */
|
|
154
|
+
function isCodeFenceEnd(line, fence) {
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
return trimmed === fence || trimmed.startsWith(fence);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Main function to fix table alignment in markdown content */
|
|
160
|
+
export function fixTableAlignment(content) {
|
|
161
|
+
const lines = content.split("\n");
|
|
162
|
+
const result = [];
|
|
163
|
+
let i = 0;
|
|
164
|
+
let inCodeBlock = false;
|
|
165
|
+
let codeFence = "";
|
|
166
|
+
|
|
167
|
+
while (i < lines.length) {
|
|
168
|
+
const line = lines[i];
|
|
169
|
+
|
|
170
|
+
// Track fenced code blocks to skip tables inside them
|
|
171
|
+
if (!inCodeBlock && isCodeFenceStart(line)) {
|
|
172
|
+
inCodeBlock = true;
|
|
173
|
+
codeFence = line.trim().match(/^(`{3,}|~{3,})/)[1];
|
|
174
|
+
result.push(line);
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (inCodeBlock) {
|
|
180
|
+
result.push(line);
|
|
181
|
+
if (isCodeFenceEnd(line, codeFence)) {
|
|
182
|
+
inCodeBlock = false;
|
|
183
|
+
codeFence = "";
|
|
184
|
+
}
|
|
185
|
+
i++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Detect table start: line with | followed by separator line
|
|
190
|
+
if (line.includes("|") && i + 1 < lines.length && isTableSeparatorLine(lines[i + 1])) {
|
|
191
|
+
const tableRows = [];
|
|
192
|
+
|
|
193
|
+
// Collect all table rows
|
|
194
|
+
while (i < lines.length && isTableRow(lines[i])) {
|
|
195
|
+
tableRows.push(lines[i]);
|
|
196
|
+
i++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
result.push(...processTable(tableRows));
|
|
200
|
+
} else {
|
|
201
|
+
result.push(line);
|
|
202
|
+
i++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result.join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// === File System Functions ===
|
|
210
|
+
|
|
211
|
+
/** Check if a filename has a markdown extension */
|
|
212
|
+
export function isMarkdownFile(filename) {
|
|
213
|
+
return MARKDOWN_EXTENSIONS.some((ext) => filename.endsWith(ext));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Recursively find all markdown/MDX files in a directory */
|
|
217
|
+
export function findMarkdownFiles(dir, files = []) {
|
|
218
|
+
let entries;
|
|
219
|
+
try {
|
|
220
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
221
|
+
} catch {
|
|
222
|
+
return files; // Directory not readable, skip silently
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const fullPath = path.join(dir, entry.name);
|
|
227
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
228
|
+
findMarkdownFiles(fullPath, files);
|
|
229
|
+
} else if (entry.isFile() && isMarkdownFile(entry.name)) {
|
|
230
|
+
files.push(fullPath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return files;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Get default files to process (root .md/.mdx files + docs directory) */
|
|
238
|
+
export function getDefaultFiles(cwd) {
|
|
239
|
+
const files = [];
|
|
240
|
+
|
|
241
|
+
// Root markdown/MDX files
|
|
242
|
+
let rootEntries;
|
|
243
|
+
try {
|
|
244
|
+
rootEntries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
245
|
+
} catch {
|
|
246
|
+
return files; // Can't read cwd, return empty
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const entry of rootEntries) {
|
|
250
|
+
if (entry.isFile() && isMarkdownFile(entry.name)) {
|
|
251
|
+
files.push(path.join(cwd, entry.name));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Docs directory
|
|
256
|
+
const docsDir = path.join(cwd, "docs");
|
|
257
|
+
if (fs.existsSync(docsDir)) {
|
|
258
|
+
findMarkdownFiles(docsDir, files);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return files;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Process a single file, applying table alignment fixes */
|
|
265
|
+
export function processFile(filePath) {
|
|
266
|
+
try {
|
|
267
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
268
|
+
const fixed = fixTableAlignment(content);
|
|
269
|
+
|
|
270
|
+
if (content !== fixed) {
|
|
271
|
+
fs.writeFileSync(filePath, fixed, "utf8");
|
|
272
|
+
console.log(` ✓ Fixed: ${filePath}`);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.error(` ✗ Error processing ${filePath}: ${err.message}`);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Main CLI runner */
|
|
283
|
+
export function run(args = []) {
|
|
284
|
+
const files = args.length > 0 ? args : getDefaultFiles(process.cwd());
|
|
285
|
+
|
|
286
|
+
console.log(` Processing ${files.length} markdown/MDX file(s)...`);
|
|
287
|
+
|
|
288
|
+
let fixedCount = 0;
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
if (processFile(file)) {
|
|
291
|
+
fixedCount++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (fixedCount > 0) {
|
|
296
|
+
console.log(` Fixed ${fixedCount} file(s) with ideographic space compensation.`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return fixedCount;
|
|
300
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fix-md-tables",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fix markdown table alignment for emoji using ideographic spaces",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fix-md-tables": "./bin/fix-md-tables.mjs"
|
|
8
|
+
},
|
|
9
|
+
"main": "./lib/index.mjs",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./lib/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"lib"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"check": "pnpm run format && pnpm run lint && pnpm run test",
|
|
19
|
+
"format": "prettier --write . && node bin/fix-md-tables.mjs",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:fix": "eslint . --fix",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"markdown",
|
|
27
|
+
"table",
|
|
28
|
+
"emoji",
|
|
29
|
+
"alignment",
|
|
30
|
+
"prettier",
|
|
31
|
+
"formatter"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/yourname/fix-md-tables"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@eslint/js": "^9.39.2",
|
|
44
|
+
"eslint": "^9.39.2",
|
|
45
|
+
"globals": "^16.5.0",
|
|
46
|
+
"prettier": "^3.4.2",
|
|
47
|
+
"vitest": "^4.0.16"
|
|
48
|
+
},
|
|
49
|
+
"packageManager": "pnpm@9.15.4"
|
|
50
|
+
}
|