@wgtechlabs/mdd-engine 0.1.0-dev.8a1ef77 → 0.1.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/README.md CHANGED
@@ -4,7 +4,7 @@ The headless Markdown documentation compiler behind mdd. Give it a local project
4
4
 
5
5
  Built with TypeScript and Bun. Runs on Node.js 22, 24, and 26 without Bun. The default is the latest Node LTS, currently pinned to **24.21.0**.
6
6
 
7
- > Build Flow's default channels publish development, PR, and manual preview packages alongside regular releases to npm and GitHub Packages. See [build channels and releasing](docs/RELEASING.md).
7
+ > Initial implementation. Publishing to npm and GitHub Packages is configured for eligible pushes to `main`. Verify the first release before using registry installation instructions. See [releasing](docs/RELEASING.md).
8
8
 
9
9
  ## A documentation project
10
10
 
@@ -38,9 +38,7 @@ All settings are optional. Custom paths are relative to `mdd/config.json` and mu
38
38
 
39
39
  ## Compile without a website
40
40
 
41
- Install the regular package with `bun add @wgtechlabs/mdd-engine`. After a successful preview publication, use `bun add @wgtechlabs/mdd-engine@dev` for development builds or `bun add @wgtechlabs/mdd-engine@pr` for PRs targeting `dev`. Preview tags track the most recently published package in their channel; install an exact version to test a particular PR. You can also install a locally packed copy.
42
-
43
- In your Node project:
41
+ After installing a locally packed copy of `@wgtechlabs/mdd-engine` in your Node project:
44
42
 
45
43
  ```js
46
44
  import { compileProject } from '@wgtechlabs/mdd-engine';
@@ -61,8 +59,6 @@ if (!result.site) {
61
59
 
62
60
  `site` is absent whenever authoring errors exist. Diagnostics contain a stable code, severity, message, and source location where available. Unexpected filesystem failures reject the promise with context. Identical inputs produce identical output.
63
61
 
64
- Excessive Markdown nesting that exceeds the runtime's call stack produces a `CONTENT_TOO_DEEP` diagnostic identifying the source file.
65
-
66
62
  The result includes:
67
63
 
68
64
  - `pages`: source, route, public URL, title, description, article HTML, readable Markdown, headings, and navigation metadata.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolveLinks } from "./links.js";
3
- import { parseDocument, renderDocument, } from "./markdown.js";
3
+ import { parseDocument, renderDocument } from "./markdown.js";
4
4
  import { loadProject, relativeSource } from "./project.js";
5
5
  import { navigationFor, normalizeBasePath, publicUrl, routeFor, } from "./routes.js";
6
6
  const reserved = new Set([
@@ -13,18 +13,6 @@ const reserved = new Set([
13
13
  "robots.txt",
14
14
  "404.html",
15
15
  ]);
16
- function nestingDiagnostic(error, file) {
17
- // V8 reports this error for excessive nesting in both parsing and rendering.
18
- if (!(error instanceof RangeError) ||
19
- error.message !== "Maximum call stack size exceeded")
20
- throw error;
21
- return {
22
- severity: "error",
23
- code: "CONTENT_TOO_DEEP",
24
- message: "Markdown nesting is too deep; reduce nested blocks or formatting.",
25
- file,
26
- };
27
- }
28
16
  /** Compile a local checkout; never fetch repositories, execute author code, or emit files. */
29
17
  export async function compileProject(options) {
30
18
  const diagnostics = [];
@@ -70,15 +58,7 @@ export async function compileProject(options) {
70
58
  file: source,
71
59
  });
72
60
  routes.set(key, source);
73
- const text = await readFile(file, "utf8");
74
- let document;
75
- try {
76
- document = parseDocument(text, source, diagnostics);
77
- }
78
- catch (error) {
79
- diagnostics.push(nestingDiagnostic(error, source));
80
- return { diagnostics };
81
- }
61
+ const document = parseDocument(await readFile(file, "utf8"), source, diagnostics);
82
62
  sources.push({ file, relative, route, document });
83
63
  }
84
64
  const assets = await resolveLinks(sources, project, basePath, diagnostics);
@@ -87,14 +67,7 @@ export async function compileProject(options) {
87
67
  const pages = [];
88
68
  for (const source of sources) {
89
69
  const { document } = source;
90
- let rendered;
91
- try {
92
- rendered = await renderDocument(document);
93
- }
94
- catch (error) {
95
- diagnostics.push(nestingDiagnostic(error, relativeSource(project.root, source.file)));
96
- return { diagnostics };
97
- }
70
+ const rendered = await renderDocument(document);
98
71
  pages.push({
99
72
  source: relativeSource(project.root, source.file),
100
73
  route: source.route,
package/dist/links.js CHANGED
@@ -60,10 +60,8 @@ export async function resolveLinks(pages, project, basePath, diagnostics) {
60
60
  throw new AuthoringError("PATH_ESCAPE", "Local links must stay inside the content root");
61
61
  let target = !decoded ? page : byFile.get(targetFile);
62
62
  const extension = path.extname(decoded).toLowerCase();
63
- let asset = decoded.endsWith("/") ? undefined : assets.get(targetFile);
64
- let assetExists = asset !== undefined;
63
+ let assetExists = false;
65
64
  if (!target &&
66
- !assetExists &&
67
65
  !decoded.endsWith("/") &&
68
66
  assetExtensions.has(extension)) {
69
67
  try {
@@ -108,23 +106,21 @@ export async function resolveLinks(pages, project, basePath, diagnostics) {
108
106
  if (isImage && [".pdf", ".txt"].includes(extension)) {
109
107
  throw new AuthoringError("INVALID_IMAGE", "Images must reference a supported raster image");
110
108
  }
111
- if (!asset) {
112
- if (!assetExists)
113
- await safeFile(project.contentsRoot, targetFile);
114
- const source = relativeSource(project.root, targetFile);
115
- const destination = `_assets/${relativeSource(project.contentsRoot, targetFile)}`;
116
- const key = destination.normalize("NFC").toLowerCase();
117
- const existing = destinations.get(key);
118
- if (existing && existing !== source)
119
- throw new AuthoringError("ASSET_COLLISION", `Asset output collides with ${existing}`);
120
- destinations.set(key, source);
121
- asset = {
122
- source,
123
- destination,
124
- url: publicUrl(basePath, encodePath(destination)),
125
- };
126
- assets.set(targetFile, asset);
127
- }
109
+ if (!assetExists)
110
+ await safeFile(project.contentsRoot, targetFile);
111
+ const source = relativeSource(project.root, targetFile);
112
+ const destination = `_assets/${relativeSource(project.contentsRoot, targetFile)}`;
113
+ const key = destination.normalize("NFC").toLowerCase();
114
+ const existing = destinations.get(key);
115
+ if (existing && existing !== source)
116
+ throw new AuthoringError("ASSET_COLLISION", `Asset output collides with ${existing}`);
117
+ destinations.set(key, source);
118
+ const asset = {
119
+ source,
120
+ destination,
121
+ url: publicUrl(basePath, encodePath(destination)),
122
+ };
123
+ assets.set(source, asset);
128
124
  node.url =
129
125
  asset.url + query + (hash ? `#${encodeURIComponent(hash)}` : "");
