md4x 0.0.1

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/build/.gitkeep ADDED
File without changes
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/lib/cli.mjs ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from "node:util";
4
+ import { readFileSync, writeFileSync } from "node:fs";
5
+ import { renderToHtml, renderToAnsi, renderToJson } from "./napi.mjs";
6
+
7
+ const { values, positionals } = parseArgs({
8
+ allowPositionals: true,
9
+ options: {
10
+ output: { type: "string", short: "o" },
11
+ format: {
12
+ type: "string",
13
+ short: "t",
14
+ default: process.stdout.isTTY ? "ansi" : "html",
15
+ },
16
+ "full-html": { type: "boolean", short: "f", default: false },
17
+ "html-title": { type: "string" },
18
+ "html-css": { type: "string" },
19
+ stat: { type: "boolean", short: "s", default: false },
20
+ help: { type: "boolean", short: "h", default: false },
21
+ version: { type: "boolean", short: "v", default: false },
22
+ },
23
+ });
24
+
25
+ function usage() {
26
+ process.stderr.write(
27
+ `Usage: md4x [OPTION]... [FILE]
28
+ Convert input FILE (or standard input) in Markdown format.
29
+
30
+ General options:
31
+ -o --output=FILE Output file (default is standard output)
32
+ -t, --format=FORMAT Output format: html, json, ansi (default: ansi for TTY, html otherwise)
33
+ -s, --stat Measure time of input parsing
34
+ -h, --help Display this help and exit
35
+ -v, --version Display version and exit
36
+
37
+ HTML output options:
38
+ -f, --full-html Generate full HTML document, including header
39
+ --html-title=TITLE Sets the title of the document
40
+ --html-css=URL In full HTML mode add a css link
41
+ `,
42
+ );
43
+ }
44
+
45
+ if (values.help) {
46
+ usage();
47
+ process.exit(0);
48
+ }
49
+
50
+ if (values.version) {
51
+ const { version } = JSON.parse(
52
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
53
+ );
54
+ process.stdout.write(`${version}\n`);
55
+ process.exit(0);
56
+ }
57
+
58
+ const format = values.format;
59
+ if (!["html", "json", "ansi"].includes(format)) {
60
+ process.stderr.write(`Unknown format: ${format}\n`);
61
+ process.stderr.write("Supported formats: html, json, ansi\n");
62
+ process.exit(1);
63
+ }
64
+
65
+ const inputPath = positionals[0];
66
+ let input;
67
+ if (!inputPath || inputPath === "-") {
68
+ if (process.stdin.isTTY) {
69
+ usage();
70
+ process.exit(1);
71
+ }
72
+ try {
73
+ input = readFileSync(0, "utf8");
74
+ } catch {
75
+ usage();
76
+ process.exit(1);
77
+ }
78
+ } else {
79
+ try {
80
+ input = readFileSync(inputPath, "utf8");
81
+ } catch {
82
+ process.stderr.write(`Cannot open ${inputPath}.\n`);
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ const t0 = values.stat ? performance.now() : 0;
88
+
89
+ let output;
90
+ switch (format) {
91
+ case "html":
92
+ output = renderToHtml(input);
93
+ break;
94
+ case "json":
95
+ output = JSON.stringify(renderToJson(input));
96
+ break;
97
+ case "ansi":
98
+ output = renderToAnsi(input);
99
+ break;
100
+ }
101
+
102
+ if (values.stat) {
103
+ const elapsed = performance.now() - t0;
104
+ if (elapsed < 1000) {
105
+ process.stderr.write(`Time spent on parsing: ${elapsed.toFixed(2)} ms.\n`);
106
+ } else {
107
+ process.stderr.write(
108
+ `Time spent on parsing: ${(elapsed / 1000).toFixed(3)} s.\n`,
109
+ );
110
+ }
111
+ }
112
+
113
+ let result = "";
114
+ if (values["full-html"] && format === "html") {
115
+ result += "<!DOCTYPE html>\n<html>\n<head>\n";
116
+ result += `<title>${values["html-title"] || ""}</title>\n`;
117
+ result += '<meta name="generator" content="md4x">\n';
118
+ result += '<meta charset="UTF-8">\n';
119
+ if (values["html-css"]) {
120
+ result += `<link rel="stylesheet" href="${values["html-css"]}">\n`;
121
+ }
122
+ result += "</head>\n<body>\n";
123
+ result += output;
124
+ result += "</body>\n</html>\n";
125
+ } else {
126
+ result = output;
127
+ }
128
+
129
+ if (values.output && values.output !== "-") {
130
+ try {
131
+ writeFileSync(values.output, result);
132
+ } catch {
133
+ process.stderr.write(`Cannot open ${values.output}.\n`);
134
+ process.exit(1);
135
+ }
136
+ } else {
137
+ process.stdout.write(result);
138
+ }
package/lib/napi.d.mts ADDED
@@ -0,0 +1,7 @@
1
+ import type { ComarkTree } from "./types.js";
2
+
3
+ export type { ComarkTree, ComarkNode, ComarkElement, ComarkText, ComarkElementAttributes } from "./types.js";
4
+
5
+ export declare function renderToHtml(input: string): string;
6
+ export declare function renderToJson(input: string): ComarkTree;
7
+ export declare function renderToAnsi(input: string): string;
package/lib/napi.mjs ADDED
@@ -0,0 +1,27 @@
1
+ import { createRequire } from "node:module";
2
+ import { arch, platform } from "node:process";
3
+
4
+ const require = createRequire(import.meta.url);
5
+
6
+ function loadBinding() {
7
+ const name = `md4x.${platform}-${arch}`;
8
+ try {
9
+ return require(`../build/${name}.node`);
10
+ } catch {
11
+ return require("../build/md4x.node");
12
+ }
13
+ }
14
+
15
+ const binding = loadBinding();
16
+
17
+ export function renderToHtml(input) {
18
+ return binding.renderToHtml(input);
19
+ }
20
+
21
+ export function renderToJson(input) {
22
+ return JSON.parse(binding.renderToJson(input));
23
+ }
24
+
25
+ export function renderToAnsi(input) {
26
+ return binding.renderToAnsi(input);
27
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type ComarkTree = {
2
+ type: "comark";
3
+ value: ComarkNode[];
4
+ };
5
+
6
+ export type ComarkNode = ComarkElement | ComarkText;
7
+
8
+ export type ComarkText = string;
9
+
10
+ export type ComarkElement = [
11
+ tag: string,
12
+ props: ComarkElementAttributes,
13
+ ...children: ComarkNode[],
14
+ ];
15
+
16
+ export type ComarkElementAttributes = {
17
+ [key: string]: unknown;
18
+ };
package/lib/wasm.d.mts ADDED
@@ -0,0 +1,8 @@
1
+ import type { ComarkTree } from "./types.js";
2
+
3
+ export type { ComarkTree, ComarkNode, ComarkElement, ComarkText, ComarkElementAttributes } from "./types.js";
4
+
5
+ export declare function initWasm(input?: ArrayBuffer | Uint8Array | WebAssembly.Module | Response | Promise<Response>): Promise<void>;
6
+ export declare function renderToHtml(input: string): string;
7
+ export declare function renderToJson(input: string): ComarkTree;
8
+ export declare function renderToAnsi(input: string): string;
package/lib/wasm.mjs ADDED
@@ -0,0 +1,82 @@
1
+ let _instance;
2
+
3
+ function getExports() {
4
+ if (!_instance) {
5
+ throw new Error("md4x: WASM not initialized. Call `await initWasm()` first.");
6
+ }
7
+ return _instance.exports;
8
+ }
9
+
10
+ export async function initWasm(input) {
11
+ if (_instance) return;
12
+ let bytes;
13
+ if (input instanceof ArrayBuffer || input instanceof Uint8Array) {
14
+ bytes = input;
15
+ } else if (input instanceof WebAssembly.Module) {
16
+ const { instance } = await WebAssembly.instantiate(input, {
17
+ wasi_snapshot_preview1: wasiStub,
18
+ });
19
+ _instance = instance;
20
+ return;
21
+ } else if (input instanceof Response || (typeof input === "object" && typeof input.then === "function")) {
22
+ const response = await input;
23
+ if (response instanceof Response) {
24
+ bytes = await response.arrayBuffer();
25
+ } else {
26
+ bytes = response;
27
+ }
28
+ } else {
29
+ const fsp = globalThis.process?.getBuiltinModule?.("fs/promises")
30
+ if (fsp) {
31
+ const wasmPath = new URL("../build/md4x.wasm", import.meta.url);
32
+ bytes = await fsp.readFile(wasmPath);
33
+ } else {
34
+ bytes = await fetch(await import("../build/md4x.wasm?url").then(m => m.default)).then(r => r.arrayBuffer())
35
+ }
36
+ }
37
+ const { instance } = await WebAssembly.instantiate(bytes, {
38
+ wasi_snapshot_preview1: wasiStub,
39
+ });
40
+ _instance = instance;
41
+ }
42
+
43
+ export function renderToHtml(input) {
44
+ return render(getExports(), getExports().md4x_to_html, input);
45
+ }
46
+
47
+ export function renderToJson(input) {
48
+ return JSON.parse(render(getExports(), getExports().md4x_to_json, input));
49
+ }
50
+
51
+ export function renderToAnsi(input) {
52
+ return render(getExports(), getExports().md4x_to_ansi, input);
53
+ }
54
+
55
+ // --- internals ---
56
+
57
+ const wasiStub = {
58
+ fd_close: () => 0,
59
+ fd_seek: () => 0,
60
+ fd_write: () => 0,
61
+ proc_exit: () => {},
62
+ };
63
+
64
+ function render(exports, fn, input) {
65
+ const { memory, md4x_alloc, md4x_free, md4x_result_ptr, md4x_result_size } =
66
+ exports;
67
+ const encoded = new TextEncoder().encode(input);
68
+ const ptr = md4x_alloc(encoded.length);
69
+ new Uint8Array(memory.buffer).set(encoded, ptr);
70
+ const ret = fn(ptr, encoded.length);
71
+ md4x_free(ptr);
72
+ if (ret !== 0) {
73
+ throw new Error("md4x: render failed");
74
+ }
75
+ const outPtr = md4x_result_ptr();
76
+ const outSize = md4x_result_size();
77
+ const result = new TextDecoder().decode(
78
+ new Uint8Array(memory.buffer, outPtr, outSize),
79
+ );
80
+ md4x_free(outPtr);
81
+ return result;
82
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "md4x",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "bin": {
7
+ "md4x": "./lib/cli.mjs"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/napi.d.mts",
12
+ "node": "./lib/napi.mjs",
13
+ "default": "./lib/wasm.mjs"
14
+ },
15
+ "./wasm": {
16
+ "types": "./lib/wasm.d.mts",
17
+ "default": "./lib/wasm.mjs"
18
+ },
19
+ "./napi": {
20
+ "types": "./lib/napi.d.mts",
21
+ "default": "./lib/napi.mjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "build",
26
+ "lib"
27
+ ],
28
+ "devDependencies": {
29
+ "@milkdown/crepe": "^7.18.0",
30
+ "ansi-html": "^0.0.9",
31
+ "markdown-it": "^14.1.1",
32
+ "md4w": "^0.2.7",
33
+ "mitata": "^1.0.34",
34
+ "shiki": "^3.22.0",
35
+ "vite": "^6.3.5",
36
+ "vitest": "^4.0.18"
37
+ }
38
+ }