@quillmark/quiver 0.17.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/README.md CHANGED
@@ -134,9 +134,9 @@ A consumer reaches an installed quiver by resolving `<specifier>/Quiver.yaml`, w
134
134
  }
135
135
  ```
136
136
 
137
- Gate the quiver in CI so a validation failure surfaces on publish rather than on a consumer's build. `quiver test` loads with `fromDir`, compiles every quill, and renders each quill's example document. One door, so what you run locally is what CI runs.
137
+ Gate the quiver in CI so a validation failure surfaces on publish rather than on a consumer's build. `quillmark-quiver test` loads with `fromDir`, compiles every quill, and renders each quill's example document. One door, so what you run locally is what CI runs.
138
138
 
139
- The CLI needs no file. Installing the package links `quiver` into `node_modules/.bin`, which npm puts on PATH for every script:
139
+ The CLI needs no file. Installing the package links `quillmark-quiver` into `node_modules/.bin`, which npm puts on PATH for every script:
140
140
 
141
141
  ```sh
142
142
  npm install --save-dev @quillmark/quiver @quillmark/wasm
@@ -145,11 +145,11 @@ npm install --save-dev @quillmark/quiver @quillmark/wasm
145
145
  ```jsonc
146
146
  // package.json
147
147
  {
148
- "scripts": { "test": "quiver test" }
148
+ "scripts": { "test": "quillmark-quiver test" }
149
149
  }
150
150
  ```
151
151
 
152
- It finds the engine itself: a named `engine` export from `quiver.config.js` at the collection root, else `@quillmark/wasm` from your own `node_modules`. `quiver build [--out <dir>]` packs the same source into a servable artifact.
152
+ It finds the engine itself: a named `engine` export from `quiver.config.js` at the collection root, else `@quillmark/wasm` from your own `node_modules`. `quillmark-quiver build [--out <dir>]` packs the same source into a servable artifact.
153
153
 
154
154
  On vitest, jest or `node:test`, spawn the bin from a test rather than rebuilding the loop against the main API. The gate stays one implementation, and the case gates what CI gates:
155
155
 
@@ -158,7 +158,7 @@ On vitest, jest or `node:test`, spawn the bin from a test rather than rebuilding
158
158
  import { execFileSync } from 'node:child_process';
159
159
 
160
160
  it('gates the quiver', () => {
161
- execFileSync('quiver', ['test'], { stdio: 'inherit' });
161
+ execFileSync('quillmark-quiver', ['test'], { stdio: 'inherit' });
162
162
  });
