@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/LICENSE +201 -21
- package/NOTICE +2 -0
- package/README.md +84 -245
- package/dist/bin/quiver.d.ts +6 -6
- package/dist/bin/quiver.js +31 -63
- package/dist/build.d.ts +12 -12
- package/dist/build.js +78 -33
- package/dist/built-loader.d.ts +16 -16
- package/dist/built-loader.js +126 -112
- package/dist/bundle.js +1 -1
- package/dist/digest.d.ts +19 -0
- package/dist/digest.js +25 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/format.d.ts +11 -0
- package/dist/format.js +11 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -5
- package/dist/node.d.ts +47 -76
- package/dist/node.js +59 -64
- package/dist/quiver-yaml.js +19 -19
- package/dist/quiver.d.ts +25 -37
- package/dist/quiver.js +46 -113
- package/dist/ref.d.ts +3 -3
- package/dist/ref.js +15 -13
- package/dist/semver.d.ts +5 -1
- package/dist/semver.js +9 -10
- package/dist/source-loader.d.ts +3 -3
- package/dist/source-loader.js +26 -26
- package/dist/transports/fs-built-transport.d.ts +2 -2
- package/dist/transports/fs-built-transport.js +10 -8
- package/dist/transports/http-transport.d.ts +2 -2
- package/dist/transports/http-transport.js +11 -9
- package/package.json +68 -72
- package/dist/preview.d.ts +0 -81
- package/dist/preview.js +0 -208
- package/dist/testing.d.ts +0 -28
- package/dist/testing.js +0 -66
package/dist/bin/quiver.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Quiver CLI — build
|
|
3
|
+
* Quiver CLI — build and test a quiver collection. Two verbs, and they are the
|
|
4
|
+
* whole of what a quiver author gets from here: `build` packs, `test` gates.
|
|
5
|
+
* Looking at rendered output is studio's, an app rather than a verb.
|
|
4
6
|
*
|
|
5
7
|
* Commands:
|
|
6
|
-
* quiver build [--out <dir>]
|
|
7
|
-
* quiver test
|
|
8
|
-
* quiver preview [--out <dir>] [--format <fmt>] [--quiet]
|
|
9
|
-
* [--include <ref>...] [--exclude <ref>...]
|
|
8
|
+
* quillmark-quiver build [--out <dir>]
|
|
9
|
+
* quillmark-quiver test
|
|
10
10
|
*
|
|
11
|
-
* Engine discovery (for `test`
|
|
11
|
+
* Engine discovery (for `test`):
|
|
12
12
|
* 1. Named export `engine` from `quiver.config.js` at the collection root,
|
|
13
13
|
* if the file exists.
|
|
14
14
|
* 2. Auto-imported `@quillmark/wasm` from the collection's node_modules.
|
package/dist/bin/quiver.js
CHANGED
|
@@ -1,43 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Quiver CLI — build
|
|
3
|
+
* Quiver CLI — build and test a quiver collection. Two verbs, and they are the
|
|
4
|
+
* whole of what a quiver author gets from here: `build` packs, `test` gates.
|
|
5
|
+
* Looking at rendered output is studio's, an app rather than a verb.
|
|
4
6
|
*
|
|
5
7
|
* Commands:
|
|
6
|
-
* quiver build [--out <dir>]
|
|
7
|
-
* quiver test
|
|
8
|
-
* quiver preview [--out <dir>] [--format <fmt>] [--quiet]
|
|
9
|
-
* [--include <ref>...] [--exclude <ref>...]
|
|
8
|
+
* quillmark-quiver build [--out <dir>]
|
|
9
|
+
* quillmark-quiver test
|
|
10
10
|
*
|
|
11
|
-
* Engine discovery (for `test`
|
|
11
|
+
* Engine discovery (for `test`):
|
|
12
12
|
* 1. Named export `engine` from `quiver.config.js` at the collection root,
|
|
13
13
|
* if the file exists.
|
|
14
14
|
* 2. Auto-imported `@quillmark/wasm` from the collection's node_modules.
|
|
15
15
|
*/
|
|
16
|
-
import { createRequire } from
|
|
17
|
-
import { join } from
|
|
18
|
-
import { pathToFileURL } from
|
|
19
|
-
import {
|
|
20
|
-
import { renderQuiverSamples } from "../preview.js";
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import { build as buildQuiver, fromDir } from '../node.js';
|
|
21
20
|
// ---------------------------------------------------------------------------
|
|
22
21
|
// Arg parsing helpers
|
|
23
22
|
// ---------------------------------------------------------------------------
|
|
24
23
|
const argv = process.argv.slice(2);
|
|
25
24
|
const command = argv[0];
|
|
25
|
+
/** One flag with a value. `build --out` is the only one either verb takes. */
|
|
26
26
|
function flag(name) {
|
|
27
27
|
const i = argv.indexOf(name);
|
|
28
28
|
return i !== -1 && i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
29
29
|
}
|
|
30
|
-
function multiFlag(name) {
|
|
31
|
-
const out = [];
|
|
32
|
-
for (let i = 0; i < argv.length - 1; i++) {
|
|
33
|
-
if (argv[i] === name)
|
|
34
|
-
out.push(argv[i + 1]);
|
|
35
|
-
}
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
function hasFlag(name) {
|
|
39
|
-
return argv.includes(name);
|
|
40
|
-
}
|
|
41
30
|
// ---------------------------------------------------------------------------
|
|
42
31
|
// Engine discovery
|
|
43
32
|
// ---------------------------------------------------------------------------
|
|
@@ -47,16 +36,21 @@ async function loadEngine(cwd) {
|
|
|
47
36
|
// backend itself). A quiver.config.js may export a custom `engine`.
|
|
48
37
|
let wasm;
|
|
49
38
|
try {
|
|
50
|
-
const req = createRequire(pathToFileURL(join(cwd,
|
|
51
|
-
const wasmPath = req.resolve(
|
|
39
|
+
const req = createRequire(pathToFileURL(join(cwd, 'package.json')).href);
|
|
40
|
+
const wasmPath = req.resolve('@quillmark/wasm');
|
|
52
41
|
wasm = (await import(pathToFileURL(wasmPath).href));
|
|
42
|
+
// Instantiate the core. Every `@quillmark/wasm` export throws
|
|
43
|
+
// `runtime::not_initialized` until `init()` resolves, and `new Engine()` is
|
|
44
|
+
// lazy, so without this the throw arrives at the first render and reads as a
|
|
45
|
+
// failing quill rather than an uninitialized runtime.
|
|
46
|
+
await wasm.init();
|
|
53
47
|
}
|
|
54
48
|
catch {
|
|
55
49
|
// Not installed — a quiver.config.js may still provide an engine.
|
|
56
50
|
}
|
|
57
51
|
// 1. Named `engine` export from quiver.config.js, if present.
|
|
58
52
|
try {
|
|
59
|
-
const cfg = await import(pathToFileURL(join(cwd,
|
|
53
|
+
const cfg = await import(pathToFileURL(join(cwd, 'quiver.config.js')).href);
|
|
60
54
|
if (cfg.engine != null) {
|
|
61
55
|
return cfg.engine;
|
|
62
56
|
}
|
|
@@ -67,8 +61,8 @@ async function loadEngine(cwd) {
|
|
|
67
61
|
// 2. Auto-discover from @quillmark/wasm.
|
|
68
62
|
if (wasm == null) {
|
|
69
63
|
throw new Error("Cannot find @quillmark/wasm in this collection's node_modules.\n" +
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
' Install it: npm install @quillmark/wasm\n' +
|
|
65
|
+
' Or export { engine } from quiver.config.js for a custom engine.');
|
|
72
66
|
}
|
|
73
67
|
return new wasm.Engine();
|
|
74
68
|
}
|
|
@@ -77,18 +71,18 @@ async function loadEngine(cwd) {
|
|
|
77
71
|
// ---------------------------------------------------------------------------
|
|
78
72
|
async function build() {
|
|
79
73
|
const cwd = process.cwd();
|
|
80
|
-
const outDir = flag(
|
|
81
|
-
console.log(`quiver build: ${cwd} → ${outDir}`);
|
|
82
|
-
await
|
|
83
|
-
console.log(
|
|
74
|
+
const outDir = flag('--out') ?? 'dist';
|
|
75
|
+
console.log(`quillmark-quiver build: ${cwd} → ${outDir}`);
|
|
76
|
+
await buildQuiver(cwd, outDir);
|
|
77
|
+
console.log('done.');
|
|
84
78
|
}
|
|
85
79
|
async function test() {
|
|
86
80
|
const cwd = process.cwd();
|
|
87
81
|
const engine = await loadEngine(cwd);
|
|
88
|
-
const quiver = await
|
|
82
|
+
const quiver = await fromDir(cwd);
|
|
89
83
|
const names = quiver.quillNames();
|
|
90
84
|
if (names.length === 0) {
|
|
91
|
-
console.error(
|
|
85
|
+
console.error('error: quiver has no quills');
|
|
92
86
|
process.exit(1);
|
|
93
87
|
}
|
|
94
88
|
let pass = 0;
|
|
@@ -107,7 +101,7 @@ async function test() {
|
|
|
107
101
|
doc.free();
|
|
108
102
|
}
|
|
109
103
|
if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
|
|
110
|
-
throw new Error(
|
|
104
|
+
throw new Error('example render produced no artifacts');
|
|
111
105
|
}
|
|
112
106
|
console.log(`pass ${ref}`);
|
|
113
107
|
pass++;
|
|
@@ -123,34 +117,11 @@ async function test() {
|
|
|
123
117
|
if (fail > 0)
|
|
124
118
|
process.exit(1);
|
|
125
119
|
}
|
|
126
|
-
async function preview() {
|
|
127
|
-
const cwd = process.cwd();
|
|
128
|
-
const engine = await loadEngine(cwd);
|
|
129
|
-
const outDir = flag("--out");
|
|
130
|
-
const format = flag("--format");
|
|
131
|
-
const quiet = hasFlag("--quiet");
|
|
132
|
-
const include = multiFlag("--include");
|
|
133
|
-
const exclude = multiFlag("--exclude");
|
|
134
|
-
await renderQuiverSamples(cwd, {
|
|
135
|
-
engine,
|
|
136
|
-
...(outDir !== undefined && { outDir }),
|
|
137
|
-
...(format !== undefined && { format }),
|
|
138
|
-
quiet,
|
|
139
|
-
...(include.length > 0 && { include }),
|
|
140
|
-
...(exclude.length > 0 && { exclude }),
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
120
|
// ---------------------------------------------------------------------------
|
|
144
121
|
// Dispatch
|
|
145
122
|
// ---------------------------------------------------------------------------
|
|
146
123
|
function usage() {
|
|
147
|
-
console.error([
|
|
148
|
-
"Usage:",
|
|
149
|
-
" quiver build [--out <dir>]",
|
|
150
|
-
" quiver test",
|
|
151
|
-
" quiver preview [--out <dir>] [--format <fmt>] [--quiet]",
|
|
152
|
-
" [--include <ref>...] [--exclude <ref>...]",
|
|
153
|
-
].join("\n"));
|
|
124
|
+
console.error(['Usage:', ' quillmark-quiver build [--out <dir>]', ' quillmark-quiver test'].join('\n'));
|
|
154
125
|
}
|
|
155
126
|
function die(err) {
|
|
156
127
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -158,15 +129,12 @@ function die(err) {
|
|
|
158
129
|
process.exit(1);
|
|
159
130
|
}
|
|
160
131
|
switch (command) {
|
|
161
|
-
case
|
|
132
|
+
case 'build':
|
|
162
133
|
build().catch(die);
|
|
163
134
|
break;
|
|
164
|
-
case
|
|
135
|
+
case 'test':
|
|
165
136
|
test().catch(die);
|
|
166
137
|
break;
|
|
167
|
-
case "preview":
|
|
168
|
-
preview().catch(die);
|
|
169
|
-
break;
|
|
170
138
|
default:
|
|
171
139
|
usage();
|
|
172
140
|
process.exit(1);
|
package/dist/build.d.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build logic — internal, Node-only.
|
|
3
3
|
*
|
|
4
|
-
* All Node.js built-in imports are done dynamically inside `buildQuiver
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* All Node.js built-in imports are done dynamically inside `buildQuiver`, so
|
|
5
|
+
* a module reaching this one for types alone does not pull `node:fs` or
|
|
6
|
+
* `node:crypto` into a browser bundle.
|
|
7
7
|
*/
|
|
8
|
-
/** Reserved for future build options (e.g. compression level, filters). */
|
|
9
|
-
export type BuildOptions = Record<string, never>;
|
|
10
8
|
/**
|
|
11
9
|
* Reads a Source Quiver, validates it, and writes the build output to outDir.
|
|
12
10
|
*
|
|
13
|
-
* Output layout
|
|
11
|
+
* Output layout. Every name but the pointer carries the SHA-256 of what it
|
|
12
|
+
* names, which is what the loader checks on fetch:
|
|
14
13
|
* outDir/
|
|
15
|
-
* latest.json
|
|
16
|
-
* manifest.<
|
|
17
|
-
* <name>@<version>.<
|
|
14
|
+
* latest.json # the format, and a stable pointer to the manifest
|
|
15
|
+
* manifest.<sha256:12>.json # hashed manifest
|
|
16
|
+
* <name>@<version>.<sha256:12>.zip # one bundle per quill
|
|
18
17
|
* store/
|
|
19
|
-
* <
|
|
18
|
+
* <sha256> # dehydrated font bytes (full hash, no ext)
|
|
20
19
|
*
|
|
21
20
|
* Throws:
|
|
22
21
|
* - `quiver_invalid` on source validation failures (propagated from scanner)
|
|
23
|
-
* - `transport_error` on I/O failures
|
|
22
|
+
* - `transport_error` on I/O failures, and on an outDir the build would have
|
|
23
|
+
* to delete something it does not own to write (see `assertSafeOutDir`)
|
|
24
24
|
*/
|
|
25
|
-
export declare function buildQuiver(sourceDir: string, outDir: string
|
|
25
|
+
export declare function buildQuiver(sourceDir: string, outDir: string): Promise<void>;
|
package/dist/build.js
CHANGED
|
@@ -1,50 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build logic — internal, Node-only.
|
|
3
3
|
*
|
|
4
|
-
* All Node.js built-in imports are done dynamically inside `buildQuiver
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* All Node.js built-in imports are done dynamically inside `buildQuiver`, so
|
|
5
|
+
* a module reaching this one for types alone does not pull `node:fs` or
|
|
6
|
+
* `node:crypto` into a browser bundle.
|
|
7
7
|
*/
|
|
8
|
-
import { QuiverError } from
|
|
9
|
-
import { packFiles } from
|
|
8
|
+
import { QuiverError } from './errors.js';
|
|
9
|
+
import { packFiles } from './bundle.js';
|
|
10
|
+
import { NAME_DIGEST_LENGTH } from './digest.js';
|
|
11
|
+
import { POINTER_FORMAT } from './format.js';
|
|
10
12
|
/** Font file extensions recognised by the builder (case-insensitive). */
|
|
11
13
|
const FONT_EXT = /\.(ttf|otf|woff|woff2)$/i;
|
|
14
|
+
/**
|
|
15
|
+
* The build's first act is `rm(outDir, { recursive: true })`, so an outDir that
|
|
16
|
+
* is, or contains, the source quiver or the working directory deletes the
|
|
17
|
+
* thing the caller was building from. `quillmark-quiver build --out .` and a
|
|
18
|
+
* mistyped `--out ..` are both one keystroke away, and the failure is
|
|
19
|
+
* unrecoverable, so an outDir that owns the caller is refused up front.
|
|
20
|
+
*
|
|
21
|
+
* An outDir *inside* sourceDir stays allowed: the scan reads the source before
|
|
22
|
+
* any write, and `dist/` under the quiver root is the ordinary layout.
|
|
23
|
+
*
|
|
24
|
+
* Throws `transport_error`.
|
|
25
|
+
*/
|
|
26
|
+
function assertSafeOutDir(path, sourceDir, outDir) {
|
|
27
|
+
const out = path.resolve(outDir);
|
|
28
|
+
/** True when `dir` is `target` or an ancestor of it. */
|
|
29
|
+
const owns = (target) => {
|
|
30
|
+
const rel = path.relative(out, path.resolve(target));
|
|
31
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
32
|
+
};
|
|
33
|
+
const what = owns(sourceDir)
|
|
34
|
+
? 'the source quiver'
|
|
35
|
+
: owns(process.cwd())
|
|
36
|
+
? 'the working directory'
|
|
37
|
+
: undefined;
|
|
38
|
+
if (what !== undefined) {
|
|
39
|
+
throw new QuiverError('transport_error', `Refusing to build into "${outDir}": the build clears its output directory, and this one holds ${what} ("${out}"). Point --out at a directory the build owns.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
/**
|
|
13
43
|
* Reads a Source Quiver, validates it, and writes the build output to outDir.
|
|
14
44
|
*
|
|
15
|
-
* Output layout
|
|
45
|
+
* Output layout. Every name but the pointer carries the SHA-256 of what it
|
|
46
|
+
* names, which is what the loader checks on fetch:
|
|
16
47
|
* outDir/
|
|
17
|
-
* latest.json
|
|
18
|
-
* manifest.<
|
|
19
|
-
* <name>@<version>.<
|
|
48
|
+
* latest.json # the format, and a stable pointer to the manifest
|
|
49
|
+
* manifest.<sha256:12>.json # hashed manifest
|
|
50
|
+
* <name>@<version>.<sha256:12>.zip # one bundle per quill
|
|
20
51
|
* store/
|
|
21
|
-
* <
|
|
52
|
+
* <sha256> # dehydrated font bytes (full hash, no ext)
|
|
22
53
|
*
|
|
23
54
|
* Throws:
|
|
24
55
|
* - `quiver_invalid` on source validation failures (propagated from scanner)
|
|
25
|
-
* - `transport_error` on I/O failures
|
|
56
|
+
* - `transport_error` on I/O failures, and on an outDir the build would have
|
|
57
|
+
* to delete something it does not own to write (see `assertSafeOutDir`)
|
|
26
58
|
*/
|
|
27
|
-
export async function buildQuiver(sourceDir, outDir
|
|
59
|
+
export async function buildQuiver(sourceDir, outDir) {
|
|
28
60
|
// Dynamic imports keep this module safe to type-import from browser contexts.
|
|
29
|
-
const
|
|
30
|
-
const {
|
|
31
|
-
const {
|
|
32
|
-
const {
|
|
61
|
+
const path = await import('node:path');
|
|
62
|
+
const { join } = path;
|
|
63
|
+
const { mkdir, rm, writeFile } = await import('node:fs/promises');
|
|
64
|
+
const { createHash } = await import('node:crypto');
|
|
65
|
+
const { scanSourceQuiver, readQuillTree } = await import('./source-loader.js');
|
|
66
|
+
// 0. The first write is a recursive delete of outDir; refuse the paths where
|
|
67
|
+
// that deletes the caller instead of a previous build.
|
|
68
|
+
assertSafeOutDir(path, sourceDir, outDir);
|
|
33
69
|
// 1. Scan + validate source quiver (throws quiver_invalid on bad input).
|
|
34
70
|
const { meta, catalog } = await scanSourceQuiver(sourceDir);
|
|
35
71
|
// 2. Clear and recreate outDir + outDir/store/.
|
|
36
72
|
try {
|
|
37
73
|
await rm(outDir, { recursive: true, force: true });
|
|
38
|
-
await mkdir(join(outDir,
|
|
74
|
+
await mkdir(join(outDir, 'store'), { recursive: true });
|
|
39
75
|
}
|
|
40
76
|
catch (err) {
|
|
41
|
-
throw new QuiverError(
|
|
77
|
+
throw new QuiverError('transport_error', `Failed to prepare output directory "${outDir}": ${err.message}`, { cause: err });
|
|
42
78
|
}
|
|
43
79
|
// 3. Process each quill version.
|
|
44
80
|
const manifestQuills = [];
|
|
45
81
|
for (const [quillName, versions] of catalog) {
|
|
46
82
|
for (const version of versions) {
|
|
47
|
-
const quillDir = join(sourceDir,
|
|
83
|
+
const quillDir = join(sourceDir, 'quills', quillName, version);
|
|
48
84
|
// a. Read quill file tree.
|
|
49
85
|
const tree = await readQuillTree(quillDir);
|
|
50
86
|
// b. Partition fonts vs content.
|
|
@@ -61,13 +97,15 @@ export async function buildQuiver(sourceDir, outDir, _opts) {
|
|
|
61
97
|
// c. Dehydrate fonts into store/.
|
|
62
98
|
const fonts = {};
|
|
63
99
|
for (const [path, bytes] of fontEntries) {
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
// Full width: the store is keyed by hash, so two distinct fonts
|
|
101
|
+
// sharing a prefix would merge into one entry.
|
|
102
|
+
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
103
|
+
const storePath = join(outDir, 'store', hash);
|
|
66
104
|
try {
|
|
67
105
|
await writeFile(storePath, bytes);
|
|
68
106
|
}
|
|
69
107
|
catch (err) {
|
|
70
|
-
throw new QuiverError(
|
|
108
|
+
throw new QuiverError('transport_error', `Failed to write font store entry "${storePath}": ${err.message}`, { cause: err });
|
|
71
109
|
}
|
|
72
110
|
fonts[path] = hash;
|
|
73
111
|
}
|
|
@@ -78,7 +116,10 @@ export async function buildQuiver(sourceDir, outDir, _opts) {
|
|
|
78
116
|
}
|
|
79
117
|
const zipBytes = packFiles(contentRecord);
|
|
80
118
|
// e–f. Compute bundle hash and name.
|
|
81
|
-
const bundleHash = createHash(
|
|
119
|
+
const bundleHash = createHash('sha256')
|
|
120
|
+
.update(zipBytes)
|
|
121
|
+
.digest('hex')
|
|
122
|
+
.slice(0, NAME_DIGEST_LENGTH);
|
|
82
123
|
const bundleName = `${quillName}@${version}.${bundleHash}.zip`;
|
|
83
124
|
// g. Write bundle zip.
|
|
84
125
|
const bundlePath = join(outDir, bundleName);
|
|
@@ -86,7 +127,7 @@ export async function buildQuiver(sourceDir, outDir, _opts) {
|
|
|
86
127
|
await writeFile(bundlePath, zipBytes);
|
|
87
128
|
}
|
|
88
129
|
catch (err) {
|
|
89
|
-
throw new QuiverError(
|
|
130
|
+
throw new QuiverError('transport_error', `Failed to write bundle "${bundlePath}": ${err.message}`, { cause: err });
|
|
90
131
|
}
|
|
91
132
|
// h. Record manifest entry.
|
|
92
133
|
manifestQuills.push({ name: quillName, version, bundle: bundleName, fonts });
|
|
@@ -96,25 +137,29 @@ export async function buildQuiver(sourceDir, outDir, _opts) {
|
|
|
96
137
|
const manifest = {
|
|
97
138
|
version: 1,
|
|
98
139
|
name: meta.name,
|
|
99
|
-
quills: manifestQuills
|
|
140
|
+
quills: manifestQuills
|
|
100
141
|
};
|
|
101
142
|
const manifestJson = JSON.stringify(manifest, null, 2);
|
|
102
|
-
const manifestHash = createHash(
|
|
143
|
+
const manifestHash = createHash('sha256')
|
|
144
|
+
.update(manifestJson)
|
|
145
|
+
.digest('hex')
|
|
146
|
+
.slice(0, NAME_DIGEST_LENGTH);
|
|
103
147
|
const manifestFileName = `manifest.${manifestHash}.json`;
|
|
104
148
|
const manifestPath = join(outDir, manifestFileName);
|
|
105
149
|
try {
|
|
106
|
-
await writeFile(manifestPath, manifestJson,
|
|
150
|
+
await writeFile(manifestPath, manifestJson, 'utf-8');
|
|
107
151
|
}
|
|
108
152
|
catch (err) {
|
|
109
|
-
throw new QuiverError(
|
|
153
|
+
throw new QuiverError('transport_error', `Failed to write manifest "${manifestPath}": ${err.message}`, { cause: err });
|
|
110
154
|
}
|
|
111
|
-
// 9–10. Write stable pointer latest.json.
|
|
112
|
-
|
|
113
|
-
const
|
|
155
|
+
// 9–10. Write stable pointer latest.json. The format is stamped here and read first,
|
|
156
|
+
// so a client older than the tree says so rather than misreading it.
|
|
157
|
+
const pointer = { format: POINTER_FORMAT, manifest: manifestFileName };
|
|
158
|
+
const pointerPath = join(outDir, 'latest.json');
|
|
114
159
|
try {
|
|
115
|
-
await writeFile(pointerPath, JSON.stringify(pointer),
|
|
160
|
+
await writeFile(pointerPath, JSON.stringify(pointer), 'utf-8');
|
|
116
161
|
}
|
|
117
162
|
catch (err) {
|
|
118
|
-
throw new QuiverError(
|
|
163
|
+
throw new QuiverError('transport_error', `Failed to write pointer "${pointerPath}": ${err.message}`, { cause: err });
|
|
119
164
|
}
|
|
120
165
|
}
|
package/dist/built-loader.d.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-quiver loader
|
|
3
|
-
*
|
|
2
|
+
* Built-quiver loader: reads a packed artifact through a transport, validates
|
|
3
|
+
* its pointer and manifest, and hands back a Quiver whose loader fetches
|
|
4
|
+
* bundles and fonts on demand, each checked against the digest in its name.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
* - BuiltTransport interface (implemented by HttpTransport)
|
|
7
|
-
* - loadBuiltQuiver(transport) → Quiver
|
|
8
|
-
*
|
|
9
|
-
* NO static node: imports — this module is safe to load in browser contexts.
|
|
6
|
+
* Package-internal, and browser-safe: no static `node:` imports at any level.
|
|
10
7
|
*/
|
|
11
|
-
import { Quiver } from
|
|
8
|
+
import type { Quiver } from './quiver.js';
|
|
12
9
|
/**
|
|
13
10
|
* Transport abstraction: fetch raw bytes by relative path within the packed
|
|
14
|
-
* artifact.
|
|
11
|
+
* artifact. Implemented by HttpTransport (browser + Node) and FsBuiltTransport
|
|
12
|
+
* (Node).
|
|
15
13
|
*/
|
|
16
14
|
export interface BuiltTransport {
|
|
17
|
-
fetchBytes(relativePath: string): Promise<Uint8Array>;
|
|
15
|
+
fetchBytes(relativePath: string, opts?: FetchOptions): Promise<Uint8Array>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `revalidate` marks the one request that must not be answered from a cache:
|
|
19
|
+
* `latest.json`, the only name in the artifact that is not content-addressed.
|
|
20
|
+
* Everything else is safe to cache forever by construction.
|
|
21
|
+
*/
|
|
22
|
+
export interface FetchOptions {
|
|
23
|
+
revalidate?: boolean;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
26
|
* Load a build-output quiver via the given transport.
|
|
@@ -25,9 +31,3 @@ export interface BuiltTransport {
|
|
|
25
31
|
* 4. Returns a Quiver instance backed by a BuiltLoader.
|
|
26
32
|
*/
|
|
27
33
|
export declare function loadBuiltQuiver(transport: BuiltTransport): Promise<Quiver>;
|
|
28
|
-
/**
|
|
29
|
-
* Seed a quiver from caller-provided manifest bytes, skipping the latest.json
|
|
30
|
-
* pointer fetch. `transport` is used only for lazy bundle/font fetches.
|
|
31
|
-
* Throws `quiver_invalid` on malformed bytes.
|
|
32
|
-
*/
|
|
33
|
-
export declare function seedBuiltQuiver(transport: BuiltTransport, manifestBytes: Uint8Array): Quiver;
|