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 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]);
@@ -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
- // Create configuration directory
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
+
@@ -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 {
@@ -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
- while (currentDir !== path.parse(currentDir).root) {
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
- const startDir = targetPath ? (await fs.stat(targetPath)).isDirectory() ? targetPath : path.dirname(targetPath) : process.cwd();
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
- return { ...defaultConfig, ...loadedConfig, resolvedConfigPath: configPath };
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
+
@@ -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 placeholder = format === mdxFormat && node.body.length > 0 ? `\n${BODY_PLACEHOLDER}\n` : BODY_PLACEHOLDER;
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 (format === mdxFormat) result = "\n" + result + "\n";
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 || format === mdxFormat) && shouldEscapeText ? escapeHTML(body_node.text) : body_node.text;
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 || format === mdxFormat) && shouldEscapeInline ? escapeHTML(body_node.value) : body_node.value,
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 || format === mdxFormat)) {
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 }).trimEnd() + "\n";
105
- context = context.trim() ? context.trimEnd() + "\n" + rendered : context + rendered;
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
- context = context.trim() ? context.trimEnd() + "\n" + blockOutput : context + blockOutput;
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
- return result.trimEnd() + "\n";
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 || format === mdxFormat);
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 || format === mdxFormat) && shouldEscapeInline ? escapeHTML(node.value) : node.value,
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 || format === mdxFormat)) {
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 }).trimEnd() + "\n";
185
- output = output.trim() ? output.trimEnd() + "\n" + rendered : output + rendered;
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
  }
@@ -147,7 +147,7 @@ HTML.register(
147
147
 
148
148
  return this.tag("pre").body(code_element.body(code));
149
149
  },
150
- { escape: false, type: "AtBlock" }
150
+ { escape: false, type: ["AtBlock", "Block"] }
151
151
  );
152
152
  // List
153
153
  HTML.register(
@@ -74,7 +74,7 @@ MARKDOWN.register(
74
74
  },
75
75
  {
76
76
  escape: false,
77
- type: "AtBlock"
77
+ type: ["AtBlock", "Block"]
78
78
  }
79
79
  );
80
80
  // Link
@@ -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 }) => content, { escape: false, type: "Block" });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sommark",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "SomMark is a declarative, extensible markup language for structured content that can be converted to HTML, Markdown, MDX, JSON, and more.",
5
5
  "main": "index.js",
6
6
  "directories": {