sommark 3.3.3 → 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
+
@@ -71,7 +71,7 @@ async function generateOutput(ast, i, format, mapper_file) {
71
71
  switch (body_node.type) {
72
72
  case TEXT:
73
73
  const shouldEscapeText = target.options?.escape !== false;
74
- 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;
75
75
  break;
76
76
 
77
77
  case INLINE:
@@ -85,7 +85,7 @@ async function generateOutput(ast, i, format, mapper_file) {
85
85
  context +=
86
86
  inlineTarget.render.call(mapper_file, {
87
87
  args: body_node.args.length > 0 ? body_node.args : [],
88
- 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,
89
89
  ast: body_node
90
90
  }) + (format === mdxFormat ? "\n" : "");
91
91
  }
@@ -99,11 +99,12 @@ async function generateOutput(ast, i, format, mapper_file) {
99
99
  if (atTarget) {
100
100
  const shouldEscapeAt = atTarget.options?.escape !== false;
101
101
  let content = body_node.content;
102
- if (shouldEscapeAt && (format === htmlFormat || format === mdxFormat)) {
102
+ if (shouldEscapeAt && (format === htmlFormat)) {
103
103
  content = escapeHTML(content);
104
104
  }
105
- const rendered = atTarget.render.call(mapper_file, { args: body_node.args, content, ast: body_node }).trimEnd() + "\n";
106
- 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;
107
108
  }
108
109
  break;
109
110
 
@@ -145,7 +146,8 @@ async function generateOutput(ast, i, format, mapper_file) {
145
146
  ]);
146
147
  }
147
148
  const newline = (format === mdxFormat && node.body.length <= 1) ? "" : "\n";
148
- return result.trimEnd() + newline;
149
+ const finalResult = (format === mdxFormat) ? result : result.trimEnd();
150
+ return finalResult + newline;
149
151
  }
150
152
 
151
153
  // ========================================================================== //
@@ -164,7 +166,7 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
164
166
  output += mapperFile.comment(node.text);
165
167
  break;
166
168
  case TEXT:
167
- const shouldEscapeText = (format === htmlFormat || format === mdxFormat);
169
+ const shouldEscapeText = (format === htmlFormat);
168
170
  output += shouldEscapeText ? escapeHTML(node.text) : node.text;
169
171
  break;
170
172
  case INLINE:
@@ -174,7 +176,7 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
174
176
  const shouldEscapeInline = inlineTarget.options?.escape !== false;
175
177
  output += inlineTarget.render.call(mapperFile, {
176
178
  args: node.args.length > 0 ? node.args : [],
177
- content: (format === htmlFormat || format === mdxFormat) && shouldEscapeInline ? escapeHTML(node.value) : node.value,
179
+ content: (format === htmlFormat) && shouldEscapeInline ? escapeHTML(node.value) : node.value,
178
180
  ast: node
179
181
  }) + (format === mdxFormat ? "\n" : "");
180
182
  }
@@ -185,11 +187,12 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
185
187
  if (atTarget) {
186
188
  const shouldEscapeAt = atTarget.options?.escape !== false;
187
189
  let content = node.content;
188
- if (shouldEscapeAt && (format === htmlFormat || format === mdxFormat)) {
190
+ if (shouldEscapeAt && (format === htmlFormat)) {
189
191
  content = escapeHTML(content);
190
192
  }
191
- const rendered = atTarget.render.call(mapperFile, { args: node.args, content, ast: node }).trimEnd() + "\n";
192
- 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;
193
196
  }
194
197
  break;
195
198
  }
@@ -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: ["AtBlock", "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.3",
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": {