@topik/core 0.1.0-alpha.4 → 0.1.0-alpha.5
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/index.d.mts +374 -16
- package/dist/index.mjs +2572 -354
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import
|
|
3
|
-
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { access, readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import wikiV1Schema from "@topik/schema/wiki/v1.json" with { type: "json" };
|
|
3
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
5
4
|
import { parse } from "yaml";
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { lstat, open, readdir, realpath } from "node:fs/promises";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { analyzeTopikContent,
|
|
8
|
+
import { analyzeTopikContent, extractTopikAssetOccurrences, isTopikGeneratedAssetName, parseTopikGeneratedAssetName, rewriteTopikAssetOccurrences, sanitizeTopikContentDiagnostic, topikLinkDiagnosticMessage, validateTopikAssetReference, validateTopikContent } from "@topik/content-schema";
|
|
8
9
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
10
|
+
import rawAssetV1Schema from "@topik/schema/asset/v1.json" with { type: "json" };
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { promisify } from "node:util";
|
|
9
13
|
import addFormats from "ajv-formats";
|
|
14
|
+
import courseV1Schema from "@topik/schema/course/v1.json" with { type: "json" };
|
|
15
|
+
import courseModuleV1Schema from "@topik/schema/course-module/v1.json" with { type: "json" };
|
|
16
|
+
import coursePageV1Schema from "@topik/schema/course-page/v1.json" with { type: "json" };
|
|
17
|
+
import guideV1Schema from "@topik/schema/guide/v1.json" with { type: "json" };
|
|
18
|
+
import personV1Schema from "@topik/schema/person/v1.json" with { type: "json" };
|
|
19
|
+
import wikiPageV1Schema from "@topik/schema/wiki-page/v1.json" with { type: "json" };
|
|
10
20
|
import { EventEmitter } from "node:events";
|
|
11
21
|
import { watch as watch$1 } from "chokidar";
|
|
12
22
|
//#region src/config/collection.ts
|
|
@@ -20,8 +30,8 @@ function parseCollectionConfig(raw) {
|
|
|
20
30
|
}
|
|
21
31
|
//#endregion
|
|
22
32
|
//#region src/config/wiki.ts
|
|
23
|
-
const wikiNavIconPattern = new RegExp(
|
|
24
|
-
const wikiExternalHrefPattern = new RegExp(
|
|
33
|
+
const wikiNavIconPattern = new RegExp(wikiV1Schema.$defs.groupNode.properties.icon.pattern);
|
|
34
|
+
const wikiExternalHrefPattern = new RegExp(wikiV1Schema.$defs.externalTabNode.properties.href.pattern);
|
|
25
35
|
const pathSegment = z.string().min(1).max(256).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Slugs must be lowercase DNS-style segments");
|
|
26
36
|
const wikiPagePath = z.string().min(1).max(512).regex(/^(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/(?:[a-z0-9]+(?:-[a-z0-9]+)*))*$/, "Wiki page paths must use lowercase DNS-style segments separated by '/'");
|
|
27
37
|
const title = z.string().min(1).max(256);
|
|
@@ -174,236 +184,2286 @@ function parseWikiConfig(raw) {
|
|
|
174
184
|
return wikiConfigSchema.parse(raw);
|
|
175
185
|
}
|
|
176
186
|
//#endregion
|
|
187
|
+
//#region src/assets/path-unicode-v17.ts
|
|
188
|
+
/**
|
|
189
|
+
* Unicode 17 path-forbidden code points, encoded as inclusive hexadecimal ranges.
|
|
190
|
+
*
|
|
191
|
+
* Generated from UnicodeData.txt, PropList.txt, and DerivedCoreProperties.txt for
|
|
192
|
+
* Unicode 17.0.0. The table contains general categories Cc, Cf, Cs, Co, and Cn,
|
|
193
|
+
* plus White_Space (except U+0020), Default_Ignorable_Code_Point, Bidi_Control,
|
|
194
|
+
* and Noncharacter_Code_Point.
|
|
195
|
+
*
|
|
196
|
+
* Source SHA-256:
|
|
197
|
+
* - UnicodeData.txt: 2e1efc1dcb59c575eedf5ccae60f95229f706ee6d031835247d843c11d96470c
|
|
198
|
+
* - PropList.txt: 130dcddcaadaf071008bdfce1e7743e04fdfbc910886f017d9f9ac931d8c64dd
|
|
199
|
+
* - DerivedCoreProperties.txt: 24c7fed1195c482faaefd5c1e7eb821c5ee1fb6de07ecdbaa64b56a99da22c08
|
|
200
|
+
*/
|
|
201
|
+
const TOPIK_PATH_FORBIDDEN_V17_DATA = [
|
|
202
|
+
"0..1f;7f..a0;ad;34f;378..379;380..383;38b;38d;3a2;530;557..558;58b..58c;590;5c8..5cf;5eb..5ee;5f5..605;61c;6dd;70e..70f;74b..74c;7b2..7bf;7fb..7fc;82e..82f;83f;85c..85d;85f;86b..86f;890..896;8e2;984;98d..98e;991..992;9a9;9b1;9b3..9b5;9ba..9bb;9c5..9c6;9c9..9ca;9cf..9d6;9d8..9db;9de;9e4..9e5;9ff..a00;a04;a0b..a0e;a11..a12;a29;a31;a34;a37;a3a..a3b;a3d;a43..a46;a49..a4a;a4e..a50;a52..a58;a5d;a5f..a65;a77..a80;a84;a8e;a92;aa9;ab1;ab4;aba..abb;ac6;aca;ace..acf;ad1..adf;ae4..ae5;af2..af8;b00;b04;b0d..b0e;b11..b12;b29;b31;b34;b3a..b3b;b45..b46;b49..b4a;b4e..b54;b58..b5b;b5e;b64..b65;b78..b81;b84;b8b..b8d;b91;b96..b98;b9b;b9d;ba0..ba2;ba5..ba7;bab..bad;bba..bbd;bc3..bc5;bc9;bce..bcf;bd1..bd6;bd8..be5;bfb..bff;c0d;c11;c29;c3a..c3b;c45;c49;c4e..c54;c57;c5b;c5e..c5f;c64..c65;c70..c76;c8d;c91;ca9;cb4;cba..cbb;cc5;cc9;cce..cd4;cd7..cdb;cdf;ce4..ce5;cf0;cf4..cff;d0d;d11;d45;d49;d50..d53;d64..d65;d80;d",
|
|
203
|
+
"84;d97..d99;db2;dbc;dbe..dbf;dc7..dc9;dcb..dce;dd5;dd7;de0..de5;df0..df1;df5..e00;e3b..e3e;e5c..e80;e83;e85;e8b;ea4;ea6;ebe..ebf;ec5;ec7;ecf;eda..edb;ee0..eff;f48;f6d..f70;f98;fbd;fcd;fdb..fff;10c6;10c8..10cc;10ce..10cf;115f..1160;1249;124e..124f;1257;1259;125e..125f;1289;128e..128f;12b1;12b6..12b7;12bf;12c1;12c6..12c7;12d7;1311;1316..1317;135b..135c;137d..137f;139a..139f;13f6..13f7;13fe..13ff;1680;169d..169f;16f9..16ff;1716..171e;1737..173f;1754..175f;176d;1771;1774..177f;17b4..17b5;17de..17df;17ea..17ef;17fa..17ff;180b..180f;181a..181f;1879..187f;18ab..18af;18f6..18ff;191f;192c..192f;193c..193f;1941..1943;196e..196f;1975..197f;19ac..19af;19ca..19cf;19db..19dd;1a1c..1a1d;1a5f;1a7d..1a7e;1a8a..1a8f;1a9a..1a9f;1aae..1aaf;1ade..1adf;1aec..1aff;1b4d;1bf4..1bfb;1c38..1c3a;1c4a..1c4c;1c8b..1c8f;1cbb..1cbc;1cc8..1ccf;1cfb..1cff;1f16..1f17;1f1e..1f1f;1f46..1f47;1f4e..1f4f;1f58;1f5a;1f5c;1f5e;1f",
|
|
204
|
+
"7e..1f7f;1fb5;1fc5;1fd4..1fd5;1fdc;1ff0..1ff1;1ff5;1fff..200f;2028..202f;205f..206f;2072..2073;208f;209d..209f;20c2..20cf;20f1..20ff;218c..218f;242a..243f;244b..245f;2b74..2b75;2cf4..2cf8;2d26;2d28..2d2c;2d2e..2d2f;2d68..2d6e;2d71..2d7e;2d97..2d9f;2da7;2daf;2db7;2dbf;2dc7;2dcf;2dd7;2ddf;2e5e..2e7f;2e9a;2ef4..2eff;2fd6..2fef;3000;3040;3097..3098;3100..3104;3130;3164;318f;31e6..31ee;321f;a48d..a48f;a4c7..a4cf;a62c..a63f;a6f8..a6ff;a7dd..a7f0;a82d..a82f;a83a..a83f;a878..a87f;a8c6..a8cd;a8da..a8df;a954..a95e;a97d..a97f;a9ce;a9da..a9dd;a9ff;aa37..aa3f;aa4e..aa4f;aa5a..aa5b;aac3..aada;aaf7..ab00;ab07..ab08;ab0f..ab10;ab17..ab1f;ab27;ab2f;ab6c..ab6f;abee..abef;abfa..abff;d7a4..d7af;d7c7..d7ca;d7fc..f8ff;fa6e..fa6f;fada..faff;fb07..fb12;fb18..fb1c;fb37;fb3d;fb3f;fb42;fb45;fdd0..fdef;fe00..fe0f;fe1a..fe1f;fe53;fe67;fe6c..fe6f;fe75;fefd..ff00;ffa0;ffbf..ffc1;ffc8..ffc9;ffd0..ffd1;ffd8..ffd9;ffdd..",
|
|
205
|
+
"ffdf;ffe7;ffef..fffb;fffe..ffff;1000c;10027;1003b;1003e;1004e..1004f;1005e..1007f;100fb..100ff;10103..10106;10134..10136;1018f;1019d..1019f;101a1..101cf;101fe..1027f;1029d..1029f;102d1..102df;102fc..102ff;10324..1032c;1034b..1034f;1037b..1037f;1039e;103c4..103c7;103d6..103ff;1049e..1049f;104aa..104af;104d4..104d7;104fc..104ff;10528..1052f;10564..1056e;1057b;1058b;10593;10596;105a2;105b2;105ba;105bd..105bf;105f4..105ff;10737..1073f;10756..1075f;10768..1077f;10786;107b1;107bb..107ff;10806..10807;10809;10836;10839..1083b;1083d..1083e;10856;1089f..108a6;108b0..108df;108f3;108f6..108fa;1091c..1091e;1093a..1093e;1095a..1097f;109b8..109bb;109d0..109d1;10a04;10a07..10a0b;10a14;10a18;10a36..10a37;10a3b..10a3e;10a49..10a4f;10a59..10a5f;10aa0..10abf;10ae7..10aea;10af7..10aff;10b36..10b38;10b56..10b57;10b73..10b77;10b92..10b98;10b9d..10ba8;10bb0..10bff;10c49..10c7f;10cb3..10cbf;10cf3..10cf9;10d28..1",
|
|
206
|
+
"0d2f;10d3a..10d3f;10d66..10d68;10d86..10d8d;10d90..10e5f;10e7f;10eaa;10eae..10eaf;10eb2..10ec1;10ec8..10ecf;10ed9..10ef9;10f28..10f2f;10f5a..10f6f;10f8a..10faf;10fcc..10fdf;10ff7..10fff;1104e..11051;11076..1107e;110bd;110c3..110cf;110e9..110ef;110fa..110ff;11135;11148..1114f;11177..1117f;111e0;111f5..111ff;11212;11242..1127f;11287;11289;1128e;1129e;112aa..112af;112eb..112ef;112fa..112ff;11304;1130d..1130e;11311..11312;11329;11331;11334;1133a;11345..11346;11349..1134a;1134e..1134f;11351..11356;11358..1135c;11364..11365;1136d..1136f;11375..1137f;1138a;1138c..1138d;1138f;113b6;113c1;113c3..113c4;113c6;113cb;113d6;113d9..113e0;113e3..113ff;1145c;11462..1147f;114c8..114cf;114da..1157f;115b6..115b7;115de..115ff;11645..1164f;1165a..1165f;1166d..1167f;116ba..116bf;116ca..116cf;116e4..116ff;1171b..1171c;1172c..1172f;11747..117ff;1183c..1189f;118f3..118fe;11907..11908;1190a..1190b;11914;11917;1193",
|
|
207
|
+
"6;11939..1193a;11947..1194f;1195a..1199f;119a8..119a9;119d8..119d9;119e5..119ff;11a48..11a4f;11aa3..11aaf;11af9..11aff;11b0a..11b5f;11b68..11bbf;11be2..11bef;11bfa..11bff;11c09;11c37;11c46..11c4f;11c6d..11c6f;11c90..11c91;11ca8;11cb7..11cff;11d07;11d0a;11d37..11d39;11d3b;11d3e;11d48..11d4f;11d5a..11d5f;11d66;11d69;11d8f;11d92;11d99..11d9f;11daa..11daf;11ddc..11ddf;11dea..11edf;11ef9..11eff;11f11;11f3b..11f3d;11f5b..11faf;11fb1..11fbf;11ff2..11ffe;1239a..123ff;1246f;12475..1247f;12544..12f8f;12ff3..12fff;13430..1343f;13456..1345f;143fb..143ff;14647..160ff;1613a..167ff;16a39..16a3f;16a5f;16a6a..16a6d;16abf;16aca..16acf;16aee..16aef;16af6..16aff;16b46..16b4f;16b5a;16b62;16b78..16b7c;16b90..16d3f;16d7a..16e3f;16e9b..16e9f;16eb9..16eba;16ed4..16eff;16f4b..16f4e;16f88..16f8e;16fa0..16fdf;16fe5..16fef;16ff7..16fff;18cd6..18cfe;18d1f..18d7f;18df3..1afef;1aff4;1affc;1afff;1b123..1b131;1b133..1b14",
|
|
208
|
+
"f;1b153..1b154;1b156..1b163;1b168..1b16f;1b2fc..1bbff;1bc6b..1bc6f;1bc7d..1bc7f;1bc89..1bc8f;1bc9a..1bc9b;1bca0..1cbff;1ccfd..1ccff;1ceb4..1ceb9;1ced1..1cedf;1cef1..1ceff;1cf2e..1cf2f;1cf47..1cf4f;1cfc4..1cfff;1d0f6..1d0ff;1d127..1d128;1d173..1d17a;1d1eb..1d1ff;1d246..1d2bf;1d2d4..1d2df;1d2f4..1d2ff;1d357..1d35f;1d379..1d3ff;1d455;1d49d;1d4a0..1d4a1;1d4a3..1d4a4;1d4a7..1d4a8;1d4ad;1d4ba;1d4bc;1d4c4;1d506;1d50b..1d50c;1d515;1d51d;1d53a;1d53f;1d545;1d547..1d549;1d551;1d6a6..1d6a7;1d7cc..1d7cd;1da8c..1da9a;1daa0;1dab0..1deff;1df1f..1df24;1df2b..1dfff;1e007;1e019..1e01a;1e022;1e025;1e02b..1e02f;1e06e..1e08e;1e090..1e0ff;1e12d..1e12f;1e13e..1e13f;1e14a..1e14d;1e150..1e28f;1e2af..1e2bf;1e2fa..1e2fe;1e300..1e4cf;1e4fa..1e5cf;1e5fb..1e5fe;1e600..1e6bf;1e6df;1e6f6..1e6fd;1e700..1e7df;1e7e7;1e7ec;1e7ef;1e7ff;1e8c5..1e8c6;1e8d7..1e8ff;1e94c..1e94f;1e95a..1e95d;1e960..1ec70;1ecb5..1ed00;1ed3e..1edff",
|
|
209
|
+
";1ee04;1ee20;1ee23;1ee25..1ee26;1ee28;1ee33;1ee38;1ee3a;1ee3c..1ee41;1ee43..1ee46;1ee48;1ee4a;1ee4c;1ee50;1ee53;1ee55..1ee56;1ee58;1ee5a;1ee5c;1ee5e;1ee60;1ee63;1ee65..1ee66;1ee6b;1ee73;1ee78;1ee7d;1ee7f;1ee8a;1ee9c..1eea0;1eea4;1eeaa;1eebc..1eeef;1eef2..1efff;1f02c..1f02f;1f094..1f09f;1f0af..1f0b0;1f0c0;1f0d0;1f0f6..1f0ff;1f1ae..1f1e5;1f203..1f20f;1f23c..1f23f;1f249..1f24f;1f252..1f25f;1f266..1f2ff;1f6d9..1f6db;1f6ed..1f6ef;1f6fd..1f6ff;1f7da..1f7df;1f7ec..1f7ef;1f7f1..1f7ff;1f80c..1f80f;1f848..1f84f;1f85a..1f85f;1f888..1f88f;1f8ae..1f8af;1f8bc..1f8bf;1f8c2..1f8cf;1f8d9..1f8ff;1fa58..1fa5f;1fa6e..1fa6f;1fa7d..1fa7f;1fa8b..1fa8d;1fac7;1fac9..1facc;1fadd..1fade;1faeb..1faee;1faf9..1faff;1fb93;1fbfb..1ffff;2a6e0..2a6ff;2b81e..2b81f;2ceae..2ceaf;2ebe1..2ebef;2ee5e..2f7ff;2fa1e..2ffff;3134b..3134f;3347a..10ffff"
|
|
210
|
+
].join("");
|
|
211
|
+
/**
|
|
212
|
+
* Marks first assigned in Unicode 17. Node 22.12's Unicode 16 normalizer does
|
|
213
|
+
* not know their canonical combining classes, so topik-path-v1 rejects them on
|
|
214
|
+
* every runtime. Unicode 17 added no canonical decompositions; these are the
|
|
215
|
+
* complete new normalization-order boundary for NFC acceptance.
|
|
216
|
+
*/
|
|
217
|
+
const TOPIK_PATH_NORMALIZATION_SENSITIVE_V17_RANGES = [
|
|
218
|
+
[6863, 6877],
|
|
219
|
+
[6880, 6891],
|
|
220
|
+
[69370, 69371],
|
|
221
|
+
[72544, 72551],
|
|
222
|
+
[124643, 124643],
|
|
223
|
+
[124646, 124646],
|
|
224
|
+
[124654, 124655],
|
|
225
|
+
[124661, 124661]
|
|
226
|
+
];
|
|
227
|
+
const TOPIK_PATH_FORBIDDEN_V17_RANGES = TOPIK_PATH_FORBIDDEN_V17_DATA.split(";").map((record) => {
|
|
228
|
+
const [start, end = start] = record.split("..");
|
|
229
|
+
return [Number.parseInt(start, 16), Number.parseInt(end, 16)];
|
|
230
|
+
});
|
|
231
|
+
function isTopikPathCodePointForbiddenV17(codePoint) {
|
|
232
|
+
return isCodePointInRanges(codePoint, TOPIK_PATH_FORBIDDEN_V17_RANGES);
|
|
233
|
+
}
|
|
234
|
+
function isTopikPathNormalizationSensitiveV17(codePoint) {
|
|
235
|
+
return isCodePointInRanges(codePoint, TOPIK_PATH_NORMALIZATION_SENSITIVE_V17_RANGES);
|
|
236
|
+
}
|
|
237
|
+
function isCodePointInRanges(codePoint, ranges) {
|
|
238
|
+
let low = 0;
|
|
239
|
+
let high = ranges.length - 1;
|
|
240
|
+
while (low <= high) {
|
|
241
|
+
const middle = low + high >>> 1;
|
|
242
|
+
const [start, end] = ranges[middle];
|
|
243
|
+
if (codePoint < start) high = middle - 1;
|
|
244
|
+
else if (codePoint > end) low = middle + 1;
|
|
245
|
+
else return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/compile/public-errors.ts
|
|
251
|
+
const PUBLIC_COMPILE_ERROR_MESSAGES = {
|
|
252
|
+
"config-access-failed": "Configuration file could not be accessed.",
|
|
253
|
+
"config-invalid": "Configuration is invalid.",
|
|
254
|
+
"config-not-found": "Required configuration file was not found.",
|
|
255
|
+
"config-parse-failed": "Configuration file could not be parsed.",
|
|
256
|
+
"config-read-failed": "Configuration file could not be read safely.",
|
|
257
|
+
"file-not-regular": "Compilation input must be a regular file.",
|
|
258
|
+
"file-outside-compilation-root": "Compilation input resolves outside the source root.",
|
|
259
|
+
"frontmatter-invalid": "Document frontmatter is invalid.",
|
|
260
|
+
"reference-list-invalid": "Document resource references are invalid.",
|
|
261
|
+
"wiki-page-not-found": "A configured Wiki page file was not found."
|
|
262
|
+
};
|
|
263
|
+
function publicCompileErrorMessage(id) {
|
|
264
|
+
return typeof id === "string" && Object.hasOwn(PUBLIC_COMPILE_ERROR_MESSAGES, id) ? PUBLIC_COMPILE_ERROR_MESSAGES[id] : void 0;
|
|
265
|
+
}
|
|
266
|
+
/** Public compiler failure with fixed wording, stable ID, and optional safe relative location. */
|
|
267
|
+
var PublicCompileError = class extends Error {
|
|
268
|
+
id;
|
|
269
|
+
location;
|
|
270
|
+
constructor(id, location) {
|
|
271
|
+
super(publicCompileErrorMessage(id) ?? "Compilation failed.");
|
|
272
|
+
this.name = "PublicCompileError";
|
|
273
|
+
this.id = id;
|
|
274
|
+
const safeLocation = sanitizeCompileLocation(location);
|
|
275
|
+
if (safeLocation !== void 0) this.location = safeLocation;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function sanitizeCompileLocation(value) {
|
|
279
|
+
if (value === void 0 || value.length === 0 || value.length > 768) return void 0;
|
|
280
|
+
if (isAbsolute(value) || win32.isAbsolute(value)) return void 0;
|
|
281
|
+
const normalized = value.replaceAll("\\", "/");
|
|
282
|
+
if (normalized.split("/").some((component) => component.length === 0 || component === "." || component === "..") || containsForbiddenCodePoint(normalized) || /[?#:@%]/u.test(normalized)) return;
|
|
283
|
+
return posix.normalize(normalized);
|
|
284
|
+
}
|
|
285
|
+
function containsForbiddenCodePoint(value) {
|
|
286
|
+
for (const character of value) if (isTopikPathCodePointForbiddenV17(character.codePointAt(0) ?? 0)) return true;
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/compile/files.ts
|
|
291
|
+
const SAFE_READ_FLAGS = constants.O_RDONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0) | (typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0);
|
|
292
|
+
var FileOutsideCompilationRootError = class extends PublicCompileError {
|
|
293
|
+
constructor(filePath, rootDir) {
|
|
294
|
+
super("file-outside-compilation-root", safeRelativePath(filePath, rootDir));
|
|
295
|
+
this.name = "FileOutsideCompilationRootError";
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var FileNotRegularError = class extends PublicCompileError {
|
|
299
|
+
constructor(filePath, rootDir) {
|
|
300
|
+
super("file-not-regular", safeRelativePath(filePath, rootDir));
|
|
301
|
+
this.name = "FileNotRegularError";
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
async function readRegularFileWithinRoot(filePath, rootDir, encoding) {
|
|
305
|
+
const handle = await openRegularFileWithinRoot(filePath, rootDir);
|
|
306
|
+
try {
|
|
307
|
+
return encoding == null ? await handle.readFile() : await handle.readFile({ encoding });
|
|
308
|
+
} finally {
|
|
309
|
+
await handle.close();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function assertRegularFileWithinRoot(filePath, rootDir) {
|
|
313
|
+
await (await openRegularFileWithinRoot(filePath, rootDir)).close();
|
|
314
|
+
}
|
|
315
|
+
async function openRegularFileWithinRoot(filePath, rootDir) {
|
|
316
|
+
const authoredRoot = resolve(rootDir);
|
|
317
|
+
const authoredFile = resolve(filePath);
|
|
318
|
+
const [canonicalRoot, canonicalFile] = await Promise.all([realpath(rootDir), realpath(filePath)]);
|
|
319
|
+
if (!isWithinRoot(canonicalRoot, canonicalFile)) throw new FileOutsideCompilationRootError(filePath, rootDir);
|
|
320
|
+
if (authoredRoot !== canonicalRoot || authoredFile !== canonicalFile) throw new FileNotRegularError(filePath, rootDir);
|
|
321
|
+
if (!(await lstat(canonicalFile)).isFile()) throw new FileNotRegularError(filePath, rootDir);
|
|
322
|
+
const handle = await open(canonicalFile, SAFE_READ_FLAGS);
|
|
323
|
+
try {
|
|
324
|
+
if (!(await handle.stat()).isFile()) throw new FileNotRegularError(filePath, rootDir);
|
|
325
|
+
return handle;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
await handle.close();
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function isWithinRoot(rootDir, filePath) {
|
|
332
|
+
const rel = relative(rootDir, filePath);
|
|
333
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
334
|
+
}
|
|
335
|
+
function safeRelativePath(filePath, rootDir) {
|
|
336
|
+
const location = relative(resolve(rootDir), resolve(filePath)).split(sep).join("/");
|
|
337
|
+
return location.length > 0 && !location.startsWith("../") && location !== ".." ? location : void 0;
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
177
340
|
//#region src/compile/config.ts
|
|
178
|
-
async function
|
|
341
|
+
async function readOptionalConfigFileWithPath(dir, candidates) {
|
|
179
342
|
for (const name of candidates) {
|
|
180
343
|
const filePath = join(dir, name);
|
|
181
344
|
let raw;
|
|
182
345
|
try {
|
|
183
|
-
raw = await
|
|
184
|
-
} catch (error) {
|
|
185
|
-
if (error.code === "ENOENT") continue;
|
|
186
|
-
throw new
|
|
346
|
+
raw = await readRegularFileWithinRoot(filePath, dir, "utf-8");
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (error.code === "ENOENT") continue;
|
|
349
|
+
throw new PublicCompileError("config-read-failed", name);
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
return {
|
|
353
|
+
path: name,
|
|
354
|
+
value: name.endsWith(".json") ? JSON.parse(raw) : parse(raw)
|
|
355
|
+
};
|
|
356
|
+
} catch {
|
|
357
|
+
throw new PublicCompileError("config-parse-failed", name);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function findConfigFile(dir, candidates) {
|
|
362
|
+
for (const name of candidates) try {
|
|
363
|
+
await assertRegularFileWithinRoot(join(dir, name), dir);
|
|
364
|
+
return name;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error.code === "ENOENT") continue;
|
|
367
|
+
throw new PublicCompileError("config-access-failed", name);
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/assets/constants.ts
|
|
373
|
+
const ASSET_API_VERSION = "v1";
|
|
374
|
+
const ASSET_TYPE = "Asset";
|
|
375
|
+
const ASSET_SCHEMA_ID = "https://topik.dev/schemas/asset/v1.json";
|
|
376
|
+
const TOPIK_JSON_VERSION = "topik-json-v1";
|
|
377
|
+
const TOPIK_PATH_VERSION = "topik-path-v1";
|
|
378
|
+
const TOPIK_ASSET_REFERENCE_VERSION = "topik-asset-reference-v1";
|
|
379
|
+
const TOPIK_MATERIALIZATION_VERSION = "topik-materialization-v1";
|
|
380
|
+
const TOPIK_ASSET_NAME_VERSION = "topik-asset-name-v1";
|
|
381
|
+
const TOPIK_BLOB_OUTPUT_PREFIX = "blobs";
|
|
382
|
+
const TOPIK_PATH_V1_DESCRIPTOR = {
|
|
383
|
+
collisionNormalization: "toNFKC_Casefold",
|
|
384
|
+
id: TOPIK_PATH_VERSION,
|
|
385
|
+
localReferenceEncoding: "rfc3986-utf8-upper-v1",
|
|
386
|
+
maxComponentUtf8Bytes: 255,
|
|
387
|
+
maxComponents: 64,
|
|
388
|
+
maxRepositoryPathUtf8Bytes: 768,
|
|
389
|
+
normalizationSensitiveExclusion: "unicode-17-new-marks",
|
|
390
|
+
storageNormalization: "NFC",
|
|
391
|
+
unicodeVersion: "17.0.0"
|
|
392
|
+
};
|
|
393
|
+
const TOPIK_ASSET_LIMITS = {
|
|
394
|
+
maxDescriptorBytes: 1048576,
|
|
395
|
+
maxAssetBytes: 268435456,
|
|
396
|
+
maxAssets: 1e4,
|
|
397
|
+
maxJsonDepth: 8,
|
|
398
|
+
maxComponentUtf8Bytes: 255,
|
|
399
|
+
maxComponents: 64,
|
|
400
|
+
maxRepositoryPathUtf8Bytes: 768
|
|
401
|
+
};
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/assets/diagnostics.ts
|
|
404
|
+
const TOPIK_ASSET_DIAGNOSTIC_IDS = [
|
|
405
|
+
"TOPIK_ASSET_UNSUPPORTED_VERSION",
|
|
406
|
+
"TOPIK_ASSET_DUPLICATE_MEMBER",
|
|
407
|
+
"TOPIK_ASSET_SCHEMA_INVALID",
|
|
408
|
+
"TOPIK_ASSET_NON_CANONICAL",
|
|
409
|
+
"TOPIK_ASSET_NAME_INVALID",
|
|
410
|
+
"TOPIK_ASSET_NAME_COLLISION",
|
|
411
|
+
"TOPIK_ASSET_SOURCE_NAMESPACE_REQUIRED",
|
|
412
|
+
"TOPIK_ASSET_SOURCE_NAMESPACE_INVALID",
|
|
413
|
+
"TOPIK_ASSET_PATH_INVALID",
|
|
414
|
+
"TOPIK_ASSET_PATH_COLLISION",
|
|
415
|
+
"TOPIK_ASSET_REFERENCE_AMBIGUOUS",
|
|
416
|
+
"TOPIK_ASSET_REFERENCE_MALFORMED",
|
|
417
|
+
"TOPIK_ASSET_REFERENCE_MISSING",
|
|
418
|
+
"TOPIK_ASSET_FILE_MISSING",
|
|
419
|
+
"TOPIK_ASSET_DIGEST_MISMATCH",
|
|
420
|
+
"TOPIK_ASSET_SIZE_MISMATCH",
|
|
421
|
+
"TOPIK_ASSET_MEDIA_TYPE_MISMATCH",
|
|
422
|
+
"TOPIK_ASSET_FILE_TYPE_UNSUPPORTED",
|
|
423
|
+
"TOPIK_ASSET_ACTIVE_CONTENT_UNSUPPORTED",
|
|
424
|
+
"TOPIK_ASSET_REFERENCE_ACCESSIBILITY_INVALID",
|
|
425
|
+
"TOPIK_EXTERNAL_REFERENCE_UNSAFE",
|
|
426
|
+
"TOPIK_ASSET_INVENTORY_INCOMPLETE",
|
|
427
|
+
"TOPIK_ASSET_VERSION_INCOMPARABLE"
|
|
428
|
+
];
|
|
429
|
+
const TOPIK_ASSET_DEFAULT_CORRELATION_ID = "cor_00000000000000000000000000";
|
|
430
|
+
const TOPIK_ASSET_CORRELATION_ID_PATTERN = /^cor_[0-7][0-9a-hjkmnp-tv-z]{25}$/u;
|
|
431
|
+
function topikAssetDiagnostic(id, message, options = {}) {
|
|
432
|
+
const location = options.location ?? {};
|
|
433
|
+
return {
|
|
434
|
+
id,
|
|
435
|
+
correlationId: options.correlationId ?? "cor_00000000000000000000000000",
|
|
436
|
+
severity: "error",
|
|
437
|
+
consequence: options.consequence ?? "block-resource",
|
|
438
|
+
descriptorVersion: sanitizeDescriptorVersion(options.descriptorVersion ?? "Asset/v1"),
|
|
439
|
+
location: {
|
|
440
|
+
...location.jsonPointer === void 0 ? {} : { jsonPointer: sanitizeJsonPointer(location.jsonPointer) },
|
|
441
|
+
...location.contentPosition === void 0 ? {} : { contentPosition: sanitizeAsciiField(location.contentPosition, "[redacted]") },
|
|
442
|
+
...location.path === void 0 ? {} : { path: sanitizePath(location.path) },
|
|
443
|
+
...location.key === void 0 ? {} : { key: sanitizeKey(location.key) },
|
|
444
|
+
...location.commit === void 0 ? {} : { commit: sanitizeCommit(location.commit) }
|
|
445
|
+
},
|
|
446
|
+
recovery: options.recovery ?? "repair-source",
|
|
447
|
+
...options.reason === void 0 ? {} : { reason: options.reason },
|
|
448
|
+
message: sanitizeMessage(message)
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function correlateTopikAssetResult(result, correlationId) {
|
|
452
|
+
if (!TOPIK_ASSET_CORRELATION_ID_PATTERN.test(correlationId)) throw new RangeError("Correlation ID must be opaque 128-bit lowercase Crockford form");
|
|
453
|
+
if (result.ok) return result;
|
|
454
|
+
return {
|
|
455
|
+
...result,
|
|
456
|
+
diagnostics: result.diagnostics.map((diagnostic) => ({
|
|
457
|
+
...diagnostic,
|
|
458
|
+
correlationId
|
|
459
|
+
}))
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const SAFE_ASCII_FIELD = /^[\x20-\x7e]{1,1024}$/u;
|
|
463
|
+
function sanitizeMessage(value) {
|
|
464
|
+
return SAFE_ASCII_FIELD.test(value) ? value : "Diagnostic detail was redacted";
|
|
465
|
+
}
|
|
466
|
+
function sanitizeAsciiField(value, replacement) {
|
|
467
|
+
return SAFE_ASCII_FIELD.test(value) ? value : replacement;
|
|
468
|
+
}
|
|
469
|
+
function sanitizeDescriptorVersion(value) {
|
|
470
|
+
return /^[A-Za-z0-9][A-Za-z0-9./_-]{0,127}$/u.test(value) ? value : "unknown-descriptor";
|
|
471
|
+
}
|
|
472
|
+
function sanitizeKey(value) {
|
|
473
|
+
return /^(?:[a-z0-9]+(?:-[a-z0-9]+)*|auto-v1-[a-z2-7]{51}[aq])$/u.test(value) ? value : "[redacted]";
|
|
474
|
+
}
|
|
475
|
+
function sanitizeCommit(value) {
|
|
476
|
+
return /^[0-9a-f]{7,64}$/u.test(value) ? value : "[redacted]";
|
|
477
|
+
}
|
|
478
|
+
function sanitizeJsonPointer(value) {
|
|
479
|
+
if (!value.startsWith("/")) return "/[redacted]";
|
|
480
|
+
return value.slice(1).split("/").every(isSafeJsonPointerSegment) ? value : "/[redacted]";
|
|
481
|
+
}
|
|
482
|
+
function isSafeJsonPointerSegment(value) {
|
|
483
|
+
return value.length === 0 || /^(?:[0-9]+|[a-z0-9]+(?:-[a-z0-9]+)*|auto-v1-[a-z2-7]{51}[aq])$/u.test(value) || [
|
|
484
|
+
"algorithm",
|
|
485
|
+
"apiVersion",
|
|
486
|
+
"attribution",
|
|
487
|
+
"creator",
|
|
488
|
+
"integrity",
|
|
489
|
+
"license",
|
|
490
|
+
"mediaType",
|
|
491
|
+
"name",
|
|
492
|
+
"spec",
|
|
493
|
+
"uri",
|
|
494
|
+
"size",
|
|
495
|
+
"sourceUrl",
|
|
496
|
+
"spdxExpression",
|
|
497
|
+
"text",
|
|
498
|
+
"title",
|
|
499
|
+
"type",
|
|
500
|
+
"url",
|
|
501
|
+
"value"
|
|
502
|
+
].includes(value);
|
|
503
|
+
}
|
|
504
|
+
function sanitizePath(value) {
|
|
505
|
+
const components = value.split("/");
|
|
506
|
+
if (value.length === 0 || value.length > 1024 || containsUnsafeDiagnosticUnicode(value) || value.includes("?") || value.includes("#") || value.includes(":") || value.includes("%") || value.includes("\\") || value.startsWith("/") || value.startsWith("~") || /^[a-z][a-z0-9+.-]*:/iu.test(value) || value.startsWith("//") || components.some((component) => component === "." || component === "..") || /[/∕⁄⧸\⧵:꞉]/u.test(value)) return "[redacted]";
|
|
507
|
+
return value;
|
|
508
|
+
}
|
|
509
|
+
function containsUnsafeDiagnosticUnicode(value) {
|
|
510
|
+
for (const character of value) {
|
|
511
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
512
|
+
if (isTopikPathCodePointForbiddenV17(codePoint) || isTopikPathNormalizationSensitiveV17(codePoint)) return true;
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/assets/json.ts
|
|
518
|
+
var TopikJsonSyntaxError = class extends SyntaxError {
|
|
519
|
+
offset;
|
|
520
|
+
duplicatePointer;
|
|
521
|
+
constructor(message, offset, duplicatePointer) {
|
|
522
|
+
super(message);
|
|
523
|
+
this.offset = offset;
|
|
524
|
+
this.duplicatePointer = duplicatePointer;
|
|
525
|
+
this.name = "TopikJsonSyntaxError";
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
function parseStrictTopikJson(source, maxDepth = 8) {
|
|
529
|
+
let offset = 0;
|
|
530
|
+
function whitespace() {
|
|
531
|
+
while (offset < source.length && isJsonWhitespace(source.charCodeAt(offset))) offset++;
|
|
532
|
+
}
|
|
533
|
+
function value(depth, pointer) {
|
|
534
|
+
if (depth > maxDepth) throw new TopikJsonSyntaxError("JSON exceeds maximum depth", offset);
|
|
535
|
+
whitespace();
|
|
536
|
+
const character = source[offset];
|
|
537
|
+
if (character === "{") return object(depth, pointer);
|
|
538
|
+
if (character === "[") return array(depth, pointer);
|
|
539
|
+
if (character === "\"") return string();
|
|
540
|
+
if (source.startsWith("true", offset)) {
|
|
541
|
+
offset += 4;
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
if (source.startsWith("false", offset)) {
|
|
545
|
+
offset += 5;
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
if (source.startsWith("null", offset)) {
|
|
549
|
+
offset += 4;
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
if (character === "-" || character >= "0" && character <= "9") return number();
|
|
553
|
+
throw new TopikJsonSyntaxError("Expected a JSON value", offset);
|
|
554
|
+
}
|
|
555
|
+
function object(depth, pointer) {
|
|
556
|
+
offset++;
|
|
557
|
+
whitespace();
|
|
558
|
+
const result = Object.create(null);
|
|
559
|
+
const keys = /* @__PURE__ */ new Set();
|
|
560
|
+
if (source[offset] === "}") {
|
|
561
|
+
offset++;
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
while (offset < source.length) {
|
|
565
|
+
whitespace();
|
|
566
|
+
if (source[offset] !== "\"") throw new TopikJsonSyntaxError("Expected object member", offset);
|
|
567
|
+
const key = string();
|
|
568
|
+
const memberPointer = `${pointer}/${escapePointer(key)}`;
|
|
569
|
+
if (keys.has(key)) throw new TopikJsonSyntaxError("Duplicate JSON object member", offset, memberPointer);
|
|
570
|
+
keys.add(key);
|
|
571
|
+
whitespace();
|
|
572
|
+
if (source[offset] !== ":") throw new TopikJsonSyntaxError("Expected ':'", offset);
|
|
573
|
+
offset++;
|
|
574
|
+
Object.defineProperty(result, key, {
|
|
575
|
+
configurable: true,
|
|
576
|
+
enumerable: true,
|
|
577
|
+
value: value(depth + 1, memberPointer),
|
|
578
|
+
writable: true
|
|
579
|
+
});
|
|
580
|
+
whitespace();
|
|
581
|
+
if (source[offset] === "}") {
|
|
582
|
+
offset++;
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
if (source[offset] !== ",") throw new TopikJsonSyntaxError("Expected ',' or '}'", offset);
|
|
586
|
+
offset++;
|
|
587
|
+
}
|
|
588
|
+
throw new TopikJsonSyntaxError("Unterminated object", offset);
|
|
589
|
+
}
|
|
590
|
+
function array(depth, pointer) {
|
|
591
|
+
offset++;
|
|
592
|
+
whitespace();
|
|
593
|
+
const result = [];
|
|
594
|
+
if (source[offset] === "]") {
|
|
595
|
+
offset++;
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
while (offset < source.length) {
|
|
599
|
+
result.push(value(depth + 1, `${pointer}/${result.length}`));
|
|
600
|
+
whitespace();
|
|
601
|
+
if (source[offset] === "]") {
|
|
602
|
+
offset++;
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
if (source[offset] !== ",") throw new TopikJsonSyntaxError("Expected ',' or ']'", offset);
|
|
606
|
+
offset++;
|
|
607
|
+
}
|
|
608
|
+
throw new TopikJsonSyntaxError("Unterminated array", offset);
|
|
609
|
+
}
|
|
610
|
+
function string() {
|
|
611
|
+
const start = offset;
|
|
612
|
+
offset++;
|
|
613
|
+
while (offset < source.length) {
|
|
614
|
+
const character = source.charCodeAt(offset);
|
|
615
|
+
if (character === 34) {
|
|
616
|
+
offset++;
|
|
617
|
+
const parsed = JSON.parse(source.slice(start, offset));
|
|
618
|
+
if (hasLoneSurrogate(parsed)) throw new TopikJsonSyntaxError("Lone surrogate is not valid topik-json-v1", start);
|
|
619
|
+
return parsed;
|
|
620
|
+
}
|
|
621
|
+
if (character < 32) throw new TopikJsonSyntaxError("Control in JSON string", offset);
|
|
622
|
+
if (character === 92) {
|
|
623
|
+
offset++;
|
|
624
|
+
const escaped = source[offset];
|
|
625
|
+
if (escaped === "u") {
|
|
626
|
+
if (!/^[0-9a-fA-F]{4}$/u.test(source.slice(offset + 1, offset + 5))) throw new TopikJsonSyntaxError("Invalid Unicode escape", offset);
|
|
627
|
+
offset += 5;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (!"\"\\/bfnrt".includes(escaped ?? "")) throw new TopikJsonSyntaxError("Invalid string escape", offset);
|
|
631
|
+
}
|
|
632
|
+
offset++;
|
|
633
|
+
}
|
|
634
|
+
throw new TopikJsonSyntaxError("Unterminated string", start);
|
|
635
|
+
}
|
|
636
|
+
function number() {
|
|
637
|
+
const start = offset;
|
|
638
|
+
while (offset < source.length && /[-+0-9.eE]/u.test(source[offset])) offset++;
|
|
639
|
+
const spelling = source.slice(start, offset);
|
|
640
|
+
if (!/^(?:0|[1-9][0-9]*)$/u.test(spelling)) throw new TopikJsonSyntaxError("topik-json-v1 permits only ordinary nonnegative integers", start);
|
|
641
|
+
const parsed = Number(spelling);
|
|
642
|
+
if (!Number.isSafeInteger(parsed)) throw new TopikJsonSyntaxError("JSON integer exceeds the safe integer range", start);
|
|
643
|
+
return parsed;
|
|
644
|
+
}
|
|
645
|
+
whitespace();
|
|
646
|
+
const parsed = value(1, "");
|
|
647
|
+
whitespace();
|
|
648
|
+
if (offset !== source.length) throw new TopikJsonSyntaxError("Trailing data after JSON value", offset);
|
|
649
|
+
return parsed;
|
|
650
|
+
}
|
|
651
|
+
/** RFC 8785 scalar/member rules with Topik's two-space layout and final LF. */
|
|
652
|
+
function serializeTopikJson(value) {
|
|
653
|
+
assertSerializable(value);
|
|
654
|
+
return `${pretty(value, 0)}\n`;
|
|
655
|
+
}
|
|
656
|
+
/** True only for values whose complete own-property graph is topik-json-v1 data. */
|
|
657
|
+
function isTopikJsonDataValue(value) {
|
|
658
|
+
try {
|
|
659
|
+
assertSerializable(value);
|
|
660
|
+
return true;
|
|
661
|
+
} catch {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function topikJsonDescriptor() {
|
|
666
|
+
return { id: TOPIK_JSON_VERSION };
|
|
667
|
+
}
|
|
668
|
+
function pretty(value, depth) {
|
|
669
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
|
|
670
|
+
if (typeof value === "number") return String(value);
|
|
671
|
+
if (Array.isArray(value)) {
|
|
672
|
+
if (value.length === 0) return "[]";
|
|
673
|
+
const indent = " ".repeat(depth + 1);
|
|
674
|
+
return `[\n${value.map((entry) => `${indent}${pretty(entry, depth + 1)}`).join(",\n")}\n${" ".repeat(depth)}]`;
|
|
675
|
+
}
|
|
676
|
+
const object = value;
|
|
677
|
+
const keys = Object.keys(object).sort(compareUtf16);
|
|
678
|
+
if (keys.length === 0) return "{}";
|
|
679
|
+
const indent = " ".repeat(depth + 1);
|
|
680
|
+
return `{\n${keys.map((key) => `${indent}${JSON.stringify(key)}: ${pretty(object[key], depth + 1)}`).join(",\n")}\n${" ".repeat(depth)}}`;
|
|
681
|
+
}
|
|
682
|
+
function assertSerializable(value, seen = /* @__PURE__ */ new Set()) {
|
|
683
|
+
if (value === null || typeof value === "boolean") return;
|
|
684
|
+
if (typeof value === "string") {
|
|
685
|
+
if (hasLoneSurrogate(value)) throw new TypeError("topik-json-v1 rejects lone surrogates");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (typeof value === "number") {
|
|
689
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError("topik-json-v1 permits only nonnegative safe integers");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (typeof value !== "object" || value === void 0) throw new TypeError("Value is not serializable by topik-json-v1");
|
|
693
|
+
if (seen.has(value)) throw new TypeError("Cyclic value is not serializable");
|
|
694
|
+
seen.add(value);
|
|
695
|
+
if (Array.isArray(value)) {
|
|
696
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) throw new TypeError("topik-json-v1 rejects nonstandard array prototypes");
|
|
697
|
+
const names = Object.getOwnPropertyNames(value);
|
|
698
|
+
if (Object.getOwnPropertySymbols(value).length !== 0 || names.length !== value.length + 1 || names.some((name) => name !== "length" && !isArrayIndex(name, value.length))) throw new TypeError("topik-json-v1 requires dense arrays without extra properties");
|
|
699
|
+
for (let index = 0; index < value.length; index++) {
|
|
700
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
701
|
+
if (descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable) throw new TypeError("topik-json-v1 requires own array data properties");
|
|
702
|
+
assertSerializable(descriptor.value, seen);
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
const prototype = Object.getPrototypeOf(value);
|
|
706
|
+
if (prototype !== Object.prototype && prototype !== null) throw new TypeError("topik-json-v1 rejects inherited or custom object prototypes");
|
|
707
|
+
if (Object.getOwnPropertySymbols(value).length !== 0) throw new TypeError("topik-json-v1 rejects symbol properties");
|
|
708
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
709
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
710
|
+
if (hasLoneSurrogate(key) || descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable || descriptor.value === void 0) throw new TypeError("topik-json-v1 requires enumerable own data properties with defined values");
|
|
711
|
+
assertSerializable(descriptor.value, seen);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
seen.delete(value);
|
|
715
|
+
}
|
|
716
|
+
function isArrayIndex(value, length) {
|
|
717
|
+
if (!/^(?:0|[1-9][0-9]*)$/u.test(value)) return false;
|
|
718
|
+
const index = Number(value);
|
|
719
|
+
return Number.isSafeInteger(index) && index >= 0 && index < length && String(index) === value;
|
|
720
|
+
}
|
|
721
|
+
function compareUtf16(left, right) {
|
|
722
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
723
|
+
}
|
|
724
|
+
function hasLoneSurrogate(value) {
|
|
725
|
+
for (let index = 0; index < value.length; index++) {
|
|
726
|
+
const unit = value.charCodeAt(index);
|
|
727
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
728
|
+
const next = value.charCodeAt(index + 1);
|
|
729
|
+
if (next < 56320 || next > 57343) return true;
|
|
730
|
+
index++;
|
|
731
|
+
} else if (unit >= 56320 && unit <= 57343) return true;
|
|
732
|
+
}
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
function escapePointer(value) {
|
|
736
|
+
return value.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
737
|
+
}
|
|
738
|
+
function isJsonWhitespace(code) {
|
|
739
|
+
return code === 9 || code === 10 || code === 13 || code === 32;
|
|
740
|
+
}
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region src/assets/nfkc-casefold-v17.ts
|
|
743
|
+
/** Unicode 17.0.0 NFKC_CF data from DerivedNormalizationProps.txt.
|
|
744
|
+
* Source SHA-256: 71fd6a206a2c0cdd41feb6b7f656aa31091db45e9cedc926985d718397f9e488
|
|
745
|
+
*/
|
|
746
|
+
const NFKC_CASEFOLD_V17_DATA = [
|
|
747
|
+
"0041=0061;0042=0062;0043=0063;0044=0064;0045=0065;0046=0066;0047=0067;0048=0068;0049=0069;004a=006a;004b=006b;004c=006c;004d=006d;004e=006e;004f=006f;0050=0070;0051=0071;0052=0072;0053=0073;0054=0074;0055=0075;0056=0076;0057=0077;0058=0078;0059=0079;005a=007a;00a0=0020;00a8=0020.0308;00aa=0061;00ad=;00af=0020.0304;00b2=0032;00b3=0033;00b4=0020.0301;00b5=03bc;00b8=0020.0327;00b9=0031;00ba=006f;00bc=0031.2044.0034;00bd=0031.2044.0032;00be=0033.2044.0034;00c0=00e0;00c1=00e1;00c2=00e2;00c3=00e3;00c4=00e4;00c5=00e5;00c6=00e6;00c7=00e7;00c8=00e8;00c9=00e9;00ca=00ea;00cb=00eb;00cc=00ec;00cd=00ed;00ce=00ee;00cf=00ef;00d0=00f0;00d1=00f1;00d2=00f2;00d3=00f3;00d4=00f4;00d5=00f5;00d6=00f6;00d8=00f8;00d9=00f9;00da=00fa;00db=00fb;00dc=00fc;00dd=00fd;00de=00fe;00df=0073.0073;0100=0101;0102=0103;0104=0105;0106=0107;0108=0109;010a=010b;010c=010d;010e=010f;0110=0111;0112=0113;0114=0115;0116=0117;0118=0119;011a=011b;011c=011d;011e=011f;0120=0121;0122=0123;0124=0125;0126=0127;0128=0129;012a=012b;012c=012d",
|
|
748
|
+
";012e=012f;0130=0069.0307;0132..0133=0069.006a;0134=0135;0136=0137;0139=013a;013b=013c;013d=013e;013f..0140=006c.00b7;0141=0142;0143=0144;0145=0146;0147=0148;0149=02bc.006e;014a=014b;014c=014d;014e=014f;0150=0151;0152=0153;0154=0155;0156=0157;0158=0159;015a=015b;015c=015d;015e=015f;0160=0161;0162=0163;0164=0165;0166=0167;0168=0169;016a=016b;016c=016d;016e=016f;0170=0171;0172=0173;0174=0175;0176=0177;0178=00ff;0179=017a;017b=017c;017d=017e;017f=0073;0181=0253;0182=0183;0184=0185;0186=0254;0187=0188;0189=0256;018a=0257;018b=018c;018e=01dd;018f=0259;0190=025b;0191=0192;0193=0260;0194=0263;0196=0269;0197=0268;0198=0199;019c=026f;019d=0272;019f=0275;01a0=01a1;01a2=01a3;01a4=01a5;01a6=0280;01a7=01a8;01a9=0283;01ac=01ad;01ae=0288;01af=01b0;01b1=028a;01b2=028b;01b3=01b4;01b5=01b6;01b7=0292;01b8=01b9;01bc=01bd;01c4..01c6=0064.017e;01c7..01c9=006c.006a;01ca..01cc=006e.006a;01cd=01ce;01cf=01d0;01d1=01d2;01d3=01d4;01d5=01d6;01d7=01d8;01d9=01da;01db=01dc;01de=01df;01e0=01e1;01e2=01e3;01e4=01e5;01e6",
|
|
749
|
+
"=01e7;01e8=01e9;01ea=01eb;01ec=01ed;01ee=01ef;01f1..01f3=0064.007a;01f4=01f5;01f6=0195;01f7=01bf;01f8=01f9;01fa=01fb;01fc=01fd;01fe=01ff;0200=0201;0202=0203;0204=0205;0206=0207;0208=0209;020a=020b;020c=020d;020e=020f;0210=0211;0212=0213;0214=0215;0216=0217;0218=0219;021a=021b;021c=021d;021e=021f;0220=019e;0222=0223;0224=0225;0226=0227;0228=0229;022a=022b;022c=022d;022e=022f;0230=0231;0232=0233;023a=2c65;023b=023c;023d=019a;023e=2c66;0241=0242;0243=0180;0244=0289;0245=028c;0246=0247;0248=0249;024a=024b;024c=024d;024e=024f;02b0=0068;02b1=0266;02b2=006a;02b3=0072;02b4=0279;02b5=027b;02b6=0281;02b7=0077;02b8=0079;02d8=0020.0306;02d9=0020.0307;02da=0020.030a;02db=0020.0328;02dc=0020.0303;02dd=0020.030b;02e0=0263;02e1=006c;02e2=0073;02e3=0078;02e4=0295;0340=0300;0341=0301;0343=0313;0344=0308.0301;0345=03b9;034f=;0370=0371;0372=0373;0374=02b9;0376=0377;037a=0020.03b9;037e=003b;037f=03f3;0384=0020.0301;0385=0020.0308.0301;0386=03ac;0387=00b7;0388=03ad;0389=03ae;038a=03af;038c=03cc;038e=03cd;03",
|
|
750
|
+
"8f=03ce;0391=03b1;0392=03b2;0393=03b3;0394=03b4;0395=03b5;0396=03b6;0397=03b7;0398=03b8;0399=03b9;039a=03ba;039b=03bb;039c=03bc;039d=03bd;039e=03be;039f=03bf;03a0=03c0;03a1=03c1;03a3=03c3;03a4=03c4;03a5=03c5;03a6=03c6;03a7=03c7;03a8=03c8;03a9=03c9;03aa=03ca;03ab=03cb;03c2=03c3;03cf=03d7;03d0=03b2;03d1=03b8;03d2=03c5;03d3=03cd;03d4=03cb;03d5=03c6;03d6=03c0;03d8=03d9;03da=03db;03dc=03dd;03de=03df;03e0=03e1;03e2=03e3;03e4=03e5;03e6=03e7;03e8=03e9;03ea=03eb;03ec=03ed;03ee=03ef;03f0=03ba;03f1=03c1;03f2=03c3;03f4=03b8;03f5=03b5;03f7=03f8;03f9=03c3;03fa=03fb;03fd=037b;03fe=037c;03ff=037d;0400=0450;0401=0451;0402=0452;0403=0453;0404=0454;0405=0455;0406=0456;0407=0457;0408=0458;0409=0459;040a=045a;040b=045b;040c=045c;040d=045d;040e=045e;040f=045f;0410=0430;0411=0431;0412=0432;0413=0433;0414=0434;0415=0435;0416=0436;0417=0437;0418=0438;0419=0439;041a=043a;041b=043b;041c=043c;041d=043d;041e=043e;041f=043f;0420=0440;0421=0441;0422=0442;0423=0443;0424=0444;0425=0445;0426=0446;0427=0447;0428=0448;04",
|
|
751
|
+
"29=0449;042a=044a;042b=044b;042c=044c;042d=044d;042e=044e;042f=044f;0460=0461;0462=0463;0464=0465;0466=0467;0468=0469;046a=046b;046c=046d;046e=046f;0470=0471;0472=0473;0474=0475;0476=0477;0478=0479;047a=047b;047c=047d;047e=047f;0480=0481;048a=048b;048c=048d;048e=048f;0490=0491;0492=0493;0494=0495;0496=0497;0498=0499;049a=049b;049c=049d;049e=049f;04a0=04a1;04a2=04a3;04a4=04a5;04a6=04a7;04a8=04a9;04aa=04ab;04ac=04ad;04ae=04af;04b0=04b1;04b2=04b3;04b4=04b5;04b6=04b7;04b8=04b9;04ba=04bb;04bc=04bd;04be=04bf;04c0=04cf;04c1=04c2;04c3=04c4;04c5=04c6;04c7=04c8;04c9=04ca;04cb=04cc;04cd=04ce;04d0=04d1;04d2=04d3;04d4=04d5;04d6=04d7;04d8=04d9;04da=04db;04dc=04dd;04de=04df;04e0=04e1;04e2=04e3;04e4=04e5;04e6=04e7;04e8=04e9;04ea=04eb;04ec=04ed;04ee=04ef;04f0=04f1;04f2=04f3;04f4=04f5;04f6=04f7;04f8=04f9;04fa=04fb;04fc=04fd;04fe=04ff;0500=0501;0502=0503;0504=0505;0506=0507;0508=0509;050a=050b;050c=050d;050e=050f;0510=0511;0512=0513;0514=0515;0516=0517;0518=0519;051a=051b;051c=051d;051e=051f;0520=0521;05",
|
|
752
|
+
"22=0523;0524=0525;0526=0527;0528=0529;052a=052b;052c=052d;052e=052f;0531=0561;0532=0562;0533=0563;0534=0564;0535=0565;0536=0566;0537=0567;0538=0568;0539=0569;053a=056a;053b=056b;053c=056c;053d=056d;053e=056e;053f=056f;0540=0570;0541=0571;0542=0572;0543=0573;0544=0574;0545=0575;0546=0576;0547=0577;0548=0578;0549=0579;054a=057a;054b=057b;054c=057c;054d=057d;054e=057e;054f=057f;0550=0580;0551=0581;0552=0582;0553=0583;0554=0584;0555=0585;0556=0586;0587=0565.0582;061c=;0675=0627.0674;0676=0648.0674;0677=06c7.0674;0678=064a.0674;0958=0915.093c;0959=0916.093c;095a=0917.093c;095b=091c.093c;095c=0921.093c;095d=0922.093c;095e=092b.093c;095f=092f.093c;09dc=09a1.09bc;09dd=09a2.09bc;09df=09af.09bc;0a33=0a32.0a3c;0a36=0a38.0a3c;0a59=0a16.0a3c;0a5a=0a17.0a3c;0a5b=0a1c.0a3c;0a5e=0a2b.0a3c;0b5c=0b21.0b3c;0b5d=0b22.0b3c;0e33=0e4d.0e32;0eb3=0ecd.0eb2;0edc=0eab.0e99;0edd=0eab.0ea1;0f0c=0f0b;0f43=0f42.0fb7;0f4d=0f4c.0fb7;0f52=0f51.0fb7;0f57=0f56.0fb7;0f5c=0f5b.0fb7;0f69=0f40.0fb5;0f73=0f71.0f72;0f75=0f71.0",
|
|
753
|
+
"f74;0f76=0fb2.0f80;0f77=0fb2.0f71.0f80;0f78=0fb3.0f80;0f79=0fb3.0f71.0f80;0f81=0f71.0f80;0f93=0f92.0fb7;0f9d=0f9c.0fb7;0fa2=0fa1.0fb7;0fa7=0fa6.0fb7;0fac=0fab.0fb7;0fb9=0f90.0fb5;10a0=2d00;10a1=2d01;10a2=2d02;10a3=2d03;10a4=2d04;10a5=2d05;10a6=2d06;10a7=2d07;10a8=2d08;10a9=2d09;10aa=2d0a;10ab=2d0b;10ac=2d0c;10ad=2d0d;10ae=2d0e;10af=2d0f;10b0=2d10;10b1=2d11;10b2=2d12;10b3=2d13;10b4=2d14;10b5=2d15;10b6=2d16;10b7=2d17;10b8=2d18;10b9=2d19;10ba=2d1a;10bb=2d1b;10bc=2d1c;10bd=2d1d;10be=2d1e;10bf=2d1f;10c0=2d20;10c1=2d21;10c2=2d22;10c3=2d23;10c4=2d24;10c5=2d25;10c7=2d27;10cd=2d2d;10fc=10dc;115f..1160=;13f8=13f0;13f9=13f1;13fa=13f2;13fb=13f3;13fc=13f4;13fd=13f5;17b4..17b5=;180b..180d=;180e=;180f=;1c80=0432;1c81=0434;1c82=043e;1c83=0441;1c84..1c85=0442;1c86=044a;1c87=0463;1c88=a64b;1c89=1c8a;1c90=10d0;1c91=10d1;1c92=10d2;1c93=10d3;1c94=10d4;1c95=10d5;1c96=10d6;1c97=10d7;1c98=10d8;1c99=10d9;1c9a=10da;1c9b=10db;1c9c=10dc;1c9d=10dd;1c9e=10de;1c9f=10df;1ca0=10e0;1ca1=10e1;1ca2=10e2;1ca3=10e3;1ca4=10",
|
|
754
|
+
"e4;1ca5=10e5;1ca6=10e6;1ca7=10e7;1ca8=10e8;1ca9=10e9;1caa=10ea;1cab=10eb;1cac=10ec;1cad=10ed;1cae=10ee;1caf=10ef;1cb0=10f0;1cb1=10f1;1cb2=10f2;1cb3=10f3;1cb4=10f4;1cb5=10f5;1cb6=10f6;1cb7=10f7;1cb8=10f8;1cb9=10f9;1cba=10fa;1cbd=10fd;1cbe=10fe;1cbf=10ff;1d2c=0061;1d2d=00e6;1d2e=0062;1d30=0064;1d31=0065;1d32=01dd;1d33=0067;1d34=0068;1d35=0069;1d36=006a;1d37=006b;1d38=006c;1d39=006d;1d3a=006e;1d3c=006f;1d3d=0223;1d3e=0070;1d3f=0072;1d40=0074;1d41=0075;1d42=0077;1d43=0061;1d44=0250;1d45=0251;1d46=1d02;1d47=0062;1d48=0064;1d49=0065;1d4a=0259;1d4b=025b;1d4c=025c;1d4d=0067;1d4f=006b;1d50=006d;1d51=014b;1d52=006f;1d53=0254;1d54=1d16;1d55=1d17;1d56=0070;1d57=0074;1d58=0075;1d59=1d1d;1d5a=026f;1d5b=0076;1d5c=1d25;1d5d=03b2;1d5e=03b3;1d5f=03b4;1d60=03c6;1d61=03c7;1d62=0069;1d63=0072;1d64=0075;1d65=0076;1d66=03b2;1d67=03b3;1d68=03c1;1d69=03c6;1d6a=03c7;1d78=043d;1d9b=0252;1d9c=0063;1d9d=0255;1d9e=00f0;1d9f=025c;1da0=0066;1da1=025f;1da2=0261;1da3=0265;1da4=0268;1da5=0269;1da6=026a;1da7=1d7b;1da8=02",
|
|
755
|
+
"9d;1da9=026d;1daa=1d85;1dab=029f;1dac=0271;1dad=0270;1dae=0272;1daf=0273;1db0=0274;1db1=0275;1db2=0278;1db3=0282;1db4=0283;1db5=01ab;1db6=0289;1db7=028a;1db8=1d1c;1db9=028b;1dba=028c;1dbb=007a;1dbc=0290;1dbd=0291;1dbe=0292;1dbf=03b8;1e00=1e01;1e02=1e03;1e04=1e05;1e06=1e07;1e08=1e09;1e0a=1e0b;1e0c=1e0d;1e0e=1e0f;1e10=1e11;1e12=1e13;1e14=1e15;1e16=1e17;1e18=1e19;1e1a=1e1b;1e1c=1e1d;1e1e=1e1f;1e20=1e21;1e22=1e23;1e24=1e25;1e26=1e27;1e28=1e29;1e2a=1e2b;1e2c=1e2d;1e2e=1e2f;1e30=1e31;1e32=1e33;1e34=1e35;1e36=1e37;1e38=1e39;1e3a=1e3b;1e3c=1e3d;1e3e=1e3f;1e40=1e41;1e42=1e43;1e44=1e45;1e46=1e47;1e48=1e49;1e4a=1e4b;1e4c=1e4d;1e4e=1e4f;1e50=1e51;1e52=1e53;1e54=1e55;1e56=1e57;1e58=1e59;1e5a=1e5b;1e5c=1e5d;1e5e=1e5f;1e60=1e61;1e62=1e63;1e64=1e65;1e66=1e67;1e68=1e69;1e6a=1e6b;1e6c=1e6d;1e6e=1e6f;1e70=1e71;1e72=1e73;1e74=1e75;1e76=1e77;1e78=1e79;1e7a=1e7b;1e7c=1e7d;1e7e=1e7f;1e80=1e81;1e82=1e83;1e84=1e85;1e86=1e87;1e88=1e89;1e8a=1e8b;1e8c=1e8d;1e8e=1e8f;1e90=1e91;1e92=1e93;1e94=1e95;1e9a=0061.02be;1e",
|
|
756
|
+
"9b=1e61;1e9e=0073.0073;1ea0=1ea1;1ea2=1ea3;1ea4=1ea5;1ea6=1ea7;1ea8=1ea9;1eaa=1eab;1eac=1ead;1eae=1eaf;1eb0=1eb1;1eb2=1eb3;1eb4=1eb5;1eb6=1eb7;1eb8=1eb9;1eba=1ebb;1ebc=1ebd;1ebe=1ebf;1ec0=1ec1;1ec2=1ec3;1ec4=1ec5;1ec6=1ec7;1ec8=1ec9;1eca=1ecb;1ecc=1ecd;1ece=1ecf;1ed0=1ed1;1ed2=1ed3;1ed4=1ed5;1ed6=1ed7;1ed8=1ed9;1eda=1edb;1edc=1edd;1ede=1edf;1ee0=1ee1;1ee2=1ee3;1ee4=1ee5;1ee6=1ee7;1ee8=1ee9;1eea=1eeb;1eec=1eed;1eee=1eef;1ef0=1ef1;1ef2=1ef3;1ef4=1ef5;1ef6=1ef7;1ef8=1ef9;1efa=1efb;1efc=1efd;1efe=1eff;1f08=1f00;1f09=1f01;1f0a=1f02;1f0b=1f03;1f0c=1f04;1f0d=1f05;1f0e=1f06;1f0f=1f07;1f18=1f10;1f19=1f11;1f1a=1f12;1f1b=1f13;1f1c=1f14;1f1d=1f15;1f28=1f20;1f29=1f21;1f2a=1f22;1f2b=1f23;1f2c=1f24;1f2d=1f25;1f2e=1f26;1f2f=1f27;1f38=1f30;1f39=1f31;1f3a=1f32;1f3b=1f33;1f3c=1f34;1f3d=1f35;1f3e=1f36;1f3f=1f37;1f48=1f40;1f49=1f41;1f4a=1f42;1f4b=1f43;1f4c=1f44;1f4d=1f45;1f59=1f51;1f5b=1f53;1f5d=1f55;1f5f=1f57;1f68=1f60;1f69=1f61;1f6a=1f62;1f6b=1f63;1f6c=1f64;1f6d=1f65;1f6e=1f66;1f6f=1f67;1f71=03ac;1f73=03",
|
|
757
|
+
"ad;1f75=03ae;1f77=03af;1f79=03cc;1f7b=03cd;1f7d=03ce;1f80=1f00.03b9;1f81=1f01.03b9;1f82=1f02.03b9;1f83=1f03.03b9;1f84=1f04.03b9;1f85=1f05.03b9;1f86=1f06.03b9;1f87=1f07.03b9;1f88=1f00.03b9;1f89=1f01.03b9;1f8a=1f02.03b9;1f8b=1f03.03b9;1f8c=1f04.03b9;1f8d=1f05.03b9;1f8e=1f06.03b9;1f8f=1f07.03b9;1f90=1f20.03b9;1f91=1f21.03b9;1f92=1f22.03b9;1f93=1f23.03b9;1f94=1f24.03b9;1f95=1f25.03b9;1f96=1f26.03b9;1f97=1f27.03b9;1f98=1f20.03b9;1f99=1f21.03b9;1f9a=1f22.03b9;1f9b=1f23.03b9;1f9c=1f24.03b9;1f9d=1f25.03b9;1f9e=1f26.03b9;1f9f=1f27.03b9;1fa0=1f60.03b9;1fa1=1f61.03b9;1fa2=1f62.03b9;1fa3=1f63.03b9;1fa4=1f64.03b9;1fa5=1f65.03b9;1fa6=1f66.03b9;1fa7=1f67.03b9;1fa8=1f60.03b9;1fa9=1f61.03b9;1faa=1f62.03b9;1fab=1f63.03b9;1fac=1f64.03b9;1fad=1f65.03b9;1fae=1f66.03b9;1faf=1f67.03b9;1fb2=1f70.03b9;1fb3=03b1.03b9;1fb4=03ac.03b9;1fb7=1fb6.03b9;1fb8=1fb0;1fb9=1fb1;1fba=1f70;1fbb=03ac;1fbc=03b1.03b9;1fbd=0020.0313;1fbe=03b9;1fbf=0020.0313;1fc0=0020.0342;1fc1=0020.0308.0342;1fc2=1f74.03b9;1fc3=03b7.03b9;1fc4=03",
|
|
758
|
+
"ae.03b9;1fc7=1fc6.03b9;1fc8=1f72;1fc9=03ad;1fca=1f74;1fcb=03ae;1fcc=03b7.03b9;1fcd=0020.0313.0300;1fce=0020.0313.0301;1fcf=0020.0313.0342;1fd3=0390;1fd8=1fd0;1fd9=1fd1;1fda=1f76;1fdb=03af;1fdd=0020.0314.0300;1fde=0020.0314.0301;1fdf=0020.0314.0342;1fe3=03b0;1fe8=1fe0;1fe9=1fe1;1fea=1f7a;1feb=03cd;1fec=1fe5;1fed=0020.0308.0300;1fee=0020.0308.0301;1fef=0060;1ff2=1f7c.03b9;1ff3=03c9.03b9;1ff4=03ce.03b9;1ff7=1ff6.03b9;1ff8=1f78;1ff9=03cc;1ffa=1f7c;1ffb=03ce;1ffc=03c9.03b9;1ffd=0020.0301;1ffe=0020.0314;2000..200a=0020;200b..200f=;2011=2010;2017=0020.0333;2024=002e;2025=002e.002e;2026=002e.002e.002e;202a..202e=;202f=0020;2033=2032.2032;2034=2032.2032.2032;2036=2035.2035;2037=2035.2035.2035;203c=0021.0021;203e=0020.0305;2047=003f.003f;2048=003f.0021;2049=0021.003f;2057=2032.2032.2032.2032;205f=0020;2060..2064=;2065=;2066..206f=;2070=0030;2071=0069;2074=0034;2075=0035;2076=0036;2077=0037;2078=0038;2079=0039;207a=002b;207b=2212;207c=003d;207d=0028;207e=0029;207f=006e;2080=0030;2081=0031;2082=00",
|
|
759
|
+
"32;2083=0033;2084=0034;2085=0035;2086=0036;2087=0037;2088=0038;2089=0039;208a=002b;208b=2212;208c=003d;208d=0028;208e=0029;2090=0061;2091=0065;2092=006f;2093=0078;2094=0259;2095=0068;2096=006b;2097=006c;2098=006d;2099=006e;209a=0070;209b=0073;209c=0074;20a8=0072.0073;2100=0061.002f.0063;2101=0061.002f.0073;2102=0063;2103=00b0.0063;2105=0063.002f.006f;2106=0063.002f.0075;2107=025b;2109=00b0.0066;210a=0067;210b..210e=0068;210f=0127;2110..2111=0069;2112..2113=006c;2115=006e;2116=006e.006f;2119=0070;211a=0071;211b..211d=0072;2120=0073.006d;2121=0074.0065.006c;2122=0074.006d;2124=007a;2126=03c9;2128=007a;212a=006b;212b=00e5;212c=0062;212d=0063;212f..2130=0065;2131=0066;2132=214e;2133=006d;2134=006f;2135=05d0;2136=05d1;2137=05d2;2138=05d3;2139=0069;213b=0066.0061.0078;213c=03c0;213d..213e=03b3;213f=03c0;2140=2211;2145..2146=0064;2147=0065;2148=0069;2149=006a;2150=0031.2044.0037;2151=0031.2044.0039;2152=0031.2044.0031.0030;2153=0031.2044.0033;2154=0032.2044.0033;2155=0031.2044.0035;2156=0032.",
|
|
760
|
+
"2044.0035;2157=0033.2044.0035;2158=0034.2044.0035;2159=0031.2044.0036;215a=0035.2044.0036;215b=0031.2044.0038;215c=0033.2044.0038;215d=0035.2044.0038;215e=0037.2044.0038;215f=0031.2044;2160=0069;2161=0069.0069;2162=0069.0069.0069;2163=0069.0076;2164=0076;2165=0076.0069;2166=0076.0069.0069;2167=0076.0069.0069.0069;2168=0069.0078;2169=0078;216a=0078.0069;216b=0078.0069.0069;216c=006c;216d=0063;216e=0064;216f=006d;2170=0069;2171=0069.0069;2172=0069.0069.0069;2173=0069.0076;2174=0076;2175=0076.0069;2176=0076.0069.0069;2177=0076.0069.0069.0069;2178=0069.0078;2179=0078;217a=0078.0069;217b=0078.0069.0069;217c=006c;217d=0063;217e=0064;217f=006d;2183=2184;2189=0030.2044.0033;222c=222b.222b;222d=222b.222b.222b;222f=222e.222e;2230=222e.222e.222e;2329=3008;232a=3009;2460=0031;2461=0032;2462=0033;2463=0034;2464=0035;2465=0036;2466=0037;2467=0038;2468=0039;2469=0031.0030;246a=0031.0031;246b=0031.0032;246c=0031.0033;246d=0031.0034;246e=0031.0035;246f=0031.0036;2470=0031.0037;2471=0031.0038;2472=0031.",
|
|
761
|
+
"0039;2473=0032.0030;2474=0028.0031.0029;2475=0028.0032.0029;2476=0028.0033.0029;2477=0028.0034.0029;2478=0028.0035.0029;2479=0028.0036.0029;247a=0028.0037.0029;247b=0028.0038.0029;247c=0028.0039.0029;247d=0028.0031.0030.0029;247e=0028.0031.0031.0029;247f=0028.0031.0032.0029;2480=0028.0031.0033.0029;2481=0028.0031.0034.0029;2482=0028.0031.0035.0029;2483=0028.0031.0036.0029;2484=0028.0031.0037.0029;2485=0028.0031.0038.0029;2486=0028.0031.0039.0029;2487=0028.0032.0030.0029;2488=0031.002e;2489=0032.002e;248a=0033.002e;248b=0034.002e;248c=0035.002e;248d=0036.002e;248e=0037.002e;248f=0038.002e;2490=0039.002e;2491=0031.0030.002e;2492=0031.0031.002e;2493=0031.0032.002e;2494=0031.0033.002e;2495=0031.0034.002e;2496=0031.0035.002e;2497=0031.0036.002e;2498=0031.0037.002e;2499=0031.0038.002e;249a=0031.0039.002e;249b=0032.0030.002e;249c=0028.0061.0029;249d=0028.0062.0029;249e=0028.0063.0029;249f=0028.0064.0029;24a0=0028.0065.0029;24a1=0028.0066.0029;24a2=0028.0067.0029;24a3=0028.0068.0029;24a4=0028.",
|
|
762
|
+
"0069.0029;24a5=0028.006a.0029;24a6=0028.006b.0029;24a7=0028.006c.0029;24a8=0028.006d.0029;24a9=0028.006e.0029;24aa=0028.006f.0029;24ab=0028.0070.0029;24ac=0028.0071.0029;24ad=0028.0072.0029;24ae=0028.0073.0029;24af=0028.0074.0029;24b0=0028.0075.0029;24b1=0028.0076.0029;24b2=0028.0077.0029;24b3=0028.0078.0029;24b4=0028.0079.0029;24b5=0028.007a.0029;24b6=0061;24b7=0062;24b8=0063;24b9=0064;24ba=0065;24bb=0066;24bc=0067;24bd=0068;24be=0069;24bf=006a;24c0=006b;24c1=006c;24c2=006d;24c3=006e;24c4=006f;24c5=0070;24c6=0071;24c7=0072;24c8=0073;24c9=0074;24ca=0075;24cb=0076;24cc=0077;24cd=0078;24ce=0079;24cf=007a;24d0=0061;24d1=0062;24d2=0063;24d3=0064;24d4=0065;24d5=0066;24d6=0067;24d7=0068;24d8=0069;24d9=006a;24da=006b;24db=006c;24dc=006d;24dd=006e;24de=006f;24df=0070;24e0=0071;24e1=0072;24e2=0073;24e3=0074;24e4=0075;24e5=0076;24e6=0077;24e7=0078;24e8=0079;24e9=007a;24ea=0030;2a0c=222b.222b.222b.222b;2a74=003a.003a.003d;2a75=003d.003d;2a76=003d.003d.003d;2adc=2add.0338;2c00=2c30;2c01=2c31;2c02=",
|
|
763
|
+
"2c32;2c03=2c33;2c04=2c34;2c05=2c35;2c06=2c36;2c07=2c37;2c08=2c38;2c09=2c39;2c0a=2c3a;2c0b=2c3b;2c0c=2c3c;2c0d=2c3d;2c0e=2c3e;2c0f=2c3f;2c10=2c40;2c11=2c41;2c12=2c42;2c13=2c43;2c14=2c44;2c15=2c45;2c16=2c46;2c17=2c47;2c18=2c48;2c19=2c49;2c1a=2c4a;2c1b=2c4b;2c1c=2c4c;2c1d=2c4d;2c1e=2c4e;2c1f=2c4f;2c20=2c50;2c21=2c51;2c22=2c52;2c23=2c53;2c24=2c54;2c25=2c55;2c26=2c56;2c27=2c57;2c28=2c58;2c29=2c59;2c2a=2c5a;2c2b=2c5b;2c2c=2c5c;2c2d=2c5d;2c2e=2c5e;2c2f=2c5f;2c60=2c61;2c62=026b;2c63=1d7d;2c64=027d;2c67=2c68;2c69=2c6a;2c6b=2c6c;2c6d=0251;2c6e=0271;2c6f=0250;2c70=0252;2c72=2c73;2c75=2c76;2c7c=006a;2c7d=0076;2c7e=023f;2c7f=0240;2c80=2c81;2c82=2c83;2c84=2c85;2c86=2c87;2c88=2c89;2c8a=2c8b;2c8c=2c8d;2c8e=2c8f;2c90=2c91;2c92=2c93;2c94=2c95;2c96=2c97;2c98=2c99;2c9a=2c9b;2c9c=2c9d;2c9e=2c9f;2ca0=2ca1;2ca2=2ca3;2ca4=2ca5;2ca6=2ca7;2ca8=2ca9;2caa=2cab;2cac=2cad;2cae=2caf;2cb0=2cb1;2cb2=2cb3;2cb4=2cb5;2cb6=2cb7;2cb8=2cb9;2cba=2cbb;2cbc=2cbd;2cbe=2cbf;2cc0=2cc1;2cc2=2cc3;2cc4=2cc5;2cc6=2cc7;2cc8=2cc9;2cca=",
|
|
764
|
+
"2ccb;2ccc=2ccd;2cce=2ccf;2cd0=2cd1;2cd2=2cd3;2cd4=2cd5;2cd6=2cd7;2cd8=2cd9;2cda=2cdb;2cdc=2cdd;2cde=2cdf;2ce0=2ce1;2ce2=2ce3;2ceb=2cec;2ced=2cee;2cf2=2cf3;2d6f=2d61;2e9f=6bcd;2ef3=9f9f;2f00=4e00;2f01=4e28;2f02=4e36;2f03=4e3f;2f04=4e59;2f05=4e85;2f06=4e8c;2f07=4ea0;2f08=4eba;2f09=513f;2f0a=5165;2f0b=516b;2f0c=5182;2f0d=5196;2f0e=51ab;2f0f=51e0;2f10=51f5;2f11=5200;2f12=529b;2f13=52f9;2f14=5315;2f15=531a;2f16=5338;2f17=5341;2f18=535c;2f19=5369;2f1a=5382;2f1b=53b6;2f1c=53c8;2f1d=53e3;2f1e=56d7;2f1f=571f;2f20=58eb;2f21=5902;2f22=590a;2f23=5915;2f24=5927;2f25=5973;2f26=5b50;2f27=5b80;2f28=5bf8;2f29=5c0f;2f2a=5c22;2f2b=5c38;2f2c=5c6e;2f2d=5c71;2f2e=5ddb;2f2f=5de5;2f30=5df1;2f31=5dfe;2f32=5e72;2f33=5e7a;2f34=5e7f;2f35=5ef4;2f36=5efe;2f37=5f0b;2f38=5f13;2f39=5f50;2f3a=5f61;2f3b=5f73;2f3c=5fc3;2f3d=6208;2f3e=6236;2f3f=624b;2f40=652f;2f41=6534;2f42=6587;2f43=6597;2f44=65a4;2f45=65b9;2f46=65e0;2f47=65e5;2f48=66f0;2f49=6708;2f4a=6728;2f4b=6b20;2f4c=6b62;2f4d=6b79;2f4e=6bb3;2f4f=6bcb;2f50=6bd4;2f51=",
|
|
765
|
+
"6bdb;2f52=6c0f;2f53=6c14;2f54=6c34;2f55=706b;2f56=722a;2f57=7236;2f58=723b;2f59=723f;2f5a=7247;2f5b=7259;2f5c=725b;2f5d=72ac;2f5e=7384;2f5f=7389;2f60=74dc;2f61=74e6;2f62=7518;2f63=751f;2f64=7528;2f65=7530;2f66=758b;2f67=7592;2f68=7676;2f69=767d;2f6a=76ae;2f6b=76bf;2f6c=76ee;2f6d=77db;2f6e=77e2;2f6f=77f3;2f70=793a;2f71=79b8;2f72=79be;2f73=7a74;2f74=7acb;2f75=7af9;2f76=7c73;2f77=7cf8;2f78=7f36;2f79=7f51;2f7a=7f8a;2f7b=7fbd;2f7c=8001;2f7d=800c;2f7e=8012;2f7f=8033;2f80=807f;2f81=8089;2f82=81e3;2f83=81ea;2f84=81f3;2f85=81fc;2f86=820c;2f87=821b;2f88=821f;2f89=826e;2f8a=8272;2f8b=8278;2f8c=864d;2f8d=866b;2f8e=8840;2f8f=884c;2f90=8863;2f91=897e;2f92=898b;2f93=89d2;2f94=8a00;2f95=8c37;2f96=8c46;2f97=8c55;2f98=8c78;2f99=8c9d;2f9a=8d64;2f9b=8d70;2f9c=8db3;2f9d=8eab;2f9e=8eca;2f9f=8f9b;2fa0=8fb0;2fa1=8fb5;2fa2=9091;2fa3=9149;2fa4=91c6;2fa5=91cc;2fa6=91d1;2fa7=9577;2fa8=9580;2fa9=961c;2faa=96b6;2fab=96b9;2fac=96e8;2fad=9751;2fae=975e;2faf=9762;2fb0=9769;2fb1=97cb;2fb2=97ed;2fb3=97f3;2fb4=9801;2fb5=",
|
|
766
|
+
"98a8;2fb6=98db;2fb7=98df;2fb8=9996;2fb9=9999;2fba=99ac;2fbb=9aa8;2fbc=9ad8;2fbd=9adf;2fbe=9b25;2fbf=9b2f;2fc0=9b32;2fc1=9b3c;2fc2=9b5a;2fc3=9ce5;2fc4=9e75;2fc5=9e7f;2fc6=9ea5;2fc7=9ebb;2fc8=9ec3;2fc9=9ecd;2fca=9ed1;2fcb=9ef9;2fcc=9efd;2fcd=9f0e;2fce=9f13;2fcf=9f20;2fd0=9f3b;2fd1=9f4a;2fd2=9f52;2fd3=9f8d;2fd4=9f9c;2fd5=9fa0;3000=0020;3036=3012;3038=5341;3039=5344;303a=5345;309b=0020.3099;309c=0020.309a;309f=3088.308a;30ff=30b3.30c8;3131=1100;3132=1101;3133=11aa;3134=1102;3135=11ac;3136=11ad;3137=1103;3138=1104;3139=1105;313a=11b0;313b=11b1;313c=11b2;313d=11b3;313e=11b4;313f=11b5;3140=111a;3141=1106;3142=1107;3143=1108;3144=1121;3145=1109;3146=110a;3147=110b;3148=110c;3149=110d;314a=110e;314b=110f;314c=1110;314d=1111;314e=1112;314f=1161;3150=1162;3151=1163;3152=1164;3153=1165;3154=1166;3155=1167;3156=1168;3157=1169;3158=116a;3159=116b;315a=116c;315b=116d;315c=116e;315d=116f;315e=1170;315f=1171;3160=1172;3161=1173;3162=1174;3163=1175;3164=;3165=1114;3166=1115;3167=11c7;3168=11c8;3169=11cc",
|
|
767
|
+
";316a=11ce;316b=11d3;316c=11d7;316d=11d9;316e=111c;316f=11dd;3170=11df;3171=111d;3172=111e;3173=1120;3174=1122;3175=1123;3176=1127;3177=1129;3178=112b;3179=112c;317a=112d;317b=112e;317c=112f;317d=1132;317e=1136;317f=1140;3180=1147;3181=114c;3182=11f1;3183=11f2;3184=1157;3185=1158;3186=1159;3187=1184;3188=1185;3189=1188;318a=1191;318b=1192;318c=1194;318d=119e;318e=11a1;3192=4e00;3193=4e8c;3194=4e09;3195=56db;3196=4e0a;3197=4e2d;3198=4e0b;3199=7532;319a=4e59;319b=4e19;319c=4e01;319d=5929;319e=5730;319f=4eba;3200=0028.1100.0029;3201=0028.1102.0029;3202=0028.1103.0029;3203=0028.1105.0029;3204=0028.1106.0029;3205=0028.1107.0029;3206=0028.1109.0029;3207=0028.110b.0029;3208=0028.110c.0029;3209=0028.110e.0029;320a=0028.110f.0029;320b=0028.1110.0029;320c=0028.1111.0029;320d=0028.1112.0029;320e=0028.ac00.0029;320f=0028.b098.0029;3210=0028.b2e4.0029;3211=0028.b77c.0029;3212=0028.b9c8.0029;3213=0028.bc14.0029;3214=0028.c0ac.0029;3215=0028.c544.0029;3216=0028.c790.0029;3217=0028.cc28.0029;3218=0028",
|
|
768
|
+
".ce74.0029;3219=0028.d0c0.0029;321a=0028.d30c.0029;321b=0028.d558.0029;321c=0028.c8fc.0029;321d=0028.c624.c804.0029;321e=0028.c624.d6c4.0029;3220=0028.4e00.0029;3221=0028.4e8c.0029;3222=0028.4e09.0029;3223=0028.56db.0029;3224=0028.4e94.0029;3225=0028.516d.0029;3226=0028.4e03.0029;3227=0028.516b.0029;3228=0028.4e5d.0029;3229=0028.5341.0029;322a=0028.6708.0029;322b=0028.706b.0029;322c=0028.6c34.0029;322d=0028.6728.0029;322e=0028.91d1.0029;322f=0028.571f.0029;3230=0028.65e5.0029;3231=0028.682a.0029;3232=0028.6709.0029;3233=0028.793e.0029;3234=0028.540d.0029;3235=0028.7279.0029;3236=0028.8ca1.0029;3237=0028.795d.0029;3238=0028.52b4.0029;3239=0028.4ee3.0029;323a=0028.547c.0029;323b=0028.5b66.0029;323c=0028.76e3.0029;323d=0028.4f01.0029;323e=0028.8cc7.0029;323f=0028.5354.0029;3240=0028.796d.0029;3241=0028.4f11.0029;3242=0028.81ea.0029;3243=0028.81f3.0029;3244=554f;3245=5e7c;3246=6587;3247=7b8f;3250=0070.0074.0065;3251=0032.0031;3252=0032.0032;3253=0032.0033;3254=0032.0034;3255=0032.0035;3256",
|
|
769
|
+
"=0032.0036;3257=0032.0037;3258=0032.0038;3259=0032.0039;325a=0033.0030;325b=0033.0031;325c=0033.0032;325d=0033.0033;325e=0033.0034;325f=0033.0035;3260=1100;3261=1102;3262=1103;3263=1105;3264=1106;3265=1107;3266=1109;3267=110b;3268=110c;3269=110e;326a=110f;326b=1110;326c=1111;326d=1112;326e=ac00;326f=b098;3270=b2e4;3271=b77c;3272=b9c8;3273=bc14;3274=c0ac;3275=c544;3276=c790;3277=cc28;3278=ce74;3279=d0c0;327a=d30c;327b=d558;327c=cc38.ace0;327d=c8fc.c758;327e=c6b0;3280=4e00;3281=4e8c;3282=4e09;3283=56db;3284=4e94;3285=516d;3286=4e03;3287=516b;3288=4e5d;3289=5341;328a=6708;328b=706b;328c=6c34;328d=6728;328e=91d1;328f=571f;3290=65e5;3291=682a;3292=6709;3293=793e;3294=540d;3295=7279;3296=8ca1;3297=795d;3298=52b4;3299=79d8;329a=7537;329b=5973;329c=9069;329d=512a;329e=5370;329f=6ce8;32a0=9805;32a1=4f11;32a2=5199;32a3=6b63;32a4=4e0a;32a5=4e2d;32a6=4e0b;32a7=5de6;32a8=53f3;32a9=533b;32aa=5b97;32ab=5b66;32ac=76e3;32ad=4f01;32ae=8cc7;32af=5354;32b0=591c;32b1=0033.0036;32b2=0033.0037;32b3=0033.0038",
|
|
770
|
+
";32b4=0033.0039;32b5=0034.0030;32b6=0034.0031;32b7=0034.0032;32b8=0034.0033;32b9=0034.0034;32ba=0034.0035;32bb=0034.0036;32bc=0034.0037;32bd=0034.0038;32be=0034.0039;32bf=0035.0030;32c0=0031.6708;32c1=0032.6708;32c2=0033.6708;32c3=0034.6708;32c4=0035.6708;32c5=0036.6708;32c6=0037.6708;32c7=0038.6708;32c8=0039.6708;32c9=0031.0030.6708;32ca=0031.0031.6708;32cb=0031.0032.6708;32cc=0068.0067;32cd=0065.0072.0067;32ce=0065.0076;32cf=006c.0074.0064;32d0=30a2;32d1=30a4;32d2=30a6;32d3=30a8;32d4=30aa;32d5=30ab;32d6=30ad;32d7=30af;32d8=30b1;32d9=30b3;32da=30b5;32db=30b7;32dc=30b9;32dd=30bb;32de=30bd;32df=30bf;32e0=30c1;32e1=30c4;32e2=30c6;32e3=30c8;32e4=30ca;32e5=30cb;32e6=30cc;32e7=30cd;32e8=30ce;32e9=30cf;32ea=30d2;32eb=30d5;32ec=30d8;32ed=30db;32ee=30de;32ef=30df;32f0=30e0;32f1=30e1;32f2=30e2;32f3=30e4;32f4=30e6;32f5=30e8;32f6=30e9;32f7=30ea;32f8=30eb;32f9=30ec;32fa=30ed;32fb=30ef;32fc=30f0;32fd=30f1;32fe=30f2;32ff=4ee4.548c;3300=30a2.30d1.30fc.30c8;3301=30a2.30eb.30d5.30a1;3302=30a2.30f3.30da",
|
|
771
|
+
".30a2;3303=30a2.30fc.30eb;3304=30a4.30cb.30f3.30b0;3305=30a4.30f3.30c1;3306=30a6.30a9.30f3;3307=30a8.30b9.30af.30fc.30c9;3308=30a8.30fc.30ab.30fc;3309=30aa.30f3.30b9;330a=30aa.30fc.30e0;330b=30ab.30a4.30ea;330c=30ab.30e9.30c3.30c8;330d=30ab.30ed.30ea.30fc;330e=30ac.30ed.30f3;330f=30ac.30f3.30de;3310=30ae.30ac;3311=30ae.30cb.30fc;3312=30ad.30e5.30ea.30fc;3313=30ae.30eb.30c0.30fc;3314=30ad.30ed;3315=30ad.30ed.30b0.30e9.30e0;3316=30ad.30ed.30e1.30fc.30c8.30eb;3317=30ad.30ed.30ef.30c3.30c8;3318=30b0.30e9.30e0;3319=30b0.30e9.30e0.30c8.30f3;331a=30af.30eb.30bc.30a4.30ed;331b=30af.30ed.30fc.30cd;331c=30b1.30fc.30b9;331d=30b3.30eb.30ca;331e=30b3.30fc.30dd;331f=30b5.30a4.30af.30eb;3320=30b5.30f3.30c1.30fc.30e0;3321=30b7.30ea.30f3.30b0;3322=30bb.30f3.30c1;3323=30bb.30f3.30c8;3324=30c0.30fc.30b9;3325=30c7.30b7;3326=30c9.30eb;3327=30c8.30f3;3328=30ca.30ce;3329=30ce.30c3.30c8;332a=30cf.30a4.30c4;332b=30d1.30fc.30bb.30f3.30c8;332c=30d1.30fc.30c4;332d=30d0.30fc.30ec.30eb;332e=30d4.30a2.30b9.30c8.30eb",
|
|
772
|
+
";332f=30d4.30af.30eb;3330=30d4.30b3;3331=30d3.30eb;3332=30d5.30a1.30e9.30c3.30c9;3333=30d5.30a3.30fc.30c8;3334=30d6.30c3.30b7.30a7.30eb;3335=30d5.30e9.30f3;3336=30d8.30af.30bf.30fc.30eb;3337=30da.30bd;3338=30da.30cb.30d2;3339=30d8.30eb.30c4;333a=30da.30f3.30b9;333b=30da.30fc.30b8;333c=30d9.30fc.30bf;333d=30dd.30a4.30f3.30c8;333e=30dc.30eb.30c8;333f=30db.30f3;3340=30dd.30f3.30c9;3341=30db.30fc.30eb;3342=30db.30fc.30f3;3343=30de.30a4.30af.30ed;3344=30de.30a4.30eb;3345=30de.30c3.30cf;3346=30de.30eb.30af;3347=30de.30f3.30b7.30e7.30f3;3348=30df.30af.30ed.30f3;3349=30df.30ea;334a=30df.30ea.30d0.30fc.30eb;334b=30e1.30ac;334c=30e1.30ac.30c8.30f3;334d=30e1.30fc.30c8.30eb;334e=30e4.30fc.30c9;334f=30e4.30fc.30eb;3350=30e6.30a2.30f3;3351=30ea.30c3.30c8.30eb;3352=30ea.30e9;3353=30eb.30d4.30fc;3354=30eb.30fc.30d6.30eb;3355=30ec.30e0;3356=30ec.30f3.30c8.30b2.30f3;3357=30ef.30c3.30c8;3358=0030.70b9;3359=0031.70b9;335a=0032.70b9;335b=0033.70b9;335c=0034.70b9;335d=0035.70b9;335e=0036.70b9;335f=0037.70b9",
|
|
773
|
+
";3360=0038.70b9;3361=0039.70b9;3362=0031.0030.70b9;3363=0031.0031.70b9;3364=0031.0032.70b9;3365=0031.0033.70b9;3366=0031.0034.70b9;3367=0031.0035.70b9;3368=0031.0036.70b9;3369=0031.0037.70b9;336a=0031.0038.70b9;336b=0031.0039.70b9;336c=0032.0030.70b9;336d=0032.0031.70b9;336e=0032.0032.70b9;336f=0032.0033.70b9;3370=0032.0034.70b9;3371=0068.0070.0061;3372=0064.0061;3373=0061.0075;3374=0062.0061.0072;3375=006f.0076;3376=0070.0063;3377=0064.006d;3378=0064.006d.0032;3379=0064.006d.0033;337a=0069.0075;337b=5e73.6210;337c=662d.548c;337d=5927.6b63;337e=660e.6cbb;337f=682a.5f0f.4f1a.793e;3380=0070.0061;3381=006e.0061;3382=03bc.0061;3383=006d.0061;3384=006b.0061;3385=006b.0062;3386=006d.0062;3387=0067.0062;3388=0063.0061.006c;3389=006b.0063.0061.006c;338a=0070.0066;338b=006e.0066;338c=03bc.0066;338d=03bc.0067;338e=006d.0067;338f=006b.0067;3390=0068.007a;3391=006b.0068.007a;3392=006d.0068.007a;3393=0067.0068.007a;3394=0074.0068.007a;3395=03bc.006c;3396=006d.006c;3397=0064.006c;3398=006b.006c;3399",
|
|
774
|
+
"=0066.006d;339a=006e.006d;339b=03bc.006d;339c=006d.006d;339d=0063.006d;339e=006b.006d;339f=006d.006d.0032;33a0=0063.006d.0032;33a1=006d.0032;33a2=006b.006d.0032;33a3=006d.006d.0033;33a4=0063.006d.0033;33a5=006d.0033;33a6=006b.006d.0033;33a7=006d.2215.0073;33a8=006d.2215.0073.0032;33a9=0070.0061;33aa=006b.0070.0061;33ab=006d.0070.0061;33ac=0067.0070.0061;33ad=0072.0061.0064;33ae=0072.0061.0064.2215.0073;33af=0072.0061.0064.2215.0073.0032;33b0=0070.0073;33b1=006e.0073;33b2=03bc.0073;33b3=006d.0073;33b4=0070.0076;33b5=006e.0076;33b6=03bc.0076;33b7=006d.0076;33b8=006b.0076;33b9=006d.0076;33ba=0070.0077;33bb=006e.0077;33bc=03bc.0077;33bd=006d.0077;33be=006b.0077;33bf=006d.0077;33c0=006b.03c9;33c1=006d.03c9;33c2=0061.002e.006d.002e;33c3=0062.0071;33c4=0063.0063;33c5=0063.0064;33c6=0063.2215.006b.0067;33c7=0063.006f.002e;33c8=0064.0062;33c9=0067.0079;33ca=0068.0061;33cb=0068.0070;33cc=0069.006e;33cd=006b.006b;33ce=006b.006d;33cf=006b.0074;33d0=006c.006d;33d1=006c.006e;33d2=006c.006f.0067;33d3",
|
|
775
|
+
"=006c.0078;33d4=006d.0062;33d5=006d.0069.006c;33d6=006d.006f.006c;33d7=0070.0068;33d8=0070.002e.006d.002e;33d9=0070.0070.006d;33da=0070.0072;33db=0073.0072;33dc=0073.0076;33dd=0077.0062;33de=0076.2215.006d;33df=0061.2215.006d;33e0=0031.65e5;33e1=0032.65e5;33e2=0033.65e5;33e3=0034.65e5;33e4=0035.65e5;33e5=0036.65e5;33e6=0037.65e5;33e7=0038.65e5;33e8=0039.65e5;33e9=0031.0030.65e5;33ea=0031.0031.65e5;33eb=0031.0032.65e5;33ec=0031.0033.65e5;33ed=0031.0034.65e5;33ee=0031.0035.65e5;33ef=0031.0036.65e5;33f0=0031.0037.65e5;33f1=0031.0038.65e5;33f2=0031.0039.65e5;33f3=0032.0030.65e5;33f4=0032.0031.65e5;33f5=0032.0032.65e5;33f6=0032.0033.65e5;33f7=0032.0034.65e5;33f8=0032.0035.65e5;33f9=0032.0036.65e5;33fa=0032.0037.65e5;33fb=0032.0038.65e5;33fc=0032.0039.65e5;33fd=0033.0030.65e5;33fe=0033.0031.65e5;33ff=0067.0061.006c;a640=a641;a642=a643;a644=a645;a646=a647;a648=a649;a64a=a64b;a64c=a64d;a64e=a64f;a650=a651;a652=a653;a654=a655;a656=a657;a658=a659;a65a=a65b;a65c=a65d;a65e=a65f;a660=a661;a662=a663",
|
|
776
|
+
";a664=a665;a666=a667;a668=a669;a66a=a66b;a66c=a66d;a680=a681;a682=a683;a684=a685;a686=a687;a688=a689;a68a=a68b;a68c=a68d;a68e=a68f;a690=a691;a692=a693;a694=a695;a696=a697;a698=a699;a69a=a69b;a69c=044a;a69d=044c;a722=a723;a724=a725;a726=a727;a728=a729;a72a=a72b;a72c=a72d;a72e=a72f;a732=a733;a734=a735;a736=a737;a738=a739;a73a=a73b;a73c=a73d;a73e=a73f;a740=a741;a742=a743;a744=a745;a746=a747;a748=a749;a74a=a74b;a74c=a74d;a74e=a74f;a750=a751;a752=a753;a754=a755;a756=a757;a758=a759;a75a=a75b;a75c=a75d;a75e=a75f;a760=a761;a762=a763;a764=a765;a766=a767;a768=a769;a76a=a76b;a76c=a76d;a76e=a76f;a770=a76f;a779=a77a;a77b=a77c;a77d=1d79;a77e=a77f;a780=a781;a782=a783;a784=a785;a786=a787;a78b=a78c;a78d=0265;a790=a791;a792=a793;a796=a797;a798=a799;a79a=a79b;a79c=a79d;a79e=a79f;a7a0=a7a1;a7a2=a7a3;a7a4=a7a5;a7a6=a7a7;a7a8=a7a9;a7aa=0266;a7ab=025c;a7ac=0261;a7ad=026c;a7ae=026a;a7b0=029e;a7b1=0287;a7b2=029d;a7b3=ab53;a7b4=a7b5;a7b6=a7b7;a7b8=a7b9;a7ba=a7bb;a7bc=a7bd;a7be=a7bf;a7c0=a7c1;a7c2=a7c3;a7c4=a794",
|
|
777
|
+
";a7c5=0282;a7c6=1d8e;a7c7=a7c8;a7c9=a7ca;a7cb=0264;a7cc=a7cd;a7ce=a7cf;a7d0=a7d1;a7d2=a7d3;a7d4=a7d5;a7d6=a7d7;a7d8=a7d9;a7da=a7db;a7dc=019b;a7f1=0073;a7f2=0063;a7f3=0066;a7f4=0071;a7f5=a7f6;a7f8=0127;a7f9=0153;ab5c=a727;ab5d=ab37;ab5e=026b;ab5f=ab52;ab69=028d;ab70=13a0;ab71=13a1;ab72=13a2;ab73=13a3;ab74=13a4;ab75=13a5;ab76=13a6;ab77=13a7;ab78=13a8;ab79=13a9;ab7a=13aa;ab7b=13ab;ab7c=13ac;ab7d=13ad;ab7e=13ae;ab7f=13af;ab80=13b0;ab81=13b1;ab82=13b2;ab83=13b3;ab84=13b4;ab85=13b5;ab86=13b6;ab87=13b7;ab88=13b8;ab89=13b9;ab8a=13ba;ab8b=13bb;ab8c=13bc;ab8d=13bd;ab8e=13be;ab8f=13bf;ab90=13c0;ab91=13c1;ab92=13c2;ab93=13c3;ab94=13c4;ab95=13c5;ab96=13c6;ab97=13c7;ab98=13c8;ab99=13c9;ab9a=13ca;ab9b=13cb;ab9c=13cc;ab9d=13cd;ab9e=13ce;ab9f=13cf;aba0=13d0;aba1=13d1;aba2=13d2;aba3=13d3;aba4=13d4;aba5=13d5;aba6=13d6;aba7=13d7;aba8=13d8;aba9=13d9;abaa=13da;abab=13db;abac=13dc;abad=13dd;abae=13de;abaf=13df;abb0=13e0;abb1=13e1;abb2=13e2;abb3=13e3;abb4=13e4;abb5=13e5;abb6=13e6;abb7=13e7;abb8=13e8;abb9=13e9",
|
|
778
|
+
";abba=13ea;abbb=13eb;abbc=13ec;abbd=13ed;abbe=13ee;abbf=13ef;f900=8c48;f901=66f4;f902=8eca;f903=8cc8;f904=6ed1;f905=4e32;f906=53e5;f907..f908=9f9c;f909=5951;f90a=91d1;f90b=5587;f90c=5948;f90d=61f6;f90e=7669;f90f=7f85;f910=863f;f911=87ba;f912=88f8;f913=908f;f914=6a02;f915=6d1b;f916=70d9;f917=73de;f918=843d;f919=916a;f91a=99f1;f91b=4e82;f91c=5375;f91d=6b04;f91e=721b;f91f=862d;f920=9e1e;f921=5d50;f922=6feb;f923=85cd;f924=8964;f925=62c9;f926=81d8;f927=881f;f928=5eca;f929=6717;f92a=6d6a;f92b=72fc;f92c=90ce;f92d=4f86;f92e=51b7;f92f=52de;f930=64c4;f931=6ad3;f932=7210;f933=76e7;f934=8001;f935=8606;f936=865c;f937=8def;f938=9732;f939=9b6f;f93a=9dfa;f93b=788c;f93c=797f;f93d=7da0;f93e=83c9;f93f=9304;f940=9e7f;f941=8ad6;f942=58df;f943=5f04;f944=7c60;f945=807e;f946=7262;f947=78ca;f948=8cc2;f949=96f7;f94a=58d8;f94b=5c62;f94c=6a13;f94d=6dda;f94e=6f0f;f94f=7d2f;f950=7e37;f951=964b;f952=52d2;f953=808b;f954=51dc;f955=51cc;f956=7a1c;f957=7dbe;f958=83f1;f959=9675;f95a=8b80;f95b=62cf;f95c=6a02;f95d=8afe;f95",
|
|
779
|
+
"e=4e39;f95f=5be7;f960=6012;f961=7387;f962=7570;f963=5317;f964=78fb;f965=4fbf;f966=5fa9;f967=4e0d;f968=6ccc;f969=6578;f96a=7d22;f96b=53c3;f96c=585e;f96d=7701;f96e=8449;f96f=8aaa;f970=6bba;f971=8fb0;f972=6c88;f973=62fe;f974=82e5;f975=63a0;f976=7565;f977=4eae;f978=5169;f979=51c9;f97a=6881;f97b=7ce7;f97c=826f;f97d=8ad2;f97e=91cf;f97f=52f5;f980=5442;f981=5973;f982=5eec;f983=65c5;f984=6ffe;f985=792a;f986=95ad;f987=9a6a;f988=9e97;f989=9ece;f98a=529b;f98b=66c6;f98c=6b77;f98d=8f62;f98e=5e74;f98f=6190;f990=6200;f991=649a;f992=6f23;f993=7149;f994=7489;f995=79ca;f996=7df4;f997=806f;f998=8f26;f999=84ee;f99a=9023;f99b=934a;f99c=5217;f99d=52a3;f99e=54bd;f99f=70c8;f9a0=88c2;f9a1=8aaa;f9a2=5ec9;f9a3=5ff5;f9a4=637b;f9a5=6bae;f9a6=7c3e;f9a7=7375;f9a8=4ee4;f9a9=56f9;f9aa=5be7;f9ab=5dba;f9ac=601c;f9ad=73b2;f9ae=7469;f9af=7f9a;f9b0=8046;f9b1=9234;f9b2=96f6;f9b3=9748;f9b4=9818;f9b5=4f8b;f9b6=79ae;f9b7=91b4;f9b8=96b8;f9b9=60e1;f9ba=4e86;f9bb=50da;f9bc=5bee;f9bd=5c3f;f9be=6599;f9bf=6a02;f9c0=71ce;f9c1=7642;f9c",
|
|
780
|
+
"2=84fc;f9c3=907c;f9c4=9f8d;f9c5=6688;f9c6=962e;f9c7=5289;f9c8=677b;f9c9=67f3;f9ca=6d41;f9cb=6e9c;f9cc=7409;f9cd=7559;f9ce=786b;f9cf=7d10;f9d0=985e;f9d1=516d;f9d2=622e;f9d3=9678;f9d4=502b;f9d5=5d19;f9d6=6dea;f9d7=8f2a;f9d8=5f8b;f9d9=6144;f9da=6817;f9db=7387;f9dc=9686;f9dd=5229;f9de=540f;f9df=5c65;f9e0=6613;f9e1=674e;f9e2=68a8;f9e3=6ce5;f9e4=7406;f9e5=75e2;f9e6=7f79;f9e7=88cf;f9e8=88e1;f9e9=91cc;f9ea=96e2;f9eb=533f;f9ec=6eba;f9ed=541d;f9ee=71d0;f9ef=7498;f9f0=85fa;f9f1=96a3;f9f2=9c57;f9f3=9e9f;f9f4=6797;f9f5=6dcb;f9f6=81e8;f9f7=7acb;f9f8=7b20;f9f9=7c92;f9fa=72c0;f9fb=7099;f9fc=8b58;f9fd=4ec0;f9fe=8336;f9ff=523a;fa00=5207;fa01=5ea6;fa02=62d3;fa03=7cd6;fa04=5b85;fa05=6d1e;fa06=66b4;fa07=8f3b;fa08=884c;fa09=964d;fa0a=898b;fa0b=5ed3;fa0c=5140;fa0d=55c0;fa10=585a;fa12=6674;fa15=51de;fa16=732a;fa17=76ca;fa18=793c;fa19=795e;fa1a=7965;fa1b=798f;fa1c=9756;fa1d=7cbe;fa1e=7fbd;fa20=8612;fa22=8af8;fa25=9038;fa26=90fd;fa2a=98ef;fa2b=98fc;fa2c=9928;fa2d=9db4;fa2e=90de;fa2f=96b7;fa30=4fae;fa31=50e7;fa3",
|
|
781
|
+
"2=514d;fa33=52c9;fa34=52e4;fa35=5351;fa36=559d;fa37=5606;fa38=5668;fa39=5840;fa3a=58a8;fa3b=5c64;fa3c=5c6e;fa3d=6094;fa3e=6168;fa3f=618e;fa40=61f2;fa41=654f;fa42=65e2;fa43=6691;fa44=6885;fa45=6d77;fa46=6e1a;fa47=6f22;fa48=716e;fa49=722b;fa4a=7422;fa4b=7891;fa4c=793e;fa4d=7949;fa4e=7948;fa4f=7950;fa50=7956;fa51=795d;fa52=798d;fa53=798e;fa54=7a40;fa55=7a81;fa56=7bc0;fa57=7df4;fa58=7e09;fa59=7e41;fa5a=7f72;fa5b=8005;fa5c=81ed;fa5d..fa5e=8279;fa5f=8457;fa60=8910;fa61=8996;fa62=8b01;fa63=8b39;fa64=8cd3;fa65=8d08;fa66=8fb6;fa67=9038;fa68=96e3;fa69=97ff;fa6a=983b;fa6b=6075;fa6c=242ee;fa6d=8218;fa70=4e26;fa71=51b5;fa72=5168;fa73=4f80;fa74=5145;fa75=5180;fa76=52c7;fa77=52fa;fa78=559d;fa79=5555;fa7a=5599;fa7b=55e2;fa7c=585a;fa7d=58b3;fa7e=5944;fa7f=5954;fa80=5a62;fa81=5b28;fa82=5ed2;fa83=5ed9;fa84=5f69;fa85=5fad;fa86=60d8;fa87=614e;fa88=6108;fa89=618e;fa8a=6160;fa8b=61f2;fa8c=6234;fa8d=63c4;fa8e=641c;fa8f=6452;fa90=6556;fa91=6674;fa92=6717;fa93=671b;fa94=6756;fa95=6b79;fa96=6bba;fa97=6d41;fa98=6",
|
|
782
|
+
"edb;fa99=6ecb;fa9a=6f22;fa9b=701e;fa9c=716e;fa9d=77a7;fa9e=7235;fa9f=72af;faa0=732a;faa1=7471;faa2=7506;faa3=753b;faa4=761d;faa5=761f;faa6=76ca;faa7=76db;faa8=76f4;faa9=774a;faaa=7740;faab=78cc;faac=7ab1;faad=7bc0;faae=7c7b;faaf=7d5b;fab0=7df4;fab1=7f3e;fab2=8005;fab3=8352;fab4=83ef;fab5=8779;fab6=8941;fab7=8986;fab8=8996;fab9=8abf;faba=8af8;fabb=8acb;fabc=8b01;fabd=8afe;fabe=8aed;fabf=8b39;fac0=8b8a;fac1=8d08;fac2=8f38;fac3=9072;fac4=9199;fac5=9276;fac6=967c;fac7=96e3;fac8=9756;fac9=97db;faca=97ff;facb=980b;facc=983b;facd=9b12;face=9f9c;facf=2284a;fad0=22844;fad1=233d5;fad2=3b9d;fad3=4018;fad4=4039;fad5=25249;fad6=25cd0;fad7=27ed3;fad8=9f43;fad9=9f8e;fb00=0066.0066;fb01=0066.0069;fb02=0066.006c;fb03=0066.0066.0069;fb04=0066.0066.006c;fb05..fb06=0073.0074;fb13=0574.0576;fb14=0574.0565;fb15=0574.056b;fb16=057e.0576;fb17=0574.056d;fb1d=05d9.05b4;fb1f=05f2.05b7;fb20=05e2;fb21=05d0;fb22=05d3;fb23=05d4;fb24=05db;fb25=05dc;fb26=05dd;fb27=05e8;fb28=05ea;fb29=002b;fb2a=05e9.05c1;fb2b=05e9.05c2",
|
|
783
|
+
";fb2c=05e9.05bc.05c1;fb2d=05e9.05bc.05c2;fb2e=05d0.05b7;fb2f=05d0.05b8;fb30=05d0.05bc;fb31=05d1.05bc;fb32=05d2.05bc;fb33=05d3.05bc;fb34=05d4.05bc;fb35=05d5.05bc;fb36=05d6.05bc;fb38=05d8.05bc;fb39=05d9.05bc;fb3a=05da.05bc;fb3b=05db.05bc;fb3c=05dc.05bc;fb3e=05de.05bc;fb40=05e0.05bc;fb41=05e1.05bc;fb43=05e3.05bc;fb44=05e4.05bc;fb46=05e6.05bc;fb47=05e7.05bc;fb48=05e8.05bc;fb49=05e9.05bc;fb4a=05ea.05bc;fb4b=05d5.05b9;fb4c=05d1.05bf;fb4d=05db.05bf;fb4e=05e4.05bf;fb4f=05d0.05dc;fb50..fb51=0671;fb52..fb55=067b;fb56..fb59=067e;fb5a..fb5d=0680;fb5e..fb61=067a;fb62..fb65=067f;fb66..fb69=0679;fb6a..fb6d=06a4;fb6e..fb71=06a6;fb72..fb75=0684;fb76..fb79=0683;fb7a..fb7d=0686;fb7e..fb81=0687;fb82..fb83=068d;fb84..fb85=068c;fb86..fb87=068e;fb88..fb89=0688;fb8a..fb8b=0698;fb8c..fb8d=0691;fb8e..fb91=06a9;fb92..fb95=06af;fb96..fb99=06b3;fb9a..fb9d=06b1;fb9e..fb9f=06ba;fba0..fba3=06bb;fba4..fba5=06c0;fba6..fba9=06c1;fbaa..fbad=06be;fbae..fbaf=06d2;fbb0..fbb1=06d3;fbd3..fbd6=06ad;fbd7..fbd8=06c7;fbd9..fbda=0",
|
|
784
|
+
"6c6;fbdb..fbdc=06c8;fbdd=06c7.0674;fbde..fbdf=06cb;fbe0..fbe1=06c5;fbe2..fbe3=06c9;fbe4..fbe7=06d0;fbe8..fbe9=0649;fbea..fbeb=0626.0627;fbec..fbed=0626.06d5;fbee..fbef=0626.0648;fbf0..fbf1=0626.06c7;fbf2..fbf3=0626.06c6;fbf4..fbf5=0626.06c8;fbf6..fbf8=0626.06d0;fbf9..fbfb=0626.0649;fbfc..fbff=06cc;fc00=0626.062c;fc01=0626.062d;fc02=0626.0645;fc03=0626.0649;fc04=0626.064a;fc05=0628.062c;fc06=0628.062d;fc07=0628.062e;fc08=0628.0645;fc09=0628.0649;fc0a=0628.064a;fc0b=062a.062c;fc0c=062a.062d;fc0d=062a.062e;fc0e=062a.0645;fc0f=062a.0649;fc10=062a.064a;fc11=062b.062c;fc12=062b.0645;fc13=062b.0649;fc14=062b.064a;fc15=062c.062d;fc16=062c.0645;fc17=062d.062c;fc18=062d.0645;fc19=062e.062c;fc1a=062e.062d;fc1b=062e.0645;fc1c=0633.062c;fc1d=0633.062d;fc1e=0633.062e;fc1f=0633.0645;fc20=0635.062d;fc21=0635.0645;fc22=0636.062c;fc23=0636.062d;fc24=0636.062e;fc25=0636.0645;fc26=0637.062d;fc27=0637.0645;fc28=0638.0645;fc29=0639.062c;fc2a=0639.0645;fc2b=063a.062c;fc2c=063a.0645;fc2d=0641.062c;fc2e=0641.0",
|
|
785
|
+
"62d;fc2f=0641.062e;fc30=0641.0645;fc31=0641.0649;fc32=0641.064a;fc33=0642.062d;fc34=0642.0645;fc35=0642.0649;fc36=0642.064a;fc37=0643.0627;fc38=0643.062c;fc39=0643.062d;fc3a=0643.062e;fc3b=0643.0644;fc3c=0643.0645;fc3d=0643.0649;fc3e=0643.064a;fc3f=0644.062c;fc40=0644.062d;fc41=0644.062e;fc42=0644.0645;fc43=0644.0649;fc44=0644.064a;fc45=0645.062c;fc46=0645.062d;fc47=0645.062e;fc48=0645.0645;fc49=0645.0649;fc4a=0645.064a;fc4b=0646.062c;fc4c=0646.062d;fc4d=0646.062e;fc4e=0646.0645;fc4f=0646.0649;fc50=0646.064a;fc51=0647.062c;fc52=0647.0645;fc53=0647.0649;fc54=0647.064a;fc55=064a.062c;fc56=064a.062d;fc57=064a.062e;fc58=064a.0645;fc59=064a.0649;fc5a=064a.064a;fc5b=0630.0670;fc5c=0631.0670;fc5d=0649.0670;fc5e=0020.064c.0651;fc5f=0020.064d.0651;fc60=0020.064e.0651;fc61=0020.064f.0651;fc62=0020.0650.0651;fc63=0020.0651.0670;fc64=0626.0631;fc65=0626.0632;fc66=0626.0645;fc67=0626.0646;fc68=0626.0649;fc69=0626.064a;fc6a=0628.0631;fc6b=0628.0632;fc6c=0628.0645;fc6d=0628.0646;fc6e=0628.0649;fc6f=0",
|
|
786
|
+
"628.064a;fc70=062a.0631;fc71=062a.0632;fc72=062a.0645;fc73=062a.0646;fc74=062a.0649;fc75=062a.064a;fc76=062b.0631;fc77=062b.0632;fc78=062b.0645;fc79=062b.0646;fc7a=062b.0649;fc7b=062b.064a;fc7c=0641.0649;fc7d=0641.064a;fc7e=0642.0649;fc7f=0642.064a;fc80=0643.0627;fc81=0643.0644;fc82=0643.0645;fc83=0643.0649;fc84=0643.064a;fc85=0644.0645;fc86=0644.0649;fc87=0644.064a;fc88=0645.0627;fc89=0645.0645;fc8a=0646.0631;fc8b=0646.0632;fc8c=0646.0645;fc8d=0646.0646;fc8e=0646.0649;fc8f=0646.064a;fc90=0649.0670;fc91=064a.0631;fc92=064a.0632;fc93=064a.0645;fc94=064a.0646;fc95=064a.0649;fc96=064a.064a;fc97=0626.062c;fc98=0626.062d;fc99=0626.062e;fc9a=0626.0645;fc9b=0626.0647;fc9c=0628.062c;fc9d=0628.062d;fc9e=0628.062e;fc9f=0628.0645;fca0=0628.0647;fca1=062a.062c;fca2=062a.062d;fca3=062a.062e;fca4=062a.0645;fca5=062a.0647;fca6=062b.0645;fca7=062c.062d;fca8=062c.0645;fca9=062d.062c;fcaa=062d.0645;fcab=062e.062c;fcac=062e.0645;fcad=0633.062c;fcae=0633.062d;fcaf=0633.062e;fcb0=0633.0645;fcb1=0635.062d;f",
|
|
787
|
+
"cb2=0635.062e;fcb3=0635.0645;fcb4=0636.062c;fcb5=0636.062d;fcb6=0636.062e;fcb7=0636.0645;fcb8=0637.062d;fcb9=0638.0645;fcba=0639.062c;fcbb=0639.0645;fcbc=063a.062c;fcbd=063a.0645;fcbe=0641.062c;fcbf=0641.062d;fcc0=0641.062e;fcc1=0641.0645;fcc2=0642.062d;fcc3=0642.0645;fcc4=0643.062c;fcc5=0643.062d;fcc6=0643.062e;fcc7=0643.0644;fcc8=0643.0645;fcc9=0644.062c;fcca=0644.062d;fccb=0644.062e;fccc=0644.0645;fccd=0644.0647;fcce=0645.062c;fccf=0645.062d;fcd0=0645.062e;fcd1=0645.0645;fcd2=0646.062c;fcd3=0646.062d;fcd4=0646.062e;fcd5=0646.0645;fcd6=0646.0647;fcd7=0647.062c;fcd8=0647.0645;fcd9=0647.0670;fcda=064a.062c;fcdb=064a.062d;fcdc=064a.062e;fcdd=064a.0645;fcde=064a.0647;fcdf=0626.0645;fce0=0626.0647;fce1=0628.0645;fce2=0628.0647;fce3=062a.0645;fce4=062a.0647;fce5=062b.0645;fce6=062b.0647;fce7=0633.0645;fce8=0633.0647;fce9=0634.0645;fcea=0634.0647;fceb=0643.0644;fcec=0643.0645;fced=0644.0645;fcee=0646.0645;fcef=0646.0647;fcf0=064a.0645;fcf1=064a.0647;fcf2=0640.064e.0651;fcf3=0640.064f.0651;f",
|
|
788
|
+
"cf4=0640.0650.0651;fcf5=0637.0649;fcf6=0637.064a;fcf7=0639.0649;fcf8=0639.064a;fcf9=063a.0649;fcfa=063a.064a;fcfb=0633.0649;fcfc=0633.064a;fcfd=0634.0649;fcfe=0634.064a;fcff=062d.0649;fd00=062d.064a;fd01=062c.0649;fd02=062c.064a;fd03=062e.0649;fd04=062e.064a;fd05=0635.0649;fd06=0635.064a;fd07=0636.0649;fd08=0636.064a;fd09=0634.062c;fd0a=0634.062d;fd0b=0634.062e;fd0c=0634.0645;fd0d=0634.0631;fd0e=0633.0631;fd0f=0635.0631;fd10=0636.0631;fd11=0637.0649;fd12=0637.064a;fd13=0639.0649;fd14=0639.064a;fd15=063a.0649;fd16=063a.064a;fd17=0633.0649;fd18=0633.064a;fd19=0634.0649;fd1a=0634.064a;fd1b=062d.0649;fd1c=062d.064a;fd1d=062c.0649;fd1e=062c.064a;fd1f=062e.0649;fd20=062e.064a;fd21=0635.0649;fd22=0635.064a;fd23=0636.0649;fd24=0636.064a;fd25=0634.062c;fd26=0634.062d;fd27=0634.062e;fd28=0634.0645;fd29=0634.0631;fd2a=0633.0631;fd2b=0635.0631;fd2c=0636.0631;fd2d=0634.062c;fd2e=0634.062d;fd2f=0634.062e;fd30=0634.0645;fd31=0633.0647;fd32=0634.0647;fd33=0637.0645;fd34=0633.062c;fd35=0633.062d;fd36=0",
|
|
789
|
+
"633.062e;fd37=0634.062c;fd38=0634.062d;fd39=0634.062e;fd3a=0637.0645;fd3b=0638.0645;fd3c..fd3d=0627.064b;fd50=062a.062c.0645;fd51..fd52=062a.062d.062c;fd53=062a.062d.0645;fd54=062a.062e.0645;fd55=062a.0645.062c;fd56=062a.0645.062d;fd57=062a.0645.062e;fd58..fd59=062c.0645.062d;fd5a=062d.0645.064a;fd5b=062d.0645.0649;fd5c=0633.062d.062c;fd5d=0633.062c.062d;fd5e=0633.062c.0649;fd5f..fd60=0633.0645.062d;fd61=0633.0645.062c;fd62..fd63=0633.0645.0645;fd64..fd65=0635.062d.062d;fd66=0635.0645.0645;fd67..fd68=0634.062d.0645;fd69=0634.062c.064a;fd6a..fd6b=0634.0645.062e;fd6c..fd6d=0634.0645.0645;fd6e=0636.062d.0649;fd6f..fd70=0636.062e.0645;fd71..fd72=0637.0645.062d;fd73=0637.0645.0645;fd74=0637.0645.064a;fd75=0639.062c.0645;fd76..fd77=0639.0645.0645;fd78=0639.0645.0649;fd79=063a.0645.0645;fd7a=063a.0645.064a;fd7b=063a.0645.0649;fd7c..fd7d=0641.062e.0645;fd7e=0642.0645.062d;fd7f=0642.0645.0645;fd80=0644.062d.0645;fd81=0644.062d.064a;fd82=0644.062d.0649;fd83..fd84=0644.062c.062c;fd85..fd86=0644.0",
|
|
790
|
+
"62e.0645;fd87..fd88=0644.0645.062d;fd89=0645.062d.062c;fd8a=0645.062d.0645;fd8b=0645.062d.064a;fd8c=0645.062c.062d;fd8d=0645.062c.0645;fd8e=0645.062e.062c;fd8f=0645.062e.0645;fd92=0645.062c.062e;fd93=0647.0645.062c;fd94=0647.0645.0645;fd95=0646.062d.0645;fd96=0646.062d.0649;fd97..fd98=0646.062c.0645;fd99=0646.062c.0649;fd9a=0646.0645.064a;fd9b=0646.0645.0649;fd9c..fd9d=064a.0645.0645;fd9e=0628.062e.064a;fd9f=062a.062c.064a;fda0=062a.062c.0649;fda1=062a.062e.064a;fda2=062a.062e.0649;fda3=062a.0645.064a;fda4=062a.0645.0649;fda5=062c.0645.064a;fda6=062c.062d.0649;fda7=062c.0645.0649;fda8=0633.062e.0649;fda9=0635.062d.064a;fdaa=0634.062d.064a;fdab=0636.062d.064a;fdac=0644.062c.064a;fdad=0644.0645.064a;fdae=064a.062d.064a;fdaf=064a.062c.064a;fdb0=064a.0645.064a;fdb1=0645.0645.064a;fdb2=0642.0645.064a;fdb3=0646.062d.064a;fdb4=0642.0645.062d;fdb5=0644.062d.0645;fdb6=0639.0645.064a;fdb7=0643.0645.064a;fdb8=0646.062c.062d;fdb9=0645.062e.064a;fdba=0644.062c.0645;fdbb=0643.0645.0645;fdbc=0644.062",
|
|
791
|
+
"c.0645;fdbd=0646.062c.062d;fdbe=062c.062d.064a;fdbf=062d.062c.064a;fdc0=0645.062c.064a;fdc1=0641.0645.064a;fdc2=0628.062d.064a;fdc3=0643.0645.0645;fdc4=0639.062c.0645;fdc5=0635.0645.0645;fdc6=0633.062e.064a;fdc7=0646.062c.064a;fdf0=0635.0644.06d2;fdf1=0642.0644.06d2;fdf2=0627.0644.0644.0647;fdf3=0627.0643.0628.0631;fdf4=0645.062d.0645.062f;fdf5=0635.0644.0639.0645;fdf6=0631.0633.0648.0644;fdf7=0639.0644.064a.0647;fdf8=0648.0633.0644.0645;fdf9=0635.0644.0649;fdfa=0635.0644.0649.0020.0627.0644.0644.0647.0020.0639.0644.064a.0647.0020.0648.0633.0644.0645;fdfb=062c.0644.0020.062c.0644.0627.0644.0647;fdfc=0631.06cc.0627.0644;fe00..fe0f=;fe10=002c;fe11=3001;fe12=3002;fe13=003a;fe14=003b;fe15=0021;fe16=003f;fe17=3016;fe18=3017;fe19=002e.002e.002e;fe30=002e.002e;fe31=2014;fe32=2013;fe33..fe34=005f;fe35=0028;fe36=0029;fe37=007b;fe38=007d;fe39=3014;fe3a=3015;fe3b=3010;fe3c=3011;fe3d=300a;fe3e=300b;fe3f=3008;fe40=3009;fe41=300c;fe42=300d;fe43=300e;fe44=300f;fe47=005b;fe48=005d;fe49..fe4c=0020.0305",
|
|
792
|
+
";fe4d..fe4f=005f;fe50=002c;fe51=3001;fe52=002e;fe54=003b;fe55=003a;fe56=003f;fe57=0021;fe58=2014;fe59=0028;fe5a=0029;fe5b=007b;fe5c=007d;fe5d=3014;fe5e=3015;fe5f=0023;fe60=0026;fe61=002a;fe62=002b;fe63=002d;fe64=003c;fe65=003e;fe66=003d;fe68=005c;fe69=0024;fe6a=0025;fe6b=0040;fe70=0020.064b;fe71=0640.064b;fe72=0020.064c;fe74=0020.064d;fe76=0020.064e;fe77=0640.064e;fe78=0020.064f;fe79=0640.064f;fe7a=0020.0650;fe7b=0640.0650;fe7c=0020.0651;fe7d=0640.0651;fe7e=0020.0652;fe7f=0640.0652;fe80=0621;fe81..fe82=0622;fe83..fe84=0623;fe85..fe86=0624;fe87..fe88=0625;fe89..fe8c=0626;fe8d..fe8e=0627;fe8f..fe92=0628;fe93..fe94=0629;fe95..fe98=062a;fe99..fe9c=062b;fe9d..fea0=062c;fea1..fea4=062d;fea5..fea8=062e;fea9..feaa=062f;feab..feac=0630;fead..feae=0631;feaf..feb0=0632;feb1..feb4=0633;feb5..feb8=0634;feb9..febc=0635;febd..fec0=0636;fec1..fec4=0637;fec5..fec8=0638;fec9..fecc=0639;fecd..fed0=063a;fed1..fed4=0641;fed5..fed8=0642;fed9..fedc=0643;fedd..fee0=0644;fee1..fee4=0645;fee5..fee8=0646;fee9..f",
|
|
793
|
+
"eec=0647;feed..feee=0648;feef..fef0=0649;fef1..fef4=064a;fef5..fef6=0644.0622;fef7..fef8=0644.0623;fef9..fefa=0644.0625;fefb..fefc=0644.0627;feff=;ff01=0021;ff02=0022;ff03=0023;ff04=0024;ff05=0025;ff06=0026;ff07=0027;ff08=0028;ff09=0029;ff0a=002a;ff0b=002b;ff0c=002c;ff0d=002d;ff0e=002e;ff0f=002f;ff10=0030;ff11=0031;ff12=0032;ff13=0033;ff14=0034;ff15=0035;ff16=0036;ff17=0037;ff18=0038;ff19=0039;ff1a=003a;ff1b=003b;ff1c=003c;ff1d=003d;ff1e=003e;ff1f=003f;ff20=0040;ff21=0061;ff22=0062;ff23=0063;ff24=0064;ff25=0065;ff26=0066;ff27=0067;ff28=0068;ff29=0069;ff2a=006a;ff2b=006b;ff2c=006c;ff2d=006d;ff2e=006e;ff2f=006f;ff30=0070;ff31=0071;ff32=0072;ff33=0073;ff34=0074;ff35=0075;ff36=0076;ff37=0077;ff38=0078;ff39=0079;ff3a=007a;ff3b=005b;ff3c=005c;ff3d=005d;ff3e=005e;ff3f=005f;ff40=0060;ff41=0061;ff42=0062;ff43=0063;ff44=0064;ff45=0065;ff46=0066;ff47=0067;ff48=0068;ff49=0069;ff4a=006a;ff4b=006b;ff4c=006c;ff4d=006d;ff4e=006e;ff4f=006f;ff50=0070;ff51=0071;ff52=0072;ff53=0073;ff54=0074;ff55=0075;ff5",
|
|
794
|
+
"6=0076;ff57=0077;ff58=0078;ff59=0079;ff5a=007a;ff5b=007b;ff5c=007c;ff5d=007d;ff5e=007e;ff5f=2985;ff60=2986;ff61=3002;ff62=300c;ff63=300d;ff64=3001;ff65=30fb;ff66=30f2;ff67=30a1;ff68=30a3;ff69=30a5;ff6a=30a7;ff6b=30a9;ff6c=30e3;ff6d=30e5;ff6e=30e7;ff6f=30c3;ff70=30fc;ff71=30a2;ff72=30a4;ff73=30a6;ff74=30a8;ff75=30aa;ff76=30ab;ff77=30ad;ff78=30af;ff79=30b1;ff7a=30b3;ff7b=30b5;ff7c=30b7;ff7d=30b9;ff7e=30bb;ff7f=30bd;ff80=30bf;ff81=30c1;ff82=30c4;ff83=30c6;ff84=30c8;ff85=30ca;ff86=30cb;ff87=30cc;ff88=30cd;ff89=30ce;ff8a=30cf;ff8b=30d2;ff8c=30d5;ff8d=30d8;ff8e=30db;ff8f=30de;ff90=30df;ff91=30e0;ff92=30e1;ff93=30e2;ff94=30e4;ff95=30e6;ff96=30e8;ff97=30e9;ff98=30ea;ff99=30eb;ff9a=30ec;ff9b=30ed;ff9c=30ef;ff9d=30f3;ff9e=3099;ff9f=309a;ffa0=;ffa1=1100;ffa2=1101;ffa3=11aa;ffa4=1102;ffa5=11ac;ffa6=11ad;ffa7=1103;ffa8=1104;ffa9=1105;ffaa=11b0;ffab=11b1;ffac=11b2;ffad=11b3;ffae=11b4;ffaf=11b5;ffb0=111a;ffb1=1106;ffb2=1107;ffb3=1108;ffb4=1121;ffb5=1109;ffb6=110a;ffb7=110b;ffb8=110c;ffb9=110d;ffba=11",
|
|
795
|
+
"0e;ffbb=110f;ffbc=1110;ffbd=1111;ffbe=1112;ffc2=1161;ffc3=1162;ffc4=1163;ffc5=1164;ffc6=1165;ffc7=1166;ffca=1167;ffcb=1168;ffcc=1169;ffcd=116a;ffce=116b;ffcf=116c;ffd2=116d;ffd3=116e;ffd4=116f;ffd5=1170;ffd6=1171;ffd7=1172;ffda=1173;ffdb=1174;ffdc=1175;ffe0=00a2;ffe1=00a3;ffe2=00ac;ffe3=0020.0304;ffe4=00a6;ffe5=00a5;ffe6=20a9;ffe8=2502;ffe9=2190;ffea=2191;ffeb=2192;ffec=2193;ffed=25a0;ffee=25cb;fff0..fff8=;10400=10428;10401=10429;10402=1042a;10403=1042b;10404=1042c;10405=1042d;10406=1042e;10407=1042f;10408=10430;10409=10431;1040a=10432;1040b=10433;1040c=10434;1040d=10435;1040e=10436;1040f=10437;10410=10438;10411=10439;10412=1043a;10413=1043b;10414=1043c;10415=1043d;10416=1043e;10417=1043f;10418=10440;10419=10441;1041a=10442;1041b=10443;1041c=10444;1041d=10445;1041e=10446;1041f=10447;10420=10448;10421=10449;10422=1044a;10423=1044b;10424=1044c;10425=1044d;10426=1044e;10427=1044f;104b0=104d8;104b1=104d9;104b2=104da;104b3=104db;104b4=104dc;104b5=104dd;104b6=104de;104b7=104df;104b8=104e0;10",
|
|
796
|
+
"4b9=104e1;104ba=104e2;104bb=104e3;104bc=104e4;104bd=104e5;104be=104e6;104bf=104e7;104c0=104e8;104c1=104e9;104c2=104ea;104c3=104eb;104c4=104ec;104c5=104ed;104c6=104ee;104c7=104ef;104c8=104f0;104c9=104f1;104ca=104f2;104cb=104f3;104cc=104f4;104cd=104f5;104ce=104f6;104cf=104f7;104d0=104f8;104d1=104f9;104d2=104fa;104d3=104fb;10570=10597;10571=10598;10572=10599;10573=1059a;10574=1059b;10575=1059c;10576=1059d;10577=1059e;10578=1059f;10579=105a0;1057a=105a1;1057c=105a3;1057d=105a4;1057e=105a5;1057f=105a6;10580=105a7;10581=105a8;10582=105a9;10583=105aa;10584=105ab;10585=105ac;10586=105ad;10587=105ae;10588=105af;10589=105b0;1058a=105b1;1058c=105b3;1058d=105b4;1058e=105b5;1058f=105b6;10590=105b7;10591=105b8;10592=105b9;10594=105bb;10595=105bc;10781=02d0;10782=02d1;10783=00e6;10784=0299;10785=0253;10787=02a3;10788=ab66;10789=02a5;1078a=02a4;1078b=0256;1078c=0257;1078d=1d91;1078e=0258;1078f=025e;10790=02a9;10791=0264;10792=0262;10793=0260;10794=029b;10795=0127;10796=029c;10797=0267;10798=0284;10799",
|
|
797
|
+
"=02aa;1079a=02ab;1079b=026c;1079c=1df04;1079d=a78e;1079e=026e;1079f=1df05;107a0=028e;107a1=1df06;107a2=00f8;107a3=0276;107a4=0277;107a5=0071;107a6=027a;107a7=1df08;107a8=027d;107a9=027e;107aa=0280;107ab=02a8;107ac=02a6;107ad=ab67;107ae=02a7;107af=0288;107b0=2c71;107b2=028f;107b3=02a1;107b4=02a2;107b5=0298;107b6=01c0;107b7=01c1;107b8=01c2;107b9=1df0a;107ba=1df1e;10c80=10cc0;10c81=10cc1;10c82=10cc2;10c83=10cc3;10c84=10cc4;10c85=10cc5;10c86=10cc6;10c87=10cc7;10c88=10cc8;10c89=10cc9;10c8a=10cca;10c8b=10ccb;10c8c=10ccc;10c8d=10ccd;10c8e=10cce;10c8f=10ccf;10c90=10cd0;10c91=10cd1;10c92=10cd2;10c93=10cd3;10c94=10cd4;10c95=10cd5;10c96=10cd6;10c97=10cd7;10c98=10cd8;10c99=10cd9;10c9a=10cda;10c9b=10cdb;10c9c=10cdc;10c9d=10cdd;10c9e=10cde;10c9f=10cdf;10ca0=10ce0;10ca1=10ce1;10ca2=10ce2;10ca3=10ce3;10ca4=10ce4;10ca5=10ce5;10ca6=10ce6;10ca7=10ce7;10ca8=10ce8;10ca9=10ce9;10caa=10cea;10cab=10ceb;10cac=10cec;10cad=10ced;10cae=10cee;10caf=10cef;10cb0=10cf0;10cb1=10cf1;10cb2=10cf2;10d50=10d70;10d51=10d71;",
|
|
798
|
+
"10d52=10d72;10d53=10d73;10d54=10d74;10d55=10d75;10d56=10d76;10d57=10d77;10d58=10d78;10d59=10d79;10d5a=10d7a;10d5b=10d7b;10d5c=10d7c;10d5d=10d7d;10d5e=10d7e;10d5f=10d7f;10d60=10d80;10d61=10d81;10d62=10d82;10d63=10d83;10d64=10d84;10d65=10d85;118a0=118c0;118a1=118c1;118a2=118c2;118a3=118c3;118a4=118c4;118a5=118c5;118a6=118c6;118a7=118c7;118a8=118c8;118a9=118c9;118aa=118ca;118ab=118cb;118ac=118cc;118ad=118cd;118ae=118ce;118af=118cf;118b0=118d0;118b1=118d1;118b2=118d2;118b3=118d3;118b4=118d4;118b5=118d5;118b6=118d6;118b7=118d7;118b8=118d8;118b9=118d9;118ba=118da;118bb=118db;118bc=118dc;118bd=118dd;118be=118de;118bf=118df;16e40=16e60;16e41=16e61;16e42=16e62;16e43=16e63;16e44=16e64;16e45=16e65;16e46=16e66;16e47=16e67;16e48=16e68;16e49=16e69;16e4a=16e6a;16e4b=16e6b;16e4c=16e6c;16e4d=16e6d;16e4e=16e6e;16e4f=16e6f;16e50=16e70;16e51=16e71;16e52=16e72;16e53=16e73;16e54=16e74;16e55=16e75;16e56=16e76;16e57=16e77;16e58=16e78;16e59=16e79;16e5a=16e7a;16e5b=16e7b;16e5c=16e7c;16e5d=16e7d;16e5e=16e7e;16e5",
|
|
799
|
+
"f=16e7f;16ea0=16ebb;16ea1=16ebc;16ea2=16ebd;16ea3=16ebe;16ea4=16ebf;16ea5=16ec0;16ea6=16ec1;16ea7=16ec2;16ea8=16ec3;16ea9=16ec4;16eaa=16ec5;16eab=16ec6;16eac=16ec7;16ead=16ec8;16eae=16ec9;16eaf=16eca;16eb0=16ecb;16eb1=16ecc;16eb2=16ecd;16eb3=16ece;16eb4=16ecf;16eb5=16ed0;16eb6=16ed1;16eb7=16ed2;16eb8=16ed3;1bca0..1bca3=;1ccd6=0061;1ccd7=0062;1ccd8=0063;1ccd9=0064;1ccda=0065;1ccdb=0066;1ccdc=0067;1ccdd=0068;1ccde=0069;1ccdf=006a;1cce0=006b;1cce1=006c;1cce2=006d;1cce3=006e;1cce4=006f;1cce5=0070;1cce6=0071;1cce7=0072;1cce8=0073;1cce9=0074;1ccea=0075;1cceb=0076;1ccec=0077;1cced=0078;1ccee=0079;1ccef=007a;1ccf0=0030;1ccf1=0031;1ccf2=0032;1ccf3=0033;1ccf4=0034;1ccf5=0035;1ccf6=0036;1ccf7=0037;1ccf8=0038;1ccf9=0039;1d15e=1d157.1d165;1d15f=1d158.1d165;1d160=1d158.1d165.1d16e;1d161=1d158.1d165.1d16f;1d162=1d158.1d165.1d170;1d163=1d158.1d165.1d171;1d164=1d158.1d165.1d172;1d173..1d17a=;1d1bb=1d1b9.1d165;1d1bc=1d1ba.1d165;1d1bd=1d1b9.1d165.1d16e;1d1be=1d1ba.1d165.1d16e;1d1bf=1d1b9.1d165.1d16f;1d1c",
|
|
800
|
+
"0=1d1ba.1d165.1d16f;1d400=0061;1d401=0062;1d402=0063;1d403=0064;1d404=0065;1d405=0066;1d406=0067;1d407=0068;1d408=0069;1d409=006a;1d40a=006b;1d40b=006c;1d40c=006d;1d40d=006e;1d40e=006f;1d40f=0070;1d410=0071;1d411=0072;1d412=0073;1d413=0074;1d414=0075;1d415=0076;1d416=0077;1d417=0078;1d418=0079;1d419=007a;1d41a=0061;1d41b=0062;1d41c=0063;1d41d=0064;1d41e=0065;1d41f=0066;1d420=0067;1d421=0068;1d422=0069;1d423=006a;1d424=006b;1d425=006c;1d426=006d;1d427=006e;1d428=006f;1d429=0070;1d42a=0071;1d42b=0072;1d42c=0073;1d42d=0074;1d42e=0075;1d42f=0076;1d430=0077;1d431=0078;1d432=0079;1d433=007a;1d434=0061;1d435=0062;1d436=0063;1d437=0064;1d438=0065;1d439=0066;1d43a=0067;1d43b=0068;1d43c=0069;1d43d=006a;1d43e=006b;1d43f=006c;1d440=006d;1d441=006e;1d442=006f;1d443=0070;1d444=0071;1d445=0072;1d446=0073;1d447=0074;1d448=0075;1d449=0076;1d44a=0077;1d44b=0078;1d44c=0079;1d44d=007a;1d44e=0061;1d44f=0062;1d450=0063;1d451=0064;1d452=0065;1d453=0066;1d454=0067;1d456=0069;1d457=006a;1d458=006b;1d459=006c;1",
|
|
801
|
+
"d45a=006d;1d45b=006e;1d45c=006f;1d45d=0070;1d45e=0071;1d45f=0072;1d460=0073;1d461=0074;1d462=0075;1d463=0076;1d464=0077;1d465=0078;1d466=0079;1d467=007a;1d468=0061;1d469=0062;1d46a=0063;1d46b=0064;1d46c=0065;1d46d=0066;1d46e=0067;1d46f=0068;1d470=0069;1d471=006a;1d472=006b;1d473=006c;1d474=006d;1d475=006e;1d476=006f;1d477=0070;1d478=0071;1d479=0072;1d47a=0073;1d47b=0074;1d47c=0075;1d47d=0076;1d47e=0077;1d47f=0078;1d480=0079;1d481=007a;1d482=0061;1d483=0062;1d484=0063;1d485=0064;1d486=0065;1d487=0066;1d488=0067;1d489=0068;1d48a=0069;1d48b=006a;1d48c=006b;1d48d=006c;1d48e=006d;1d48f=006e;1d490=006f;1d491=0070;1d492=0071;1d493=0072;1d494=0073;1d495=0074;1d496=0075;1d497=0076;1d498=0077;1d499=0078;1d49a=0079;1d49b=007a;1d49c=0061;1d49e=0063;1d49f=0064;1d4a2=0067;1d4a5=006a;1d4a6=006b;1d4a9=006e;1d4aa=006f;1d4ab=0070;1d4ac=0071;1d4ae=0073;1d4af=0074;1d4b0=0075;1d4b1=0076;1d4b2=0077;1d4b3=0078;1d4b4=0079;1d4b5=007a;1d4b6=0061;1d4b7=0062;1d4b8=0063;1d4b9=0064;1d4bb=0066;1d4bd=0068;1d4be=0069;",
|
|
802
|
+
"1d4bf=006a;1d4c0=006b;1d4c1=006c;1d4c2=006d;1d4c3=006e;1d4c5=0070;1d4c6=0071;1d4c7=0072;1d4c8=0073;1d4c9=0074;1d4ca=0075;1d4cb=0076;1d4cc=0077;1d4cd=0078;1d4ce=0079;1d4cf=007a;1d4d0=0061;1d4d1=0062;1d4d2=0063;1d4d3=0064;1d4d4=0065;1d4d5=0066;1d4d6=0067;1d4d7=0068;1d4d8=0069;1d4d9=006a;1d4da=006b;1d4db=006c;1d4dc=006d;1d4dd=006e;1d4de=006f;1d4df=0070;1d4e0=0071;1d4e1=0072;1d4e2=0073;1d4e3=0074;1d4e4=0075;1d4e5=0076;1d4e6=0077;1d4e7=0078;1d4e8=0079;1d4e9=007a;1d4ea=0061;1d4eb=0062;1d4ec=0063;1d4ed=0064;1d4ee=0065;1d4ef=0066;1d4f0=0067;1d4f1=0068;1d4f2=0069;1d4f3=006a;1d4f4=006b;1d4f5=006c;1d4f6=006d;1d4f7=006e;1d4f8=006f;1d4f9=0070;1d4fa=0071;1d4fb=0072;1d4fc=0073;1d4fd=0074;1d4fe=0075;1d4ff=0076;1d500=0077;1d501=0078;1d502=0079;1d503=007a;1d504=0061;1d505=0062;1d507=0064;1d508=0065;1d509=0066;1d50a=0067;1d50d=006a;1d50e=006b;1d50f=006c;1d510=006d;1d511=006e;1d512=006f;1d513=0070;1d514=0071;1d516=0073;1d517=0074;1d518=0075;1d519=0076;1d51a=0077;1d51b=0078;1d51c=0079;1d51e=0061;1d51f=0062",
|
|
803
|
+
";1d520=0063;1d521=0064;1d522=0065;1d523=0066;1d524=0067;1d525=0068;1d526=0069;1d527=006a;1d528=006b;1d529=006c;1d52a=006d;1d52b=006e;1d52c=006f;1d52d=0070;1d52e=0071;1d52f=0072;1d530=0073;1d531=0074;1d532=0075;1d533=0076;1d534=0077;1d535=0078;1d536=0079;1d537=007a;1d538=0061;1d539=0062;1d53b=0064;1d53c=0065;1d53d=0066;1d53e=0067;1d540=0069;1d541=006a;1d542=006b;1d543=006c;1d544=006d;1d546=006f;1d54a=0073;1d54b=0074;1d54c=0075;1d54d=0076;1d54e=0077;1d54f=0078;1d550=0079;1d552=0061;1d553=0062;1d554=0063;1d555=0064;1d556=0065;1d557=0066;1d558=0067;1d559=0068;1d55a=0069;1d55b=006a;1d55c=006b;1d55d=006c;1d55e=006d;1d55f=006e;1d560=006f;1d561=0070;1d562=0071;1d563=0072;1d564=0073;1d565=0074;1d566=0075;1d567=0076;1d568=0077;1d569=0078;1d56a=0079;1d56b=007a;1d56c=0061;1d56d=0062;1d56e=0063;1d56f=0064;1d570=0065;1d571=0066;1d572=0067;1d573=0068;1d574=0069;1d575=006a;1d576=006b;1d577=006c;1d578=006d;1d579=006e;1d57a=006f;1d57b=0070;1d57c=0071;1d57d=0072;1d57e=0073;1d57f=0074;1d580=0075;1d581=007",
|
|
804
|
+
"6;1d582=0077;1d583=0078;1d584=0079;1d585=007a;1d586=0061;1d587=0062;1d588=0063;1d589=0064;1d58a=0065;1d58b=0066;1d58c=0067;1d58d=0068;1d58e=0069;1d58f=006a;1d590=006b;1d591=006c;1d592=006d;1d593=006e;1d594=006f;1d595=0070;1d596=0071;1d597=0072;1d598=0073;1d599=0074;1d59a=0075;1d59b=0076;1d59c=0077;1d59d=0078;1d59e=0079;1d59f=007a;1d5a0=0061;1d5a1=0062;1d5a2=0063;1d5a3=0064;1d5a4=0065;1d5a5=0066;1d5a6=0067;1d5a7=0068;1d5a8=0069;1d5a9=006a;1d5aa=006b;1d5ab=006c;1d5ac=006d;1d5ad=006e;1d5ae=006f;1d5af=0070;1d5b0=0071;1d5b1=0072;1d5b2=0073;1d5b3=0074;1d5b4=0075;1d5b5=0076;1d5b6=0077;1d5b7=0078;1d5b8=0079;1d5b9=007a;1d5ba=0061;1d5bb=0062;1d5bc=0063;1d5bd=0064;1d5be=0065;1d5bf=0066;1d5c0=0067;1d5c1=0068;1d5c2=0069;1d5c3=006a;1d5c4=006b;1d5c5=006c;1d5c6=006d;1d5c7=006e;1d5c8=006f;1d5c9=0070;1d5ca=0071;1d5cb=0072;1d5cc=0073;1d5cd=0074;1d5ce=0075;1d5cf=0076;1d5d0=0077;1d5d1=0078;1d5d2=0079;1d5d3=007a;1d5d4=0061;1d5d5=0062;1d5d6=0063;1d5d7=0064;1d5d8=0065;1d5d9=0066;1d5da=0067;1d5db=0068;1d5dc=00",
|
|
805
|
+
"69;1d5dd=006a;1d5de=006b;1d5df=006c;1d5e0=006d;1d5e1=006e;1d5e2=006f;1d5e3=0070;1d5e4=0071;1d5e5=0072;1d5e6=0073;1d5e7=0074;1d5e8=0075;1d5e9=0076;1d5ea=0077;1d5eb=0078;1d5ec=0079;1d5ed=007a;1d5ee=0061;1d5ef=0062;1d5f0=0063;1d5f1=0064;1d5f2=0065;1d5f3=0066;1d5f4=0067;1d5f5=0068;1d5f6=0069;1d5f7=006a;1d5f8=006b;1d5f9=006c;1d5fa=006d;1d5fb=006e;1d5fc=006f;1d5fd=0070;1d5fe=0071;1d5ff=0072;1d600=0073;1d601=0074;1d602=0075;1d603=0076;1d604=0077;1d605=0078;1d606=0079;1d607=007a;1d608=0061;1d609=0062;1d60a=0063;1d60b=0064;1d60c=0065;1d60d=0066;1d60e=0067;1d60f=0068;1d610=0069;1d611=006a;1d612=006b;1d613=006c;1d614=006d;1d615=006e;1d616=006f;1d617=0070;1d618=0071;1d619=0072;1d61a=0073;1d61b=0074;1d61c=0075;1d61d=0076;1d61e=0077;1d61f=0078;1d620=0079;1d621=007a;1d622=0061;1d623=0062;1d624=0063;1d625=0064;1d626=0065;1d627=0066;1d628=0067;1d629=0068;1d62a=0069;1d62b=006a;1d62c=006b;1d62d=006c;1d62e=006d;1d62f=006e;1d630=006f;1d631=0070;1d632=0071;1d633=0072;1d634=0073;1d635=0074;1d636=0075;1d637=0",
|
|
806
|
+
"076;1d638=0077;1d639=0078;1d63a=0079;1d63b=007a;1d63c=0061;1d63d=0062;1d63e=0063;1d63f=0064;1d640=0065;1d641=0066;1d642=0067;1d643=0068;1d644=0069;1d645=006a;1d646=006b;1d647=006c;1d648=006d;1d649=006e;1d64a=006f;1d64b=0070;1d64c=0071;1d64d=0072;1d64e=0073;1d64f=0074;1d650=0075;1d651=0076;1d652=0077;1d653=0078;1d654=0079;1d655=007a;1d656=0061;1d657=0062;1d658=0063;1d659=0064;1d65a=0065;1d65b=0066;1d65c=0067;1d65d=0068;1d65e=0069;1d65f=006a;1d660=006b;1d661=006c;1d662=006d;1d663=006e;1d664=006f;1d665=0070;1d666=0071;1d667=0072;1d668=0073;1d669=0074;1d66a=0075;1d66b=0076;1d66c=0077;1d66d=0078;1d66e=0079;1d66f=007a;1d670=0061;1d671=0062;1d672=0063;1d673=0064;1d674=0065;1d675=0066;1d676=0067;1d677=0068;1d678=0069;1d679=006a;1d67a=006b;1d67b=006c;1d67c=006d;1d67d=006e;1d67e=006f;1d67f=0070;1d680=0071;1d681=0072;1d682=0073;1d683=0074;1d684=0075;1d685=0076;1d686=0077;1d687=0078;1d688=0079;1d689=007a;1d68a=0061;1d68b=0062;1d68c=0063;1d68d=0064;1d68e=0065;1d68f=0066;1d690=0067;1d691=0068;1d692=",
|
|
807
|
+
"0069;1d693=006a;1d694=006b;1d695=006c;1d696=006d;1d697=006e;1d698=006f;1d699=0070;1d69a=0071;1d69b=0072;1d69c=0073;1d69d=0074;1d69e=0075;1d69f=0076;1d6a0=0077;1d6a1=0078;1d6a2=0079;1d6a3=007a;1d6a4=0131;1d6a5=0237;1d6a8=03b1;1d6a9=03b2;1d6aa=03b3;1d6ab=03b4;1d6ac=03b5;1d6ad=03b6;1d6ae=03b7;1d6af=03b8;1d6b0=03b9;1d6b1=03ba;1d6b2=03bb;1d6b3=03bc;1d6b4=03bd;1d6b5=03be;1d6b6=03bf;1d6b7=03c0;1d6b8=03c1;1d6b9=03b8;1d6ba=03c3;1d6bb=03c4;1d6bc=03c5;1d6bd=03c6;1d6be=03c7;1d6bf=03c8;1d6c0=03c9;1d6c1=2207;1d6c2=03b1;1d6c3=03b2;1d6c4=03b3;1d6c5=03b4;1d6c6=03b5;1d6c7=03b6;1d6c8=03b7;1d6c9=03b8;1d6ca=03b9;1d6cb=03ba;1d6cc=03bb;1d6cd=03bc;1d6ce=03bd;1d6cf=03be;1d6d0=03bf;1d6d1=03c0;1d6d2=03c1;1d6d3..1d6d4=03c3;1d6d5=03c4;1d6d6=03c5;1d6d7=03c6;1d6d8=03c7;1d6d9=03c8;1d6da=03c9;1d6db=2202;1d6dc=03b5;1d6dd=03b8;1d6de=03ba;1d6df=03c6;1d6e0=03c1;1d6e1=03c0;1d6e2=03b1;1d6e3=03b2;1d6e4=03b3;1d6e5=03b4;1d6e6=03b5;1d6e7=03b6;1d6e8=03b7;1d6e9=03b8;1d6ea=03b9;1d6eb=03ba;1d6ec=03bb;1d6ed=03bc;1d6ee=03bd;1d6ef=03b",
|
|
808
|
+
"e;1d6f0=03bf;1d6f1=03c0;1d6f2=03c1;1d6f3=03b8;1d6f4=03c3;1d6f5=03c4;1d6f6=03c5;1d6f7=03c6;1d6f8=03c7;1d6f9=03c8;1d6fa=03c9;1d6fb=2207;1d6fc=03b1;1d6fd=03b2;1d6fe=03b3;1d6ff=03b4;1d700=03b5;1d701=03b6;1d702=03b7;1d703=03b8;1d704=03b9;1d705=03ba;1d706=03bb;1d707=03bc;1d708=03bd;1d709=03be;1d70a=03bf;1d70b=03c0;1d70c=03c1;1d70d..1d70e=03c3;1d70f=03c4;1d710=03c5;1d711=03c6;1d712=03c7;1d713=03c8;1d714=03c9;1d715=2202;1d716=03b5;1d717=03b8;1d718=03ba;1d719=03c6;1d71a=03c1;1d71b=03c0;1d71c=03b1;1d71d=03b2;1d71e=03b3;1d71f=03b4;1d720=03b5;1d721=03b6;1d722=03b7;1d723=03b8;1d724=03b9;1d725=03ba;1d726=03bb;1d727=03bc;1d728=03bd;1d729=03be;1d72a=03bf;1d72b=03c0;1d72c=03c1;1d72d=03b8;1d72e=03c3;1d72f=03c4;1d730=03c5;1d731=03c6;1d732=03c7;1d733=03c8;1d734=03c9;1d735=2207;1d736=03b1;1d737=03b2;1d738=03b3;1d739=03b4;1d73a=03b5;1d73b=03b6;1d73c=03b7;1d73d=03b8;1d73e=03b9;1d73f=03ba;1d740=03bb;1d741=03bc;1d742=03bd;1d743=03be;1d744=03bf;1d745=03c0;1d746=03c1;1d747..1d748=03c3;1d749=03c4;1d74a=03c5;1d74b",
|
|
809
|
+
"=03c6;1d74c=03c7;1d74d=03c8;1d74e=03c9;1d74f=2202;1d750=03b5;1d751=03b8;1d752=03ba;1d753=03c6;1d754=03c1;1d755=03c0;1d756=03b1;1d757=03b2;1d758=03b3;1d759=03b4;1d75a=03b5;1d75b=03b6;1d75c=03b7;1d75d=03b8;1d75e=03b9;1d75f=03ba;1d760=03bb;1d761=03bc;1d762=03bd;1d763=03be;1d764=03bf;1d765=03c0;1d766=03c1;1d767=03b8;1d768=03c3;1d769=03c4;1d76a=03c5;1d76b=03c6;1d76c=03c7;1d76d=03c8;1d76e=03c9;1d76f=2207;1d770=03b1;1d771=03b2;1d772=03b3;1d773=03b4;1d774=03b5;1d775=03b6;1d776=03b7;1d777=03b8;1d778=03b9;1d779=03ba;1d77a=03bb;1d77b=03bc;1d77c=03bd;1d77d=03be;1d77e=03bf;1d77f=03c0;1d780=03c1;1d781..1d782=03c3;1d783=03c4;1d784=03c5;1d785=03c6;1d786=03c7;1d787=03c8;1d788=03c9;1d789=2202;1d78a=03b5;1d78b=03b8;1d78c=03ba;1d78d=03c6;1d78e=03c1;1d78f=03c0;1d790=03b1;1d791=03b2;1d792=03b3;1d793=03b4;1d794=03b5;1d795=03b6;1d796=03b7;1d797=03b8;1d798=03b9;1d799=03ba;1d79a=03bb;1d79b=03bc;1d79c=03bd;1d79d=03be;1d79e=03bf;1d79f=03c0;1d7a0=03c1;1d7a1=03b8;1d7a2=03c3;1d7a3=03c4;1d7a4=03c5;1d7a5=03c6;1d7a6=03",
|
|
810
|
+
"c7;1d7a7=03c8;1d7a8=03c9;1d7a9=2207;1d7aa=03b1;1d7ab=03b2;1d7ac=03b3;1d7ad=03b4;1d7ae=03b5;1d7af=03b6;1d7b0=03b7;1d7b1=03b8;1d7b2=03b9;1d7b3=03ba;1d7b4=03bb;1d7b5=03bc;1d7b6=03bd;1d7b7=03be;1d7b8=03bf;1d7b9=03c0;1d7ba=03c1;1d7bb..1d7bc=03c3;1d7bd=03c4;1d7be=03c5;1d7bf=03c6;1d7c0=03c7;1d7c1=03c8;1d7c2=03c9;1d7c3=2202;1d7c4=03b5;1d7c5=03b8;1d7c6=03ba;1d7c7=03c6;1d7c8=03c1;1d7c9=03c0;1d7ca..1d7cb=03dd;1d7ce=0030;1d7cf=0031;1d7d0=0032;1d7d1=0033;1d7d2=0034;1d7d3=0035;1d7d4=0036;1d7d5=0037;1d7d6=0038;1d7d7=0039;1d7d8=0030;1d7d9=0031;1d7da=0032;1d7db=0033;1d7dc=0034;1d7dd=0035;1d7de=0036;1d7df=0037;1d7e0=0038;1d7e1=0039;1d7e2=0030;1d7e3=0031;1d7e4=0032;1d7e5=0033;1d7e6=0034;1d7e7=0035;1d7e8=0036;1d7e9=0037;1d7ea=0038;1d7eb=0039;1d7ec=0030;1d7ed=0031;1d7ee=0032;1d7ef=0033;1d7f0=0034;1d7f1=0035;1d7f2=0036;1d7f3=0037;1d7f4=0038;1d7f5=0039;1d7f6=0030;1d7f7=0031;1d7f8=0032;1d7f9=0033;1d7fa=0034;1d7fb=0035;1d7fc=0036;1d7fd=0037;1d7fe=0038;1d7ff=0039;1e030=0430;1e031=0431;1e032=0432;1e033=0433;1e03",
|
|
811
|
+
"4=0434;1e035=0435;1e036=0436;1e037=0437;1e038=0438;1e039=043a;1e03a=043b;1e03b=043c;1e03c=043e;1e03d=043f;1e03e=0440;1e03f=0441;1e040=0442;1e041=0443;1e042=0444;1e043=0445;1e044=0446;1e045=0447;1e046=0448;1e047=044b;1e048=044d;1e049=044e;1e04a=a689;1e04b=04d9;1e04c=0456;1e04d=0458;1e04e=04e9;1e04f=04af;1e050=04cf;1e051=0430;1e052=0431;1e053=0432;1e054=0433;1e055=0434;1e056=0435;1e057=0436;1e058=0437;1e059=0438;1e05a=043a;1e05b=043b;1e05c=043e;1e05d=043f;1e05e=0441;1e05f=0443;1e060=0444;1e061=0445;1e062=0446;1e063=0447;1e064=0448;1e065=044a;1e066=044b;1e067=0491;1e068=0456;1e069=0455;1e06a=045f;1e06b=04ab;1e06c=a651;1e06d=04b1;1e900=1e922;1e901=1e923;1e902=1e924;1e903=1e925;1e904=1e926;1e905=1e927;1e906=1e928;1e907=1e929;1e908=1e92a;1e909=1e92b;1e90a=1e92c;1e90b=1e92d;1e90c=1e92e;1e90d=1e92f;1e90e=1e930;1e90f=1e931;1e910=1e932;1e911=1e933;1e912=1e934;1e913=1e935;1e914=1e936;1e915=1e937;1e916=1e938;1e917=1e939;1e918=1e93a;1e919=1e93b;1e91a=1e93c;1e91b=1e93d;1e91c=1e93e;1e91d=1e93f;1e91e=",
|
|
812
|
+
"1e940;1e91f=1e941;1e920=1e942;1e921=1e943;1ee00=0627;1ee01=0628;1ee02=062c;1ee03=062f;1ee05=0648;1ee06=0632;1ee07=062d;1ee08=0637;1ee09=064a;1ee0a=0643;1ee0b=0644;1ee0c=0645;1ee0d=0646;1ee0e=0633;1ee0f=0639;1ee10=0641;1ee11=0635;1ee12=0642;1ee13=0631;1ee14=0634;1ee15=062a;1ee16=062b;1ee17=062e;1ee18=0630;1ee19=0636;1ee1a=0638;1ee1b=063a;1ee1c=066e;1ee1d=06ba;1ee1e=06a1;1ee1f=066f;1ee21=0628;1ee22=062c;1ee24=0647;1ee27=062d;1ee29=064a;1ee2a=0643;1ee2b=0644;1ee2c=0645;1ee2d=0646;1ee2e=0633;1ee2f=0639;1ee30=0641;1ee31=0635;1ee32=0642;1ee34=0634;1ee35=062a;1ee36=062b;1ee37=062e;1ee39=0636;1ee3b=063a;1ee42=062c;1ee47=062d;1ee49=064a;1ee4b=0644;1ee4d=0646;1ee4e=0633;1ee4f=0639;1ee51=0635;1ee52=0642;1ee54=0634;1ee57=062e;1ee59=0636;1ee5b=063a;1ee5d=06ba;1ee5f=066f;1ee61=0628;1ee62=062c;1ee64=0647;1ee67=062d;1ee68=0637;1ee69=064a;1ee6a=0643;1ee6c=0645;1ee6d=0646;1ee6e=0633;1ee6f=0639;1ee70=0641;1ee71=0635;1ee72=0642;1ee74=0634;1ee75=062a;1ee76=062b;1ee77=062e;1ee79=0636;1ee7a=0638;1ee7b=063a;1",
|
|
813
|
+
"ee7c=066e;1ee7e=06a1;1ee80=0627;1ee81=0628;1ee82=062c;1ee83=062f;1ee84=0647;1ee85=0648;1ee86=0632;1ee87=062d;1ee88=0637;1ee89=064a;1ee8b=0644;1ee8c=0645;1ee8d=0646;1ee8e=0633;1ee8f=0639;1ee90=0641;1ee91=0635;1ee92=0642;1ee93=0631;1ee94=0634;1ee95=062a;1ee96=062b;1ee97=062e;1ee98=0630;1ee99=0636;1ee9a=0638;1ee9b=063a;1eea1=0628;1eea2=062c;1eea3=062f;1eea5=0648;1eea6=0632;1eea7=062d;1eea8=0637;1eea9=064a;1eeab=0644;1eeac=0645;1eead=0646;1eeae=0633;1eeaf=0639;1eeb0=0641;1eeb1=0635;1eeb2=0642;1eeb3=0631;1eeb4=0634;1eeb5=062a;1eeb6=062b;1eeb7=062e;1eeb8=0630;1eeb9=0636;1eeba=0638;1eebb=063a;1f100=0030.002e;1f101=0030.002c;1f102=0031.002c;1f103=0032.002c;1f104=0033.002c;1f105=0034.002c;1f106=0035.002c;1f107=0036.002c;1f108=0037.002c;1f109=0038.002c;1f10a=0039.002c;1f110=0028.0061.0029;1f111=0028.0062.0029;1f112=0028.0063.0029;1f113=0028.0064.0029;1f114=0028.0065.0029;1f115=0028.0066.0029;1f116=0028.0067.0029;1f117=0028.0068.0029;1f118=0028.0069.0029;1f119=0028.006a.0029;1f11a=0028.006b.0029;",
|
|
814
|
+
"1f11b=0028.006c.0029;1f11c=0028.006d.0029;1f11d=0028.006e.0029;1f11e=0028.006f.0029;1f11f=0028.0070.0029;1f120=0028.0071.0029;1f121=0028.0072.0029;1f122=0028.0073.0029;1f123=0028.0074.0029;1f124=0028.0075.0029;1f125=0028.0076.0029;1f126=0028.0077.0029;1f127=0028.0078.0029;1f128=0028.0079.0029;1f129=0028.007a.0029;1f12a=3014.0073.3015;1f12b=0063;1f12c=0072;1f12d=0063.0064;1f12e=0077.007a;1f130=0061;1f131=0062;1f132=0063;1f133=0064;1f134=0065;1f135=0066;1f136=0067;1f137=0068;1f138=0069;1f139=006a;1f13a=006b;1f13b=006c;1f13c=006d;1f13d=006e;1f13e=006f;1f13f=0070;1f140=0071;1f141=0072;1f142=0073;1f143=0074;1f144=0075;1f145=0076;1f146=0077;1f147=0078;1f148=0079;1f149=007a;1f14a=0068.0076;1f14b=006d.0076;1f14c=0073.0064;1f14d=0073.0073;1f14e=0070.0070.0076;1f14f=0077.0063;1f16a=006d.0063;1f16b=006d.0064;1f16c=006d.0072;1f190=0064.006a;1f200=307b.304b;1f201=30b3.30b3;1f202=30b5;1f210=624b;1f211=5b57;1f212=53cc;1f213=30c7;1f214=4e8c;1f215=591a;1f216=89e3;1f217=5929;1f218=4ea4;1f219=6620;1f21a=",
|
|
815
|
+
"7121;1f21b=6599;1f21c=524d;1f21d=5f8c;1f21e=518d;1f21f=65b0;1f220=521d;1f221=7d42;1f222=751f;1f223=8ca9;1f224=58f0;1f225=5439;1f226=6f14;1f227=6295;1f228=6355;1f229=4e00;1f22a=4e09;1f22b=904a;1f22c=5de6;1f22d=4e2d;1f22e=53f3;1f22f=6307;1f230=8d70;1f231=6253;1f232=7981;1f233=7a7a;1f234=5408;1f235=6e80;1f236=6709;1f237=6708;1f238=7533;1f239=5272;1f23a=55b6;1f23b=914d;1f240=3014.672c.3015;1f241=3014.4e09.3015;1f242=3014.4e8c.3015;1f243=3014.5b89.3015;1f244=3014.70b9.3015;1f245=3014.6253.3015;1f246=3014.76d7.3015;1f247=3014.52dd.3015;1f248=3014.6557.3015;1f250=5f97;1f251=53ef;1fbf0=0030;1fbf1=0031;1fbf2=0032;1fbf3=0033;1fbf4=0034;1fbf5=0035;1fbf6=0036;1fbf7=0037;1fbf8=0038;1fbf9=0039;2f800=4e3d;2f801=4e38;2f802=4e41;2f803=20122;2f804=4f60;2f805=4fae;2f806=4fbb;2f807=5002;2f808=507a;2f809=5099;2f80a=50e7;2f80b=50cf;2f80c=349e;2f80d=2063a;2f80e=514d;2f80f=5154;2f810=5164;2f811=5177;2f812=2051c;2f813=34b9;2f814=5167;2f815=518d;2f816=2054b;2f817=5197;2f818=51a4;2f819=4ecc;2f81a=51ac;2f81b=51b5",
|
|
816
|
+
";2f81c=291df;2f81d=51f5;2f81e=5203;2f81f=34df;2f820=523b;2f821=5246;2f822=5272;2f823=5277;2f824=3515;2f825=52c7;2f826=52c9;2f827=52e4;2f828=52fa;2f829=5305;2f82a=5306;2f82b=5317;2f82c=5349;2f82d=5351;2f82e=535a;2f82f=5373;2f830=537d;2f831..2f833=537f;2f834=20a2c;2f835=7070;2f836=53ca;2f837=53df;2f838=20b63;2f839=53eb;2f83a=53f1;2f83b=5406;2f83c=549e;2f83d=5438;2f83e=5448;2f83f=5468;2f840=54a2;2f841=54f6;2f842=5510;2f843=5553;2f844=5563;2f845..2f846=5584;2f847=5599;2f848=55ab;2f849=55b3;2f84a=55c2;2f84b=5716;2f84c=5606;2f84d=5717;2f84e=5651;2f84f=5674;2f850=5207;2f851=58ee;2f852=57ce;2f853=57f4;2f854=580d;2f855=578b;2f856=5832;2f857=5831;2f858=58ac;2f859=214e4;2f85a=58f2;2f85b=58f7;2f85c=5906;2f85d=591a;2f85e=5922;2f85f=5962;2f860=216a8;2f861=216ea;2f862=59ec;2f863=5a1b;2f864=5a27;2f865=59d8;2f866=5a66;2f867=36ee;2f868=36fc;2f869=5b08;2f86a..2f86b=5b3e;2f86c=219c8;2f86d=5bc3;2f86e=5bd8;2f86f=5be7;2f870=5bf3;2f871=21b18;2f872=5bff;2f873=5c06;2f874=5f53;2f875=5c22;2f876=3781;2f877=5c60;2f",
|
|
817
|
+
"878=5c6e;2f879=5cc0;2f87a=5c8d;2f87b=21de4;2f87c=5d43;2f87d=21de6;2f87e=5d6e;2f87f=5d6b;2f880=5d7c;2f881=5de1;2f882=5de2;2f883=382f;2f884=5dfd;2f885=5e28;2f886=5e3d;2f887=5e69;2f888=3862;2f889=22183;2f88a=387c;2f88b=5eb0;2f88c=5eb3;2f88d=5eb6;2f88e=5eca;2f88f=2a392;2f890=5efe;2f891..2f892=22331;2f893=8201;2f894..2f895=5f22;2f896=38c7;2f897=232b8;2f898=261da;2f899=5f62;2f89a=5f6b;2f89b=38e3;2f89c=5f9a;2f89d=5fcd;2f89e=5fd7;2f89f=5ff9;2f8a0=6081;2f8a1=393a;2f8a2=391c;2f8a3=6094;2f8a4=226d4;2f8a5=60c7;2f8a6=6148;2f8a7=614c;2f8a8=614e;2f8a9=614c;2f8aa=617a;2f8ab=618e;2f8ac=61b2;2f8ad=61a4;2f8ae=61af;2f8af=61de;2f8b0=61f2;2f8b1=61f6;2f8b2=6210;2f8b3=621b;2f8b4=625d;2f8b5=62b1;2f8b6=62d4;2f8b7=6350;2f8b8=22b0c;2f8b9=633d;2f8ba=62fc;2f8bb=6368;2f8bc=6383;2f8bd=63e4;2f8be=22bf1;2f8bf=6422;2f8c0=63c5;2f8c1=63a9;2f8c2=3a2e;2f8c3=6469;2f8c4=647e;2f8c5=649d;2f8c6=6477;2f8c7=3a6c;2f8c8=654f;2f8c9=656c;2f8ca=2300a;2f8cb=65e3;2f8cc=66f8;2f8cd=6649;2f8ce=3b19;2f8cf=6691;2f8d0=3b08;2f8d1=3ae4;2f8d2=519",
|
|
818
|
+
"2;2f8d3=5195;2f8d4=6700;2f8d5=669c;2f8d6=80ad;2f8d7=43d9;2f8d8=6717;2f8d9=671b;2f8da=6721;2f8db=675e;2f8dc=6753;2f8dd=233c3;2f8de=3b49;2f8df=67fa;2f8e0=6785;2f8e1=6852;2f8e2=6885;2f8e3=2346d;2f8e4=688e;2f8e5=681f;2f8e6=6914;2f8e7=3b9d;2f8e8=6942;2f8e9=69a3;2f8ea=69ea;2f8eb=6aa8;2f8ec=236a3;2f8ed=6adb;2f8ee=3c18;2f8ef=6b21;2f8f0=238a7;2f8f1=6b54;2f8f2=3c4e;2f8f3=6b72;2f8f4=6b9f;2f8f5=6bba;2f8f6=6bbb;2f8f7=23a8d;2f8f8=21d0b;2f8f9=23afa;2f8fa=6c4e;2f8fb=23cbc;2f8fc=6cbf;2f8fd=6ccd;2f8fe=6c67;2f8ff=6d16;2f900=6d3e;2f901=6d77;2f902=6d41;2f903=6d69;2f904=6d78;2f905=6d85;2f906=23d1e;2f907=6d34;2f908=6e2f;2f909=6e6e;2f90a=3d33;2f90b=6ecb;2f90c=6ec7;2f90d=23ed1;2f90e=6df9;2f90f=6f6e;2f910=23f5e;2f911=23f8e;2f912=6fc6;2f913=7039;2f914=701e;2f915=701b;2f916=3d96;2f917=704a;2f918=707d;2f919=7077;2f91a=70ad;2f91b=20525;2f91c=7145;2f91d=24263;2f91e=719c;2f91f=243ab;2f920=7228;2f921=7235;2f922=7250;2f923=24608;2f924=7280;2f925=7295;2f926=24735;2f927=24814;2f928=737a;2f929=738b;2f92a=3eac;2f92b=73a5;2",
|
|
819
|
+
"f92c..2f92d=3eb8;2f92e=7447;2f92f=745c;2f930=7471;2f931=7485;2f932=74ca;2f933=3f1b;2f934=7524;2f935=24c36;2f936=753e;2f937=24c92;2f938=7570;2f939=2219f;2f93a=7610;2f93b=24fa1;2f93c=24fb8;2f93d=25044;2f93e=3ffc;2f93f=4008;2f940=76f4;2f941=250f3;2f942=250f2;2f943=25119;2f944=25133;2f945=771e;2f946..2f947=771f;2f948=774a;2f949=4039;2f94a=778b;2f94b=4046;2f94c=4096;2f94d=2541d;2f94e=784e;2f94f=788c;2f950=78cc;2f951=40e3;2f952=25626;2f953=7956;2f954=2569a;2f955=256c5;2f956=798f;2f957=79eb;2f958=412f;2f959=7a40;2f95a=7a4a;2f95b=7a4f;2f95c=2597c;2f95d..2f95e=25aa7;2f95f=7aee;2f960=4202;2f961=25bab;2f962=7bc6;2f963=7bc9;2f964=4227;2f965=25c80;2f966=7cd2;2f967=42a0;2f968=7ce8;2f969=7ce3;2f96a=7d00;2f96b=25f86;2f96c=7d63;2f96d=4301;2f96e=7dc7;2f96f=7e02;2f970=7e45;2f971=4334;2f972=26228;2f973=26247;2f974=4359;2f975=262d9;2f976=7f7a;2f977=2633e;2f978=7f95;2f979=7ffa;2f97a=8005;2f97b=264da;2f97c=26523;2f97d=8060;2f97e=265a8;2f97f=8070;2f980=2335f;2f981=43d5;2f982=80b2;2f983=8103;2f984=440b;2f985=8",
|
|
820
|
+
"13e;2f986=5ab5;2f987=267a7;2f988=267b5;2f989=23393;2f98a=2339c;2f98b=8201;2f98c=8204;2f98d=8f9e;2f98e=446b;2f98f=8291;2f990=828b;2f991=829d;2f992=52b3;2f993=82b1;2f994=82b3;2f995=82bd;2f996=82e6;2f997=26b3c;2f998=82e5;2f999=831d;2f99a=8363;2f99b=83ad;2f99c=8323;2f99d=83bd;2f99e=83e7;2f99f=8457;2f9a0=8353;2f9a1=83ca;2f9a2=83cc;2f9a3=83dc;2f9a4=26c36;2f9a5=26d6b;2f9a6=26cd5;2f9a7=452b;2f9a8=84f1;2f9a9=84f3;2f9aa=8516;2f9ab=273ca;2f9ac=8564;2f9ad=26f2c;2f9ae=455d;2f9af=4561;2f9b0=26fb1;2f9b1=270d2;2f9b2=456b;2f9b3=8650;2f9b4=865c;2f9b5=8667;2f9b6=8669;2f9b7=86a9;2f9b8=8688;2f9b9=870e;2f9ba=86e2;2f9bb=8779;2f9bc=8728;2f9bd=876b;2f9be=8786;2f9bf=45d7;2f9c0=87e1;2f9c1=8801;2f9c2=45f9;2f9c3=8860;2f9c4=8863;2f9c5=27667;2f9c6=88d7;2f9c7=88de;2f9c8=4635;2f9c9=88fa;2f9ca=34bb;2f9cb=278ae;2f9cc=27966;2f9cd=46be;2f9ce=46c7;2f9cf=8aa0;2f9d0=8aed;2f9d1=8b8a;2f9d2=8c55;2f9d3=27ca8;2f9d4=8cab;2f9d5=8cc1;2f9d6=8d1b;2f9d7=8d77;2f9d8=27f2f;2f9d9=20804;2f9da=8dcb;2f9db=8dbc;2f9dc=8df0;2f9dd=208de;2f9de=8ed",
|
|
821
|
+
"4;2f9df=8f38;2f9e0=285d2;2f9e1=285ed;2f9e2=9094;2f9e3=90f1;2f9e4=9111;2f9e5=2872e;2f9e6=911b;2f9e7=9238;2f9e8=92d7;2f9e9=92d8;2f9ea=927c;2f9eb=93f9;2f9ec=9415;2f9ed=28bfa;2f9ee=958b;2f9ef=4995;2f9f0=95b7;2f9f1=28d77;2f9f2=49e6;2f9f3=96c3;2f9f4=5db2;2f9f5=9723;2f9f6=29145;2f9f7=2921a;2f9f8=4a6e;2f9f9=4a76;2f9fa=97e0;2f9fb=2940a;2f9fc=4ab2;2f9fd=29496;2f9fe..2f9ff=980b;2fa00=9829;2fa01=295b6;2fa02=98e2;2fa03=4b33;2fa04=9929;2fa05=99a7;2fa06=99c2;2fa07=99fe;2fa08=4bce;2fa09=29b30;2fa0a=9b12;2fa0b=9c40;2fa0c=9cfd;2fa0d=4cce;2fa0e=4ced;2fa0f=9d67;2fa10=2a0ce;2fa11=4cf8;2fa12=2a105;2fa13=2a20e;2fa14=2a291;2fa15=9ebb;2fa16=4d56;2fa17=9ef9;2fa18=9efe;2fa19=9f05;2fa1a=9f0f;2fa1b=9f16;2fa1c=9f3b;2fa1d=2a600;e0000=;e0001=;e0002..e001f=;e0020..e007f=;e0080..e00ff=;e0100..e01ef=;e01f0..e0fff="
|
|
822
|
+
].join("");
|
|
823
|
+
//#endregion
|
|
824
|
+
//#region src/assets/path.ts
|
|
825
|
+
const encoder$1 = new TextEncoder();
|
|
826
|
+
const decoder$1 = new TextDecoder("utf-8", { fatal: true });
|
|
827
|
+
const FORBIDDEN_ASCII = /[\\%<>:"|?*]/u;
|
|
828
|
+
const SEPARATOR_ALIASES = /[\u2044\u2215\u29f5\u29f8\u29f9\ufe68\uff0f\uff3c]/u;
|
|
829
|
+
const DOS_STEM = /^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9]|lpt[1-9])$/u;
|
|
830
|
+
const NFKC_CASEFOLD_V17 = parseNfkcCasefoldData(NFKC_CASEFOLD_V17_DATA);
|
|
831
|
+
function validateTopikPath(input, options = {}) {
|
|
832
|
+
let path;
|
|
833
|
+
try {
|
|
834
|
+
path = typeof input === "string" ? input : decoder$1.decode(input);
|
|
835
|
+
} catch {
|
|
836
|
+
return pathFailure("", "invalid_utf8", "Path is not strict UTF-8");
|
|
837
|
+
}
|
|
838
|
+
if (!validPathCapabilities(options.capabilities)) return pathFailure(path, "capability_invalid", "Path capabilities must stay within immutable portable maxima");
|
|
839
|
+
if (path.length === 0 || path.includes("\0") || path.includes("")) return pathFailure(path, "forbidden_character", "Path is empty or contains a forbidden value");
|
|
840
|
+
if (path.startsWith("/") || path.startsWith("//") || /^[a-z]:/iu.test(path) || path.startsWith("\\\\") || /^[a-z][a-z0-9+.-]*:/iu.test(path)) return pathFailure(path, "absolute", "Path must be repository relative");
|
|
841
|
+
if (FORBIDDEN_ASCII.test(path)) return pathFailure(path, "forbidden_character", "Path contains a forbidden character");
|
|
842
|
+
if (SEPARATOR_ALIASES.test(path)) return pathFailure(path, "separator_alias", "Path contains a separator-confusable character");
|
|
843
|
+
if (containsTopikPathForbiddenCodePoint(path)) return pathFailure(path, "forbidden_character", "Path contains a forbidden Unicode value");
|
|
844
|
+
if (path.normalize("NFC") !== path) return pathFailure(path, "not_nfc", "Path must already be in NFC storage spelling");
|
|
845
|
+
const components = path.split("/");
|
|
846
|
+
if (components.some((component) => component.length === 0)) return pathFailure(path, "separator_alias", "Path contains an empty component");
|
|
847
|
+
if (components.some((component) => component === "." || component === "..")) return pathFailure(path, "dot_segment", "Path contains a dot segment");
|
|
848
|
+
const maxComponents = options.capabilities?.maxComponents ?? TOPIK_PATH_V1_DESCRIPTOR.maxComponents;
|
|
849
|
+
if (components.length > maxComponents) return pathFailure(path, "too_long", "Path has too many components");
|
|
850
|
+
const collisionComponents = [];
|
|
851
|
+
for (const component of components) {
|
|
852
|
+
if (component.startsWith(" ") || component.endsWith(" ") || component.endsWith(".")) return pathFailure(path, "forbidden_character", "Path component has unsafe edge spelling");
|
|
853
|
+
const maxComponentBytes = options.capabilities?.maxComponentUtf8Bytes ?? TOPIK_PATH_V1_DESCRIPTOR.maxComponentUtf8Bytes;
|
|
854
|
+
if (encoder$1.encode(component).byteLength > maxComponentBytes) return pathFailure(path, "too_long", "Path component exceeds the UTF-8 byte limit");
|
|
855
|
+
const collision = toNfkcCasefold(component);
|
|
856
|
+
if (collision.length === 0 || collision === "." || collision === ".." || collision.includes("/") || collision.includes("\\")) return pathFailure(path, "separator_alias", "Path component folds to an unsafe spelling");
|
|
857
|
+
const stem = collision.replace(/[ .]+$/u, "").split(".", 1)[0];
|
|
858
|
+
if (DOS_STEM.test(stem) || collision === ".git" || /^git~[1-9][0-9]*$/u.test(collision) || collision === ".topik") return pathFailure(path, "reserved_name", "Path contains a reserved component");
|
|
859
|
+
collisionComponents.push(collision);
|
|
860
|
+
}
|
|
861
|
+
const repositoryPath = options.bindingRoot ? `${options.bindingRoot}/${path}` : path;
|
|
862
|
+
const maxRepositoryBytes = options.capabilities?.maxRepositoryPathUtf8Bytes ?? TOPIK_PATH_V1_DESCRIPTOR.maxRepositoryPathUtf8Bytes;
|
|
863
|
+
if (encoder$1.encode(repositoryPath).byteLength > maxRepositoryBytes) return pathFailure(path, "too_long", "Complete repository path exceeds the UTF-8 byte limit");
|
|
864
|
+
return {
|
|
865
|
+
ok: true,
|
|
866
|
+
value: {
|
|
867
|
+
path,
|
|
868
|
+
collisionKey: collisionComponents.join("/"),
|
|
869
|
+
utf8Bytes: encoder$1.encode(path).length
|
|
870
|
+
},
|
|
871
|
+
diagnostics: []
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function containsTopikPathForbiddenCodePoint(value) {
|
|
875
|
+
for (const character of value) {
|
|
876
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
877
|
+
if (isTopikPathCodePointForbiddenV17(codePoint) || isTopikPathNormalizationSensitiveV17(codePoint)) return true;
|
|
878
|
+
}
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
function validPathCapabilities(capabilities) {
|
|
882
|
+
if (capabilities === void 0) return true;
|
|
883
|
+
return validOptionalLimit(capabilities.maxComponentUtf8Bytes, TOPIK_PATH_V1_DESCRIPTOR.maxComponentUtf8Bytes) && validOptionalLimit(capabilities.maxComponents, TOPIK_PATH_V1_DESCRIPTOR.maxComponents) && validOptionalLimit(capabilities.maxRepositoryPathUtf8Bytes, TOPIK_PATH_V1_DESCRIPTOR.maxRepositoryPathUtf8Bytes);
|
|
884
|
+
}
|
|
885
|
+
function validOptionalLimit(value, maximum) {
|
|
886
|
+
return value === void 0 || Number.isInteger(value) && value >= 1 && value <= maximum;
|
|
887
|
+
}
|
|
888
|
+
function computeTopikPathCollisionKey(path, options = {}) {
|
|
889
|
+
const result = validateTopikPath(path, options);
|
|
890
|
+
return result.ok ? {
|
|
891
|
+
ok: true,
|
|
892
|
+
value: result.value.collisionKey,
|
|
893
|
+
diagnostics: []
|
|
894
|
+
} : {
|
|
895
|
+
ok: false,
|
|
896
|
+
diagnostics: result.diagnostics
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function validateTopikPathSet(paths, options = {}) {
|
|
900
|
+
const byCollision = /* @__PURE__ */ new Map();
|
|
901
|
+
const diagnostics = [];
|
|
902
|
+
for (const path of paths) {
|
|
903
|
+
const result = validateTopikPath(path, options);
|
|
904
|
+
if (!result.ok) {
|
|
905
|
+
diagnostics.push(...result.diagnostics);
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const existing = byCollision.get(result.value.collisionKey);
|
|
909
|
+
if (existing !== void 0 && existing !== path) diagnostics.push(topikAssetDiagnostic("TOPIK_ASSET_PATH_COLLISION", "Paths have the same collision key", {
|
|
910
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
911
|
+
location: { path },
|
|
912
|
+
reason: "casefold_collision"
|
|
913
|
+
}));
|
|
914
|
+
else byCollision.set(result.value.collisionKey, path);
|
|
915
|
+
}
|
|
916
|
+
for (const child of byCollision.keys()) {
|
|
917
|
+
const components = child.split("/");
|
|
918
|
+
for (let count = 1; count < components.length; count++) {
|
|
919
|
+
if (!byCollision.has(components.slice(0, count).join("/"))) continue;
|
|
920
|
+
diagnostics.push(topikAssetDiagnostic("TOPIK_ASSET_PATH_COLLISION", "A portable file path cannot also be another file's parent", {
|
|
921
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
922
|
+
location: { path: byCollision.get(child) },
|
|
923
|
+
reason: "casefold_collision"
|
|
924
|
+
}));
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return diagnostics.length === 0 ? {
|
|
929
|
+
ok: true,
|
|
930
|
+
value: byCollision,
|
|
931
|
+
diagnostics: []
|
|
932
|
+
} : {
|
|
933
|
+
ok: false,
|
|
934
|
+
value: byCollision,
|
|
935
|
+
diagnostics
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/** Unicode 17 NFKC casefold for topik-path-v1-admitted input. */
|
|
939
|
+
function toNfkcCasefold(value) {
|
|
940
|
+
let mapped = "";
|
|
941
|
+
for (const character of value) mapped += NFKC_CASEFOLD_V17.get(character.codePointAt(0) ?? 0) ?? character;
|
|
942
|
+
return mapped.normalize("NFC");
|
|
943
|
+
}
|
|
944
|
+
function pathFailure(path, reason, message) {
|
|
945
|
+
return {
|
|
946
|
+
ok: false,
|
|
947
|
+
diagnostics: [topikAssetDiagnostic("TOPIK_ASSET_PATH_INVALID", message, {
|
|
948
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
949
|
+
location: { path: safePath(path) },
|
|
950
|
+
reason
|
|
951
|
+
})]
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function safePath(path) {
|
|
955
|
+
return JSON.stringify(path).slice(1, -1);
|
|
956
|
+
}
|
|
957
|
+
function parseNfkcCasefoldData(source) {
|
|
958
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
959
|
+
const records = source.split("\n").filter((line) => !line.startsWith("#")).join("").split(";").filter(Boolean);
|
|
960
|
+
for (const record of records) {
|
|
961
|
+
const [range, encoded = ""] = record.split("=");
|
|
962
|
+
const [startValue, endValue = startValue] = range.split("..");
|
|
963
|
+
const start = Number.parseInt(startValue, 16);
|
|
964
|
+
const end = Number.parseInt(endValue, 16);
|
|
965
|
+
const replacement = encoded.split(".").filter(Boolean).map((value) => String.fromCodePoint(Number.parseInt(value, 16))).join("");
|
|
966
|
+
for (let codePoint = start; codePoint <= end; codePoint++) mapping.set(codePoint, replacement);
|
|
967
|
+
}
|
|
968
|
+
return mapping;
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/assets/asset.ts
|
|
972
|
+
const encoder = new TextEncoder();
|
|
973
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
974
|
+
const ASSET_BLOB_URI_VALIDATOR = /^blobs\/[0-9a-f]{64}$/u;
|
|
975
|
+
const validateSchema = new Ajv2020({
|
|
976
|
+
strict: true,
|
|
977
|
+
strictRequired: false,
|
|
978
|
+
allErrors: true,
|
|
979
|
+
ownProperties: true
|
|
980
|
+
}).compile(rawAssetV1Schema);
|
|
981
|
+
function isAssetBlobUri(value) {
|
|
982
|
+
return typeof value === "string" && ASSET_BLOB_URI_VALIDATOR.test(value);
|
|
983
|
+
}
|
|
984
|
+
function parseAssetBlobUri(value) {
|
|
985
|
+
if (!isAssetBlobUri(value)) throw new TypeError("Asset blob URI is not canonical");
|
|
986
|
+
return value;
|
|
987
|
+
}
|
|
988
|
+
function parseGeneratedAssetName(value) {
|
|
989
|
+
return parseTopikGeneratedAssetName(value);
|
|
990
|
+
}
|
|
991
|
+
function parseAsset(input) {
|
|
992
|
+
const source = typeof input === "string" ? encoder.encode(input) : Uint8Array.from(input);
|
|
993
|
+
if (source.byteLength > TOPIK_ASSET_LIMITS.maxDescriptorBytes) return fail(source, "TOPIK_ASSET_SCHEMA_INVALID", "Asset descriptor exceeds the byte limit");
|
|
994
|
+
let text;
|
|
995
|
+
try {
|
|
996
|
+
text = decoder.decode(source);
|
|
997
|
+
} catch {
|
|
998
|
+
return fail(source, "TOPIK_ASSET_SCHEMA_INVALID", "Asset descriptor is not strict UTF-8");
|
|
999
|
+
}
|
|
1000
|
+
if (text.startsWith("")) return fail(source, "TOPIK_ASSET_SCHEMA_INVALID", "Asset descriptor must not contain a BOM");
|
|
1001
|
+
let value;
|
|
1002
|
+
try {
|
|
1003
|
+
value = parseStrictTopikJson(text, TOPIK_ASSET_LIMITS.maxJsonDepth);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
return {
|
|
1006
|
+
ok: false,
|
|
1007
|
+
diagnostics: [topikAssetDiagnostic(error instanceof TopikJsonSyntaxError && error.duplicatePointer !== void 0 ? "TOPIK_ASSET_DUPLICATE_MEMBER" : "TOPIK_ASSET_SCHEMA_INVALID", error instanceof Error ? error.message : "Asset descriptor is not strict JSON", { location: { jsonPointer: error instanceof TopikJsonSyntaxError ? error.duplicatePointer ?? "/" : "/" } })],
|
|
1008
|
+
source
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const validated = validateAssetValue(value);
|
|
1012
|
+
if (!validated.ok) return {
|
|
1013
|
+
ok: false,
|
|
1014
|
+
diagnostics: validated.diagnostics,
|
|
1015
|
+
source
|
|
1016
|
+
};
|
|
1017
|
+
const canonicalText = serializeTopikJson(validated.value);
|
|
1018
|
+
const canonicalBytes = encoder.encode(canonicalText);
|
|
1019
|
+
if (!equalBytes$1(source, canonicalBytes)) return {
|
|
1020
|
+
ok: false,
|
|
1021
|
+
diagnostics: [topikAssetDiagnostic("TOPIK_ASSET_NON_CANONICAL", "Asset bytes are not canonical JSON", {
|
|
1022
|
+
location: { jsonPointer: "/" },
|
|
1023
|
+
recovery: "repair-source",
|
|
1024
|
+
consequence: "block-identity-and-writes"
|
|
1025
|
+
})],
|
|
1026
|
+
source
|
|
1027
|
+
};
|
|
1028
|
+
return {
|
|
1029
|
+
ok: true,
|
|
1030
|
+
value: {
|
|
1031
|
+
asset: validated.value,
|
|
1032
|
+
canonicalBytes
|
|
1033
|
+
},
|
|
1034
|
+
diagnostics: [],
|
|
1035
|
+
source
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function validateAssetValue(value) {
|
|
1039
|
+
if (!isTopikJsonDataValue(value)) return failure("TOPIK_ASSET_SCHEMA_INVALID", "Asset must contain only own JSON data properties");
|
|
1040
|
+
if (isRecord$2(value)) {
|
|
1041
|
+
const type = Object.hasOwn(value, "type") ? value.type : void 0;
|
|
1042
|
+
const apiVersion = Object.hasOwn(value, "apiVersion") ? value.apiVersion : void 0;
|
|
1043
|
+
if (type === "Asset" && apiVersion !== "v1") return failure("TOPIK_ASSET_UNSUPPORTED_VERSION", "Unsupported Asset apiVersion", "/apiVersion");
|
|
1044
|
+
const spec = Object.hasOwn(value, "spec") && isRecord$2(value.spec) ? value.spec : void 0;
|
|
1045
|
+
if (spec !== void 0 && Object.hasOwn(spec, "size") && typeof spec.size === "number" && spec.size > TOPIK_ASSET_LIMITS.maxAssetBytes) return failure("TOPIK_ASSET_SIZE_MISMATCH", "Asset size exceeds the portable byte limit", "/spec/size");
|
|
1046
|
+
}
|
|
1047
|
+
if (!validateSchema(value)) return {
|
|
1048
|
+
ok: false,
|
|
1049
|
+
diagnostics: (validateSchema.errors ?? []).map(schemaDiagnostic)
|
|
1050
|
+
};
|
|
1051
|
+
const asset = value;
|
|
1052
|
+
if (!hasMatchingAssetDigests(asset)) return {
|
|
1053
|
+
ok: false,
|
|
1054
|
+
diagnostics: [topikAssetDiagnostic("TOPIK_ASSET_DIGEST_MISMATCH", "Asset payload URI and integrity must identify the same digest", {
|
|
1055
|
+
consequence: "block-identity-and-writes",
|
|
1056
|
+
location: { jsonPointer: "/spec/integrity" },
|
|
1057
|
+
recovery: "verify-bytes"
|
|
1058
|
+
})]
|
|
1059
|
+
};
|
|
1060
|
+
return {
|
|
1061
|
+
ok: true,
|
|
1062
|
+
value: asset,
|
|
1063
|
+
diagnostics: []
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
/** Enforce the compiler blob/SHA-256 invariant when both forms are present. */
|
|
1067
|
+
function hasMatchingAssetDigests(asset) {
|
|
1068
|
+
const { integrity, uri } = asset.spec;
|
|
1069
|
+
if (integrity === void 0 || !isAssetBlobUri(uri) || !integrity.startsWith("sha256:")) return true;
|
|
1070
|
+
const digest = /^sha256:([0-9a-f]{64})$/u.exec(integrity);
|
|
1071
|
+
return digest !== null && uri.slice(6) === digest[1];
|
|
1072
|
+
}
|
|
1073
|
+
/** Serialize one Asset to deterministic UTF-8 canonical JSON and prove its round trip. */
|
|
1074
|
+
function serializeAsset(value) {
|
|
1075
|
+
const validation = validateAssetValue(value);
|
|
1076
|
+
if (!validation.ok) return {
|
|
1077
|
+
ok: false,
|
|
1078
|
+
diagnostics: validation.diagnostics
|
|
1079
|
+
};
|
|
1080
|
+
const bytes = encoder.encode(serializeTopikJson(validation.value));
|
|
1081
|
+
const parsed = parseAsset(bytes);
|
|
1082
|
+
if (!parsed.ok || serializeTopikJson(parsed.value.asset) !== serializeTopikJson(validation.value)) return failure("TOPIK_ASSET_NON_CANONICAL", "Serialized Asset failed strict semantic reparse");
|
|
1083
|
+
return {
|
|
1084
|
+
ok: true,
|
|
1085
|
+
value: bytes,
|
|
1086
|
+
diagnostics: []
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
/** Admit a compiler-materialized blob path; Asset resources may use other URI forms. */
|
|
1090
|
+
function validateAssetBlobUri(value) {
|
|
1091
|
+
const local = validateTopikPath(value);
|
|
1092
|
+
if (!local.ok) return {
|
|
1093
|
+
ok: false,
|
|
1094
|
+
diagnostics: local.diagnostics
|
|
1095
|
+
};
|
|
1096
|
+
if (!isAssetBlobUri(local.value.path)) return failure("TOPIK_ASSET_SCHEMA_INVALID", "Asset URI must identify a compiler-materialized payload", "/spec/uri");
|
|
1097
|
+
return {
|
|
1098
|
+
ok: true,
|
|
1099
|
+
value: { uri: local.value.path },
|
|
1100
|
+
diagnostics: []
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function isGeneratedAssetName(value) {
|
|
1104
|
+
return isTopikGeneratedAssetName(value);
|
|
1105
|
+
}
|
|
1106
|
+
function validateStableSourceNamespace(value) {
|
|
1107
|
+
if (containsForbiddenPortableText(value)) return failure("TOPIK_ASSET_SOURCE_NAMESPACE_INVALID", "Stable source namespace is not portable text after NFC normalization");
|
|
1108
|
+
const normalized = value.normalize("NFC");
|
|
1109
|
+
const bytes = encoder.encode(normalized);
|
|
1110
|
+
if (bytes.byteLength < 1 || bytes.byteLength > 1024 || containsForbiddenPortableText(normalized)) return failure("TOPIK_ASSET_SOURCE_NAMESPACE_INVALID", "Stable source namespace is not portable text after NFC normalization");
|
|
1111
|
+
return {
|
|
1112
|
+
ok: true,
|
|
1113
|
+
value: normalized,
|
|
1114
|
+
diagnostics: []
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function containsForbiddenPortableText(value) {
|
|
1118
|
+
for (const character of value) {
|
|
1119
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
1120
|
+
if (isTopikPathCodePointForbiddenV17(codePoint) || isTopikPathNormalizationSensitiveV17(codePoint)) return true;
|
|
1121
|
+
}
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
function generateAutomaticAssetName(options) {
|
|
1125
|
+
const namespace = validateStableSourceNamespace(options.stableSourceNamespace);
|
|
1126
|
+
if (!namespace.ok) return {
|
|
1127
|
+
ok: false,
|
|
1128
|
+
diagnostics: namespace.diagnostics
|
|
1129
|
+
};
|
|
1130
|
+
const path = validateTopikPath(options.normalizedPath);
|
|
1131
|
+
if (!path.ok) return {
|
|
1132
|
+
ok: false,
|
|
1133
|
+
diagnostics: path.diagnostics
|
|
1134
|
+
};
|
|
1135
|
+
const input = new Uint8Array(encoder.encode(namespace.value).byteLength + 1 + encoder.encode(path.value.path).byteLength);
|
|
1136
|
+
const namespaceBytes = encoder.encode(namespace.value);
|
|
1137
|
+
const pathBytes = encoder.encode(path.value.path);
|
|
1138
|
+
input.set(namespaceBytes, 0);
|
|
1139
|
+
input[namespaceBytes.byteLength] = 0;
|
|
1140
|
+
input.set(pathBytes, namespaceBytes.byteLength + 1);
|
|
1141
|
+
return {
|
|
1142
|
+
ok: true,
|
|
1143
|
+
value: parseGeneratedAssetName(`auto-v1-${base32(createHash("sha256").update(input).digest())}`),
|
|
1144
|
+
diagnostics: []
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function topikAssetNameDescriptor() {
|
|
1148
|
+
return { id: TOPIK_ASSET_NAME_VERSION };
|
|
1149
|
+
}
|
|
1150
|
+
function schemaDiagnostic(error) {
|
|
1151
|
+
return topikAssetDiagnostic("TOPIK_ASSET_SCHEMA_INVALID", "Asset does not match Asset/v1", { location: { jsonPointer: error.instancePath || "/" } });
|
|
1152
|
+
}
|
|
1153
|
+
function failure(id, message, jsonPointer = "/") {
|
|
1154
|
+
return {
|
|
1155
|
+
ok: false,
|
|
1156
|
+
diagnostics: [topikAssetDiagnostic(id, message, { location: { jsonPointer } })]
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function fail(source, id, message) {
|
|
1160
|
+
return {
|
|
1161
|
+
...failure(id, message),
|
|
1162
|
+
source
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function isRecord$2(value) {
|
|
1166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1167
|
+
}
|
|
1168
|
+
function equalBytes$1(left, right) {
|
|
1169
|
+
return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
|
|
1170
|
+
}
|
|
1171
|
+
function base32(bytes) {
|
|
1172
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
1173
|
+
let accumulator = 0;
|
|
1174
|
+
let bits = 0;
|
|
1175
|
+
let result = "";
|
|
1176
|
+
for (const byte of bytes) {
|
|
1177
|
+
accumulator = accumulator << 8 | byte;
|
|
1178
|
+
bits += 8;
|
|
1179
|
+
while (bits >= 5) {
|
|
1180
|
+
bits -= 5;
|
|
1181
|
+
result += alphabet[accumulator >>> bits & 31];
|
|
1182
|
+
accumulator &= (1 << bits) - 1;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (bits > 0) result += alphabet[accumulator << 5 - bits & 31];
|
|
1186
|
+
return result;
|
|
1187
|
+
}
|
|
1188
|
+
//#endregion
|
|
1189
|
+
//#region src/assets/files.ts
|
|
1190
|
+
const execFileAsync = promisify(execFile);
|
|
1191
|
+
function validatePortableAssetFile(file) {
|
|
1192
|
+
const diagnostics = [];
|
|
1193
|
+
const path = validateTopikPath(file.path);
|
|
1194
|
+
if (!path.ok) diagnostics.push(...path.diagnostics);
|
|
1195
|
+
const basename = file.path.split("/").at(-1)?.toLowerCase();
|
|
1196
|
+
if ([
|
|
1197
|
+
".gitattributes",
|
|
1198
|
+
".gitmodules",
|
|
1199
|
+
".lfsconfig"
|
|
1200
|
+
].includes(basename ?? "")) diagnostics.push(unsupported(file, "Security-sensitive Git configuration cannot be asset-owned"));
|
|
1201
|
+
if (file.type !== "regular" || !hasPortableMode(file) || file.source !== "git" && file.linkCount !== 1 || file.hasAlternateDataStream === true || file.contentFilter !== void 0 && file.contentFilter !== "unspecified" || file.workingTreeEncoding !== void 0) diagnostics.push(unsupported(file, "Portable assets must be unfiltered regular non-executable files"));
|
|
1202
|
+
if (file.bytes === void 0) diagnostics.push(topikAssetDiagnostic("TOPIK_ASSET_FILE_MISSING", "File bytes are unavailable", {
|
|
1203
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
1204
|
+
location: { path: file.path },
|
|
1205
|
+
recovery: "restore-file"
|
|
1206
|
+
}));
|
|
1207
|
+
else if (file.bytes.byteLength > TOPIK_ASSET_LIMITS.maxAssetBytes) diagnostics.push(unsupported(file, "Asset bytes exceed the portable size limit"));
|
|
1208
|
+
else if (looksLikeGitLfsPointer(file.bytes)) diagnostics.push(unsupported(file, "Git LFS pointers and near-miss signatures are not portable bytes"));
|
|
1209
|
+
return diagnostics.length === 0 ? {
|
|
1210
|
+
ok: true,
|
|
1211
|
+
value: file,
|
|
1212
|
+
diagnostics: []
|
|
1213
|
+
} : {
|
|
1214
|
+
ok: false,
|
|
1215
|
+
value: file,
|
|
1216
|
+
diagnostics
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
function validatePortableAssetTree(files) {
|
|
1220
|
+
const diagnostics = [];
|
|
1221
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1222
|
+
for (const file of files) {
|
|
1223
|
+
if (seen.has(file.path)) {
|
|
1224
|
+
diagnostics.push(topikAssetDiagnostic("TOPIK_ASSET_PATH_COLLISION", "Tree repeats an exact file path", {
|
|
1225
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
1226
|
+
location: { path: file.path },
|
|
1227
|
+
reason: "casefold_collision"
|
|
1228
|
+
}));
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
seen.add(file.path);
|
|
1232
|
+
const validation = validatePortableAssetFile(file);
|
|
1233
|
+
if (!validation.ok) diagnostics.push(...validation.diagnostics);
|
|
1234
|
+
}
|
|
1235
|
+
const paths = validateTopikPathSet(files.map((file) => file.path));
|
|
1236
|
+
if (!paths.ok) diagnostics.push(...paths.diagnostics);
|
|
1237
|
+
return diagnostics.length === 0 ? {
|
|
1238
|
+
ok: true,
|
|
1239
|
+
value: files,
|
|
1240
|
+
diagnostics: []
|
|
1241
|
+
} : {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
value: files,
|
|
1244
|
+
diagnostics
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
/** @internal Classify directory navigation through anchored no-follow handles. */
|
|
1248
|
+
async function classifyPortableNavigationPath(options) {
|
|
1249
|
+
const pathValidation = validateTopikPath(options.path);
|
|
1250
|
+
if (!pathValidation.ok) return "other";
|
|
1251
|
+
if (process.platform !== "linux" || typeof constants.O_NOFOLLOW !== "number" || typeof constants.O_DIRECTORY !== "number") return "other";
|
|
1252
|
+
const handles = [];
|
|
1253
|
+
try {
|
|
1254
|
+
const expectedRoot = await lstat(options.root, { bigint: true });
|
|
1255
|
+
if (!expectedRoot.isDirectory() || expectedRoot.isSymbolicLink()) return "other";
|
|
1256
|
+
const root = await open(options.root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
1257
|
+
handles.push(root);
|
|
1258
|
+
const openedRoot = await root.stat({ bigint: true });
|
|
1259
|
+
if (!openedRoot.isDirectory() || expectedRoot.dev !== openedRoot.dev || expectedRoot.ino !== openedRoot.ino) return "other";
|
|
1260
|
+
for (const component of pathValidation.value.path.split("/")) {
|
|
1261
|
+
const parent = handles.at(-1);
|
|
1262
|
+
if (parent === void 0) return "other";
|
|
1263
|
+
const child = await open(procFdChild(parent.fd, component), constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
1264
|
+
handles.push(child);
|
|
1265
|
+
if (!(await child.stat({ bigint: true })).isDirectory()) return "other";
|
|
1266
|
+
}
|
|
1267
|
+
return "directory";
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
return isMissingFilesystemError(error) ? "missing" : "other";
|
|
1270
|
+
} finally {
|
|
1271
|
+
for (const handle of handles.reverse()) await handle.close().catch(() => void 0);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function isMissingFilesystemError(error) {
|
|
1275
|
+
return error?.code === "ENOENT";
|
|
1276
|
+
}
|
|
1277
|
+
/** Filesystem reader using no-follow traversal and stable same-handle identity checks. */
|
|
1278
|
+
async function readPortableAssetFile(options) {
|
|
1279
|
+
return readPortableAssetFileAnchored(options);
|
|
1280
|
+
}
|
|
1281
|
+
async function readPortableAssetFileAnchored(options, afterDirectoryOpened, afterFileRead) {
|
|
1282
|
+
const pathValidation = validateTopikPath(options.path);
|
|
1283
|
+
if (!pathValidation.ok) return {
|
|
1284
|
+
ok: false,
|
|
1285
|
+
diagnostics: pathValidation.diagnostics
|
|
1286
|
+
};
|
|
1287
|
+
if (process.platform !== "linux" || typeof constants.O_NOFOLLOW !== "number" || typeof constants.O_DIRECTORY !== "number") return {
|
|
1288
|
+
ok: false,
|
|
1289
|
+
diagnostics: [unsupportedPath(options.path, "This platform cannot prove descriptor-anchored no-follow traversal")]
|
|
1290
|
+
};
|
|
1291
|
+
const directoryHandles = [];
|
|
1292
|
+
const gitBoundaryFingerprints = [];
|
|
1293
|
+
let fileHandle;
|
|
1294
|
+
try {
|
|
1295
|
+
const repositoryBefore = await repositoryEvidenceFingerprint(options.root, options.path);
|
|
1296
|
+
if (!repositoryBefore.ok) return {
|
|
1297
|
+
ok: false,
|
|
1298
|
+
diagnostics: [unsupportedPath(options.path, repositoryBefore.failure)]
|
|
1299
|
+
};
|
|
1300
|
+
const gitAttributesBefore = await effectiveGitAttributeFingerprint(options.root, options.path);
|
|
1301
|
+
if (!gitAttributesBefore.ok) return {
|
|
1302
|
+
ok: false,
|
|
1303
|
+
diagnostics: [unsupportedPath(options.path, gitAttributesBefore.failure)]
|
|
1304
|
+
};
|
|
1305
|
+
const expectedRoot = await lstat(options.root, { bigint: true });
|
|
1306
|
+
if (!expectedRoot.isDirectory() || expectedRoot.isSymbolicLink()) return {
|
|
1307
|
+
ok: false,
|
|
1308
|
+
diagnostics: [unsupportedPath(options.path, "Resource root is not a stable directory")]
|
|
1309
|
+
};
|
|
1310
|
+
const rootHandle = await open(options.root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
1311
|
+
directoryHandles.push(rootHandle);
|
|
1312
|
+
const openedRoot = await rootHandle.stat({ bigint: true });
|
|
1313
|
+
if (!openedRoot.isDirectory() || expectedRoot.dev !== openedRoot.dev || expectedRoot.ino !== openedRoot.ino) return {
|
|
1314
|
+
ok: false,
|
|
1315
|
+
diagnostics: [unsupportedPath(options.path, "Resource root identity changed before open")]
|
|
1316
|
+
};
|
|
1317
|
+
const rootGitBoundary = await anchoredGitBoundaryFingerprint(rootHandle);
|
|
1318
|
+
if (!rootGitBoundary.ok) return {
|
|
1319
|
+
ok: false,
|
|
1320
|
+
diagnostics: [unsupportedPath(options.path, rootGitBoundary.failure)]
|
|
1321
|
+
};
|
|
1322
|
+
gitBoundaryFingerprints.push(rootGitBoundary.fingerprint);
|
|
1323
|
+
const components = options.path.split("/");
|
|
1324
|
+
const attributes = {};
|
|
1325
|
+
const rootAttributeFailure = await applyGitAttributes(rootHandle, components.join("/"), attributes);
|
|
1326
|
+
if (rootAttributeFailure !== void 0) return {
|
|
1327
|
+
ok: false,
|
|
1328
|
+
diagnostics: [unsupportedPath(options.path, rootAttributeFailure)]
|
|
1329
|
+
};
|
|
1330
|
+
for (let index = 0; index < components.length - 1; index++) {
|
|
1331
|
+
const parent = directoryHandles.at(-1);
|
|
1332
|
+
if (parent === void 0) throw new Error("Missing anchored parent directory handle");
|
|
1333
|
+
const child = await open(procFdChild(parent.fd, components[index]), constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
1334
|
+
directoryHandles.push(child);
|
|
1335
|
+
if (!(await child.stat({ bigint: true })).isDirectory()) return {
|
|
1336
|
+
ok: false,
|
|
1337
|
+
diagnostics: [unsupportedPath(options.path, "Non-directory traversal is unsupported")]
|
|
1338
|
+
};
|
|
1339
|
+
const gitBoundary = await anchoredGitBoundaryFingerprint(child);
|
|
1340
|
+
if (!gitBoundary.ok || gitBoundary.present) return {
|
|
1341
|
+
ok: false,
|
|
1342
|
+
diagnostics: [unsupportedPath(options.path, gitBoundary.ok ? "Nested Git worktree or submodule traversal is unsupported" : gitBoundary.failure)]
|
|
1343
|
+
};
|
|
1344
|
+
gitBoundaryFingerprints.push(gitBoundary.fingerprint);
|
|
1345
|
+
const attributeFailure = await applyGitAttributes(child, components.slice(index + 1).join("/"), attributes);
|
|
1346
|
+
if (attributeFailure !== void 0) return {
|
|
1347
|
+
ok: false,
|
|
1348
|
+
diagnostics: [unsupportedPath(options.path, attributeFailure)]
|
|
1349
|
+
};
|
|
1350
|
+
await afterDirectoryOpened?.(components.slice(0, index + 1));
|
|
1351
|
+
}
|
|
1352
|
+
if (attributes.filter !== void 0 && attributes.filter !== "unspecified" || attributes.workingTreeEncoding !== void 0 && attributes.workingTreeEncoding !== "unspecified") return {
|
|
1353
|
+
ok: false,
|
|
1354
|
+
diagnostics: [unsupportedPath(options.path, "Effective Git content filters or working-tree encoding are not portable")]
|
|
1355
|
+
};
|
|
1356
|
+
const parent = directoryHandles.at(-1);
|
|
1357
|
+
if (parent === void 0) throw new Error("Missing anchored parent directory handle");
|
|
1358
|
+
fileHandle = await open(procFdChild(parent.fd, components.at(-1) ?? ""), constants.O_RDONLY | constants.O_NOFOLLOW | (typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0));
|
|
1359
|
+
const before = await fileHandle.stat({ bigint: true });
|
|
1360
|
+
if (!before.isFile() || before.nlink !== 1n || (before.mode & 73n) !== 0n || before.size > BigInt(TOPIK_ASSET_LIMITS.maxAssetBytes) || before.size > 0n && before.blocks * 512n < before.size) return {
|
|
1361
|
+
ok: false,
|
|
1362
|
+
diagnostics: [unsupportedPath(options.path, "Unsafe filesystem file type or mode")]
|
|
1363
|
+
};
|
|
1364
|
+
const bytes = await fileHandle.readFile();
|
|
1365
|
+
await afterFileRead?.();
|
|
1366
|
+
const after = await fileHandle.stat({ bigint: true });
|
|
1367
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.nlink !== after.nlink || before.size !== after.size || before.ctimeNs !== after.ctimeNs || before.mtimeNs !== after.mtimeNs) return {
|
|
1368
|
+
ok: false,
|
|
1369
|
+
diagnostics: [unsupportedPath(options.path, "File identity changed while hashing")]
|
|
1370
|
+
};
|
|
1371
|
+
for (let index = 0; index < directoryHandles.length; index++) {
|
|
1372
|
+
const gitBoundary = await anchoredGitBoundaryFingerprint(directoryHandles[index]);
|
|
1373
|
+
if (!gitBoundary.ok || gitBoundary.fingerprint !== gitBoundaryFingerprints[index] || index > 0 && gitBoundary.present) return {
|
|
1374
|
+
ok: false,
|
|
1375
|
+
diagnostics: [unsupportedPath(options.path, gitBoundary.ok ? "Git worktree boundary evidence changed while the file was read" : gitBoundary.failure)]
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const repositoryAfter = await repositoryEvidenceFingerprint(options.root, options.path);
|
|
1379
|
+
if (!repositoryAfter.ok || repositoryAfter.fingerprint !== repositoryBefore.fingerprint) return {
|
|
1380
|
+
ok: false,
|
|
1381
|
+
diagnostics: [unsupportedPath(options.path, repositoryAfter.ok ? "Git repository evidence changed while the file was read" : repositoryAfter.failure)]
|
|
1382
|
+
};
|
|
1383
|
+
const gitAttributesAfter = await effectiveGitAttributeFingerprint(options.root, options.path);
|
|
1384
|
+
if (!gitAttributesAfter.ok || gitAttributesAfter.fingerprint !== gitAttributesBefore.fingerprint) return {
|
|
1385
|
+
ok: false,
|
|
1386
|
+
diagnostics: [unsupportedPath(options.path, gitAttributesAfter.ok ? "Effective Git attribute evidence changed while it was evaluated" : gitAttributesAfter.failure)]
|
|
1387
|
+
};
|
|
1388
|
+
return validatePortableAssetFile({
|
|
1389
|
+
path: options.path,
|
|
1390
|
+
type: "regular",
|
|
1391
|
+
source: "filesystem",
|
|
1392
|
+
mode: "100644",
|
|
1393
|
+
bytes,
|
|
1394
|
+
linkCount: Number(after.nlink)
|
|
1395
|
+
});
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
return {
|
|
1398
|
+
ok: false,
|
|
1399
|
+
diagnostics: [error.code === "ENOENT" ? topikAssetDiagnostic("TOPIK_ASSET_FILE_MISSING", "Asset file is missing", {
|
|
1400
|
+
location: { path: options.path },
|
|
1401
|
+
recovery: "restore-file"
|
|
1402
|
+
}) : unsupportedPath(options.path, "File read failed without a portable no-follow proof")]
|
|
1403
|
+
};
|
|
1404
|
+
} finally {
|
|
1405
|
+
await fileHandle?.close().catch(() => void 0);
|
|
1406
|
+
await Promise.all(directoryHandles.map((handle) => handle.close().catch(() => void 0)));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
async function repositoryEvidenceFingerprint(root, path) {
|
|
1410
|
+
if (!await hasGitBoundary(root)) return {
|
|
1411
|
+
ok: true,
|
|
1412
|
+
fingerprint: "not-in-worktree"
|
|
1413
|
+
};
|
|
1414
|
+
try {
|
|
1415
|
+
const worktreeResult = await execFileAsync("/usr/bin/git", [
|
|
1416
|
+
"-C",
|
|
1417
|
+
root,
|
|
1418
|
+
"rev-parse",
|
|
1419
|
+
"--show-toplevel"
|
|
1420
|
+
], {
|
|
1421
|
+
encoding: "utf8",
|
|
1422
|
+
maxBuffer: 65536
|
|
1423
|
+
});
|
|
1424
|
+
if ((await execFileAsync("/usr/bin/git", [
|
|
1425
|
+
"-C",
|
|
1426
|
+
root,
|
|
1427
|
+
"rev-parse",
|
|
1428
|
+
"--show-superproject-working-tree"
|
|
1429
|
+
], {
|
|
1430
|
+
encoding: "utf8",
|
|
1431
|
+
maxBuffer: 65536
|
|
1432
|
+
})).stdout.trim().length > 0) return {
|
|
1433
|
+
ok: false,
|
|
1434
|
+
failure: "Compilation root traverses a checked-out Git submodule"
|
|
1435
|
+
};
|
|
1436
|
+
const worktree = worktreeResult.stdout.trim();
|
|
1437
|
+
const rootFromWorktree = relative(resolve(worktree), resolve(root));
|
|
1438
|
+
if (rootFromWorktree === ".." || rootFromWorktree.startsWith(`..${sep}`) || isAbsolute(rootFromWorktree)) return {
|
|
1439
|
+
ok: false,
|
|
1440
|
+
failure: "Compilation root is outside its Git worktree"
|
|
1441
|
+
};
|
|
1442
|
+
const worktreePath = [...rootFromWorktree === "" ? [] : rootFromWorktree.split(sep), ...path.split("/")].join("/");
|
|
1443
|
+
const indexResult = await execFileAsync("/usr/bin/git", [
|
|
1444
|
+
"-C",
|
|
1445
|
+
worktree,
|
|
1446
|
+
"ls-files",
|
|
1447
|
+
"--stage",
|
|
1448
|
+
"-z"
|
|
1449
|
+
], {
|
|
1450
|
+
encoding: "utf8",
|
|
1451
|
+
maxBuffer: 67108864
|
|
1452
|
+
});
|
|
1453
|
+
for (const entry of indexResult.stdout.split("\0")) {
|
|
1454
|
+
if (entry.length === 0) continue;
|
|
1455
|
+
const separator = entry.indexOf(" ");
|
|
1456
|
+
if (separator === -1) return {
|
|
1457
|
+
ok: false,
|
|
1458
|
+
failure: "Git index evidence is malformed"
|
|
1459
|
+
};
|
|
1460
|
+
const [mode] = entry.slice(0, separator).split(" ");
|
|
1461
|
+
const entryPath = entry.slice(separator + 1);
|
|
1462
|
+
if (mode === "160000" && isPathAtOrBelow(worktreePath, entryPath)) return {
|
|
1463
|
+
ok: false,
|
|
1464
|
+
failure: "Asset path traverses a Gitlink index entry"
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
ok: true,
|
|
1469
|
+
fingerprint: createHash("sha256").update(worktree).update("\0").update(rootFromWorktree).update("\0").update(indexResult.stdout).digest("hex")
|
|
1470
|
+
};
|
|
1471
|
+
} catch {
|
|
1472
|
+
return {
|
|
1473
|
+
ok: false,
|
|
1474
|
+
failure: "Git repository boundary could not be proven"
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function isPathAtOrBelow(path, ancestor) {
|
|
1479
|
+
return path === ancestor || path.startsWith(`${ancestor}/`);
|
|
1480
|
+
}
|
|
1481
|
+
async function anchoredGitBoundaryFingerprint(directory) {
|
|
1482
|
+
let handle;
|
|
1483
|
+
try {
|
|
1484
|
+
handle = await open(procFdChild(directory.fd, ".git"), constants.O_RDONLY | constants.O_NOFOLLOW | (typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0));
|
|
1485
|
+
const before = await handle.stat({ bigint: true });
|
|
1486
|
+
if (!before.isFile() && !before.isDirectory()) return {
|
|
1487
|
+
ok: false,
|
|
1488
|
+
failure: "Git boundary is not a regular file or directory"
|
|
1489
|
+
};
|
|
1490
|
+
const after = await handle.stat({ bigint: true });
|
|
1491
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.nlink !== after.nlink || before.size !== after.size || before.ctimeNs !== after.ctimeNs || before.mtimeNs !== after.mtimeNs) return {
|
|
1492
|
+
ok: false,
|
|
1493
|
+
failure: "Git boundary identity changed while it was inspected"
|
|
1494
|
+
};
|
|
1495
|
+
return {
|
|
1496
|
+
ok: true,
|
|
1497
|
+
present: true,
|
|
1498
|
+
fingerprint: [
|
|
1499
|
+
"present",
|
|
1500
|
+
after.dev,
|
|
1501
|
+
after.ino,
|
|
1502
|
+
after.mode,
|
|
1503
|
+
after.nlink,
|
|
1504
|
+
after.size,
|
|
1505
|
+
after.ctimeNs,
|
|
1506
|
+
after.mtimeNs
|
|
1507
|
+
].join(":")
|
|
1508
|
+
};
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
return error.code === "ENOENT" ? {
|
|
1511
|
+
ok: true,
|
|
1512
|
+
present: false,
|
|
1513
|
+
fingerprint: "absent"
|
|
1514
|
+
} : {
|
|
1515
|
+
ok: false,
|
|
1516
|
+
failure: "Git boundary could not be inspected without following links"
|
|
1517
|
+
};
|
|
1518
|
+
} finally {
|
|
1519
|
+
await handle?.close().catch(() => void 0);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function effectiveGitAttributeFingerprint(root, path) {
|
|
1523
|
+
if (!await hasGitBoundary(root)) return {
|
|
1524
|
+
ok: true,
|
|
1525
|
+
fingerprint: "not-in-worktree"
|
|
1526
|
+
};
|
|
1527
|
+
let worktree;
|
|
1528
|
+
try {
|
|
1529
|
+
worktree = (await execFileAsync("/usr/bin/git", [
|
|
1530
|
+
"-C",
|
|
1531
|
+
root,
|
|
1532
|
+
"rev-parse",
|
|
1533
|
+
"--show-toplevel"
|
|
1534
|
+
], { encoding: "utf8" })).stdout.trim();
|
|
1535
|
+
} catch {
|
|
1536
|
+
return {
|
|
1537
|
+
ok: false,
|
|
1538
|
+
failure: "Git worktree boundary could not be proven"
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
const rootFromWorktree = relative(resolve(worktree), resolve(root));
|
|
1542
|
+
if (rootFromWorktree === ".." || rootFromWorktree.startsWith(`..${sep}`) || isAbsolute(rootFromWorktree)) return {
|
|
1543
|
+
ok: false,
|
|
1544
|
+
failure: "Compilation root is outside its Git worktree"
|
|
1545
|
+
};
|
|
1546
|
+
const worktreePath = [...rootFromWorktree === "" ? [] : rootFromWorktree.split(sep), ...path.split("/")].join("/");
|
|
1547
|
+
let stdout;
|
|
1548
|
+
try {
|
|
1549
|
+
stdout = (await execFileAsync("/usr/bin/git", [
|
|
1550
|
+
"-C",
|
|
1551
|
+
worktree,
|
|
1552
|
+
"check-attr",
|
|
1553
|
+
"-z",
|
|
1554
|
+
"filter",
|
|
1555
|
+
"working-tree-encoding",
|
|
1556
|
+
"--",
|
|
1557
|
+
worktreePath
|
|
1558
|
+
], {
|
|
1559
|
+
encoding: "utf8",
|
|
1560
|
+
maxBuffer: 65536
|
|
1561
|
+
})).stdout;
|
|
1562
|
+
} catch {
|
|
1563
|
+
return {
|
|
1564
|
+
ok: false,
|
|
1565
|
+
failure: "Effective Git attributes could not be evaluated safely"
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
const fields = stdout.split("\0");
|
|
1569
|
+
if (fields.at(-1) === "") fields.pop();
|
|
1570
|
+
if (fields.length !== 6) return {
|
|
1571
|
+
ok: false,
|
|
1572
|
+
failure: "Effective Git attribute output is malformed"
|
|
1573
|
+
};
|
|
1574
|
+
const values = /* @__PURE__ */ new Map();
|
|
1575
|
+
for (let index = 0; index < fields.length; index += 3) {
|
|
1576
|
+
if (fields[index] !== worktreePath) return {
|
|
1577
|
+
ok: false,
|
|
1578
|
+
failure: "Effective Git attribute path binding changed"
|
|
1579
|
+
};
|
|
1580
|
+
values.set(fields[index + 1], fields[index + 2]);
|
|
1581
|
+
}
|
|
1582
|
+
if (values.get("filter") !== "unspecified" || values.get("working-tree-encoding") !== "unspecified") return {
|
|
1583
|
+
ok: false,
|
|
1584
|
+
failure: "Effective Git content filters or working-tree encoding are not portable"
|
|
1585
|
+
};
|
|
1586
|
+
return {
|
|
1587
|
+
ok: true,
|
|
1588
|
+
fingerprint: `${values.get("filter")}\0${values.get("working-tree-encoding")}`
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
async function hasGitBoundary(root) {
|
|
1592
|
+
let current = resolve(root);
|
|
1593
|
+
for (;;) {
|
|
1594
|
+
try {
|
|
1595
|
+
const stat = await lstat(`${current}/.git`);
|
|
1596
|
+
if (stat.isFile()) return true;
|
|
1597
|
+
if (stat.isDirectory()) try {
|
|
1598
|
+
if ((await lstat(`${current}/.git/HEAD`)).isFile()) return true;
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
if (error.code !== "ENOENT") return true;
|
|
1601
|
+
}
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
if (error.code !== "ENOENT") return true;
|
|
1604
|
+
}
|
|
1605
|
+
const parent = dirname(current);
|
|
1606
|
+
if (parent === current) return false;
|
|
1607
|
+
current = parent;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
async function applyGitAttributes(directory, relativePath, state) {
|
|
1611
|
+
let handle;
|
|
1612
|
+
try {
|
|
1613
|
+
handle = await open(procFdChild(directory.fd, ".gitattributes"), constants.O_RDONLY | constants.O_NOFOLLOW | (typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0));
|
|
1614
|
+
const before = await handle.stat({ bigint: true });
|
|
1615
|
+
if (!before.isFile() || before.nlink !== 1n) return "Git attribute evidence is not a stable regular file";
|
|
1616
|
+
const bytes = await handle.readFile();
|
|
1617
|
+
const after = await handle.stat({ bigint: true });
|
|
1618
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.nlink !== after.nlink || before.size !== after.size || before.ctimeNs !== after.ctimeNs || before.mtimeNs !== after.mtimeNs) return "Git attribute evidence changed while it was evaluated";
|
|
1619
|
+
let source;
|
|
1620
|
+
try {
|
|
1621
|
+
source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1622
|
+
} catch {
|
|
1623
|
+
return "Git attribute evidence is not strict UTF-8";
|
|
1624
|
+
}
|
|
1625
|
+
for (const rawLine of source.split(/\r?\n/u)) {
|
|
1626
|
+
const line = rawLine.trim();
|
|
1627
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
1628
|
+
const fields = line.split(/[\t ]+/u);
|
|
1629
|
+
const pattern = fields.shift();
|
|
1630
|
+
if (pattern === void 0 || pattern.startsWith("\"")) {
|
|
1631
|
+
if (fields.some(isPortableAttributeToken)) return "Quoted Git attribute patterns cannot be proven by the filesystem compiler";
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
if (hasUnsupportedGitAttributePattern(pattern) && fields.some(isPortableAttributeToken)) return "Git attribute pattern cannot be proven by the filesystem compiler";
|
|
1635
|
+
if (!gitAttributePatternMatches(pattern, relativePath)) continue;
|
|
1636
|
+
for (const token of fields) applyAttributeToken(token, state);
|
|
1637
|
+
}
|
|
1638
|
+
return;
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
return error.code === "ENOENT" ? void 0 : "Git attribute evidence could not be read without following links";
|
|
1641
|
+
} finally {
|
|
1642
|
+
await handle?.close().catch(() => void 0);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function applyAttributeToken(token, state) {
|
|
1646
|
+
const [rawName] = token.replace(/^[!-]/u, "").split("=", 1);
|
|
1647
|
+
const property = rawName === "filter" ? "filter" : rawName === "working-tree-encoding" ? "workingTreeEncoding" : void 0;
|
|
1648
|
+
if (property === void 0) return;
|
|
1649
|
+
state[property] = token.startsWith("!") ? "unspecified" : token.startsWith("-") ? "unset" : "set";
|
|
1650
|
+
}
|
|
1651
|
+
function isPortableAttributeToken(token) {
|
|
1652
|
+
return /^(?:[!-])?(?:filter|working-tree-encoding)(?:=|$)/u.test(token);
|
|
1653
|
+
}
|
|
1654
|
+
function hasUnsupportedGitAttributePattern(pattern) {
|
|
1655
|
+
return pattern.startsWith("!") || pattern.includes("\\") || pattern.includes("[") || pattern.includes("]") || pattern.includes("**");
|
|
1656
|
+
}
|
|
1657
|
+
function gitAttributePatternMatches(pattern, relativePath) {
|
|
1658
|
+
if (pattern.length === 0 || pattern.startsWith("!") || pattern.endsWith("/")) return false;
|
|
1659
|
+
const anchored = pattern.startsWith("/");
|
|
1660
|
+
const normalizedPattern = anchored ? pattern.slice(1) : pattern;
|
|
1661
|
+
const candidate = anchored || normalizedPattern.includes("/") ? relativePath : relativePath.split("/").at(-1) ?? relativePath;
|
|
1662
|
+
return globToRegExp(normalizedPattern).test(candidate);
|
|
1663
|
+
}
|
|
1664
|
+
function globToRegExp(pattern) {
|
|
1665
|
+
let source = "^";
|
|
1666
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
1667
|
+
const character = pattern[index];
|
|
1668
|
+
if (character === "*" && pattern[index + 1] === "*") {
|
|
1669
|
+
source += ".*";
|
|
1670
|
+
index++;
|
|
1671
|
+
} else if (character === "*") source += "[^/]*";
|
|
1672
|
+
else if (character === "?") source += "[^/]";
|
|
1673
|
+
else source += character.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1674
|
+
}
|
|
1675
|
+
return new RegExp(`${source}$`, "u");
|
|
1676
|
+
}
|
|
1677
|
+
function looksLikeGitLfsPointer(bytes) {
|
|
1678
|
+
const prefix = new TextDecoder().decode(bytes.slice(0, 1024));
|
|
1679
|
+
return /(?:^|\n)version\s+https?:\/\/git-lfs\.github\.com\/spec\/v1(?:\s|$)/iu.test(prefix) || /(?:^|\n)oid\s+sha256:[0-9a-f]{32,64}(?:\s|$)/iu.test(prefix) || /git-media|git-lfs/iu.test(prefix.slice(0, 256));
|
|
1680
|
+
}
|
|
1681
|
+
function unsupported(file, message) {
|
|
1682
|
+
return unsupportedPath(file.path, message);
|
|
1683
|
+
}
|
|
1684
|
+
function hasPortableMode(file) {
|
|
1685
|
+
if (file.source === "git" || file.source === "filesystem") return file.mode === "100644";
|
|
1686
|
+
if (file.source === "archive") return file.mode === "0644";
|
|
1687
|
+
return file.mode === "100644" || file.mode === "0644";
|
|
1688
|
+
}
|
|
1689
|
+
function procFdChild(parentFd, component) {
|
|
1690
|
+
return `/proc/self/fd/${parentFd}/${component}`;
|
|
1691
|
+
}
|
|
1692
|
+
function unsupportedPath(path, message) {
|
|
1693
|
+
return topikAssetDiagnostic("TOPIK_ASSET_FILE_TYPE_UNSUPPORTED", message, {
|
|
1694
|
+
descriptorVersion: TOPIK_PATH_VERSION,
|
|
1695
|
+
location: { path },
|
|
1696
|
+
recovery: "preserve-read-only"
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
//#endregion
|
|
1700
|
+
//#region src/validate.ts
|
|
1701
|
+
const ajv = new Ajv2020({
|
|
1702
|
+
strict: true,
|
|
1703
|
+
discriminator: true,
|
|
1704
|
+
ownProperties: true
|
|
1705
|
+
});
|
|
1706
|
+
addFormats(ajv);
|
|
1707
|
+
const validators = /* @__PURE__ */ new Map([
|
|
1708
|
+
["Course/v1", ajv.compile(courseV1Schema)],
|
|
1709
|
+
["CourseModule/v1", ajv.compile(courseModuleV1Schema)],
|
|
1710
|
+
["CoursePage/v1", ajv.compile(coursePageV1Schema)],
|
|
1711
|
+
["Guide/v1", ajv.compile(guideV1Schema)],
|
|
1712
|
+
["Person/v1", ajv.compile(personV1Schema)],
|
|
1713
|
+
["Wiki/v1", ajv.compile(wikiV1Schema)],
|
|
1714
|
+
["WikiPage/v1", ajv.compile(wikiPageV1Schema)]
|
|
1715
|
+
]);
|
|
1716
|
+
function isRecord$1(value) {
|
|
1717
|
+
return typeof value === "object" && value !== null;
|
|
1718
|
+
}
|
|
1719
|
+
function getResourceLabel(resource) {
|
|
1720
|
+
return `${Object.hasOwn(resource, "type") && typeof resource.type === "string" ? resource.type : "unknown"}/${Object.hasOwn(resource, "name") && typeof resource.name === "string" ? resource.name : "unknown"}`;
|
|
1721
|
+
}
|
|
1722
|
+
/** Validate the resources produced by `compile` and the CLI export. */
|
|
1723
|
+
function validateResources(resources) {
|
|
1724
|
+
const errors = [];
|
|
1725
|
+
for (const resource of resources) {
|
|
1726
|
+
if (!isRecord$1(resource)) {
|
|
1727
|
+
errors.push({
|
|
1728
|
+
resource: "unknown/unknown",
|
|
1729
|
+
path: "/",
|
|
1730
|
+
message: "Resource must be an object"
|
|
1731
|
+
});
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (!Object.hasOwn(resource, "type") || typeof resource.type !== "string") {
|
|
1735
|
+
errors.push({
|
|
1736
|
+
resource: getResourceLabel(resource),
|
|
1737
|
+
path: "/type",
|
|
1738
|
+
message: "Resource type must be a string"
|
|
1739
|
+
});
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
if (![
|
|
1743
|
+
"Asset",
|
|
1744
|
+
"Course",
|
|
1745
|
+
"CourseModule",
|
|
1746
|
+
"CoursePage",
|
|
1747
|
+
"Guide",
|
|
1748
|
+
"Person",
|
|
1749
|
+
"Wiki",
|
|
1750
|
+
"WikiPage"
|
|
1751
|
+
].includes(resource.type)) {
|
|
1752
|
+
errors.push({
|
|
1753
|
+
resource: getResourceLabel(resource),
|
|
1754
|
+
path: "/type",
|
|
1755
|
+
message: `Unsupported resource type: ${resource.type}`
|
|
1756
|
+
});
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (!Object.hasOwn(resource, "apiVersion") || typeof resource.apiVersion !== "string") {
|
|
1760
|
+
errors.push({
|
|
1761
|
+
resource: getResourceLabel(resource),
|
|
1762
|
+
path: "/apiVersion",
|
|
1763
|
+
message: "Resource apiVersion must be a string"
|
|
1764
|
+
});
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
if (resource.type === "Asset") {
|
|
1768
|
+
const validation = validateAssetValue(resource);
|
|
1769
|
+
if (!validation.ok) for (const diagnostic of validation.diagnostics) errors.push({
|
|
1770
|
+
id: diagnostic.id,
|
|
1771
|
+
resource: getResourceLabel(resource),
|
|
1772
|
+
path: diagnostic.location.jsonPointer ?? "/",
|
|
1773
|
+
message: diagnostic.message
|
|
1774
|
+
});
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
const validate = validators.get(`${resource.type}/${resource.apiVersion}`);
|
|
1778
|
+
if (!validate) {
|
|
1779
|
+
errors.push({
|
|
1780
|
+
id: "resource-unsupported-version",
|
|
1781
|
+
resource: getResourceLabel(resource),
|
|
1782
|
+
path: "/apiVersion",
|
|
1783
|
+
message: `Unsupported ${resource.type} apiVersion: ${resource.apiVersion}`
|
|
1784
|
+
});
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
if (!validate(resource)) for (const err of validate.errors ?? []) errors.push({
|
|
1788
|
+
resource: getResourceLabel(resource),
|
|
1789
|
+
path: err.instancePath || "/",
|
|
1790
|
+
message: err.message ?? "Unknown error"
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
return {
|
|
1794
|
+
valid: errors.length === 0,
|
|
1795
|
+
errors
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
//#endregion
|
|
1799
|
+
//#region src/assets/identity.ts
|
|
1800
|
+
function createTopikAssetSemanticRecord(assets, references) {
|
|
1801
|
+
return {
|
|
1802
|
+
descriptor: "topik-asset-semantic-v1",
|
|
1803
|
+
assetNames: assets.map((asset) => asset.name).sort(compareUtf8$1),
|
|
1804
|
+
references: [...references].sort((left, right) => compareUtf8$1(`${left.resource}\0${left.position}\0${left.slot}\0${left.name}`, `${right.resource}\0${right.position}\0${right.slot}\0${right.name}`))
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
function createTopikMaterializationRecord(resources, payloads) {
|
|
1808
|
+
const resourceKeys = /* @__PURE__ */ new Set();
|
|
1809
|
+
const payloadPaths = /* @__PURE__ */ new Set();
|
|
1810
|
+
for (const input of resources) {
|
|
1811
|
+
const key = `${input.resource.type}/${input.resource.name}`;
|
|
1812
|
+
if (resourceKeys.has(key)) throw new TypeError("Materialization repeats a resource descriptor");
|
|
1813
|
+
resourceKeys.add(key);
|
|
1814
|
+
}
|
|
1815
|
+
for (const input of payloads) {
|
|
1816
|
+
if (payloadPaths.has(input.path)) throw new TypeError("Materialization repeats a payload path");
|
|
1817
|
+
payloadPaths.add(input.path);
|
|
1818
|
+
}
|
|
1819
|
+
return {
|
|
1820
|
+
descriptor: TOPIK_MATERIALIZATION_VERSION,
|
|
1821
|
+
resources: resources.map((input) => ({
|
|
1822
|
+
resource: `${input.resource.type}/${input.resource.name}`,
|
|
1823
|
+
path: `resources/${input.resource.type}/${input.resource.name}.json`,
|
|
1824
|
+
size: input.bytes.byteLength,
|
|
1825
|
+
sha256: sha256$1(input.bytes)
|
|
1826
|
+
})).sort((left, right) => compareUtf8$1(left.resource, right.resource)),
|
|
1827
|
+
payloads: payloads.map((input) => ({
|
|
1828
|
+
path: input.path,
|
|
1829
|
+
size: input.bytes.byteLength,
|
|
1830
|
+
sha256: sha256$1(input.bytes),
|
|
1831
|
+
assetNames: [...input.assetNames].sort(compareUtf8$1)
|
|
1832
|
+
})).sort((left, right) => compareUtf8$1(left.path, right.path))
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function validateTopikMaterializationRecord(record, resources, semantic) {
|
|
1836
|
+
if (!isTopikJsonDataValue(record) || !isRecord(record) || !Object.hasOwn(record, "descriptor") || typeof record.descriptor !== "string") return materializationFailure("TOPIK_ASSET_SCHEMA_INVALID", "Materialization record is malformed");
|
|
1837
|
+
if (record.descriptor !== "topik-materialization-v1") return materializationFailure("TOPIK_ASSET_UNSUPPORTED_VERSION", "Materialization descriptor version is unsupported", "topik-materialization-unknown");
|
|
1838
|
+
if (!isMaterializationRecordV1(record)) return materializationFailure("TOPIK_ASSET_SCHEMA_INVALID", "Materialization record is malformed");
|
|
1839
|
+
if (!hasCanonicalMaterializationStructure(record)) return materializationFailure("TOPIK_ASSET_INVENTORY_INCOMPLETE", "Materialization inventory records are noncanonical or duplicated");
|
|
1840
|
+
if (!validateResources(resources).valid) return materializationFailure("TOPIK_ASSET_INVENTORY_INCOMPLETE", "Materialization requires a valid complete compiled resource set");
|
|
1841
|
+
if (!isTopikJsonDataValue(semantic) || !isRecord(semantic) || !isSemanticRecordV1(semantic)) return materializationFailure("TOPIK_ASSET_SCHEMA_INVALID", "Semantic record is malformed");
|
|
1842
|
+
if (semantic.descriptor !== "topik-asset-semantic-v1") return materializationFailure("TOPIK_ASSET_UNSUPPORTED_VERSION", "Semantic descriptor version is unsupported", "topik-asset-semantic-unknown");
|
|
1843
|
+
const expectedSemantic = expectedSemanticRecord(resources);
|
|
1844
|
+
if (expectedSemantic === void 0 || serializeTopikJson(semantic) !== serializeTopikJson(expectedSemantic)) return materializationFailure("TOPIK_ASSET_INVENTORY_INCOMPLETE", "Semantic mappings do not close over the compiled Asset set");
|
|
1845
|
+
const expected = expectedMaterializationRecord(resources);
|
|
1846
|
+
if (expected === void 0 || serializeTopikJson(record) !== serializeTopikJson(expected)) return materializationFailure("TOPIK_ASSET_INVENTORY_INCOMPLETE", "Materialization does not exactly describe the compiled resource set");
|
|
1847
|
+
return {
|
|
1848
|
+
ok: true,
|
|
1849
|
+
value: record,
|
|
1850
|
+
diagnostics: []
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function isSemanticRecordV1(value) {
|
|
1854
|
+
return hasExactKeys(value, [
|
|
1855
|
+
"assetNames",
|
|
1856
|
+
"descriptor",
|
|
1857
|
+
"references"
|
|
1858
|
+
]) && typeof value.descriptor === "string" && Array.isArray(value.assetNames) && value.assetNames.every((name) => typeof name === "string") && Array.isArray(value.references) && value.references.every((reference) => isRecord(reference) && hasExactKeys(reference, [
|
|
1859
|
+
"name",
|
|
1860
|
+
"position",
|
|
1861
|
+
"resource",
|
|
1862
|
+
"slot"
|
|
1863
|
+
]) && typeof reference.name === "string" && typeof reference.position === "string" && typeof reference.resource === "string" && typeof reference.slot === "string");
|
|
1864
|
+
}
|
|
1865
|
+
function expectedSemanticRecord(resources) {
|
|
1866
|
+
const assets = resources.filter((resource) => resource.type === "Asset");
|
|
1867
|
+
const assetNames = new Set(assets.map((asset) => asset.name));
|
|
1868
|
+
if (assetNames.size !== assets.length) return void 0;
|
|
1869
|
+
const references = [];
|
|
1870
|
+
for (const resource of resources) {
|
|
1871
|
+
if (resource.type !== "Guide" && resource.type !== "WikiPage" && resource.type !== "CoursePage" || resource.spec.content.format !== "topik") continue;
|
|
1872
|
+
const content = resource.spec.content.value;
|
|
1873
|
+
if (!validateTopikContent(content, { allowCompiledAssetReferences: true }).valid) return;
|
|
1874
|
+
for (const occurrence of extractTopikAssetOccurrences(content)) {
|
|
1875
|
+
const parsedValidation = validateTopikAssetReference(occurrence.parsedReference);
|
|
1876
|
+
if (occurrence.kind === "external-https" || occurrence.reference.length === 0 && parsedValidation.valid && parsedValidation.kind === "external-https") continue;
|
|
1877
|
+
if (occurrence.kind !== "asset") return void 0;
|
|
1878
|
+
const name = occurrence.reference.slice(6);
|
|
1879
|
+
if (!assetNames.has(name)) return void 0;
|
|
1880
|
+
references.push({
|
|
1881
|
+
resource: `${resource.type}/${resource.name}`,
|
|
1882
|
+
position: occurrence.position,
|
|
1883
|
+
slot: occurrence.slot,
|
|
1884
|
+
name
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const referencedNames = new Set(references.map((reference) => reference.name));
|
|
1889
|
+
if (assets.some((asset) => !referencedNames.has(asset.name))) return void 0;
|
|
1890
|
+
return createTopikAssetSemanticRecord(assets, references);
|
|
1891
|
+
}
|
|
1892
|
+
function isMaterializationRecordV1(value) {
|
|
1893
|
+
if (!hasExactKeys(value, [
|
|
1894
|
+
"descriptor",
|
|
1895
|
+
"payloads",
|
|
1896
|
+
"resources"
|
|
1897
|
+
]) || !Array.isArray(value.resources) || !Array.isArray(value.payloads)) return false;
|
|
1898
|
+
return value.resources.every((entry) => isRecord(entry) && hasExactKeys(entry, [
|
|
1899
|
+
"path",
|
|
1900
|
+
"resource",
|
|
1901
|
+
"sha256",
|
|
1902
|
+
"size"
|
|
1903
|
+
]) && typeof entry.resource === "string" && typeof entry.path === "string" && validInventorySize(entry.size, TOPIK_ASSET_LIMITS.maxDescriptorBytes) && isSha256(entry.sha256)) && value.payloads.every((entry) => isRecord(entry) && hasExactKeys(entry, [
|
|
1904
|
+
"assetNames",
|
|
1905
|
+
"path",
|
|
1906
|
+
"sha256",
|
|
1907
|
+
"size"
|
|
1908
|
+
]) && typeof entry.path === "string" && validInventorySize(entry.size, TOPIK_ASSET_LIMITS.maxAssetBytes) && isSha256(entry.sha256) && Array.isArray(entry.assetNames) && entry.assetNames.every((name) => typeof name === "string"));
|
|
1909
|
+
}
|
|
1910
|
+
function hasCanonicalMaterializationStructure(record) {
|
|
1911
|
+
const resourceKeys = record.resources.map((entry) => entry.resource);
|
|
1912
|
+
const resourcePaths = record.resources.map((entry) => entry.path);
|
|
1913
|
+
const payloadPaths = record.payloads.map((entry) => entry.path);
|
|
1914
|
+
if (new Set(resourceKeys).size !== resourceKeys.length || new Set(resourcePaths).size !== resourcePaths.length || new Set(payloadPaths).size !== payloadPaths.length || !isSorted(resourceKeys) || !isSorted(payloadPaths) || record.payloads.some((entry) => new Set(entry.assetNames).size !== entry.assetNames.length || !isSorted(entry.assetNames))) return false;
|
|
1915
|
+
return validateTopikPathSet([...resourcePaths, ...payloadPaths]).ok;
|
|
1916
|
+
}
|
|
1917
|
+
function expectedMaterializationRecord(resources) {
|
|
1918
|
+
let descriptorRecord;
|
|
1919
|
+
try {
|
|
1920
|
+
descriptorRecord = createTopikMaterializationRecord(resources.map((resource) => ({
|
|
1921
|
+
resource,
|
|
1922
|
+
bytes: new TextEncoder().encode(serializeTopikJson(resource))
|
|
1923
|
+
})), []);
|
|
1924
|
+
} catch {
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
const payloads = /* @__PURE__ */ new Map();
|
|
1928
|
+
for (const resource of resources) {
|
|
1929
|
+
if (resource.type !== "Asset") continue;
|
|
1930
|
+
const { integrity, size, uri } = resource.spec;
|
|
1931
|
+
if (integrity === void 0 || size === void 0 || size > TOPIK_ASSET_LIMITS.maxAssetBytes || uri !== `blobs/${integrity.slice(7)}`) return;
|
|
1932
|
+
const sha256 = integrity.slice(7);
|
|
1933
|
+
if (!isSha256(sha256)) return void 0;
|
|
1934
|
+
const existing = payloads.get(uri);
|
|
1935
|
+
if (existing !== void 0) {
|
|
1936
|
+
if (existing.sha256 !== sha256 || existing.size !== size) return void 0;
|
|
1937
|
+
existing.assetNames.push(resource.name);
|
|
1938
|
+
} else payloads.set(uri, {
|
|
1939
|
+
path: uri,
|
|
1940
|
+
size,
|
|
1941
|
+
sha256,
|
|
1942
|
+
assetNames: [resource.name]
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
const sortedPayloads = [...payloads.values()].map((payload) => ({
|
|
1946
|
+
...payload,
|
|
1947
|
+
assetNames: [...payload.assetNames].sort(compareUtf8$1)
|
|
1948
|
+
})).sort((left, right) => compareUtf8$1(left.path, right.path));
|
|
1949
|
+
const expected = {
|
|
1950
|
+
descriptor: TOPIK_MATERIALIZATION_VERSION,
|
|
1951
|
+
resources: descriptorRecord.resources,
|
|
1952
|
+
payloads: sortedPayloads
|
|
1953
|
+
};
|
|
1954
|
+
return hasCanonicalMaterializationStructure(expected) ? expected : void 0;
|
|
1955
|
+
}
|
|
1956
|
+
function materializationFailure(id, message, descriptorVersion = TOPIK_MATERIALIZATION_VERSION) {
|
|
1957
|
+
return {
|
|
1958
|
+
ok: false,
|
|
1959
|
+
diagnostics: [topikAssetDiagnostic(id, message, {
|
|
1960
|
+
consequence: "block-identity-and-writes",
|
|
1961
|
+
descriptorVersion,
|
|
1962
|
+
recovery: "upgrade-reader"
|
|
1963
|
+
})]
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function validInventorySize(value, maximum) {
|
|
1967
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum;
|
|
1968
|
+
}
|
|
1969
|
+
function isSha256(value) {
|
|
1970
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/u.test(value);
|
|
1971
|
+
}
|
|
1972
|
+
function hasExactKeys(value, expected) {
|
|
1973
|
+
const keys = Object.keys(value).sort();
|
|
1974
|
+
return keys.length === expected.length && keys.every((key, index) => key === expected[index]);
|
|
1975
|
+
}
|
|
1976
|
+
function isRecord(value) {
|
|
1977
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1978
|
+
}
|
|
1979
|
+
function isSorted(values) {
|
|
1980
|
+
return values.every((value, index) => index === 0 || compareUtf8$1(values[index - 1], value) <= 0);
|
|
1981
|
+
}
|
|
1982
|
+
function digestTopikAssetSemanticRecord(record) {
|
|
1983
|
+
return sha256$1(new TextEncoder().encode(serializeTopikJson(record)));
|
|
1984
|
+
}
|
|
1985
|
+
function digestTopikMaterializationRecord(record) {
|
|
1986
|
+
return sha256$1(new TextEncoder().encode(serializeTopikJson(record)));
|
|
1987
|
+
}
|
|
1988
|
+
function compareTopikAssetIdentities(left, right) {
|
|
1989
|
+
if (left.semantic.descriptor !== right.semantic.descriptor || left.materialization.descriptor !== right.materialization.descriptor) return {
|
|
1990
|
+
ok: false,
|
|
1991
|
+
diagnostics: [topikAssetDiagnostic("TOPIK_ASSET_VERSION_INCOMPARABLE", "Identity descriptor versions differ", {
|
|
1992
|
+
consequence: "block-identity-and-writes",
|
|
1993
|
+
recovery: "upgrade-reader"
|
|
1994
|
+
})]
|
|
1995
|
+
};
|
|
1996
|
+
return {
|
|
1997
|
+
ok: true,
|
|
1998
|
+
value: {
|
|
1999
|
+
semanticEqual: digestTopikAssetSemanticRecord(left.semantic) === digestTopikAssetSemanticRecord(right.semantic),
|
|
2000
|
+
exactEqual: digestTopikMaterializationRecord(left.materialization) === digestTopikMaterializationRecord(right.materialization)
|
|
2001
|
+
},
|
|
2002
|
+
diagnostics: []
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
function sha256$1(bytes) {
|
|
2006
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
2007
|
+
}
|
|
2008
|
+
function compareUtf8$1(left, right) {
|
|
2009
|
+
return Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
2010
|
+
}
|
|
2011
|
+
//#endregion
|
|
2012
|
+
//#region src/assets/media.ts
|
|
2013
|
+
const MARKUP_INSPECTION_LIMIT = 65536;
|
|
2014
|
+
const TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE = "application/x-topik-active-content";
|
|
2015
|
+
/** Sniff exact bytes conservatively. Unknown safe bytes remain opaque downloads. */
|
|
2016
|
+
function sniffPortableMediaType(bytes) {
|
|
2017
|
+
const has = (...values) => values.every((value, index) => bytes[index] === value);
|
|
2018
|
+
if (has(137, 80, 78, 71, 13, 10, 26, 10)) return "image/png";
|
|
2019
|
+
if (has(255, 216, 255)) return "image/jpeg";
|
|
2020
|
+
if (ascii(bytes, 0, 6) === "GIF87a" || ascii(bytes, 0, 6) === "GIF89a") return "image/gif";
|
|
2021
|
+
if (ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WEBP") return "image/webp";
|
|
2022
|
+
if (ascii(bytes, 4, 8).includes("ftypavif")) return "image/avif";
|
|
2023
|
+
if (ascii(bytes, 0, 2) === "BM") return "image/bmp";
|
|
2024
|
+
if (has(0, 0, 1, 0)) return "image/x-icon";
|
|
2025
|
+
if (ascii(bytes, 0, 5) === "%PDF-") return "application/pdf";
|
|
2026
|
+
if (has(80, 75, 3, 4) || has(80, 75, 5, 6)) return "application/zip";
|
|
2027
|
+
if (has(31, 139)) return "application/gzip";
|
|
2028
|
+
if (ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 4) === "WAVE") return "audio/wav";
|
|
2029
|
+
if (ascii(bytes, 0, 4) === "OggS") return "audio/ogg";
|
|
2030
|
+
if (ascii(bytes, 0, 4) === "fLaC") return "audio/flac";
|
|
2031
|
+
if (ascii(bytes, 0, 3) === "ID3" || bytes[0] === 255 && ((bytes[1] ?? 0) & 224) === 224) return "audio/mpeg";
|
|
2032
|
+
if (ascii(bytes, 4, 4) === "ftyp") return "video/mp4";
|
|
2033
|
+
if (has(127, 69, 76, 70) || has(77, 90) || has(254, 237, 250, 206) || has(206, 250, 237, 254) || has(254, 237, 250, 207) || has(207, 250, 237, 254) || has(202, 254, 186, 190) || has(35, 33)) return "application/x-executable";
|
|
2034
|
+
if (has(0, 97, 115, 109)) return "application/wasm";
|
|
2035
|
+
return sniffPortableMarkupType(bytes) ?? "application/octet-stream";
|
|
2036
|
+
}
|
|
2037
|
+
function isTopikActiveMediaType(mediaType) {
|
|
2038
|
+
return [
|
|
2039
|
+
"text/html",
|
|
2040
|
+
"image/svg+xml",
|
|
2041
|
+
"application/javascript",
|
|
2042
|
+
"text/javascript",
|
|
2043
|
+
"application/x-executable",
|
|
2044
|
+
"application/wasm",
|
|
2045
|
+
TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE
|
|
2046
|
+
].includes(mediaType);
|
|
2047
|
+
}
|
|
2048
|
+
function isInlineMediaCompatible(mediaType, role) {
|
|
2049
|
+
return role.startsWith("image") ? mediaType.startsWith("image/") : /^(?:image|audio|video)\//u.test(mediaType);
|
|
2050
|
+
}
|
|
2051
|
+
function sniffPortableMarkupType(bytes) {
|
|
2052
|
+
const boundary = Math.min(bytes.byteLength, MARKUP_INSPECTION_LIMIT);
|
|
2053
|
+
const completionBytes = trailingUtf8Completion(bytes.slice(0, boundary));
|
|
2054
|
+
if (completionBytes > bytes.byteLength - boundary) return TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE;
|
|
2055
|
+
const inspectedEnd = boundary + completionBytes;
|
|
2056
|
+
if (completionBytes > 0) {
|
|
2057
|
+
const sequenceStart = trailingUtf8SequenceStart(bytes.slice(0, boundary));
|
|
2058
|
+
try {
|
|
2059
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes.slice(sequenceStart, inspectedEnd));
|
|
2060
|
+
} catch {
|
|
2061
|
+
return TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE;
|
|
187
2062
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
2063
|
+
}
|
|
2064
|
+
let text;
|
|
2065
|
+
try {
|
|
2066
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes.slice(0, inspectedEnd)).toLowerCase();
|
|
2067
|
+
} catch {
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
let cursor = skipMarkupWhitespace(text, 0);
|
|
2071
|
+
let consumedPreamble = false;
|
|
2072
|
+
while (cursor < text.length) {
|
|
2073
|
+
if (text.startsWith("<!--", cursor)) {
|
|
2074
|
+
const end = text.indexOf("-->", cursor + 4);
|
|
2075
|
+
if (end === -1) return "text/html";
|
|
2076
|
+
consumedPreamble = true;
|
|
2077
|
+
cursor = skipMarkupWhitespace(text, end + 3);
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (text.startsWith("<?xml", cursor)) {
|
|
2081
|
+
const end = text.indexOf("?>", cursor + 5);
|
|
2082
|
+
if (end === -1) return TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE;
|
|
2083
|
+
consumedPreamble = true;
|
|
2084
|
+
cursor = skipMarkupWhitespace(text, end + 2);
|
|
2085
|
+
continue;
|
|
192
2086
|
}
|
|
2087
|
+
break;
|
|
193
2088
|
}
|
|
2089
|
+
if (cursor === text.length) return bytes.byteLength > inspectedEnd && (cursor > 0 || consumedPreamble) ? TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE : void 0;
|
|
2090
|
+
const markup = text.slice(cursor);
|
|
2091
|
+
if (/<!doctype\s+svg\b/u.test(markup) || /<svg\b/u.test(markup)) return "image/svg+xml";
|
|
2092
|
+
if (/(?:<!doctype\s+html\b|<[a-z][a-z0-9:-]*(?:\s|>|\/))/u.test(markup) || /<[^>]+\bon[a-z][a-z0-9_-]*\s*=/u.test(markup) || /<[^>]+(?:href|src)\s*=\s*["']?\s*javascript:/u.test(markup) || /<script\b/u.test(markup)) return "text/html";
|
|
2093
|
+
if (markup.startsWith("<") && (consumedPreamble || bytes.byteLength > inspectedEnd)) return TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE;
|
|
194
2094
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
2095
|
+
function trailingUtf8Completion(bytes) {
|
|
2096
|
+
if (bytes.byteLength === 0) return 0;
|
|
2097
|
+
const start = trailingUtf8SequenceStart(bytes);
|
|
2098
|
+
const lead = bytes[start];
|
|
2099
|
+
const width = lead >= 194 && lead <= 223 ? 2 : lead >= 224 && lead <= 239 ? 3 : lead >= 240 && lead <= 244 ? 4 : 1;
|
|
2100
|
+
const present = bytes.byteLength - start;
|
|
2101
|
+
return width > present ? width - present : 0;
|
|
2102
|
+
}
|
|
2103
|
+
function trailingUtf8SequenceStart(bytes) {
|
|
2104
|
+
let start = bytes.byteLength - 1;
|
|
2105
|
+
while (start > 0 && bytes[start] >= 128 && bytes[start] <= 191) start--;
|
|
2106
|
+
return start;
|
|
2107
|
+
}
|
|
2108
|
+
function skipMarkupWhitespace(value, start) {
|
|
2109
|
+
let cursor = start;
|
|
2110
|
+
while (/\s/u.test(value[cursor] ?? "")) cursor++;
|
|
2111
|
+
return cursor;
|
|
2112
|
+
}
|
|
2113
|
+
function ascii(bytes, start, length) {
|
|
2114
|
+
return String.fromCharCode(...bytes.slice(start, start + length));
|
|
204
2115
|
}
|
|
205
2116
|
//#endregion
|
|
206
2117
|
//#region src/compile/assets.ts
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
".avif": "image/avif",
|
|
215
|
-
".ico": "image/x-icon",
|
|
216
|
-
".bmp": "image/bmp",
|
|
217
|
-
".mp4": "video/mp4",
|
|
218
|
-
".webm": "video/webm",
|
|
219
|
-
".pdf": "application/pdf",
|
|
220
|
-
".zip": "application/zip",
|
|
221
|
-
".tar": "application/x-tar",
|
|
222
|
-
".gz": "application/gzip",
|
|
223
|
-
".mp3": "audio/mpeg",
|
|
224
|
-
".wav": "audio/wav",
|
|
225
|
-
".ogg": "audio/ogg",
|
|
226
|
-
".m4a": "audio/mp4",
|
|
227
|
-
".flac": "audio/flac",
|
|
228
|
-
".csv": "text/csv",
|
|
229
|
-
".txt": "text/plain",
|
|
230
|
-
".doc": "application/msword",
|
|
231
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
232
|
-
".xls": "application/vnd.ms-excel",
|
|
233
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
234
|
-
".ppt": "application/vnd.ms-powerpoint",
|
|
235
|
-
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
2118
|
+
var AssetCompilationError = class extends Error {
|
|
2119
|
+
diagnostics;
|
|
2120
|
+
constructor(message, diagnostics = []) {
|
|
2121
|
+
super(message);
|
|
2122
|
+
this.diagnostics = diagnostics;
|
|
2123
|
+
this.name = "AssetCompilationError";
|
|
2124
|
+
}
|
|
236
2125
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
2126
|
+
/** Discover and resolve one compilation-wide Asset set and deduplicated payload inventory. */
|
|
2127
|
+
async function compileAssetResources(input) {
|
|
2128
|
+
return compileAssetResourcesWithReader(input, readPortableAssetFile);
|
|
2129
|
+
}
|
|
2130
|
+
async function compileAssetResourcesWithReader(input, readAssetFile) {
|
|
2131
|
+
const receivedResources = input.resources;
|
|
2132
|
+
if (receivedResources.some((resource) => resource.type === "Asset")) throw new AssetCompilationError("Asset resources are compiler output and cannot be inputs", [topikAssetDiagnostic("TOPIK_ASSET_SCHEMA_INVALID", "Asset resources are compiler output and cannot be inputs")]);
|
|
2133
|
+
const sourceResources = receivedResources;
|
|
2134
|
+
const initialValidation = validateResources(sourceResources);
|
|
2135
|
+
if (!initialValidation.valid) throw new AssetCompilationError("Asset compilation received an invalid resource", resourceValidationDiagnostics(initialValidation.errors, sourceResources));
|
|
2136
|
+
const sourcePaths = /* @__PURE__ */ new Map();
|
|
2137
|
+
for (const [resource, source] of Object.entries(input.sourcePathsByResource)) {
|
|
2138
|
+
const path = requirePath(source, "Content source path is not portable");
|
|
2139
|
+
if (sourcePaths.has(resource)) throw new AssetCompilationError("Content source binding repeats");
|
|
2140
|
+
sourcePaths.set(resource, path);
|
|
2141
|
+
}
|
|
2142
|
+
const contentResources = sourceResources.filter(isContentBearingResource);
|
|
2143
|
+
const topikContentResources = contentResources.filter(isTopikContentResource);
|
|
2144
|
+
for (const resource of contentResources) if (!sourcePaths.has(resourceKey$1(resource))) throw new AssetCompilationError("Content-bearing resource lacks a source-path binding");
|
|
2145
|
+
for (const key of sourcePaths.keys()) if (!contentResources.some((resource) => resourceKey$1(resource) === key)) throw new AssetCompilationError("Source-path binding names a non-content resource");
|
|
2146
|
+
for (const resource of topikContentResources) {
|
|
2147
|
+
const sourcePath = sourcePaths.get(resourceKey$1(resource));
|
|
2148
|
+
const validation = validateTopikContent(resource.spec.content.value, { file: sourcePath });
|
|
2149
|
+
if (!validation.valid) throw new AssetCompilationError("Asset compilation received invalid Topik content", contentValidationDiagnostics(validation.errors, sourcePath));
|
|
2150
|
+
}
|
|
2151
|
+
const readCache = /* @__PURE__ */ new Map();
|
|
2152
|
+
const localPathByGeneratedName = /* @__PURE__ */ new Map();
|
|
2153
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2154
|
+
const mappings = [];
|
|
2155
|
+
const rolesByName = /* @__PURE__ */ new Map();
|
|
2156
|
+
const allPaths = new Set(sourcePaths.values());
|
|
2157
|
+
const protectedPaths = new Set(sourcePaths.values());
|
|
2158
|
+
for (const source of input.protectedSourcePaths ?? []) {
|
|
2159
|
+
const path = requirePath(source, "Protected compiler source path is not portable");
|
|
2160
|
+
allPaths.add(path);
|
|
2161
|
+
protectedPaths.add(path);
|
|
2162
|
+
}
|
|
2163
|
+
for (const resource of topikContentResources) {
|
|
2164
|
+
const key = resourceKey$1(resource);
|
|
2165
|
+
const sourcePath = sourcePaths.get(key);
|
|
2166
|
+
const resourceReplacements = /* @__PURE__ */ new Map();
|
|
2167
|
+
replacements.set(key, resourceReplacements);
|
|
2168
|
+
const occurrences = extractTopikAssetOccurrences(resource.spec.content.value, { includeGenericLinkCandidates: true });
|
|
2169
|
+
for (const occurrence of occurrences) {
|
|
2170
|
+
if (occurrence.kind === "asset" || occurrence.kind === "reserved-asset") throw referenceError("Source content cannot use the reserved Asset locator scheme", occurrence, sourcePath);
|
|
2171
|
+
if (occurrence.kind === "external-https") continue;
|
|
2172
|
+
if (occurrence.kind === "unsafe") {
|
|
2173
|
+
const parsedValidation = validateTopikAssetReference(occurrence.parsedReference);
|
|
2174
|
+
if (!parsedValidation.valid && parsedValidation.failureKind === "external" && /^https?:/iu.test(occurrence.parsedReference)) throw referenceError("Asset-capable slot contains an unsafe reference", occurrence, sourcePath, occurrence.parsedReference);
|
|
2175
|
+
if (occurrence.reference.length === 0 && parsedValidation.valid && parsedValidation.kind === "external-https") continue;
|
|
2176
|
+
if (occurrence.slot === "link.href") {
|
|
2177
|
+
if (parsedValidation.valid && parsedValidation.kind === "local") {
|
|
2178
|
+
let parsedPath;
|
|
2179
|
+
try {
|
|
2180
|
+
parsedPath = resolveOccurrencePath(sourcePath, occurrence.parsedReference);
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
if (error instanceof AssetCompilationError) continue;
|
|
2183
|
+
throw error;
|
|
2184
|
+
}
|
|
2185
|
+
if (protectedPaths.has(parsedPath)) continue;
|
|
2186
|
+
if (await readGenericNavigationCandidate(readAssetFile, input.rootDir, parsedPath) !== void 0) throw referenceError("Proven download reference is not canonical", occurrence, sourcePath);
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (!/^https?:/iu.test(occurrence.reference)) continue;
|
|
2190
|
+
}
|
|
2191
|
+
throw referenceError("Asset-capable slot contains an unsafe reference", occurrence, sourcePath);
|
|
2192
|
+
}
|
|
2193
|
+
const ordinaryNavigation = occurrence.slot === "link.href";
|
|
2194
|
+
let normalizedPath;
|
|
2195
|
+
if (ordinaryNavigation) try {
|
|
2196
|
+
normalizedPath = resolveOccurrencePath(sourcePath, occurrence.reference);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
if (error instanceof AssetCompilationError) continue;
|
|
2199
|
+
throw error;
|
|
2200
|
+
}
|
|
2201
|
+
else normalizedPath = resolveOccurrencePath(sourcePath, occurrence.reference);
|
|
2202
|
+
if (protectedPaths.has(normalizedPath)) {
|
|
2203
|
+
if (ordinaryNavigation) continue;
|
|
2204
|
+
throw ambiguousReference("Local reference conflicts with a compiler input path", normalizedPath, occurrence.position);
|
|
2205
|
+
}
|
|
2206
|
+
if (ordinaryNavigation) {
|
|
2207
|
+
const proof = await readGenericNavigationCandidate(readAssetFile, input.rootDir, normalizedPath);
|
|
2208
|
+
if (proof === void 0) continue;
|
|
2209
|
+
readCache.set(normalizedPath, proof);
|
|
2210
|
+
}
|
|
2211
|
+
if (input.sourceNamespace === void 0) throw new AssetCompilationError("Automatic Asset discovery requires a stable source namespace", [topikAssetDiagnostic("TOPIK_ASSET_SOURCE_NAMESPACE_REQUIRED", "Provide the versioned stable source namespace option", { location: {
|
|
2212
|
+
path: normalizedPath,
|
|
2213
|
+
contentPosition: occurrence.position
|
|
2214
|
+
} })]);
|
|
2215
|
+
const generated = generateAutomaticAssetName({
|
|
2216
|
+
stableSourceNamespace: input.sourceNamespace,
|
|
2217
|
+
normalizedPath
|
|
260
2218
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const url = stringAttr(node, "src");
|
|
269
|
-
if (url) collect(url, {
|
|
270
|
-
node,
|
|
271
|
-
attr: "src"
|
|
272
|
-
}, false);
|
|
273
|
-
} else if (node.type === "link") {
|
|
274
|
-
const url = stringAttr(node, "href");
|
|
275
|
-
if (url) collect(url, {
|
|
276
|
-
node,
|
|
277
|
-
attr: "href"
|
|
278
|
-
}, true);
|
|
279
|
-
} else if (node.type === "tag") for (const attr of ASSET_TAG_ATTRS) {
|
|
280
|
-
const url = stringAttr(node, attr);
|
|
281
|
-
if (url) collect(url, {
|
|
282
|
-
node,
|
|
283
|
-
attr
|
|
284
|
-
}, true);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
if (refs.size === 0) return {
|
|
288
|
-
content: source,
|
|
289
|
-
assets: [],
|
|
290
|
-
manifest: []
|
|
291
|
-
};
|
|
292
|
-
const urlToId = /* @__PURE__ */ new Map();
|
|
293
|
-
const byName = /* @__PURE__ */ new Map();
|
|
294
|
-
const refList = Array.from(refs.values());
|
|
295
|
-
const settled = await Promise.allSettled(refList.map((ref) => toAsset(ref)));
|
|
296
|
-
const failures = [];
|
|
297
|
-
for (let i = 0; i < settled.length; i++) {
|
|
298
|
-
const result = settled[i];
|
|
299
|
-
if (result.status === "fulfilled") {
|
|
300
|
-
urlToId.set(refList[i].original, result.value.name);
|
|
301
|
-
byName.set(result.value.name, result.value);
|
|
302
|
-
} else failures.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
303
|
-
}
|
|
304
|
-
if (failures.length > 0) throw new Error(`Failed to extract ${failures.length} asset(s):\n - ${failures.join("\n - ")}`);
|
|
305
|
-
const manifest = [];
|
|
306
|
-
const seen = /* @__PURE__ */ new Set();
|
|
307
|
-
for (const ref of refs.values()) {
|
|
308
|
-
const name = urlToId.get(ref.original);
|
|
309
|
-
if (name && !seen.has(name)) {
|
|
310
|
-
manifest.push(name);
|
|
311
|
-
seen.add(name);
|
|
2219
|
+
if (!generated.ok) throw new AssetCompilationError("Automatic Asset name could not be generated", generated.diagnostics);
|
|
2220
|
+
const name = generated.value;
|
|
2221
|
+
registerGeneratedAssetPath(localPathByGeneratedName, name, normalizedPath);
|
|
2222
|
+
if (localPathByGeneratedName.size > TOPIK_ASSET_LIMITS.maxAssets) throw new AssetCompilationError("Compilation exceeds its Asset limit");
|
|
2223
|
+
resourceReplacements.set(occurrence.position, `asset:${name}`);
|
|
2224
|
+
addMapping(mappings, rolesByName, key, occurrence, name);
|
|
2225
|
+
allPaths.add(normalizedPath);
|
|
312
2226
|
}
|
|
313
2227
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
2228
|
+
const completePathSet = validateTopikPathSet([...allPaths]);
|
|
2229
|
+
if (!completePathSet.ok) throw new AssetCompilationError("Compilation paths collide", completePathSet.diagnostics);
|
|
2230
|
+
const resolvedAssets = [];
|
|
2231
|
+
const payloadsByDigest = /* @__PURE__ */ new Map();
|
|
2232
|
+
for (const [name, sourcePath] of localPathByGeneratedName) {
|
|
2233
|
+
const roles = rolesByName.get(name) ?? [];
|
|
2234
|
+
const file = readCache.get(sourcePath) ?? await requireAssetFile(input.rootDir, sourcePath, readAssetFile);
|
|
2235
|
+
readCache.set(sourcePath, file);
|
|
2236
|
+
const digest = sha256(file.bytes);
|
|
2237
|
+
const integrity = `sha256:${digest}`;
|
|
2238
|
+
const mediaType = sniffPortableMediaType(file.bytes);
|
|
2239
|
+
if (isTopikActiveMediaType(mediaType)) throw activeError(name, sourcePath);
|
|
2240
|
+
assertRoleMediaCompatibility(name, sourcePath, mediaType, roles);
|
|
2241
|
+
const payloadPath = parseAssetBlobUri(`${TOPIK_BLOB_OUTPUT_PREFIX}/${digest}`);
|
|
2242
|
+
resolvedAssets.push({
|
|
2243
|
+
apiVersion: "v1",
|
|
2244
|
+
type: "Asset",
|
|
2245
|
+
name,
|
|
2246
|
+
spec: {
|
|
2247
|
+
uri: payloadPath,
|
|
2248
|
+
integrity,
|
|
2249
|
+
size: file.bytes.byteLength,
|
|
2250
|
+
mediaType
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
const payload = payloadsByDigest.get(digest);
|
|
2254
|
+
if (payload !== void 0) {
|
|
2255
|
+
if (!equalBytes(payload.bytes, file.bytes)) throw new AssetCompilationError("SHA-256 payload collision was detected");
|
|
2256
|
+
payload.names.add(name);
|
|
2257
|
+
} else payloadsByDigest.set(digest, {
|
|
2258
|
+
bytes: file.bytes,
|
|
2259
|
+
mediaType,
|
|
2260
|
+
names: /* @__PURE__ */ new Set([name])
|
|
2261
|
+
});
|
|
318
2262
|
}
|
|
2263
|
+
const resources = [...sourceResources.map((resource) => {
|
|
2264
|
+
if (!isContentBearingResource(resource)) return resource;
|
|
2265
|
+
const key = resourceKey$1(resource);
|
|
2266
|
+
const sourcePath = sourcePaths.get(key);
|
|
2267
|
+
const byPosition = replacements.get(key);
|
|
2268
|
+
if (byPosition === void 0 || byPosition.size === 0) return resource;
|
|
2269
|
+
const rewrittenContent = rewriteTopikAssetOccurrences(resource.spec.content.value, (occurrence) => byPosition.get(occurrence.position), {
|
|
2270
|
+
includeGenericLinkCandidates: true,
|
|
2271
|
+
file: sourcePath
|
|
2272
|
+
});
|
|
2273
|
+
if (!rewrittenContent.ok) throw new AssetCompilationError("Asset compilation could not rewrite invalid content", contentValidationDiagnostics(rewrittenContent.diagnostics, sourcePath));
|
|
2274
|
+
return {
|
|
2275
|
+
...resource,
|
|
2276
|
+
spec: {
|
|
2277
|
+
...resource.spec,
|
|
2278
|
+
content: {
|
|
2279
|
+
...resource.spec.content,
|
|
2280
|
+
value: rewrittenContent.content
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
}), ...resolvedAssets].sort(compareResources);
|
|
2285
|
+
const finalValidation = validateResources(resources);
|
|
2286
|
+
if (!finalValidation.valid) throw new AssetCompilationError("Compiled resources are invalid", resourceValidationDiagnostics(finalValidation.errors, resources));
|
|
2287
|
+
const payloads = [...payloadsByDigest.entries()].map(([digest, payload]) => ({
|
|
2288
|
+
path: parseAssetBlobUri(`${TOPIK_BLOB_OUTPUT_PREFIX}/${digest}`),
|
|
2289
|
+
integrity: `sha256:${digest}`,
|
|
2290
|
+
mediaType: payload.mediaType,
|
|
2291
|
+
size: payload.bytes.byteLength,
|
|
2292
|
+
bytes: payload.bytes,
|
|
2293
|
+
assetNames: [...payload.names].sort(compareUtf8)
|
|
2294
|
+
})).sort((left, right) => compareUtf8(left.path, right.path));
|
|
2295
|
+
const semantic = createTopikAssetSemanticRecord(resolvedAssets, mappings);
|
|
2296
|
+
const materialization = createTopikMaterializationRecord(resources.map((resource) => ({
|
|
2297
|
+
resource,
|
|
2298
|
+
bytes: new TextEncoder().encode(serializeTopikJson(resource))
|
|
2299
|
+
})), payloads.map((payload) => ({
|
|
2300
|
+
path: payload.path,
|
|
2301
|
+
bytes: payload.bytes,
|
|
2302
|
+
assetNames: payload.assetNames
|
|
2303
|
+
})));
|
|
2304
|
+
const inventory = validateTopikMaterializationRecord(materialization, resources, semantic);
|
|
2305
|
+
if (!inventory.ok) throw new AssetCompilationError("Compiled inventory is incomplete", inventory.diagnostics);
|
|
319
2306
|
return {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
2307
|
+
resources,
|
|
2308
|
+
payloads,
|
|
2309
|
+
semantic,
|
|
2310
|
+
materialization
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
/** @internal Shared with the source test suite to prove generated-name collision handling. */
|
|
2314
|
+
function registerGeneratedAssetPath(paths, name, normalizedPath) {
|
|
2315
|
+
const previousPath = paths.get(name);
|
|
2316
|
+
if (previousPath !== void 0 && previousPath !== normalizedPath) throw collision("Generated Asset name collides with another normalized path", name);
|
|
2317
|
+
paths.set(name, normalizedPath);
|
|
2318
|
+
}
|
|
2319
|
+
function assertRoleMediaCompatibility(name, path, mediaType, roles) {
|
|
2320
|
+
if (roles.find((role) => role !== "download" && !isInlineMediaCompatible(mediaType, role)) === void 0) return;
|
|
2321
|
+
throw new AssetCompilationError("Asset media type is incompatible with its reference role", [topikAssetDiagnostic("TOPIK_ASSET_MEDIA_TYPE_MISMATCH", "Asset media type is incompatible with its reference role", { location: {
|
|
2322
|
+
key: name,
|
|
2323
|
+
path
|
|
2324
|
+
} })]);
|
|
2325
|
+
}
|
|
2326
|
+
function resourceValidationDiagnostics(errors, resources) {
|
|
2327
|
+
return errors.map((error) => {
|
|
2328
|
+
const descriptorVersion = safeResourceDescriptorVersion(resources.find((candidate) => typeof candidate === "object" && candidate !== null && Object.hasOwn(candidate, "type") && Object.hasOwn(candidate, "name") && `${String(candidate.type)}/${String(candidate.name)}` === error.resource));
|
|
2329
|
+
return topikAssetDiagnostic(isTopikAssetDiagnosticId(error.id) ? error.id : error.id === "resource-unsupported-version" ? "TOPIK_ASSET_UNSUPPORTED_VERSION" : "TOPIK_ASSET_SCHEMA_INVALID", error.id === "resource-unsupported-version" ? "Resource apiVersion is unsupported" : "Resource schema validation failed", {
|
|
2330
|
+
descriptorVersion,
|
|
2331
|
+
location: { jsonPointer: error.path }
|
|
2332
|
+
});
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
function isTopikAssetDiagnosticId(value) {
|
|
2336
|
+
return TOPIK_ASSET_DIAGNOSTIC_IDS.some((id) => id === value);
|
|
2337
|
+
}
|
|
2338
|
+
function contentValidationDiagnostics(diagnostics, sourcePath) {
|
|
2339
|
+
return diagnostics.map((diagnostic) => {
|
|
2340
|
+
const lines = diagnostic.lines.filter((line) => line > 0);
|
|
2341
|
+
const unsupportedNavigation = diagnostic.id === "link-asset-navigation-unsupported";
|
|
2342
|
+
return topikAssetDiagnostic(unsupportedNavigation ? "TOPIK_ASSET_REFERENCE_MALFORMED" : isTopikAssetDiagnosticId(diagnostic.id) ? diagnostic.id : "TOPIK_ASSET_SCHEMA_INVALID", unsupportedNavigation ? "Navigation-only targets cannot use Asset locators" : diagnostic.message, { location: {
|
|
2343
|
+
path: diagnostic.file ?? sourcePath,
|
|
2344
|
+
...lines.length === 0 ? {} : { contentPosition: `line ${lines.join(",")}` }
|
|
2345
|
+
} });
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
const KNOWN_RESOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
2349
|
+
"Asset",
|
|
2350
|
+
"Course",
|
|
2351
|
+
"CourseModule",
|
|
2352
|
+
"CoursePage",
|
|
2353
|
+
"Guide",
|
|
2354
|
+
"Person",
|
|
2355
|
+
"Wiki",
|
|
2356
|
+
"WikiPage"
|
|
2357
|
+
]);
|
|
2358
|
+
function safeResourceDescriptorVersion(resource) {
|
|
2359
|
+
if (typeof resource?.type !== "string" || !KNOWN_RESOURCE_TYPES.has(resource.type)) return "unknown-resource";
|
|
2360
|
+
return `${resource.type}/${resource.apiVersion === "v1" ? "v1" : "unsupported"}`;
|
|
2361
|
+
}
|
|
2362
|
+
async function requireAssetFile(root, path, readAssetFile) {
|
|
2363
|
+
const read = await readAssetFile({
|
|
2364
|
+
root,
|
|
2365
|
+
path
|
|
2366
|
+
});
|
|
2367
|
+
if (!read.ok || read.value.bytes === void 0) throw new AssetCompilationError("Local Asset bytes could not be proven", read.diagnostics);
|
|
2368
|
+
return read.value;
|
|
2369
|
+
}
|
|
2370
|
+
async function readGenericNavigationCandidate(readAssetFile, root, path) {
|
|
2371
|
+
if (await classifyPortableNavigationPath({
|
|
2372
|
+
root,
|
|
2373
|
+
path
|
|
2374
|
+
}) === "directory") return void 0;
|
|
2375
|
+
const proof = await readAssetFile({
|
|
2376
|
+
root,
|
|
2377
|
+
path
|
|
2378
|
+
});
|
|
2379
|
+
if (!proof.ok) {
|
|
2380
|
+
if (proof.diagnostics.every((diagnostic) => diagnostic.id === "TOPIK_ASSET_FILE_MISSING")) return;
|
|
2381
|
+
throw new AssetCompilationError("Existing generic-link target failed portable Asset proof", proof.diagnostics);
|
|
372
2382
|
}
|
|
2383
|
+
return proof.value.bytes === void 0 ? void 0 : proof.value;
|
|
373
2384
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
2385
|
+
function resolveOccurrencePath(sourcePath, reference) {
|
|
2386
|
+
const validation = validateTopikAssetReference(reference);
|
|
2387
|
+
if (!validation.valid || validation.kind !== "local") throw new AssetCompilationError("Local occurrence path is invalid", [topikAssetDiagnostic("TOPIK_ASSET_PATH_INVALID", "Local occurrence path is invalid")]);
|
|
2388
|
+
return requirePath(posix.join(posix.dirname(sourcePath), validation.decodedPath), "Resolved Asset path is outside the compilation root");
|
|
2389
|
+
}
|
|
2390
|
+
function addMapping(mappings, rolesByName, resource, occurrence, name) {
|
|
2391
|
+
mappings.push({
|
|
2392
|
+
resource,
|
|
2393
|
+
position: occurrence.position,
|
|
2394
|
+
slot: occurrence.slot,
|
|
2395
|
+
name
|
|
2396
|
+
});
|
|
2397
|
+
const roles = rolesByName.get(name) ?? [];
|
|
2398
|
+
roles.push(occurrence.role);
|
|
2399
|
+
rolesByName.set(name, roles);
|
|
2400
|
+
if (!hasAccessibleMeaning(occurrence)) throw new AssetCompilationError("Asset occurrence lacks accessible meaning", [topikAssetDiagnostic("TOPIK_ASSET_REFERENCE_ACCESSIBILITY_INVALID", "Asset occurrence lacks schema-supported accessible meaning", { location: {
|
|
2401
|
+
key: name,
|
|
2402
|
+
contentPosition: occurrence.position
|
|
2403
|
+
} })]);
|
|
2404
|
+
}
|
|
2405
|
+
function hasAccessibleMeaning(occurrence) {
|
|
2406
|
+
if (occurrence.role === "download") return (occurrence.semantics.linkLabel?.trim().length ?? 0) > 0;
|
|
2407
|
+
if (occurrence.slot.startsWith("figure.")) return (occurrence.semantics.alt?.trim().length ?? 0) > 0;
|
|
2408
|
+
return occurrence.semantics.decorative === true || (occurrence.semantics.alt?.trim().length ?? 0) > 0;
|
|
2409
|
+
}
|
|
2410
|
+
function isContentBearingResource(resource) {
|
|
2411
|
+
return resource.type === "Guide" || resource.type === "WikiPage" || resource.type === "CoursePage";
|
|
2412
|
+
}
|
|
2413
|
+
function isTopikContentResource(resource) {
|
|
2414
|
+
return resource.spec.content.format === "topik";
|
|
2415
|
+
}
|
|
2416
|
+
function requirePath(path, message) {
|
|
2417
|
+
const validation = validateTopikPath(path);
|
|
2418
|
+
if (!validation.ok) throw new AssetCompilationError(message, validation.diagnostics);
|
|
2419
|
+
return validation.value.path;
|
|
2420
|
+
}
|
|
2421
|
+
function resourceKey$1(resource) {
|
|
2422
|
+
return `${resource.type}/${resource.name}`;
|
|
2423
|
+
}
|
|
2424
|
+
function collision(message, name) {
|
|
2425
|
+
return new AssetCompilationError(message, [topikAssetDiagnostic("TOPIK_ASSET_NAME_COLLISION", message, { location: { key: name } })]);
|
|
2426
|
+
}
|
|
2427
|
+
function referenceError(message, occurrence, path, effectiveReference = occurrence.reference) {
|
|
2428
|
+
const validation = validateTopikAssetReference(effectiveReference);
|
|
2429
|
+
return new AssetCompilationError(message, [topikAssetDiagnostic(occurrence.kind === "asset" || occurrence.kind === "reserved-asset" ? "TOPIK_ASSET_REFERENCE_MALFORMED" : !validation.valid && validation.failureKind === "external" ? "TOPIK_EXTERNAL_REFERENCE_UNSAFE" : "TOPIK_ASSET_REFERENCE_MALFORMED", message, { location: {
|
|
2430
|
+
path,
|
|
2431
|
+
contentPosition: occurrence.position
|
|
2432
|
+
} })]);
|
|
2433
|
+
}
|
|
2434
|
+
function ambiguousReference(message, path, contentPosition) {
|
|
2435
|
+
return new AssetCompilationError(message, [topikAssetDiagnostic("TOPIK_ASSET_REFERENCE_AMBIGUOUS", message, { location: {
|
|
2436
|
+
path,
|
|
2437
|
+
...contentPosition === void 0 ? {} : { contentPosition }
|
|
2438
|
+
} })]);
|
|
2439
|
+
}
|
|
2440
|
+
function activeError(name, path) {
|
|
2441
|
+
return new AssetCompilationError("Asset media is active or incompatible with its slot", [topikAssetDiagnostic("TOPIK_ASSET_ACTIVE_CONTENT_UNSUPPORTED", "Asset media is active or incompatible with its slot", { location: {
|
|
2442
|
+
key: name,
|
|
2443
|
+
path
|
|
2444
|
+
} })]);
|
|
2445
|
+
}
|
|
2446
|
+
function sha256(bytes) {
|
|
2447
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
2448
|
+
}
|
|
2449
|
+
function equalBytes(left, right) {
|
|
2450
|
+
return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
|
|
2451
|
+
}
|
|
2452
|
+
function compareResources(left, right) {
|
|
2453
|
+
return compareUtf8(resourceKey$1(left), resourceKey$1(right));
|
|
2454
|
+
}
|
|
2455
|
+
function compareUtf8(left, right) {
|
|
2456
|
+
return Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
398
2457
|
}
|
|
399
2458
|
//#endregion
|
|
400
2459
|
//#region src/compile/shared.ts
|
|
401
2460
|
var CompileError = class extends Error {
|
|
402
2461
|
diagnostics;
|
|
403
2462
|
constructor(diagnostics) {
|
|
404
|
-
|
|
405
|
-
|
|
2463
|
+
const sanitized = diagnostics.map(sanitizeTopikContentDiagnostic);
|
|
2464
|
+
super(formatContentDiagnostics(sanitized));
|
|
406
2465
|
this.name = "CompileError";
|
|
2466
|
+
this.diagnostics = sanitized;
|
|
407
2467
|
}
|
|
408
2468
|
};
|
|
409
2469
|
function linkValidationPolicy(options) {
|
|
@@ -438,9 +2498,8 @@ function parseMarkdownFrontmatter(raw, filePath) {
|
|
|
438
2498
|
frontmatter,
|
|
439
2499
|
content: match[2]
|
|
440
2500
|
};
|
|
441
|
-
} catch
|
|
442
|
-
|
|
443
|
-
throw new Error(`Invalid frontmatter in ${filePath}: ${message}`, { cause: error });
|
|
2501
|
+
} catch {
|
|
2502
|
+
throw new PublicCompileError("frontmatter-invalid", filePath);
|
|
444
2503
|
}
|
|
445
2504
|
}
|
|
446
2505
|
function extractMarkdownTitle(content, fallback) {
|
|
@@ -450,15 +2509,16 @@ function extractMarkdownTitle(content, fallback) {
|
|
|
450
2509
|
}
|
|
451
2510
|
function formatContentDiagnostics(diagnostics) {
|
|
452
2511
|
return diagnostics.filter(isErrorDiagnostic).map((diagnostic) => {
|
|
453
|
-
|
|
2512
|
+
const sanitized = sanitizeTopikContentDiagnostic(diagnostic);
|
|
2513
|
+
return `${sanitized.file ?? "content"}${sanitized.lines.length > 0 ? `:${sanitized.lines.join(",")}` : ""} ${sanitized.level} ${sanitized.id}: ${sanitized.message}`;
|
|
454
2514
|
}).join("\n");
|
|
455
2515
|
}
|
|
456
|
-
function parseReferenceList(value,
|
|
2516
|
+
function parseReferenceList(value, _fieldName, filePath) {
|
|
457
2517
|
if (value == null) return;
|
|
458
|
-
if (!Array.isArray(value)) throw new
|
|
459
|
-
const references = value.map((entry
|
|
460
|
-
if (typeof entry !== "string") throw new
|
|
461
|
-
if (entry.length > 63 || !DNS_LABEL_PATTERN.test(entry)) throw new
|
|
2518
|
+
if (!Array.isArray(value)) throw new PublicCompileError("reference-list-invalid", filePath);
|
|
2519
|
+
const references = value.map((entry) => {
|
|
2520
|
+
if (typeof entry !== "string") throw new PublicCompileError("reference-list-invalid", filePath);
|
|
2521
|
+
if (entry.length > 63 || !DNS_LABEL_PATTERN.test(entry)) throw new PublicCompileError("reference-list-invalid", filePath);
|
|
462
2522
|
return entry;
|
|
463
2523
|
});
|
|
464
2524
|
return references.length > 0 ? references : void 0;
|
|
@@ -467,7 +2527,7 @@ function parseReferenceList(value, fieldName, filePath) {
|
|
|
467
2527
|
//#region src/compile/links.ts
|
|
468
2528
|
const NON_PAGE_SCHEME = /^(?:asset|https?|mailto|tel):/i;
|
|
469
2529
|
const LINK_BASE = "https://topik.local";
|
|
470
|
-
function validateWikiLinks(pages, policy) {
|
|
2530
|
+
function validateWikiLinks(pages, policy, nonPageLinks = /* @__PURE__ */ new Set()) {
|
|
471
2531
|
if (policy === "off") return [];
|
|
472
2532
|
const level = policy === "error" ? "error" : "warning";
|
|
473
2533
|
const pagesBySlug = new Map(pages.map((page) => [page.slug, page]));
|
|
@@ -478,10 +2538,11 @@ function validateWikiLinks(pages, policy) {
|
|
|
478
2538
|
if (!target) continue;
|
|
479
2539
|
const targetPage = pagesBySlug.get(target.slug);
|
|
480
2540
|
if (!targetPage) {
|
|
481
|
-
|
|
2541
|
+
if (nonPageLinks.has(link)) continue;
|
|
2542
|
+
diagnostics.push(linkDiagnostic("link-page-not-found", level, link));
|
|
482
2543
|
continue;
|
|
483
2544
|
}
|
|
484
|
-
if (target.fragment && !targetPage.analysis.headings.some((heading) => heading.id === target.fragment)) diagnostics.push(linkDiagnostic("link-fragment-not-found", level,
|
|
2545
|
+
if (target.fragment && !targetPage.analysis.headings.some((heading) => heading.id === target.fragment)) diagnostics.push(linkDiagnostic("link-fragment-not-found", level, link));
|
|
485
2546
|
}
|
|
486
2547
|
return diagnostics;
|
|
487
2548
|
}
|
|
@@ -493,7 +2554,7 @@ function validateLocalFragments(analysis, policy) {
|
|
|
493
2554
|
for (const link of analysis.links) {
|
|
494
2555
|
if (!link.href.startsWith("#") || link.href === "#") continue;
|
|
495
2556
|
const fragment = decodeFragment(link.href.slice(1));
|
|
496
|
-
if (fragment && !headingIds.has(fragment)) diagnostics.push(linkDiagnostic("link-fragment-not-found", level,
|
|
2557
|
+
if (fragment && !headingIds.has(fragment)) diagnostics.push(linkDiagnostic("link-fragment-not-found", level, link));
|
|
497
2558
|
}
|
|
498
2559
|
return diagnostics;
|
|
499
2560
|
}
|
|
@@ -529,7 +2590,9 @@ function decodePath(pathname) {
|
|
|
529
2590
|
if (normalized === "index") return "";
|
|
530
2591
|
return normalized.replace(/\/index$/, "");
|
|
531
2592
|
}
|
|
532
|
-
function linkDiagnostic(id, level,
|
|
2593
|
+
function linkDiagnostic(id, level, link) {
|
|
2594
|
+
const message = topikLinkDiagnosticMessage(id);
|
|
2595
|
+
if (message === void 0) throw new TypeError("Unknown link diagnostic identifier");
|
|
533
2596
|
return {
|
|
534
2597
|
id,
|
|
535
2598
|
type: link.kind,
|
|
@@ -540,6 +2603,105 @@ function linkDiagnostic(id, level, message, link) {
|
|
|
540
2603
|
};
|
|
541
2604
|
}
|
|
542
2605
|
//#endregion
|
|
2606
|
+
//#region src/wiki-navigation.ts
|
|
2607
|
+
const INTERNAL_WIKI_ORIGIN = "https://topik.invalid";
|
|
2608
|
+
/** Returns true for tab, dropdown, and group nodes containing navigation children. */
|
|
2609
|
+
function hasWikiNavChildren(node) {
|
|
2610
|
+
return "children" in node;
|
|
2611
|
+
}
|
|
2612
|
+
function isInternalWikiTab(node) {
|
|
2613
|
+
return node.type === "tab" && "children" in node;
|
|
2614
|
+
}
|
|
2615
|
+
function isExternalWikiTab(node) {
|
|
2616
|
+
return node.type === "tab" && "href" in node;
|
|
2617
|
+
}
|
|
2618
|
+
function isInternalWikiDropdown(node) {
|
|
2619
|
+
return node.type === "dropdown" && "children" in node;
|
|
2620
|
+
}
|
|
2621
|
+
function isExternalWikiDropdown(node) {
|
|
2622
|
+
return node.type === "dropdown" && "href" in node;
|
|
2623
|
+
}
|
|
2624
|
+
/** Resolves page routes, source paths, and navigation ancestry from a compiled Wiki tree. */
|
|
2625
|
+
function resolveWikiNavigation(navigation) {
|
|
2626
|
+
const pages = [];
|
|
2627
|
+
const pageByName = /* @__PURE__ */ new Map();
|
|
2628
|
+
const pageByRoute = /* @__PURE__ */ new Map();
|
|
2629
|
+
const visit = (nodes, prefix, ancestors) => {
|
|
2630
|
+
for (const node of nodes) if (node.type === "page") {
|
|
2631
|
+
const route = joinWikiPath(prefix, node.slug);
|
|
2632
|
+
const resolved = {
|
|
2633
|
+
page: node.page,
|
|
2634
|
+
node,
|
|
2635
|
+
route,
|
|
2636
|
+
sourcePath: node.sourcePath ?? joinWikiPath(prefix, node.slug || "index"),
|
|
2637
|
+
ancestors
|
|
2638
|
+
};
|
|
2639
|
+
if (pageByName.has(node.page)) throw new Error(`Wiki navigation contains page ${node.page} more than once`);
|
|
2640
|
+
if (pageByRoute.has(route)) throw new Error(`Wiki navigation contains duplicate route /${route}`);
|
|
2641
|
+
pages.push(resolved);
|
|
2642
|
+
pageByName.set(node.page, resolved);
|
|
2643
|
+
pageByRoute.set(route, resolved);
|
|
2644
|
+
} else if (hasWikiNavChildren(node)) visit(node.children, joinWikiPath(prefix, node.slug), [...ancestors, node]);
|
|
2645
|
+
};
|
|
2646
|
+
visit(navigation, "", []);
|
|
2647
|
+
return {
|
|
2648
|
+
pages,
|
|
2649
|
+
pageByName,
|
|
2650
|
+
pageByRoute
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
/** Finds the first page node in navigation order, ignoring external destinations. */
|
|
2654
|
+
function findFirstWikiPage(navigation) {
|
|
2655
|
+
for (const node of navigation) {
|
|
2656
|
+
if (node.type === "page") return node;
|
|
2657
|
+
if (hasWikiNavChildren(node)) {
|
|
2658
|
+
const page = findFirstWikiPage(node.children);
|
|
2659
|
+
if (page) return page;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
return null;
|
|
2663
|
+
}
|
|
2664
|
+
/** Returns the internal containers owning a page, or an empty array for an unknown page. */
|
|
2665
|
+
function findWikiPageAncestors(resolved, pageName) {
|
|
2666
|
+
return resolved.pageByName.get(pageName)?.ancestors ?? [];
|
|
2667
|
+
}
|
|
2668
|
+
/** Resolves an internal Markdown link against the logical source path of its current page. */
|
|
2669
|
+
function resolveWikiContentHref(href, currentPageName, resolved) {
|
|
2670
|
+
if (href.startsWith("asset:")) return null;
|
|
2671
|
+
const currentPage = resolved.pageByName.get(currentPageName);
|
|
2672
|
+
if (!currentPage) return null;
|
|
2673
|
+
if (href.startsWith("#")) return {
|
|
2674
|
+
page: currentPage,
|
|
2675
|
+
route: currentPage.route,
|
|
2676
|
+
hash: href.slice(1)
|
|
2677
|
+
};
|
|
2678
|
+
let url;
|
|
2679
|
+
try {
|
|
2680
|
+
url = new URL(href, `${INTERNAL_WIKI_ORIGIN}/${currentPage.sourcePath}`);
|
|
2681
|
+
} catch {
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2684
|
+
if (url.origin !== INTERNAL_WIKI_ORIGIN) return null;
|
|
2685
|
+
let decodedPath;
|
|
2686
|
+
try {
|
|
2687
|
+
decodedPath = decodeURIComponent(url.pathname);
|
|
2688
|
+
} catch {
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2691
|
+
const normalizedPath = decodedPath.replace(/^\/+|\/+$/g, "").replace(/\.(?:mdx?|markdown)$/i, "");
|
|
2692
|
+
const route = normalizedPath === "index" ? "" : normalizedPath.replace(/\/index$/, "");
|
|
2693
|
+
const page = resolved.pageByRoute.get(route);
|
|
2694
|
+
return page ? {
|
|
2695
|
+
page,
|
|
2696
|
+
route,
|
|
2697
|
+
hash: url.hash.replace(/^#/, "")
|
|
2698
|
+
} : null;
|
|
2699
|
+
}
|
|
2700
|
+
function joinWikiPath(prefix, slug) {
|
|
2701
|
+
if (!slug) return prefix;
|
|
2702
|
+
return prefix ? `${prefix}/${slug}` : slug;
|
|
2703
|
+
}
|
|
2704
|
+
//#endregion
|
|
543
2705
|
//#region src/compile/wiki.ts
|
|
544
2706
|
async function compileWiki(options) {
|
|
545
2707
|
const result = await inspectWiki(options);
|
|
@@ -547,45 +2709,57 @@ async function compileWiki(options) {
|
|
|
547
2709
|
return result;
|
|
548
2710
|
}
|
|
549
2711
|
async function inspectWiki(options) {
|
|
2712
|
+
const discovered = await discoverWiki(options);
|
|
2713
|
+
const compiled = await compileAssetResources({
|
|
2714
|
+
rootDir: resolve(options.dir),
|
|
2715
|
+
resources: discovered.resources,
|
|
2716
|
+
sourcePathsByResource: discovered.sourcePathsByResource,
|
|
2717
|
+
protectedSourcePaths: discovered.consumedSourcePaths,
|
|
2718
|
+
...options.assets
|
|
2719
|
+
});
|
|
2720
|
+
return {
|
|
2721
|
+
diagnostics: discovered.diagnostics,
|
|
2722
|
+
...compiled
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
/** @internal Discovery phase used by the mixed top-level compiler. */
|
|
2726
|
+
async function discoverWiki(options) {
|
|
550
2727
|
const dir = resolve(options.dir);
|
|
551
|
-
const
|
|
2728
|
+
const loadedConfig = await readOptionalConfigFileWithPath(dir, [
|
|
552
2729
|
"wiki.yaml",
|
|
553
2730
|
"wiki.yml",
|
|
554
2731
|
"wiki.json"
|
|
555
2732
|
]);
|
|
556
|
-
if (
|
|
2733
|
+
if (loadedConfig == null) return {
|
|
557
2734
|
diagnostics: [],
|
|
558
|
-
resources: []
|
|
2735
|
+
resources: [],
|
|
2736
|
+
sourcePathsByResource: {},
|
|
2737
|
+
consumedSourcePaths: []
|
|
559
2738
|
};
|
|
560
|
-
|
|
2739
|
+
let config;
|
|
2740
|
+
try {
|
|
2741
|
+
config = parseWikiConfig(loadedConfig.value);
|
|
2742
|
+
} catch {
|
|
2743
|
+
throw new PublicCompileError("config-invalid", loadedConfig.path);
|
|
2744
|
+
}
|
|
561
2745
|
const pagePaths = config.navigation ? [...new Set(collectPagePaths(config.navigation))] : [];
|
|
562
|
-
const resolvedFiles = await Promise.all(pagePaths.map(
|
|
563
|
-
const filePath = await resolveFilePath(dir, pagePath);
|
|
564
|
-
return {
|
|
565
|
-
filePath,
|
|
566
|
-
raw: await readFile(filePath, "utf-8")
|
|
567
|
-
};
|
|
568
|
-
}));
|
|
2746
|
+
const resolvedFiles = await Promise.all(pagePaths.map((pagePath) => readPageFile(dir, pagePath)));
|
|
569
2747
|
const resources = [];
|
|
570
2748
|
const diagnostics = [];
|
|
571
2749
|
const pageAnalyses = [];
|
|
572
|
-
const
|
|
2750
|
+
const sourcePathsByResource = {};
|
|
573
2751
|
for (let i = 0; i < pagePaths.length; i++) {
|
|
574
2752
|
const pagePath = pagePaths[i];
|
|
575
2753
|
const { filePath, raw } = resolvedFiles[i];
|
|
2754
|
+
const sourcePath = `${pagePath}${filePath.endsWith(".mdx") ? ".mdx" : ".md"}`;
|
|
576
2755
|
const { frontmatter, content } = parseMarkdownFrontmatter(raw, pagePath);
|
|
577
|
-
const validation = validateTopikContent(content, { file:
|
|
2756
|
+
const validation = validateTopikContent(content, { file: sourcePath });
|
|
578
2757
|
diagnostics.push(...validation.errors);
|
|
579
2758
|
if (!validation.valid) continue;
|
|
580
|
-
const { content: rewritten, assets, manifest } = await extractAssets(content, {
|
|
581
|
-
baseDir: dir,
|
|
582
|
-
filePath
|
|
583
|
-
});
|
|
584
|
-
for (const asset of assets) if (!assetsById.has(asset.name)) assetsById.set(asset.name, asset);
|
|
585
2759
|
const name = pagePathToName(config.id, pagePath);
|
|
586
|
-
const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(
|
|
2760
|
+
const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(content, pagePathToTitleFallback(pagePath));
|
|
587
2761
|
const description = normalizeWikiPageDescription(frontmatter.description);
|
|
588
|
-
const analysis = analyzeTopikContent(
|
|
2762
|
+
const analysis = analyzeTopikContent(content, { file: sourcePath });
|
|
589
2763
|
diagnostics.push(...analysis.diagnostics);
|
|
590
2764
|
pageAnalyses.push({
|
|
591
2765
|
analysis,
|
|
@@ -602,14 +2776,13 @@ async function inspectWiki(options) {
|
|
|
602
2776
|
...description != null ? { description } : {},
|
|
603
2777
|
content: {
|
|
604
2778
|
format: "topik",
|
|
605
|
-
value:
|
|
606
|
-
}
|
|
607
|
-
...manifest.length > 0 ? { assets: manifest } : {}
|
|
2779
|
+
value: content
|
|
2780
|
+
}
|
|
608
2781
|
}
|
|
609
2782
|
};
|
|
610
2783
|
resources.push(pageResource);
|
|
2784
|
+
sourcePathsByResource[`WikiPage/${pageResource.name}`] = sourcePath;
|
|
611
2785
|
}
|
|
612
|
-
for (const asset of assetsById.values()) resources.push(asset);
|
|
613
2786
|
const wikiResource = {
|
|
614
2787
|
apiVersion: "v1",
|
|
615
2788
|
type: "Wiki",
|
|
@@ -622,12 +2795,45 @@ async function inspectWiki(options) {
|
|
|
622
2795
|
}
|
|
623
2796
|
};
|
|
624
2797
|
resources.push(wikiResource);
|
|
625
|
-
if (!hasCompileErrors(diagnostics))
|
|
2798
|
+
if (!hasCompileErrors(diagnostics)) {
|
|
2799
|
+
const nonPageLinks = await classifyWikiNonPageLinks(dir, pageAnalyses);
|
|
2800
|
+
diagnostics.push(...validateWikiLinks(pageAnalyses, linkValidationPolicy(options.validation), nonPageLinks));
|
|
2801
|
+
}
|
|
626
2802
|
return {
|
|
627
2803
|
diagnostics,
|
|
628
|
-
resources
|
|
2804
|
+
resources,
|
|
2805
|
+
sourcePathsByResource,
|
|
2806
|
+
consumedSourcePaths: [loadedConfig.path]
|
|
629
2807
|
};
|
|
630
2808
|
}
|
|
2809
|
+
async function classifyWikiNonPageLinks(root, pages) {
|
|
2810
|
+
const nonPageLinks = /* @__PURE__ */ new Set();
|
|
2811
|
+
const existingByPath = /* @__PURE__ */ new Map();
|
|
2812
|
+
for (const page of pages) for (const link of page.analysis.links) {
|
|
2813
|
+
if (link.kind !== "link") continue;
|
|
2814
|
+
const reference = validateTopikAssetReference(link.href);
|
|
2815
|
+
if (!reference.valid || reference.kind !== "local") continue;
|
|
2816
|
+
const path = validateTopikPath(posix.join(posix.dirname(page.sourcePath), reference.decodedPath));
|
|
2817
|
+
if (!path.ok) continue;
|
|
2818
|
+
let existing = existingByPath.get(path.value.path);
|
|
2819
|
+
if (existing === void 0) {
|
|
2820
|
+
if (await classifyPortableNavigationPath({
|
|
2821
|
+
root,
|
|
2822
|
+
path: path.value.path
|
|
2823
|
+
}) === "directory") existing = true;
|
|
2824
|
+
else {
|
|
2825
|
+
const proof = await readPortableAssetFile({
|
|
2826
|
+
root,
|
|
2827
|
+
path: path.value.path
|
|
2828
|
+
});
|
|
2829
|
+
existing = proof.ok || proof.diagnostics.some((diagnostic) => diagnostic.id !== "TOPIK_ASSET_FILE_MISSING");
|
|
2830
|
+
}
|
|
2831
|
+
existingByPath.set(path.value.path, existing);
|
|
2832
|
+
}
|
|
2833
|
+
if (existing) nonPageLinks.add(link);
|
|
2834
|
+
}
|
|
2835
|
+
return nonPageLinks;
|
|
2836
|
+
}
|
|
631
2837
|
function normalizeWikiPageDescription(description) {
|
|
632
2838
|
return typeof description === "string" ? description.slice(0, 1024) : void 0;
|
|
633
2839
|
}
|
|
@@ -637,15 +2843,20 @@ function collectPagePaths(nodes, prefix = "") {
|
|
|
637
2843
|
else if ("children" in node) paths.push(...collectPagePaths(node.children, joinWikiPath(prefix, node.slug)));
|
|
638
2844
|
return paths;
|
|
639
2845
|
}
|
|
640
|
-
async function
|
|
2846
|
+
async function readPageFile(dir, pagePath) {
|
|
641
2847
|
for (const ext of [".mdx", ".md"]) {
|
|
642
2848
|
const filePath = join(dir, pagePath + ext);
|
|
643
2849
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
2850
|
+
return {
|
|
2851
|
+
filePath,
|
|
2852
|
+
raw: await readRegularFileWithinRoot(filePath, dir, "utf-8")
|
|
2853
|
+
};
|
|
2854
|
+
} catch (error) {
|
|
2855
|
+
if (error.code === "ENOENT") continue;
|
|
2856
|
+
throw error;
|
|
2857
|
+
}
|
|
647
2858
|
}
|
|
648
|
-
throw new
|
|
2859
|
+
throw new PublicCompileError("wiki-page-not-found");
|
|
649
2860
|
}
|
|
650
2861
|
function pagePathToSlug(pagePath) {
|
|
651
2862
|
if (pagePath === "index") return "";
|
|
@@ -734,39 +2945,84 @@ async function compileGuides(options) {
|
|
|
734
2945
|
return result;
|
|
735
2946
|
}
|
|
736
2947
|
async function inspectGuides(options) {
|
|
2948
|
+
const discovered = await discoverGuides(options);
|
|
2949
|
+
const compiled = await compileAssetResources({
|
|
2950
|
+
rootDir: resolve(options.dir),
|
|
2951
|
+
resources: discovered.resources,
|
|
2952
|
+
sourcePathsByResource: discovered.sourcePathsByResource,
|
|
2953
|
+
protectedSourcePaths: discovered.consumedSourcePaths,
|
|
2954
|
+
...options.assets
|
|
2955
|
+
});
|
|
2956
|
+
return {
|
|
2957
|
+
diagnostics: discovered.diagnostics,
|
|
2958
|
+
...compiled
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
/** @internal Discovery phase used by the mixed top-level compiler. */
|
|
2962
|
+
async function discoverGuides(options) {
|
|
737
2963
|
const dir = resolve(options.dir);
|
|
738
|
-
const
|
|
2964
|
+
const loadedConfig = await readOptionalConfigFileWithPath(dir, [
|
|
739
2965
|
"collection.yaml",
|
|
740
2966
|
"collection.yml",
|
|
741
2967
|
"collection.json"
|
|
742
2968
|
]);
|
|
743
|
-
if (
|
|
2969
|
+
if (loadedConfig == null) return {
|
|
744
2970
|
diagnostics: [],
|
|
745
|
-
resources: []
|
|
2971
|
+
resources: [],
|
|
2972
|
+
sourcePathsByResource: {},
|
|
2973
|
+
consumedSourcePaths: []
|
|
746
2974
|
};
|
|
747
|
-
|
|
2975
|
+
let config;
|
|
2976
|
+
try {
|
|
2977
|
+
config = parseCollectionConfig(loadedConfig.value);
|
|
2978
|
+
} catch {
|
|
2979
|
+
throw new PublicCompileError("config-invalid", loadedConfig.path);
|
|
2980
|
+
}
|
|
748
2981
|
const markdownFiles = (await readdir(dir)).filter((f) => f.endsWith(".md") || f.endsWith(".mdx")).sort();
|
|
749
2982
|
const resources = [];
|
|
750
2983
|
const diagnostics = [];
|
|
751
|
-
const
|
|
2984
|
+
const sourcePathsByResource = {};
|
|
752
2985
|
for (const file of markdownFiles) {
|
|
753
2986
|
const filePath = join(dir, file);
|
|
754
|
-
|
|
755
|
-
|
|
2987
|
+
let rawContent;
|
|
2988
|
+
try {
|
|
2989
|
+
rawContent = await readRegularFileWithinRoot(filePath, dir, "utf-8");
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
if (error instanceof FileOutsideCompilationRootError) {
|
|
2992
|
+
diagnostics.push({
|
|
2993
|
+
id: "guide-outside-compilation-root",
|
|
2994
|
+
type: "Guide",
|
|
2995
|
+
level: "error",
|
|
2996
|
+
message: "Guide files must resolve within the compilation directory",
|
|
2997
|
+
lines: [],
|
|
2998
|
+
file
|
|
2999
|
+
});
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
if (error instanceof FileNotRegularError) {
|
|
3003
|
+
diagnostics.push({
|
|
3004
|
+
id: "guide-not-regular-file",
|
|
3005
|
+
type: "Guide",
|
|
3006
|
+
level: "error",
|
|
3007
|
+
message: "Guide entries must be regular files",
|
|
3008
|
+
lines: [],
|
|
3009
|
+
file
|
|
3010
|
+
});
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
throw error;
|
|
3014
|
+
}
|
|
3015
|
+
const { frontmatter, content } = parseMarkdownFrontmatter(rawContent, file);
|
|
3016
|
+
const validation = validateTopikContent(content, { file });
|
|
756
3017
|
diagnostics.push(...validation.errors);
|
|
757
3018
|
if (!validation.valid) continue;
|
|
758
|
-
const { content: rewritten, assets, manifest } = await extractAssets(content, {
|
|
759
|
-
baseDir: dir,
|
|
760
|
-
filePath
|
|
761
|
-
});
|
|
762
|
-
for (const asset of assets) if (!assetsById.has(asset.name)) assetsById.set(asset.name, asset);
|
|
763
3019
|
const slug = fileToSlug(file);
|
|
764
3020
|
const name = `${config.id}-${slug}`;
|
|
765
|
-
const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(
|
|
3021
|
+
const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(content, slug);
|
|
766
3022
|
const tags = mergeTags(config.tags, frontmatter.tags);
|
|
767
3023
|
const authors = parseReferenceList(frontmatter.authors, "authors", file);
|
|
768
3024
|
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
769
|
-
const analysis = analyzeTopikContent(
|
|
3025
|
+
const analysis = analyzeTopikContent(content, { file });
|
|
770
3026
|
diagnostics.push(...analysis.diagnostics);
|
|
771
3027
|
diagnostics.push(...validateLocalFragments(analysis, linkValidationPolicy(options.validation)));
|
|
772
3028
|
const guide = {
|
|
@@ -781,17 +3037,18 @@ async function inspectGuides(options) {
|
|
|
781
3037
|
...tags.length > 0 ? { tags } : {},
|
|
782
3038
|
content: {
|
|
783
3039
|
format: "topik",
|
|
784
|
-
value:
|
|
785
|
-
}
|
|
786
|
-
...manifest.length > 0 ? { assets: manifest } : {}
|
|
3040
|
+
value: content
|
|
3041
|
+
}
|
|
787
3042
|
}
|
|
788
3043
|
};
|
|
789
3044
|
resources.push(guide);
|
|
3045
|
+
sourcePathsByResource[`Guide/${guide.name}`] = file;
|
|
790
3046
|
}
|
|
791
|
-
for (const asset of assetsById.values()) resources.push(asset);
|
|
792
3047
|
return {
|
|
793
3048
|
diagnostics,
|
|
794
|
-
resources
|
|
3049
|
+
resources,
|
|
3050
|
+
sourcePathsByResource,
|
|
3051
|
+
consumedSourcePaths: [loadedConfig.path]
|
|
795
3052
|
};
|
|
796
3053
|
}
|
|
797
3054
|
function fileToSlug(filename) {
|
|
@@ -821,98 +3078,47 @@ async function compile(options) {
|
|
|
821
3078
|
const dir = resolve(options.dir);
|
|
822
3079
|
const resources = [];
|
|
823
3080
|
const diagnostics = [];
|
|
3081
|
+
const sourcePathsByResource = {};
|
|
3082
|
+
const protectedSourcePaths = [];
|
|
824
3083
|
if (await findConfigFile(dir, WIKI_CONFIG_FILES)) {
|
|
825
|
-
const result = await
|
|
3084
|
+
const result = await discoverWiki({
|
|
826
3085
|
dir,
|
|
827
3086
|
validation: options.validation
|
|
828
3087
|
});
|
|
829
3088
|
resources.push(...result.resources);
|
|
830
3089
|
diagnostics.push(...result.diagnostics);
|
|
3090
|
+
Object.assign(sourcePathsByResource, result.sourcePathsByResource);
|
|
3091
|
+
protectedSourcePaths.push(...result.consumedSourcePaths);
|
|
831
3092
|
}
|
|
832
3093
|
if (await findConfigFile(dir, COLLECTION_CONFIG_FILES)) {
|
|
833
|
-
const result = await
|
|
3094
|
+
const result = await discoverGuides({
|
|
834
3095
|
dir,
|
|
835
3096
|
validation: options.validation
|
|
836
3097
|
});
|
|
837
3098
|
resources.push(...result.resources);
|
|
838
3099
|
diagnostics.push(...result.diagnostics);
|
|
3100
|
+
Object.assign(sourcePathsByResource, result.sourcePathsByResource);
|
|
3101
|
+
protectedSourcePaths.push(...result.consumedSourcePaths);
|
|
839
3102
|
}
|
|
3103
|
+
throwOnCompileErrors(diagnostics);
|
|
840
3104
|
return {
|
|
841
3105
|
diagnostics,
|
|
842
|
-
|
|
3106
|
+
...await compileAssetResources({
|
|
3107
|
+
rootDir: dir,
|
|
3108
|
+
resources,
|
|
3109
|
+
sourcePathsByResource,
|
|
3110
|
+
protectedSourcePaths,
|
|
3111
|
+
...options.assets
|
|
3112
|
+
})
|
|
843
3113
|
};
|
|
844
3114
|
}
|
|
845
3115
|
async function lint(options) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
})).diagnostics);
|
|
852
|
-
if (await findConfigFile(dir, COLLECTION_CONFIG_FILES)) diagnostics.push(...(await inspectGuides({
|
|
853
|
-
dir,
|
|
854
|
-
validation: options.validation
|
|
855
|
-
})).diagnostics);
|
|
856
|
-
return { diagnostics };
|
|
857
|
-
}
|
|
858
|
-
//#endregion
|
|
859
|
-
//#region src/validate.ts
|
|
860
|
-
const ajv = new Ajv2020({
|
|
861
|
-
strict: true,
|
|
862
|
-
discriminator: true
|
|
863
|
-
});
|
|
864
|
-
addFormats(ajv);
|
|
865
|
-
const validators = {
|
|
866
|
-
Asset: ajv.compile(assetSchema),
|
|
867
|
-
Guide: ajv.compile(guideSchema),
|
|
868
|
-
Wiki: ajv.compile(wikiSchema),
|
|
869
|
-
WikiPage: ajv.compile(wikiPageSchema)
|
|
870
|
-
};
|
|
871
|
-
function isRecord(value) {
|
|
872
|
-
return typeof value === "object" && value !== null;
|
|
873
|
-
}
|
|
874
|
-
function getResourceLabel(resource) {
|
|
875
|
-
return `${typeof resource.type === "string" ? resource.type : "unknown"}/${typeof resource.name === "string" ? resource.name : "unknown"}`;
|
|
876
|
-
}
|
|
877
|
-
/** Validate the resources produced by `compile` and the CLI export. */
|
|
878
|
-
function validateResources(resources) {
|
|
879
|
-
const errors = [];
|
|
880
|
-
for (const resource of resources) {
|
|
881
|
-
if (!isRecord(resource)) {
|
|
882
|
-
errors.push({
|
|
883
|
-
resource: "unknown/unknown",
|
|
884
|
-
path: "/",
|
|
885
|
-
message: "Resource must be an object"
|
|
886
|
-
});
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
if (typeof resource.type !== "string") {
|
|
890
|
-
errors.push({
|
|
891
|
-
resource: getResourceLabel(resource),
|
|
892
|
-
path: "/type",
|
|
893
|
-
message: "Resource type must be a string"
|
|
894
|
-
});
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
const validate = validators[resource.type];
|
|
898
|
-
if (!validate) {
|
|
899
|
-
errors.push({
|
|
900
|
-
resource: getResourceLabel(resource),
|
|
901
|
-
path: "/type",
|
|
902
|
-
message: `Unsupported resource type: ${resource.type}`
|
|
903
|
-
});
|
|
904
|
-
continue;
|
|
905
|
-
}
|
|
906
|
-
if (!validate(resource)) for (const err of validate.errors ?? []) errors.push({
|
|
907
|
-
resource: getResourceLabel(resource),
|
|
908
|
-
path: err.instancePath || "/",
|
|
909
|
-
message: err.message ?? "Unknown error"
|
|
910
|
-
});
|
|
3116
|
+
try {
|
|
3117
|
+
return { diagnostics: (await compile(options)).diagnostics };
|
|
3118
|
+
} catch (error) {
|
|
3119
|
+
if (error instanceof CompileError) return { diagnostics: error.diagnostics };
|
|
3120
|
+
throw error;
|
|
911
3121
|
}
|
|
912
|
-
return {
|
|
913
|
-
valid: errors.length === 0,
|
|
914
|
-
errors
|
|
915
|
-
};
|
|
916
3122
|
}
|
|
917
3123
|
//#endregion
|
|
918
3124
|
//#region src/watch.ts
|
|
@@ -923,22 +3129,33 @@ async function watch(options) {
|
|
|
923
3129
|
const dir = resolve(options.dir);
|
|
924
3130
|
const emitter = new EventEmitter();
|
|
925
3131
|
const resources = /* @__PURE__ */ new Map();
|
|
926
|
-
const
|
|
3132
|
+
const payloads = /* @__PURE__ */ new Map();
|
|
3133
|
+
const initial = await compile({
|
|
3134
|
+
dir,
|
|
3135
|
+
assets: options.assets
|
|
3136
|
+
});
|
|
3137
|
+
let materialization = initial.materialization;
|
|
927
3138
|
for (const resource of initial.resources) resources.set(resourceKey(resource), resource);
|
|
3139
|
+
for (const payload of initial.payloads) payloads.set(payload.path, payload);
|
|
928
3140
|
let debounceTimer = null;
|
|
929
3141
|
async function recompile() {
|
|
930
3142
|
try {
|
|
931
|
-
const result = await compile({
|
|
3143
|
+
const result = await compile({
|
|
3144
|
+
dir,
|
|
3145
|
+
assets: options.assets
|
|
3146
|
+
});
|
|
932
3147
|
const newKeys = /* @__PURE__ */ new Set();
|
|
3148
|
+
const materializationChanged = digestTopikMaterializationRecord(materialization) !== digestTopikMaterializationRecord(result.materialization);
|
|
3149
|
+
materialization = result.materialization;
|
|
3150
|
+
payloads.clear();
|
|
3151
|
+
for (const payload of result.payloads) payloads.set(payload.path, payload);
|
|
933
3152
|
for (const resource of result.resources) {
|
|
934
3153
|
const key = resourceKey(resource);
|
|
935
3154
|
newKeys.add(key);
|
|
936
3155
|
const existing = resources.get(key);
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
emitter.emit("update", key, resource);
|
|
941
|
-
}
|
|
3156
|
+
const resourceChanged = !existing || JSON.stringify(existing) !== JSON.stringify(resource);
|
|
3157
|
+
resources.set(key, resource);
|
|
3158
|
+
if (resourceChanged || resource.type === "Asset" && materializationChanged) emitter.emit("update", key, resource);
|
|
942
3159
|
}
|
|
943
3160
|
for (const key of resources.keys()) if (!newKeys.has(key)) resources.delete(key);
|
|
944
3161
|
} catch (error) {
|
|
@@ -951,7 +3168,7 @@ async function watch(options) {
|
|
|
951
3168
|
}
|
|
952
3169
|
const fsWatcher = watch$1(dir, {
|
|
953
3170
|
ignoreInitial: true,
|
|
954
|
-
ignored: [/(^|[/\\])
|
|
3171
|
+
ignored: [/(^|[/\\])(?:\.git|\.topik(?:-compilation-(?:generation|prior)-[^/\\]+)?)(?:[/\\]|$)/, "**/node_modules/**"]
|
|
955
3172
|
});
|
|
956
3173
|
fsWatcher.on("add", scheduleRecompile);
|
|
957
3174
|
fsWatcher.on("change", scheduleRecompile);
|
|
@@ -961,6 +3178,7 @@ async function watch(options) {
|
|
|
961
3178
|
});
|
|
962
3179
|
const watcher = {
|
|
963
3180
|
resources,
|
|
3181
|
+
payloads,
|
|
964
3182
|
on(event, listener) {
|
|
965
3183
|
emitter.on(event, listener);
|
|
966
3184
|
return watcher;
|
|
@@ -977,4 +3195,4 @@ async function watch(options) {
|
|
|
977
3195
|
return watcher;
|
|
978
3196
|
}
|
|
979
3197
|
//#endregion
|
|
980
|
-
export { CompileError, compile, compileGuides, compileWiki, isErrorDiagnostic, lint, pagePathToName, parseCollectionConfig, parseWikiConfig, validateResources, watch };
|
|
3198
|
+
export { ASSET_API_VERSION, ASSET_SCHEMA_ID, ASSET_TYPE, AssetCompilationError, CompileError, PublicCompileError, TOPIK_ASSET_CORRELATION_ID_PATTERN, TOPIK_ASSET_DEFAULT_CORRELATION_ID, TOPIK_ASSET_DIAGNOSTIC_IDS, TOPIK_ASSET_LIMITS, TOPIK_ASSET_NAME_VERSION, TOPIK_ASSET_REFERENCE_VERSION, TOPIK_BLOB_OUTPUT_PREFIX, TOPIK_JSON_VERSION, TOPIK_MATERIALIZATION_VERSION, TOPIK_PATH_V1_DESCRIPTOR, TOPIK_PATH_VERSION, TOPIK_UNRESOLVED_ACTIVE_CONTENT_TYPE, TopikJsonSyntaxError, compareTopikAssetIdentities, compile, compileAssetResources, compileGuides, compileWiki, computeTopikPathCollisionKey, correlateTopikAssetResult, createTopikAssetSemanticRecord, createTopikMaterializationRecord, digestTopikAssetSemanticRecord, digestTopikMaterializationRecord, findFirstWikiPage, findWikiPageAncestors, generateAutomaticAssetName, hasWikiNavChildren, isAssetBlobUri, isErrorDiagnostic, isExternalWikiDropdown, isExternalWikiTab, isGeneratedAssetName, isInlineMediaCompatible, isInternalWikiDropdown, isInternalWikiTab, isTopikActiveMediaType, joinWikiPath, lint, looksLikeGitLfsPointer, pagePathToName, parseAsset, parseAssetBlobUri, parseCollectionConfig, parseGeneratedAssetName, parseStrictTopikJson, parseWikiConfig, publicCompileErrorMessage, readPortableAssetFile, resolveWikiContentHref, resolveWikiNavigation, serializeAsset, serializeTopikJson, sniffPortableMediaType, toNfkcCasefold, topikAssetNameDescriptor, topikJsonDescriptor, validateAssetBlobUri, validateAssetValue, validatePortableAssetFile, validatePortableAssetTree, validateResources, validateStableSourceNamespace, validateTopikMaterializationRecord, validateTopikPath, validateTopikPathSet, watch };
|