130
126
  }
@@ -143,6 +139,15 @@ export async function resolveLinks(pages, project, basePath, diagnostics) {
143
139
  message: `Malformed URL encoding in '${url}'`,
144
140
  ...location,
145
141
  });
142
+ else if (error instanceof Error &&
143
+ "code" in error &&
144
+ error.code === "ENOENT")
145
+ diagnostics.push({
146
+ severity: "error",
147
+ code: "MISSING_ASSET",
148
+ message: `Asset not found: '${url}'`,
149
+ ...location,
150
+ });
146
151
  else
147
152
  throw new Error(`Unable to resolve '${url}' in ${location.file}`, {
148
153
  cause: error,
package/dist/markdown.js CHANGED
@@ -160,10 +160,6 @@ export function parseDocument(source, file, diagnostics) {
160
160
  visit(tree, (node, index, parent) => {
161
161
  if (node.type === "html") {
162
162
  report("RAW_HTML", "Raw HTML is not supported; use Markdown or an mdd component.", node);
163
- if (parent && index !== undefined) {
164
- parent.children.splice(index, 1);
165
- return [SKIP, index];
166
- }
167
163
  }
168
164
  if ((node.type === "link" ||
169
165
  node.type === "image" ||
@@ -242,8 +238,17 @@ export function parseDocument(source, file, diagnostics) {
242
238
  }
243
239
  export async function renderDocument(document) {
244
240
  const htmlTree = await htmlRenderer.run(document.tree);
241
+ const markdownTree = structuredClone(document.tree);
242
+ visit(markdownTree, (node, index, parent) => {
243
+ if ((node.type === "html" || node.type === "yaml") &&
244
+ parent &&
245
+ index !== undefined) {
246
+ parent.children.splice(index, 1);
247
+ return [SKIP, index];
248
+ }
249
+ });
245
250
  return {
246
251
  html: String(htmlRenderer.stringify(htmlTree)),
247
- markdown: String(markdownRenderer.stringify(document.tree)),
252
+ markdown: String(markdownRenderer.stringify(markdownTree)),
248
253
  };
249
254
  }
package/dist/project.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { CompileOptions, Diagnostic, Theme } from "./types.js";
2
2
  export interface Project {
3
3
  root: string;
4
+ mddRoot: string;
4
5
  contentsRoot: string;
6
+ themesRoot: string;
5
7
  title?: string;
6
8
  theme: Theme;
7
9
  files: string[];
package/dist/project.js CHANGED
@@ -236,7 +236,7 @@ export async function loadProject(options, diagnostics) {
236
236
  }
237
237
  if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
238
238
  return undefined;
239
- return { root, contentsRoot, title, theme, files };
239
+ return { root, mddRoot, contentsRoot, themesRoot, title, theme, files };
240
240
  }
241
241
  catch (error) {
242
242
  if (!(error instanceof AuthoringError))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wgtechlabs/mdd-engine",
3
- "version": "0.1.0-dev.8a1ef77",
3
+ "version": "0.1.0",
4
4
  "description": "Headless Markdown documentation compiler for mdd",
5
5
  "type": "module",
6
6
  "license": "MIT",