@taprootio/docs-artifact 1.0.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.
Files changed (36) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +362 -0
  3. package/bin/taproot-docs-conformance.js +20 -0
  4. package/bin/taproot-docs-validate.js +17 -0
  5. package/conformance.d.ts +11 -0
  6. package/fixtures/README.md +24 -0
  7. package/fixtures/conformance.json +1630 -0
  8. package/fixtures/invalid/duplicate-json-key.json +1 -0
  9. package/fixtures/invalid/hash-drift/taproot-docs/fragments/welcome.html +1 -0
  10. package/fixtures/invalid/size-drift/taproot-docs/fragments/welcome.html +1 -0
  11. package/fixtures/invalid/unsafe-markup/taproot-docs/fragments/welcome.html +1 -0
  12. package/fixtures/valid/complete/taproot-docs/assets/pixel.png.base64 +1 -0
  13. package/fixtures/valid/complete/taproot-docs/fragments/button.en-us.html +1 -0
  14. package/fixtures/valid/complete/taproot-docs/fragments/button.fr-fr.html +1 -0
  15. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.en-us.html +3 -0
  16. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.fr-fr.html +3 -0
  17. package/fixtures/valid/complete/taproot-docs-manifest.json +231 -0
  18. package/fixtures/valid/minimal/taproot-docs/fragments/welcome.html +1 -0
  19. package/fixtures/valid/minimal/taproot-docs-manifest.json +80 -0
  20. package/index.d.ts +204 -0
  21. package/node.d.ts +6 -0
  22. package/package.json +54 -0
  23. package/schema/taproot-docs-manifest.schema.json +487 -0
  24. package/src/artifact-validator.js +870 -0
  25. package/src/binary.js +67 -0
  26. package/src/conformance.js +578 -0
  27. package/src/constants.js +104 -0
  28. package/src/errors.js +139 -0
  29. package/src/index.js +18 -0
  30. package/src/json.js +516 -0
  31. package/src/manifest-validator.js +650 -0
  32. package/src/markup.js +578 -0
  33. package/src/node-internal.js +4 -0
  34. package/src/node.js +513 -0
  35. package/src/path.js +103 -0
  36. package/src/text.js +30 -0
