@quillmark/quiver 0.1.0 → 0.2.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/dist/assert-node.d.ts +5 -0
- package/dist/assert-node.js +12 -0
- package/dist/bundle.d.ts +13 -0
- package/dist/bundle.js +33 -0
- package/dist/engine-types.d.ts +26 -0
- package/dist/engine-types.js +20 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +5 -0
- package/dist/pack.d.ts +25 -0
- package/dist/pack.js +120 -0
- package/dist/packed-loader.d.ts +27 -0
- package/dist/packed-loader.js +232 -0
- package/dist/quiver-yaml.d.ts +22 -0
- package/dist/quiver-yaml.js +63 -0
- package/dist/quiver.d.ts +74 -0
- package/dist/quiver.js +109 -0
- package/dist/ref.d.ts +14 -0
- package/dist/ref.js +44 -0
- package/dist/registry.d.ts +39 -0
- package/dist/registry.js +115 -0
- package/dist/semver.d.ts +8 -0
- package/dist/semver.js +44 -0
- package/dist/source-loader.d.ts +42 -0
- package/dist/source-loader.js +179 -0
- package/dist/testing.d.ts +39 -0
- package/dist/testing.js +108 -0
- package/dist/transports/fs-transport.d.ts +14 -0
- package/dist/transports/fs-transport.js +33 -0
- package/dist/transports/http-transport.d.ts +12 -0
- package/dist/transports/http-transport.js +33 -0
- package/package.json +17 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plug-and-play test suite for Quiver authors.
|
|
3
|
+
*
|
|
4
|
+
* Usage (place this file next to your Quiver.yaml):
|
|
5
|
+
*
|
|
6
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
7
|
+
* import { runQuiverTests } from "@quillmark/quiver/testing";
|
|
8
|
+
* const engine = await Quillmark.load();
|
|
9
|
+
* runQuiverTests(import.meta.url, engine);
|
|
10
|
+
*
|
|
11
|
+
* Requires vitest in your devDependencies.
|
|
12
|
+
*/
|
|
13
|
+
import type { QuillmarkLike, QuillLike } from "./engine-types.js";
|
|
14
|
+
export type { QuillmarkLike, QuillLike };
|
|
15
|
+
/**
|
|
16
|
+
* Returns a lightweight mock engine and a record of every tree passed to it.
|
|
17
|
+
* Useful for writing custom test helpers; not intended as a substitute for the
|
|
18
|
+
* real engine in runQuiverTests (the mock performs no template compilation).
|
|
19
|
+
*/
|
|
20
|
+
export declare function makeMockEngine(): {
|
|
21
|
+
calls: Array<Map<string, Uint8Array>>;
|
|
22
|
+
engine: QuillmarkLike;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Registers a Vitest describe block that validates every quill version in the
|
|
26
|
+
* quiver at `sourceDirOrMetaUrl` against the provided Quillmark engine.
|
|
27
|
+
*
|
|
28
|
+
* Pass `import.meta.url` when your test file lives at the quiver root (next to
|
|
29
|
+
* Quiver.yaml). Pass an absolute directory path for any other layout.
|
|
30
|
+
*
|
|
31
|
+
* Each (quill, version) pair gets its own `it()` so failures are reported
|
|
32
|
+
* individually. The quiver and engine are initialised once in `beforeAll`.
|
|
33
|
+
*
|
|
34
|
+
* Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml, all
|
|
35
|
+
* template files, and engine compilation via engine.quill(tree). A quill that
|
|
36
|
+
* contains a template error will cause its test to fail with the engine's own
|
|
37
|
+
* error message.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runQuiverTests(sourceDirOrMetaUrl: string, engine: QuillmarkLike): void;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plug-and-play test suite for Quiver authors.
|
|
3
|
+
*
|
|
4
|
+
* Usage (place this file next to your Quiver.yaml):
|
|
5
|
+
*
|
|
6
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
7
|
+
* import { runQuiverTests } from "@quillmark/quiver/testing";
|
|
8
|
+
* const engine = await Quillmark.load();
|
|
9
|
+
* runQuiverTests(import.meta.url, engine);
|
|
10
|
+
*
|
|
11
|
+
* Requires vitest in your devDependencies.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
14
|
+
import { readdirSync } from "node:fs";
|
|
15
|
+
import { join, basename } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { Quiver } from "./quiver.js";
|
|
18
|
+
import { QuiverRegistry } from "./registry.js";
|
|
19
|
+
/**
|
|
20
|
+
* Returns a lightweight mock engine and a record of every tree passed to it.
|
|
21
|
+
* Useful for writing custom test helpers; not intended as a substitute for the
|
|
22
|
+
* real engine in runQuiverTests (the mock performs no template compilation).
|
|
23
|
+
*/
|
|
24
|
+
export function makeMockEngine() {
|
|
25
|
+
const calls = [];
|
|
26
|
+
const engine = {
|
|
27
|
+
quill(tree) {
|
|
28
|
+
calls.push(tree);
|
|
29
|
+
return { render: () => ({ ok: true }) };
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
return { calls, engine };
|
|
33
|
+
}
|
|
34
|
+
function resolveSourceDir(sourceDirOrMetaUrl) {
|
|
35
|
+
if (sourceDirOrMetaUrl.startsWith("file://")) {
|
|
36
|
+
return fileURLToPath(new URL(".", sourceDirOrMetaUrl));
|
|
37
|
+
}
|
|
38
|
+
return sourceDirOrMetaUrl;
|
|
39
|
+
}
|
|
40
|
+
function discoverQuills(sourceDir) {
|
|
41
|
+
const quillsDir = join(sourceDir, "quills");
|
|
42
|
+
const results = [];
|
|
43
|
+
let nameDirs;
|
|
44
|
+
try {
|
|
45
|
+
nameDirs = readdirSync(quillsDir, { withFileTypes: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
for (const nameEntry of nameDirs) {
|
|
51
|
+
if (!nameEntry.isDirectory() || nameEntry.name.startsWith("."))
|
|
52
|
+
continue;
|
|
53
|
+
let versionDirs;
|
|
54
|
+
try {
|
|
55
|
+
versionDirs = readdirSync(join(quillsDir, nameEntry.name), {
|
|
56
|
+
withFileTypes: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const versionEntry of versionDirs) {
|
|
63
|
+
if (!versionEntry.isDirectory() || versionEntry.name.startsWith("."))
|
|
64
|
+
continue;
|
|
65
|
+
results.push({ name: nameEntry.name, version: versionEntry.name });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Registers a Vitest describe block that validates every quill version in the
|
|
72
|
+
* quiver at `sourceDirOrMetaUrl` against the provided Quillmark engine.
|
|
73
|
+
*
|
|
74
|
+
* Pass `import.meta.url` when your test file lives at the quiver root (next to
|
|
75
|
+
* Quiver.yaml). Pass an absolute directory path for any other layout.
|
|
76
|
+
*
|
|
77
|
+
* Each (quill, version) pair gets its own `it()` so failures are reported
|
|
78
|
+
* individually. The quiver and engine are initialised once in `beforeAll`.
|
|
79
|
+
*
|
|
80
|
+
* Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml, all
|
|
81
|
+
* template files, and engine compilation via engine.quill(tree). A quill that
|
|
82
|
+
* contains a template error will cause its test to fail with the engine's own
|
|
83
|
+
* error message.
|
|
84
|
+
*/
|
|
85
|
+
export function runQuiverTests(sourceDirOrMetaUrl, engine) {
|
|
86
|
+
const sourceDir = resolveSourceDir(sourceDirOrMetaUrl);
|
|
87
|
+
// Enumerate quills synchronously so Vitest can collect test cases before
|
|
88
|
+
// any async work begins. Errors here (missing quills/ dir, unreadable dirs)
|
|
89
|
+
// surface as the "has at least one quill" test failing rather than a
|
|
90
|
+
// collection-time crash.
|
|
91
|
+
const quills = discoverQuills(sourceDir);
|
|
92
|
+
describe(`Quiver: ${basename(sourceDir)}`, () => {
|
|
93
|
+
let registry;
|
|
94
|
+
beforeAll(async () => {
|
|
95
|
+
const quiver = await Quiver.fromSourceDir(sourceDir);
|
|
96
|
+
registry = new QuiverRegistry({ engine, quivers: [quiver] });
|
|
97
|
+
});
|
|
98
|
+
it("has at least one quill", () => {
|
|
99
|
+
expect(quills).not.toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
for (const { name, version } of quills) {
|
|
102
|
+
it(`${name}@${version} compiles without error`, async () => {
|
|
103
|
+
const quill = await registry.getQuill(`${name}@${version}`);
|
|
104
|
+
expect(typeof quill.render).toBe("function");
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FsTransport — Node-only packed quiver transport that reads from local disk.
|
|
3
|
+
* Internal; not exported from index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Uses dynamic imports of node:fs/promises and node:path so the module can be
|
|
6
|
+
* imported in environments where those builtins are not available (the dynamic
|
|
7
|
+
* import only executes when fetchBytes is actually called).
|
|
8
|
+
*/
|
|
9
|
+
import type { PackedTransport } from "../packed-loader.js";
|
|
10
|
+
export declare class FsTransport implements PackedTransport {
|
|
11
|
+
private rootDir;
|
|
12
|
+
constructor(rootDir: string);
|
|
13
|
+
fetchBytes(relativePath: string): Promise<Uint8Array>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FsTransport — Node-only packed quiver transport that reads from local disk.
|
|
3
|
+
* Internal; not exported from index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Uses dynamic imports of node:fs/promises and node:path so the module can be
|
|
6
|
+
* imported in environments where those builtins are not available (the dynamic
|
|
7
|
+
* import only executes when fetchBytes is actually called).
|
|
8
|
+
*/
|
|
9
|
+
import { QuiverError } from "../errors.js";
|
|
10
|
+
export class FsTransport {
|
|
11
|
+
rootDir;
|
|
12
|
+
constructor(rootDir) {
|
|
13
|
+
this.rootDir = rootDir;
|
|
14
|
+
}
|
|
15
|
+
async fetchBytes(relativePath) {
|
|
16
|
+
const { join, resolve } = await import("node:path");
|
|
17
|
+
const { readFile } = await import("node:fs/promises");
|
|
18
|
+
const rootResolved = resolve(this.rootDir);
|
|
19
|
+
const fullPath = resolve(join(this.rootDir, relativePath));
|
|
20
|
+
// Defense-in-depth: ensure resolved path stays within rootDir.
|
|
21
|
+
if (!fullPath.startsWith(rootResolved + "/") && fullPath !== rootResolved) {
|
|
22
|
+
throw new QuiverError("transport_error", `Path traversal detected: "${relativePath}" escapes quiver root`);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const buf = await readFile(fullPath);
|
|
26
|
+
// Ensure we return a plain Uint8Array, not a Node.js Buffer subclass.
|
|
27
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new QuiverError("transport_error", `Failed to read "${relativePath}" from packed quiver at "${this.rootDir}": ${err.message}`, { cause: err });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpTransport — browser-safe packed quiver transport that fetches via HTTP.
|
|
3
|
+
* Internal; not exported from index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Uses globalThis.fetch — no node: imports at any level.
|
|
6
|
+
*/
|
|
7
|
+
import type { PackedTransport } from "../packed-loader.js";
|
|
8
|
+
export declare class HttpTransport implements PackedTransport {
|
|
9
|
+
private readonly baseUrl;
|
|
10
|
+
constructor(baseUrl: string);
|
|
11
|
+
fetchBytes(relativePath: string): Promise<Uint8Array>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpTransport — browser-safe packed quiver transport that fetches via HTTP.
|
|
3
|
+
* Internal; not exported from index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Uses globalThis.fetch — no node: imports at any level.
|
|
6
|
+
*/
|
|
7
|
+
import { QuiverError } from "../errors.js";
|
|
8
|
+
export class HttpTransport {
|
|
9
|
+
baseUrl;
|
|
10
|
+
constructor(baseUrl) {
|
|
11
|
+
// Normalize: ensure exactly one trailing slash.
|
|
12
|
+
this.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
13
|
+
}
|
|
14
|
+
async fetchBytes(relativePath) {
|
|
15
|
+
// Strip any leading slash from relativePath to avoid double slashes.
|
|
16
|
+
const cleanPath = relativePath.startsWith("/")
|
|
17
|
+
? relativePath.slice(1)
|
|
18
|
+
: relativePath;
|
|
19
|
+
const url = `${this.baseUrl}${cleanPath}`;
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await globalThis.fetch(url);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
throw new QuiverError("transport_error", `Network error fetching "${url}": ${err.message}`, { cause: err });
|
|
26
|
+
}
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new QuiverError("transport_error", `HTTP ${response.status} fetching "${url}"`);
|
|
29
|
+
}
|
|
30
|
+
const buffer = await response.arrayBuffer();
|
|
31
|
+
return new Uint8Array(buffer);
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Quiver registry and packaging for Quillmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/quillmark
|
|
9
|
+
"url": "git+https://github.com/nibsbin/quillmark-quiver.git"
|
|
10
10
|
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/nibsbin/quillmark-quiver/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/nibsbin/quillmark-quiver#readme",
|
|
11
15
|
"keywords": [
|
|
12
16
|
"quillmark",
|
|
13
17
|
"quiver",
|
|
@@ -26,6 +30,10 @@
|
|
|
26
30
|
"./node": {
|
|
27
31
|
"types": "./dist/node.d.ts",
|
|
28
32
|
"import": "./dist/node.js"
|
|
33
|
+
},
|
|
34
|
+
"./testing": {
|
|
35
|
+
"types": "./dist/testing.d.ts",
|
|
36
|
+
"import": "./dist/testing.js"
|
|
29
37
|
}
|
|
30
38
|
},
|
|
31
39
|
"files": [
|
|
@@ -34,7 +42,13 @@
|
|
|
34
42
|
"README.md"
|
|
35
43
|
],
|
|
36
44
|
"peerDependencies": {
|
|
37
|
-
"@quillmark/wasm": ">=0.59.0-rc.2"
|
|
45
|
+
"@quillmark/wasm": ">=0.59.0-rc.2",
|
|
46
|
+
"vitest": ">=4.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"vitest": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
38
52
|
},
|
|
39
53
|
"dependencies": {
|
|
40
54
|
"fflate": "^0.8.2",
|