@takazudo/mdx-formatter 0.1.2 → 0.3.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/dist/cli.js +6 -6
- package/dist/hybrid-formatter.d.ts +6 -0
- package/dist/hybrid-formatter.js +56 -3
- package/dist/load-config.d.ts +13 -0
- package/dist/load-config.js +25 -4
- package/dist/settings.js +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { program } from 'commander';
|
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { formatFile, checkFile } from './index.js';
|
|
7
|
+
import { loadFullConfig } from './load-config.js';
|
|
7
8
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
8
9
|
program
|
|
9
10
|
.name('mdx-formatter')
|
|
@@ -29,12 +30,11 @@ program.parse();
|
|
|
29
30
|
* Main CLI function
|
|
30
31
|
*/
|
|
31
32
|
async function main(patterns, options) {
|
|
32
|
-
const
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
33
|
+
const cliIgnorePatterns = options.ignore.split(',').map((p) => p.trim());
|
|
34
|
+
// Load config once: get both formatter settings and exclude patterns
|
|
35
|
+
const { settings, excludePatterns } = loadFullConfig(options.config ? { config: options.config } : {});
|
|
36
|
+
const ignorePatterns = [...new Set([...cliIgnorePatterns, ...excludePatterns])];
|
|
37
|
+
const formatOptions = { settings };
|
|
38
38
|
// Find all matching files
|
|
39
39
|
const files = [];
|
|
40
40
|
for (const pattern of patterns) {
|
|
@@ -53,6 +53,12 @@ export declare class HybridFormatter {
|
|
|
53
53
|
extractHtmlFromNode(node: MdxJsxElement): string | null;
|
|
54
54
|
collectJsxIndentOperations(operations: FormatterOperation[]): void;
|
|
55
55
|
collectBlockJsxEmptyLineOperations(operations: FormatterOperation[]): void;
|
|
56
|
+
/**
|
|
57
|
+
* Pre-process YAML text to fix values that would cause parsing failures
|
|
58
|
+
* or silent data corruption. Detects unquoted values containing special
|
|
59
|
+
* YAML characters and wraps them in double quotes.
|
|
60
|
+
*/
|
|
61
|
+
preprocessYamlForParsing(yamlText: string): string;
|
|
56
62
|
collectYamlFormatOperations(operations: FormatterOperation[]): void;
|
|
57
63
|
getLineAtPosition(charPos: number): number;
|
|
58
64
|
applyOperation(lines: string[], op: FormatterOperation): void;
|
package/dist/hybrid-formatter.js
CHANGED
|
@@ -893,16 +893,69 @@ export class HybridFormatter {
|
|
|
893
893
|
}
|
|
894
894
|
});
|
|
895
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Pre-process YAML text to fix values that would cause parsing failures
|
|
898
|
+
* or silent data corruption. Detects unquoted values containing special
|
|
899
|
+
* YAML characters and wraps them in double quotes.
|
|
900
|
+
*/
|
|
901
|
+
preprocessYamlForParsing(yamlText) {
|
|
902
|
+
const lines = yamlText.split('\n');
|
|
903
|
+
const result = [];
|
|
904
|
+
for (const line of lines) {
|
|
905
|
+
// Match a YAML mapping entry: optional indent, key, colon, space, value
|
|
906
|
+
// Keys must start with a word char, may contain word chars, dots, hyphens
|
|
907
|
+
const match = line.match(/^(\s*)([\w][\w.-]*):\s+(.+)$/);
|
|
908
|
+
if (match) {
|
|
909
|
+
const [, indent, key, value] = match;
|
|
910
|
+
const trimmedValue = value.trim();
|
|
911
|
+
// Skip if already quoted
|
|
912
|
+
if ((trimmedValue.startsWith('"') && trimmedValue.endsWith('"')) ||
|
|
913
|
+
(trimmedValue.startsWith("'") && trimmedValue.endsWith("'"))) {
|
|
914
|
+
result.push(line);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
// Skip if the value is a flow sequence [...] or flow mapping {...}
|
|
918
|
+
if ((trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) ||
|
|
919
|
+
(trimmedValue.startsWith('{') && trimmedValue.endsWith('}'))) {
|
|
920
|
+
result.push(line);
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
// Skip block scalar indicators (>, |, >-, |-, >+, |+)
|
|
924
|
+
if (/^[|>][-+]?$/.test(trimmedValue)) {
|
|
925
|
+
result.push(line);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
const needsQuoting = trimmedValue.includes(': ') ||
|
|
929
|
+
trimmedValue.includes(' #') ||
|
|
930
|
+
/^[!&*%@`]/.test(trimmedValue);
|
|
931
|
+
if (needsQuoting) {
|
|
932
|
+
const escaped = trimmedValue.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
933
|
+
result.push(`${indent}${key}: "${escaped}"`);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
result.push(line);
|
|
938
|
+
}
|
|
939
|
+
return result.join('\n');
|
|
940
|
+
}
|
|
896
941
|
collectYamlFormatOperations(operations) {
|
|
897
942
|
const yamlSettings = this.settings.formatYamlFrontmatter;
|
|
898
943
|
visit(this.ast, (node) => {
|
|
899
944
|
if (node.type === 'yaml' && node.position) {
|
|
900
945
|
const yamlNode = node;
|
|
901
946
|
try {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
947
|
+
let yamlToParse = yamlNode.value;
|
|
948
|
+
// Pre-process YAML to fix unsafe values (e.g., unquoted colons)
|
|
949
|
+
if (yamlSettings.fixUnsafeValues !== false) {
|
|
950
|
+
yamlToParse = this.preprocessYamlForParsing(yamlToParse);
|
|
951
|
+
}
|
|
952
|
+
// Parse the YAML content using JSON_SCHEMA to prevent silent
|
|
953
|
+
// data corruption (e.g., dates parsed as Date objects, octals)
|
|
954
|
+
const parsed = yaml.load(yamlToParse, { schema: yaml.JSON_SCHEMA });
|
|
955
|
+
// Format it back with proper formatting using JSON_SCHEMA
|
|
956
|
+
// to preserve string representations (dates, etc.)
|
|
905
957
|
const formatted = yaml.dump(parsed, {
|
|
958
|
+
schema: yaml.JSON_SCHEMA,
|
|
906
959
|
indent: yamlSettings.indent || 2,
|
|
907
960
|
lineWidth: yamlSettings.lineWidth || 100,
|
|
908
961
|
quotingType: (yamlSettings.quotingType || '"'),
|
package/dist/load-config.d.ts
CHANGED
|
@@ -7,6 +7,19 @@
|
|
|
7
7
|
* 3. Programmatic options (passed to format())
|
|
8
8
|
*/
|
|
9
9
|
import type { FormatterSettings, FormatOptions } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Load config file once and return both formatter settings and exclude patterns.
|
|
12
|
+
* This avoids reading the config file multiple times in the CLI path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadFullConfig(options?: FormatOptions): {
|
|
15
|
+
settings: FormatterSettings;
|
|
16
|
+
excludePatterns: string[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Load exclude patterns from config file.
|
|
20
|
+
* These are glob patterns for files the CLI should skip.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadExcludePatterns(configPath?: string): string[];
|
|
10
23
|
/**
|
|
11
24
|
* Load and merge all configuration layers
|
|
12
25
|
*/
|
package/dist/load-config.js
CHANGED
|
@@ -53,19 +53,40 @@ function findConfigFile(configPath) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Load and
|
|
56
|
+
* Load config file once and return both formatter settings and exclude patterns.
|
|
57
|
+
* This avoids reading the config file multiple times in the CLI path.
|
|
57
58
|
*/
|
|
58
|
-
export function
|
|
59
|
+
export function loadFullConfig(options = {}) {
|
|
59
60
|
// Layer 1: Built-in defaults
|
|
60
61
|
let settings = deepCloneSettings(formatterSettings);
|
|
61
62
|
// Layer 2: Config file
|
|
62
63
|
const fileConfig = findConfigFile(options.config);
|
|
64
|
+
// Extract exclude patterns (CLI concern, not formatter settings)
|
|
65
|
+
let excludePatterns = [];
|
|
63
66
|
if (fileConfig) {
|
|
64
|
-
|
|
67
|
+
if (Array.isArray(fileConfig.exclude)) {
|
|
68
|
+
excludePatterns = fileConfig.exclude.filter((item) => typeof item === 'string');
|
|
69
|
+
}
|
|
70
|
+
const formatterConfig = { ...fileConfig };
|
|
71
|
+
delete formatterConfig.exclude;
|
|
72
|
+
settings = mergeSettings(settings, formatterConfig);
|
|
65
73
|
}
|
|
66
74
|
// Layer 3: Programmatic options
|
|
67
75
|
if (options.settings) {
|
|
68
76
|
settings = mergeSettings(settings, options.settings);
|
|
69
77
|
}
|
|
70
|
-
return settings;
|
|
78
|
+
return { settings, excludePatterns };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Load exclude patterns from config file.
|
|
82
|
+
* These are glob patterns for files the CLI should skip.
|
|
83
|
+
*/
|
|
84
|
+
export function loadExcludePatterns(configPath) {
|
|
85
|
+
return loadFullConfig({ config: configPath }).excludePatterns;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Load and merge all configuration layers
|
|
89
|
+
*/
|
|
90
|
+
export function loadConfig(options = {}) {
|
|
91
|
+
return loadFullConfig(options).settings;
|
|
71
92
|
}
|
package/dist/settings.js
CHANGED
|
@@ -60,6 +60,7 @@ export const formatterSettings = {
|
|
|
60
60
|
quotingType: '"', // Quote type for strings that need quoting: '"' or "'"
|
|
61
61
|
forceQuotes: false, // Force quotes on all string values
|
|
62
62
|
noCompatMode: true, // Use YAML 1.2 spec (not 1.1)
|
|
63
|
+
fixUnsafeValues: true, // Pre-process YAML to quote values containing special characters like colons
|
|
63
64
|
},
|
|
64
65
|
// Rule 8: Preserve Docusaurus admonitions
|
|
65
66
|
preserveAdmonitions: {
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takazudo/mdx-formatter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AST-based markdown and MDX formatter with Japanese text support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"lint:fix": "eslint --fix .",
|
|
34
34
|
"check": "prettier --check . && eslint .",
|
|
35
35
|
"check:fix": "prettier --write . && eslint --fix .",
|
|
36
|
+
"b4push": "./scripts/run-b4push.sh",
|
|
37
|
+
"doc:start": "pnpm --dir doc start",
|
|
36
38
|
"prepare": "husky",
|
|
37
39
|
"prepublishOnly": "tsc && vitest run"
|
|
38
40
|
},
|