163
163
  ```
164
164
 
@@ -5,8 +5,8 @@
5
5
  * Looking at rendered output is studio's, an app rather than a verb.
6
6
  *
7
7
  * Commands:
8
- * quiver build [--out <dir>]
9
- * quiver test
8
+ * quillmark-quiver build [--out <dir>]
9
+ * quillmark-quiver test
10
10
  *
11
11
  * Engine discovery (for `test`):
12
12
  * 1. Named export `engine` from `quiver.config.js` at the collection root,
@@ -5,8 +5,8 @@
5
5
  * Looking at rendered output is studio's, an app rather than a verb.
6
6
  *
7
7
  * Commands:
8
- * quiver build [--out <dir>]
9
- * quiver test
8
+ * quillmark-quiver build [--out <dir>]
9
+ * quillmark-quiver test
10
10
  *
11
11
  * Engine discovery (for `test`):
12
12
  * 1. Named export `engine` from `quiver.config.js` at the collection root,
@@ -72,7 +72,7 @@ async function loadEngine(cwd) {
72
72
  async function build() {
73
73
  const cwd = process.cwd();
74
74
  const outDir = flag('--out') ?? 'dist';
75
- console.log(`quiver build: ${cwd} → ${outDir}`);
75
+ console.log(`quillmark-quiver build: ${cwd} → ${outDir}`);
76
76
  await buildQuiver(cwd, outDir);
77
77
  console.log('done.');
78
78
  }
@@ -121,7 +121,7 @@ async function test() {
121
121
  // Dispatch
122
122
  // ---------------------------------------------------------------------------
123
123
  function usage() {
124
- console.error(['Usage:', ' quiver build [--out <dir>]', ' quiver test'].join('\n'));
124
+ console.error(['Usage:', ' quillmark-quiver build [--out <dir>]', ' quillmark-quiver test'].join('\n'));
125
125
  }
126
126
  function die(err) {
127
127
  const msg = err instanceof Error ? err.message : String(err);
package/dist/build.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * Output layout. Every name but the pointer carries the SHA-256 of what it
12
12
  * names, which is what the loader checks on fetch:
13
13
  * outDir/
14
- * latest.json # stable pointer to the current manifest
14
+ * latest.json # the format, and a stable pointer to the manifest
15
15
  * manifest.<sha256:12>.json # hashed manifest
16
16
  * <name>@<version>.<sha256:12>.zip # one bundle per quill
17
17
  * store/
package/dist/build.js CHANGED
@@ -8,14 +8,15 @@
8
8
  import { QuiverError } from './errors.js';
9
9
  import { packFiles } from './bundle.js';
10
10
  import { NAME_DIGEST_LENGTH } from './digest.js';
11
+ import { POINTER_FORMAT } from './format.js';
11
12
  /** Font file extensions recognised by the builder (case-insensitive). */
12
13
  const FONT_EXT = /\.(ttf|otf|woff|woff2)$/i;
13
14
  /**
14
15
  * The build's first act is `rm(outDir, { recursive: true })`, so an outDir that
15
16
  * is, or contains, the source quiver or the working directory deletes the
16
- * thing the caller was building from. `quiver build --out .` and a mistyped
17
- * `--out ..` are both one keystroke away, and the failure is unrecoverable, so
18
- * an outDir that owns the caller is refused up front.
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.
19
20
  *
20
21
  * An outDir *inside* sourceDir stays allowed: the scan reads the source before
21
22
  * any write, and `dist/` under the quiver root is the ordinary layout.
@@ -44,7 +45,7 @@ function assertSafeOutDir(path, sourceDir, outDir) {
44
45
  * Output layout. Every name but the pointer carries the SHA-256 of what it
45
46
  * names, which is what the loader checks on fetch:
46
47
  * outDir/
47
- * latest.json # stable pointer to the current manifest
48
+ * latest.json # the format, and a stable pointer to the manifest
48
49
  * manifest.<sha256:12>.json # hashed manifest
49
50
  * <name>@<version>.<sha256:12>.zip # one bundle per quill
50
51
  * store/
@@ -151,8 +152,9 @@ export async function buildQuiver(sourceDir, outDir) {
151
152
  catch (err) {
152
153
  throw new QuiverError('transport_error', `Failed to write manifest "${manifestPath}": ${err.message}`, { cause: err });
153
154
  }
154
- // 9–10. Write stable pointer latest.json.
155
- const pointer = { manifest: manifestFileName };
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 };
156
158
  const pointerPath = join(outDir, 'latest.json');
157
159
  try {
158
160
  await writeFile(pointerPath, JSON.stringify(pointer), 'utf-8');
@@ -9,6 +9,7 @@ import { QuiverError } from './errors.js';
9
9
  import { unpackFiles } from './bundle.js';
10
10
  import { isCanonicalSemver, compareSemver } from './semver.js';
11
11
  import { NAME_DIGEST_LENGTH, sha256Hex } from './digest.js';
12
+ import { POINTER_FORMAT } from './format.js';
12
13
  import { createQuiver } from './quiver.js';
13
14
  /**
14
15
  * Fetch, then check the bytes against the digest their name carries. That
@@ -115,7 +116,18 @@ function parsePointer(raw) {
115
116
  throw new QuiverError('quiver_invalid', 'latest.json must be a JSON object');
116
117
  }
117
118
  const obj = parsed;
118
- assertNoUnknownKeys(obj, ['manifest'], 'latest.json');
119
+ // The format, read before anything is believed about the rest. An unknown key is NOT
120
+ // a failure here: this is the document a newer format announces itself on, and a
121
+ // reader rejecting what it does not recognise refuses the announcement too. Absent
122
+ // means a build from before the marker (`format.ts`).
123
+ const format = obj['format'] ?? POINTER_FORMAT;
124
+ if (typeof format !== 'number' || !Number.isInteger(format) || format < 1) {
125
+ throw new QuiverError('quiver_invalid', `latest.json: "format" must be a positive integer, got ${JSON.stringify(obj['format'])}`);
126
+ }
127
+ if (format > POINTER_FORMAT) {
128
+ throw new QuiverError('quiver_invalid', `This quiver is built in format ${format} and this loader reads ${POINTER_FORMAT}. ` +
129
+ `Upgrade @quillmark/quiver, or for a served client the client itself, which carries the copy that reads this.`);
130
+ }
119
131
  if (typeof obj['manifest'] !== 'string' || obj['manifest'].length === 0) {
120
132
  throw new QuiverError('quiver_invalid', 'latest.json must have a non-empty string "manifest" field');
121
133
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The built artifact's format, stamped into `latest.json` and refused to be read past
3
+ * (QUIVER §"The pointer" for why the pointer carries it and reads past keys it does not
4
+ * know).
5
+ *
6
+ * It moves when a reader assuming it would MISREAD a tree rather than fail on it: a
7
+ * renamed pointer field, a second pointer, a store keyed by something other than the
8
+ * full hash. A change confined to the manifest's own shape moves the manifest's
9
+ * `version` instead.
10
+ */
11
+ export declare const POINTER_FORMAT = 1;
package/dist/format.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The built artifact's format, stamped into `latest.json` and refused to be read past
3
+ * (QUIVER §"The pointer" for why the pointer carries it and reads past keys it does not
4
+ * know).
5
+ *
6
+ * It moves when a reader assuming it would MISREAD a tree rather than fail on it: a
7
+ * renamed pointer field, a second pointer, a store keyed by something other than the
8
+ * full hash. A change confined to the manifest's own shape moves the manifest's
9
+ * `version` instead.
10
+ */
11
+ export const POINTER_FORMAT = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/quiver",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Quiver registry and build tooling for Quillmark",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -26,11 +26,13 @@
26
26
  "exports": {
27
27
  ".": {
28
28
  "types": "./dist/index.d.ts",
29
- "import": "./dist/index.js"
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
30
31
  },
31
32
  "./node": {
32
33
  "types": "./dist/node.d.ts",
33
- "import": "./dist/node.js"
34
+ "import": "./dist/node.js",
35
+ "default": "./dist/node.js"
34
36
  }
35
37
  },
36
38
  "sideEffects": false,
@@ -52,7 +54,7 @@
52
54
  "vitest": "^4.0.18"
53
55
  },
54
56
  "bin": {
55
- "quiver": "./dist/bin/quiver.js"
57
+ "quillmark-quiver": "./dist/bin/quiver.js"
56
58
  },
57
59
  "scripts": {
58
60
  "build": "tsc",