@@ -0,0 +1,104 @@
1
+ export const MANIFEST_FILE_NAME = "taproot-docs-manifest.json";
2
+ export const SCHEMA_VERSION = 1;
3
+ export const HTML_FRAGMENT_CAPABILITY = "taproot.docs.fragments.html.v1";
4
+
5
+ export const SUPPORTED_CAPABILITIES = Object.freeze([
6
+ HTML_FRAGMENT_CAPABILITY,
7
+ ]);
8
+
9
+ export const LIMITS = Object.freeze({
10
+ manifestBytes: 2 * 1024 * 1024,
11
+ manifestObjectWork: 250_000,
12
+ manifestObjectDepth: 64,
13
+ artifactBytes: 256 * 1024 * 1024,
14
+ files: 20_000,
15
+ resources: 10_000,
16
+ variants: 20_000,
17
+ fragments: 20_000,
18
+ assets: 10_000,
19
+ redirects: 10_000,
20
+ navigationNodes: 20_000,
21
+ navigationDepth: 12,
22
+ headingsPerVariant: 500,
23
+ markupElements: 100_000,
24
+ markupDepth: 128,
25
+ fragmentBytes: 2 * 1024 * 1024,
26
+ assetBytes: 25 * 1024 * 1024,
27
+ decodedPixels: 64 * 1024 * 1024,
28
+ decodedAnimationPixels: 64 * 1024 * 1024,
29
+ animationFrames: 1_000,
30
+ supportedCapabilities: 100,
31
+ string: 2_000,
32
+ title: 300,
33
+ description: 1_000,
34
+ resourceKey: 200,
35
+ artifactPath: 512,
36
+ directoryEntries: 40_000,
37
+ directoryDepth: 32,
38
+ route: 512,
39
+ sourcePath: 1_000,
40
+ url: 2_000,
41
+ validationErrors: 200,
42
+ diagnosticPathScalars: 1_000,
43
+ diagnosticPathBytes: 4_096,
44
+ diagnosticMessageScalars: 2_000,
45
+ diagnosticMessageBytes: 8_192,
46
+ diagnosticAggregateScalars: 16_000,
47
+ diagnosticAggregateBytes: 32_768,
48
+ });
49
+
50
+ export const ASSET_MEDIA_TYPES = Object.freeze([
51
+ "image/gif",
52
+ "image/jpeg",
53
+ "image/png",
54
+ "image/webp",
55
+ ]);
56
+
57
+ export const FRAGMENT_MEDIA_TYPE = "text/html; charset=utf-8";
58
+
59
+ export const RESOURCE_KINDS = Object.freeze([
60
+ "api",
61
+ "changelog",
62
+ "concept",
63
+ "guide",
64
+ "other",
65
+ "reference",
66
+ ]);
67
+
68
+ export const AUDIENCES = Object.freeze([
69
+ "administrator",
70
+ "developer",
71
+ "operator",
72
+ "user",
73
+ ]);
74
+
75
+ export const FRAGMENT_ROLES = Object.freeze(["aside", "body", "example"]);
76
+
77
+ export const RESERVED_ROUTE_PREFIXES = Object.freeze([
78
+ "/.well-known/",
79
+ "/_taproot/",
80
+ "/api/",
81
+ "/assets/",
82
+ "/bust/",
83
+ "/pagefind/",
84
+ "/public/",
85
+ "/taproot-docs/",
86
+ "/404.html/",
87
+ "/favicon.ico/",
88
+ "/index.html/",
89
+ "/manifest.webmanifest/",
90
+ "/robots.txt/",
91
+ "/sitemap.xml/",
92
+ "/taproot-docs-manifest.json/",
93
+ ]);
94
+
95
+ export const RESERVED_ROUTES = Object.freeze([
96
+ "/404/",
97
+ "/404.html/",
98
+ "/favicon.ico/",
99
+ "/index.html/",
100
+ "/manifest.webmanifest/",
101
+ "/robots.txt/",
102
+ "/sitemap.xml/",
103
+ "/taproot-docs-manifest.json/",
104
+ ]);
package/src/errors.js ADDED
@@ -0,0 +1,139 @@
1
+ import { LIMITS } from "./constants.js";
2
+ import { isDisallowedStringCodePoint } from "./text.js";
3
+
4
+ function utf8Bytes(codePoint) {
5
+ if (codePoint <= 0x7f) return 1;
6
+ if (codePoint <= 0x7ff) return 2;
7
+ if (codePoint <= 0xffff) return 3;
8
+ return 4;
9
+ }
10
+
11
+ function escapedCodePoint(codePoint) {
12
+ return `\\u${codePoint.toString(16).padStart(4, "0")}`;
13
+ }
14
+
15
+ function safeDiagnosticPrefix(value, maximumScalars) {
16
+ const source = String(value);
17
+ const result = [];
18
+ let scalarCount = 0;
19
+ let truncated = false;
20
+ for (let offset = 0; offset < source.length;) {
21
+ if (scalarCount >= maximumScalars) {
22
+ truncated = true;
23
+ break;
24
+ }
25
+ const first = source.charCodeAt(offset);
26
+ let scalar;
27
+ let width = 1;
28
+ if (first >= 0xd800 && first <= 0xdbff && offset + 1 < source.length) {
29
+ const second = source.charCodeAt(offset + 1);
30
+ if (second >= 0xdc00 && second <= 0xdfff) {
31
+ scalar = source.slice(offset, offset + 2);
32
+ width = 2;
33
+ }
34
+ }
35
+ if (scalar === undefined) {
36
+ scalar = first >= 0xd800 && first <= 0xdfff
37
+ ? escapedCodePoint(first)
38
+ : source[offset];
39
+ }
40
+ const codePoint = scalar.codePointAt(0);
41
+ if (isDisallowedStringCodePoint(codePoint)) {
42
+ scalar = escapedCodePoint(codePoint);
43
+ }
44
+ result.push(scalar);
45
+ scalarCount += 1;
46
+ offset += width;
47
+ }
48
+ return { value: result.join("").normalize("NFC"), truncated };
49
+ }
50
+
51
+ export function sanitizeDiagnostic(value, maximumScalars, maximumBytes) {
52
+ const prefix = safeDiagnosticPrefix(value, maximumScalars);
53
+ const suffix = "…";
54
+ const suffixBytes = utf8Bytes(suffix.codePointAt(0));
55
+ const result = [];
56
+ let scalarCount = 0;
57
+ let byteCount = 0;
58
+ let truncated = prefix.truncated;
59
+ for (const scalar of prefix.value) {
60
+ const scalarBytes = utf8Bytes(scalar.codePointAt(0));
61
+ if (scalarCount >= maximumScalars || byteCount + scalarBytes > maximumBytes) {
62
+ truncated = true;
63
+ break;
64
+ }
65
+ result.push(scalar);
66
+ scalarCount += 1;
67
+ byteCount += scalarBytes;
68
+ }
69
+ if (truncated && maximumScalars > 0 && maximumBytes >= suffixBytes) {
70
+ while (result.length > 0 && (scalarCount >= maximumScalars || byteCount + suffixBytes > maximumBytes)) {
71
+ const removed = result.pop();
72
+ scalarCount -= 1;
73
+ byteCount -= utf8Bytes(removed.codePointAt(0));
74
+ }
75
+ if (scalarCount < maximumScalars && byteCount + suffixBytes <= maximumBytes) result.push(suffix);
76
+ }
77
+ return result.join("");
78
+ }
79
+
80
+ export function sanitizeValidationError(error) {
81
+ return {
82
+ code: typeof error.code === "string" ? error.code : "validation.invalid_error",
83
+ path: sanitizeDiagnostic(error.path, LIMITS.diagnosticPathScalars, LIMITS.diagnosticPathBytes),
84
+ message: sanitizeDiagnostic(error.message, LIMITS.diagnosticMessageScalars, LIMITS.diagnosticMessageBytes),
85
+ };
86
+ }
87
+
88
+ export function sortValidationErrors(errors) {
89
+ return [...errors].sort((left, right) => (
90
+ compareCanonicalStrings(left.path, right.path)
91
+ || compareCanonicalStrings(left.code, right.code)
92
+ || compareCanonicalStrings(left.message, right.message)
93
+ ));
94
+ }
95
+
96
+ export function compareCanonicalStrings(left, right) {
97
+ if (left === right) return 0;
98
+ return left < right ? -1 : 1;
99
+ }
100
+
101
+ export class ValidationContext {
102
+ constructor() {
103
+ this.errors = [];
104
+ this.truncated = false;
105
+ }
106
+
107
+ add(code, path, message) {
108
+ if (this.errors.length < LIMITS.validationErrors) {
109
+ this.errors.push(sanitizeValidationError({ code, path, message }));
110
+ return;
111
+ }
112
+ if (!this.truncated) {
113
+ this.errors.push({
114
+ code: "validation.too_many_errors",
115
+ path: "$",
116
+ message: `Validation stopped after ${LIMITS.validationErrors} errors.`,
117
+ });
118
+ this.truncated = true;
119
+ }
120
+ }
121
+
122
+ finish(value) {
123
+ if (this.errors.length > 0) return { ok: false, errors: sortValidationErrors(this.errors) };
124
+ return { ok: true, value };
125
+ }
126
+ }
127
+
128
+ export class DocsArtifactValidationError extends Error {
129
+ constructor(errors) {
130
+ const sanitizedErrors = errors.map(sanitizeValidationError);
131
+ super(sanitizeDiagnostic(
132
+ sanitizedErrors.map((error) => `${error.code} ${error.path}: ${error.message}`).join("\n"),
133
+ LIMITS.diagnosticAggregateScalars,
134
+ LIMITS.diagnosticAggregateBytes,
135
+ ));
136
+ this.name = "DocsArtifactValidationError";
137
+ this.errors = sanitizedErrors;
138
+ }
139
+ }
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export {
2
+ ASSET_MEDIA_TYPES,
3
+ AUDIENCES,
4
+ FRAGMENT_MEDIA_TYPE,
5
+ FRAGMENT_ROLES,
6
+ HTML_FRAGMENT_CAPABILITY,
7
+ LIMITS,
8
+ MANIFEST_FILE_NAME,
9
+ RESERVED_ROUTE_PREFIXES,
10
+ RESERVED_ROUTES,
11
+ RESOURCE_KINDS,
12
+ SCHEMA_VERSION,
13
+ SUPPORTED_CAPABILITIES,
14
+ } from "./constants.js";
15
+ export { assertValidArtifact, validateArtifact } from "./artifact-validator.js";
16
+ export { DocsArtifactValidationError } from "./errors.js";
17
+ export { assertValidManifest, serializeManifest, validateManifest } from "./manifest-validator.js";
18
+ export { normalizeArtifactPath, normalizeRoute, normalizeSourcePath } from "./path.js";