@draftably/markdown 0.0.0-stage → 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
@@ -1,3 +1,73 @@
1
- # Temporary Holding Version
1
+ # `@draftably/markdown`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This package converts canonical `draft` code fences to static SVG image nodes.
4
+ It supports async remark and markdown-it adapters. Static pages do not load or
5
+ compile diagrams in the reader.
6
+
7
+ Start with the [complete Markdown example](https://github.com/byronwall/draftably/blob/main/docs/integrations/README.md#render-markdown)
8
+ for installation, file storage, and a working first diagram.
9
+
10
+ The host must provide `emitAsset`. It receives final SVG bytes and returns the
11
+ public URL. Use a durable content-addressed store with your page cache. Re-emit
12
+ the asset on a warm Markdown transform, and retain asset references with cached
13
+ pages. The host owns base paths, publication, cleanup, and watcher integration.
14
+
15
+ For Node builds, `@draftably/markdown/files` provides a filesystem store for
16
+ content-addressed SVG and interactive JSON assets:
17
+
18
+ ```js
19
+ import { createFileAssets } from '@draftably/markdown/files';
20
+
21
+ const assets = createFileAssets({ directory: '.cache/diagrams', publicPath: '/docs/diagrams/' });
22
+ const options = { emitAsset: assets.emitAsset };
23
+ // Add `assets.emitInit` to the optional interactive bridge.
24
+ await assets.copyTo('dist/diagrams');
25
+ ```
26
+
27
+ The store checks each filename against the asset bytes and recreates deleted
28
+ files on warm transforms. The host still chooses paths and retains cached page
29
+ references.
30
+
31
+ Use `src="./diagram.draft"` only with `sources.rootDir` and a Markdown file path.
32
+ The resolver rejects remote and absolute paths, non-`.draft` files, and symlink
33
+ escapes. It reports missing paths as dependencies and the nearest existing
34
+ in-root directory as `watchPath`, so the host can detect when a file appears.
35
+
36
+ Supported fence keys are `id`, `alt`, `caption`, `src`, `mode`, and `state`.
37
+ Unknown, repeated, malformed, or control-containing values fail the build.
38
+ Captions and alternatives are plain text. The adapter does not evaluate HTML
39
+ or JSX. Inline SVG is not available.
40
+
41
+ ## markdown-it
42
+
43
+ Call `createMarkdownItDraft(options)` and add its `plugin` to markdown-it. Use
44
+ `adapter.render(md, source, env)` to parse, prepare, and render one document. The
45
+ adapter also exposes `prepare(tokens, env)` for hosts that need custom rendering.
46
+ Prepared state stays local to each token tree, so separate documents can prepare
47
+ at the same time.
48
+
49
+ ## Optional interactive mode
50
+
51
+ `mode="interactive"` requires a host `interactive` bridge. The bridge emits a
52
+ versioned JSON asset and names the client module. This mode publishes the
53
+ diagram source in that JSON asset by design. Keep the JSON and SVG references
54
+ with the page cache.
55
+
56
+ The host can load `@draftably/markdown/client` after hydration and call
57
+ `enhanceDrafts(root, { policies })`. The client validates same-origin URLs and
58
+ bounded JSON before it mounts `@draftably/runtime/viewer`. It hides the static
59
+ image only after the viewer commits an update. On failure or destroy, the
60
+ fallback image stays or becomes visible.
61
+
62
+ The host owns module loading and teardown. Static adapter entry points do not
63
+ import the client or viewer. Use interactive mode only when public source is
64
+ acceptable.
65
+
66
+ For positioned author errors, preserve mdast positions and inspect
67
+ `file.data.draftably.diagnostics`. Production builds fail by default. Set
68
+ `errors: 'placeholder'` for expected content, metadata, or include errors. Host
69
+ emitter, dependency callback, and renderer failures still fail the build.
70
+
71
+ See [`docs/integrations/markdown.md`](../../docs/integrations/markdown.md) and
72
+ [`integration-examples/unified/`](../../integration-examples/unified/) for
73
+ pinned Unified, MDX, markdown-it, and interactive consumer proofs.
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Byron Wall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,317 @@
1
+ import { createHash as J } from "node:crypto";
2
+ import { runtimeVersion as W, fontMetricsVersion as Z, renderSvg as H } from "@draftably/runtime";
3
+ import { realpath as D, stat as K, readFile as V } from "node:fs/promises";
4
+ import { resolve as k, relative as q, sep as G, isAbsolute as Y } from "node:path";
5
+ function N(e, t) {
6
+ const r = q(e, t);
7
+ return r === "" || r !== ".." && !r.startsWith(`..${G}`) && !Y(r);
8
+ }
9
+ async function Q(e, t, r) {
10
+ if (!e || e.includes("\0") || e.startsWith("/") || e.startsWith("\\") || /^[A-Za-z][A-Za-z\d+.-]*:/.test(e) || /^[A-Za-z]:/.test(e))
11
+ throw new Error("src must be a relative local path.");
12
+ const s = await D(r), g = k(t, "..", e);
13
+ if (!N(k(r), g))
14
+ throw new Error("src is outside the configured content root.");
15
+ let n;
16
+ try {
17
+ n = await D(g);
18
+ } catch (m) {
19
+ let f = g, y;
20
+ for (; f !== k(f, ".."); )
21
+ try {
22
+ const l = await D(f);
23
+ if (!N(s, l))
24
+ throw new Error("src resolves outside the configured content root.", { cause: m });
25
+ y = l;
26
+ break;
27
+ } catch (l) {
28
+ if (l instanceof Error && l.message.includes("outside"))
29
+ throw l;
30
+ f = k(f, "..");
31
+ }
32
+ throw Object.assign(new Error("Included .draft file does not exist.", { cause: m }), {
33
+ missingPath: g,
34
+ watchPath: y
35
+ });
36
+ }
37
+ if (!N(s, n))
38
+ throw new Error("src resolves outside the configured content root.");
39
+ if (!n.endsWith(".draft")) throw new Error("src must name a .draft file.");
40
+ const p = await K(n);
41
+ if (!p.isFile()) throw new Error("src must name a regular file.");
42
+ if (p.size > 1e6) throw new Error("Included .draft file exceeds 1 MB.");
43
+ const h = await V(n);
44
+ if (h.byteLength > 1e6) throw new Error("Included .draft file exceeds 1 MB.");
45
+ return { source: new TextDecoder("utf-8", { fatal: !0 }).decode(h), path: n };
46
+ }
47
+ function z(e, t, r, s) {
48
+ return {
49
+ code: e,
50
+ severity: "error",
51
+ message: t,
52
+ location: r,
53
+ itemIds: [],
54
+ ...s ? { related: s } : {}
55
+ };
56
+ }
57
+ function M(e) {
58
+ return J("sha256").update(e).digest("hex");
59
+ }
60
+ function x(e) {
61
+ for (const t of e) {
62
+ const r = t.charCodeAt(0);
63
+ if (r < 32 || r === 127) return !0;
64
+ }
65
+ return !1;
66
+ }
67
+ function c(e, t = "Markdown host operation failed.") {
68
+ return X(e) ? e : Object.assign(new Error(t, { cause: e }), { hostFailure: !0 });
69
+ }
70
+ function X(e) {
71
+ return !!e && typeof e == "object" && "hostFailure" in e;
72
+ }
73
+ function P(e, t, r) {
74
+ return Object.assign(new Error(t), { diagnostic: z(e, t, r) });
75
+ }
76
+ function se(e) {
77
+ return !!e && typeof e == "object" && "noPlaceholder" in e;
78
+ }
79
+ function F(e) {
80
+ if (e?.aborted)
81
+ throw Object.assign(new Error("Markdown diagram preparation was aborted."), {
82
+ noPlaceholder: !0,
83
+ cause: e.reason
84
+ });
85
+ }
86
+ const v = /* @__PURE__ */ new Map(), ee = 128;
87
+ function te(e, t) {
88
+ const r = e.render ?? {};
89
+ if (!(r.links?.mode === "safe" && r.links.resolve && !r.links.cacheKey))
90
+ return JSON.stringify({
91
+ includeSource: r.includeSource ?? !1,
92
+ links: r.links ?? { mode: "none" },
93
+ limits: r.limits ?? {},
94
+ state: t ?? null,
95
+ target: "image",
96
+ fonts: "embed"
97
+ });
98
+ }
99
+ async function ce(e, t, r, s, g, n) {
100
+ const p = [];
101
+ F(n.render?.signal);
102
+ let h = e, m = s.path, f, y = !1;
103
+ if (t.src) {
104
+ if (e.trim())
105
+ throw P(
106
+ "DRAFT_INCLUDE",
107
+ "src cannot be combined with a non-empty fence body.",
108
+ r
109
+ );
110
+ if (!s.path || !n.sources)
111
+ throw P(
112
+ "DRAFT_INCLUDE",
113
+ "src requires a document path and configured sources.rootDir.",
114
+ r
115
+ );
116
+ let i;
117
+ try {
118
+ i = await Q(t.src, s.path, n.sources.rootDir);
119
+ } catch (a) {
120
+ const { missingPath: u, watchPath: I } = a;
121
+ if (u)
122
+ try {
123
+ n.sources.onDependency?.({
124
+ documentPath: s.path,
125
+ dependencyPath: u,
126
+ missing: !0,
127
+ watchPath: I
128
+ });
129
+ } catch (A) {
130
+ throw c(A, "Markdown dependency callback failed.");
131
+ }
132
+ const o = a instanceof Error ? a.message : "Could not read included diagram.";
133
+ throw Object.assign(new Error(o), {
134
+ diagnostic: z(
135
+ "DRAFT_INCLUDE",
136
+ o,
137
+ r,
138
+ u ? [{ message: "Included here.", location: r }] : void 0
139
+ )
140
+ });
141
+ }
142
+ h = i.source, m = i.path, y = !0, f = i.path;
143
+ try {
144
+ n.sources.onDependency?.({
145
+ documentPath: s.path,
146
+ dependencyPath: i.path,
147
+ missing: !1
148
+ });
149
+ } catch (a) {
150
+ throw c(a, "Markdown dependency callback failed.");
151
+ }
152
+ }
153
+ if (t.mode === "interactive" && !n.interactive) {
154
+ const i = P(
155
+ "DRAFT_CAPABILITY",
156
+ "Interactive diagrams require a configured enhancement bridge.",
157
+ r
158
+ );
159
+ throw Object.assign(i, { noPlaceholder: !0 }), i;
160
+ }
161
+ const l = te(n, t.state), b = l === void 0 ? void 0 : M(
162
+ JSON.stringify([
163
+ h,
164
+ m ?? "",
165
+ W,
166
+ Z,
167
+ l
168
+ ])
169
+ );
170
+ let w = b ? v.get(b) : void 0;
171
+ const L = (i) => i.map((a) => ({
172
+ ...a,
173
+ location: a.location ? {
174
+ ...a.location,
175
+ line: a.location.line + (y ? 0 : r?.line ?? 1),
176
+ ...y ? {} : { column: void 0 }
177
+ } : r,
178
+ ...y && r ? { related: [...a.related ?? [], { message: "Included here.", location: r }] } : {}
179
+ }));
180
+ if (!w) {
181
+ let i;
182
+ try {
183
+ i = await H(h, {
184
+ ...n.render ?? {},
185
+ target: "image",
186
+ fonts: "embed",
187
+ includeSource: n.render?.includeSource ?? !1,
188
+ links: n.render?.links ?? { mode: "none" },
189
+ sourceId: m,
190
+ state: t.state ?? null
191
+ });
192
+ } catch (a) {
193
+ throw Object.assign(new Error("Draftably renderer failed."), {
194
+ noPlaceholder: !0,
195
+ cause: a
196
+ });
197
+ }
198
+ if (!i.ok) {
199
+ const a = L(i.diagnostics);
200
+ p.push(...a);
201
+ const u = Object.assign(new Error("Draftably could not render this diagram."), {
202
+ diagnostics: a
203
+ });
204
+ if ((n.errors ?? "fail") === "fail" || !["input", "limit"].includes(i.reason))
205
+ throw Object.assign(u, { noPlaceholder: !0 }), u;
206
+ return {
207
+ node: { type: "paragraph", children: [{ type: "text", value: "Diagram unavailable." }] },
208
+ diagnostics: p
209
+ };
210
+ }
211
+ w = {
212
+ svg: i.value.svg,
213
+ width: i.value.width,
214
+ height: i.value.height,
215
+ title: i.value.title,
216
+ diagnostics: i.diagnostics
217
+ }, b && (v.set(b, w), v.size > ee && v.delete(v.keys().next().value));
218
+ }
219
+ p.push(...L(w.diagnostics)), F(n.render?.signal);
220
+ const T = new TextEncoder().encode(w.svg), O = `draft-${M(T)}.svg`;
221
+ let d;
222
+ try {
223
+ d = await n.emitAsset(
224
+ { fileName: O, mediaType: "image/svg+xml", bytes: T },
225
+ { document: s, fenceIndex: g }
226
+ );
227
+ } catch (i) {
228
+ throw c(i, "SVG asset emitter failed.");
229
+ }
230
+ if (!d || typeof d.url != "string")
231
+ throw c(new TypeError("Asset emitter must return a URL string."));
232
+ let R;
233
+ try {
234
+ R = new URL(d.url, "https://draftably.invalid");
235
+ } catch {
236
+ throw c(new Error("Asset emitter returned an invalid URL."));
237
+ }
238
+ if (!d.url || d.url.charCodeAt(0) <= 32 || x(d.url) || !["https:", "http:"].includes(R.protocol))
239
+ throw c(new Error("Asset emitter returned an unsafe URL."));
240
+ const U = t.alt?.trim() || w.title, _ = {
241
+ type: "image",
242
+ url: d.url,
243
+ alt: U,
244
+ data: { hProperties: { width: Math.ceil(w.width), height: Math.ceil(w.height) } }
245
+ }, B = { fileName: O, url: d.url }, S = [];
246
+ let j;
247
+ const E = [_];
248
+ if (t.mode === "interactive") {
249
+ const i = n.interactive;
250
+ if (!/^[A-Za-z0-9._-]{1,64}$/.test(i.policyId))
251
+ throw c(new TypeError("Interactive policyId must be a short ASCII token."));
252
+ const a = `draft-${M(JSON.stringify([h, t.state ?? null, t.id ?? null, g])).slice(0, 24)}`, u = {
253
+ version: 1,
254
+ key: a,
255
+ source: h,
256
+ state: t.state ?? null,
257
+ label: U,
258
+ policyId: i.policyId
259
+ }, I = new TextEncoder().encode(JSON.stringify(u));
260
+ if (I.byteLength > 1e6)
261
+ throw P("DRAFT_LIMIT", "Interactive initialization data exceeds 1 MB.", r);
262
+ let o;
263
+ try {
264
+ o = await i.emitInit(u);
265
+ } catch ($) {
266
+ throw c($, "Interactive initialization emitter failed.");
267
+ }
268
+ if (!o || typeof o.fileName != "string" || typeof o.url != "string")
269
+ throw c(new TypeError("Initialization emitter must return a filename and URL."));
270
+ let A, C;
271
+ try {
272
+ A = new URL(o.url, "https://draftably.invalid"), C = new URL(i.clientModuleUrl, "https://draftably.invalid");
273
+ } catch {
274
+ throw c(new Error("Interactive bridge returned an invalid URL."));
275
+ }
276
+ if (o.fileName !== `draft-${M(I)}.json` || !o.url || o.url.charCodeAt(0) <= 32 || x(o.url) || !["https:", "http:"].includes(A.protocol))
277
+ throw c(new Error("Initialization emitter returned an invalid asset."));
278
+ if (!i.clientModuleUrl || i.clientModuleUrl.charCodeAt(0) <= 32 || x(i.clientModuleUrl) || !["https:", "http:"].includes(C.protocol))
279
+ throw c(new Error("clientModuleUrl is unsafe."));
280
+ S.push({ fileName: o.fileName, url: o.url }), j = i.clientModuleUrl, E.push({
281
+ type: "div",
282
+ data: {
283
+ hName: "div",
284
+ hProperties: {
285
+ className: ["draftably-viewer-region"],
286
+ "data-draftably-region": "",
287
+ "data-draftably-init": o.url,
288
+ "data-draftably-policy": i.policyId,
289
+ "data-draftably-key": a,
290
+ role: "region",
291
+ "aria-label": U
292
+ }
293
+ },
294
+ children: []
295
+ });
296
+ }
297
+ return t.caption && E.push({
298
+ type: "figcaption",
299
+ data: { hName: "figcaption" },
300
+ children: [{ type: "text", value: t.caption }]
301
+ }), { node: t.mode === "interactive" || t.caption ? {
302
+ type: "figure",
303
+ data: { hName: "figure", ...t.id ? { hProperties: { id: t.id } } : {} },
304
+ children: E
305
+ } : {
306
+ type: "paragraph",
307
+ ...t.id ? { data: { hProperties: { id: t.id } } } : {},
308
+ children: E
309
+ }, asset: B, extraAssets: S, clientModule: j, diagnostics: p, dependency: f };
310
+ }
311
+ export {
312
+ se as a,
313
+ c as h,
314
+ X as i,
315
+ z as m,
316
+ ce as p
317
+ };
@@ -0,0 +1,53 @@
1
+ function w(a) {
2
+ const n = a.charCodeAt(0);
3
+ return n < 32 || n === 127;
4
+ }
5
+ function f(a) {
6
+ const n = /^draft(?:\s+\{([\s\S]*)\})?$/.exec(a.trim());
7
+ if (!n)
8
+ throw new Error('Expected the canonical draft fence, optionally followed by {key="value"}.');
9
+ const r = n[1];
10
+ if (r === void 0) return {};
11
+ const t = {};
12
+ let e = 0;
13
+ const i = () => {
14
+ for (; /\s/.test(r[e] ?? "") && e < r.length; ) e++;
15
+ };
16
+ for (; e < r.length && (i(), e !== r.length); ) {
17
+ const o = /^[A-Za-z][A-Za-z0-9_-]*/.exec(r.slice(e))?.[0];
18
+ if (!o) throw new Error(`Invalid metadata near "${r.slice(e)}".`);
19
+ if (e += o.length, i(), r[e++] !== "=") throw new Error(`Expected '=' after ${o}.`);
20
+ if (i(), r[e++] !== '"') throw new Error(`${o} must use a quoted value.`);
21
+ let c = "", d = !1;
22
+ for (; e < r.length; ) {
23
+ const s = r[e++];
24
+ if (s === '"') {
25
+ d = !0;
26
+ break;
27
+ }
28
+ if (s === "\\") {
29
+ const l = r[e++];
30
+ if (l !== '"' && l !== "\\") throw new Error(`Invalid escape in ${o}.`);
31
+ c += l;
32
+ } else {
33
+ if (w(s)) throw new Error(`${o} cannot contain control characters.`);
34
+ c += s;
35
+ }
36
+ }
37
+ if (!d) throw new Error(`Unclosed value for ${o}.`);
38
+ if (!["id", "alt", "caption", "src", "mode", "state"].includes(o))
39
+ throw new Error(`Unknown draft option "${o}".`);
40
+ if (Object.hasOwn(t, o)) throw new Error(`Duplicate draft option "${o}".`);
41
+ t[o] = c, i();
42
+ }
43
+ if (t.id !== void 0 && !/^[A-Za-z0-9_-]+$/.test(t.id))
44
+ throw new Error("id must be a non-empty ASCII token.");
45
+ if (t.mode !== void 0 && t.mode !== "static" && t.mode !== "interactive")
46
+ throw new Error('mode must be "static" or "interactive".');
47
+ if (t.src !== void 0 && (!t.src.endsWith(".draft") || t.src.trim() !== t.src || t.src.includes("\0") || t.src.startsWith("/") || t.src.startsWith("\\") || /^[A-Za-z][A-Za-z\d+.-]*:/.test(t.src) || /^[A-Za-z]:/.test(t.src)))
48
+ throw new Error("src must name a relative local .draft file.");
49
+ return t;
50
+ }
51
+ export {
52
+ f as p
53
+ };
package/dist/client.js ADDED
@@ -0,0 +1,153 @@
1
+ import { mountViewer as k } from "@draftably/runtime/viewer";
2
+ const m = 1e6, v = 1e6;
3
+ function T(e) {
4
+ for (const a of e) {
5
+ const n = a.charCodeAt(0);
6
+ if (n < 32 || n === 127) return !0;
7
+ }
8
+ return !1;
9
+ }
10
+ function p(e, a, n) {
11
+ if (!e || T(e)) throw new Error("Invalid initialization URL.");
12
+ const i = new URL(e, n);
13
+ if (!["http:", "https:"].includes(i.protocol) || i.origin !== a || i.username || i.password)
14
+ throw new Error("Initialization URL must be same-origin.");
15
+ return i;
16
+ }
17
+ function A(e, a, n) {
18
+ if (new TextEncoder().encode(e).byteLength > m) throw new Error("Initialization data exceeds the size limit.");
19
+ let o;
20
+ try {
21
+ o = JSON.parse(e);
22
+ } catch {
23
+ throw new Error("Initialization data is not valid JSON.");
24
+ }
25
+ if (!o || typeof o != "object" || Array.isArray(o))
26
+ throw new Error("Initialization data has an invalid shape.");
27
+ const t = o;
28
+ if (Object.keys(t).sort().join(",") !== "key,label,policyId,source,state,version")
29
+ throw new Error("Initialization data has unknown or missing fields.");
30
+ if (t.version !== 1 || t.key !== a || !/^draft-[0-9a-f]{24}$/.test(a))
31
+ throw new Error("Initialization identity is invalid.");
32
+ if (t.policyId !== n || typeof t.policyId != "string")
33
+ throw new Error("Initialization policy does not match.");
34
+ if (typeof t.source != "string" || new TextEncoder().encode(t.source).byteLength > v)
35
+ throw new Error("Diagram source is invalid or too large.");
36
+ if (typeof t.label != "string" || t.label.length > 2e3)
37
+ throw new Error("Diagram label is invalid.");
38
+ if (t.state !== null && typeof t.state != "string")
39
+ throw new Error("Diagram state is invalid.");
40
+ return t;
41
+ }
42
+ const u = /* @__PURE__ */ new WeakSet();
43
+ async function S(e) {
44
+ if (!e.ok) throw new Error(`Initialization request failed with HTTP ${e.status}.`);
45
+ const a = Number(e.headers.get("content-length"));
46
+ if (Number.isFinite(a) && a > m)
47
+ throw new Error("Initialization response exceeds the size limit.");
48
+ if (!e.body) throw new Error("Initialization response has no body.");
49
+ const n = e.body.getReader(), i = [];
50
+ let o = 0;
51
+ try {
52
+ for (; ; ) {
53
+ const { done: s, value: f } = await n.read();
54
+ if (s) break;
55
+ if (o += f.byteLength, o > m)
56
+ throw await n.cancel(), new Error("Initialization response exceeds the size limit.");
57
+ i.push(f);
58
+ }
59
+ } finally {
60
+ n.releaseLock();
61
+ }
62
+ const t = new Uint8Array(o);
63
+ let l = 0;
64
+ for (const s of i)
65
+ t.set(s, l), l += s.byteLength;
66
+ return new TextDecoder("utf-8", { fatal: !0 }).decode(t);
67
+ }
68
+ function D(e) {
69
+ return { code: "DRAFT_ENHANCE", severity: "warning", message: e, itemIds: [] };
70
+ }
71
+ function U(e, a) {
72
+ if (u.has(e))
73
+ throw new Error("This Markdown root already has an active Draftably enhancement.");
74
+ u.add(e);
75
+ const n = e.ownerDocument, i = n.defaultView;
76
+ if (!i)
77
+ throw u.delete(e), new Error("The Markdown root has no browser window.");
78
+ let o = !1;
79
+ const t = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Set(), s = [], f = (r) => {
80
+ try {
81
+ a.onDiagnostic?.(D(r));
82
+ } catch {
83
+ }
84
+ }, I = Array.from(
85
+ e.querySelectorAll("[data-draftably-region][data-draftably-init]")
86
+ );
87
+ for (const r of I) {
88
+ const w = r.closest("figure")?.querySelector("img");
89
+ if (w) {
90
+ const h = w.hidden;
91
+ s.push(() => {
92
+ w.hidden = h;
93
+ });
94
+ }
95
+ const c = new i.AbortController();
96
+ t.add(c), (async () => {
97
+ let h;
98
+ try {
99
+ const y = p(
100
+ r.dataset.draftablyInit ?? "",
101
+ i.location.origin,
102
+ n.baseURI
103
+ ), g = await i.fetch(y, {
104
+ signal: c.signal,
105
+ credentials: "same-origin",
106
+ redirect: "error"
107
+ });
108
+ g.url && p(g.url, i.location.origin, n.baseURI);
109
+ const z = await S(g);
110
+ if (o || c.signal.aborted) return;
111
+ const d = A(
112
+ z,
113
+ r.dataset.draftablyKey ?? "",
114
+ r.dataset.draftablyPolicy ?? ""
115
+ ), b = a.policies.get(d.policyId);
116
+ if (!b) throw new Error("No viewer policy is configured for this diagram.");
117
+ if (r.dataset.draftablyKey !== d.key || r.dataset.draftablyPolicy !== d.policyId)
118
+ throw new Error("Figure markers do not match initialization data.");
119
+ if (r.childNodes.length) throw new Error("Viewer region is not empty.");
120
+ h = k(r, { ...b, label: b.label ?? d.label }), l.add(h);
121
+ const E = await h.update(d.source, {
122
+ state: d.state,
123
+ camera: "fit",
124
+ signal: c.signal
125
+ });
126
+ if (o || c.signal.aborted) return;
127
+ E.status === "committed" && w ? w.hidden = !0 : E.status === "error" && f("The interactive diagram could not load. The static image is still available.");
128
+ } catch (y) {
129
+ !o && !c.signal.aborted && f(
130
+ y instanceof Error ? y.message : "The interactive diagram could not load."
131
+ );
132
+ } finally {
133
+ t.delete(c);
134
+ }
135
+ })();
136
+ }
137
+ return {
138
+ destroy() {
139
+ if (!o) {
140
+ o = !0;
141
+ for (const r of t) r.abort();
142
+ t.clear();
143
+ for (const r of l) r.destroy();
144
+ l.clear();
145
+ for (const r of s) r();
146
+ s.length = 0, u.delete(e);
147
+ }
148
+ }
149
+ };
150
+ }
151
+ export {
152
+ U as enhanceDrafts
153
+ };
package/dist/files.js ADDED
@@ -0,0 +1,103 @@
1
+ import { randomUUID as v, createHash as $ } from "node:crypto";
2
+ import { mkdir as m, readdir as x, readFile as p, writeFile as b, link as N, rm as U } from "node:fs/promises";
3
+ import { resolve as h, join as l } from "node:path";
4
+ function A(t) {
5
+ return $("sha256").update(t).digest("hex");
6
+ }
7
+ function g(t, r) {
8
+ return `draft-${A(t)}.${r}`;
9
+ }
10
+ function j(t) {
11
+ for (const r of t) {
12
+ const o = r.charCodeAt(0);
13
+ if (o < 32 || o === 127) return !0;
14
+ }
15
+ return !1;
16
+ }
17
+ function S(t) {
18
+ if (typeof t != "string" || !t || /[\s\\?#]/.test(t) || j(t))
19
+ throw new TypeError(
20
+ "publicPath must be a URL path without query, hash, backslash, or controls."
21
+ );
22
+ let r;
23
+ if (t.startsWith("/")) {
24
+ if (t.startsWith("//"))
25
+ throw new TypeError("publicPath must not be protocol-relative.");
26
+ r = t;
27
+ } else {
28
+ let o;
29
+ try {
30
+ o = new URL(t);
31
+ } catch {
32
+ throw new TypeError("publicPath must be root-relative or an absolute HTTP(S) URL.");
33
+ }
34
+ if (!["http:", "https:"].includes(o.protocol) || o.username || o.password)
35
+ throw new TypeError("publicPath must be root-relative or an absolute HTTP(S) URL.");
36
+ r = `${o.origin}${o.pathname}`;
37
+ }
38
+ return r.replace(/\/+$/, "");
39
+ }
40
+ async function d(t, r, o) {
41
+ await m(t, { recursive: !0 });
42
+ const s = l(t, r), c = l(t, `.${r}.${v()}.tmp`);
43
+ try {
44
+ await b(c, o, { flag: "wx" });
45
+ try {
46
+ await N(c, s);
47
+ } catch (f) {
48
+ if (f.code !== "EEXIST") throw f;
49
+ if (!(await p(s)).equals(o)) throw new Error(`Asset collision: ${r}`, { cause: f });
50
+ }
51
+ } finally {
52
+ await U(c, { force: !0 });
53
+ }
54
+ }
55
+ function y(t, r) {
56
+ const o = t.endsWith(".svg") ? "svg" : t.endsWith(".json") ? "json" : void 0;
57
+ return !!o && t === g(r, o);
58
+ }
59
+ function R(t) {
60
+ if (!t || typeof t.directory != "string" || !t.directory)
61
+ throw new TypeError("directory must be a non-empty path.");
62
+ const r = h(t.directory), o = S(t.publicPath), s = /* @__PURE__ */ new Map();
63
+ async function c(e, i) {
64
+ if (!y(e, i))
65
+ throw new TypeError(`Invalid content-addressed asset name: ${e}`);
66
+ let n = s.get(e);
67
+ n || (n = d(r, e, i), s.set(e, n));
68
+ try {
69
+ await n;
70
+ } finally {
71
+ s.get(e) === n && s.delete(e);
72
+ }
73
+ }
74
+ const f = (e) => `${o}/${e}`, u = async (e) => {
75
+ if (e.mediaType !== "image/svg+xml" || !e.fileName.endsWith(".svg"))
76
+ throw new TypeError("Expected a content-addressed Draftably SVG.");
77
+ return await c(e.fileName, e.bytes), { url: f(e.fileName) };
78
+ }, T = async (e) => {
79
+ const i = new TextEncoder().encode(JSON.stringify(e)), n = g(i, "json");
80
+ return await c(n, i), { fileName: n, url: f(n) };
81
+ };
82
+ async function E(e) {
83
+ const i = h(e);
84
+ await m(i, { recursive: !0 });
85
+ let n;
86
+ try {
87
+ n = await x(r);
88
+ } catch (a) {
89
+ if (a.code === "ENOENT") return;
90
+ throw a;
91
+ }
92
+ for (const a of n) {
93
+ if (!/^draft-[0-9a-f]{64}\.(?:svg|json)$/.test(a)) continue;
94
+ const w = await p(l(r, a));
95
+ if (!y(a, w)) throw new Error(`Asset content hash mismatch: ${a}`);
96
+ await d(i, a, w);
97
+ }
98
+ }
99
+ return { directory: r, emitAsset: u, emitInit: T, copyTo: E };
100
+ }
101
+ export {
102
+ R as createFileAssets
103
+ };
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import "node:crypto";
2
+ import "@draftably/runtime";
3
+ import "node:fs/promises";
4
+ import "node:path";
5
+ import { p as e } from "./chunks/metadata-Dx6T_2Zy.js";
6
+ export {
7
+ e as parseFenceOptions
8
+ };
@@ -0,0 +1,155 @@
1
+ import { m as T, p as S, i as P, a as I, h as F } from "./chunks/core-DPjuIyPG.js";
2
+ import { p as j } from "./chunks/metadata-Dx6T_2Zy.js";
3
+ function x(t) {
4
+ return /^draft(?:\s|$)/.test(t.trim());
5
+ }
6
+ function E(t) {
7
+ return t.replace(
8
+ /[&<>"']/g,
9
+ (r) => ({
10
+ "&": "&amp;",
11
+ "<": "&lt;",
12
+ ">": "&gt;",
13
+ '"': "&quot;",
14
+ "'": "&#39;"
15
+ })[r]
16
+ );
17
+ }
18
+ function H(t) {
19
+ const r = `${t.width === void 0 ? "" : ` width="${t.width}"`}${t.height === void 0 ? "" : ` height="${t.height}"`}`;
20
+ return `<img src="${E(t.url)}" alt="${E(t.alt)}"${r}>`;
21
+ }
22
+ function A(t, r) {
23
+ const d = t.type;
24
+ if (d === "paragraph") {
25
+ const e = t.data?.hProperties ?? {}, l = typeof e.id == "string" ? ` id="${r(e.id)}"` : "", a = t.children.map((o) => A(o, r)).join("");
26
+ return `<p${l}>${a}</p>
27
+ `;
28
+ }
29
+ if (d === "figure") {
30
+ const l = t.data?.hProperties?.id, h = typeof l == "string" ? ` id="${r(l)}"` : "", a = t.children;
31
+ return `<figure${h}>${a.map((o) => A(o, r)).join("")}</figure>
32
+ `;
33
+ }
34
+ if (d === "image") {
35
+ const e = t.data?.hProperties ?? {};
36
+ return H({
37
+ url: String(t.url ?? ""),
38
+ alt: String(t.alt ?? ""),
39
+ ...typeof e.width == "number" ? { width: e.width } : {},
40
+ ...typeof e.height == "number" ? { height: e.height } : {}
41
+ });
42
+ }
43
+ if (d === "figcaption")
44
+ return `<figcaption>${t.children.map((l) => A(l, r)).join("")}</figcaption>`;
45
+ if (d === "div") {
46
+ const e = t.data?.hProperties ?? {};
47
+ if (!(e["data-draftably-region"] === "") || typeof e["data-draftably-init"] != "string" || typeof e["data-draftably-policy"] != "string" || typeof e["data-draftably-key"] != "string")
48
+ throw new Error("Invalid generated viewer region.");
49
+ const h = typeof e["aria-label"] == "string" ? e["aria-label"] : "Diagram viewer";
50
+ return `<div class="draftably-viewer-region" data-draftably-region="" data-draftably-init="${r(String(e["data-draftably-init"]))}" data-draftably-policy="${r(String(e["data-draftably-policy"]))}" data-draftably-key="${r(String(e["data-draftably-key"]))}" role="region" aria-label="${r(h)}"></div>`;
51
+ }
52
+ if (d === "text") return r(String(t.value ?? ""));
53
+ throw new Error(`Unsupported generated AST node: ${String(d)}.`);
54
+ }
55
+ function _(t) {
56
+ if (!t || typeof t.emitAsset != "function")
57
+ throw new TypeError("createMarkdownItDraft requires an emitAsset function.");
58
+ const r = /* @__PURE__ */ new WeakMap(), d = (a) => a.type === "fence" && x(a.info ?? "");
59
+ function e(a) {
60
+ const o = a, p = o.renderer.rules.fence;
61
+ o.renderer.rules.fence = function(c, u, g, y, b) {
62
+ const $ = c[u];
63
+ if (!d($))
64
+ return p ? p(c, u, g, y, b) : b.renderToken(c, u, g);
65
+ const D = r.get($);
66
+ if (!D)
67
+ throw new Error(
68
+ "Draft fence rendered before prepare(). Prepare and render the same token tree."
69
+ );
70
+ return A(D.node, (k) => o.utils.escapeHtml(k));
71
+ };
72
+ }
73
+ async function l(a, o, p = {}) {
74
+ const c = a.parse(o, p), u = await h(c, p);
75
+ return { html: a.renderer.render(c, a.options, p), manifest: u };
76
+ }
77
+ async function h(a, o = {}) {
78
+ const p = { path: o.draftably?.documentPath }, c = [], u = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Set(), y = [], b = /* @__PURE__ */ new Set(), $ = t.sources && {
79
+ ...t.sources,
80
+ onDependency: (w) => {
81
+ u.add(w.dependencyPath);
82
+ try {
83
+ t.sources?.onDependency?.(w);
84
+ } catch (s) {
85
+ throw F(s, "Markdown dependency callback failed.");
86
+ }
87
+ }
88
+ };
89
+ let D = 0;
90
+ const k = async (w) => {
91
+ for (const s of w) {
92
+ if (d(s)) {
93
+ const M = D++, v = { sourceId: p.path, line: (s.map?.[0] ?? 0) + 1 };
94
+ r.delete(s);
95
+ try {
96
+ let n;
97
+ try {
98
+ n = j(s.info ?? "");
99
+ } catch (f) {
100
+ const m = f instanceof Error ? f.message : "Invalid draft fence metadata.";
101
+ throw Object.assign(new Error(m), {
102
+ diagnostic: T("DRAFT_METADATA", m, v)
103
+ });
104
+ }
105
+ if (n.id && b.has(n.id)) {
106
+ const f = `Duplicate draft id "${n.id}" in this document.`;
107
+ throw Object.assign(new Error(f), {
108
+ diagnostic: T("DRAFT_METADATA", f, v)
109
+ });
110
+ }
111
+ n.id && b.add(n.id);
112
+ const i = await S(s.content ?? "", n, v, p, M, {
113
+ ...t,
114
+ sources: $
115
+ });
116
+ r.set(s, { node: i.node, diagnostics: i.diagnostics }), i.asset && c.push(i.asset), c.push(...i.extraAssets ?? []), i.dependency && u.add(i.dependency), i.clientModule && g.add(i.clientModule), y.push(...i.diagnostics);
117
+ } catch (n) {
118
+ if (P(n)) throw n.cause ?? n;
119
+ if (I(n)) throw n;
120
+ const i = n.diagnostic, f = n.diagnostics, m = i ?? f?.[0] ?? T(
121
+ "DRAFT_BUILD",
122
+ n instanceof Error ? n.message : "Could not prepare diagram.",
123
+ v
124
+ );
125
+ if (y.push(...f ?? [m]), (t.errors ?? "fail") === "fail")
126
+ throw Object.assign(new Error("Draftably Markdown preparation failed."), {
127
+ diagnostics: f ?? [m]
128
+ });
129
+ r.set(s, {
130
+ node: {
131
+ type: "paragraph",
132
+ children: [{ type: "text", value: "Diagram unavailable." }]
133
+ },
134
+ diagnostics: f ?? [m]
135
+ });
136
+ }
137
+ }
138
+ s.children?.length && await k(s.children);
139
+ }
140
+ };
141
+ await k(a);
142
+ for (const w of y) t.onDiagnostic?.(w);
143
+ return {
144
+ version: 1,
145
+ assets: c,
146
+ clientModules: [...g],
147
+ dependencies: [...u],
148
+ diagnostics: y
149
+ };
150
+ }
151
+ return { plugin: e, prepare: h, render: l };
152
+ }
153
+ export {
154
+ _ as createMarkdownItDraft
155
+ };
package/dist/remark.js ADDED
@@ -0,0 +1,86 @@
1
+ import { m as h, p as b, i as k, a as v, h as F } from "./chunks/core-DPjuIyPG.js";
2
+ import { p as x } from "./chunks/metadata-Dx6T_2Zy.js";
3
+ function j(s) {
4
+ if (!s || typeof s.emitAsset != "function")
5
+ throw new TypeError("remarkDraft requires an emitAsset function.");
6
+ return async (D, d) => {
7
+ const A = { path: d.path }, u = [], m = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set(), E = s.sources && {
8
+ ...s.sources,
9
+ onDependency: (i) => {
10
+ p.add(i.dependencyPath);
11
+ try {
12
+ s.sources?.onDependency?.(i);
13
+ } catch (r) {
14
+ throw F(r, "Markdown dependency callback failed.");
15
+ }
16
+ }
17
+ }, l = [], g = /* @__PURE__ */ new Set();
18
+ let M = 0;
19
+ async function w(i) {
20
+ const r = i.children;
21
+ if (r)
22
+ for (let o = 0; o < r.length; o++) {
23
+ const n = r[o];
24
+ if (n.type === "code" && n.lang === "draft") {
25
+ const T = M++, f = {
26
+ sourceId: d.path,
27
+ line: n.position?.start?.line ?? 1,
28
+ column: n.position?.start?.column
29
+ };
30
+ try {
31
+ let e;
32
+ try {
33
+ e = x(n.meta ? `draft ${n.meta}` : "draft");
34
+ } catch (a) {
35
+ const c = a instanceof Error ? a.message : "Invalid draft fence metadata.";
36
+ throw Object.assign(new Error(c), {
37
+ diagnostic: h("DRAFT_METADATA", c, f)
38
+ });
39
+ }
40
+ if (e.id && g.has(e.id)) {
41
+ const a = `Duplicate draft id "${e.id}" in this document.`;
42
+ throw Object.assign(new Error(a), {
43
+ diagnostic: h("DRAFT_METADATA", a, f)
44
+ });
45
+ }
46
+ e.id && g.add(e.id);
47
+ const t = await b(n.value ?? "", e, f, A, T, {
48
+ ...s,
49
+ sources: E
50
+ });
51
+ r.splice(o, 1, t.node), t.asset && u.push(t.asset), u.push(...t.extraAssets ?? []), t.clientModule && m.add(t.clientModule), t.dependency && p.add(t.dependency), l.push(...t.diagnostics);
52
+ } catch (e) {
53
+ if (k(e)) throw e.cause ?? e;
54
+ if (v(e)) throw e;
55
+ const t = e.diagnostic, a = e.diagnostics, c = t ?? a?.[0] ?? h(
56
+ "DRAFT_BUILD",
57
+ e instanceof Error ? e.message : "Could not prepare diagram.",
58
+ f
59
+ );
60
+ if (l.push(...a ?? [c]), (s.errors ?? "fail") === "fail") {
61
+ const y = new Error("Draftably Markdown transform failed.");
62
+ throw Object.assign(y, { diagnostics: a ?? [c] }), y;
63
+ }
64
+ r.splice(o, 1, {
65
+ type: "paragraph",
66
+ children: [{ type: "text", value: "Diagram unavailable." }]
67
+ });
68
+ }
69
+ } else
70
+ await w(n);
71
+ }
72
+ }
73
+ await w(D);
74
+ for (const i of l) s.onDiagnostic?.(i);
75
+ d.data ??= {}, d.data.draftably = {
76
+ version: 1,
77
+ assets: u,
78
+ clientModules: [...m],
79
+ dependencies: [...p],
80
+ diagnostics: l
81
+ };
82
+ };
83
+ }
84
+ export {
85
+ j as default
86
+ };
@@ -0,0 +1,5 @@
1
+ import type { DraftViewerInitV1 } from './core.js';
2
+ export declare const MAX_INIT_BYTES = 1000000;
3
+ export declare const MAX_SOURCE_BYTES = 1000000;
4
+ export declare function sameOriginInitUrl(value: string, origin: string, baseUrl: string): URL;
5
+ export declare function parseInitJson(text: string, expectedKey: string, expectedPolicy: string): DraftViewerInitV1;
@@ -0,0 +1,12 @@
1
+ import { mountViewer } from '@draftably/runtime/viewer';
2
+ import type { Diagnostic } from '@draftably/runtime';
3
+ type ViewerOptions = Parameters<typeof mountViewer>[1];
4
+ export interface EnhanceDraftsOptions {
5
+ policies: ReadonlyMap<string, ViewerOptions>;
6
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
7
+ }
8
+ /** Load generated init assets and progressively enhance static diagram figures under root. */
9
+ export declare function enhanceDrafts(root: HTMLElement, options: EnhanceDraftsOptions): {
10
+ destroy(): void;
11
+ };
12
+ export {};
@@ -0,0 +1,99 @@
1
+ import type { Diagnostic } from '@draftably/runtime';
2
+ import type { FenceOptions } from './metadata.js';
3
+ export { resolveSource } from './source.js';
4
+ export { parseFenceOptions } from './metadata.js';
5
+ export type { Diagnostic } from '@draftably/runtime';
6
+ export interface DraftDocument {
7
+ path?: string;
8
+ }
9
+ export interface DraftAsset {
10
+ fileName: string;
11
+ mediaType: 'image/svg+xml';
12
+ bytes: Uint8Array;
13
+ }
14
+ export interface AssetReference {
15
+ fileName: string;
16
+ url: string;
17
+ }
18
+ export interface DraftDocumentResult {
19
+ version: 1;
20
+ assets: readonly AssetReference[];
21
+ clientModules: readonly string[];
22
+ dependencies: readonly string[];
23
+ diagnostics: readonly Diagnostic[];
24
+ }
25
+ export interface SourceDependency {
26
+ documentPath: string;
27
+ dependencyPath: string;
28
+ missing: boolean;
29
+ watchPath?: string;
30
+ }
31
+ export type EmitAsset = (asset: DraftAsset, context: {
32
+ document: DraftDocument;
33
+ fenceIndex: number;
34
+ }) => Promise<{
35
+ url: string;
36
+ }>;
37
+ export interface DraftMarkdownOptions {
38
+ emitAsset: EmitAsset;
39
+ errors?: 'fail' | 'placeholder';
40
+ interactive?: MarkdownInteractiveBridge;
41
+ render?: {
42
+ includeSource?: boolean;
43
+ links?: {
44
+ mode: 'none';
45
+ } | {
46
+ mode: 'safe';
47
+ baseUrl?: string;
48
+ target?: '_self' | '_blank';
49
+ cacheKey?: string;
50
+ resolve?: (request: {
51
+ itemId: string;
52
+ href: string;
53
+ }) => string | null;
54
+ };
55
+ limits?: {
56
+ maxSourceChars?: number;
57
+ maxSourceBytes?: number;
58
+ maxOutputBytes?: number;
59
+ maxObjects?: number;
60
+ maxEdges?: number;
61
+ maxDepth?: number;
62
+ };
63
+ signal?: AbortSignal;
64
+ };
65
+ sources?: {
66
+ rootDir: string;
67
+ onDependency?: (dependency: SourceDependency) => void;
68
+ };
69
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
70
+ }
71
+ export interface DraftViewerInitV1 {
72
+ version: 1;
73
+ key: string;
74
+ source: string;
75
+ state: string | null;
76
+ label: string;
77
+ policyId: string;
78
+ }
79
+ export interface MarkdownInteractiveBridge {
80
+ clientModuleUrl: string;
81
+ policyId: string;
82
+ emitInit(data: DraftViewerInitV1): Promise<{
83
+ fileName: string;
84
+ url: string;
85
+ }>;
86
+ }
87
+ export declare function makeDiagnostic(code: string, message: string, location?: Diagnostic['location'], related?: Diagnostic['related']): Diagnostic;
88
+ export declare function contentHash(value: string | Uint8Array): string;
89
+ export declare function hostFailure(error: unknown, message?: string): Error;
90
+ export declare function isHostFailure(error: unknown): boolean;
91
+ export declare function isNoPlaceholder(error: unknown): boolean;
92
+ export declare function prepare(source: string, metadata: FenceOptions, location: Diagnostic['location'], document: DraftDocument, fenceIndex: number, options: DraftMarkdownOptions): Promise<{
93
+ node: Record<string, unknown>;
94
+ asset?: AssetReference;
95
+ diagnostics: Diagnostic[];
96
+ dependency?: string;
97
+ extraAssets?: AssetReference[];
98
+ clientModule?: string;
99
+ }>;
@@ -0,0 +1,12 @@
1
+ import type { EmitAsset, MarkdownInteractiveBridge } from './core.js';
2
+ export interface FileAssets {
3
+ directory: string;
4
+ emitAsset: EmitAsset;
5
+ emitInit: MarkdownInteractiveBridge['emitInit'];
6
+ copyTo(directory: string): Promise<void>;
7
+ }
8
+ /** Create a content-addressed Node file store for generated SVG and viewer JSON assets. */
9
+ export declare function createFileAssets(options: {
10
+ directory: string;
11
+ publicPath: string;
12
+ }): FileAssets;
@@ -0,0 +1,3 @@
1
+ export { parseFenceOptions } from './core.js';
2
+ export type { FenceOptions } from './metadata.js';
3
+ export type { AssetReference, Diagnostic, DraftAsset, DraftDocument, DraftDocumentResult, DraftMarkdownOptions, DraftViewerInitV1, EmitAsset, MarkdownInteractiveBridge, SourceDependency, } from './core.js';
@@ -0,0 +1,9 @@
1
+ export declare function isDraftFenceInfo(info: string): boolean;
2
+ export interface HtmlImage {
3
+ url: string;
4
+ alt: string;
5
+ width?: number;
6
+ height?: number;
7
+ }
8
+ export declare function escapeHtml(value: string): string;
9
+ export declare function imageHtml(image: HtmlImage): string;
@@ -0,0 +1,30 @@
1
+ import { type DraftDocumentResult, type DraftMarkdownOptions } from './core.js';
2
+ export interface DraftMarkdownItEnvironment {
3
+ draftably?: {
4
+ documentPath?: string;
5
+ };
6
+ [key: string]: unknown;
7
+ }
8
+ interface Token {
9
+ type: string;
10
+ info?: string;
11
+ content?: string;
12
+ map?: number[] | null;
13
+ children?: Token[] | null;
14
+ }
15
+ interface MarkdownItRenderHost<Tokens> {
16
+ parse(source: string, env?: unknown): Tokens[];
17
+ options: unknown;
18
+ renderer: {
19
+ render(tokens: Tokens[], options: unknown, env?: unknown): string;
20
+ };
21
+ }
22
+ export declare function createMarkdownItDraft(options: DraftMarkdownOptions): {
23
+ plugin: (instance: unknown) => void;
24
+ prepare: (tokens: Token[], env?: DraftMarkdownItEnvironment) => Promise<DraftDocumentResult>;
25
+ render: (md: MarkdownItRenderHost<unknown>, source: string, env?: DraftMarkdownItEnvironment) => Promise<{
26
+ html: string;
27
+ manifest: DraftDocumentResult;
28
+ }>;
29
+ };
30
+ export {};
@@ -0,0 +1,9 @@
1
+ export interface FenceOptions {
2
+ id?: string;
3
+ alt?: string;
4
+ caption?: string;
5
+ src?: string;
6
+ mode?: string;
7
+ state?: string;
8
+ }
9
+ export declare function parseFenceOptions(info: string): FenceOptions;
@@ -0,0 +1,21 @@
1
+ import { type DraftMarkdownOptions } from './core.js';
2
+ interface Node {
3
+ type: string;
4
+ lang?: string | null;
5
+ meta?: string | null;
6
+ value?: string;
7
+ children?: Node[];
8
+ position?: {
9
+ start?: {
10
+ line?: number;
11
+ column?: number;
12
+ };
13
+ };
14
+ }
15
+ interface FileLike {
16
+ path?: string;
17
+ data?: Record<string, unknown>;
18
+ }
19
+ /** Convert only parser-recognized `draft` code nodes into static image nodes. */
20
+ export default function remarkDraft(options: DraftMarkdownOptions): (tree: Node, file: FileLike) => Promise<void>;
21
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare function resolveSource(src: string, documentPath: string, rootDir: string): Promise<{
2
+ source: string;
3
+ path: string;
4
+ }>;
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@draftably/markdown",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Build static SVG diagram assets from Markdown draft fences.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/types/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./remark": {
18
+ "types": "./dist/types/remark.d.ts",
19
+ "import": "./dist/remark.js"
20
+ },
21
+ "./markdown-it": {
22
+ "types": "./dist/types/markdown-it.d.ts",
23
+ "import": "./dist/markdown-it.js"
24
+ },
25
+ "./files": {
26
+ "types": "./dist/types/files.d.ts",
27
+ "import": "./dist/files.js"
28
+ },
29
+ "./client": {
30
+ "types": "./dist/types/client.d.ts",
31
+ "import": "./dist/client.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "dependencies": {
36
+ "@draftably/runtime": "0.1.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=22.21.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/byronwall/draftably.git",
44
+ "directory": "packages/markdown"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org"
49
+ },
50
+ "scripts": {
51
+ "build": "node ../../scripts/build-markdown.ts",
52
+ "test": "node ../../scripts/prove-markdown-package.ts"
53
+ }
6
54
  }