@wgtechlabs/mdd-engine 0.1.0-pr.cab0713 → 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 +2 -6
- package/dist/index.js +3 -30
- package/dist/links.js +25 -20
- package/dist/markdown.js +10 -5
- package/dist/project.d.ts +2 -0
- package/dist/project.js +1 -1
- package/package.json +1 -1
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
|
-
>
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
|
252
|
+
markdown: String(markdownRenderer.stringify(markdownTree)),
|
|
248
253
|
};
|
|
249
254
|
}
|
package/dist/project.d.ts
CHANGED
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))
|