sommark 3.3.2 → 3.3.4
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/cli/cli.mjs +3 -1
- package/cli/commands/init.js +9 -21
- package/cli/commands/print.js +8 -2
- package/cli/helpers/transpile.js +5 -1
- package/core/helpers/config-loader.js +31 -5
- package/core/transpiler.js +24 -14
- package/mappers/languages/html.js +1 -1
- package/mappers/languages/markdown.js +1 -1
- package/mappers/languages/mdx.js +9 -3
- package/package.json +1 -1
package/cli/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { enableColor } from "../helpers/colorize.js";
|
|
3
|
+
|
|
3
4
|
import { getHelp } from "./commands/help.js";
|
|
4
5
|
import { printVersion, printHeader } from "./commands/version.js";
|
|
5
6
|
import { runBuild } from "./commands/build.js";
|
|
@@ -48,10 +49,11 @@ async function main() {
|
|
|
48
49
|
|
|
49
50
|
// 4. Init
|
|
50
51
|
if (command === "init") {
|
|
51
|
-
await runInit();
|
|
52
|
+
await runInit(args[1] === "--local" || args[1] === "-l");
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
|
|
55
57
|
// 5. Show
|
|
56
58
|
if (command === "show") {
|
|
57
59
|
await runShow(args[1]);
|
package/cli/commands/init.js
CHANGED
|
@@ -18,42 +18,29 @@ export function getConfigDir() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export async function runInit() {
|
|
21
|
+
export async function runInit(isLocal = false) {
|
|
22
22
|
try {
|
|
23
|
-
const configDir = getConfigDir();
|
|
23
|
+
const configDir = isLocal ? process.cwd() : getConfigDir();
|
|
24
24
|
const configFilePath = path.join(configDir, "smark.config.js");
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
31
|
-
|
|
32
|
-
// ======================================================
|
|
33
|
-
// Default configuration content
|
|
34
|
-
// ======================================================
|
|
26
|
+
if (!isLocal) {
|
|
27
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
28
|
+
}
|
|
35
29
|
|
|
36
30
|
const defaultConfigContent = `export default {
|
|
37
|
-
outputDir: "",
|
|
31
|
+
outputDir: "./",
|
|
38
32
|
outputFile: "output",
|
|
39
|
-
mappingFile:
|
|
33
|
+
mappingFile: null,
|
|
40
34
|
plugins: [],
|
|
41
35
|
priority: [],
|
|
36
|
+
excludePlugins: [],
|
|
42
37
|
};
|
|
43
38
|
`;
|
|
44
39
|
|
|
45
|
-
// ======================================================
|
|
46
|
-
// Check if config file already exists
|
|
47
|
-
// ======================================================
|
|
48
|
-
|
|
49
40
|
try {
|
|
50
41
|
await fs.access(configFilePath);
|
|
51
42
|
console.log(formatMessage(`<$yellow:Configuration already exists at:$> <$cyan:${configFilePath}$>`));
|
|
52
43
|
} catch {
|
|
53
|
-
// ======================================================
|
|
54
|
-
// Create default config file if it doesn't exist
|
|
55
|
-
// ======================================================
|
|
56
|
-
|
|
57
44
|
await fs.writeFile(configFilePath, defaultConfigContent, "utf-8");
|
|
58
45
|
console.log(formatMessage(`<$green:Initialized SomMark configuration at:$> <$cyan:${configFilePath}$>`));
|
|
59
46
|
}
|
|
@@ -63,3 +50,4 @@ export async function runInit() {
|
|
|
63
50
|
]);
|
|
64
51
|
}
|
|
65
52
|
}
|
|
53
|
+
|
package/cli/commands/print.js
CHANGED
|
@@ -41,9 +41,12 @@ export async function printLex(filePath) {
|
|
|
41
41
|
format: "text",
|
|
42
42
|
filename: absolutePath,
|
|
43
43
|
plugins: config.plugins,
|
|
44
|
-
priority: config.priority
|
|
44
|
+
priority: config.priority,
|
|
45
|
+
excludePlugins: config.excludePlugins,
|
|
46
|
+
includeDocument: config.includeDocument ?? true
|
|
45
47
|
});
|
|
46
48
|
|
|
49
|
+
|
|
47
50
|
const tokens = await smark.lex();
|
|
48
51
|
console.log(JSON.stringify(tokens, null, 2));
|
|
49
52
|
} else {
|
|
@@ -67,9 +70,12 @@ export async function printParse(filePath) {
|
|
|
67
70
|
format: "text",
|
|
68
71
|
filename: absolutePath,
|
|
69
72
|
plugins: config.plugins,
|
|
70
|
-
priority: config.priority
|
|
73
|
+
priority: config.priority,
|
|
74
|
+
excludePlugins: config.excludePlugins,
|
|
75
|
+
includeDocument: config.includeDocument ?? true
|
|
71
76
|
});
|
|
72
77
|
|
|
78
|
+
|
|
73
79
|
const ast = await smark.parse();
|
|
74
80
|
console.log(JSON.stringify(ast, null, 2));
|
|
75
81
|
} else {
|
package/cli/helpers/transpile.js
CHANGED
|
@@ -19,6 +19,8 @@ export async function transpile({ src, format, filename = null, mappingFile = ""
|
|
|
19
19
|
const config = await loadConfig(filename);
|
|
20
20
|
let finalMapper = mappingFile;
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
|
|
22
24
|
// 1. Resolve Mapping File
|
|
23
25
|
if (typeof mappingFile !== "object" || mappingFile === null) {
|
|
24
26
|
if (config.mappingFile) {
|
|
@@ -42,7 +44,9 @@ export async function transpile({ src, format, filename = null, mappingFile = ""
|
|
|
42
44
|
filename,
|
|
43
45
|
mapperFile: finalMapper,
|
|
44
46
|
plugins: config.plugins,
|
|
45
|
-
priority: config.priority
|
|
47
|
+
priority: config.priority,
|
|
48
|
+
excludePlugins: config.excludePlugins,
|
|
49
|
+
includeDocument: config.includeDocument ?? true
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
return await smark.transpile();
|
|
@@ -23,13 +23,16 @@ export function getConfigDir() {
|
|
|
23
23
|
* Recursively searches for smark.config.js up the directory tree starting from startDir.
|
|
24
24
|
*/
|
|
25
25
|
async function findConfig(startDir) {
|
|
26
|
-
let currentDir = startDir;
|
|
27
|
-
|
|
26
|
+
let currentDir = path.resolve(startDir);
|
|
27
|
+
const root = path.parse(currentDir).root;
|
|
28
|
+
|
|
29
|
+
while (true) {
|
|
28
30
|
const configPath = path.join(currentDir, CONFIG_FILE_NAME);
|
|
29
31
|
try {
|
|
30
32
|
await fs.access(configPath);
|
|
31
33
|
return configPath;
|
|
32
34
|
} catch {
|
|
35
|
+
if (currentDir === root) break;
|
|
33
36
|
currentDir = path.dirname(currentDir);
|
|
34
37
|
}
|
|
35
38
|
}
|
|
@@ -56,7 +59,17 @@ async function loadConfigFile(configPath) {
|
|
|
56
59
|
* Checks local directory, parent directories, and finally the global config directory.
|
|
57
60
|
*/
|
|
58
61
|
export async function findAndLoadConfig(targetPath) {
|
|
59
|
-
|
|
62
|
+
let startDir;
|
|
63
|
+
if (targetPath) {
|
|
64
|
+
try {
|
|
65
|
+
const stats = await fs.stat(targetPath);
|
|
66
|
+
startDir = stats.isDirectory() ? targetPath : path.dirname(targetPath);
|
|
67
|
+
} catch {
|
|
68
|
+
startDir = process.cwd();
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
startDir = process.cwd();
|
|
72
|
+
}
|
|
60
73
|
|
|
61
74
|
let configPath = await findConfig(startDir);
|
|
62
75
|
|
|
@@ -87,15 +100,28 @@ export async function findAndLoadConfig(targetPath) {
|
|
|
87
100
|
outputDir: startDir,
|
|
88
101
|
mappingFile: null,
|
|
89
102
|
plugins: [],
|
|
90
|
-
priority: []
|
|
103
|
+
priority: [],
|
|
104
|
+
excludePlugins: [],
|
|
105
|
+
includeDocument: true
|
|
91
106
|
};
|
|
92
107
|
|
|
93
108
|
if (configPath) {
|
|
94
109
|
const loadedConfig = await loadConfigFile(configPath);
|
|
95
110
|
if (loadedConfig) {
|
|
96
|
-
|
|
111
|
+
const finalConfig = { ...defaultConfig, ...loadedConfig, resolvedConfigPath: configPath };
|
|
112
|
+
|
|
113
|
+
// Ensure outputDir is resolved if it's relative in the config
|
|
114
|
+
if (loadedConfig.outputDir) {
|
|
115
|
+
const configDir = path.dirname(configPath);
|
|
116
|
+
finalConfig.outputDir = path.resolve(configDir, loadedConfig.outputDir);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return finalConfig;
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
return { ...defaultConfig, resolvedConfigPath: null };
|
|
101
124
|
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
package/core/transpiler.js
CHANGED
|
@@ -56,11 +56,12 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
56
56
|
// ========================================================================== //
|
|
57
57
|
// Always use placeholders for blocks to support wrapping //
|
|
58
58
|
// ========================================================================== //
|
|
59
|
-
const
|
|
59
|
+
const isParentBlock = format === mdxFormat && node.body.length > 1;
|
|
60
|
+
const placeholder = isParentBlock ? `\n${BODY_PLACEHOLDER}\n` : BODY_PLACEHOLDER;
|
|
60
61
|
const textContent = getNodeText(node);
|
|
61
62
|
|
|
62
63
|
result += target.render.call(mapper_file, { args: node.args, content: placeholder, textContent, ast: node });
|
|
63
|
-
if (
|
|
64
|
+
if (isParentBlock) result = "\n" + result + "\n";
|
|
64
65
|
|
|
65
66
|
// ========================================================================== //
|
|
66
67
|
// Process body nodes recursively //
|
|
@@ -70,7 +71,7 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
70
71
|
switch (body_node.type) {
|
|
71
72
|
case TEXT:
|
|
72
73
|
const shouldEscapeText = target.options?.escape !== false;
|
|
73
|
-
context += (format === htmlFormat
|
|
74
|
+
context += (format === htmlFormat) && shouldEscapeText ? escapeHTML(body_node.text) : body_node.text;
|
|
74
75
|
break;
|
|
75
76
|
|
|
76
77
|
case INLINE:
|
|
@@ -84,7 +85,7 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
84
85
|
context +=
|
|
85
86
|
inlineTarget.render.call(mapper_file, {
|
|
86
87
|
args: body_node.args.length > 0 ? body_node.args : [],
|
|
87
|
-
content: (format === htmlFormat
|
|
88
|
+
content: (format === htmlFormat) && shouldEscapeInline ? escapeHTML(body_node.value) : body_node.value,
|
|
88
89
|
ast: body_node
|
|
89
90
|
}) + (format === mdxFormat ? "\n" : "");
|
|
90
91
|
}
|
|
@@ -98,11 +99,12 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
98
99
|
if (atTarget) {
|
|
99
100
|
const shouldEscapeAt = atTarget.options?.escape !== false;
|
|
100
101
|
let content = body_node.content;
|
|
101
|
-
if (shouldEscapeAt && (format === htmlFormat
|
|
102
|
+
if (shouldEscapeAt && (format === htmlFormat)) {
|
|
102
103
|
content = escapeHTML(content);
|
|
103
104
|
}
|
|
104
|
-
const rendered = atTarget.render.call(mapper_file, { args: body_node.args, content, ast: body_node })
|
|
105
|
-
|
|
105
|
+
const rendered = atTarget.render.call(mapper_file, { args: body_node.args, content, ast: body_node });
|
|
106
|
+
const finalAtRendered = (format === mdxFormat) ? rendered : rendered.trimEnd() + "\n";
|
|
107
|
+
context = context.trim() ? context.trimEnd() + "\n" + finalAtRendered : context + finalAtRendered;
|
|
106
108
|
}
|
|
107
109
|
break;
|
|
108
110
|
|
|
@@ -112,7 +114,12 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
112
114
|
|
|
113
115
|
case BLOCK:
|
|
114
116
|
const blockOutput = await generateOutput(body_node, i, format, mapper_file);
|
|
115
|
-
|
|
117
|
+
const blockIsParent = format === mdxFormat && body_node.body.length > 1;
|
|
118
|
+
if (format === mdxFormat && !blockIsParent) {
|
|
119
|
+
context += blockOutput;
|
|
120
|
+
} else {
|
|
121
|
+
context = context.trim() ? context.trimEnd() + "\n" + blockOutput : context + blockOutput;
|
|
122
|
+
}
|
|
116
123
|
break;
|
|
117
124
|
}
|
|
118
125
|
}
|
|
@@ -138,7 +145,9 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
138
145
|
`<$yellow:Identifier$> <$blue:'${node.id}'$> <$yellow: is not found in mapping outputs$>{line}`
|
|
139
146
|
]);
|
|
140
147
|
}
|
|
141
|
-
|
|
148
|
+
const newline = (format === mdxFormat && node.body.length <= 1) ? "" : "\n";
|
|
149
|
+
const finalResult = (format === mdxFormat) ? result : result.trimEnd();
|
|
150
|
+
return finalResult + newline;
|
|
142
151
|
}
|
|
143
152
|
|
|
144
153
|
// ========================================================================== //
|
|
@@ -157,7 +166,7 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
|
|
|
157
166
|
output += mapperFile.comment(node.text);
|
|
158
167
|
break;
|
|
159
168
|
case TEXT:
|
|
160
|
-
const shouldEscapeText = (format === htmlFormat
|
|
169
|
+
const shouldEscapeText = (format === htmlFormat);
|
|
161
170
|
output += shouldEscapeText ? escapeHTML(node.text) : node.text;
|
|
162
171
|
break;
|
|
163
172
|
case INLINE:
|
|
@@ -167,7 +176,7 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
|
|
|
167
176
|
const shouldEscapeInline = inlineTarget.options?.escape !== false;
|
|
168
177
|
output += inlineTarget.render.call(mapperFile, {
|
|
169
178
|
args: node.args.length > 0 ? node.args : [],
|
|
170
|
-
content: (format === htmlFormat
|
|
179
|
+
content: (format === htmlFormat) && shouldEscapeInline ? escapeHTML(node.value) : node.value,
|
|
171
180
|
ast: node
|
|
172
181
|
}) + (format === mdxFormat ? "\n" : "");
|
|
173
182
|
}
|
|
@@ -178,11 +187,12 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
|
|
|
178
187
|
if (atTarget) {
|
|
179
188
|
const shouldEscapeAt = atTarget.options?.escape !== false;
|
|
180
189
|
let content = node.content;
|
|
181
|
-
if (shouldEscapeAt && (format === htmlFormat
|
|
190
|
+
if (shouldEscapeAt && (format === htmlFormat)) {
|
|
182
191
|
content = escapeHTML(content);
|
|
183
192
|
}
|
|
184
|
-
const rendered = atTarget.render.call(mapperFile, { args: node.args, content, ast: node })
|
|
185
|
-
|
|
193
|
+
const rendered = atTarget.render.call(mapperFile, { args: node.args, content, ast: node });
|
|
194
|
+
const finalAtRendered = (format === mdxFormat) ? rendered : rendered.trimEnd() + "\n";
|
|
195
|
+
output = output.trim() ? output.trimEnd() + "\n" + finalAtRendered : output + finalAtRendered;
|
|
186
196
|
}
|
|
187
197
|
break;
|
|
188
198
|
}
|
package/mappers/languages/mdx.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
2
|
import MARKDOWN from "./markdown.js";
|
|
3
3
|
import { HTML_TAGS } from "../../constants/html_tags.js";
|
|
4
|
-
import { HTML_PROPS } from "../../constants/html_props.js";
|
|
5
4
|
import { VOID_ELEMENTS } from "../../constants/void_elements.js";
|
|
6
|
-
import kebabize from "../../helpers/kebabize.js";
|
|
7
5
|
|
|
8
6
|
class MdxMapper extends Mapper {
|
|
9
7
|
constructor() {
|
|
@@ -90,7 +88,14 @@ const { tag } = MDX;
|
|
|
90
88
|
MDX.inherit(MARKDOWN);
|
|
91
89
|
|
|
92
90
|
// Block for raw MDX content (ESM, etc.)
|
|
93
|
-
MDX.register("mdx", ({ content }) =>
|
|
91
|
+
MDX.register("mdx", ({ content }) => {
|
|
92
|
+
// Clean up hidden characters
|
|
93
|
+
let clean = content.replace(/\u200B/g, "").trim();
|
|
94
|
+
// Remove leading semicolon to avoid MDX errors
|
|
95
|
+
if (clean.startsWith(";")) clean = clean.substring(1).trim();
|
|
96
|
+
// Add spacing around ESM blocks
|
|
97
|
+
return "\n" + clean + "\n\n";
|
|
98
|
+
}, { escape: false, type: ["AtBlock", "Block"] });
|
|
94
99
|
|
|
95
100
|
// Re-register HTML tags to use jsxProps
|
|
96
101
|
HTML_TAGS.forEach(tagName => {
|
|
@@ -122,6 +127,7 @@ HTML_TAGS.forEach(tagName => {
|
|
|
122
127
|
return element.body(content);
|
|
123
128
|
},
|
|
124
129
|
{
|
|
130
|
+
escape: false,
|
|
125
131
|
type: VOID_ELEMENTS.has(tagName) ? "Block" : ["Block", "Inline"],
|
|
126
132
|
rules: {
|
|
127
133
|
is_self_closing: VOID_ELEMENTS.has(tagName)
|
package/package.json
CHANGED