halfcode-compiler.xnl 0.2.0 → 0.2.2

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.
@@ -0,0 +1,305 @@
1
+ import { n as wordToString, t as parseXnl } from "./dist-Bkv7YeVi.js";
2
+ import { copyFile, lstat, mkdir, readdir } from "node:fs/promises";
3
+ import { dirname, join, posix, relative, resolve, sep } from "node:path";
4
+ //#region ../resource-mapping/src/index.ts
5
+ var ResourceMappingPathError = class extends Error {
6
+ uri;
7
+ attribute;
8
+ owner;
9
+ rawPath;
10
+ issue;
11
+ code = "RESOURCE_MAPPING_PATH_INVALID";
12
+ constructor(uri, attribute, owner, rawPath, issue) {
13
+ super(`RESOURCE_MAPPING_PATH_INVALID: ${uri} owner ${owner} raw ${attribute} ${JSON.stringify(rawPath)}: ${issue}`);
14
+ this.uri = uri;
15
+ this.attribute = attribute;
16
+ this.owner = owner;
17
+ this.rawPath = rawPath;
18
+ this.issue = issue;
19
+ this.name = "ResourceMappingPathError";
20
+ }
21
+ };
22
+ function parseResourceMappings(source, uri = "vfs://./resource-mappings.xnl") {
23
+ let root;
24
+ try {
25
+ const document = parseXnl(source);
26
+ if (document.warnings?.length || document.nodes.length !== 1 || !isDataElement(document.nodes[0])) throw new Error("expected exactly one ResourceMappings data root without warnings");
27
+ root = document.nodes[0];
28
+ } catch (error) {
29
+ throw new Error(`Invalid ResourceMappings at ${uri}: ${error instanceof Error ? error.message : String(error)}`);
30
+ }
31
+ if (root.tag !== "ResourceMappings") throw new Error(`Invalid ResourceMappings at ${uri}: root must be ResourceMappings`);
32
+ const referenceTargets = /* @__PURE__ */ new Map();
33
+ for (const entry of bodyElements(extension(root, "ReferenceTargets"), "ReferenceTarget")) {
34
+ const kind = requiredXnlProperty(entry, "kind", uri);
35
+ if (referenceTargets.has(kind)) throw new Error(`Invalid ResourceMappings at ${uri}: duplicate ReferenceTarget kind ${kind}`);
36
+ referenceTargets.set(kind, normalizeOutputDirectory(requiredXnlPathProperty(entry, "target", uri), uri, `generated-reference:${kind}`));
37
+ }
38
+ const callable = extension(root, "CallableArtifacts");
39
+ const callableArtifactsTarget = normalizeOutputDirectory(callable ? requiredXnlPathProperty(callable, "target", uri) : "functions/", uri, "generated-callable-artifacts");
40
+ const sources = bodyElements(extension(root, "SourceRoots"), "SourceRoot").map((entry) => {
41
+ const id = wordToString(entry.id);
42
+ if (!id || !/^[A-Z][A-Za-z0-9]*$/.test(id)) throw new Error(`Invalid ResourceMappings at ${uri}: SourceRoot #id must be a PascalCase segment`);
43
+ const operations = (entry.body ?? []).filter(isDataElement).filter((operation) => operation.tag === "CopyDirectory" || operation.tag === "CopyFile").map((operation) => {
44
+ const owner = `mapping:${id}:${operation.tag}`;
45
+ return {
46
+ kind: operation.tag,
47
+ from: normalizeRelativePath(requiredXnlPathProperty(operation, "from", uri), uri, "from", owner),
48
+ to: normalizeRelativePath(requiredXnlPathProperty(operation, "to", uri), uri, "to", owner)
49
+ };
50
+ });
51
+ return {
52
+ id,
53
+ sourceRoot: requiredXnlProperty(entry, "sourceRoot", uri),
54
+ operations
55
+ };
56
+ });
57
+ if (new Set(sources.map((item) => item.id)).size !== sources.length) throw new Error(`Invalid ResourceMappings at ${uri}: SourceRoot ids must be unique`);
58
+ return {
59
+ referenceTargets,
60
+ callableArtifactsTarget,
61
+ sources,
62
+ source: { uri }
63
+ };
64
+ }
65
+ function extension(node, name) {
66
+ const child = node.extend?.children[name];
67
+ return child?.kind === "DataElement" ? child : void 0;
68
+ }
69
+ function bodyElements(node, tag) {
70
+ return (node?.body ?? []).filter(isDataElement).filter((entry) => entry.tag === tag);
71
+ }
72
+ function requiredXnlProperty(node, name, uri) {
73
+ const value = node.attributes?.[name];
74
+ if (typeof value !== "string" || !value.trim()) throw new Error(`Invalid ResourceMappings at ${uri}: <${node.tag}> requires ${name}`);
75
+ return value;
76
+ }
77
+ function requiredXnlPathProperty(node, name, uri) {
78
+ const value = node.attributes?.[name];
79
+ if (typeof value !== "string") throw new Error(`Invalid ResourceMappings at ${uri}: <${node.tag}> requires ${name}`);
80
+ return value;
81
+ }
82
+ function isDataElement(value) {
83
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "kind" in value && value.kind === "DataElement";
84
+ }
85
+ async function planResourceMappings(mappings, options) {
86
+ const roots = asRootMap(options.moduleRoots);
87
+ const claims = [];
88
+ const entries = [];
89
+ const reserved = (options.reservedTargetPaths ?? []).map((path) => normalizeRelativePath(path, mappings.source.uri, "to", `generated-reserved:${path}`));
90
+ const normalizedSources = mappings.sources.map((mapping) => ({
91
+ ...mapping,
92
+ operations: mapping.operations.map((operation) => {
93
+ const owner = `mapping:${mapping.id}:${operation.kind}`;
94
+ return {
95
+ ...operation,
96
+ from: normalizeRelativePath(operation.from, mappings.source.uri, "from", owner),
97
+ to: normalizeRelativePath(operation.to, mappings.source.uri, "to", owner)
98
+ };
99
+ })
100
+ }));
101
+ for (const mapping of normalizedSources) {
102
+ const sourceRootPath = resolveSourceRoot(mapping.sourceRoot, roots, options.defaultSourceRoot, mappings.source.uri);
103
+ await assertDirectory(sourceRootPath, mappings.source.uri, mapping.sourceRoot);
104
+ for (const operation of mapping.operations) {
105
+ const owner = `mapping:${mapping.id}:${operation.kind}`;
106
+ const from = operation.from;
107
+ const to = operation.to;
108
+ for (const prior of claims) if (pathsOverlap(prior.target, to)) throw new Error(`ResourceMappings target collision between ${prior.mappingId}:${prior.from} -> ${prior.target} and ${mapping.id}:${from} -> ${to}`);
109
+ claims.push({
110
+ target: to,
111
+ mappingId: mapping.id,
112
+ from
113
+ });
114
+ const sourcePath = safeResolve(sourceRootPath, from, mappings.source.uri);
115
+ if (operation.kind === "CopyFile") {
116
+ await assertFile(sourcePath, mappings.source.uri, from);
117
+ assertNotReserved(to, reserved, mappings.source.uri);
118
+ entries.push({
119
+ sourcePath,
120
+ targetRelativePath: to,
121
+ mappingId: mapping.id
122
+ });
123
+ continue;
124
+ }
125
+ await assertDirectory(sourcePath, mappings.source.uri, from);
126
+ for (const filePath of await listFiles(sourcePath, mappings.source.uri)) {
127
+ const child = normalizeRelativePath(relative(sourcePath, filePath).split(sep).join("/"), mappings.source.uri, "from", `${owner}:source-child`);
128
+ const targetRelativePath = posix.join(to, child);
129
+ assertSafeResourceMappingPath(targetRelativePath, mappings.source.uri, "to", owner, "relative-path");
130
+ assertNotReserved(targetRelativePath, reserved, mappings.source.uri);
131
+ entries.push({
132
+ sourcePath: filePath,
133
+ targetRelativePath,
134
+ mappingId: mapping.id
135
+ });
136
+ }
137
+ }
138
+ }
139
+ const seenTargets = /* @__PURE__ */ new Map();
140
+ for (const entry of entries) {
141
+ const prior = seenTargets.get(entry.targetRelativePath);
142
+ if (prior) throw new Error(`ResourceMappings duplicate target ${entry.targetRelativePath} from ${prior.sourcePath} and ${entry.sourcePath}`);
143
+ seenTargets.set(entry.targetRelativePath, entry);
144
+ }
145
+ return { entries: entries.sort((a, b) => compareCodeUnits(a.targetRelativePath, b.targetRelativePath)) };
146
+ }
147
+ async function applyResourceMappingPlan(plan, outputRoot) {
148
+ for (const entry of plan.entries) assertSafeResourceMappingPath(entry.targetRelativePath, "resource-mapping-plan", "to", `mapping:${entry.mappingId}:planned-copy`, "relative-path");
149
+ const resolvedOutputRoot = resolve(outputRoot);
150
+ const files = [];
151
+ for (const entry of plan.entries) {
152
+ const target = resolve(resolvedOutputRoot, entry.targetRelativePath);
153
+ if (!isWithinRoot(resolvedOutputRoot, target)) throw new Error(`ResourceMappings output escapes target root: ${entry.targetRelativePath}`);
154
+ await mkdir(dirname(target), { recursive: true });
155
+ await copyFile(entry.sourcePath, target);
156
+ files.push(entry.targetRelativePath);
157
+ }
158
+ return files;
159
+ }
160
+ function resolveSourceRoot(uri, roots, defaultSourceRoot, mappingUri) {
161
+ const moduleMatch = /^vfs:\/\/module\/([A-Z][A-Za-z0-9]*)\/(.*)$/.exec(uri);
162
+ if (moduleMatch) {
163
+ const base = roots.get(moduleMatch[1]);
164
+ if (!base) throw new Error(`Invalid ResourceMappings at ${mappingUri}: unknown module root ${moduleMatch[1]}`);
165
+ return safeResolve(resolve(base), moduleMatch[2], mappingUri);
166
+ }
167
+ const localMatch = /^vfs:\/\/@\/(.*)$/.exec(uri);
168
+ if (localMatch && defaultSourceRoot) return safeResolve(resolve(defaultSourceRoot), localMatch[1], mappingUri);
169
+ throw new Error(`Invalid ResourceMappings at ${mappingUri}: unsupported sourceRoot ${uri}`);
170
+ }
171
+ async function listFiles(root, uri) {
172
+ const files = [];
173
+ for (const entry of (await readdir(root, { withFileTypes: true })).sort((a, b) => compareCodeUnits(a.name, b.name))) {
174
+ const path = join(root, entry.name);
175
+ if (entry.isSymbolicLink()) throw new Error(`Invalid ResourceMappings at ${uri}: symbolic links are not supported: ${path}`);
176
+ if (entry.isDirectory()) files.push(...await listFiles(path, uri));
177
+ else if (entry.isFile()) files.push(path);
178
+ else throw new Error(`Invalid ResourceMappings at ${uri}: unsupported source entry ${path}`);
179
+ }
180
+ return files;
181
+ }
182
+ async function assertDirectory(path, uri, label) {
183
+ const info = await lstat(path).catch(() => void 0);
184
+ if (!info || info.isSymbolicLink() || !info.isDirectory()) throw new Error(`Invalid ResourceMappings at ${uri}: directory source does not exist: ${label}`);
185
+ }
186
+ async function assertFile(path, uri, label) {
187
+ const info = await lstat(path).catch(() => void 0);
188
+ if (!info || info.isSymbolicLink() || !info.isFile()) throw new Error(`Invalid ResourceMappings at ${uri}: file source does not exist: ${label}`);
189
+ }
190
+ function safeResolve(root, path, uri) {
191
+ const target = resolve(root, path);
192
+ if (!isWithinRoot(root, target)) throw new Error(`Invalid ResourceMappings at ${uri}: path escapes source root: ${path}`);
193
+ return target;
194
+ }
195
+ function isWithinRoot(root, target) {
196
+ return target === root || target.startsWith(`${root}${sep}`);
197
+ }
198
+ function assertNotReserved(path, reserved, uri) {
199
+ for (const item of reserved) if (pathsOverlap(path, item)) throw new Error(`Invalid ResourceMappings at ${uri}: target ${path} conflicts with generated target ${item}`);
200
+ }
201
+ function pathsOverlap(left, right) {
202
+ return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
203
+ }
204
+ function normalizeRelativePath(value, uri, attribute, owner) {
205
+ assertSafeResourceMappingPath(value, uri, attribute, owner, "relative-directory");
206
+ return value.endsWith("/") ? value.slice(0, -1) : value;
207
+ }
208
+ function normalizeOutputDirectory(value, uri, owner) {
209
+ return `${normalizeRelativePath(value, uri, "to", owner)}/`;
210
+ }
211
+ function assertSafeResourceMappingPath(value, uri, attribute, owner, kind) {
212
+ const issue = safePathLexicalIssue(value, kind);
213
+ if (issue) throw new ResourceMappingPathError(uri, attribute, owner, value, issue);
214
+ }
215
+ function safePathLexicalIssue(value, kind) {
216
+ if (value.length === 0) return "path must be non-empty";
217
+ const unicode = unsafeUnicodeCodeUnitIssue(value);
218
+ if (unicode) return unicode;
219
+ if (value.includes("\\")) return "backslash separators are not allowed";
220
+ if (value.startsWith("/")) return "absolute paths are not allowed";
221
+ if (isWindowsDriveAbsolute(value)) return "Windows drive-absolute paths are not allowed";
222
+ if (kind === "segment" && value.includes("/")) return "path segment must not contain a separator";
223
+ const segments = kind === "segment" ? [value] : value.split("/");
224
+ for (const [index, segment] of segments.entries()) {
225
+ const isAllowedDirectoryMarker = kind === "relative-directory" && index === segments.length - 1 && segment.length === 0 && segments.length > 1;
226
+ if (segment.length === 0 && !isAllowedDirectoryMarker) return `path segment ${index} must be non-empty`;
227
+ if (isAllowedDirectoryMarker) continue;
228
+ if (segment.trim() !== segment) return `path segment ${index} has leading or trailing whitespace`;
229
+ if (segment === "." || segment === ".." || decodedDotSegment(segment)) return `path segment ${index} must not be dot or parent traversal`;
230
+ const encoded = unsafePercentEncodingIssue(segment);
231
+ if (encoded) return `path segment ${index} ${encoded}`;
232
+ }
233
+ }
234
+ function unsafeUnicodeCodeUnitIssue(value) {
235
+ for (let index = 0; index < value.length;) {
236
+ const codeUnit = value.charCodeAt(index);
237
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
238
+ const next = index + 1 < value.length ? value.charCodeAt(index + 1) : void 0;
239
+ if (next === void 0 || next < 56320 || next > 57343) return `contains unpaired high surrogate ${formatCodePoint(codeUnit)} at UTF-16 index ${index}`;
240
+ index += 2;
241
+ continue;
242
+ }
243
+ if (codeUnit >= 56320 && codeUnit <= 57343) return `contains unpaired low surrogate ${formatCodePoint(codeUnit)} at UTF-16 index ${index}`;
244
+ if (isUnsafeControlCodePoint(codeUnit)) return `contains control code point ${formatCodePoint(codeUnit)} at UTF-16 index ${index}`;
245
+ index += 1;
246
+ }
247
+ }
248
+ function unsafeControlCodePointIssue(value) {
249
+ return unsafeUnicodeCodeUnitIssue(value);
250
+ }
251
+ function isUnsafeControlCodePoint(codePoint) {
252
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
253
+ }
254
+ function formatCodePoint(codePoint) {
255
+ return `U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`;
256
+ }
257
+ function isWindowsDriveAbsolute(value) {
258
+ if (value.length < 3 || value[1] !== ":" || value[2] !== "/") return false;
259
+ const first = value.charCodeAt(0);
260
+ return first >= 65 && first <= 90 || first >= 97 && first <= 122;
261
+ }
262
+ function decodedDotSegment(segment) {
263
+ let decoded = "";
264
+ for (let index = 0; index < segment.length;) if (percentEncodedByteAt(segment, index) === 46) {
265
+ decoded += ".";
266
+ index += 3;
267
+ } else {
268
+ decoded += segment[index];
269
+ index += 1;
270
+ }
271
+ return decoded === "." || decoded === "..";
272
+ }
273
+ function unsafePercentEncodingIssue(segment) {
274
+ for (let index = 0; index < segment.length; index += 1) {
275
+ const byte = percentEncodedByteAt(segment, index);
276
+ if (byte === void 0) continue;
277
+ if (byte === 47 || byte === 92) return `contains encoded separator %${byte.toString(16).toUpperCase().padStart(2, "0")}`;
278
+ if (isUnsafeControlCodePoint(byte)) return `contains encoded control byte %${byte.toString(16).toUpperCase().padStart(2, "0")}`;
279
+ index += 2;
280
+ }
281
+ }
282
+ function percentEncodedByteAt(value, index) {
283
+ if (value[index] !== "%" || index + 2 >= value.length) return void 0;
284
+ const high = asciiHexValue(value.charCodeAt(index + 1));
285
+ const low = asciiHexValue(value.charCodeAt(index + 2));
286
+ return high === void 0 || low === void 0 ? void 0 : high * 16 + low;
287
+ }
288
+ function asciiHexValue(code) {
289
+ if (code >= 48 && code <= 57) return code - 48;
290
+ if (code >= 65 && code <= 70) return code - 65 + 10;
291
+ if (code >= 97 && code <= 102) return code - 97 + 10;
292
+ }
293
+ function compareCodeUnits(left, right) {
294
+ return left < right ? -1 : left > right ? 1 : 0;
295
+ }
296
+ function asRootMap(value) {
297
+ return typeof value.get === "function" ? value : new Map(Object.entries(value));
298
+ }
299
+ const resourceMappingPackage = {
300
+ role: "framework",
301
+ area: "resource-mapping",
302
+ owns: "declarative multi-root resource copy planning and collision preflight"
303
+ };
304
+ //#endregion
305
+ export { resourceMappingPackage as a, unsafeUnicodeCodeUnitIssue as c, planResourceMappings as i, applyResourceMappingPlan as n, safePathLexicalIssue as o, parseResourceMappings as r, unsafeControlCodePointIssue as s, ResourceMappingPathError as t };