@taprootio/docs-artifact 1.0.1 → 1.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 +196 -30
- package/bin/taproot-docs-conformance.js +34 -13
- package/bin/taproot-docs-validate.js +44 -9
- package/fixtures/README.md +19 -0
- package/fixtures/prebuilt/conformance.json +444 -0
- package/fixtures/prebuilt/golden/espalier.tar.gz +0 -0
- package/fixtures/prebuilt/invalid/duplicate-json-key.json +4 -0
- package/fixtures/prebuilt/valid/espalier/404.html +2 -0
- package/fixtures/prebuilt/valid/espalier/api/show-toast/index.html +5 -0
- package/fixtures/prebuilt/valid/espalier/assets/icons.svg +1 -0
- package/fixtures/prebuilt/valid/espalier/assets/pulse.svg +1 -0
- package/fixtures/prebuilt/valid/espalier/assets/search-worker.js +1 -0
- package/fixtures/prebuilt/valid/espalier/assets/search.wasm +0 -0
- package/fixtures/prebuilt/valid/espalier/assets/site.css +3 -0
- package/fixtures/prebuilt/valid/espalier/assets/site.js +2 -0
- package/fixtures/prebuilt/valid/espalier/dist/-6iE9DOe.css +1 -0
- package/fixtures/prebuilt/valid/espalier/dist/_AN3XUT_.css +1 -0
- package/fixtures/prebuilt/valid/espalier/guides/index.html +2 -0
- package/fixtures/prebuilt/valid/espalier/index.html +2 -0
- package/fixtures/prebuilt/valid/espalier/pagefind/index/abc.pf_index +0 -0
- package/fixtures/prebuilt/valid/espalier/pagefind/pagefind.js +4 -0
- package/fixtures/prebuilt/valid/espalier/taproot-docs-prebuilt-manifest.json +135 -0
- package/index.d.ts +9 -0
- package/node.d.ts +1 -0
- package/package.json +28 -5
- package/prebuilt-archive.d.ts +27 -0
- package/prebuilt-conformance.d.ts +27 -0
- package/prebuilt-node.d.ts +7 -0
- package/prebuilt.d.ts +128 -0
- package/schema/taproot-docs-prebuilt-manifest.schema.json +156 -0
- package/src/constants.js +4 -0
- package/src/index.js +13 -0
- package/src/json.js +50 -26
- package/src/node.js +2 -0
- package/src/prebuilt-archive.js +246 -0
- package/src/prebuilt-artifact-validator.js +236 -0
- package/src/prebuilt-conformance.js +151 -0
- package/src/prebuilt-constants.js +55 -0
- package/src/prebuilt-manifest-validator.js +654 -0
- package/src/prebuilt-node.js +518 -0
- package/src/prebuilt-path.js +129 -0
- package/src/prebuilt.js +20 -0
package/README.md
CHANGED
|
@@ -1,20 +1,134 @@
|
|
|
1
1
|
# `@taprootio/docs-artifact`
|
|
2
2
|
|
|
3
3
|
The canonical, independently versioned contract between documentation producers
|
|
4
|
-
and Taproot Docs consumers.
|
|
5
|
-
|
|
4
|
+
and Taproot Docs consumers. Version 1.1 adds a separate prebuilt contract without
|
|
5
|
+
changing or reinterpreting the released managed schema v1 contract. The public
|
|
6
|
+
npm package contains the authorities that must move together:
|
|
6
7
|
|
|
7
8
|
- `schema/taproot-docs-manifest.schema.json` for the structural v1 shape;
|
|
8
9
|
- the validator and deterministic serializer exported from the package root;
|
|
9
10
|
- the Node directory validator and `taproot-docs-validate` command;
|
|
10
11
|
- valid and adversarial fixtures exposed by `@taprootio/docs-artifact/conformance`.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Prebuilt mode has its own `schema/taproot-docs-prebuilt-manifest.schema.json`,
|
|
14
|
+
`@taprootio/docs-artifact/prebuilt` validator and serializer,
|
|
15
|
+
`@taprootio/docs-artifact/prebuilt/node` directory validator, and
|
|
16
|
+
`@taprootio/docs-artifact/prebuilt/archive` deterministic encoder, and
|
|
17
|
+
`@taprootio/docs-artifact/prebuilt/conformance` cases and golden vector.
|
|
18
|
+
Consumers select one entry point and one manifest filename explicitly; they
|
|
19
|
+
never infer a mode from fields or archive contents.
|
|
20
|
+
|
|
21
|
+
Each producer and consumer pins the exact package release for its selected
|
|
22
|
+
contract. Managed artifacts produced under `1.0.1` remain valid and unchanged;
|
|
23
|
+
prebuilt producers and consumers pin `1.1.0`. No repository copies the schema,
|
|
24
|
+
path rules, markup allow-list, limits, or fixture data.
|
|
25
|
+
|
|
26
|
+
## Prebuilt Docs contract
|
|
27
|
+
|
|
28
|
+
Prebuilt mode is a Docs publication mode for repository-built static output,
|
|
29
|
+
not a general static-hosting product. Its root is
|
|
30
|
+
`taproot-docs-prebuilt-manifest.json`, `schemaVersion` is `1`, `mode` is
|
|
31
|
+
`prebuilt`, and its required capability is
|
|
32
|
+
`taproot.docs.prebuilt.files.v1`. The closed manifest contains only immutable
|
|
33
|
+
GitHub source/build provenance, capabilities, `notFoundFile`, exact `files`,
|
|
34
|
+
stable `resources`, and direct `redirects`. It has no response headers, cache
|
|
35
|
+
directives, CSP fragments, routes table, origin allowlist, or executable hook.
|
|
36
|
+
|
|
37
|
+
Every payload file has one ASCII relative POSIX path, exact media type, byte
|
|
38
|
+
length, and `sha256:<64 lowercase hex>` digest. Paths use portable filename
|
|
39
|
+
segments whose first character is a letter, digit, `-`, or `_` and whose last
|
|
40
|
+
character is a letter or digit. They reject absolute paths, backslashes,
|
|
41
|
+
percent escapes, controls, empty or dot segments, Windows device aliases, and
|
|
42
|
+
the manifest name, and must fit both the 255-byte/64-directory envelope and
|
|
43
|
+
POSIX USTAR's 100-byte name plus 155-byte prefix fields. Exact and
|
|
44
|
+
ASCII-case-folded duplicates fail closed.
|
|
45
|
+
The complete directory is closed: every declared file must be a regular file,
|
|
46
|
+
and every other file, empty/unneeded directory, link, socket, FIFO, or device is
|
|
47
|
+
rejected.
|
|
48
|
+
|
|
49
|
+
The authenticated platform control route
|
|
50
|
+
`/__taproot/internal/published-site-routing` is matched first and is the only
|
|
51
|
+
reserved request route in the prebuilt file namespace. The edge then resolves
|
|
52
|
+
the exact host, site, environment, immutable output pointer, and publication
|
|
53
|
+
mode before applying public `/api/*` behavior. Managed mode retains the Taproot
|
|
54
|
+
API proxy. Prebuilt files and redirects own the complete remaining namespace,
|
|
55
|
+
including `/api/*`, and never fall through to that proxy.
|
|
56
|
+
|
|
57
|
+
Within the prebuilt namespace, the file tree is the request router.
|
|
58
|
+
`index.html` maps to `/`, `x/index.html` maps to `/x/`, and every other file
|
|
59
|
+
maps to its exact `/<path>`. Redirects resolve before file lookup, map one
|
|
60
|
+
canonical source directly to a stable resource key, and permit only status 301
|
|
61
|
+
or 308. A redirect source may not collide, including after ASCII case-folding,
|
|
62
|
+
with another redirect or a file route. `notFoundFile` is exactly `404.html` and
|
|
63
|
+
must be declared as `text/html; charset=utf-8`.
|
|
64
|
+
|
|
65
|
+
Resources contain exactly a durable `key`, one declared UTF-8 HTML `file`, and
|
|
66
|
+
its current display `title`. Two keys cannot claim one file, and the 404 cannot
|
|
67
|
+
be a successful-response resource. At publication, Taproot attaches the
|
|
68
|
+
resulting `SiteResource` id as origin object metadata for mapped HTML. Eligible
|
|
69
|
+
unmapped HTML is an explicit path-only analytics bucket. HTML 200 analytics stay
|
|
70
|
+
cookie-free and no browser analytics script is injected.
|
|
71
|
+
|
|
72
|
+
Taproot preserves every declared payload byte and publisher path. It owns and
|
|
73
|
+
may attach `Content-Type`, cache policy, CSP, framing, MIME-sniffing, referrer,
|
|
74
|
+
permissions, staging authorization/noindex, range, and content-encoding
|
|
75
|
+
behavior; publisher fields cannot override them. Every prebuilt response gets
|
|
76
|
+
this exact v1 policy, including HTML, the canonical 404, redirects, JavaScript
|
|
77
|
+
and worker scripts, WebAssembly, directly navigated SVG documents, and all
|
|
78
|
+
other assets:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; form-action 'self'; worker-src 'self'; manifest-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`'wasm-unsafe-eval'` permits the accepted same-origin Pagefind Wasm runtime
|
|
85
|
+
without permitting string evaluation through `'unsafe-eval'`. V1 has no
|
|
86
|
+
publisher-supplied origin allowlist or per-response relaxation.
|
|
87
|
+
|
|
88
|
+
The archive identifier is the distinct
|
|
89
|
+
`taproot-docs-prebuilt-tar-gzip-v1`. Its deterministic bytes contain the
|
|
90
|
+
canonical manifest first and the exact payload snapshots in lexicographic path
|
|
91
|
+
order. Each entry is a regular POSIX USTAR record with paths of at most 100
|
|
92
|
+
bytes stored entirely in the name field; longer paths split at the rightmost
|
|
93
|
+
slash that fits the 155-byte prefix and 100-byte name fields. Records use mode
|
|
94
|
+
`0000644`; zero uid/gid/mtime/devmajor/devminor numeric fields; typeflag `0`;
|
|
95
|
+
magic `ustar\0`; version `00`; empty owner/group names; six-octal-digit,
|
|
96
|
+
NUL-space checksum; zero content padding; and exactly two zero terminator
|
|
97
|
+
blocks. One gzip member
|
|
98
|
+
uses header bytes `1f 8b 08 00 00 00 00 00 00 ff`; every non-final stored
|
|
99
|
+
DEFLATE block is exactly 65,535 bytes and the final block holds the remaining
|
|
100
|
+
1–65,535 bytes. The trailer contains the tar CRC-32 and low 32 bits of its byte
|
|
101
|
+
length. The committed representative archive and its byte length/SHA-256 are
|
|
102
|
+
the compatibility vector; platform `tar` or `gzip` output is not a substitute.
|
|
103
|
+
The separate platform upload ceiling remains 256 MiB compressed; conformance
|
|
104
|
+
under the artifact contract does not imply that an upload fits that independent
|
|
105
|
+
limit.
|
|
106
|
+
|
|
107
|
+
Public prebuilt ceilings are 8 MiB canonical manifest bytes, 25,000 files,
|
|
108
|
+
512 MiB declared/actual uncompressed bytes, 64 MiB per file, 25,000 resources,
|
|
109
|
+
10,000 redirects, 255 path bytes, and 64 directory levels. Object inputs retain
|
|
110
|
+
the package's bounded 250,000-value/64-level work envelope, and diagnostics use
|
|
111
|
+
the same stable bounded/sorted error contract as managed mode. Filesystem walks
|
|
112
|
+
retain the existing 40,000-entry work ceiling. The Node validator opens files
|
|
113
|
+
once with nonblocking/no-follow semantics, verifies the
|
|
114
|
+
descriptor and every path component before and after reads, repeats the closed
|
|
115
|
+
tree walk, and rejects identity, size, type, or ancestor races. Validation never
|
|
116
|
+
executes or parses customer HTML, CSS, JavaScript, WebAssembly, Pagefind data,
|
|
117
|
+
or SVG.
|
|
118
|
+
|
|
119
|
+
Ordinary prebuilt output may execute only on separately verified customer
|
|
120
|
+
production and staging hostnames. The only first-party platform-host exception
|
|
121
|
+
is the closed Espalier tuple: hosts `espalier.taproot.io` and
|
|
122
|
+
`espalier.taproot.test`, GitHub repository id `934883082` with locator
|
|
123
|
+
`taprootio/taproot-controls`, and `refs/heads/main`. Product ingestion must
|
|
124
|
+
enforce that complete tuple before serving executable prebuilt bytes beneath
|
|
125
|
+
`taproot.io` or `taproot.test`. WTFM remains managed.
|
|
15
126
|
|
|
16
127
|
## Artifact layout and portability
|
|
17
128
|
|
|
129
|
+
The remainder of this document describes the unchanged managed schema-v1
|
|
130
|
+
contract.
|
|
131
|
+
|
|
18
132
|
The producer writes this additive payload into its ordinary static output:
|
|
19
133
|
|
|
20
134
|
```text
|
|
@@ -87,7 +201,8 @@ The npm package uses semantic versioning independently of `schemaVersion`:
|
|
|
87
201
|
enforcement. A patch may reject an input that never conformed to the stated
|
|
88
202
|
fail-closed rules.
|
|
89
203
|
|
|
90
|
-
Producer and consumer release PRs
|
|
204
|
+
Producer and consumer release PRs coordinate each selected contract's exact
|
|
205
|
+
dependency pin. The
|
|
91
206
|
private Taproot `docs-artifact-v<package-version>` tag does not publish npm
|
|
92
207
|
directly. It verifies and copies the reviewed package allowlist into
|
|
93
208
|
`packages/docs-artifact/` in public
|
|
@@ -189,28 +304,28 @@ ceiling as the runtime manifest and markup `lang` validators.
|
|
|
189
304
|
|
|
190
305
|
The exported `LIMITS` object is the v1 ceiling. Important bounds are:
|
|
191
306
|
|
|
192
|
-
| Input
|
|
193
|
-
|
|
194
|
-
| Manifest
|
|
195
|
-
| Object-manifest traversal / nesting depth
|
|
196
|
-
| Managed semantic bytes
|
|
197
|
-
| Listed semantic files
|
|
198
|
-
| Managed filesystem entries
|
|
199
|
-
| Managed directory depth
|
|
200
|
-
| Managed relative path
|
|
201
|
-
| Resources
|
|
202
|
-
| Locale variants
|
|
203
|
-
| Fragments
|
|
204
|
-
| Assets
|
|
205
|
-
| Redirects
|
|
206
|
-
| Navigation nodes / depth
|
|
207
|
-
| Markup elements / nesting depth per fragment |
|
|
208
|
-
| One fragment
|
|
209
|
-
| One asset
|
|
210
|
-
| Decoded image canvas
|
|
211
|
-
| Cumulative decoded animated frames
|
|
212
|
-
| Animated image frames
|
|
213
|
-
| Consumer-supported capabilities
|
|
307
|
+
| Input | Maximum |
|
|
308
|
+
| -------------------------------------------- | -------------------------------: |
|
|
309
|
+
| Manifest | 2 MiB |
|
|
310
|
+
| Object-manifest traversal / nesting depth | 250,000 values / 64 |
|
|
311
|
+
| Managed semantic bytes | 256 MiB |
|
|
312
|
+
| Listed semantic files | 20,000 |
|
|
313
|
+
| Managed filesystem entries | 40,000 |
|
|
314
|
+
| Managed directory depth | 32 |
|
|
315
|
+
| Managed relative path | 512 characters |
|
|
316
|
+
| Resources | 10,000 |
|
|
317
|
+
| Locale variants | 20,000 |
|
|
318
|
+
| Fragments | 20,000 |
|
|
319
|
+
| Assets | 10,000 |
|
|
320
|
+
| Redirects | 10,000 |
|
|
321
|
+
| Navigation nodes / depth | 20,000 / 12 |
|
|
322
|
+
| Markup elements / nesting depth per fragment | 100,000 / 128 |
|
|
323
|
+
| One fragment | 2 MiB |
|
|
324
|
+
| One asset | 25 MiB |
|
|
325
|
+
| Decoded image canvas | 67,108,864 pixels |
|
|
326
|
+
| Cumulative decoded animated frames | 67,108,864 pixels |
|
|
327
|
+
| Animated image frames | 1,000 |
|
|
328
|
+
| Consumer-supported capabilities | 100 entries, 200 characters each |
|
|
214
329
|
|
|
215
330
|
Navigation depth counts each top-level item as level one. A leaf at level 12 is
|
|
216
331
|
valid; a child node at level 13 is rejected, while the global node ceiling still
|
|
@@ -349,14 +464,65 @@ const canonicalBytes = serializeManifest(manifestObject);
|
|
|
349
464
|
const sharedCases = await loadConformanceCases();
|
|
350
465
|
```
|
|
351
466
|
|
|
467
|
+
The root also exports additive `validateManagedManifest`,
|
|
468
|
+
`assertValidManagedManifest`, `serializeManagedManifest`,
|
|
469
|
+
`validateManagedArtifact`, and `assertValidManagedArtifact` aliases plus
|
|
470
|
+
`MANAGED_*` identity constants. `@taprootio/docs-artifact/node` likewise exports
|
|
471
|
+
`validateManagedArtifactDirectory`. The legacy managed names above remain
|
|
472
|
+
unchanged.
|
|
473
|
+
|
|
474
|
+
Prebuilt consumers use the separate, explicit surface:
|
|
475
|
+
|
|
476
|
+
```js
|
|
477
|
+
import {
|
|
478
|
+
PREBUILT_ARCHIVE_FORMAT,
|
|
479
|
+
serializePrebuiltManifest,
|
|
480
|
+
validatePrebuiltArtifact,
|
|
481
|
+
validatePrebuiltManifest,
|
|
482
|
+
} from "@taprootio/docs-artifact/prebuilt";
|
|
483
|
+
import { createDeterministicPrebuiltArchive } from "@taprootio/docs-artifact/prebuilt/archive";
|
|
484
|
+
import {
|
|
485
|
+
loadPrebuiltConformanceCases,
|
|
486
|
+
validatePrebuiltConformanceCase,
|
|
487
|
+
} from "@taprootio/docs-artifact/prebuilt/conformance";
|
|
488
|
+
import { validatePrebuiltArtifactDirectory } from "@taprootio/docs-artifact/prebuilt/node";
|
|
489
|
+
|
|
490
|
+
const manifestResult = validatePrebuiltManifest(manifestBytes);
|
|
491
|
+
const artifactResult = await validatePrebuiltArtifact(
|
|
492
|
+
manifestBytes,
|
|
493
|
+
exactFiles,
|
|
494
|
+
);
|
|
495
|
+
const directoryResult = await validatePrebuiltArtifactDirectory("_site");
|
|
496
|
+
const canonicalBytes = serializePrebuiltManifest(manifestObject);
|
|
497
|
+
const sharedCases = await loadPrebuiltConformanceCases();
|
|
498
|
+
const sharedResults = await Promise.all(
|
|
499
|
+
sharedCases.map(validatePrebuiltConformanceCase),
|
|
500
|
+
);
|
|
501
|
+
const archive = createDeterministicPrebuiltArchive(directoryResult.value);
|
|
502
|
+
const representativeVector = sharedCases.find((fixture) =>
|
|
503
|
+
fixture.expectedArchive
|
|
504
|
+
)?.expectedArchive;
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
`createDeterministicPrebuiltArchive()` accepts only the isolated snapshot
|
|
508
|
+
returned by a successful prebuilt artifact or directory validation. It verifies
|
|
509
|
+
the snapshot's declared sizes and hashes again against the bytes it archives,
|
|
510
|
+
then returns `{ format, bytes, byteLength, contentHash }`. The representative
|
|
511
|
+
conformance case exposes its committed `expectedArchive` bytes, `byteLength`,
|
|
512
|
+
and `sha256` for independent producer and consumer byte-identity checks.
|
|
513
|
+
`validatePrebuiltConformanceCase()` runs ordinary cases against the in-memory
|
|
514
|
+
validator and safely materializes the published non-regular-file race case in a
|
|
515
|
+
disposable directory for the Node boundary.
|
|
516
|
+
|
|
352
517
|
The assertion variants throw `DocsArtifactValidationError` with the same stable
|
|
353
518
|
`errors` array. The CLI prints `code path: message` diagnostics and exits nonzero:
|
|
354
519
|
|
|
355
520
|
```bash
|
|
356
|
-
npx --package=@taprootio/docs-artifact@1.0
|
|
521
|
+
npx --package=@taprootio/docs-artifact@1.1.0 taproot-docs-validate ./_site
|
|
522
|
+
npx --package=@taprootio/docs-artifact@1.1.0 taproot-docs-validate --mode prebuilt ./_site
|
|
357
523
|
```
|
|
358
524
|
|
|
359
525
|
Consumers should assert error `code` and `path`, not human-readable wording.
|
|
360
526
|
The package test suite also runs `npm pack --dry-run --json --ignore-scripts`
|
|
361
|
-
and requires the exact reviewed
|
|
527
|
+
and requires the exact reviewed tarball inventory, including the ISC
|
|
362
528
|
`LICENSE`, so a publishable file cannot appear or disappear silently.
|
|
@@ -2,19 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
import { validateArtifact } from "../src/artifact-validator.js";
|
|
4
4
|
import { loadConformanceCases } from "../src/conformance.js";
|
|
5
|
+
import { loadPrebuiltConformanceCases, validatePrebuiltConformanceCase } from "../src/prebuilt-conformance.js";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
if (failed) {
|
|
17
|
-
process.exitCode = 1;
|
|
7
|
+
const arguments_ = process.argv.slice(2);
|
|
8
|
+
const modeIndex = arguments_.indexOf("--mode");
|
|
9
|
+
const mode = modeIndex === -1 ? "managed" : arguments_[modeIndex + 1];
|
|
10
|
+
const invalidArguments = modeIndex === -1
|
|
11
|
+
? arguments_.length !== 0
|
|
12
|
+
: arguments_.length !== 2 || arguments_.filter((argument) => argument === "--mode").length !== 1;
|
|
13
|
+
if (!["managed", "prebuilt"].includes(mode) || invalidArguments) {
|
|
14
|
+
process.stderr.write("Usage: taproot-docs-conformance [--mode managed|prebuilt]\n");
|
|
15
|
+
process.exitCode = 2;
|
|
18
16
|
} else {
|
|
19
|
-
|
|
17
|
+
const loadCases = mode === "prebuilt" ? loadPrebuiltConformanceCases : loadConformanceCases;
|
|
18
|
+
let failed = false;
|
|
19
|
+
for (const fixture of await loadCases()) {
|
|
20
|
+
const result = mode === "prebuilt"
|
|
21
|
+
? await validatePrebuiltConformanceCase(fixture)
|
|
22
|
+
: await validateArtifact(fixture.manifest, fixture.files);
|
|
23
|
+
const codes = result.ok ? [] : [...new Set(result.errors.map((error) => error.code))].sort();
|
|
24
|
+
const expected = [...fixture.expectedCodes].sort();
|
|
25
|
+
if (result.ok !== fixture.valid || JSON.stringify(codes) !== JSON.stringify(expected)) {
|
|
26
|
+
failed = true;
|
|
27
|
+
process.stderr.write(
|
|
28
|
+
`${fixture.name}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(codes)}\n`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (failed) {
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
} else {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
mode === "prebuilt"
|
|
37
|
+
? "Taproot Docs prebuilt artifact conformance fixtures passed.\n"
|
|
38
|
+
: "Taproot Docs artifact conformance fixtures passed.\n",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
20
41
|
}
|
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { validateArtifactDirectory } from "../src/node.js";
|
|
4
|
+
import { validatePrebuiltArtifactDirectory } from "../src/prebuilt-node.js";
|
|
4
5
|
|
|
5
6
|
const arguments_ = process.argv.slice(2);
|
|
6
7
|
const json = arguments_.includes("--json");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const modeIndex = arguments_.indexOf("--mode");
|
|
9
|
+
const mode = modeIndex === -1 ? "managed" : arguments_[modeIndex + 1];
|
|
10
|
+
const positional = arguments_.filter((argument, index) => (
|
|
11
|
+
argument !== "--json" && argument !== "--mode" && (modeIndex === -1 || index !== modeIndex + 1)
|
|
12
|
+
));
|
|
13
|
+
const invalidArguments = arguments_.filter((argument) => argument === "--mode").length > 1
|
|
14
|
+
|| arguments_.filter((argument) => argument === "--json").length > 1
|
|
15
|
+
|| positional.length > 1
|
|
16
|
+
|| positional.some((argument) => argument.startsWith("--"));
|
|
17
|
+
if (!["managed", "prebuilt"].includes(mode) || invalidArguments) {
|
|
18
|
+
process.stderr.write("Usage: taproot-docs-validate [--json] [--mode managed|prebuilt] [directory]\n");
|
|
19
|
+
process.exitCode = 2;
|
|
14
20
|
} else {
|
|
15
|
-
|
|
21
|
+
const directory = positional[0] ?? process.cwd();
|
|
22
|
+
const result = mode === "prebuilt"
|
|
23
|
+
? await validatePrebuiltArtifactDirectory(directory)
|
|
24
|
+
: await validateArtifactDirectory(directory);
|
|
25
|
+
|
|
26
|
+
if (json) {
|
|
27
|
+
const output = mode === "prebuilt" && result.ok
|
|
28
|
+
? {
|
|
29
|
+
ok: true,
|
|
30
|
+
value: {
|
|
31
|
+
manifest: result.value.manifest,
|
|
32
|
+
fileCount: result.value.fileCount,
|
|
33
|
+
totalBytes: result.value.totalBytes,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
: result;
|
|
37
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
38
|
+
} else if (result.ok) {
|
|
39
|
+
if (mode === "prebuilt") {
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
`Valid Taproot Docs prebuilt artifact: ${result.value.fileCount} payload files, ${result.value.totalBytes} bytes.\n`,
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
process.stdout.write(
|
|
45
|
+
`Valid Taproot Docs artifact: ${result.value.fileCount} semantic files, ${result.value.totalBytes} bytes.\n`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
for (const error of result.errors) process.stderr.write(`${error.code} ${error.path}: ${error.message}\n`);
|
|
50
|
+
}
|
|
51
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
16
52
|
}
|
|
17
|
-
process.exitCode = result.ok ? 0 : 1;
|
package/fixtures/README.md
CHANGED
|
@@ -22,3 +22,22 @@ contract regardless of host ICU data.
|
|
|
22
22
|
localized fixture keeps its one PNG as reviewable base64 in source control; the
|
|
23
23
|
loader materializes it at the manifest path without requiring a generated binary
|
|
24
24
|
file in the contract repository.
|
|
25
|
+
|
|
26
|
+
Prebuilt cases are consumed through `loadPrebuiltConformanceCases()` from
|
|
27
|
+
`@taprootio/docs-artifact/prebuilt/conformance`. The representative
|
|
28
|
+
`prebuilt/valid/espalier/` fixture is a self-contained static tree with HTML,
|
|
29
|
+
repository JavaScript and bundler-shaped CSS, a worker, a minimal WebAssembly
|
|
30
|
+
module, an `/api/` content route, an SVG symbol sprite, an animated SVG,
|
|
31
|
+
Pagefind JavaScript and binary index data, a redirect, stable resources, and the
|
|
32
|
+
canonical 404. The sibling committed golden vector at
|
|
33
|
+
`prebuilt/golden/espalier.tar.gz` freezes the exact
|
|
34
|
+
deterministic USTAR/stored-gzip bytes; the loader exposes those bytes together
|
|
35
|
+
with their expected length and SHA-256. The fixture does not depend on a
|
|
36
|
+
checkout of the Espalier repository. Its adversarial cases pin traversal,
|
|
37
|
+
normalization and case-fold collisions, extra/missing files, hash/size and
|
|
38
|
+
media-type drift, every published collection/byte/path/depth/USTAR bound,
|
|
39
|
+
ambiguous redirects, missing resource and canonical-404 targets, a file that
|
|
40
|
+
becomes non-regular before its validated open, and rejection of
|
|
41
|
+
publisher-controlled headers, origins, routes, and validation hooks. Run the
|
|
42
|
+
loaded cases through `validatePrebuiltConformanceCase()` so both in-memory and
|
|
43
|
+
Node-boundary scenarios use their owning validator.
|