@void/md 1.4.0 → 1.6.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.
@@ -1,5 +1,4 @@
1
- import { t as extractHeadings } from "./metadata-TdHmXD-i.mjs";
2
- import matter from "gray-matter";
1
+ import { r as parseFrontmatter, t as extractHeadings } from "./metadata-DDtNQG1v.mjs";
3
2
  import MarkdownExit from "markdown-exit";
4
3
  import anchor from "markdown-it-anchor";
5
4
  import footnote from "markdown-it-footnote";
@@ -9,7 +8,7 @@ import attrs from "markdown-it-attrs";
9
8
  import { full } from "markdown-it-emoji";
10
9
  import { bundledLanguages, createHighlighter, isSpecialLang } from "shiki";
11
10
  import container from "markdown-it-container";
12
- //#region src/plugins/containers.ts
11
+ // src/plugins/containers.ts
13
12
  function createContainer(md, klass, defaultTitle) {
14
13
  return [
15
14
  container,
@@ -36,8 +35,7 @@ function createContainer(md, klass, defaultTitle) {
36
35
  function containerPlugin(md) {
37
36
  md.use(...createContainer(md, "tip", "TIP")).use(...createContainer(md, "info", "INFO")).use(...createContainer(md, "warning", "WARNING")).use(...createContainer(md, "danger", "DANGER")).use(...createContainer(md, "details", "Details"));
38
37
  }
39
- //#endregion
40
- //#region src/plugins/github-alerts.ts
38
+ // src/plugins/github-alerts.ts
41
39
  const markerRE = /^\[!(TIP|NOTE|INFO|IMPORTANT|WARNING|CAUTION|DANGER)\]([^\n\r]*)/i;
42
40
  const titleMark = {
43
41
  tip: "TIP",
@@ -85,8 +83,7 @@ function gitHubAlertsPlugin(md) {
85
83
  return `<div class="${type} custom-block github-alert"><p class="custom-block-title">${title}</p>\n`;
86
84
  };
87
85
  }
88
- //#endregion
89
- //#region src/plugins/image.ts
86
+ // src/plugins/image.ts
90
87
  const EXTERNAL_URL_RE$1 = /^[a-z]+:/i;
91
88
  /**
92
89
  * Normalizes relative image paths and optionally adds loading="lazy".
@@ -105,8 +102,7 @@ function imagePlugin(md, options) {
105
102
  return imageRule(tokens, idx, mdOptions, env, self);
106
103
  };
107
104
  }
108
- //#endregion
109
- //#region src/plugins/line-numbers.ts
105
+ // src/plugins/line-numbers.ts
110
106
  /**
111
107
  * Adds a line number gutter to code blocks that opt in with `:line-numbers`.
112
108
  * `{=N}` sets the start line.
@@ -127,8 +123,7 @@ function lineNumberPlugin(md) {
127
123
  return rawCode.replace(/<\/div>$/, `${lineNumbersWrapper}</div>`).replace(/"(language-[^"]*?)"/, "\"$1 line-numbers-mode\"");
128
124
  };
129
125
  }
130
- //#endregion
131
- //#region src/plugins/link.ts
126
+ // src/plugins/link.ts
132
127
  const EXTERNAL_URL_RE = /^[a-z]+:/i;
133
128
  const EXTERNAL_ATTRS = {
134
129
  target: "_blank",
@@ -156,8 +151,7 @@ function linkPlugin(md) {
156
151
  return self.renderToken(tokens, idx, mdOptions);
157
152
  };
158
153
  }
159
- //#endregion
160
- //#region src/plugins/pre-wrapper.ts
154
+ // src/plugins/pre-wrapper.ts
161
155
  function extractLang(info) {
162
156
  return /^[a-zA-Z0-9-_]+/.exec(info)?.[0].replace(/-vue$/, "").replace(/^vue-html$/, "template").replace(/^ansi$/, "") || "";
163
157
  }
@@ -189,8 +183,7 @@ function preWrapperPlugin(md) {
189
183
  return `<div class="language-${lang}${active}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>` + highlighted + `</div>`;
190
184
  };
191
185
  }
192
- //#endregion
193
- //#region src/plugins/toc.ts
186
+ // src/plugins/toc.ts
194
187
  const TOC_RE = /^\[\[toc\]\]$/i;
195
188
  const TOC_LEVELS = [2, 3];
196
189
  function renderHeaders(headers) {
@@ -229,8 +222,7 @@ function tocPlugin(md) {
229
222
  return renderHeaders(extractHeadings(tokens).filter((heading) => TOC_LEVELS.includes(heading.depth)));
230
223
  };
231
224
  }
232
- //#endregion
233
- //#region src/compile.ts
225
+ // src/compile.ts
234
226
  let compilerPromise = null;
235
227
  async function getCompiler(options) {
236
228
  if (compilerPromise) return compilerPromise;
@@ -296,7 +288,7 @@ async function createCompiler(options) {
296
288
  return markdown;
297
289
  }
298
290
  async function compile(source, options) {
299
- const { data: frontmatter, content } = matter(source);
291
+ const { frontmatter, content } = parseFrontmatter(source);
300
292
  const compiler = await getCompiler(options);
301
293
  const env = {};
302
294
  const tokens = compiler.parse(content, env);
@@ -311,5 +303,4 @@ async function compile(source, options) {
311
303
  title
312
304
  };
313
305
  }
314
- //#endregion
315
306
  export { compile };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { FrontmatterContext, setFrontmatter, useFrontmatter } from "./runtime/index.mjs";
2
- import { n as MdPage } from "./plugin-ieYHo6qp.mjs";
2
+ import { n as MdPage } from "./plugin-CZ4JTnkJ.mjs";
3
3
  export { FrontmatterContext, type MdPage, setFrontmatter, useFrontmatter };
@@ -2,7 +2,7 @@ import matter from "gray-matter";
2
2
  import MarkdownExit from "markdown-exit";
3
3
  import anchor from "markdown-it-anchor";
4
4
  import footnote from "markdown-it-footnote";
5
- //#region src/metadata.ts
5
+ // src/metadata.ts
6
6
  let metadataCompiler = null;
7
7
  function getMetadataCompiler() {
8
8
  if (metadataCompiler) return metadataCompiler;
@@ -32,8 +32,20 @@ function extractHeadings(tokens) {
32
32
  }
33
33
  return headings;
34
34
  }
35
+ function parseFrontmatter(source) {
36
+ try {
37
+ const { data: frontmatter, content } = matter(source, {});
38
+ return {
39
+ frontmatter,
40
+ content
41
+ };
42
+ } catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ throw new Error(`Invalid frontmatter: ${message}`, { cause: error });
45
+ }
46
+ }
35
47
  function extractMetadata(source) {
36
- const { data: frontmatter, content } = matter(source);
48
+ const { frontmatter, content } = parseFrontmatter(source);
37
49
  const headings = extractHeadings(getMetadataCompiler().parse(content, {}));
38
50
  return {
39
51
  frontmatter,
@@ -41,5 +53,4 @@ function extractMetadata(source) {
41
53
  title: frontmatter.title ?? headings.find((heading) => heading.depth === 1)?.text ?? ""
42
54
  };
43
55
  }
44
- //#endregion
45
- export { extractMetadata as n, extractHeadings as t };
56
+ export { extractMetadata as n, parseFrontmatter as r, extractHeadings as t };
@@ -1,5 +1,4 @@
1
1
  import { Plugin } from "vite";
2
- //#region src/compile.d.ts
3
2
  type Heading = Readonly<{
4
3
  depth: number;
5
4
  slug: string;
@@ -25,8 +24,6 @@ type CompileOptions = Readonly<{
25
24
  langs?: ReadonlyArray<string>;
26
25
  };
27
26
  }>;
28
- //#endregion
29
- //#region src/extract-script.d.ts
30
27
  type IslandImport = {
31
28
  name: string;
32
29
  specifier: string;
@@ -40,8 +37,6 @@ type ScriptExtraction = Readonly<{
40
37
  islandImports: Array<IslandImport>;
41
38
  clientCode: string | null;
42
39
  }>;
43
- //#endregion
44
- //#region src/plugin.d.ts
45
40
  type MdPage = Readonly<{
46
41
  path: string;
47
42
  title: string;
@@ -56,5 +51,4 @@ type MarkdownOptions = Readonly<{
56
51
  shiki?: CompileOptions["shiki"];
57
52
  }>;
58
53
  declare function voidMarkdown(options?: MarkdownOptions): Array<Plugin>;
59
- //#endregion
60
54
  export { ScriptExtraction as a, IslandImport as i, MdPage as n, CompileOptions as o, voidMarkdown as r, CompileResult as s, MarkdownOptions as t };
package/dist/plugin.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ScriptExtraction, i as IslandImport, n as MdPage, o as CompileOptions, r as voidMarkdown, s as CompileResult, t as MarkdownOptions } from "./plugin-ieYHo6qp.mjs";
1
+ import { a as ScriptExtraction, i as IslandImport, n as MdPage, o as CompileOptions, r as voidMarkdown, s as CompileResult, t as MarkdownOptions } from "./plugin-CZ4JTnkJ.mjs";
2
2
  export { type CompileOptions, type CompileResult, type IslandImport, MarkdownOptions, MdPage, type ScriptExtraction, voidMarkdown };
package/dist/plugin.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { n as extractMetadata } from "./metadata-TdHmXD-i.mjs";
1
+ import { n as extractMetadata } from "./metadata-DDtNQG1v.mjs";
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "pathslash";
5
5
  import { glob } from "tinyglobby";
6
6
  import { normalizePath } from "vite";
7
7
  import { pageFileEnvironment, resolvePagesDir } from "void/pages";
8
8
  import { editIslandSource, findIslandImports } from "void/pages-islands-plugin";
9
- //#region src/emit-shared.ts
9
+ // src/emit-shared.ts
10
10
  const ISLAND_TAG_RE = /<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)\/\s*>|<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/\3\s*>/g;
11
11
  function findIslandTags(html, names) {
12
12
  const matches = [];
@@ -64,8 +64,7 @@ function buildRenderChunks(html, islandImports) {
64
64
  }
65
65
  return chunks;
66
66
  }
67
- //#endregion
68
- //#region src/emit-react.ts
67
+ // src/emit-react.ts
69
68
  function emitReactComponent(result, islandImports) {
70
69
  const chunks = buildRenderChunks(result.html, islandImports);
71
70
  let code = `import React from "react";\n`;
@@ -116,8 +115,7 @@ export default function MarkdownPage() {
116
115
  `;
117
116
  return code;
118
117
  }
119
- //#endregion
120
- //#region src/emit-solid.ts
118
+ // src/emit-solid.ts
121
119
  function emitSolidComponent(result, islandImports) {
122
120
  const chunks = buildRenderChunks(result.html, islandImports);
123
121
  const importAliasByName = /* @__PURE__ */ new Map();
@@ -165,8 +163,7 @@ export default function MarkdownPage() {
165
163
  `;
166
164
  return code;
167
165
  }
168
- //#endregion
169
- //#region src/emit-svelte.ts
166
+ // src/emit-svelte.ts
170
167
  function toSvelteLiteral(value) {
171
168
  return JSON.stringify(value).replace(/<\//g, "<\\/");
172
169
  }
@@ -213,8 +210,7 @@ ${body.join("\n")}
213
210
  `;
214
211
  return source;
215
212
  }
216
- //#endregion
217
- //#region src/emit-vue.ts
213
+ // src/emit-vue.ts
218
214
  function emitVueComponent(result, islandImports) {
219
215
  const chunks = buildRenderChunks(result.html, islandImports);
220
216
  let code = `import { defineComponent, h, createStaticVNode, provide } from "vue";\n`;
@@ -256,8 +252,7 @@ export default defineComponent({
256
252
  `;
257
253
  return code;
258
254
  }
259
- //#endregion
260
- //#region src/extract-script.ts
255
+ // src/extract-script.ts
261
256
  const SCRIPT_RE = /^<script\b[^>]*>([\s\S]*?)<\/script>\s*\n?/;
262
257
  function extractScript(source) {
263
258
  const match = source.match(SCRIPT_RE);
@@ -285,8 +280,7 @@ function extractScript(source) {
285
280
  }))).code.trim() : script) || null
286
281
  };
287
282
  }
288
- //#endregion
289
- //#region src/plugin.ts
283
+ // src/plugin.ts
290
284
  const MD_CLIENT_ENTRY_ID = "virtual:void-md-client";
291
285
  const ADAPTER_PLUGIN_NAMES = {
292
286
  "void-vue:pages": "vue",
@@ -305,26 +299,16 @@ function mdRouteName(file, env) {
305
299
  if (name === "index") return "";
306
300
  return name.endsWith("/index") ? name.slice(0, -6) : name;
307
301
  }
308
- function hasMdFilesWithScript(dir) {
309
- try {
310
- for (const entry of readdirSync(dir, {
311
- withFileTypes: true,
312
- recursive: true
313
- })) if (entry.isFile() && entry.name.endsWith(".md")) {
314
- const fullPath = join(entry.parentPath, entry.name);
315
- const content = readFileSync(fullPath, "utf-8");
316
- if (/^<script\b/m.test(content)) return true;
317
- }
318
- } catch {}
319
- return false;
302
+ /** The Markdown files that `environment` keeps, relative to `pagesDir`. */
303
+ async function listMdFiles(pagesDir, environment) {
304
+ return (await glob("**/*.md", { cwd: pagesDir })).filter((file) => {
305
+ const fileEnv = pageFileEnvironment(file);
306
+ return !fileEnv || fileEnv === environment;
307
+ });
320
308
  }
321
- function hasIslandFiles(dir) {
322
- try {
323
- for (const entry of readdirSync(dir, {
324
- withFileTypes: true,
325
- recursive: true
326
- })) if (entry.isFile() && /\.island\./.test(entry.name)) return true;
327
- } catch {}
309
+ /** Whether any page has client code. Without it, pages would load an empty client entry. */
310
+ async function hasMdClientCode(pagesDir, environment) {
311
+ for (const file of await listMdFiles(pagesDir, environment)) if (extractScript(readFileSync(join(pagesDir, file), "utf-8")).clientCode) return true;
328
312
  return false;
329
313
  }
330
314
  function voidMarkdown(options) {
@@ -332,31 +316,51 @@ function voidMarkdown(options) {
332
316
  let mdPages = [];
333
317
  let root = "";
334
318
  let pagesDir = "";
319
+ let logger;
335
320
  let mdEnvironment = "prod";
336
321
  const svelteModules = /* @__PURE__ */ new Map();
337
322
  const solidModules = /* @__PURE__ */ new Map();
338
323
  const clientScripts = /* @__PURE__ */ new Map();
324
+ const loggedPageErrors = /* @__PURE__ */ new Map();
339
325
  function createVirtualModuleId(prefix, importerId, source, extension) {
340
326
  return `${prefix}${Buffer.from(importerId).toString("base64url")}.${createHash("sha1").update(source).digest("hex").slice(0, 8)}.${extension}`;
341
327
  }
328
+ function createPageError(file, error) {
329
+ const message = error instanceof Error ? error.message : String(error);
330
+ return new Error(`void-md: ${relative(root, file)}: ${message}`, { cause: error });
331
+ }
342
332
  async function scanMdPages() {
343
333
  if (!existsSync(pagesDir)) return [];
344
- const liveFiles = (await glob("**/*.md", { cwd: pagesDir })).filter((file) => {
345
- const fileEnv = pageFileEnvironment(file);
346
- return !fileEnv || fileEnv === mdEnvironment;
347
- });
334
+ const liveFiles = await listMdFiles(pagesDir, mdEnvironment);
348
335
  return (await Promise.all(liveFiles.map(async (file) => {
349
- const { body, clientCode } = extractScript(readFileSync(join(pagesDir, file), "utf-8"));
350
- const result = extractMetadata(body);
351
- const componentId = stripPageSuffix(file) || "index";
352
- if (clientCode) clientScripts.set(componentId, clientCode);
353
- return {
354
- path: "/" + mdRouteName(file, pageFileEnvironment(file)),
355
- title: result.title,
356
- frontmatter: result.frontmatter,
357
- headings: result.headings
358
- };
359
- }))).sort((leftPage, rightPage) => leftPage.path.localeCompare(rightPage.path));
336
+ const path = "/" + mdRouteName(file, pageFileEnvironment(file));
337
+ const filePath = join(pagesDir, file);
338
+ try {
339
+ const { body, clientCode } = extractScript(readFileSync(filePath, "utf-8"));
340
+ const result = extractMetadata(body);
341
+ const componentId = stripPageSuffix(file) || "index";
342
+ if (clientCode) clientScripts.set(componentId, clientCode);
343
+ loggedPageErrors.delete(file);
344
+ return {
345
+ path,
346
+ title: result.title,
347
+ frontmatter: result.frontmatter,
348
+ headings: result.headings
349
+ };
350
+ } catch (error) {
351
+ const pageError = createPageError(filePath, error);
352
+ if (mdEnvironment === "prod") throw pageError;
353
+ if (error.code === "ENOENT") return;
354
+ if (loggedPageErrors.get(file) !== pageError.message) {
355
+ loggedPageErrors.set(file, pageError.message);
356
+ logger?.error(pageError.message, {
357
+ error: pageError,
358
+ timestamp: true
359
+ });
360
+ }
361
+ return mdPages.find((page) => page.path === path);
362
+ }
363
+ }))).filter((page) => page !== void 0).sort((leftPage, rightPage) => leftPage.path.localeCompare(rightPage.path));
360
364
  }
361
365
  return [{
362
366
  name: "void-md",
@@ -367,16 +371,17 @@ function voidMarkdown(options) {
367
371
  return clientScripts;
368
372
  }
369
373
  },
370
- config(config, env) {
374
+ async config(config, env) {
371
375
  mdEnvironment = env.command === "serve" && !env.isPreview ? "dev" : "prod";
372
376
  if (env.command === "serve") return;
373
377
  const configRoot = config.root || process.cwd();
374
378
  const pagesDir = resolvePagesDir(configRoot);
375
379
  if (!existsSync(pagesDir)) return;
376
- if (hasMdFilesWithScript(pagesDir) && hasIslandFiles(pagesDir)) return { environments: { client: { build: { rollupOptions: { input: { "md-client": MD_CLIENT_ENTRY_ID } } } } } };
380
+ if (await hasMdClientCode(pagesDir, mdEnvironment)) return { environments: { client: { build: { rollupOptions: { input: { "md-client": MD_CLIENT_ENTRY_ID } } } } } };
377
381
  },
378
382
  configResolved(config) {
379
383
  root = config.root;
384
+ logger = config.logger;
380
385
  pagesDir = resolvePagesDir(root);
381
386
  for (const plugin of config.plugins) {
382
387
  const match = ADAPTER_PLUGIN_NAMES[plugin.name];
@@ -398,7 +403,15 @@ function voidMarkdown(options) {
398
403
  const isInPagesDir = (file) => file === normalizedPagesDir || file.startsWith(normalizedPagesDir + "/");
399
404
  const handleMdChange = async (file) => {
400
405
  if (!file.endsWith(".md") || !isInPagesDir(normalizePath(file))) return;
401
- mdPages = await scanMdPages();
406
+ try {
407
+ mdPages = await scanMdPages();
408
+ } catch (error) {
409
+ server.config.logger.error(error instanceof Error ? error.message : String(error), {
410
+ error: error instanceof Error ? error : void 0,
411
+ timestamp: true
412
+ });
413
+ return;
414
+ }
402
415
  const mod = server.moduleGraph.getModuleById("\0void-md-pages");
403
416
  if (mod) server.moduleGraph.invalidateModule(mod);
404
417
  for (const [compId] of clientScripts) {
@@ -487,8 +500,10 @@ export function useFrontmatter() { return useContext(FrontmatterContext) ?? {};
487
500
  islandId
488
501
  };
489
502
  });
490
- const { compile } = await import("./compile-B0tLGZa4.mjs");
491
- const result = await compile(body, { shiki: options?.shiki });
503
+ const { compile } = await import("./compile-5IQ1Trxe.mjs");
504
+ const result = await compile(body, { shiki: options?.shiki }).catch((error) => {
505
+ throw createPageError(_id, error);
506
+ });
492
507
  if (adapter === "vue") return {
493
508
  code: emitVueComponent(result, islands),
494
509
  map: null
@@ -528,5 +543,4 @@ function generateMdClientEntry(scripts) {
528
543
  lines.push(`if (mdScriptId && mdScripts[mdScriptId]) mdScripts[mdScriptId]();`);
529
544
  return lines.join("\n");
530
545
  }
531
- //#endregion
532
546
  export { voidMarkdown };
@@ -1,4 +1,3 @@
1
- //#region src/runtime/index.d.ts
2
1
  export declare function useFrontmatter(): Record<string, unknown>;
3
2
  /**
4
3
  * @deprecated Internal to generated Markdown components and not available with
@@ -11,5 +10,4 @@ export declare const FrontmatterContext: Record<string, unknown> | null;
11
10
  * every framework adapter. Use `useFrontmatter()`. Removed in the next major
12
11
  * version.
13
12
  */
14
- export declare function setFrontmatter(_frontmatter: Record<string, unknown>): void;
15
- //#endregion
13
+ export declare function setFrontmatter(_frontmatter: Record<string, unknown>): void;
@@ -1,4 +1,4 @@
1
- //#region src/runtime/index.ts
1
+ // src/runtime/index.ts
2
2
  function useFrontmatter() {
3
3
  return {};
4
4
  }
@@ -14,5 +14,4 @@ const FrontmatterContext = null;
14
14
  * version.
15
15
  */
16
16
  function setFrontmatter(_frontmatter) {}
17
- //#endregion
18
17
  export { FrontmatterContext, setFrontmatter, useFrontmatter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/md",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Markdown pages with layouts and interactive islands for Void.",
5
5
  "homepage": "https://void.cloud",
6
6
  "bugs": {
@@ -51,14 +51,14 @@
51
51
  "tinyglobby": "^0.2.17"
52
52
  },
53
53
  "devDependencies": {
54
- "@types/node": "^26.6.5",
55
- "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
56
- "vite-plus": "1.3.0",
57
- "void": "1.4.0"
54
+ "@types/node": "^26.6.6",
55
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
56
+ "vite-plus": "1.4.0",
57
+ "void": "1.6.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "vite": "^8.0.0",
61
- "void": "1.4.0"
61
+ "void": "1.6.0"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "vp pack",