halfcode-compiler.xnl 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/application-assembly.d.ts +2 -0
- package/dist/application-assembly.js +2 -0
- package/dist/authoring-runtime.d.ts +2 -0
- package/dist/authoring-runtime.js +2 -0
- package/dist/contract-schema.d.ts +45 -0
- package/dist/contract-schema.js +297 -0
- package/dist/dist-Bkv7YeVi.js +491 -0
- package/dist/index-Ba5Gt9iW.d.ts +67 -0
- package/dist/index-D-cBhQVw.d.ts +38 -0
- package/dist/index-Dg0imcjf.d.ts +31 -0
- package/dist/index-DuRG4TQo.d.ts +12 -0
- package/dist/index-FdimP_SL.d.ts +236 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/kind-definition.d.ts +14 -0
- package/dist/kind-definition.js +8 -0
- package/dist/resource-core.d.ts +2 -0
- package/dist/resource-core.js +2 -0
- package/dist/resource-mapping.d.ts +43 -0
- package/dist/resource-mapping.js +2 -0
- package/dist/resource-projection.d.ts +2 -0
- package/dist/resource-projection.js +8 -0
- package/dist/skill-capsule.d.ts +2 -0
- package/dist/skill-capsule.js +2 -0
- package/dist/src-B87eJQat.js +39 -0
- package/dist/src-BlKlpg0J.js +180 -0
- package/dist/src-COEMEqZw.js +431 -0
- package/dist/src-DEuFHizY.js +561 -0
- package/dist/src-Dv8bfFUo.js +607 -0
- package/dist/testing.d.ts +30 -0
- package/dist/testing.js +57 -0
- package/package.json +74 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { n as wordToString, t as parseXnl } from "./dist-Bkv7YeVi.js";
|
|
2
|
+
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
//#region ../resource-core/src/xnl-loader.ts
|
|
5
|
+
async function buildXnlResourceTree(options) {
|
|
6
|
+
const context = {
|
|
7
|
+
rootDir: resolve(options.rootDir),
|
|
8
|
+
diagnostics: [],
|
|
9
|
+
kindDefinitions: /* @__PURE__ */ new Map(),
|
|
10
|
+
resources: [],
|
|
11
|
+
seenIdentities: /* @__PURE__ */ new Map()
|
|
12
|
+
};
|
|
13
|
+
const manifest = await loadManifest(resolve(context.rootDir, options.manifestPath), "manifest", context, true);
|
|
14
|
+
if (!manifest || context.diagnostics.length > 0) return { diagnostics: context.diagnostics };
|
|
15
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const resource of context.resources) {
|
|
17
|
+
const records = byKind.get(resource.kind) ?? [];
|
|
18
|
+
records.push(resource);
|
|
19
|
+
byKind.set(resource.kind, records);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
diagnostics: [],
|
|
23
|
+
tree: {
|
|
24
|
+
manifest,
|
|
25
|
+
registry: { byKind },
|
|
26
|
+
diagnostics: []
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function loadManifest(filePath, sourceShape, context, isRoot) {
|
|
31
|
+
const record = await loadXnlRecord(filePath, sourceShape, context);
|
|
32
|
+
if (!record) return void 0;
|
|
33
|
+
if (isRoot && record.kind !== "ResourcePackage") {
|
|
34
|
+
context.diagnostics.push({
|
|
35
|
+
code: "RESOURCE_XNL_ROOT_INVALID",
|
|
36
|
+
location: record.documentUri,
|
|
37
|
+
message: "The root manifest.xnl must contain one ResourcePackage root."
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const catalogs = readCatalogs(record, context.diagnostics);
|
|
42
|
+
for (const catalog of catalogs.filter((item) => item.kind === "KindDefinition")) await loadKindDefinitions(catalog, dirname(filePath), context);
|
|
43
|
+
for (const catalog of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(catalog, dirname(filePath), context);
|
|
44
|
+
if (!isRoot) {
|
|
45
|
+
validateIdentity(record, context);
|
|
46
|
+
context.resources.push(record);
|
|
47
|
+
}
|
|
48
|
+
return record;
|
|
49
|
+
}
|
|
50
|
+
async function loadKindDefinitions(catalog, manifestDir, context) {
|
|
51
|
+
const files = await catalogFiles(catalog, manifestDir, context);
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const record = await loadXnlRecord(file, catalog.shape, context);
|
|
54
|
+
if (!record) continue;
|
|
55
|
+
if (record.kind !== "KindDefinition") {
|
|
56
|
+
context.diagnostics.push(kindMismatch(record.kind, "KindDefinition", record.documentUri));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const definition = normalizeKindDefinition(record, context.diagnostics);
|
|
60
|
+
if (!definition) continue;
|
|
61
|
+
context.kindDefinitions.set(definition.resourceKind, definition);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function loadCatalog(catalog, manifestDir, context) {
|
|
65
|
+
const definition = context.kindDefinitions.get(catalog.kind);
|
|
66
|
+
if (!definition) {
|
|
67
|
+
context.diagnostics.push({
|
|
68
|
+
code: "KIND_DEFINITION_MISSING",
|
|
69
|
+
location: catalog.location,
|
|
70
|
+
message: `No KindDefinition is registered for catalog kind '${catalog.kind}'.`
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!definition.sourceShapes.includes(catalog.shape)) {
|
|
75
|
+
context.diagnostics.push({
|
|
76
|
+
code: "RESOURCE_SOURCE_SHAPE_NOT_ALLOWED",
|
|
77
|
+
location: catalog.location,
|
|
78
|
+
message: `Kind '${catalog.kind}' does not allow source shape '${catalog.shape}'.`
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const files = await catalogFiles(catalog, manifestDir, context);
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
if (catalog.shape === "manifest") {
|
|
85
|
+
const record = await loadXnlRecord(file, "manifest", context);
|
|
86
|
+
if (!record) continue;
|
|
87
|
+
if (record.kind !== catalog.kind) {
|
|
88
|
+
context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
|
|
92
|
+
validateIdentity(record, context);
|
|
93
|
+
context.resources.push(record);
|
|
94
|
+
const catalogs = readCatalogs(record, context.diagnostics);
|
|
95
|
+
for (const nested of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(nested, dirname(file), context);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const record = await loadXnlRecord(file, catalog.shape, context);
|
|
99
|
+
if (!record) continue;
|
|
100
|
+
if (record.kind !== catalog.kind) {
|
|
101
|
+
context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
|
|
105
|
+
validateIdentity(record, context);
|
|
106
|
+
context.resources.push(record);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function catalogFiles(catalog, manifestDir, context) {
|
|
110
|
+
const root = await resolveCatalogDirectory(catalog.root, manifestDir, catalog.location, context);
|
|
111
|
+
if (!root) return [];
|
|
112
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => void 0);
|
|
113
|
+
if (!entries) {
|
|
114
|
+
context.diagnostics.push({
|
|
115
|
+
code: "RESOURCE_CATALOG_ROOT_MISSING",
|
|
116
|
+
location: catalog.location,
|
|
117
|
+
message: "Catalog root does not exist or is not readable."
|
|
118
|
+
});
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
if (catalog.shape === "single-file") return entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".xnl")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name));
|
|
122
|
+
if (!catalog.entry || !isPlainEntry(catalog.entry)) {
|
|
123
|
+
context.diagnostics.push({
|
|
124
|
+
code: "RESOURCE_CATALOG_ENTRY_INVALID",
|
|
125
|
+
location: catalog.location,
|
|
126
|
+
message: "Directory and manifest catalogs require a plain XNL entry filename."
|
|
127
|
+
});
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
return entries.filter((entry) => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name, catalog.entry));
|
|
131
|
+
}
|
|
132
|
+
async function loadXnlRecord(filePath, sourceShape, context) {
|
|
133
|
+
const documentUri = documentUriFor(context.rootDir, filePath);
|
|
134
|
+
const source = await readFile(filePath, "utf8").catch(() => void 0);
|
|
135
|
+
if (source === void 0) {
|
|
136
|
+
context.diagnostics.push({
|
|
137
|
+
code: "RESOURCE_FILE_MISSING",
|
|
138
|
+
location: documentUri,
|
|
139
|
+
message: "Resource file does not exist."
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let root;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = parseXnl(source, { textBlockStyle: true });
|
|
146
|
+
if (parsed.warnings?.length) {
|
|
147
|
+
context.diagnostics.push({
|
|
148
|
+
code: "RESOURCE_XNL_WARNING",
|
|
149
|
+
location: documentUri,
|
|
150
|
+
message: parsed.warnings.map((warning) => warning.message).join("; ")
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (parsed.nodes.length !== 1 || !isDataElement(parsed.nodes[0])) {
|
|
155
|
+
context.diagnostics.push({
|
|
156
|
+
code: "RESOURCE_XNL_ROOT_INVALID",
|
|
157
|
+
location: documentUri,
|
|
158
|
+
message: "XNL resource documents must contain exactly one data-element root."
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
root = parsed.nodes[0];
|
|
163
|
+
} catch (error) {
|
|
164
|
+
context.diagnostics.push({
|
|
165
|
+
code: "RESOURCE_XNL_SYNTAX",
|
|
166
|
+
location: documentUri,
|
|
167
|
+
message: `Invalid XNL resource document: ${error instanceof Error ? error.message : String(error)}`
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const node = normalizeNode(root);
|
|
172
|
+
const resourceId = node.resourceId;
|
|
173
|
+
const apiVersion = asString(node.metadata.apiVersion);
|
|
174
|
+
const lifecycle = asString(node.properties.lifecycle);
|
|
175
|
+
const description = asString(node.properties.description) ?? node.subdomains.Description?.text;
|
|
176
|
+
if (!resourceId) context.diagnostics.push({
|
|
177
|
+
code: "RESOURCE_IDENTITY_MISSING",
|
|
178
|
+
location: documentUri,
|
|
179
|
+
message: "XNL resource roots must declare a #id."
|
|
180
|
+
});
|
|
181
|
+
if (!apiVersion) context.diagnostics.push({
|
|
182
|
+
code: "RESOURCE_METADATA_FIELD_MISSING",
|
|
183
|
+
location: documentUri,
|
|
184
|
+
message: "XNL resources require metadata apiVersion."
|
|
185
|
+
});
|
|
186
|
+
if (!resourceId || !apiVersion) return void 0;
|
|
187
|
+
const metadata = {
|
|
188
|
+
apiVersion,
|
|
189
|
+
...lifecycle ? { lifecycle } : {},
|
|
190
|
+
version: asString(node.metadata.version)
|
|
191
|
+
};
|
|
192
|
+
const logicalPath = relative(context.rootDir, filePath).split(sep).join("/");
|
|
193
|
+
return {
|
|
194
|
+
kind: node.tag,
|
|
195
|
+
resourceId,
|
|
196
|
+
fqn: resourceId,
|
|
197
|
+
...description ? { description } : {},
|
|
198
|
+
metadata,
|
|
199
|
+
sourceShape,
|
|
200
|
+
logicalPath,
|
|
201
|
+
documentUri,
|
|
202
|
+
format: "xnl",
|
|
203
|
+
node
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function normalizeNode(node) {
|
|
207
|
+
const subdomains = {};
|
|
208
|
+
if (node.kind === "DataElement" && node.extend) for (const name of node.extend.order) {
|
|
209
|
+
const child = node.extend.children[name];
|
|
210
|
+
if (child) subdomains[name] = normalizeNode(child);
|
|
211
|
+
}
|
|
212
|
+
return Object.freeze({
|
|
213
|
+
tag: node.tag,
|
|
214
|
+
resourceId: wordToString(node.id),
|
|
215
|
+
metadata: Object.freeze(normalizeMap(node.metadata)),
|
|
216
|
+
properties: Object.freeze(normalizeMap(node.attributes)),
|
|
217
|
+
body: Object.freeze(node.kind === "DataElement" ? (node.body ?? []).map(normalizeValue) : []),
|
|
218
|
+
subdomains: Object.freeze(subdomains),
|
|
219
|
+
...node.kind === "TextElement" ? { text: node.text ?? "" } : {}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function normalizeMap(map) {
|
|
223
|
+
return Object.fromEntries(Object.entries(map ?? {}).map(([key, value]) => [key, normalizeValue(value)]));
|
|
224
|
+
}
|
|
225
|
+
function normalizeValue(value) {
|
|
226
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
227
|
+
if (Array.isArray(value)) return Object.freeze(value.map(normalizeValue));
|
|
228
|
+
if (isElement(value)) return normalizeNode(value);
|
|
229
|
+
if (isWord(value)) return wordToString(value) ?? "";
|
|
230
|
+
if (isComment(value)) return value.value;
|
|
231
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, normalizeValue(child)])));
|
|
232
|
+
}
|
|
233
|
+
function readCatalogs(record, diagnostics) {
|
|
234
|
+
const catalogs = record.node.subdomains.Catalogs;
|
|
235
|
+
if (!catalogs) return [];
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const value of catalogs.body) {
|
|
238
|
+
if (!isResourceNode(value) || value.tag !== "Catalog") continue;
|
|
239
|
+
const id = value.resourceId;
|
|
240
|
+
const kind = asString(value.properties.kind);
|
|
241
|
+
const root = asString(value.properties.root);
|
|
242
|
+
const entry = asString(value.properties.entry);
|
|
243
|
+
const shapeValue = asString(value.properties.shape) ?? (entry ? "directory" : "single-file");
|
|
244
|
+
const location = `${record.documentUri}#Catalog:${id ?? "unknown"}`;
|
|
245
|
+
if (!id || !kind || !root || !isXnlShape(shapeValue)) {
|
|
246
|
+
diagnostics.push({
|
|
247
|
+
code: "RESOURCE_CATALOG_INVALID",
|
|
248
|
+
location,
|
|
249
|
+
message: "Catalog requires #id, kind, root and a valid shape."
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
out.push({
|
|
254
|
+
id,
|
|
255
|
+
kind,
|
|
256
|
+
root,
|
|
257
|
+
entry,
|
|
258
|
+
shape: shapeValue,
|
|
259
|
+
location
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
function normalizeKindDefinition(record, diagnostics) {
|
|
265
|
+
const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
|
|
266
|
+
const shapes = record.node.properties.sourceShapes;
|
|
267
|
+
const sourceShapes = Array.isArray(shapes) ? shapes.filter(isXnlShape) : [];
|
|
268
|
+
if (!resourceKind || sourceShapes.length === 0) {
|
|
269
|
+
diagnostics.push({
|
|
270
|
+
code: "KIND_DEFINITION_INVALID",
|
|
271
|
+
location: record.documentUri,
|
|
272
|
+
message: "KindDefinition requires resourceKind (or an id suffix) and sourceShapes."
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
resourceKind,
|
|
278
|
+
sourceShapes,
|
|
279
|
+
requiredFiles: ((record.node.subdomains.DescriptorContract?.subdomains.RequiredFiles)?.body ?? []).filter(isResourceNode).filter((node) => node.tag === "File").map((node) => asString(node.properties.name)).filter((value) => Boolean(value))
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async function validateRequiredFiles(resourceDir, record, definition, diagnostics) {
|
|
283
|
+
for (const name of definition.requiredFiles) {
|
|
284
|
+
if (!isPlainEntry(name)) {
|
|
285
|
+
diagnostics.push({
|
|
286
|
+
code: "KIND_DEFINITION_REQUIRED_FILE_INVALID",
|
|
287
|
+
location: record.documentUri,
|
|
288
|
+
message: `Invalid required file '${name}'.`
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!await stat(join(resourceDir, name)).then((info) => info.isFile()).catch(() => false)) diagnostics.push({
|
|
293
|
+
code: "RESOURCE_REQUIRED_FILE_MISSING",
|
|
294
|
+
location: `${record.documentUri}#${name}`,
|
|
295
|
+
message: `Resource is missing required material '${name}'.`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function resolveCatalogDirectory(uri, manifestDir, location, context) {
|
|
300
|
+
const relativeMatch = /^vfs:\/\/(\.\/|@\/)(.*)$/.exec(uri);
|
|
301
|
+
if (!relativeMatch || !uri.endsWith("/")) {
|
|
302
|
+
context.diagnostics.push({
|
|
303
|
+
code: "RESOURCE_REF_UNSUPPORTED",
|
|
304
|
+
location,
|
|
305
|
+
message: "Catalog roots must be directory vfs://./ or vfs://@/ references."
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const rawPath = relativeMatch[2];
|
|
310
|
+
let decoded;
|
|
311
|
+
try {
|
|
312
|
+
decoded = decodeURIComponent(rawPath);
|
|
313
|
+
} catch {
|
|
314
|
+
context.diagnostics.push({
|
|
315
|
+
code: "RESOURCE_REF_CONTAINMENT",
|
|
316
|
+
location,
|
|
317
|
+
message: "VFS reference contains invalid encoding."
|
|
318
|
+
});
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (isAbsolute(decoded) || decoded.split(/[\\/]+/).includes("..") || /%2f|%5c/i.test(rawPath)) {
|
|
322
|
+
context.diagnostics.push({
|
|
323
|
+
code: "RESOURCE_REF_CONTAINMENT",
|
|
324
|
+
location,
|
|
325
|
+
message: "VFS reference escapes the owning package boundary."
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const base = relativeMatch[1] === "@/" ? context.rootDir : manifestDir;
|
|
330
|
+
const boundary = await realpath(context.rootDir);
|
|
331
|
+
const candidate = resolve(base, decoded);
|
|
332
|
+
const resolved = await realpath(candidate).catch(() => candidate);
|
|
333
|
+
if (resolved !== boundary && !resolved.startsWith(`${boundary}${sep}`)) {
|
|
334
|
+
context.diagnostics.push({
|
|
335
|
+
code: "RESOURCE_REF_CONTAINMENT",
|
|
336
|
+
location,
|
|
337
|
+
message: "VFS reference resolves outside the owning package boundary."
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
return resolved;
|
|
342
|
+
}
|
|
343
|
+
function validateIdentity(record, context) {
|
|
344
|
+
const prior = context.seenIdentities.get(record.resourceId);
|
|
345
|
+
if (prior) {
|
|
346
|
+
context.diagnostics.push({
|
|
347
|
+
code: "RESOURCE_IDENTITY_DUPLICATE",
|
|
348
|
+
location: record.documentUri,
|
|
349
|
+
message: `Duplicate resource identity '${record.resourceId}'.`,
|
|
350
|
+
hint: `First seen at ${prior}.`
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
context.seenIdentities.set(record.resourceId, record.documentUri);
|
|
355
|
+
}
|
|
356
|
+
function documentUriFor(rootDir, filePath) {
|
|
357
|
+
return `vfs://@/${relative(rootDir, filePath).split(sep).join("/")}`;
|
|
358
|
+
}
|
|
359
|
+
function kindMismatch(actual, expected, location) {
|
|
360
|
+
return {
|
|
361
|
+
code: "RESOURCE_KIND_MISMATCH",
|
|
362
|
+
location,
|
|
363
|
+
message: `Resource kind '${actual}' does not match catalog kind '${expected}'.`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function isXnlShape(value) {
|
|
367
|
+
return value === "single-file" || value === "directory" || value === "manifest";
|
|
368
|
+
}
|
|
369
|
+
function isPlainEntry(value) {
|
|
370
|
+
return Boolean(value) && !value.includes("/") && !value.includes("\\") && !value.includes("..");
|
|
371
|
+
}
|
|
372
|
+
function asString(value) {
|
|
373
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
374
|
+
}
|
|
375
|
+
function isResourceNode(value) {
|
|
376
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "tag" in value;
|
|
377
|
+
}
|
|
378
|
+
function isDataElement(value) {
|
|
379
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "kind" in value && value.kind === "DataElement";
|
|
380
|
+
}
|
|
381
|
+
function isElement(value) {
|
|
382
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "kind" in value && (value.kind === "DataElement" || value.kind === "TextElement");
|
|
383
|
+
}
|
|
384
|
+
function isWord(value) {
|
|
385
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "kind" in value && value.kind === "Word";
|
|
386
|
+
}
|
|
387
|
+
function isComment(value) {
|
|
388
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "kind" in value && value.kind === "Comment";
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region ../resource-core/src/index.ts
|
|
392
|
+
var ResourceValidationError = class extends Error {
|
|
393
|
+
diagnostics;
|
|
394
|
+
constructor(diagnostics) {
|
|
395
|
+
super(`Resource validation failed with ${diagnostics.length} diagnostic(s)`);
|
|
396
|
+
this.name = "ResourceValidationError";
|
|
397
|
+
this.diagnostics = diagnostics;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
async function loadResourceTree(options) {
|
|
401
|
+
const unsupported = unsupportedAuthorityDiagnostic(options);
|
|
402
|
+
if (unsupported) throw new ResourceValidationError([unsupported]);
|
|
403
|
+
const manifestPath = options.manifestPath ?? "manifest.xnl";
|
|
404
|
+
const result = await buildXnlResourceTree({
|
|
405
|
+
...options,
|
|
406
|
+
rootDir: resolve(options.rootDir),
|
|
407
|
+
manifestPath
|
|
408
|
+
});
|
|
409
|
+
if (result.diagnostics.length > 0 || !result.tree) throw new ResourceValidationError(result.diagnostics);
|
|
410
|
+
return result.tree;
|
|
411
|
+
}
|
|
412
|
+
async function validateResourceTree(options) {
|
|
413
|
+
const unsupported = unsupportedAuthorityDiagnostic(options);
|
|
414
|
+
if (unsupported) return [unsupported];
|
|
415
|
+
const manifestPath = options.manifestPath ?? "manifest.xnl";
|
|
416
|
+
return (await buildXnlResourceTree({
|
|
417
|
+
...options,
|
|
418
|
+
rootDir: resolve(options.rootDir),
|
|
419
|
+
manifestPath
|
|
420
|
+
})).diagnostics;
|
|
421
|
+
}
|
|
422
|
+
function unsupportedAuthorityDiagnostic(options) {
|
|
423
|
+
const manifestPath = options.manifestPath ?? "manifest.xnl";
|
|
424
|
+
if (!manifestPath.toLowerCase().endsWith(".xnl")) return {
|
|
425
|
+
code: "RESOURCE_AUTHORITY_FORMAT_UNSUPPORTED",
|
|
426
|
+
location: `vfs://@/${manifestPath}`,
|
|
427
|
+
message: "Resource package manifests must use XNL authority."
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
export { loadResourceTree as n, validateResourceTree as r, ResourceValidationError as t };
|