@quillmark/quiver 0.16.0 → 0.18.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/dist/preview.js DELETED
@@ -1,208 +0,0 @@
1
- /**
2
- * Manual-validation helper for Quiver authors.
3
- *
4
- * `runQuiverTests` (`@quillmark/quiver/testing`) proves every quill *compiles*
5
- * — it never produces a rendered artifact a human can look at. This module
6
- * closes that gap: it renders each quill's example document, writes the
7
- * artifacts to a directory, and emits an `index.html` gallery so an author can
8
- * eyeball real output before publishing.
9
- *
10
- * Node-only: writes files and loads a source quiver from disk.
11
- *
12
- * Usage (place a script next to your Quiver.yaml):
13
- *
14
- * import { Engine } from "@quillmark/wasm";
15
- * import { renderQuiverSamples } from "@quillmark/quiver/preview";
16
- *
17
- * await renderQuiverSamples(import.meta.url, { engine: new Engine() });
18
- * // → open ./preview/index.html
19
- *
20
- * The sample document is the illustrative example seeded by
21
- * `quill.seedDocument()` for each quill version — a fully filled-out,
22
- * always-renderable document (the blueprint itself carries `<must-fill>`
23
- * sentinels and is not renderable). Every quill always seeds an example, so no
24
- * quills are skipped for lack of a sample document.
25
- *
26
- * A `.gitignore` is written into `outDir` so the generated artifacts are not
27
- * accidentally committed.
28
- */
29
- import { mkdir, writeFile } from "node:fs/promises";
30
- import { join } from "node:path";
31
- import { Quiver } from "./node.js";
32
- import { isQuillmarkError } from "@quillmark/wasm";
33
- /** Default directory rendered artifacts are written to. */
34
- const DEFAULT_OUT_DIR = "preview";
35
- /**
36
- * Renders every quill's example document and writes the artifacts plus an
37
- * `index.html` gallery to `outDir`.
38
- *
39
- * Does NOT fail fast: a quill that throws is recorded as `failed` and the
40
- * run continues, so one broken quill never hides the others. Inspect the
41
- * returned array (or `index.html`) for the full picture.
42
- *
43
- * @param metaUrlOrDir `import.meta.url` when called from the quiver root, or
44
- * an absolute path to the source quiver directory.
45
- */
46
- export async function renderQuiverSamples(metaUrlOrDir, opts) {
47
- const outDir = opts.outDir ?? DEFAULT_OUT_DIR;
48
- const quiver = await Quiver.fromDir(metaUrlOrDir);
49
- await mkdir(outDir, { recursive: true });
50
- const results = [];
51
- for (const name of quiver.quillNames()) {
52
- for (const version of quiver.versionsOf(name)) {
53
- if (!isSelected(name, `${name}@${version}`, opts))
54
- continue;
55
- results.push(await renderOne(quiver, name, version, outDir, opts));
56
- }
57
- }
58
- await writeFile(join(outDir, ".gitignore"), "*\n");
59
- await writeFile(join(outDir, "index.html"), renderIndexHtml(quiver.name, results));
60
- if (!opts.quiet)
61
- printSummary(quiver.name, outDir, results);
62
- return results;
63
- }
64
- /** Whether a quill passes the `include`/`exclude` filters. */
65
- function isSelected(name, ref, opts) {
66
- const matches = (list) => list.includes(name) || list.includes(ref);
67
- if (opts.include && !matches(opts.include))
68
- return false;
69
- if (opts.exclude && matches(opts.exclude))
70
- return false;
71
- return true;
72
- }
73
- /**
74
- * Formats a thrown render error into one string per diagnostic. `@quillmark/wasm`
75
- * throws `QuillmarkError`s (an `Error` carrying `diagnostics`); fall back to
76
- * the message for anything else.
77
- */
78
- function failureReasons(err) {
79
- if (isQuillmarkError(err) && err.diagnostics.length > 0) {
80
- return err.diagnostics.map((d) => `${d.severity}: ${d.message}`);
81
- }
82
- return [err.message];
83
- }
84
- async function renderOne(quiver, name, version, outDir, opts) {
85
- const ref = `${name}@${version}`;
86
- let result;
87
- try {
88
- // Canonical render path: a core Quill + its seeded Document handed straight
89
- // to the Engine, which clones them into the backend and frees the clones.
90
- const quill = await quiver.getQuill(ref);
91
- const doc = quill.seedDocument();
92
- try {
93
- result = await opts.engine.render(quill, doc, opts.format ? { format: opts.format } : undefined);
94
- }
95
- finally {
96
- doc.free();
97
- }
98
- }
99
- catch (err) {
100
- return {
101
- ref,
102
- status: "failed",
103
- files: [],
104
- warnings: [],
105
- reasons: failureReasons(err),
106
- };
107
- }
108
- const warnings = (result.warnings ?? []).map((w) => `${w.severity}: ${w.message}`);
109
- const artifacts = result.artifacts ?? [];
110
- if (artifacts.length === 0) {
111
- return {
112
- ref,
113
- status: "failed",
114
- files: [],
115
- warnings,
116
- reasons: ["render produced no artifacts"],
117
- };
118
- }
119
- const files = [];
120
- for (let i = 0; i < artifacts.length; i++) {
121
- const artifact = artifacts[i];
122
- const suffix = artifacts.length > 1 ? `.${i}` : "";
123
- const fileName = `${ref}${suffix}.${artifact.format}`;
124
- await writeFile(join(outDir, fileName), artifact.bytes);
125
- files.push(fileName);
126
- }
127
- return { ref, status: "rendered", files, warnings, reasons: [] };
128
- }
129
- function printSummary(quiverName, outDir, results) {
130
- const count = (s) => results.filter((r) => r.status === s).length;
131
- console.log(`\nQuiver "${quiverName}" — sample render`);
132
- for (const r of results) {
133
- const detail = r.status === "rendered" ? r.files.join(", ") : (r.reasons[0] ?? "");
134
- console.log(` [${r.status.padEnd(8)}] ${r.ref}${detail ? ` — ${detail}` : ""}`);
135
- for (const extra of r.reasons.slice(1))
136
- console.log(` ${extra}`);
137
- for (const w of r.warnings)
138
- console.log(` ⚠ ${w}`);
139
- }
140
- console.log(`\n${count("rendered")} rendered, ${count("failed")} failed`);
141
- console.log(`Open ${join(outDir, "index.html")} to review.\n`);
142
- }
143
- function escapeHtml(text) {
144
- return text
145
- .replace(/&/g, "&amp;")
146
- .replace(/</g, "&lt;")
147
- .replace(/>/g, "&gt;")
148
- .replace(/"/g, "&quot;");
149
- }
150
- function embedArtifact(fileName) {
151
- const ext = fileName.slice(fileName.lastIndexOf(".") + 1).toLowerCase();
152
- const src = escapeHtml(fileName);
153
- if (ext === "pdf") {
154
- return `<iframe class="art" src="${src}" title="${src}"></iframe>`;
155
- }
156
- if (ext === "png" || ext === "svg") {
157
- return `<img class="art" src="${src}" alt="${src}" />`;
158
- }
159
- return `<a href="${src}">${src}</a>`;
160
- }
161
- function renderIndexHtml(quiverName, results) {
162
- const cards = results
163
- .map((r) => {
164
- const body = r.status === "rendered"
165
- ? r.files.map(embedArtifact).join("\n")
166
- : `<ul class="reasons">${r.reasons
167
- .map((reason) => `<li>${escapeHtml(reason)}</li>`)
168
- .join("")}</ul>`;
169
- const warnings = r.warnings.length
170
- ? `<ul class="warnings">${r.warnings
171
- .map((w) => `<li>${escapeHtml(w)}</li>`)
172
- .join("")}</ul>`
173
- : "";
174
- return `<section class="card ${r.status}">
175
- <h2>${escapeHtml(r.ref)} <span class="badge">${r.status}</span></h2>
176
- ${warnings}
177
- ${body}
178
- </section>`;
179
- })
180
- .join("\n");
181
- return `<!doctype html>
182
- <html lang="en">
183
- <head>
184
- <meta charset="utf-8" />
185
- <title>Quiver preview — ${escapeHtml(quiverName)}</title>
186
- <style>
187
- body { font-family: system-ui, sans-serif; margin: 2rem; background: #fafafa; }
188
- h1 { font-size: 1.4rem; }
189
- .card { background: #fff; border: 1px solid #ddd; border-radius: 8px;
190
- padding: 1rem; margin: 1rem 0; }
191
- .card.failed { border-color: #e0b4b4; }
192
- h2 { font-size: 1rem; margin: 0 0 0.5rem; }
193
- .badge { font-size: 0.7rem; text-transform: uppercase; background: #eee;
194
- border-radius: 4px; padding: 2px 6px; vertical-align: middle; }
195
- .failed .badge { background: #f3d2d2; }
196
- .art { width: 100%; height: 600px; border: 1px solid #eee; }
197
- img.art { height: auto; }
198
- .reasons { color: #a33; font-style: italic; }
199
- .warnings { color: #96690a; font-size: 0.85rem; }
200
- </style>
201
- </head>
202
- <body>
203
- <h1>Quiver preview — ${escapeHtml(quiverName)}</h1>
204
- ${cards}
205
- </body>
206
- </html>
207
- `;
208
- }
package/dist/testing.d.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Convenience test harness for Quiver authors using `node:test`.
3
- *
4
- * Built into Node 18+; no extra test-runner dependency required. If you
5
- * prefer vitest, jest, or another runner, write a 12-line loop against
6
- * the main API instead — every primitive used here is public.
7
- *
8
- * Usage (place this file next to your Quiver.yaml):
9
- *
10
- * import { Engine } from "@quillmark/wasm";
11
- * import { runQuiverTests } from "@quillmark/quiver/testing";
12
- * runQuiverTests(import.meta.url, new Engine());
13
- *
14
- * Run with `node --test`.
15
- */
16
- import type { Engine } from "@quillmark/wasm";
17
- /**
18
- * Registers a `node:test` describe block that validates every quill
19
- * version in the quiver at `metaUrlOrDir` against the provided engine.
20
- *
21
- * Pass `import.meta.url` when this file lives at the quiver root (next
22
- * to Quiver.yaml). Pass an absolute directory path for any other layout.
23
- *
24
- * Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml,
25
- * all template files, quill construction via Quill.fromTree(tree), and a
26
- * full render of each quill's example document via engine.render(quill, doc).
27
- */
28
- export declare function runQuiverTests(metaUrlOrDir: string, engine: Engine): void;
package/dist/testing.js DELETED
@@ -1,66 +0,0 @@
1
- /**
2
- * Convenience test harness for Quiver authors using `node:test`.
3
- *
4
- * Built into Node 18+; no extra test-runner dependency required. If you
5
- * prefer vitest, jest, or another runner, write a 12-line loop against
6
- * the main API instead — every primitive used here is public.
7
- *
8
- * Usage (place this file next to your Quiver.yaml):
9
- *
10
- * import { Engine } from "@quillmark/wasm";
11
- * import { runQuiverTests } from "@quillmark/quiver/testing";
12
- * runQuiverTests(import.meta.url, new Engine());
13
- *
14
- * Run with `node --test`.
15
- */
16
- import { describe, it, before } from "node:test";
17
- // Import from the Node entry: this installs the runtime patch so
18
- // `Quiver.fromDir` is callable at runtime, and gives us the augmented
19
- // static-method type signature.
20
- import { Quiver } from "./node.js";
21
- /**
22
- * Registers a `node:test` describe block that validates every quill
23
- * version in the quiver at `metaUrlOrDir` against the provided engine.
24
- *
25
- * Pass `import.meta.url` when this file lives at the quiver root (next
26
- * to Quiver.yaml). Pass an absolute directory path for any other layout.
27
- *
28
- * Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml,
29
- * all template files, quill construction via Quill.fromTree(tree), and a
30
- * full render of each quill's example document via engine.render(quill, doc).
31
- */
32
- export function runQuiverTests(metaUrlOrDir, engine) {
33
- describe("Quiver", () => {
34
- let quiver;
35
- before(async () => {
36
- quiver = await Quiver.fromDir(metaUrlOrDir);
37
- });
38
- it("has at least one quill", () => {
39
- if (quiver.quillNames().length === 0) {
40
- throw new Error("Quiver has no quills");
41
- }
42
- });
43
- it("compiles and renders every quill's example without error", async () => {
44
- for (const name of quiver.quillNames()) {
45
- for (const version of quiver.versionsOf(name)) {
46
- const ref = `${name}@${version}`;
47
- // The Engine takes the core Quill + its seeded Document directly and
48
- // clones them into the backend; the doc clone is freed inside render,
49
- // and we free our own core Document here.
50
- const quill = await quiver.getQuill(ref);
51
- const doc = quill.seedDocument();
52
- let result;
53
- try {
54
- result = await engine.render(quill, doc);
55
- }
56
- finally {
57
- doc.free();
58
- }
59
- if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
60
- throw new Error(`${ref}: example render produced no artifacts`);
61
- }
62
- }
63
- }
64
- });
65
- });
66
- }