halfcode-compiler.xnl 0.2.7 → 0.2.9
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/application-assembly.js +1 -1
- package/dist/authoring-runtime.d.ts +163 -1
- package/dist/authoring-runtime.js +2 -2
- package/dist/{resource-core-zEONBZ_o.d.ts → effective-content-identities-CSVFi-N5.d.ts} +2 -8
- package/dist/effective-content-identities-DV5fTr5Q.js +118 -0
- package/dist/{index-X6R_4lMW.d.ts → index-B515mMkO.d.ts} +1 -1
- package/dist/{index-CEJBgfBZ.d.ts → index-CDIT65pI.d.ts} +2 -33
- package/dist/{index-DkqggaxV.d.ts → index-CF4nEkOi.d.ts} +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +6 -5
- package/dist/kind-definition.d.ts +1 -1
- package/dist/resource-core-C0Fj-VJA.d.ts +29 -0
- package/dist/resource-core.d.ts +4 -3
- package/dist/resource-core.js +91 -120
- package/dist/resource-projection.d.ts +1 -1
- package/dist/skill-capsule.d.ts +1 -1
- package/dist/skill-capsule.js +1 -1
- package/dist/src-3MZLm339.js +588 -0
- package/dist/{src-D2au7Wcj.js → src-3pnDAjBD.js} +1 -1
- package/dist/{src-BxyKIkBg.js → src-CaQ25rMj.js} +1 -1
- package/dist/{src-88_zDKZE.js → src-D3qSzGFn.js} +89 -301
- package/dist/system-skills/resource-dsl/documents.md +15 -27
- package/dist/system-skills/resource-dsl/files.md +2 -1
- package/dist/system-skills/sys-halfcode-resource-dsl.plan.js +8 -8
- package/dist/testing.js +1 -1
- package/package.json +6 -2
- package/dist/src-B87eJQat.js +0 -39
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { n as wordToString, t as parseXnl } from "./dist-Bkv7YeVi.js";
|
|
2
|
-
import { lstat,
|
|
3
|
-
import { dirname, extname, isAbsolute, join,
|
|
2
|
+
import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
import { pathToFileURL } from "node:url";
|
|
8
6
|
//#region ../resource-core/src/canonical.ts
|
|
9
7
|
function compareCodeUnits(left, right) {
|
|
10
8
|
if (left < right) return -1;
|
|
@@ -75,17 +73,17 @@ function createResourceContentIdentity(input) {
|
|
|
75
73
|
strictCanonicalInput: false,
|
|
76
74
|
validateClaimedDigest: false
|
|
77
75
|
});
|
|
78
|
-
if (diagnostics.length > 0 || !identity) throw new ResourceCompositionError(stableDiagnostics
|
|
76
|
+
if (diagnostics.length > 0 || !identity) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
|
|
79
77
|
return identity;
|
|
80
78
|
}
|
|
81
79
|
function buildResourceDependencySnapshot(input) {
|
|
82
80
|
const diagnostics = [];
|
|
83
|
-
if (!isEffectiveRegistryAuthentic(input.registry) || input.registry.revision !== input.registry.compositionRevision) throw new ResourceCompositionError(Object.freeze([diagnostic
|
|
81
|
+
if (!isEffectiveRegistryAuthentic(input.registry) || input.registry.revision !== input.registry.compositionRevision) throw new ResourceCompositionError(Object.freeze([diagnostic("RESOURCE_EFFECTIVE_REGISTRY_UNTRUSTED", "effective-registry:revision", "Dependency snapshots require an authentic immutable registry returned by composeLayeredResourceRegistry().")]));
|
|
84
82
|
const roots = validateRoots(input.roots, input.registry, diagnostics);
|
|
85
83
|
const edges = validateEdges(input.edges, input.registry, diagnostics);
|
|
86
84
|
validateContentIdentityIndex(input.contentIdentities, diagnostics);
|
|
87
85
|
const validatedIdentities = validateEffectiveContentIdentities(input.registry, input.contentIdentities, diagnostics);
|
|
88
|
-
if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics
|
|
86
|
+
if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
|
|
89
87
|
const registryRevision = digestCanonical({
|
|
90
88
|
compositionRevision: input.registry.compositionRevision,
|
|
91
89
|
resources: [...validatedIdentities.values()].sort((left, right) => compareCodeUnits(left.resourceId, right.resourceId)).map((identity) => ({
|
|
@@ -109,7 +107,7 @@ function buildResourceDependencySnapshot(input) {
|
|
|
109
107
|
if (visiting.has(resourceId)) {
|
|
110
108
|
const cycleStart = path.indexOf(resourceId);
|
|
111
109
|
const cycle = [...path.slice(cycleStart), resourceId];
|
|
112
|
-
diagnostics.push(diagnostic
|
|
110
|
+
diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_CYCLE", dependencyLocation(resourceId), `Resource dependency cycle detected: ${cycle.join(" -> ")}.`));
|
|
113
111
|
return;
|
|
114
112
|
}
|
|
115
113
|
if (visited.has(resourceId)) return;
|
|
@@ -125,7 +123,7 @@ function buildResourceDependencySnapshot(input) {
|
|
|
125
123
|
visited.add(resourceId);
|
|
126
124
|
};
|
|
127
125
|
for (const root of roots) visit(root);
|
|
128
|
-
if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics
|
|
126
|
+
if (diagnostics.length > 0) throw new ResourceCompositionError(stableDiagnostics(diagnostics));
|
|
129
127
|
const closure = Object.freeze([...reachable].sort(compareCodeUnits).map((resourceId) => {
|
|
130
128
|
const entry = input.registry.byId.get(resourceId);
|
|
131
129
|
const identity = validatedIdentities.get(resourceId);
|
|
@@ -163,19 +161,19 @@ function validateRoots(inputRoots, registry, diagnostics) {
|
|
|
163
161
|
for (const value of inputRoots) {
|
|
164
162
|
const resourceId = value.trim();
|
|
165
163
|
if (resourceId.length === 0) {
|
|
166
|
-
diagnostics.push(diagnostic
|
|
164
|
+
diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_EMPTY", "dependency-root:unnamed", "Dependency snapshot roots must be non-empty resource ids."));
|
|
167
165
|
continue;
|
|
168
166
|
}
|
|
169
167
|
if (seen.has(resourceId)) {
|
|
170
|
-
diagnostics.push(diagnostic
|
|
168
|
+
diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_DUPLICATE", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is declared more than once.`));
|
|
171
169
|
continue;
|
|
172
170
|
}
|
|
173
171
|
seen.add(resourceId);
|
|
174
172
|
roots.push(resourceId);
|
|
175
173
|
const entry = registry.byId.get(resourceId);
|
|
176
|
-
if (!entry || !entry.resource) diagnostics.push(diagnostic
|
|
174
|
+
if (!entry || !entry.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOT_MISSING", dependencyLocation(resourceId), `Dependency snapshot root '${resourceId}' is not an effective resource.`));
|
|
177
175
|
}
|
|
178
|
-
if (roots.length === 0 && diagnostics.length === 0) diagnostics.push(diagnostic
|
|
176
|
+
if (roots.length === 0 && diagnostics.length === 0) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_ROOTS_EMPTY", "dependency-root:none", "Dependency snapshot requires at least one explicit root."));
|
|
179
177
|
return roots;
|
|
180
178
|
}
|
|
181
179
|
function validateEdges(inputEdges, registry, diagnostics) {
|
|
@@ -188,13 +186,13 @@ function validateEdges(inputEdges, registry, diagnostics) {
|
|
|
188
186
|
declaredBy: inputEdge.declaredBy.trim()
|
|
189
187
|
});
|
|
190
188
|
if (!edge.fromResourceId || !edge.toResourceId || !edge.relation || !edge.declaredBy) {
|
|
191
|
-
diagnostics.push(diagnostic
|
|
189
|
+
diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_EDGE_INVALID", `dependency-edge:${index}`, "Dependency edges require non-empty fromResourceId, toResourceId, relation, and declaredBy fields."));
|
|
192
190
|
return;
|
|
193
191
|
}
|
|
194
192
|
const source = registry.byId.get(edge.fromResourceId);
|
|
195
193
|
const target = registry.byId.get(edge.toResourceId);
|
|
196
|
-
if (!source?.resource) diagnostics.push(diagnostic
|
|
197
|
-
if (!target?.resource) diagnostics.push(diagnostic
|
|
194
|
+
if (!source?.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_SOURCE_MISSING", dependencyLocation(edge.fromResourceId), `Dependency edge source '${edge.fromResourceId}' is not an effective resource.`));
|
|
195
|
+
if (!target?.resource) diagnostics.push(diagnostic("RESOURCE_DEPENDENCY_TARGET_MISSING", dependencyLocation(edge.toResourceId), `Dependency edge target '${edge.toResourceId}' is not an effective resource.`));
|
|
198
196
|
values.set(edgeKey(edge), edge);
|
|
199
197
|
});
|
|
200
198
|
return [...values.values()].sort(compareEdges);
|
|
@@ -202,41 +200,41 @@ function validateEdges(inputEdges, registry, diagnostics) {
|
|
|
202
200
|
function normalizeContentIdentity(input, diagnostics, options) {
|
|
203
201
|
const initialDiagnosticCount = diagnostics.length;
|
|
204
202
|
if (!isRecord(input)) {
|
|
205
|
-
diagnostics.push(diagnostic
|
|
203
|
+
diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_INVALID", contentLocation(""), "Resource content identity must be an object fact."));
|
|
206
204
|
return;
|
|
207
205
|
}
|
|
208
206
|
const rawResourceId = input.resourceId;
|
|
209
207
|
const resourceId = typeof rawResourceId === "string" ? rawResourceId.trim() : "";
|
|
210
|
-
if (resourceId.length === 0) diagnostics.push(diagnostic
|
|
211
|
-
else if (options.strictCanonicalInput && rawResourceId !== resourceId) diagnostics.push(diagnostic
|
|
208
|
+
if (resourceId.length === 0) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_ID_EMPTY", contentLocation(""), "Resource content identity requires a non-empty resource id."));
|
|
209
|
+
else if (options.strictCanonicalInput && rawResourceId !== resourceId) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_NON_CANONICAL", contentLocation(resourceId), `Resource content identity '${resourceId}' must use its canonical id without surrounding whitespace.`));
|
|
212
210
|
const authorityDigest = typeof input.authorityDigest === "string" ? input.authorityDigest : "";
|
|
213
|
-
if (!isSha256Digest(authorityDigest)) diagnostics.push(diagnostic
|
|
211
|
+
if (!isSha256Digest(authorityDigest)) diagnostics.push(diagnostic("RESOURCE_CONTENT_AUTHORITY_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has an invalid authority digest.`));
|
|
214
212
|
const rawContributions = input.contributions;
|
|
215
213
|
let contributionFacts = [];
|
|
216
214
|
if (rawContributions === void 0 && !options.requireContributions) contributionFacts = [];
|
|
217
|
-
else if (!Array.isArray(rawContributions)) diagnostics.push(diagnostic
|
|
215
|
+
else if (!Array.isArray(rawContributions)) diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTIONS_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' must provide an explicit digest contribution array.`));
|
|
218
216
|
else contributionFacts = rawContributions;
|
|
219
217
|
const contributions = /* @__PURE__ */ new Map();
|
|
220
218
|
for (const fact of contributionFacts) {
|
|
221
219
|
if (!isRecord(fact)) {
|
|
222
|
-
diagnostics.push(diagnostic
|
|
220
|
+
diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a non-object digest contribution.`));
|
|
223
221
|
continue;
|
|
224
222
|
}
|
|
225
223
|
const rawKey = fact.key;
|
|
226
224
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
227
225
|
if (key.length === 0) {
|
|
228
|
-
diagnostics.push(diagnostic
|
|
226
|
+
diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_KEY_EMPTY", contentLocation(resourceId), `Resource '${resourceId || "unnamed"}' has a digest contribution with an empty key.`));
|
|
229
227
|
continue;
|
|
230
228
|
}
|
|
231
|
-
if (options.strictCanonicalInput && rawKey !== key) diagnostics.push(diagnostic
|
|
229
|
+
if (options.strictCanonicalInput && rawKey !== key) diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_NON_CANONICAL", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' must use its canonical key.`));
|
|
232
230
|
const digest = typeof fact.digest === "string" ? fact.digest : "";
|
|
233
231
|
if (!isSha256Digest(digest)) {
|
|
234
|
-
diagnostics.push(diagnostic
|
|
232
|
+
diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is invalid.`));
|
|
235
233
|
continue;
|
|
236
234
|
}
|
|
237
235
|
const sourceUri = fact.sourceUri;
|
|
238
236
|
if (sourceUri !== void 0 && typeof sourceUri !== "string") {
|
|
239
|
-
diagnostics.push(diagnostic
|
|
237
|
+
diagnostics.push(diagnostic("RESOURCE_DIGEST_CONTRIBUTION_SOURCE_INVALID", contentLocation(resourceId, key), `Digest contribution '${key}' for resource '${resourceId}' has an invalid source URI.`));
|
|
240
238
|
continue;
|
|
241
239
|
}
|
|
242
240
|
const contribution = Object.freeze({
|
|
@@ -247,7 +245,7 @@ function normalizeContentIdentity(input, diagnostics, options) {
|
|
|
247
245
|
const current = contributions.get(key);
|
|
248
246
|
if (current) {
|
|
249
247
|
const conflicting = current.digest !== digest;
|
|
250
|
-
diagnostics.push(diagnostic
|
|
248
|
+
diagnostics.push(diagnostic(conflicting ? "RESOURCE_DIGEST_CONTRIBUTION_CONFLICT" : "RESOURCE_DIGEST_CONTRIBUTION_DUPLICATE", contentLocation(resourceId, key), conflicting ? `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' has conflicting digests.` : `Digest contribution '${key}' for resource '${resourceId || "unnamed"}' is declared more than once.`));
|
|
251
249
|
continue;
|
|
252
250
|
}
|
|
253
251
|
contributions.set(key, contribution);
|
|
@@ -265,11 +263,11 @@ function normalizeContentIdentity(input, diagnostics, options) {
|
|
|
265
263
|
if (options.validateClaimedDigest) {
|
|
266
264
|
const claimedDigest = input.contentDigest;
|
|
267
265
|
if (typeof claimedDigest !== "string" || !isSha256Digest(claimedDigest)) {
|
|
268
|
-
diagnostics.push(diagnostic
|
|
266
|
+
diagnostics.push(diagnostic("RESOURCE_CONTENT_DIGEST_INVALID", contentLocation(resourceId), `Resource '${resourceId}' has an invalid claimed content digest.`));
|
|
269
267
|
return;
|
|
270
268
|
}
|
|
271
269
|
if (claimedDigest !== contentDigest) {
|
|
272
|
-
diagnostics.push(diagnostic
|
|
270
|
+
diagnostics.push(diagnostic("RESOURCE_CONTENT_DIGEST_MISMATCH", contentLocation(resourceId), `Resource '${resourceId}' claimed a content digest that does not match its explicit authority and contributions.`));
|
|
273
271
|
return;
|
|
274
272
|
}
|
|
275
273
|
}
|
|
@@ -286,7 +284,7 @@ function validateEffectiveContentIdentities(registry, identities, diagnostics) {
|
|
|
286
284
|
for (const entry of effectiveEntries) {
|
|
287
285
|
const input = identities.get(entry.resourceId);
|
|
288
286
|
if (input === void 0) {
|
|
289
|
-
diagnostics.push(diagnostic
|
|
287
|
+
diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_MISSING", dependencyLocation(entry.resourceId), `Effective resource '${entry.resourceId}' has no explicit content identity.`));
|
|
290
288
|
continue;
|
|
291
289
|
}
|
|
292
290
|
const identity = normalizeContentIdentity(input, diagnostics, {
|
|
@@ -301,7 +299,7 @@ function validateEffectiveContentIdentities(registry, identities, diagnostics) {
|
|
|
301
299
|
function validateContentIdentityIndex(identities, diagnostics) {
|
|
302
300
|
for (const [key, identity] of [...identities.entries()].sort(([left], [right]) => compareCodeUnits(left, right))) {
|
|
303
301
|
const identityResourceId = isRecord(identity) && typeof identity.resourceId === "string" ? identity.resourceId : void 0;
|
|
304
|
-
if (key !== identityResourceId) diagnostics.push(diagnostic
|
|
302
|
+
if (key !== identityResourceId) diagnostics.push(diagnostic("RESOURCE_CONTENT_IDENTITY_KEY_MISMATCH", dependencyLocation(key), `Content identity index key '${key}' does not match identity '${identityResourceId ?? "invalid"}'.`));
|
|
305
303
|
}
|
|
306
304
|
}
|
|
307
305
|
function isSha256Digest(value) {
|
|
@@ -347,10 +345,10 @@ function cloneOrigin(origin) {
|
|
|
347
345
|
...origin.logicalPath === void 0 ? {} : { logicalPath: origin.logicalPath }
|
|
348
346
|
});
|
|
349
347
|
}
|
|
350
|
-
function stableDiagnostics
|
|
348
|
+
function stableDiagnostics(diagnostics) {
|
|
351
349
|
return Object.freeze([...diagnostics].sort((left, right) => compareCodeUnits(left.location, right.location) || compareCodeUnits(left.code, right.code)));
|
|
352
350
|
}
|
|
353
|
-
function diagnostic
|
|
351
|
+
function diagnostic(code, location, message) {
|
|
354
352
|
return Object.freeze({
|
|
355
353
|
code,
|
|
356
354
|
location,
|
|
@@ -374,224 +372,6 @@ function isLoadedResourceTreeAuthentic(tree) {
|
|
|
374
372
|
return authenticLoadedResourceTrees.has(tree);
|
|
375
373
|
}
|
|
376
374
|
//#endregion
|
|
377
|
-
//#region ../resource-core/src/code-package.ts
|
|
378
|
-
const CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY = "code-binding:module";
|
|
379
|
-
/** @internal Node effect implementation bound by the public facade. */
|
|
380
|
-
const nodeCodePackageRuntime = Object.freeze({
|
|
381
|
-
realpath,
|
|
382
|
-
readFile,
|
|
383
|
-
stat,
|
|
384
|
-
prepareModuleSource: async ({ bytes }) => {
|
|
385
|
-
const directory = await mkdtemp(join(tmpdir(), "halfcode-code-package-module-"));
|
|
386
|
-
const sourcePath = join(directory, "entry.mjs");
|
|
387
|
-
try {
|
|
388
|
-
await writeFile(sourcePath, bytes, { flag: "wx" });
|
|
389
|
-
} catch (error) {
|
|
390
|
-
await rm(directory, {
|
|
391
|
-
recursive: true,
|
|
392
|
-
force: true
|
|
393
|
-
}).catch(() => void 0);
|
|
394
|
-
throw error;
|
|
395
|
-
}
|
|
396
|
-
return Object.freeze({
|
|
397
|
-
url: pathToFileURL(sourcePath).href,
|
|
398
|
-
dispose: async () => rm(directory, {
|
|
399
|
-
recursive: true,
|
|
400
|
-
force: true
|
|
401
|
-
})
|
|
402
|
-
});
|
|
403
|
-
},
|
|
404
|
-
importModule: async (url) => import(url)
|
|
405
|
-
});
|
|
406
|
-
var CodePackageLoadError = class extends Error {
|
|
407
|
-
diagnostics;
|
|
408
|
-
constructor(diagnostics) {
|
|
409
|
-
super(`CodePackage loading failed with ${diagnostics.length} diagnostic(s)`);
|
|
410
|
-
this.name = "CodePackageLoadError";
|
|
411
|
-
this.diagnostics = Object.freeze([...diagnostics]);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
/** @internal Used by the canonical XNL loader before it brands the completed tree. */
|
|
415
|
-
function normalizeCodePackageBinding(record) {
|
|
416
|
-
const diagnostics = [];
|
|
417
|
-
if (record.sourceShape !== "code-package") {
|
|
418
|
-
diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_SHAPE_INVALID", record.documentUri, `Resource '${record.resourceId}' does not use the code-package source shape.`));
|
|
419
|
-
return { diagnostics };
|
|
420
|
-
}
|
|
421
|
-
const declaredEntry = stringValue(record.node.properties.entry);
|
|
422
|
-
const declaredRuntime = stringValue(record.node.properties.runtime);
|
|
423
|
-
const declaredModuleUri = declaredEntry ? normalizeModuleUri(`vfs://./${declaredEntry}`) : void 0;
|
|
424
|
-
if (!declaredEntry) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_DECLARATION_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires a manifest entry.`));
|
|
425
|
-
else if (!declaredModuleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_DECLARATION_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid manifest entry.`));
|
|
426
|
-
if (!declaredRuntime) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_RUNTIME_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires a manifest runtime.`));
|
|
427
|
-
const node = record.node.subdomains.CodeBinding;
|
|
428
|
-
if (!node || node.tag !== "CodeBinding") {
|
|
429
|
-
diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_BINDING_MISSING", record.documentUri, `CodePackage resource '${record.resourceId}' requires one CodeBinding.`));
|
|
430
|
-
return { diagnostics };
|
|
431
|
-
}
|
|
432
|
-
if (node.properties.package !== void 0) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_BINDING_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' must bind a package-local module, not an external package.`));
|
|
433
|
-
const rawModule = stringValue(node.properties.module);
|
|
434
|
-
const moduleUri = rawModule === void 0 ? void 0 : normalizeModuleUri(rawModule);
|
|
435
|
-
if (!moduleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_MODULE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' requires a canonical package-local .js or .mjs module URI.`));
|
|
436
|
-
const rawExport = node.properties.export;
|
|
437
|
-
const exportName = stringValue(rawExport);
|
|
438
|
-
if (rawExport !== void 0 && (!exportName || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName))) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_EXPORT_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid named export binding.`));
|
|
439
|
-
if (moduleUri && declaredModuleUri && moduleUri !== declaredModuleUri) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_BINDING_MISMATCH", record.documentUri, `CodePackage resource '${record.resourceId}' manifest entry and CodeBinding.module must identify the same package-local module.`));
|
|
440
|
-
if (diagnostics.length > 0 || !moduleUri) return { diagnostics: stableDiagnostics(diagnostics) };
|
|
441
|
-
return {
|
|
442
|
-
binding: Object.freeze({
|
|
443
|
-
resourceId: record.resourceId,
|
|
444
|
-
moduleUri,
|
|
445
|
-
...exportName ? { exportName } : {}
|
|
446
|
-
}),
|
|
447
|
-
diagnostics: Object.freeze([])
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
/** @internal Resolves and reads the exact entry bytes under the descriptor directory. */
|
|
451
|
-
async function readCodePackageEntry(runtime, rootDir, record, binding) {
|
|
452
|
-
const diagnostics = [];
|
|
453
|
-
const realRoot = await runtime.realpath(resolve(rootDir)).catch(() => void 0);
|
|
454
|
-
if (!realRoot) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ROOT_MISSING", record.documentUri, `The resource root for '${record.resourceId}' is missing or unreadable.`)] };
|
|
455
|
-
if (!isSafeLogicalPath(record.logicalPath)) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_PROVENANCE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has unsafe logical provenance.`)] };
|
|
456
|
-
const descriptorPath = resolve(realRoot, record.logicalPath);
|
|
457
|
-
const descriptorDirectory = await runtime.realpath(dirname(descriptorPath)).catch(() => void 0);
|
|
458
|
-
if (!descriptorDirectory || !isWithin(realRoot, descriptorDirectory)) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_CONTAINMENT", record.documentUri, `CodePackage resource '${record.resourceId}' resolves outside the resource root.`)] };
|
|
459
|
-
const localPath = moduleLocalPath(binding.moduleUri);
|
|
460
|
-
if (!localPath) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_MODULE_INVALID", record.documentUri, `CodePackage resource '${record.resourceId}' has an invalid module URI.`)] };
|
|
461
|
-
const entryPath = await runtime.realpath(resolve(descriptorDirectory, ...localPath.split("/"))).catch(() => void 0);
|
|
462
|
-
if (!entryPath) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry is missing for '${record.resourceId}'.`)] };
|
|
463
|
-
if (!isWithin(descriptorDirectory, entryPath) || entryPath === descriptorDirectory) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_CONTAINMENT", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' resolves outside its package directory.`));
|
|
464
|
-
if (!(await runtime.stat(entryPath).catch(() => void 0))?.isFile()) diagnostics.push(diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_INVALID", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' must be a regular file.`));
|
|
465
|
-
if (diagnostics.length > 0) return { diagnostics: stableDiagnostics(diagnostics) };
|
|
466
|
-
const bytes = await runtime.readFile(entryPath).catch(() => void 0);
|
|
467
|
-
if (!bytes) return { diagnostics: [diagnostic("RESOURCE_CODE_PACKAGE_ENTRY_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry is unreadable for '${record.resourceId}'.`)] };
|
|
468
|
-
const resourceDirectory = posix.dirname(record.logicalPath);
|
|
469
|
-
return {
|
|
470
|
-
entryPath,
|
|
471
|
-
bytes,
|
|
472
|
-
sourceUri: `vfs://@/${resourceDirectory === "." ? localPath : `${resourceDirectory}/${localPath}`}`,
|
|
473
|
-
diagnostics: Object.freeze([])
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
async function loadCodePackageModule(input) {
|
|
477
|
-
return loadCodePackageModuleWithRuntime(nodeCodePackageRuntime, input);
|
|
478
|
-
}
|
|
479
|
-
/** @internal Pure orchestration over an injected effect runtime; hidden by the package facade. */
|
|
480
|
-
async function loadCodePackageModuleWithRuntime(runtime, input) {
|
|
481
|
-
if (!isLoadedResourceTreeAuthentic(input.tree)) throw loadError("RESOURCE_CODE_PACKAGE_TREE_UNTRUSTED", `code-package:${input.resourceId}`, "CodePackage modules require an authentic LoadedResourceTree.");
|
|
482
|
-
const record = findResource(input.tree, input.resourceId);
|
|
483
|
-
const binding = input.tree.codePackageBindings.get(input.resourceId);
|
|
484
|
-
const identity = input.tree.contentIdentities.get(input.resourceId);
|
|
485
|
-
if (!record || record.sourceShape !== "code-package" || !binding || !identity) throw loadError("RESOURCE_CODE_PACKAGE_RESOURCE_INVALID", `code-package:${input.resourceId}`, `Resource '${input.resourceId}' is not a loaded CodePackage.`);
|
|
486
|
-
const entry = await readCodePackageEntry(runtime, input.rootDir, record, binding);
|
|
487
|
-
if (entry.diagnostics.length > 0 || !entry.entryPath || !entry.bytes) throw new CodePackageLoadError(entry.diagnostics);
|
|
488
|
-
const contribution = identity.contributions.find((item) => item.key === CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY);
|
|
489
|
-
if (!contribution || contribution.digest !== sha256Digest(entry.bytes) || contribution.sourceUri !== entry.sourceUri) throw loadError("RESOURCE_CODE_PACKAGE_REVISION_STALE", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' changed after its ResourceTree was loaded.`);
|
|
490
|
-
let preparedSource;
|
|
491
|
-
try {
|
|
492
|
-
preparedSource = await runtime.prepareModuleSource({
|
|
493
|
-
bytes: entry.bytes,
|
|
494
|
-
revision: identity.contentDigest
|
|
495
|
-
});
|
|
496
|
-
} catch {
|
|
497
|
-
throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_PREPARE_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be prepared for import.`);
|
|
498
|
-
}
|
|
499
|
-
let moduleNamespace;
|
|
500
|
-
let importFailed = false;
|
|
501
|
-
try {
|
|
502
|
-
moduleNamespace = await runtime.importModule(preparedSource.url);
|
|
503
|
-
} catch {
|
|
504
|
-
importFailed = true;
|
|
505
|
-
moduleNamespace = Object.freeze({});
|
|
506
|
-
}
|
|
507
|
-
const cleanupFailed = await preparedSource.dispose().then(() => false, () => true);
|
|
508
|
-
if (importFailed) throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be imported.`);
|
|
509
|
-
if (cleanupFailed) throw loadError("RESOURCE_CODE_PACKAGE_IMPORT_CLEANUP_FAILED", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' could not be cleaned up after import.`);
|
|
510
|
-
if (binding.exportName && !(binding.exportName in moduleNamespace)) throw loadError("RESOURCE_CODE_PACKAGE_EXPORT_MISSING", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' does not export '${binding.exportName}'.`);
|
|
511
|
-
const currentBytes = await runtime.readFile(entry.entryPath).catch(() => void 0);
|
|
512
|
-
if (!currentBytes || sha256Digest(currentBytes) !== contribution.digest) throw loadError("RESOURCE_CODE_PACKAGE_REVISION_STALE", `${record.documentUri}#CodeBinding`, `CodePackage entry for '${record.resourceId}' changed while it was being imported.`);
|
|
513
|
-
const frozenNamespace = Object.freeze(Object.fromEntries(Object.keys(moduleNamespace).sort(compareCodeUnits).map((name) => [name, moduleNamespace[name]])));
|
|
514
|
-
return Object.freeze({
|
|
515
|
-
resourceId: record.resourceId,
|
|
516
|
-
binding,
|
|
517
|
-
revision: identity.contentDigest,
|
|
518
|
-
moduleNamespace: frozenNamespace
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
function admitCodePackageExports(loaded, contract) {
|
|
522
|
-
return admitCandidates([{
|
|
523
|
-
loaded,
|
|
524
|
-
contract
|
|
525
|
-
}]);
|
|
526
|
-
}
|
|
527
|
-
function admitCodePackages(loaded, contract) {
|
|
528
|
-
return admitCandidates(loaded.map((item) => ({
|
|
529
|
-
loaded: item,
|
|
530
|
-
contract
|
|
531
|
-
})));
|
|
532
|
-
}
|
|
533
|
-
function admitCandidates(packages) {
|
|
534
|
-
const definitions = /* @__PURE__ */ new Map();
|
|
535
|
-
for (const { loaded, contract } of packages) {
|
|
536
|
-
const names = loaded.binding.exportName ? [loaded.binding.exportName] : Object.keys(loaded.moduleNamespace).sort(compareCodeUnits);
|
|
537
|
-
for (const name of names) {
|
|
538
|
-
const exported = loaded.moduleNamespace[name];
|
|
539
|
-
const candidates = Array.isArray(exported) ? exported : [exported];
|
|
540
|
-
for (const candidate of candidates) {
|
|
541
|
-
if (!contract.isDefinition(candidate)) continue;
|
|
542
|
-
const identity = contract.identityOf(candidate).trim();
|
|
543
|
-
if (!identity) throw new TypeError(`CodePackage definition from '${loaded.resourceId}' has an empty identity`);
|
|
544
|
-
if (definitions.has(identity)) throw new TypeError(`Duplicate CodePackage definition identity '${identity}'`);
|
|
545
|
-
definitions.set(identity, candidate);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return Object.freeze([...definitions.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([, definition]) => definition));
|
|
550
|
-
}
|
|
551
|
-
function normalizeModuleUri(value) {
|
|
552
|
-
const prefix = "vfs://./";
|
|
553
|
-
if (!value.startsWith(prefix) || value !== value.trim() || value.includes("%") || value.includes("\\") || value.includes("?") || value.includes("#")) return void 0;
|
|
554
|
-
const localPath = value.slice(8);
|
|
555
|
-
if (!localPath || isAbsolute(localPath) || posix.isAbsolute(localPath)) return void 0;
|
|
556
|
-
const parts = localPath.split("/");
|
|
557
|
-
if (parts.some((part) => !part || part === "." || part === "..")) return void 0;
|
|
558
|
-
const extension = extname(localPath).toLowerCase();
|
|
559
|
-
if (extension !== ".js" && extension !== ".mjs") return void 0;
|
|
560
|
-
return `${prefix}${parts.join("/")}`;
|
|
561
|
-
}
|
|
562
|
-
function moduleLocalPath(moduleUri) {
|
|
563
|
-
return normalizeModuleUri(moduleUri)?.slice(8);
|
|
564
|
-
}
|
|
565
|
-
function findResource(tree, resourceId) {
|
|
566
|
-
for (const records of tree.registry.byKind.values()) {
|
|
567
|
-
const record = records.find((item) => item.resourceId === resourceId);
|
|
568
|
-
if (record) return record;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
function isSafeLogicalPath(value) {
|
|
572
|
-
return Boolean(value) && !isAbsolute(value) && !value.includes("\\") && !value.split("/").some((part) => !part || part === "." || part === "..");
|
|
573
|
-
}
|
|
574
|
-
function isWithin(boundary, candidate) {
|
|
575
|
-
const path = relative(boundary, candidate);
|
|
576
|
-
return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
|
|
577
|
-
}
|
|
578
|
-
function stringValue(value) {
|
|
579
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
580
|
-
}
|
|
581
|
-
function loadError(code, location, message) {
|
|
582
|
-
return new CodePackageLoadError([diagnostic(code, location, message)]);
|
|
583
|
-
}
|
|
584
|
-
function stableDiagnostics(diagnostics) {
|
|
585
|
-
return Object.freeze([...diagnostics].sort((left, right) => compareCodeUnits(left.location, right.location) || compareCodeUnits(left.code, right.code)));
|
|
586
|
-
}
|
|
587
|
-
function diagnostic(code, location, message) {
|
|
588
|
-
return Object.freeze({
|
|
589
|
-
code,
|
|
590
|
-
location,
|
|
591
|
-
message
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
//#endregion
|
|
595
375
|
//#region ../resource-core/src/readonly-map.ts
|
|
596
376
|
function readonlyMap(entries) {
|
|
597
377
|
const values = new Map(entries);
|
|
@@ -635,8 +415,7 @@ async function buildXnlResourceTree(options) {
|
|
|
635
415
|
seenIdentities: /* @__PURE__ */ new Map(),
|
|
636
416
|
authorityFiles: /* @__PURE__ */ new Map(),
|
|
637
417
|
recordAuthorityDigests: /* @__PURE__ */ new WeakMap(),
|
|
638
|
-
recordContributions: /* @__PURE__ */ new WeakMap()
|
|
639
|
-
codePackageBindings: /* @__PURE__ */ new Map()
|
|
418
|
+
recordContributions: /* @__PURE__ */ new WeakMap()
|
|
640
419
|
};
|
|
641
420
|
const manifest = await loadManifest(resolve(context.rootDir, options.manifestPath), "manifest", context, true);
|
|
642
421
|
if (!manifest || context.diagnostics.length > 0) return { diagnostics: context.diagnostics };
|
|
@@ -649,7 +428,6 @@ async function buildXnlResourceTree(options) {
|
|
|
649
428
|
const frozenByKind = readonlyMap([...byKind.entries()].sort(([left], [right]) => compareCodeUnits(left, right)).map(([kind, records]) => [kind, Object.freeze([...records].sort((left, right) => compareCodeUnits(left.resourceId, right.resourceId)))]));
|
|
650
429
|
const frozenKindDefinitions = readonlyMap([...context.kindDefinitions.entries()].sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
651
430
|
const contentIdentities = readonlyMap(context.resources.map((resource) => [resource.resourceId, loadedContentIdentity(resource, context)]).sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
652
|
-
const codePackageBindings = readonlyMap([...context.codePackageBindings.entries()].sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
653
431
|
return {
|
|
654
432
|
diagnostics: [],
|
|
655
433
|
tree: markLoadedResourceTreeAuthentic(Object.freeze({
|
|
@@ -659,24 +437,38 @@ async function buildXnlResourceTree(options) {
|
|
|
659
437
|
kindDefinitions: frozenKindDefinitions
|
|
660
438
|
}),
|
|
661
439
|
diagnostics: Object.freeze([]),
|
|
662
|
-
contentIdentities
|
|
663
|
-
codePackageBindings
|
|
440
|
+
contentIdentities
|
|
664
441
|
}))
|
|
665
442
|
};
|
|
666
443
|
}
|
|
667
444
|
async function loadManifest(filePath, sourceShape, context, isRoot) {
|
|
668
445
|
const record = await loadXnlRecord(filePath, sourceShape, context);
|
|
669
446
|
if (!record) return void 0;
|
|
670
|
-
if (isRoot && record.kind !== "ResourcePackage") {
|
|
671
|
-
context.diagnostics.push({
|
|
672
|
-
code: "RESOURCE_XNL_ROOT_INVALID",
|
|
673
|
-
location: record.documentUri,
|
|
674
|
-
message: "The root manifest.xnl must contain one ResourcePackage root."
|
|
675
|
-
});
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
447
|
const catalogs = readCatalogs(record, context.diagnostics);
|
|
679
448
|
for (const catalog of catalogs.filter((item) => item.kind === "KindDefinition")) await loadKindDefinitions(catalog, dirname(filePath), context);
|
|
449
|
+
if (isRoot && record.kind !== "ResourcePackage") {
|
|
450
|
+
const definition = context.kindDefinitions.get(record.kind);
|
|
451
|
+
if (!definition) {
|
|
452
|
+
context.diagnostics.push({
|
|
453
|
+
code: "KIND_DEFINITION_MISSING",
|
|
454
|
+
location: record.documentUri,
|
|
455
|
+
message: `No KindDefinition is registered for semantic root kind '${record.kind}'.`
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!definition.sourceShapes.includes("manifest")) {
|
|
460
|
+
context.diagnostics.push({
|
|
461
|
+
code: "RESOURCE_SOURCE_SHAPE_NOT_ALLOWED",
|
|
462
|
+
location: record.documentUri,
|
|
463
|
+
message: `Semantic root kind '${record.kind}' does not allow source shape 'manifest'.`
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
validateResourceApiVersion(record, definition, context.diagnostics);
|
|
468
|
+
await validateRequiredFiles(dirname(filePath), record, definition, context.diagnostics);
|
|
469
|
+
validateIdentity(record, context);
|
|
470
|
+
context.resources.push(record);
|
|
471
|
+
}
|
|
680
472
|
for (const catalog of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(catalog, dirname(filePath), context);
|
|
681
473
|
if (!isRoot) {
|
|
682
474
|
validateIdentity(record, context);
|
|
@@ -749,7 +541,6 @@ async function loadCatalog(catalog, manifestDir, context) {
|
|
|
749
541
|
}
|
|
750
542
|
validateResourceApiVersion(record, definition, context.diagnostics);
|
|
751
543
|
await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
|
|
752
|
-
if (catalog.shape === "code-package" && !await prepareCodePackageRecord(record, context)) continue;
|
|
753
544
|
validateIdentity(record, context);
|
|
754
545
|
context.resources.push(record);
|
|
755
546
|
}
|
|
@@ -785,27 +576,26 @@ async function catalogFiles(catalog, manifestDir, context) {
|
|
|
785
576
|
context.diagnostics.push({
|
|
786
577
|
code: "RESOURCE_CATALOG_ENTRY_INVALID",
|
|
787
578
|
location: catalog.location,
|
|
788
|
-
message: "Directory
|
|
579
|
+
message: "Directory and manifest catalogs require a plain XNL entry filename."
|
|
789
580
|
});
|
|
790
581
|
return [];
|
|
791
582
|
}
|
|
792
|
-
if (catalog.shape === "
|
|
583
|
+
if (catalog.shape === "directory" && catalog.scope === "root") {
|
|
793
584
|
const directEntry = join(root, catalog.entry);
|
|
794
585
|
const directStat = await lstat(directEntry).catch((error) => {
|
|
795
586
|
if (error.code === "ENOENT") return void 0;
|
|
796
587
|
throw error;
|
|
797
588
|
});
|
|
798
|
-
if (directStat)
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
return [directEntry];
|
|
589
|
+
if (!directStat) return [directEntry];
|
|
590
|
+
if (directStat.isSymbolicLink() || !directStat.isFile()) {
|
|
591
|
+
context.diagnostics.push({
|
|
592
|
+
code: "RESOURCE_CATALOG_ENTRY_INVALID",
|
|
593
|
+
location: documentUriFor(context.rootDir, directEntry),
|
|
594
|
+
message: "A root-scoped directory catalog entry must be a regular non-symbolic-link XNL file."
|
|
595
|
+
});
|
|
596
|
+
return [];
|
|
808
597
|
}
|
|
598
|
+
return [directEntry];
|
|
809
599
|
}
|
|
810
600
|
return entries.filter((entry) => entry.isDirectory()).sort((left, right) => compareCodeUnits(left.name, right.name)).map((entry) => join(root, entry.name, catalog.entry));
|
|
811
601
|
}
|
|
@@ -993,22 +783,6 @@ function loadedContentIdentity(resource, context) {
|
|
|
993
783
|
contributions: context.recordContributions.get(resource) ?? []
|
|
994
784
|
});
|
|
995
785
|
}
|
|
996
|
-
async function prepareCodePackageRecord(record, context) {
|
|
997
|
-
const normalized = normalizeCodePackageBinding(record);
|
|
998
|
-
context.diagnostics.push(...normalized.diagnostics);
|
|
999
|
-
if (!normalized.binding) return false;
|
|
1000
|
-
const entry = await readCodePackageEntry(nodeCodePackageRuntime, context.rootDir, record, normalized.binding);
|
|
1001
|
-
context.diagnostics.push(...entry.diagnostics);
|
|
1002
|
-
if (!entry.bytes || !entry.sourceUri) return false;
|
|
1003
|
-
const contribution = Object.freeze({
|
|
1004
|
-
key: CODE_PACKAGE_ENTRY_CONTRIBUTION_KEY,
|
|
1005
|
-
digest: sha256Digest(entry.bytes),
|
|
1006
|
-
sourceUri: entry.sourceUri
|
|
1007
|
-
});
|
|
1008
|
-
context.recordContributions.set(record, Object.freeze([contribution]));
|
|
1009
|
-
context.codePackageBindings.set(record.resourceId, normalized.binding);
|
|
1010
|
-
return true;
|
|
1011
|
-
}
|
|
1012
786
|
function resourceRecordFromRoot(root, filePath, sourceShape, context) {
|
|
1013
787
|
const documentUri = documentUriFor(context.rootDir, filePath);
|
|
1014
788
|
const node = normalizeNode(root);
|
|
@@ -1079,18 +853,26 @@ function readCatalogs(record, diagnostics) {
|
|
|
1079
853
|
if (!catalogs) return [];
|
|
1080
854
|
const out = [];
|
|
1081
855
|
for (const value of catalogs.body) {
|
|
1082
|
-
if (!isResourceNode(value)
|
|
856
|
+
if (!isResourceNode(value)) continue;
|
|
857
|
+
const fixedShape = catalogShapeForTag(value.tag);
|
|
858
|
+
if (value.tag !== "Catalog" && !fixedShape) continue;
|
|
1083
859
|
const id = value.resourceId;
|
|
1084
|
-
const kind = asString(value.properties.kind);
|
|
860
|
+
const kind = asString(value.properties.resourceKind) ?? asString(value.properties.kind);
|
|
1085
861
|
const root = asString(value.properties.root);
|
|
1086
862
|
const entry = asString(value.properties.entry);
|
|
1087
|
-
const
|
|
1088
|
-
const
|
|
1089
|
-
|
|
863
|
+
const declaredShape = asString(value.properties.shape);
|
|
864
|
+
const shapeValue = fixedShape ?? declaredShape ?? (entry ? "directory" : "single-file");
|
|
865
|
+
const declaredScope = asString(value.properties.scope);
|
|
866
|
+
const scope = declaredScope ?? "children";
|
|
867
|
+
const location = `${record.documentUri}#${value.tag}:${id ?? "unknown"}`;
|
|
868
|
+
const shapeConflict = Boolean(fixedShape && declaredShape && declaredShape !== fixedShape);
|
|
869
|
+
const invalidScope = scope !== "root" && scope !== "children";
|
|
870
|
+
const scopeNotAllowed = Boolean(declaredScope && shapeValue !== "directory");
|
|
871
|
+
if (!id || !kind || !root || !isXnlShape(shapeValue) || shapeConflict || invalidScope || scopeNotAllowed) {
|
|
1090
872
|
diagnostics.push({
|
|
1091
873
|
code: "RESOURCE_CATALOG_INVALID",
|
|
1092
874
|
location,
|
|
1093
|
-
message: "
|
|
875
|
+
message: "Resource catalog requires #id, resourceKind (or legacy kind), root, a tag-consistent shape and a valid directory scope."
|
|
1094
876
|
});
|
|
1095
877
|
continue;
|
|
1096
878
|
}
|
|
@@ -1100,11 +882,17 @@ function readCatalogs(record, diagnostics) {
|
|
|
1100
882
|
root,
|
|
1101
883
|
entry,
|
|
1102
884
|
shape: shapeValue,
|
|
885
|
+
scope,
|
|
1103
886
|
location
|
|
1104
887
|
});
|
|
1105
888
|
}
|
|
1106
889
|
return out;
|
|
1107
890
|
}
|
|
891
|
+
function catalogShapeForTag(tag) {
|
|
892
|
+
if (tag === "FileResourceCatalog") return "single-file";
|
|
893
|
+
if (tag === "DirectoryResourceCatalog") return "directory";
|
|
894
|
+
if (tag === "ManifestResourceCatalog") return "manifest";
|
|
895
|
+
}
|
|
1108
896
|
function normalizeKindDefinition(record, diagnostics) {
|
|
1109
897
|
const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
|
|
1110
898
|
const shapes = record.node.properties.sourceShapes;
|
|
@@ -1243,7 +1031,7 @@ function kindMismatch(actual, expected, location) {
|
|
|
1243
1031
|
};
|
|
1244
1032
|
}
|
|
1245
1033
|
function isXnlShape(value) {
|
|
1246
|
-
return value === "single-file" || value === "directory" || value === "manifest"
|
|
1034
|
+
return value === "single-file" || value === "directory" || value === "manifest";
|
|
1247
1035
|
}
|
|
1248
1036
|
function isDocumentCardinality(value) {
|
|
1249
1037
|
return value === "one" || value === "many";
|
|
@@ -1332,4 +1120,4 @@ function unsupportedAuthorityDiagnostic(options) {
|
|
|
1332
1120
|
};
|
|
1333
1121
|
}
|
|
1334
1122
|
//#endregion
|
|
1335
|
-
export {
|
|
1123
|
+
export { isLoadedResourceTreeAuthentic as a, sha256Digest as c, markEffectiveRegistryAuthentic as d, ResourceCompositionError as f, readonlyMap as i, effectiveRegistryLayerBindings as l, digestCanonical as m, loadResourceTree as n, buildResourceDependencySnapshot as o, compareCodeUnits as p, validateResourceTree as r, createResourceContentIdentity as s, ResourceValidationError as t, isEffectiveRegistryAuthentic as u };
|