@kubuild/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3614 @@
1
+ // src/binding-resolver.ts
2
+ var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3
+ function resolveBinding(binding, context) {
4
+ const segments = binding.key.split(".").filter(Boolean);
5
+ let current = context?.variables;
6
+ for (const segment of segments) {
7
+ if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
8
+ return applyMissingPolicy(binding);
9
+ }
10
+ if (current === null || typeof current !== "object") {
11
+ return applyMissingPolicy(binding);
12
+ }
13
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) {
14
+ return applyMissingPolicy(binding);
15
+ }
16
+ current = current[segment];
17
+ }
18
+ if (current === void 0 || typeof current === "function") {
19
+ return applyMissingPolicy(binding);
20
+ }
21
+ return { status: "resolved", value: current };
22
+ }
23
+ function applyMissingPolicy(binding) {
24
+ if (binding.fallback !== void 0) {
25
+ return { status: "fallback", value: binding.fallback };
26
+ }
27
+ return { status: "empty", value: "" };
28
+ }
29
+ function resolveBindingValue(binding, context) {
30
+ return resolveBinding(binding, context).value;
31
+ }
32
+
33
+ // src/variable-catalog.ts
34
+ var FORBIDDEN_KEY_SEGMENTS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
35
+ function buildSampleVariablesFromCatalog(catalog) {
36
+ const result = {};
37
+ if (!catalog) {
38
+ return result;
39
+ }
40
+ for (const entry of catalog) {
41
+ const segments = entry.key.split(".").filter(Boolean);
42
+ if (segments.length === 0 || segments.some((segment) => FORBIDDEN_KEY_SEGMENTS2.has(segment))) {
43
+ continue;
44
+ }
45
+ let cursor = result;
46
+ for (let i = 0; i < segments.length - 1; i += 1) {
47
+ const segment = segments[i];
48
+ const existing = cursor[segment];
49
+ if (existing === null || typeof existing !== "object" || Array.isArray(existing)) {
50
+ cursor[segment] = {};
51
+ }
52
+ cursor = cursor[segment];
53
+ }
54
+ cursor[segments[segments.length - 1]] = entry.sampleValue;
55
+ }
56
+ return result;
57
+ }
58
+
59
+ // src/validator.ts
60
+ import {
61
+ PageDocumentSchema,
62
+ SCHEMA_NAME
63
+ } from "@kubuild/schema";
64
+
65
+ // src/security.ts
66
+ var DEFAULT_DOCUMENT_SECURITY_LIMITS = {
67
+ maxNodeCount: 5e3,
68
+ maxTreeDepth: 32,
69
+ maxPropsCount: 200,
70
+ maxStringLength: 1e5,
71
+ maxChildrenPerNode: 1e3,
72
+ maxDocumentSizeBytes: 10 * 1024 * 1024,
73
+ // 10 MB
74
+ allowedUrlSchemes: ["http:", "https:", "mailto:", "tel:", "#", "relative", "data:image/"],
75
+ prohibitedPropertyKeys: [
76
+ "__proto__",
77
+ "constructor",
78
+ "prototype",
79
+ "__defineGetter__",
80
+ "__defineSetter__",
81
+ "__lookupGetter__",
82
+ "__lookupSetter__"
83
+ ],
84
+ prohibitedAssetExtensions: [
85
+ ".exe",
86
+ ".dll",
87
+ ".so",
88
+ ".sh",
89
+ ".bat",
90
+ ".cmd",
91
+ ".js",
92
+ ".mjs",
93
+ ".cjs",
94
+ ".php",
95
+ ".py",
96
+ ".vbs",
97
+ ".wasm",
98
+ ".bin",
99
+ ".com",
100
+ ".scr",
101
+ ".pif",
102
+ ".hta",
103
+ ".jar"
104
+ ],
105
+ maxZipExpansionRatio: 100
106
+ };
107
+ function isSafeUrl(url, options) {
108
+ if (typeof url !== "string") return false;
109
+ const trimmed = url.trim();
110
+ if (trimmed.length === 0) return true;
111
+ if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("?")) {
112
+ return true;
113
+ }
114
+ if (/[\u0000-\u001F\u007F-\u009F]/.test(trimmed)) {
115
+ return false;
116
+ }
117
+ const colonIndex = trimmed.indexOf(":");
118
+ if (colonIndex === -1) {
119
+ return true;
120
+ }
121
+ const scheme = trimmed.substring(0, colonIndex + 1).toLowerCase();
122
+ const dangerousSchemes = ["javascript:", "vbscript:", "file:", "data:"];
123
+ if (scheme !== "data:" && dangerousSchemes.includes(scheme)) {
124
+ if (scheme === "blob:" && options?.allowBlobMedia) {
125
+ return true;
126
+ }
127
+ return false;
128
+ }
129
+ if (scheme === "data:") {
130
+ const allowDataImage = options?.allowDataImage ?? true;
131
+ if (!allowDataImage) return false;
132
+ const dataPrefix = trimmed.substring(0, 30).toLowerCase();
133
+ const safeDataImagePrefixes = [
134
+ "data:image/png",
135
+ "data:image/jpeg",
136
+ "data:image/jpg",
137
+ "data:image/webp",
138
+ "data:image/gif",
139
+ "data:image/avif",
140
+ "data:image/svg+xml"
141
+ ];
142
+ return safeDataImagePrefixes.some((p) => dataPrefix.startsWith(p));
143
+ }
144
+ const allowed = options?.allowedSchemes || DEFAULT_DOCUMENT_SECURITY_LIMITS.allowedUrlSchemes;
145
+ return allowed.some((s) => s.toLowerCase() === scheme);
146
+ }
147
+ function sanitizeUrl(url, fallback = "", options) {
148
+ if (typeof url !== "string") return fallback;
149
+ return isSafeUrl(url, options) ? url : fallback;
150
+ }
151
+ function containsProhibitedKeys(obj, prohibitedKeys = DEFAULT_DOCUMENT_SECURITY_LIMITS.prohibitedPropertyKeys) {
152
+ const prohibitedSet = new Set(prohibitedKeys.map((k) => k.toLowerCase()));
153
+ const visited = /* @__PURE__ */ new WeakSet();
154
+ function check(value, currentPath) {
155
+ if (!value || typeof value !== "object") {
156
+ return { found: false, path: "", key: "" };
157
+ }
158
+ if (visited.has(value)) {
159
+ return { found: false, path: "", key: "" };
160
+ }
161
+ visited.add(value);
162
+ if (Array.isArray(value)) {
163
+ for (let i = 0; i < value.length; i++) {
164
+ const res = check(value[i], `${currentPath}[${i}]`);
165
+ if (res.found) return res;
166
+ }
167
+ return { found: false, path: "", key: "" };
168
+ }
169
+ const record = value;
170
+ const keysToCheck = Array.from(/* @__PURE__ */ new Set([...Object.keys(record), ...Object.getOwnPropertyNames(record)]));
171
+ for (const key of keysToCheck) {
172
+ if (prohibitedSet.has(key.toLowerCase())) {
173
+ return { found: true, path: currentPath ? `${currentPath}.${key}` : key, key };
174
+ }
175
+ const res = check(record[key], currentPath ? `${currentPath}.${key}` : key);
176
+ if (res.found) return res;
177
+ }
178
+ if (Object.prototype.hasOwnProperty.call(record, "__proto__")) {
179
+ return { found: true, path: currentPath ? `${currentPath}.__proto__` : "__proto__", key: "__proto__" };
180
+ }
181
+ return { found: false, path: "", key: "" };
182
+ }
183
+ return check(obj, "");
184
+ }
185
+ function validateDocumentSecurity(doc, customLimits) {
186
+ const limits = { ...DEFAULT_DOCUMENT_SECURITY_LIMITS, ...customLimits };
187
+ const errors = [];
188
+ if (!doc || typeof doc !== "object") {
189
+ return { safe: true, errors: [] };
190
+ }
191
+ const protoCheck = containsProhibitedKeys(doc, limits.prohibitedPropertyKeys);
192
+ if (protoCheck.found) {
193
+ errors.push({
194
+ code: "PROTOTYPE_POLLUTION_DETECTED",
195
+ message: `Prohibited property key "${protoCheck.key}" detected at path "${protoCheck.path}" (Prototype Pollution Defense).`,
196
+ path: protoCheck.path,
197
+ details: { key: protoCheck.key }
198
+ });
199
+ }
200
+ try {
201
+ const jsonStr = JSON.stringify(doc);
202
+ if (jsonStr.length > limits.maxDocumentSizeBytes) {
203
+ errors.push({
204
+ code: "MAX_DOCUMENT_SIZE_EXCEEDED",
205
+ message: `Document size (${jsonStr.length} bytes) exceeds limit of ${limits.maxDocumentSizeBytes} bytes.`,
206
+ details: { size: jsonStr.length, limit: limits.maxDocumentSizeBytes }
207
+ });
208
+ }
209
+ } catch {
210
+ }
211
+ const docObj = doc;
212
+ const rootNode = docObj.document;
213
+ if (rootNode && typeof rootNode === "object") {
214
+ let traverse2 = function(node, depth, currentPath) {
215
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
216
+ totalNodes++;
217
+ if (totalNodes > limits.maxNodeCount) {
218
+ errors.push({
219
+ code: "MAX_NODE_COUNT_EXCEEDED",
220
+ message: `Total document nodes (${totalNodes}) exceeded security limit of ${limits.maxNodeCount}.`,
221
+ path: currentPath,
222
+ details: { nodeCount: totalNodes, limit: limits.maxNodeCount }
223
+ });
224
+ return;
225
+ }
226
+ if (depth > limits.maxTreeDepth) {
227
+ errors.push({
228
+ code: "MAX_TREE_DEPTH_EXCEEDED",
229
+ message: `Document nesting depth (${depth}) exceeded security limit of ${limits.maxTreeDepth}.`,
230
+ path: currentPath,
231
+ details: { depth, limit: limits.maxTreeDepth }
232
+ });
233
+ return;
234
+ }
235
+ const nodeRecord = node;
236
+ if (nodeRecord.props && typeof nodeRecord.props === "object" && !Array.isArray(nodeRecord.props)) {
237
+ const propsRecord = nodeRecord.props;
238
+ const propKeys = Object.keys(propsRecord);
239
+ if (propKeys.length > limits.maxPropsCount) {
240
+ errors.push({
241
+ code: "MAX_PROPS_COUNT_EXCEEDED",
242
+ message: `Node has ${propKeys.length} props, exceeding limit of ${limits.maxPropsCount}.`,
243
+ path: `${currentPath}/props`,
244
+ details: { count: propKeys.length, limit: limits.maxPropsCount }
245
+ });
246
+ }
247
+ for (const [k, val] of Object.entries(propsRecord)) {
248
+ if (typeof val === "string" && val.length > limits.maxStringLength) {
249
+ errors.push({
250
+ code: "MAX_STRING_LENGTH_EXCEEDED",
251
+ message: `Prop "${k}" string length (${val.length}) exceeds limit of ${limits.maxStringLength}.`,
252
+ path: `${currentPath}/props/${k}`,
253
+ details: { length: val.length, limit: limits.maxStringLength }
254
+ });
255
+ }
256
+ }
257
+ }
258
+ if (nodeRecord.children && Array.isArray(nodeRecord.children)) {
259
+ if (nodeRecord.children.length > limits.maxChildrenPerNode) {
260
+ errors.push({
261
+ code: "MAX_CHILDREN_COUNT_EXCEEDED",
262
+ message: `Node has ${nodeRecord.children.length} direct children, exceeding limit of ${limits.maxChildrenPerNode}.`,
263
+ path: `${currentPath}/children`,
264
+ details: { count: nodeRecord.children.length, limit: limits.maxChildrenPerNode }
265
+ });
266
+ }
267
+ for (let i = 0; i < nodeRecord.children.length; i++) {
268
+ traverse2(nodeRecord.children[i], depth + 1, `${currentPath}/children/${i}`);
269
+ }
270
+ }
271
+ };
272
+ var traverse = traverse2;
273
+ let totalNodes = 0;
274
+ traverse2(rootNode, 1, "/document");
275
+ }
276
+ return {
277
+ safe: errors.length === 0,
278
+ errors
279
+ };
280
+ }
281
+ function isDangerousAssetFilename(filename, prohibitedExtensions = DEFAULT_DOCUMENT_SECURITY_LIMITS.prohibitedAssetExtensions) {
282
+ if (!filename || typeof filename !== "string") return true;
283
+ const lower = filename.toLowerCase().trim();
284
+ return prohibitedExtensions.some((ext) => {
285
+ const extLower = ext.toLowerCase();
286
+ return lower.endsWith(extLower) || lower.includes(`${extLower}.`);
287
+ });
288
+ }
289
+ function checkZipBomb(compressedSize, uncompressedSize, maxRatio = DEFAULT_DOCUMENT_SECURITY_LIMITS.maxZipExpansionRatio) {
290
+ if (compressedSize <= 0) return false;
291
+ if (uncompressedSize > 10 * 1024 * 1024) {
292
+ const ratio = uncompressedSize / compressedSize;
293
+ return ratio > maxRatio;
294
+ }
295
+ return false;
296
+ }
297
+ function sanitizeHtml(rawHtml) {
298
+ if (typeof rawHtml !== "string") return "";
299
+ let html = rawHtml;
300
+ html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
301
+ html = html.replace(/<\/?(object|embed|applet|base|link)\b[^>]*>/gi, "");
302
+ html = html.replace(/\s+on[a-z0-9_-]+\s*=\s*(?:'[^']*'|"[^"]*"|[^\s>]+)/gi, "");
303
+ html = html.replace(
304
+ /\b(href|src|data|action)\s*=\s*(['"])\s*(?:javascript:|vbscript:|data:text\/html)[^'"]*\2/gi,
305
+ '$1="#"'
306
+ );
307
+ html = html.replace(
308
+ /\b(href|src|data|action)\s*=\s*(?:javascript:|vbscript:|data:text\/html)[^\s>]+/gi,
309
+ '$1="#"'
310
+ );
311
+ return html;
312
+ }
313
+
314
+ // src/validator.ts
315
+ function validateDocument(input, options = {}) {
316
+ const errors = [];
317
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
318
+ return {
319
+ valid: false,
320
+ success: false,
321
+ errors: [
322
+ {
323
+ code: "GLOBAL_SCHEMA_INVALID",
324
+ message: "Document must be a valid non-null object",
325
+ path: ""
326
+ }
327
+ ]
328
+ };
329
+ }
330
+ const doc = input;
331
+ const securityCheck = validateDocumentSecurity(doc, options.securityLimits);
332
+ if (!securityCheck.safe) {
333
+ for (const secErr of securityCheck.errors) {
334
+ errors.push({
335
+ code: secErr.code === "PROTOTYPE_POLLUTION_DETECTED" ? "PROTOTYPE_POLLUTION_DETECTED" : "SECURITY_LIMIT_EXCEEDED",
336
+ message: secErr.message,
337
+ path: secErr.path || "",
338
+ details: secErr.details
339
+ });
340
+ }
341
+ if (securityCheck.errors.some((e) => e.code === "PROTOTYPE_POLLUTION_DETECTED")) {
342
+ return {
343
+ valid: false,
344
+ success: false,
345
+ errors
346
+ };
347
+ }
348
+ }
349
+ if (doc.schema !== SCHEMA_NAME) {
350
+ errors.push({
351
+ code: "GLOBAL_SCHEMA_INVALID",
352
+ message: `Invalid schema identifier. Expected "${SCHEMA_NAME}", received "${String(doc.schema)}"`,
353
+ path: "/schema"
354
+ });
355
+ }
356
+ if (typeof doc.version !== "string" || doc.version.trim().length === 0) {
357
+ errors.push({
358
+ code: "GLOBAL_SCHEMA_INVALID",
359
+ message: "Schema version must be a non-empty string",
360
+ path: "/version"
361
+ });
362
+ }
363
+ if (!doc.document || typeof doc.document !== "object" || Array.isArray(doc.document)) {
364
+ errors.push({
365
+ code: "GLOBAL_SCHEMA_INVALID",
366
+ message: "Document must contain a root document node object",
367
+ path: "/document"
368
+ });
369
+ return {
370
+ valid: false,
371
+ success: false,
372
+ errors
373
+ };
374
+ }
375
+ const rootNode = doc.document;
376
+ if (rootNode.type !== "page") {
377
+ errors.push({
378
+ code: "ROOT_NODE_INVALID",
379
+ message: `Root node type must be "page", received "${String(rootNode.type)}"`,
380
+ path: "/document/type",
381
+ nodeId: typeof rootNode.id === "string" ? rootNode.id : void 0
382
+ });
383
+ }
384
+ if (doc.metadata !== void 0) {
385
+ if (typeof doc.metadata !== "object" || doc.metadata === null || Array.isArray(doc.metadata)) {
386
+ errors.push({
387
+ code: "INVALID_METADATA",
388
+ message: "Document metadata must be an object",
389
+ path: "/metadata"
390
+ });
391
+ } else {
392
+ const meta = doc.metadata;
393
+ if (meta.title !== void 0 && (typeof meta.title !== "string" || meta.title.trim().length === 0)) {
394
+ errors.push({
395
+ code: "INVALID_METADATA",
396
+ message: "Metadata title must be a non-empty string when provided",
397
+ path: "/metadata/title"
398
+ });
399
+ }
400
+ }
401
+ }
402
+ const seenIds = /* @__PURE__ */ new Map();
403
+ const visitedObjects = /* @__PURE__ */ new Set();
404
+ validateNodeRecursive(
405
+ rootNode,
406
+ "/document",
407
+ void 0,
408
+ seenIds,
409
+ visitedObjects,
410
+ options,
411
+ errors
412
+ );
413
+ if (errors.length === 0) {
414
+ const zodResult = PageDocumentSchema.safeParse(input);
415
+ if (!zodResult.success) {
416
+ for (const issue of zodResult.error.issues) {
417
+ const jsonPath = "/" + issue.path.join("/");
418
+ errors.push({
419
+ code: "GLOBAL_SCHEMA_INVALID",
420
+ message: issue.message,
421
+ path: jsonPath === "/" ? "" : jsonPath
422
+ });
423
+ }
424
+ } else {
425
+ return {
426
+ valid: true,
427
+ success: true,
428
+ errors: [],
429
+ data: zodResult.data
430
+ };
431
+ }
432
+ }
433
+ return {
434
+ valid: errors.length === 0,
435
+ success: errors.length === 0,
436
+ errors
437
+ };
438
+ }
439
+ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visitedObjects, options, errors) {
440
+ if (visitedObjects.has(nodeObj)) {
441
+ errors.push({
442
+ code: "TREE_CYCLE_DETECTED",
443
+ message: `Cyclic reference detected at node path ${currentPath}`,
444
+ path: currentPath,
445
+ nodeId: typeof nodeObj.id === "string" ? nodeObj.id : void 0
446
+ });
447
+ return;
448
+ }
449
+ visitedObjects.add(nodeObj);
450
+ const nodeId = nodeObj.id;
451
+ if (typeof nodeId !== "string" || nodeId.trim().length === 0) {
452
+ errors.push({
453
+ code: "NODE_SHAPE_INVALID",
454
+ message: "Node id must be a non-empty string",
455
+ path: `${currentPath}/id`
456
+ });
457
+ } else {
458
+ if (seenIds.has(nodeId)) {
459
+ errors.push({
460
+ code: "DUPLICATE_NODE_ID",
461
+ message: `Duplicate node ID "${nodeId}" found at ${currentPath} (first seen at ${seenIds.get(nodeId)})`,
462
+ path: `${currentPath}/id`,
463
+ nodeId,
464
+ details: { firstSeenAt: seenIds.get(nodeId) }
465
+ });
466
+ } else {
467
+ seenIds.set(nodeId, currentPath);
468
+ }
469
+ }
470
+ const nodeType = nodeObj.type;
471
+ if (typeof nodeType !== "string" || nodeType.trim().length === 0) {
472
+ errors.push({
473
+ code: "NODE_SHAPE_INVALID",
474
+ message: "Node type must be a non-empty string",
475
+ path: `${currentPath}/type`,
476
+ nodeId: typeof nodeId === "string" ? nodeId : void 0
477
+ });
478
+ }
479
+ const effectiveNodeId = typeof nodeId === "string" ? nodeId : void 0;
480
+ const effectiveNodeType = typeof nodeType === "string" ? nodeType : "";
481
+ let componentDef;
482
+ if (effectiveNodeType) {
483
+ if (options.componentRegistry) {
484
+ componentDef = options.componentRegistry.get(effectiveNodeType);
485
+ if (!componentDef && (options.strictComponentTypes || !options.componentRegistry.has(effectiveNodeType))) {
486
+ errors.push({
487
+ code: "UNKNOWN_COMPONENT_TYPE",
488
+ message: `Unknown component type: "${effectiveNodeType}"`,
489
+ path: `${currentPath}/type`,
490
+ nodeId: effectiveNodeId
491
+ });
492
+ }
493
+ } else if (options.knownComponentTypes) {
494
+ const isKnown = Array.isArray(options.knownComponentTypes) ? options.knownComponentTypes.includes(effectiveNodeType) : options.knownComponentTypes.has(effectiveNodeType);
495
+ if (!isKnown && options.strictComponentTypes) {
496
+ errors.push({
497
+ code: "UNKNOWN_COMPONENT_TYPE",
498
+ message: `Unknown component type: "${effectiveNodeType}"`,
499
+ path: `${currentPath}/type`,
500
+ nodeId: effectiveNodeId
501
+ });
502
+ }
503
+ }
504
+ }
505
+ const children = nodeObj.children;
506
+ if (children !== void 0) {
507
+ if (!Array.isArray(children)) {
508
+ errors.push({
509
+ code: "NODE_SHAPE_INVALID",
510
+ message: "Node children must be an array",
511
+ path: `${currentPath}/children`,
512
+ nodeId: effectiveNodeId
513
+ });
514
+ } else {
515
+ if (componentDef && componentDef.acceptsChildren === false && children.length > 0) {
516
+ errors.push({
517
+ code: "CHILD_POLICY_VIOLATION",
518
+ message: `Component "${effectiveNodeType}" does not accept children, but found ${children.length} child node(s)`,
519
+ path: `${currentPath}/children`,
520
+ nodeId: effectiveNodeId
521
+ });
522
+ }
523
+ if (parentNode && componentDef?.disallowedParents?.includes(parentNode.type)) {
524
+ errors.push({
525
+ code: "CHILD_POLICY_VIOLATION",
526
+ message: `Component "${effectiveNodeType}" cannot be a child of "${parentNode.type}"`,
527
+ path: currentPath,
528
+ nodeId: effectiveNodeId
529
+ });
530
+ }
531
+ for (let i = 0; i < children.length; i++) {
532
+ const child = children[i];
533
+ const childPath = `${currentPath}/children/${i}`;
534
+ if (!child || typeof child !== "object" || Array.isArray(child)) {
535
+ errors.push({
536
+ code: "NODE_SHAPE_INVALID",
537
+ message: `Child at index ${i} must be a valid node object`,
538
+ path: childPath,
539
+ nodeId: effectiveNodeId
540
+ });
541
+ continue;
542
+ }
543
+ const childRecord = child;
544
+ const childType = typeof childRecord.type === "string" ? childRecord.type : "";
545
+ const childIsKnown = !options.componentRegistry || options.componentRegistry.has(childType);
546
+ const childCategory = options.componentRegistry?.get(childType)?.category;
547
+ if (componentDef?.allowedChildren && childType && childIsKnown && !componentDef.allowedChildren.includes(childType) && !componentDef.allowedChildren.includes("*") && !(childCategory && componentDef.allowedChildren.includes(childCategory))) {
548
+ errors.push({
549
+ code: "CHILD_POLICY_VIOLATION",
550
+ message: `Component type "${childType}" is not allowed as a child of "${effectiveNodeType}". Allowed types: ${componentDef.allowedChildren.join(", ")}`,
551
+ path: `${childPath}/type`,
552
+ nodeId: typeof childRecord.id === "string" ? childRecord.id : void 0
553
+ });
554
+ }
555
+ validateNodeRecursive(
556
+ childRecord,
557
+ childPath,
558
+ { id: effectiveNodeId || "", type: effectiveNodeType },
559
+ seenIds,
560
+ visitedObjects,
561
+ options,
562
+ errors
563
+ );
564
+ }
565
+ }
566
+ }
567
+ if (nodeObj.props !== void 0) {
568
+ if (typeof nodeObj.props !== "object" || nodeObj.props === null || Array.isArray(nodeObj.props)) {
569
+ errors.push({
570
+ code: "NODE_SHAPE_INVALID",
571
+ message: "Node props must be an object",
572
+ path: `${currentPath}/props`,
573
+ nodeId: effectiveNodeId
574
+ });
575
+ } else {
576
+ validatePropsBindings(
577
+ nodeObj.props,
578
+ `${currentPath}/props`,
579
+ effectiveNodeId,
580
+ options,
581
+ errors
582
+ );
583
+ }
584
+ }
585
+ if (nodeObj.styles !== void 0) {
586
+ if (typeof nodeObj.styles !== "object" || nodeObj.styles === null || Array.isArray(nodeObj.styles)) {
587
+ errors.push({
588
+ code: "NODE_SHAPE_INVALID",
589
+ message: "Node styles must be an object",
590
+ path: `${currentPath}/styles`,
591
+ nodeId: effectiveNodeId
592
+ });
593
+ }
594
+ }
595
+ }
596
+ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
597
+ for (const [key, value] of Object.entries(propsObj)) {
598
+ const currentPath = `${propsPath}/${key}`;
599
+ if (!value || typeof value !== "object") {
600
+ continue;
601
+ }
602
+ if (Array.isArray(value)) {
603
+ value.forEach((item, index) => {
604
+ if (item && typeof item === "object" && !Array.isArray(item)) {
605
+ validatePropsBindings(
606
+ item,
607
+ `${currentPath}/${index}`,
608
+ nodeId,
609
+ options,
610
+ errors
611
+ );
612
+ }
613
+ });
614
+ continue;
615
+ }
616
+ const record = value;
617
+ if (record.type === "asset") {
618
+ if (typeof record.assetId !== "string" || record.assetId.trim().length === 0) {
619
+ errors.push({
620
+ code: "INVALID_ASSET_REFERENCE",
621
+ message: 'Asset reference must have a non-empty "assetId"',
622
+ path: `${currentPath}/assetId`,
623
+ nodeId
624
+ });
625
+ }
626
+ if (record.fallbackUrl !== void 0 && typeof record.fallbackUrl !== "string") {
627
+ errors.push({
628
+ code: "INVALID_ASSET_REFERENCE",
629
+ message: "Asset fallbackUrl must be a string if provided",
630
+ path: `${currentPath}/fallbackUrl`,
631
+ nodeId
632
+ });
633
+ }
634
+ } else if (record.type === "variable") {
635
+ if (typeof record.key !== "string" || record.key.trim().length === 0) {
636
+ errors.push({
637
+ code: "INVALID_VARIABLE_BINDING",
638
+ message: 'Variable binding must have a non-empty "key"',
639
+ path: `${currentPath}/key`,
640
+ nodeId
641
+ });
642
+ }
643
+ } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
644
+ if (typeof record.type !== "string" || record.type.trim().length === 0) {
645
+ errors.push({
646
+ code: "INVALID_ACTION_BINDING",
647
+ message: 'Action binding must have a non-empty "type"',
648
+ path: `${currentPath}/type`,
649
+ nodeId
650
+ });
651
+ }
652
+ } else {
653
+ validatePropsBindings(record, currentPath, nodeId, options, errors);
654
+ }
655
+ }
656
+ }
657
+
658
+ // src/document-utils.ts
659
+ import { SCHEMA_NAME as SCHEMA_NAME2 } from "@kubuild/schema";
660
+ function createBlankDocument(title = "Untitled Page") {
661
+ return {
662
+ schema: SCHEMA_NAME2,
663
+ version: "1.0.0",
664
+ metadata: {
665
+ title,
666
+ description: "",
667
+ author: "",
668
+ tags: [],
669
+ category: "general",
670
+ version: "1.0.0",
671
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
672
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
673
+ },
674
+ document: {
675
+ id: "root-page",
676
+ type: "page",
677
+ props: { title },
678
+ styles: { base: { minHeight: "100vh", backgroundColor: "#ffffff" } },
679
+ children: []
680
+ }
681
+ };
682
+ }
683
+ function findNodeById(rootNode, targetId) {
684
+ if (rootNode.id === targetId) return rootNode;
685
+ if (!rootNode.children) return null;
686
+ for (const child of rootNode.children) {
687
+ const found = findNodeById(child, targetId);
688
+ if (found) return found;
689
+ }
690
+ return null;
691
+ }
692
+ function collectAssetReferences(rootNode) {
693
+ const results = [];
694
+ function traverseProps(propsObj, currentPath, nodeId) {
695
+ if (!propsObj || typeof propsObj !== "object") return;
696
+ if (Array.isArray(propsObj)) {
697
+ propsObj.forEach((item, index) => {
698
+ traverseProps(item, `${currentPath}/${index}`, nodeId);
699
+ });
700
+ return;
701
+ }
702
+ const record = propsObj;
703
+ if (record.type === "asset" && typeof record.assetId === "string" && record.assetId.trim().length > 0) {
704
+ results.push({
705
+ assetId: record.assetId,
706
+ reference: {
707
+ type: "asset",
708
+ assetId: record.assetId,
709
+ filename: typeof record.filename === "string" ? record.filename : void 0,
710
+ mimeType: typeof record.mimeType === "string" ? record.mimeType : void 0,
711
+ fallbackUrl: typeof record.fallbackUrl === "string" ? record.fallbackUrl : void 0
712
+ },
713
+ nodeId,
714
+ propPath: currentPath
715
+ });
716
+ }
717
+ for (const [key, val] of Object.entries(record)) {
718
+ if (val && typeof val === "object") {
719
+ traverseProps(val, `${currentPath}/${key}`, nodeId);
720
+ }
721
+ }
722
+ }
723
+ function traverseNode(node) {
724
+ if (node.props) {
725
+ traverseProps(node.props, `/document/${node.id}/props`, node.id);
726
+ }
727
+ if (node.children && Array.isArray(node.children)) {
728
+ for (const child of node.children) {
729
+ traverseNode(child);
730
+ }
731
+ }
732
+ }
733
+ traverseNode(rootNode);
734
+ return results;
735
+ }
736
+ function extractRequirementsFromTree(rootNode, registry) {
737
+ const requiredComponents = /* @__PURE__ */ new Set();
738
+ const requiredCapabilities = /* @__PURE__ */ new Set();
739
+ function traverse(node) {
740
+ if (registry) {
741
+ const def = registry.get(node.type);
742
+ if (def?.category === "custom") {
743
+ requiredComponents.add(node.type);
744
+ }
745
+ if (def?.capabilities && Array.isArray(def.capabilities)) {
746
+ for (const cap of def.capabilities) {
747
+ requiredCapabilities.add(cap);
748
+ }
749
+ }
750
+ }
751
+ if (node.children && Array.isArray(node.children)) {
752
+ for (const child of node.children) {
753
+ traverse(child);
754
+ }
755
+ }
756
+ }
757
+ traverse(rootNode);
758
+ return {
759
+ requiredComponents: Array.from(requiredComponents).sort(),
760
+ requiredCapabilities: Array.from(requiredCapabilities).sort()
761
+ };
762
+ }
763
+ function remapAssetReferences(rootNode, idMap) {
764
+ let remappedCount = 0;
765
+ const getMappedId = (id) => {
766
+ if (idMap instanceof Map) return idMap.get(id);
767
+ return idMap[id];
768
+ };
769
+ function traverseProps(propsObj) {
770
+ if (!propsObj || typeof propsObj !== "object") return;
771
+ if (Array.isArray(propsObj)) {
772
+ for (const item of propsObj) {
773
+ traverseProps(item);
774
+ }
775
+ return;
776
+ }
777
+ const record = propsObj;
778
+ if (record.type === "asset" && typeof record.assetId === "string") {
779
+ const newId = getMappedId(record.assetId);
780
+ if (newId !== void 0 && newId !== record.assetId) {
781
+ record.assetId = newId;
782
+ remappedCount++;
783
+ }
784
+ }
785
+ for (const val of Object.values(record)) {
786
+ if (val && typeof val === "object") {
787
+ traverseProps(val);
788
+ }
789
+ }
790
+ }
791
+ function traverseNode(node) {
792
+ if (node.props) {
793
+ traverseProps(node.props);
794
+ }
795
+ if (node.children && Array.isArray(node.children)) {
796
+ for (const child of node.children) {
797
+ traverseNode(child);
798
+ }
799
+ }
800
+ }
801
+ traverseNode(rootNode);
802
+ return remappedCount;
803
+ }
804
+ function remapDocumentAssetReferences(doc, idMap) {
805
+ if (doc && doc.document) {
806
+ remapAssetReferences(doc.document, idMap);
807
+ }
808
+ return doc;
809
+ }
810
+ function findMissingComponentNodes(rootNode, registry, knownTypes) {
811
+ const missing = [];
812
+ const knownSet = /* @__PURE__ */ new Set();
813
+ if (knownTypes) {
814
+ for (const t of knownTypes) knownSet.add(t);
815
+ }
816
+ const standardBuiltIns = [
817
+ "page",
818
+ "section",
819
+ "container",
820
+ "columns",
821
+ "heading",
822
+ "text",
823
+ "image",
824
+ "button",
825
+ "collection"
826
+ ];
827
+ for (const b of standardBuiltIns) knownSet.add(b);
828
+ function checkNode(node) {
829
+ let isSupported = knownSet.has(node.type);
830
+ if (!isSupported && registry) {
831
+ isSupported = registry.has(node.type);
832
+ }
833
+ if (!isSupported) {
834
+ missing.push({
835
+ nodeId: node.id,
836
+ componentType: node.type,
837
+ props: node.props ? { ...node.props } : {},
838
+ node
839
+ });
840
+ }
841
+ if (node.children && Array.isArray(node.children)) {
842
+ for (const child of node.children) {
843
+ checkNode(child);
844
+ }
845
+ }
846
+ }
847
+ checkNode(rootNode);
848
+ return missing;
849
+ }
850
+
851
+ // src/commands.ts
852
+ import {
853
+ StyleDefinitionSchema,
854
+ ResponsiveStylesSchema
855
+ } from "@kubuild/schema";
856
+
857
+ // src/command-tree-utils.ts
858
+ import { collectNodeIds } from "@kubuild/schema";
859
+ function deepClone(val) {
860
+ return JSON.parse(JSON.stringify(val));
861
+ }
862
+ function findNodeLocation(root, targetId) {
863
+ if (root.id === targetId) {
864
+ return {
865
+ node: root,
866
+ parent: null,
867
+ index: -1
868
+ };
869
+ }
870
+ return searchNodeLocationRecursive(root, targetId);
871
+ }
872
+ function searchNodeLocationRecursive(currentParent, targetId) {
873
+ if (!currentParent.children || currentParent.children.length === 0) {
874
+ return null;
875
+ }
876
+ for (let i = 0; i < currentParent.children.length; i++) {
877
+ const child = currentParent.children[i];
878
+ if (child.id === targetId) {
879
+ return {
880
+ node: child,
881
+ parent: currentParent,
882
+ index: i
883
+ };
884
+ }
885
+ const foundInSubtree = searchNodeLocationRecursive(child, targetId);
886
+ if (foundInSubtree) {
887
+ return foundInSubtree;
888
+ }
889
+ }
890
+ return null;
891
+ }
892
+ function getNavigationTarget(root, nodeId, direction) {
893
+ const location = findNodeLocation(root, nodeId);
894
+ if (!location) return null;
895
+ switch (direction) {
896
+ case "parent":
897
+ return location.parent?.id ?? null;
898
+ case "child":
899
+ return location.node.children?.[0]?.id ?? null;
900
+ case "previous-sibling": {
901
+ if (!location.parent) return null;
902
+ return (location.parent.children ?? [])[location.index - 1]?.id ?? null;
903
+ }
904
+ case "next-sibling": {
905
+ if (!location.parent) return null;
906
+ return (location.parent.children ?? [])[location.index + 1]?.id ?? null;
907
+ }
908
+ }
909
+ }
910
+ function getAncestorChain(root, targetId) {
911
+ if (root.id === targetId) return [];
912
+ function walk(node, acc) {
913
+ if (!node.children) return null;
914
+ for (const child of node.children) {
915
+ if (child.id === targetId) return acc;
916
+ const found = walk(child, [...acc, child.id]);
917
+ if (found) return found;
918
+ }
919
+ return null;
920
+ }
921
+ return walk(root, [root.id]) ?? [];
922
+ }
923
+ function getParentNodeId(root, targetId) {
924
+ if (root.id === targetId) return null;
925
+ const stack = [root];
926
+ while (stack.length > 0) {
927
+ const current = stack.pop();
928
+ const children = current.children;
929
+ if (!children) continue;
930
+ for (const child of children) {
931
+ if (child.id === targetId) return current.id;
932
+ stack.push(child);
933
+ }
934
+ }
935
+ return null;
936
+ }
937
+ function getNodeAncestors(root, targetId) {
938
+ const path = [];
939
+ function walk(node) {
940
+ path.push(node);
941
+ if (node.id === targetId) return true;
942
+ if (node.children) {
943
+ for (const child of node.children) {
944
+ if (walk(child)) return true;
945
+ }
946
+ }
947
+ path.pop();
948
+ return false;
949
+ }
950
+ return walk(root) ? path : [];
951
+ }
952
+ function isDescendantOf(ancestorNode, targetId) {
953
+ if (ancestorNode.id === targetId) {
954
+ return true;
955
+ }
956
+ if (!ancestorNode.children || ancestorNode.children.length === 0) {
957
+ return false;
958
+ }
959
+ for (const child of ancestorNode.children) {
960
+ if (isDescendantOf(child, targetId)) {
961
+ return true;
962
+ }
963
+ }
964
+ return false;
965
+ }
966
+ function collectNodeIdSet(root) {
967
+ return new Set(collectNodeIds(root));
968
+ }
969
+ function defaultIdGenerator(oldId, existingIds) {
970
+ const baseName = oldId.replace(/_copy(_\d+)?$/, "");
971
+ let candidate = `${baseName}_copy`;
972
+ let counter = 1;
973
+ if (existingIds) {
974
+ while (existingIds.has(candidate)) {
975
+ candidate = `${baseName}_copy_${counter}`;
976
+ counter++;
977
+ }
978
+ existingIds.add(candidate);
979
+ }
980
+ return candidate;
981
+ }
982
+ function cloneTreeWithNewIds(root, idGenerator, existingIds) {
983
+ const idMap = /* @__PURE__ */ new Map();
984
+ const idGen = idGenerator || ((oldId) => defaultIdGenerator(oldId, existingIds));
985
+ function cloneRecursive(node) {
986
+ const newId = idGen(node.id);
987
+ idMap.set(node.id, newId);
988
+ const clonedProps = node.props ? deepClone(node.props) : void 0;
989
+ const clonedStyles = node.styles ? deepClone(node.styles) : void 0;
990
+ const clonedChildren = node.children ? node.children.map((child) => cloneRecursive(child)) : [];
991
+ return {
992
+ id: newId,
993
+ type: node.type,
994
+ ...clonedProps ? { props: clonedProps } : {},
995
+ ...clonedStyles ? { styles: clonedStyles } : {},
996
+ children: clonedChildren
997
+ };
998
+ }
999
+ const clonedNode = cloneRecursive(root);
1000
+ return { clonedNode, idMap };
1001
+ }
1002
+
1003
+ // src/commands.ts
1004
+ function insertNode(document, params) {
1005
+ const { parentId, node, index } = params;
1006
+ if (!node || typeof node !== "object" || !node.id || !node.type) {
1007
+ throw new Error('Invalid node: Node must be an object with non-empty "id" and "type"');
1008
+ }
1009
+ const newDoc = deepClone(document);
1010
+ const existingIds = collectNodeIdSet(newDoc.document);
1011
+ const incomingIds = collectNodeIdSet(node);
1012
+ for (const incId of incomingIds) {
1013
+ if (existingIds.has(incId)) {
1014
+ throw new Error(`Cannot insert node: Duplicate node ID "${incId}" already exists in document.`);
1015
+ }
1016
+ }
1017
+ const parentLoc = findNodeLocation(newDoc.document, parentId);
1018
+ if (!parentLoc) {
1019
+ throw new Error(`Cannot insert node: Target parent node with ID "${parentId}" not found.`);
1020
+ }
1021
+ const parentNode = parentLoc.node;
1022
+ if (!parentNode.children) {
1023
+ parentNode.children = [];
1024
+ }
1025
+ const insertIndex = typeof index === "number" && index >= 0 && index <= parentNode.children.length ? index : parentNode.children.length;
1026
+ parentNode.children.splice(insertIndex, 0, deepClone(node));
1027
+ return {
1028
+ document: newDoc,
1029
+ event: {
1030
+ type: "NODE_INSERTED",
1031
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1032
+ nodeId: node.id,
1033
+ parentId,
1034
+ index: insertIndex,
1035
+ payload: {
1036
+ nodeType: node.type
1037
+ }
1038
+ }
1039
+ };
1040
+ }
1041
+ function moveNode(document, params) {
1042
+ const { nodeId, targetParentId, index } = params;
1043
+ if (nodeId === document.document.id) {
1044
+ throw new Error("Cannot move the root page node.");
1045
+ }
1046
+ const newDoc = deepClone(document);
1047
+ const sourceLoc = findNodeLocation(newDoc.document, nodeId);
1048
+ if (!sourceLoc || !sourceLoc.parent) {
1049
+ throw new Error(`Cannot move node: Node with ID "${nodeId}" not found in document.`);
1050
+ }
1051
+ const targetLoc = findNodeLocation(newDoc.document, targetParentId);
1052
+ if (!targetLoc) {
1053
+ throw new Error(`Cannot move node: Target parent with ID "${targetParentId}" not found in document.`);
1054
+ }
1055
+ if (isDescendantOf(sourceLoc.node, targetParentId)) {
1056
+ throw new Error(
1057
+ `Cannot move node: Target parent "${targetParentId}" is the node itself or a descendant of node "${nodeId}".`
1058
+ );
1059
+ }
1060
+ const previousParentId = sourceLoc.parent.id;
1061
+ const previousIndex = sourceLoc.index;
1062
+ const [removedNode] = sourceLoc.parent.children.splice(previousIndex, 1);
1063
+ const targetParent = targetLoc.node;
1064
+ if (!targetParent.children) {
1065
+ targetParent.children = [];
1066
+ }
1067
+ let targetIndex = typeof index === "number" && index >= 0 && index <= targetParent.children.length ? index : targetParent.children.length;
1068
+ targetParent.children.splice(targetIndex, 0, removedNode);
1069
+ return {
1070
+ document: newDoc,
1071
+ event: {
1072
+ type: "NODE_MOVED",
1073
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1074
+ nodeId,
1075
+ parentId: targetParentId,
1076
+ previousParentId,
1077
+ index: targetIndex,
1078
+ previousIndex
1079
+ }
1080
+ };
1081
+ }
1082
+ function updateProps(document, params) {
1083
+ const { nodeId, props, merge = true } = params;
1084
+ if (!props || typeof props !== "object" || Array.isArray(props)) {
1085
+ throw new Error("Props must be an object.");
1086
+ }
1087
+ const newDoc = deepClone(document);
1088
+ const loc = findNodeLocation(newDoc.document, nodeId);
1089
+ if (!loc) {
1090
+ throw new Error(`Cannot update props: Node with ID "${nodeId}" not found in document.`);
1091
+ }
1092
+ const targetNode = loc.node;
1093
+ const previousProps = targetNode.props ? deepClone(targetNode.props) : {};
1094
+ if (merge) {
1095
+ targetNode.props = {
1096
+ ...previousProps,
1097
+ ...deepClone(props)
1098
+ };
1099
+ } else {
1100
+ targetNode.props = deepClone(props);
1101
+ }
1102
+ return {
1103
+ document: newDoc,
1104
+ event: {
1105
+ type: "PROPS_UPDATED",
1106
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1107
+ nodeId,
1108
+ parentId: loc.parent ? loc.parent.id : void 0,
1109
+ payload: {
1110
+ props: targetNode.props,
1111
+ previousProps
1112
+ }
1113
+ }
1114
+ };
1115
+ }
1116
+ function updateStyle(document, params) {
1117
+ const { nodeId, styles, breakpoint, state, merge = true } = params;
1118
+ if (!styles || typeof styles !== "object" || Array.isArray(styles)) {
1119
+ throw new Error("Styles must be an object.");
1120
+ }
1121
+ const newDoc = deepClone(document);
1122
+ const loc = findNodeLocation(newDoc.document, nodeId);
1123
+ if (!loc) {
1124
+ throw new Error(`Cannot update style: Node with ID "${nodeId}" not found in document.`);
1125
+ }
1126
+ const targetNode = loc.node;
1127
+ const currentStyles = targetNode.styles ? deepClone(targetNode.styles) : {};
1128
+ const previousStyles = deepClone(currentStyles);
1129
+ if (state) {
1130
+ const newBreakpointStyles = StyleDefinitionSchema.parse(styles);
1131
+ const states = { ...currentStyles.states ?? {} };
1132
+ const currentStateStyles = states[state] ?? {};
1133
+ if (merge) {
1134
+ states[state] = { ...currentStateStyles, ...deepClone(newBreakpointStyles) };
1135
+ } else {
1136
+ states[state] = deepClone(newBreakpointStyles);
1137
+ }
1138
+ targetNode.styles = { ...currentStyles, states };
1139
+ return {
1140
+ document: newDoc,
1141
+ event: {
1142
+ type: "STYLE_UPDATED",
1143
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1144
+ nodeId,
1145
+ parentId: loc.parent ? loc.parent.id : void 0,
1146
+ payload: {
1147
+ styles: targetNode.styles,
1148
+ previousStyles,
1149
+ state
1150
+ }
1151
+ }
1152
+ };
1153
+ }
1154
+ if (breakpoint) {
1155
+ const currentBreakpointStyles = currentStyles[breakpoint] || {};
1156
+ const newBreakpointStyles = StyleDefinitionSchema.parse(styles);
1157
+ if (merge) {
1158
+ currentStyles[breakpoint] = {
1159
+ ...currentBreakpointStyles,
1160
+ ...deepClone(newBreakpointStyles)
1161
+ };
1162
+ } else {
1163
+ currentStyles[breakpoint] = deepClone(newBreakpointStyles);
1164
+ }
1165
+ } else {
1166
+ const responsiveStylesInput = ResponsiveStylesSchema.parse(styles);
1167
+ if (merge) {
1168
+ for (const bp of ["base", "desktop", "tablet", "mobile"]) {
1169
+ if (responsiveStylesInput[bp] !== void 0) {
1170
+ const currentBp = currentStyles[bp] || {};
1171
+ const incomingBp = responsiveStylesInput[bp] || {};
1172
+ currentStyles[bp] = {
1173
+ ...currentBp,
1174
+ ...deepClone(incomingBp)
1175
+ };
1176
+ }
1177
+ }
1178
+ } else {
1179
+ targetNode.styles = deepClone(responsiveStylesInput);
1180
+ return {
1181
+ document: newDoc,
1182
+ event: {
1183
+ type: "STYLE_UPDATED",
1184
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1185
+ nodeId,
1186
+ parentId: loc.parent ? loc.parent.id : void 0,
1187
+ payload: {
1188
+ styles: targetNode.styles,
1189
+ previousStyles
1190
+ }
1191
+ }
1192
+ };
1193
+ }
1194
+ }
1195
+ targetNode.styles = currentStyles;
1196
+ return {
1197
+ document: newDoc,
1198
+ event: {
1199
+ type: "STYLE_UPDATED",
1200
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1201
+ nodeId,
1202
+ parentId: loc.parent ? loc.parent.id : void 0,
1203
+ payload: {
1204
+ styles: currentStyles,
1205
+ previousStyles,
1206
+ breakpoint
1207
+ }
1208
+ }
1209
+ };
1210
+ }
1211
+ function removeNode(document, params) {
1212
+ const { nodeId } = params;
1213
+ if (nodeId === document.document.id) {
1214
+ throw new Error("Cannot remove the root page node.");
1215
+ }
1216
+ const newDoc = deepClone(document);
1217
+ const loc = findNodeLocation(newDoc.document, nodeId);
1218
+ if (!loc || !loc.parent) {
1219
+ throw new Error(`Cannot remove node: Node with ID "${nodeId}" not found in document.`);
1220
+ }
1221
+ const parentId = loc.parent.id;
1222
+ const index = loc.index;
1223
+ const [removedNode] = loc.parent.children.splice(index, 1);
1224
+ return {
1225
+ document: newDoc,
1226
+ event: {
1227
+ type: "NODE_REMOVED",
1228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1229
+ nodeId,
1230
+ parentId,
1231
+ index,
1232
+ payload: {
1233
+ node: removedNode
1234
+ }
1235
+ }
1236
+ };
1237
+ }
1238
+ function duplicateNode(document, params) {
1239
+ const { nodeId, targetParentId, index, idGenerator } = params;
1240
+ if (nodeId === document.document.id) {
1241
+ throw new Error("Cannot duplicate the root page node.");
1242
+ }
1243
+ const newDoc = deepClone(document);
1244
+ const loc = findNodeLocation(newDoc.document, nodeId);
1245
+ if (!loc || !loc.parent) {
1246
+ throw new Error(`Cannot duplicate node: Node with ID "${nodeId}" not found in document.`);
1247
+ }
1248
+ const existingIds = collectNodeIdSet(newDoc.document);
1249
+ const { clonedNode, idMap } = cloneTreeWithNewIds(loc.node, idGenerator, existingIds);
1250
+ const destinationParentId = targetParentId || loc.parent.id;
1251
+ const destParentLoc = findNodeLocation(newDoc.document, destinationParentId);
1252
+ if (!destParentLoc) {
1253
+ throw new Error(`Cannot duplicate node: Destination parent "${destinationParentId}" not found.`);
1254
+ }
1255
+ const destParent = destParentLoc.node;
1256
+ if (!destParent.children) {
1257
+ destParent.children = [];
1258
+ }
1259
+ let insertIndex;
1260
+ if (typeof index === "number" && index >= 0 && index <= destParent.children.length) {
1261
+ insertIndex = index;
1262
+ } else if (destinationParentId === loc.parent.id) {
1263
+ insertIndex = loc.index + 1;
1264
+ } else {
1265
+ insertIndex = destParent.children.length;
1266
+ }
1267
+ destParent.children.splice(insertIndex, 0, clonedNode);
1268
+ return {
1269
+ document: newDoc,
1270
+ event: {
1271
+ type: "NODE_DUPLICATED",
1272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1273
+ nodeId: clonedNode.id,
1274
+ parentId: destinationParentId,
1275
+ index: insertIndex,
1276
+ payload: {
1277
+ originalNodeId: nodeId,
1278
+ idMap: Object.fromEntries(idMap.entries())
1279
+ }
1280
+ }
1281
+ };
1282
+ }
1283
+
1284
+ // src/history.ts
1285
+ var DEFAULT_MAX_HISTORY = 50;
1286
+ var HistoryEngine = class {
1287
+ past = [];
1288
+ current;
1289
+ future = [];
1290
+ maxHistory;
1291
+ constructor(initialState, options = {}) {
1292
+ if (initialState === void 0 || initialState === null) {
1293
+ throw new Error("Initial state must be defined.");
1294
+ }
1295
+ const limit = options.maxHistory ?? DEFAULT_MAX_HISTORY;
1296
+ if (typeof limit !== "number" || limit <= 0 || !Number.isFinite(limit)) {
1297
+ throw new Error("maxHistory option must be a positive integer.");
1298
+ }
1299
+ this.maxHistory = Math.floor(limit);
1300
+ this.current = {
1301
+ state: deepClone(initialState),
1302
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1303
+ };
1304
+ }
1305
+ /**
1306
+ * Get the current active state (returns an immutable clone).
1307
+ */
1308
+ get present() {
1309
+ return deepClone(this.current.state);
1310
+ }
1311
+ /**
1312
+ * Check if undo is available.
1313
+ */
1314
+ canUndo() {
1315
+ return this.past.length > 0;
1316
+ }
1317
+ /**
1318
+ * Check if redo is available.
1319
+ */
1320
+ canRedo() {
1321
+ return this.future.length > 0;
1322
+ }
1323
+ /**
1324
+ * Get total number of states in undo stack.
1325
+ */
1326
+ get undoCount() {
1327
+ return this.past.length;
1328
+ }
1329
+ /**
1330
+ * Get total number of states in redo stack.
1331
+ */
1332
+ get redoCount() {
1333
+ return this.future.length;
1334
+ }
1335
+ /**
1336
+ * Get max history limit.
1337
+ */
1338
+ get limit() {
1339
+ return this.maxHistory;
1340
+ }
1341
+ /**
1342
+ * Push a new state.
1343
+ * Clears the redo stack and shifts oldest past entries if maxHistory is exceeded.
1344
+ */
1345
+ push(nextState, event) {
1346
+ if (nextState === void 0 || nextState === null) {
1347
+ throw new Error("Cannot push undefined or null state into history.");
1348
+ }
1349
+ this.past.push(this.current);
1350
+ while (this.past.length > this.maxHistory) {
1351
+ this.past.shift();
1352
+ }
1353
+ this.current = {
1354
+ state: deepClone(nextState),
1355
+ event,
1356
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1357
+ };
1358
+ this.future = [];
1359
+ }
1360
+ /**
1361
+ * Undo to the previous state.
1362
+ * Returns the restored state, or undefined if undo is not possible.
1363
+ */
1364
+ undo() {
1365
+ if (!this.canUndo()) {
1366
+ return void 0;
1367
+ }
1368
+ const previousEntry = this.past.pop();
1369
+ this.future.unshift(this.current);
1370
+ this.current = previousEntry;
1371
+ return this.present;
1372
+ }
1373
+ /**
1374
+ * Redo to the next state in future stack.
1375
+ * Returns the restored state, or undefined if redo is not possible.
1376
+ */
1377
+ redo() {
1378
+ if (!this.canRedo()) {
1379
+ return void 0;
1380
+ }
1381
+ const nextEntry = this.future.shift();
1382
+ this.past.push(this.current);
1383
+ this.current = nextEntry;
1384
+ return this.present;
1385
+ }
1386
+ /**
1387
+ * Reset the history engine with a new initial state.
1388
+ * Clears both undo and redo stacks (e.g. when loading a new document).
1389
+ */
1390
+ reset(newState) {
1391
+ if (newState === void 0 || newState === null) {
1392
+ throw new Error("New state must be defined when resetting history.");
1393
+ }
1394
+ this.past = [];
1395
+ this.future = [];
1396
+ this.current = {
1397
+ state: deepClone(newState),
1398
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1399
+ };
1400
+ }
1401
+ /**
1402
+ * Clear both past and future stacks without changing the current state.
1403
+ */
1404
+ clear() {
1405
+ this.past = [];
1406
+ this.future = [];
1407
+ }
1408
+ /**
1409
+ * Get a snapshot of current history status.
1410
+ */
1411
+ getState() {
1412
+ return {
1413
+ canUndo: this.canUndo(),
1414
+ canRedo: this.canRedo(),
1415
+ undoCount: this.undoCount,
1416
+ redoCount: this.redoCount,
1417
+ present: this.present
1418
+ };
1419
+ }
1420
+ };
1421
+ var DocumentHistoryManager = class {
1422
+ history;
1423
+ constructor(initialDocument, options) {
1424
+ this.history = new HistoryEngine(initialDocument, options);
1425
+ }
1426
+ get document() {
1427
+ return this.history.present;
1428
+ }
1429
+ get canUndo() {
1430
+ return this.history.canUndo();
1431
+ }
1432
+ get canRedo() {
1433
+ return this.history.canRedo();
1434
+ }
1435
+ /**
1436
+ * Execute a mutation command immutably and record the resulting state and change event in history.
1437
+ */
1438
+ execute(executor) {
1439
+ const currentDoc = this.history.present;
1440
+ const result = executor(currentDoc);
1441
+ this.history.push(result.document, result.event);
1442
+ return result;
1443
+ }
1444
+ undo() {
1445
+ return this.history.undo();
1446
+ }
1447
+ redo() {
1448
+ return this.history.redo();
1449
+ }
1450
+ reset(newDocument) {
1451
+ this.history.reset(newDocument);
1452
+ }
1453
+ clear() {
1454
+ this.history.clear();
1455
+ }
1456
+ getState() {
1457
+ return this.history.getState();
1458
+ }
1459
+ };
1460
+
1461
+ // src/migration.ts
1462
+ import {
1463
+ CURRENT_SCHEMA_VERSION,
1464
+ SCHEMA_NAME as SCHEMA_NAME3
1465
+ } from "@kubuild/schema";
1466
+ var MigrationRegistry = class {
1467
+ steps = /* @__PURE__ */ new Map();
1468
+ /**
1469
+ * Register a migration step from one version to another.
1470
+ */
1471
+ register(step) {
1472
+ const key = this.getKey(step.fromVersion, step.toVersion);
1473
+ this.steps.set(key, step);
1474
+ }
1475
+ getKey(fromVersion, toVersion) {
1476
+ return `${fromVersion}->${toVersion}`;
1477
+ }
1478
+ /**
1479
+ * Find shortest path of versions to migrate from source to target using BFS.
1480
+ */
1481
+ findPath(sourceVersion, targetVersion) {
1482
+ if (sourceVersion === targetVersion) {
1483
+ return [sourceVersion];
1484
+ }
1485
+ const queue = [
1486
+ { version: sourceVersion, path: [sourceVersion] }
1487
+ ];
1488
+ const visited = /* @__PURE__ */ new Set([sourceVersion]);
1489
+ while (queue.length > 0) {
1490
+ const { version: currentVersion, path } = queue.shift();
1491
+ for (const step of this.steps.values()) {
1492
+ if (step.fromVersion === currentVersion) {
1493
+ const nextVersion = step.toVersion;
1494
+ if (nextVersion === targetVersion) {
1495
+ return [...path, nextVersion];
1496
+ }
1497
+ if (!visited.has(nextVersion)) {
1498
+ visited.add(nextVersion);
1499
+ queue.push({ version: nextVersion, path: [...path, nextVersion] });
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ return null;
1505
+ }
1506
+ /**
1507
+ * Check if a valid migration path exists between source and target versions.
1508
+ */
1509
+ hasPath(sourceVersion, targetVersion) {
1510
+ return this.findPath(sourceVersion, targetVersion) !== null;
1511
+ }
1512
+ /**
1513
+ * Get the migration step handler for a direct version step.
1514
+ */
1515
+ getStep(fromVersion, toVersion) {
1516
+ return this.steps.get(this.getKey(fromVersion, toVersion));
1517
+ }
1518
+ };
1519
+ function convertNodeStylesToResponsive(node) {
1520
+ if (node.styles && typeof node.styles === "object" && !Array.isArray(node.styles)) {
1521
+ const styles = node.styles;
1522
+ const hasBreakpointKeys = "base" in styles || "desktop" in styles || "tablet" in styles || "mobile" in styles;
1523
+ if (!hasBreakpointKeys) {
1524
+ node.styles = {
1525
+ base: { ...styles }
1526
+ };
1527
+ }
1528
+ }
1529
+ if (Array.isArray(node.children)) {
1530
+ for (const child of node.children) {
1531
+ if (child && typeof child === "object") {
1532
+ convertNodeStylesToResponsive(child);
1533
+ }
1534
+ }
1535
+ }
1536
+ }
1537
+ var defaultMigrationRegistry = new MigrationRegistry();
1538
+ defaultMigrationRegistry.register({
1539
+ fromVersion: "0.1.0",
1540
+ toVersion: "1.0.0",
1541
+ description: "Migrate alpha 0.1.0 schema (root node key, flat styles) to v1.0.0",
1542
+ migrate: (rawDoc) => {
1543
+ const migrated = deepClone(rawDoc);
1544
+ migrated.schema = SCHEMA_NAME3;
1545
+ migrated.version = "1.0.0";
1546
+ if (migrated.root && !migrated.document) {
1547
+ migrated.document = migrated.root;
1548
+ delete migrated.root;
1549
+ }
1550
+ if (!migrated.metadata || typeof migrated.metadata !== "object") {
1551
+ migrated.metadata = {
1552
+ title: "Migrated Page",
1553
+ description: "",
1554
+ author: "",
1555
+ tags: [],
1556
+ category: "general",
1557
+ version: "1.0.0",
1558
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1559
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1560
+ };
1561
+ } else {
1562
+ const meta = migrated.metadata;
1563
+ meta.title = meta.title || "Migrated Page";
1564
+ meta.tags = Array.isArray(meta.tags) ? meta.tags : [];
1565
+ meta.version = "1.0.0";
1566
+ }
1567
+ if (migrated.document && typeof migrated.document === "object") {
1568
+ convertNodeStylesToResponsive(migrated.document);
1569
+ }
1570
+ return migrated;
1571
+ }
1572
+ });
1573
+ defaultMigrationRegistry.register({
1574
+ fromVersion: "0.2.0",
1575
+ toVersion: "1.0.0",
1576
+ description: "Migrate 0.2.0 schema to v1.0.0",
1577
+ migrate: (rawDoc) => {
1578
+ const migrated = deepClone(rawDoc);
1579
+ migrated.schema = SCHEMA_NAME3;
1580
+ migrated.version = "1.0.0";
1581
+ if (migrated.document && typeof migrated.document === "object") {
1582
+ convertNodeStylesToResponsive(migrated.document);
1583
+ }
1584
+ return migrated;
1585
+ }
1586
+ });
1587
+ defaultMigrationRegistry.register({
1588
+ fromVersion: "0.9.0",
1589
+ toVersion: "1.0.0",
1590
+ description: "Migrate 0.9.0 beta schema to v1.0.0",
1591
+ migrate: (rawDoc) => {
1592
+ const migrated = deepClone(rawDoc);
1593
+ migrated.schema = SCHEMA_NAME3;
1594
+ migrated.version = "1.0.0";
1595
+ if (!migrated.metadata || typeof migrated.metadata !== "object") {
1596
+ migrated.metadata = {
1597
+ title: "Migrated Page",
1598
+ description: "",
1599
+ author: "",
1600
+ tags: [],
1601
+ category: "general",
1602
+ version: "1.0.0"
1603
+ };
1604
+ }
1605
+ return migrated;
1606
+ }
1607
+ });
1608
+ defaultMigrationRegistry.register({
1609
+ fromVersion: "1.0",
1610
+ toVersion: "1.0.0",
1611
+ description: "Normalize 1.0 version string to 1.0.0",
1612
+ migrate: (rawDoc) => {
1613
+ const migrated = deepClone(rawDoc);
1614
+ migrated.version = "1.0.0";
1615
+ return migrated;
1616
+ }
1617
+ });
1618
+ function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
1619
+ if (!sourceVersion || !targetVersion) return false;
1620
+ if (sourceVersion === targetVersion) return true;
1621
+ return registry.hasPath(sourceVersion, targetVersion);
1622
+ }
1623
+ function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
1624
+ if (!sourceVersion || !targetVersion) return null;
1625
+ return registry.findPath(sourceVersion, targetVersion);
1626
+ }
1627
+ function migrateDocument(rawDocument, options = {}) {
1628
+ const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION;
1629
+ const dryRun = options.dryRun ?? false;
1630
+ const registry = options.registry ?? defaultMigrationRegistry;
1631
+ const validate = options.validate ?? true;
1632
+ if (!rawDocument || typeof rawDocument !== "object" || Array.isArray(rawDocument)) {
1633
+ const error = {
1634
+ code: "INVALID_SOURCE_DOCUMENT",
1635
+ message: "Source document must be a non-null object.",
1636
+ sourceVersion: "unknown",
1637
+ targetVersion
1638
+ };
1639
+ return {
1640
+ success: false,
1641
+ diagnostic: {
1642
+ success: false,
1643
+ sourceVersion: "unknown",
1644
+ targetVersion,
1645
+ migrationPath: [],
1646
+ stepsApplied: 0,
1647
+ dryRun,
1648
+ errors: [error]
1649
+ }
1650
+ };
1651
+ }
1652
+ const doc = rawDocument;
1653
+ const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
1654
+ if (sourceVersion === targetVersion) {
1655
+ const cloned = deepClone(doc);
1656
+ if (validate) {
1657
+ const validation = validateDocument(cloned);
1658
+ if (!validation.valid) {
1659
+ return {
1660
+ success: false,
1661
+ diagnostic: {
1662
+ success: false,
1663
+ sourceVersion,
1664
+ targetVersion,
1665
+ migrationPath: [sourceVersion],
1666
+ stepsApplied: 0,
1667
+ dryRun,
1668
+ errors: validation.errors.map((e) => ({
1669
+ code: "INVALID_SOURCE_DOCUMENT",
1670
+ message: `Validation error: ${e.message} at ${e.path}`,
1671
+ sourceVersion,
1672
+ targetVersion
1673
+ }))
1674
+ }
1675
+ };
1676
+ }
1677
+ }
1678
+ return {
1679
+ success: true,
1680
+ document: cloned,
1681
+ diagnostic: {
1682
+ success: true,
1683
+ sourceVersion,
1684
+ targetVersion,
1685
+ migrationPath: [sourceVersion],
1686
+ stepsApplied: 0,
1687
+ dryRun
1688
+ }
1689
+ };
1690
+ }
1691
+ const path = registry.findPath(sourceVersion, targetVersion);
1692
+ if (!path || path.length <= 1) {
1693
+ const error = {
1694
+ code: "NO_MIGRATION_PATH",
1695
+ message: `No migration path found from schema version "${sourceVersion}" to "${targetVersion}".`,
1696
+ sourceVersion,
1697
+ targetVersion
1698
+ };
1699
+ return {
1700
+ success: false,
1701
+ diagnostic: {
1702
+ success: false,
1703
+ sourceVersion,
1704
+ targetVersion,
1705
+ migrationPath: [],
1706
+ stepsApplied: 0,
1707
+ dryRun,
1708
+ errors: [error]
1709
+ }
1710
+ };
1711
+ }
1712
+ if (dryRun) {
1713
+ return {
1714
+ success: true,
1715
+ diagnostic: {
1716
+ success: true,
1717
+ sourceVersion,
1718
+ targetVersion,
1719
+ migrationPath: path,
1720
+ stepsApplied: path.length - 1,
1721
+ dryRun: true
1722
+ }
1723
+ };
1724
+ }
1725
+ let currentDoc = deepClone(doc);
1726
+ let stepsApplied = 0;
1727
+ for (let i = 0; i < path.length - 1; i++) {
1728
+ const fromVer = path[i];
1729
+ const toVer = path[i + 1];
1730
+ const step = registry.getStep(fromVer, toVer);
1731
+ if (!step) {
1732
+ const error = {
1733
+ code: "MIGRATION_EXECUTION_FAILED",
1734
+ message: `Registered migration step for "${fromVer}" -> "${toVer}" is missing executor.`,
1735
+ sourceVersion,
1736
+ targetVersion
1737
+ };
1738
+ return {
1739
+ success: false,
1740
+ diagnostic: {
1741
+ success: false,
1742
+ sourceVersion,
1743
+ targetVersion,
1744
+ migrationPath: path,
1745
+ stepsApplied,
1746
+ dryRun,
1747
+ errors: [error]
1748
+ }
1749
+ };
1750
+ }
1751
+ try {
1752
+ currentDoc = step.migrate(currentDoc);
1753
+ stepsApplied++;
1754
+ } catch (err) {
1755
+ const error = {
1756
+ code: "MIGRATION_EXECUTION_FAILED",
1757
+ message: `Failed executing migration step from "${fromVer}" to "${toVer}": ${err instanceof Error ? err.message : String(err)}`,
1758
+ sourceVersion,
1759
+ targetVersion,
1760
+ details: { stepFrom: fromVer, stepTo: toVer }
1761
+ };
1762
+ return {
1763
+ success: false,
1764
+ diagnostic: {
1765
+ success: false,
1766
+ sourceVersion,
1767
+ targetVersion,
1768
+ migrationPath: path,
1769
+ stepsApplied,
1770
+ dryRun,
1771
+ errors: [error]
1772
+ }
1773
+ };
1774
+ }
1775
+ }
1776
+ const finalDoc = currentDoc;
1777
+ if (validate) {
1778
+ const validation = validateDocument(finalDoc);
1779
+ if (!validation.valid) {
1780
+ return {
1781
+ success: false,
1782
+ diagnostic: {
1783
+ success: false,
1784
+ sourceVersion,
1785
+ targetVersion,
1786
+ migrationPath: path,
1787
+ stepsApplied,
1788
+ dryRun,
1789
+ errors: validation.errors.map((e) => ({
1790
+ code: "MIGRATION_EXECUTION_FAILED",
1791
+ message: `Post-migration validation error: ${e.message} at ${e.path}`,
1792
+ sourceVersion,
1793
+ targetVersion
1794
+ }))
1795
+ }
1796
+ };
1797
+ }
1798
+ }
1799
+ return {
1800
+ success: true,
1801
+ document: finalDoc,
1802
+ diagnostic: {
1803
+ success: true,
1804
+ sourceVersion,
1805
+ targetVersion,
1806
+ migrationPath: path,
1807
+ stepsApplied,
1808
+ dryRun: false
1809
+ }
1810
+ };
1811
+ }
1812
+
1813
+ // src/exporter.ts
1814
+ import { zipSync, strToU8 } from "fflate";
1815
+ import {
1816
+ SCHEMA_NAME as SCHEMA_NAME4,
1817
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2
1818
+ } from "@kubuild/schema";
1819
+ function sha256Sync(data) {
1820
+ const K = [
1821
+ 1116352408,
1822
+ 1899447441,
1823
+ 3049323471,
1824
+ 3921009573,
1825
+ 961987163,
1826
+ 1508970993,
1827
+ 2453635748,
1828
+ 2870763221,
1829
+ 3624381080,
1830
+ 310598401,
1831
+ 607225278,
1832
+ 1426881987,
1833
+ 1925078388,
1834
+ 2162078206,
1835
+ 2614888103,
1836
+ 3248222580,
1837
+ 3835390401,
1838
+ 4022224774,
1839
+ 264347078,
1840
+ 604807628,
1841
+ 770255983,
1842
+ 1249150122,
1843
+ 1555081692,
1844
+ 1996064986,
1845
+ 2554220882,
1846
+ 2821834349,
1847
+ 2952996808,
1848
+ 3210313671,
1849
+ 3336571891,
1850
+ 3584528711,
1851
+ 113926993,
1852
+ 338241895,
1853
+ 666307205,
1854
+ 773529912,
1855
+ 1294757372,
1856
+ 1396182291,
1857
+ 1695183700,
1858
+ 1986661051,
1859
+ 2177026350,
1860
+ 2456956037,
1861
+ 2730485921,
1862
+ 2820302411,
1863
+ 3259730800,
1864
+ 3345764771,
1865
+ 3516065817,
1866
+ 3600352804,
1867
+ 4094571909,
1868
+ 275423344,
1869
+ 430227734,
1870
+ 506948616,
1871
+ 659060556,
1872
+ 883997877,
1873
+ 958139571,
1874
+ 1322822218,
1875
+ 1537002063,
1876
+ 1747873779,
1877
+ 1955562222,
1878
+ 2024104815,
1879
+ 2227730452,
1880
+ 2361852424,
1881
+ 2428436474,
1882
+ 2756734187,
1883
+ 3204031479,
1884
+ 3329325298
1885
+ ];
1886
+ let h0 = 1779033703;
1887
+ let h1 = 3144134277;
1888
+ let h2 = 1013904242;
1889
+ let h3 = 2773480762;
1890
+ let h4 = 1359893119;
1891
+ let h5 = 2600822924;
1892
+ let h6 = 528734635;
1893
+ let h7 = 1541459225;
1894
+ const length = data.length;
1895
+ const bitLength = length * 8;
1896
+ const newLength = length + 9 + 63 >>> 6 << 6;
1897
+ const padded = new Uint8Array(newLength);
1898
+ padded.set(data);
1899
+ padded[length] = 128;
1900
+ const view = new DataView(padded.buffer);
1901
+ view.setUint32(newLength - 4, bitLength >>> 0, false);
1902
+ view.setUint32(newLength - 8, Math.floor(bitLength / 4294967296), false);
1903
+ const w = new Uint32Array(64);
1904
+ for (let chunk = 0; chunk < newLength; chunk += 64) {
1905
+ for (let i = 0; i < 16; i++) {
1906
+ w[i] = view.getUint32(chunk + i * 4, false);
1907
+ }
1908
+ for (let i = 16; i < 64; i++) {
1909
+ const s0 = (w[i - 15] >>> 7 | w[i - 15] << 25) ^ (w[i - 15] >>> 18 | w[i - 15] << 14) ^ w[i - 15] >>> 3;
1910
+ const s1 = (w[i - 2] >>> 17 | w[i - 2] << 15) ^ (w[i - 2] >>> 19 | w[i - 2] << 13) ^ w[i - 2] >>> 10;
1911
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
1912
+ }
1913
+ let a = h0;
1914
+ let b = h1;
1915
+ let c = h2;
1916
+ let d = h3;
1917
+ let e = h4;
1918
+ let f = h5;
1919
+ let g = h6;
1920
+ let h = h7;
1921
+ for (let i = 0; i < 64; i++) {
1922
+ const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
1923
+ const ch = e & f ^ ~e & g;
1924
+ const temp1 = h + S1 + ch + K[i] + w[i] >>> 0;
1925
+ const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
1926
+ const maj = a & b ^ a & c ^ b & c;
1927
+ const temp2 = S0 + maj >>> 0;
1928
+ h = g;
1929
+ g = f;
1930
+ f = e;
1931
+ e = d + temp1 >>> 0;
1932
+ d = c;
1933
+ c = b;
1934
+ b = a;
1935
+ a = temp1 + temp2 >>> 0;
1936
+ }
1937
+ h0 = h0 + a >>> 0;
1938
+ h1 = h1 + b >>> 0;
1939
+ h2 = h2 + c >>> 0;
1940
+ h3 = h3 + d >>> 0;
1941
+ h4 = h4 + e >>> 0;
1942
+ h5 = h5 + f >>> 0;
1943
+ h6 = h6 + g >>> 0;
1944
+ h7 = h7 + h >>> 0;
1945
+ }
1946
+ const result = [h0, h1, h2, h3, h4, h5, h6, h7].map((val) => val.toString(16).padStart(8, "0")).join("");
1947
+ return result;
1948
+ }
1949
+ async function calculateChecksum(data) {
1950
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle?.digest) {
1951
+ try {
1952
+ const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
1953
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", buffer);
1954
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1955
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1956
+ return `sha256:${hashHex}`;
1957
+ } catch {
1958
+ }
1959
+ }
1960
+ return `sha256:${sha256Sync(data)}`;
1961
+ }
1962
+ function sanitizeFilename(filename, assetId, mimeType) {
1963
+ let name = (filename || "").trim();
1964
+ name = name.replace(/^[./\\]+/, "").replace(/[/\\]+/g, "_").replace(/\0/g, "");
1965
+ name = name.replace(/[^a-zA-Z0-9._-]/g, "_");
1966
+ if (!name || name === "." || name === "..") {
1967
+ const ext = getExtensionFromMime(mimeType) || "bin";
1968
+ const cleanId = assetId.replace(/[^a-zA-Z0-9_-]/g, "_");
1969
+ name = `${cleanId}.${ext}`;
1970
+ }
1971
+ return name;
1972
+ }
1973
+ function getExtensionFromMime(mimeType) {
1974
+ if (!mimeType) return null;
1975
+ const m = mimeType.toLowerCase();
1976
+ if (m.includes("png")) return "png";
1977
+ if (m.includes("jpeg") || m.includes("jpg")) return "jpg";
1978
+ if (m.includes("webp")) return "webp";
1979
+ if (m.includes("svg")) return "svg";
1980
+ if (m.includes("gif")) return "gif";
1981
+ if (m.includes("json")) return "json";
1982
+ if (m.includes("pdf")) return "pdf";
1983
+ if (m.includes("mp4")) return "mp4";
1984
+ if (m.includes("mp3")) return "mp3";
1985
+ return null;
1986
+ }
1987
+ async function convertToUint8Array(input) {
1988
+ if (!input) return null;
1989
+ if (input instanceof Uint8Array) {
1990
+ return input;
1991
+ }
1992
+ if (input instanceof ArrayBuffer) {
1993
+ return new Uint8Array(input);
1994
+ }
1995
+ if (typeof Blob !== "undefined" && input instanceof Blob) {
1996
+ const arrayBuffer = await input.arrayBuffer();
1997
+ return new Uint8Array(arrayBuffer);
1998
+ }
1999
+ if (typeof input === "string") {
2000
+ if (input.startsWith("data:")) {
2001
+ const commaIdx = input.indexOf(",");
2002
+ if (commaIdx !== -1) {
2003
+ const meta = input.substring(0, commaIdx);
2004
+ const rawData = input.substring(commaIdx + 1);
2005
+ if (meta.includes(";base64")) {
2006
+ if (typeof atob === "function") {
2007
+ const binary = atob(rawData);
2008
+ const bytes = new Uint8Array(binary.length);
2009
+ for (let i = 0; i < binary.length; i++) {
2010
+ bytes[i] = binary.charCodeAt(i);
2011
+ }
2012
+ return bytes;
2013
+ }
2014
+ }
2015
+ }
2016
+ }
2017
+ return new TextEncoder().encode(input);
2018
+ }
2019
+ return null;
2020
+ }
2021
+ async function exportPackage(document, options = {}) {
2022
+ const validation = validateDocument(document, {
2023
+ componentRegistry: options.componentRegistry,
2024
+ knownComponentTypes: options.knownComponentTypes,
2025
+ ...options.validationOptions
2026
+ });
2027
+ if (!validation.valid || !validation.data) {
2028
+ const diagnosticMessage = `Document validation failed with ${validation.errors.length} error(s):
2029
+ ` + validation.errors.map((e) => `[${e.code}] ${e.path}: ${e.message}`).join("\n");
2030
+ return {
2031
+ success: false,
2032
+ errors: validation.errors,
2033
+ diagnosticMessage
2034
+ };
2035
+ }
2036
+ const pageDoc = JSON.parse(JSON.stringify(validation.data));
2037
+ if (options.metadata) {
2038
+ pageDoc.metadata = {
2039
+ ...pageDoc.metadata || {
2040
+ title: "Untitled Page",
2041
+ description: "",
2042
+ author: "",
2043
+ tags: [],
2044
+ category: "general",
2045
+ version: "1.0.0"
2046
+ },
2047
+ ...options.metadata,
2048
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2049
+ };
2050
+ }
2051
+ const collectedAssets = collectAssetReferences(pageDoc.document);
2052
+ const assetRefsById = /* @__PURE__ */ new Map();
2053
+ for (const item of collectedAssets) {
2054
+ if (!assetRefsById.has(item.assetId)) {
2055
+ assetRefsById.set(item.assetId, item);
2056
+ }
2057
+ }
2058
+ const requirements = extractRequirementsFromTree(pageDoc.document, options.componentRegistry);
2059
+ const manifestAssets = [];
2060
+ const archiveFiles = {};
2061
+ const assetCollectionErrors = [];
2062
+ const usedArchivePaths = /* @__PURE__ */ new Set();
2063
+ for (const [assetId, assetRef] of assetRefsById.entries()) {
2064
+ let rawAsset;
2065
+ let customMime = assetRef.reference.mimeType;
2066
+ let customFilename = assetRef.reference.filename;
2067
+ if (options.assets) {
2068
+ if (options.assets instanceof Map) {
2069
+ rawAsset = options.assets.get(assetId);
2070
+ } else if (typeof options.assets === "object" && assetId in options.assets) {
2071
+ rawAsset = options.assets[assetId];
2072
+ }
2073
+ }
2074
+ if (!rawAsset && options.getAssetBytes) {
2075
+ try {
2076
+ rawAsset = await options.getAssetBytes(assetId, assetRef);
2077
+ } catch (err) {
2078
+ assetCollectionErrors.push({
2079
+ code: "INVALID_ASSET_REFERENCE",
2080
+ message: `Failed to retrieve asset bytes for "${assetId}": ${err instanceof Error ? err.message : String(err)}`,
2081
+ path: assetRef.propPath,
2082
+ nodeId: assetRef.nodeId
2083
+ });
2084
+ continue;
2085
+ }
2086
+ }
2087
+ if (!rawAsset && options.assetProvider?.resolve) {
2088
+ try {
2089
+ const resolved = await options.assetProvider.resolve(assetId);
2090
+ if (resolved) {
2091
+ rawAsset = resolved;
2092
+ }
2093
+ } catch {
2094
+ }
2095
+ }
2096
+ let dataToConvert = rawAsset;
2097
+ if (rawAsset && typeof rawAsset === "object" && "data" in rawAsset) {
2098
+ const assetObj = rawAsset;
2099
+ dataToConvert = assetObj.data;
2100
+ if (assetObj.mimeType) customMime = assetObj.mimeType;
2101
+ if (assetObj.filename) customFilename = assetObj.filename;
2102
+ }
2103
+ const assetBytes = await convertToUint8Array(dataToConvert);
2104
+ if (assetBytes) {
2105
+ const sanitizedName = sanitizeFilename(customFilename, assetId, customMime);
2106
+ let archivePath = `assets/${sanitizedName}`;
2107
+ let counter = 1;
2108
+ while (usedArchivePaths.has(archivePath)) {
2109
+ const extDot = sanitizedName.lastIndexOf(".");
2110
+ if (extDot !== -1) {
2111
+ archivePath = `assets/${sanitizedName.substring(0, extDot)}_${counter}${sanitizedName.substring(extDot)}`;
2112
+ } else {
2113
+ archivePath = `assets/${sanitizedName}_${counter}`;
2114
+ }
2115
+ counter++;
2116
+ }
2117
+ usedArchivePaths.add(archivePath);
2118
+ const mimeType = customMime || "application/octet-stream";
2119
+ const checksum = await calculateChecksum(assetBytes);
2120
+ manifestAssets.push({
2121
+ id: assetId,
2122
+ path: archivePath,
2123
+ mimeType,
2124
+ size: assetBytes.byteLength,
2125
+ checksum
2126
+ });
2127
+ archiveFiles[archivePath] = assetBytes;
2128
+ } else {
2129
+ const isExternal = Boolean(assetRef.reference.fallbackUrl || options.allowExternalFallback);
2130
+ if (!isExternal) {
2131
+ assetCollectionErrors.push({
2132
+ code: "INVALID_ASSET_REFERENCE",
2133
+ message: `Required asset "${assetId}" could not be collected for export and has no external fallback`,
2134
+ path: assetRef.propPath,
2135
+ nodeId: assetRef.nodeId
2136
+ });
2137
+ }
2138
+ }
2139
+ }
2140
+ if (assetCollectionErrors.length > 0) {
2141
+ const diagnosticMessage = `Asset collection failed with ${assetCollectionErrors.length} error(s):
2142
+ ` + assetCollectionErrors.map((e) => `[${e.code}] ${e.path}: ${e.message}`).join("\n");
2143
+ return {
2144
+ success: false,
2145
+ errors: assetCollectionErrors,
2146
+ diagnosticMessage
2147
+ };
2148
+ }
2149
+ const metadata = pageDoc.metadata || {
2150
+ title: "Untitled Page",
2151
+ description: "",
2152
+ author: "",
2153
+ tags: [],
2154
+ category: "general",
2155
+ version: "1.0.0",
2156
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2157
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2158
+ };
2159
+ const manifest = {
2160
+ schema: SCHEMA_NAME4,
2161
+ schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION2,
2162
+ packageVersion: options.packageVersion || "1.0.0",
2163
+ builderCompatibility: options.builderCompatibility || ">=0.1.0",
2164
+ requiredComponents: requirements.requiredComponents,
2165
+ requiredCapabilities: requirements.requiredCapabilities,
2166
+ assets: manifestAssets,
2167
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2168
+ };
2169
+ archiveFiles["manifest.json"] = strToU8(JSON.stringify(manifest, null, 2));
2170
+ archiveFiles["page.json"] = strToU8(JSON.stringify(pageDoc, null, 2));
2171
+ archiveFiles["metadata.json"] = strToU8(JSON.stringify(metadata, null, 2));
2172
+ const archive = zipSync(archiveFiles, { level: 6 });
2173
+ return {
2174
+ success: true,
2175
+ archive,
2176
+ manifest,
2177
+ page: pageDoc,
2178
+ metadata,
2179
+ assetCount: manifestAssets.length
2180
+ };
2181
+ }
2182
+ var exportStoraPackage = exportPackage;
2183
+
2184
+ // src/importer.ts
2185
+ import { unzipSync, strFromU8 } from "fflate";
2186
+ import {
2187
+ ManifestSchema,
2188
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
2189
+ } from "@kubuild/schema";
2190
+ var DEFAULT_SECURITY_LIMITS = {
2191
+ maxArchiveSize: 50 * 1024 * 1024,
2192
+ // 50 MB
2193
+ maxUncompressedSize: 100 * 1024 * 1024,
2194
+ // 100 MB
2195
+ maxFileCount: 1e3,
2196
+ maxAssetSize: 25 * 1024 * 1024
2197
+ // 25 MB
2198
+ };
2199
+ function isDangerousPath(filepath) {
2200
+ if (!filepath || typeof filepath !== "string") return true;
2201
+ if (filepath.includes("\0")) return true;
2202
+ if (filepath.startsWith("/") || filepath.startsWith("\\") || /^[a-zA-Z]:/.test(filepath)) {
2203
+ return true;
2204
+ }
2205
+ const normalized = filepath.replace(/\\/g, "/");
2206
+ const segments = normalized.split("/");
2207
+ for (const seg of segments) {
2208
+ if (seg === ".." || seg.trim() === "..") {
2209
+ return true;
2210
+ }
2211
+ }
2212
+ return false;
2213
+ }
2214
+ async function preflightPackage(archiveData, options = {}) {
2215
+ const limits = {
2216
+ ...DEFAULT_SECURITY_LIMITS,
2217
+ ...options.securityLimits
2218
+ };
2219
+ const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION3;
2220
+ const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
2221
+ const diagnostics = [];
2222
+ let dependencyPolicy = options.dependencyPolicy || "cancel";
2223
+ if (!options.dependencyPolicy) {
2224
+ if (options.allowMissingComponents && options.allowMissingCapabilities) {
2225
+ dependencyPolicy = "import-with-placeholder";
2226
+ }
2227
+ }
2228
+ const assetCollisionStrategy = options.assetCollisionStrategy || "reject";
2229
+ const archiveBytes = archiveData instanceof Uint8Array ? archiveData : new Uint8Array(archiveData);
2230
+ if (!archiveBytes || archiveBytes.byteLength === 0) {
2231
+ diagnostics.push({
2232
+ code: "ARCHIVE_EMPTY",
2233
+ severity: "error",
2234
+ message: "Archive is empty or 0 bytes."
2235
+ });
2236
+ return buildReport(false, false, { targetVersion, dependencyPolicy, assetCollisionStrategy, diagnostics });
2237
+ }
2238
+ if (archiveBytes.byteLength > limits.maxArchiveSize) {
2239
+ diagnostics.push({
2240
+ code: "SIZE_LIMIT_EXCEEDED",
2241
+ severity: "error",
2242
+ message: `Archive size (${archiveBytes.byteLength} bytes) exceeds maximum limit of ${limits.maxArchiveSize} bytes.`,
2243
+ details: { actualBytes: archiveBytes.byteLength, limitBytes: limits.maxArchiveSize }
2244
+ });
2245
+ return buildReport(false, false, { targetVersion, dependencyPolicy, assetCollisionStrategy, diagnostics });
2246
+ }
2247
+ let unzipped;
2248
+ try {
2249
+ unzipped = unzipSync(archiveBytes);
2250
+ } catch (err) {
2251
+ diagnostics.push({
2252
+ code: "ARCHIVE_CORRUPT",
2253
+ severity: "error",
2254
+ message: `Failed to unzip archive: ${err instanceof Error ? err.message : String(err)}`
2255
+ });
2256
+ return buildReport(false, false, { targetVersion, dependencyPolicy, assetCollisionStrategy, diagnostics });
2257
+ }
2258
+ const entries = Object.keys(unzipped);
2259
+ if (entries.length > limits.maxFileCount) {
2260
+ diagnostics.push({
2261
+ code: "FILE_COUNT_EXCEEDED",
2262
+ severity: "error",
2263
+ message: `Archive file count (${entries.length}) exceeds maximum limit of ${limits.maxFileCount}.`,
2264
+ details: { fileCount: entries.length, limit: limits.maxFileCount }
2265
+ });
2266
+ }
2267
+ let totalUncompressedBytes = 0;
2268
+ for (const entryPath of entries) {
2269
+ if (isDangerousPath(entryPath)) {
2270
+ diagnostics.push({
2271
+ code: "ZIP_SLIP_DETECTED",
2272
+ severity: "error",
2273
+ message: `Security violation: Path traversal or Zip-Slip attempt detected in archive entry "${entryPath}".`,
2274
+ path: entryPath
2275
+ });
2276
+ }
2277
+ if (entryPath.startsWith("assets/") && isDangerousAssetFilename(entryPath)) {
2278
+ diagnostics.push({
2279
+ code: "EXECUTABLE_ASSET_DETECTED",
2280
+ severity: "error",
2281
+ message: `Security violation: Executable or dangerous asset file extension detected in archive entry "${entryPath}".`,
2282
+ path: entryPath
2283
+ });
2284
+ }
2285
+ const fileBytes = unzipped[entryPath];
2286
+ if (fileBytes) {
2287
+ totalUncompressedBytes += fileBytes.byteLength;
2288
+ if (entryPath.startsWith("assets/") && fileBytes.byteLength > limits.maxAssetSize) {
2289
+ diagnostics.push({
2290
+ code: "ASSET_LIMIT_EXCEEDED",
2291
+ severity: "error",
2292
+ message: `Asset "${entryPath}" size (${fileBytes.byteLength} bytes) exceeds maximum limit of ${limits.maxAssetSize} bytes.`,
2293
+ path: entryPath,
2294
+ details: { actualBytes: fileBytes.byteLength, limitBytes: limits.maxAssetSize }
2295
+ });
2296
+ }
2297
+ }
2298
+ }
2299
+ if (totalUncompressedBytes > limits.maxUncompressedSize) {
2300
+ diagnostics.push({
2301
+ code: "SIZE_LIMIT_EXCEEDED",
2302
+ severity: "error",
2303
+ message: `Total uncompressed archive size (${totalUncompressedBytes} bytes) exceeds maximum limit of ${limits.maxUncompressedSize} bytes.`,
2304
+ details: { actualBytes: totalUncompressedBytes, limitBytes: limits.maxUncompressedSize }
2305
+ });
2306
+ }
2307
+ if (checkZipBomb(archiveBytes.byteLength, totalUncompressedBytes)) {
2308
+ diagnostics.push({
2309
+ code: "SIZE_LIMIT_EXCEEDED",
2310
+ severity: "error",
2311
+ message: `Security violation: Potential Zip Bomb attack detected (excessive compression ratio).`,
2312
+ details: { compressedBytes: archiveBytes.byteLength, uncompressedBytes: totalUncompressedBytes }
2313
+ });
2314
+ }
2315
+ const manifestEntry = unzipped["manifest.json"];
2316
+ if (!manifestEntry) {
2317
+ diagnostics.push({
2318
+ code: "MISSING_MANIFEST",
2319
+ severity: "error",
2320
+ message: 'Archive is missing mandatory "manifest.json" file.'
2321
+ });
2322
+ }
2323
+ const pageEntry = unzipped["page.json"];
2324
+ if (!pageEntry) {
2325
+ diagnostics.push({
2326
+ code: "MISSING_PAGE_DOCUMENT",
2327
+ severity: "error",
2328
+ message: 'Archive is missing mandatory "page.json" file.'
2329
+ });
2330
+ }
2331
+ if (!manifestEntry || !pageEntry || diagnostics.some((d) => d.code === "ZIP_SLIP_DETECTED")) {
2332
+ return buildReport(false, false, {
2333
+ targetVersion,
2334
+ dependencyPolicy,
2335
+ assetCollisionStrategy,
2336
+ totalUncompressedBytes,
2337
+ diagnostics
2338
+ });
2339
+ }
2340
+ let parsedManifest;
2341
+ try {
2342
+ const manifestJson = JSON.parse(strFromU8(manifestEntry));
2343
+ const manifestProtoCheck = containsProhibitedKeys(manifestJson);
2344
+ if (manifestProtoCheck.found) {
2345
+ diagnostics.push({
2346
+ code: "PROTOTYPE_POLLUTION_DETECTED",
2347
+ severity: "error",
2348
+ message: `Security violation: Prohibited key "${manifestProtoCheck.key}" in manifest.json.`,
2349
+ path: manifestProtoCheck.path
2350
+ });
2351
+ }
2352
+ const parseResult = ManifestSchema.safeParse(manifestJson);
2353
+ if (!parseResult.success) {
2354
+ const issues = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
2355
+ diagnostics.push({
2356
+ code: "INVALID_MANIFEST",
2357
+ severity: "error",
2358
+ message: `Manifest validation failed: ${issues}`,
2359
+ details: { issues: parseResult.error.issues }
2360
+ });
2361
+ } else {
2362
+ parsedManifest = parseResult.data;
2363
+ }
2364
+ } catch (err) {
2365
+ diagnostics.push({
2366
+ code: "INVALID_MANIFEST",
2367
+ severity: "error",
2368
+ message: `Failed to parse "manifest.json" as valid JSON: ${err instanceof Error ? err.message : String(err)}`
2369
+ });
2370
+ }
2371
+ let rawMetadata;
2372
+ const metadataEntry = unzipped["metadata.json"];
2373
+ if (metadataEntry) {
2374
+ try {
2375
+ rawMetadata = JSON.parse(strFromU8(metadataEntry));
2376
+ } catch {
2377
+ }
2378
+ }
2379
+ let rawPageDoc;
2380
+ try {
2381
+ rawPageDoc = JSON.parse(strFromU8(pageEntry));
2382
+ const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
2383
+ if (pageProtoCheck.found) {
2384
+ diagnostics.push({
2385
+ code: "PROTOTYPE_POLLUTION_DETECTED",
2386
+ severity: "error",
2387
+ message: `Security violation: Prohibited key "${pageProtoCheck.key}" in page.json.`,
2388
+ path: pageProtoCheck.path
2389
+ });
2390
+ }
2391
+ } catch (err) {
2392
+ diagnostics.push({
2393
+ code: "INVALID_PAGE_DOCUMENT",
2394
+ severity: "error",
2395
+ message: `Failed to parse "page.json" as valid JSON: ${err instanceof Error ? err.message : String(err)}`
2396
+ });
2397
+ }
2398
+ const docVersion = typeof rawPageDoc?.version === "string" ? rawPageDoc.version : parsedManifest?.schemaVersion || "unknown";
2399
+ let requiresMigration = false;
2400
+ let migrationPath;
2401
+ if (rawPageDoc) {
2402
+ if (docVersion !== targetVersion) {
2403
+ if (canMigrate(docVersion, targetVersion, migrationRegistry)) {
2404
+ requiresMigration = true;
2405
+ migrationPath = getMigrationPath(docVersion, targetVersion, migrationRegistry) || void 0;
2406
+ diagnostics.push({
2407
+ code: "UNSUPPORTED_SCHEMA_VERSION",
2408
+ severity: "info",
2409
+ message: `Document schema version "${docVersion}" requires migration to current version "${targetVersion}".`,
2410
+ details: { sourceVersion: docVersion, targetVersion, migrationPath }
2411
+ });
2412
+ } else {
2413
+ diagnostics.push({
2414
+ code: "NO_MIGRATION_PATH",
2415
+ severity: "error",
2416
+ message: `Document schema version "${docVersion}" is not compatible and no migration path exists to "${targetVersion}".`,
2417
+ details: { sourceVersion: docVersion, targetVersion }
2418
+ });
2419
+ }
2420
+ } else {
2421
+ const docValidation = validateDocument(rawPageDoc, {
2422
+ componentRegistry: options.componentRegistry,
2423
+ knownComponentTypes: options.knownComponentTypes
2424
+ });
2425
+ const criticalDocErrors = docValidation.errors.filter((e) => {
2426
+ if (e.code === "UNKNOWN_COMPONENT_TYPE" && dependencyPolicy !== "cancel") {
2427
+ return false;
2428
+ }
2429
+ return true;
2430
+ });
2431
+ if (criticalDocErrors.length > 0) {
2432
+ diagnostics.push({
2433
+ code: "INVALID_PAGE_DOCUMENT",
2434
+ severity: "error",
2435
+ message: `Document validation failed with ${criticalDocErrors.length} error(s):
2436
+ ` + criticalDocErrors.map((e) => `[${e.code}] ${e.path}: ${e.message}`).join("\n")
2437
+ });
2438
+ }
2439
+ }
2440
+ }
2441
+ const missingComponents = [];
2442
+ const missingCapabilities = [];
2443
+ const assetConflicts = [];
2444
+ const existingAssetIdsSet = /* @__PURE__ */ new Set();
2445
+ if (options.existingAssetIds) {
2446
+ if (Array.isArray(options.existingAssetIds)) {
2447
+ for (const id of options.existingAssetIds) existingAssetIdsSet.add(id);
2448
+ } else if (options.existingAssetIds instanceof Set) {
2449
+ for (const id of options.existingAssetIds) existingAssetIdsSet.add(id);
2450
+ }
2451
+ }
2452
+ if (options.assetProvider?.list && !options.existingAssetIds) {
2453
+ try {
2454
+ const list = await options.assetProvider.list();
2455
+ if (Array.isArray(list)) {
2456
+ for (const item of list) {
2457
+ if (item?.id) existingAssetIdsSet.add(item.id);
2458
+ }
2459
+ }
2460
+ } catch {
2461
+ }
2462
+ }
2463
+ if (parsedManifest) {
2464
+ for (const assetItem of parsedManifest.assets) {
2465
+ const assetFile = unzipped[assetItem.path];
2466
+ if (!assetFile) {
2467
+ diagnostics.push({
2468
+ code: "ASSET_FILE_MISSING",
2469
+ severity: "error",
2470
+ message: `Declared asset "${assetItem.id}" is missing in archive at path "${assetItem.path}".`,
2471
+ path: assetItem.path,
2472
+ details: { assetId: assetItem.id }
2473
+ });
2474
+ continue;
2475
+ }
2476
+ if (assetFile.byteLength !== assetItem.size) {
2477
+ diagnostics.push({
2478
+ code: "ASSET_SIZE_MISMATCH",
2479
+ severity: "error",
2480
+ message: `Asset "${assetItem.id}" size mismatch: expected ${assetItem.size} bytes, found ${assetFile.byteLength} bytes.`,
2481
+ path: assetItem.path,
2482
+ details: { assetId: assetItem.id, expected: assetItem.size, actual: assetFile.byteLength }
2483
+ });
2484
+ }
2485
+ if (assetItem.checksum) {
2486
+ const actualChecksum = await calculateChecksum(assetFile);
2487
+ if (actualChecksum !== assetItem.checksum) {
2488
+ diagnostics.push({
2489
+ code: "ASSET_CHECKSUM_MISMATCH",
2490
+ severity: "error",
2491
+ message: `Asset "${assetItem.id}" checksum mismatch: expected ${assetItem.checksum}, calculated ${actualChecksum}.`,
2492
+ path: assetItem.path,
2493
+ details: { assetId: assetItem.id, expected: assetItem.checksum, actual: actualChecksum }
2494
+ });
2495
+ }
2496
+ }
2497
+ let isConflict = false;
2498
+ if (typeof options.existingAssetIds === "function") {
2499
+ const fnRes = await options.existingAssetIds(assetItem.id);
2500
+ if (fnRes) isConflict = true;
2501
+ } else if (existingAssetIdsSet.has(assetItem.id)) {
2502
+ isConflict = true;
2503
+ }
2504
+ if (isConflict) {
2505
+ assetConflicts.push({
2506
+ assetId: assetItem.id,
2507
+ path: assetItem.path,
2508
+ mimeType: assetItem.mimeType,
2509
+ size: assetItem.size
2510
+ });
2511
+ }
2512
+ }
2513
+ if (assetConflicts.length > 0) {
2514
+ const conflictIds = assetConflicts.map((c) => c.assetId).join(", ");
2515
+ if (assetCollisionStrategy === "reject" && !options.onAssetConflict) {
2516
+ diagnostics.push({
2517
+ code: "ASSET_CONFLICT",
2518
+ severity: "error",
2519
+ message: `Asset collision detected for asset(s): ${conflictIds}. Host assets will not be overwritten without an explicit assetCollisionStrategy.`,
2520
+ details: { assetConflicts, strategy: assetCollisionStrategy }
2521
+ });
2522
+ } else {
2523
+ diagnostics.push({
2524
+ code: "ASSET_CONFLICT",
2525
+ severity: "info",
2526
+ message: `Asset collision detected for asset(s): ${conflictIds}. Strategy "${assetCollisionStrategy}" will be applied on import.`,
2527
+ details: { assetConflicts, strategy: assetCollisionStrategy }
2528
+ });
2529
+ }
2530
+ }
2531
+ const knownSet = /* @__PURE__ */ new Set();
2532
+ if (options.knownComponentTypes) {
2533
+ for (const t of options.knownComponentTypes) knownSet.add(t);
2534
+ }
2535
+ const builtIns = ["page", "section", "container", "columns", "heading", "text", "image", "button", "collection"];
2536
+ for (const b of builtIns) knownSet.add(b);
2537
+ for (const compType of parsedManifest.requiredComponents) {
2538
+ let isSupported = knownSet.has(compType);
2539
+ if (!isSupported && options.componentRegistry) {
2540
+ isSupported = options.componentRegistry.has(compType);
2541
+ }
2542
+ if (!isSupported) {
2543
+ missingComponents.push(compType);
2544
+ }
2545
+ }
2546
+ if (missingComponents.length > 0) {
2547
+ if (dependencyPolicy === "cancel" && !options.allowMissingComponents) {
2548
+ diagnostics.push({
2549
+ code: "MISSING_COMPONENTS",
2550
+ severity: "error",
2551
+ message: `Package requires custom component(s) not registered in host: ${missingComponents.join(", ")}.`,
2552
+ details: { missingComponents, policy: dependencyPolicy }
2553
+ });
2554
+ } else if (dependencyPolicy === "import-with-placeholder" || options.allowMissingComponents) {
2555
+ diagnostics.push({
2556
+ code: "MISSING_COMPONENTS",
2557
+ severity: "warning",
2558
+ message: `Package requires custom component(s) [${missingComponents.join(", ")}] not registered in host. Nodes and props are preserved intact for placeholder rendering.`,
2559
+ details: { missingComponents, policy: dependencyPolicy }
2560
+ });
2561
+ } else if (dependencyPolicy === "install-or-register-before-import") {
2562
+ diagnostics.push({
2563
+ code: "MISSING_COMPONENTS",
2564
+ severity: "info",
2565
+ message: `Package requires custom component(s) [${missingComponents.join(", ")}]. Installation/registration required before import.`,
2566
+ details: { missingComponents, policy: dependencyPolicy }
2567
+ });
2568
+ }
2569
+ }
2570
+ const supportedCaps = /* @__PURE__ */ new Set();
2571
+ if (options.supportedCapabilities) {
2572
+ for (const c of options.supportedCapabilities) supportedCaps.add(c);
2573
+ }
2574
+ for (const cap of parsedManifest.requiredCapabilities) {
2575
+ if (!supportedCaps.has(cap)) {
2576
+ missingCapabilities.push(cap);
2577
+ }
2578
+ }
2579
+ if (missingCapabilities.length > 0) {
2580
+ if (dependencyPolicy === "cancel" && !options.allowMissingCapabilities) {
2581
+ diagnostics.push({
2582
+ code: "MISSING_CAPABILITIES",
2583
+ severity: "error",
2584
+ message: `Package requires capability/capabilities not supported by host: ${missingCapabilities.join(", ")}.`,
2585
+ details: { missingCapabilities, policy: dependencyPolicy }
2586
+ });
2587
+ } else if (dependencyPolicy === "import-with-placeholder" || options.allowMissingCapabilities) {
2588
+ diagnostics.push({
2589
+ code: "MISSING_CAPABILITIES",
2590
+ severity: "warning",
2591
+ message: `Package requires capability/capabilities [${missingCapabilities.join(", ")}] not supported by host. Placeholder rendering enabled.`,
2592
+ details: { missingCapabilities, policy: dependencyPolicy }
2593
+ });
2594
+ } else if (dependencyPolicy === "install-or-register-before-import") {
2595
+ diagnostics.push({
2596
+ code: "MISSING_CAPABILITIES",
2597
+ severity: "info",
2598
+ message: `Package requires capability/capabilities [${missingCapabilities.join(", ")}]. Registration required before import.`,
2599
+ details: { missingCapabilities, policy: dependencyPolicy }
2600
+ });
2601
+ }
2602
+ }
2603
+ }
2604
+ const hasErrors = diagnostics.some((d) => d.severity === "error");
2605
+ const valid = !hasErrors;
2606
+ const canImport = valid;
2607
+ return buildReport(valid, canImport, {
2608
+ manifest: parsedManifest,
2609
+ rawMetadata,
2610
+ sourceVersion: docVersion,
2611
+ targetVersion,
2612
+ requiresMigration,
2613
+ migrationPath,
2614
+ missingComponents,
2615
+ missingCapabilities,
2616
+ dependencyPolicy,
2617
+ assetCount: parsedManifest?.assets.length || 0,
2618
+ assetConflicts,
2619
+ assetCollisionStrategy,
2620
+ totalUncompressedBytes,
2621
+ diagnostics
2622
+ });
2623
+ }
2624
+ function buildReport(valid, canImport, fields) {
2625
+ return {
2626
+ valid,
2627
+ canImport,
2628
+ targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION3,
2629
+ requiresMigration: fields.requiresMigration || false,
2630
+ missingComponents: fields.missingComponents || [],
2631
+ missingCapabilities: fields.missingCapabilities || [],
2632
+ dependencyPolicy: fields.dependencyPolicy || "cancel",
2633
+ assetCount: fields.assetCount || 0,
2634
+ assetConflicts: fields.assetConflicts || [],
2635
+ assetCollisionStrategy: fields.assetCollisionStrategy || "reject",
2636
+ totalUncompressedBytes: fields.totalUncompressedBytes || 0,
2637
+ diagnostics: fields.diagnostics || [],
2638
+ ...fields
2639
+ };
2640
+ }
2641
+ var previewImportPackage = preflightPackage;
2642
+ var inspectPackage = preflightPackage;
2643
+ function defaultRenameAssetStrategy(incoming, existingIds) {
2644
+ let candidate = `${incoming.id}_imported`;
2645
+ let counter = 1;
2646
+ while (existingIds.has(candidate)) {
2647
+ candidate = `${incoming.id}_imported_${counter}`;
2648
+ counter++;
2649
+ }
2650
+ return candidate;
2651
+ }
2652
+ async function importPackage(archiveData, options = {}) {
2653
+ let preflight = await preflightPackage(archiveData, options);
2654
+ const dependencyPolicy = options.dependencyPolicy || preflight.dependencyPolicy;
2655
+ if (dependencyPolicy === "install-or-register-before-import" && (preflight.missingComponents.length > 0 || preflight.missingCapabilities.length > 0) && options.onMissingDependency) {
2656
+ try {
2657
+ await options.onMissingDependency(
2658
+ {
2659
+ components: [...preflight.missingComponents],
2660
+ capabilities: [...preflight.missingCapabilities]
2661
+ },
2662
+ preflight
2663
+ );
2664
+ preflight = await preflightPackage(archiveData, {
2665
+ ...options,
2666
+ dependencyPolicy: "cancel"
2667
+ // Strict check after install attempt
2668
+ });
2669
+ } catch (err) {
2670
+ const diag = {
2671
+ code: "HOST_ADAPTER_ERROR",
2672
+ severity: "error",
2673
+ message: `Dynamic dependency registration failed: ${err instanceof Error ? err.message : String(err)}`
2674
+ };
2675
+ return {
2676
+ success: false,
2677
+ errors: [diag],
2678
+ diagnosticMessage: diag.message,
2679
+ preflight
2680
+ };
2681
+ }
2682
+ }
2683
+ if (!preflight.canImport || !preflight.manifest) {
2684
+ const errorDiagnostics = preflight.diagnostics.filter((d) => d.severity === "error");
2685
+ const diagnosticMessage = `Package import rejected with ${errorDiagnostics.length} error(s):
2686
+ ` + errorDiagnostics.map((d) => `[${d.code}] ${d.path ? d.path + ": " : ""}${d.message}`).join("\n");
2687
+ return {
2688
+ success: false,
2689
+ errors: errorDiagnostics,
2690
+ diagnosticMessage,
2691
+ preflight
2692
+ };
2693
+ }
2694
+ const archiveBytes = archiveData instanceof Uint8Array ? archiveData : new Uint8Array(archiveData);
2695
+ const unzipped = unzipSync(archiveBytes);
2696
+ const pageRaw = JSON.parse(strFromU8(unzipped["page.json"]));
2697
+ let finalDocument;
2698
+ if (preflight.requiresMigration) {
2699
+ const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
2700
+ const migrationRes = migrateDocument(pageRaw, {
2701
+ targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION3,
2702
+ registry: migrationRegistry,
2703
+ validate: true
2704
+ });
2705
+ if (!migrationRes.success || !migrationRes.document) {
2706
+ const err = {
2707
+ code: "MIGRATION_FAILED",
2708
+ severity: "error",
2709
+ message: `Failed to migrate document from "${preflight.sourceVersion}" to "${preflight.targetVersion}".`,
2710
+ details: { diagnostic: migrationRes.diagnostic }
2711
+ };
2712
+ return {
2713
+ success: false,
2714
+ errors: [err],
2715
+ diagnosticMessage: err.message,
2716
+ preflight
2717
+ };
2718
+ }
2719
+ finalDocument = migrationRes.document;
2720
+ } else {
2721
+ finalDocument = pageRaw;
2722
+ }
2723
+ let metadata = finalDocument.metadata || {
2724
+ title: "Imported Page",
2725
+ description: "",
2726
+ author: "",
2727
+ tags: [],
2728
+ category: "general",
2729
+ version: finalDocument.version || CURRENT_SCHEMA_VERSION3
2730
+ };
2731
+ if (unzipped["metadata.json"]) {
2732
+ try {
2733
+ const metaFromFile = JSON.parse(strFromU8(unzipped["metadata.json"]));
2734
+ metadata = {
2735
+ ...metadata,
2736
+ ...metaFromFile
2737
+ };
2738
+ } catch {
2739
+ }
2740
+ }
2741
+ finalDocument.metadata = metadata;
2742
+ const existingAssetIdsSet = /* @__PURE__ */ new Set();
2743
+ if (options.existingAssetIds) {
2744
+ if (Array.isArray(options.existingAssetIds)) {
2745
+ for (const id of options.existingAssetIds) existingAssetIdsSet.add(id);
2746
+ } else if (options.existingAssetIds instanceof Set) {
2747
+ for (const id of options.existingAssetIds) existingAssetIdsSet.add(id);
2748
+ }
2749
+ }
2750
+ if (options.assetProvider?.list && !options.existingAssetIds) {
2751
+ try {
2752
+ const list = await options.assetProvider.list();
2753
+ if (Array.isArray(list)) {
2754
+ for (const item of list) {
2755
+ if (item?.id) existingAssetIdsSet.add(item.id);
2756
+ }
2757
+ }
2758
+ } catch {
2759
+ }
2760
+ }
2761
+ const renameAssetFn = options.renameAssetStrategy || defaultRenameAssetStrategy;
2762
+ const renameMap = {};
2763
+ const extractedAssets = /* @__PURE__ */ new Map();
2764
+ for (const assetItem of preflight.manifest.assets) {
2765
+ const assetBytes = unzipped[assetItem.path];
2766
+ if (assetBytes) {
2767
+ let targetAssetId = assetItem.id;
2768
+ let effectiveStrategy = options.assetCollisionStrategy || "reject";
2769
+ const conflictInfo = preflight.assetConflicts.find((c) => c.assetId === assetItem.id);
2770
+ if (conflictInfo) {
2771
+ if (options.onAssetConflict) {
2772
+ try {
2773
+ const conflictRes = await options.onAssetConflict(conflictInfo);
2774
+ if (typeof conflictRes === "string") {
2775
+ effectiveStrategy = conflictRes;
2776
+ } else {
2777
+ effectiveStrategy = conflictRes.action;
2778
+ if (conflictRes.newAssetId) {
2779
+ targetAssetId = conflictRes.newAssetId;
2780
+ }
2781
+ }
2782
+ } catch (err) {
2783
+ const diag = {
2784
+ code: "HOST_ADAPTER_ERROR",
2785
+ severity: "error",
2786
+ message: `Asset conflict resolution handler failed for "${assetItem.id}": ${err instanceof Error ? err.message : String(err)}`,
2787
+ path: assetItem.path
2788
+ };
2789
+ return {
2790
+ success: false,
2791
+ errors: [diag],
2792
+ diagnosticMessage: diag.message,
2793
+ preflight
2794
+ };
2795
+ }
2796
+ }
2797
+ if (effectiveStrategy === "reject") {
2798
+ const diag = {
2799
+ code: "ASSET_CONFLICT",
2800
+ severity: "error",
2801
+ message: `Import aborted: Asset "${assetItem.id}" collides with host asset. No overwrite strategy specified.`,
2802
+ path: assetItem.path
2803
+ };
2804
+ return {
2805
+ success: false,
2806
+ errors: [diag],
2807
+ diagnosticMessage: diag.message,
2808
+ preflight
2809
+ };
2810
+ }
2811
+ if (effectiveStrategy === "rename") {
2812
+ if (targetAssetId === assetItem.id) {
2813
+ targetAssetId = renameAssetFn(assetItem, existingAssetIdsSet);
2814
+ }
2815
+ renameMap[assetItem.id] = targetAssetId;
2816
+ existingAssetIdsSet.add(targetAssetId);
2817
+ }
2818
+ }
2819
+ let hostInfo;
2820
+ if (effectiveStrategy === "reuse-existing" && conflictInfo) {
2821
+ if (options.assetProvider?.resolve) {
2822
+ hostInfo = await options.assetProvider.resolve(targetAssetId);
2823
+ }
2824
+ } else {
2825
+ const effectiveMeta = {
2826
+ ...assetItem,
2827
+ id: targetAssetId
2828
+ };
2829
+ if (options.onAssetImport) {
2830
+ try {
2831
+ const result = await options.onAssetImport(targetAssetId, assetBytes, effectiveMeta);
2832
+ if (result) hostInfo = result;
2833
+ } catch (err) {
2834
+ const diag = {
2835
+ code: "HOST_ADAPTER_ERROR",
2836
+ severity: "error",
2837
+ message: `Host asset import hook failed for "${targetAssetId}": ${err instanceof Error ? err.message : String(err)}`,
2838
+ path: assetItem.path
2839
+ };
2840
+ return {
2841
+ success: false,
2842
+ errors: [diag],
2843
+ diagnosticMessage: diag.message,
2844
+ preflight
2845
+ };
2846
+ }
2847
+ } else if (options.assetProvider?.upload) {
2848
+ try {
2849
+ const blob = new Blob([assetBytes], { type: assetItem.mimeType });
2850
+ const filename = assetItem.path.replace(/^assets\//, "");
2851
+ const file = new File([blob], filename, { type: assetItem.mimeType });
2852
+ const info = await options.assetProvider.upload(file, {
2853
+ assetId: targetAssetId,
2854
+ originalAssetId: assetItem.id
2855
+ });
2856
+ if (info) hostInfo = info;
2857
+ } catch {
2858
+ }
2859
+ }
2860
+ }
2861
+ extractedAssets.set(targetAssetId, {
2862
+ bytes: assetBytes,
2863
+ meta: {
2864
+ ...assetItem,
2865
+ id: targetAssetId
2866
+ },
2867
+ hostInfo
2868
+ });
2869
+ }
2870
+ }
2871
+ if (Object.keys(renameMap).length > 0) {
2872
+ remapDocumentAssetReferences(finalDocument, renameMap);
2873
+ }
2874
+ return {
2875
+ success: true,
2876
+ document: finalDocument,
2877
+ manifest: preflight.manifest,
2878
+ metadata,
2879
+ extractedAssets,
2880
+ renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
2881
+ preflight
2882
+ };
2883
+ }
2884
+ var importStoraPackage = importPackage;
2885
+
2886
+ // src/round-trip-comparator.ts
2887
+ function isAssetReferenceLike(val) {
2888
+ if (!val || typeof val !== "object") return false;
2889
+ const obj = val;
2890
+ return obj.type === "asset" && typeof obj.assetId === "string" && obj.assetId.trim().length > 0;
2891
+ }
2892
+ function isVariableBindingLike(val) {
2893
+ if (!val || typeof val !== "object") return false;
2894
+ const obj = val;
2895
+ return obj.type === "variable" && typeof obj.key === "string" && obj.key.trim().length > 0;
2896
+ }
2897
+ function isActionBindingLike(val) {
2898
+ if (!val || typeof val !== "object") return false;
2899
+ const obj = val;
2900
+ return obj.type !== void 0 && typeof obj.type === "string" && obj.type.trim().length > 0 && ("payload" in obj || Object.keys(obj).length <= 2);
2901
+ }
2902
+ function normalizeAssetId(id, assetIdMap) {
2903
+ if (!assetIdMap) return id;
2904
+ if (assetIdMap instanceof Map) {
2905
+ return assetIdMap.get(id) ?? id;
2906
+ }
2907
+ return assetIdMap[id] ?? id;
2908
+ }
2909
+ function deepEqual(a, b) {
2910
+ if (a === b) return true;
2911
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
2912
+ return false;
2913
+ }
2914
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2915
+ if (Array.isArray(a) && Array.isArray(b)) {
2916
+ if (a.length !== b.length) return false;
2917
+ for (let i = 0; i < a.length; i++) {
2918
+ if (!deepEqual(a[i], b[i])) return false;
2919
+ }
2920
+ return true;
2921
+ }
2922
+ const objA = a;
2923
+ const objB = b;
2924
+ const keysA = Object.keys(objA);
2925
+ const keysB = Object.keys(objB);
2926
+ if (keysA.length !== keysB.length) return false;
2927
+ for (const k of keysA) {
2928
+ if (!Object.prototype.hasOwnProperty.call(objB, k)) return false;
2929
+ if (!deepEqual(objA[k], objB[k])) return false;
2930
+ }
2931
+ return true;
2932
+ }
2933
+ function comparePropValues(path, expected, actual, differences, options) {
2934
+ if (isVariableBindingLike(expected)) {
2935
+ if (!isVariableBindingLike(actual)) {
2936
+ differences.push({
2937
+ path,
2938
+ kind: "BINDING_MISMATCH",
2939
+ message: `Expected variable binding at "${path}", but got non-binding value.`,
2940
+ expected,
2941
+ actual
2942
+ });
2943
+ return;
2944
+ }
2945
+ const expBinding = expected;
2946
+ const actBinding = actual;
2947
+ if (expBinding.key !== actBinding.key) {
2948
+ differences.push({
2949
+ path: `${path}.key`,
2950
+ kind: "BINDING_MISMATCH",
2951
+ message: `Variable binding key mismatch at "${path}": expected "${expBinding.key}", got "${actBinding.key}".`,
2952
+ expected: expBinding.key,
2953
+ actual: actBinding.key
2954
+ });
2955
+ }
2956
+ if (!deepEqual(expBinding.fallback, actBinding.fallback)) {
2957
+ differences.push({
2958
+ path: `${path}.fallback`,
2959
+ kind: "BINDING_MISMATCH",
2960
+ message: `Variable binding fallback mismatch at "${path}".`,
2961
+ expected: expBinding.fallback,
2962
+ actual: actBinding.fallback
2963
+ });
2964
+ }
2965
+ return;
2966
+ }
2967
+ if (isActionBindingLike(expected) && !isAssetReferenceLike(expected) && !isVariableBindingLike(expected)) {
2968
+ if (!isActionBindingLike(actual)) {
2969
+ differences.push({
2970
+ path,
2971
+ kind: "ACTION_MISMATCH",
2972
+ message: `Expected action binding at "${path}", but got non-action value.`,
2973
+ expected,
2974
+ actual
2975
+ });
2976
+ return;
2977
+ }
2978
+ const expAction = expected;
2979
+ const actAction = actual;
2980
+ if (expAction.type !== actAction.type) {
2981
+ differences.push({
2982
+ path: `${path}.type`,
2983
+ kind: "ACTION_MISMATCH",
2984
+ message: `Action binding type mismatch at "${path}": expected "${expAction.type}", got "${actAction.type}".`,
2985
+ expected: expAction.type,
2986
+ actual: actAction.type
2987
+ });
2988
+ }
2989
+ if (!deepEqual(expAction.payload ?? {}, actAction.payload ?? {})) {
2990
+ differences.push({
2991
+ path: `${path}.payload`,
2992
+ kind: "ACTION_MISMATCH",
2993
+ message: `Action binding payload mismatch at "${path}".`,
2994
+ expected: expAction.payload,
2995
+ actual: actAction.payload
2996
+ });
2997
+ }
2998
+ return;
2999
+ }
3000
+ if (isAssetReferenceLike(expected)) {
3001
+ if (!isAssetReferenceLike(actual)) {
3002
+ differences.push({
3003
+ path,
3004
+ kind: "ASSET_MISMATCH",
3005
+ message: `Expected asset reference at "${path}", but got non-asset value.`,
3006
+ expected,
3007
+ actual
3008
+ });
3009
+ return;
3010
+ }
3011
+ const expAsset = expected;
3012
+ const actAsset = actual;
3013
+ const expectedMappedId = normalizeAssetId(expAsset.assetId, options?.assetIdMap);
3014
+ if (actAsset.assetId !== expectedMappedId) {
3015
+ differences.push({
3016
+ path: `${path}.assetId`,
3017
+ kind: "ASSET_MISMATCH",
3018
+ message: `Asset reference ID mismatch at "${path}": expected "${expectedMappedId}" (original: "${expAsset.assetId}"), got "${actAsset.assetId}".`,
3019
+ expected: expectedMappedId,
3020
+ actual: actAsset.assetId
3021
+ });
3022
+ }
3023
+ if (expAsset.filename !== actAsset.filename) {
3024
+ differences.push({
3025
+ path: `${path}.filename`,
3026
+ kind: "ASSET_MISMATCH",
3027
+ message: `Asset reference filename mismatch at "${path}": expected "${expAsset.filename}", got "${actAsset.filename}".`,
3028
+ expected: expAsset.filename,
3029
+ actual: actAsset.filename
3030
+ });
3031
+ }
3032
+ if (expAsset.mimeType !== actAsset.mimeType) {
3033
+ differences.push({
3034
+ path: `${path}.mimeType`,
3035
+ kind: "ASSET_MISMATCH",
3036
+ message: `Asset reference mimeType mismatch at "${path}": expected "${expAsset.mimeType}", got "${actAsset.mimeType}".`,
3037
+ expected: expAsset.mimeType,
3038
+ actual: actAsset.mimeType
3039
+ });
3040
+ }
3041
+ return;
3042
+ }
3043
+ if (expected !== null && actual !== null && typeof expected === "object" && typeof actual === "object") {
3044
+ if (Array.isArray(expected) !== Array.isArray(actual)) {
3045
+ differences.push({
3046
+ path,
3047
+ kind: "PROP_MISMATCH",
3048
+ message: `Type mismatch at "${path}": one is array, other is object.`,
3049
+ expected,
3050
+ actual
3051
+ });
3052
+ return;
3053
+ }
3054
+ if (Array.isArray(expected) && Array.isArray(actual)) {
3055
+ if (expected.length !== actual.length) {
3056
+ differences.push({
3057
+ path,
3058
+ kind: "PROP_MISMATCH",
3059
+ message: `Array length mismatch at "${path}": expected length ${expected.length}, got ${actual.length}.`,
3060
+ expected,
3061
+ actual
3062
+ });
3063
+ return;
3064
+ }
3065
+ for (let i = 0; i < expected.length; i++) {
3066
+ comparePropValues(`${path}[${i}]`, expected[i], actual[i], differences, options);
3067
+ }
3068
+ return;
3069
+ }
3070
+ const expObj = expected;
3071
+ const actObj = actual;
3072
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(expObj), ...Object.keys(actObj)]);
3073
+ for (const key of allKeys) {
3074
+ if (!(key in expObj)) {
3075
+ differences.push({
3076
+ path: `${path}.${key}`,
3077
+ kind: "PROP_MISMATCH",
3078
+ message: `Unexpected extra prop key "${key}" at "${path}".`,
3079
+ actual: actObj[key]
3080
+ });
3081
+ } else if (!(key in actObj)) {
3082
+ differences.push({
3083
+ path: `${path}.${key}`,
3084
+ kind: "PROP_MISMATCH",
3085
+ message: `Missing expected prop key "${key}" at "${path}".`,
3086
+ expected: expObj[key]
3087
+ });
3088
+ } else {
3089
+ comparePropValues(`${path}.${key}`, expObj[key], actObj[key], differences, options);
3090
+ }
3091
+ }
3092
+ return;
3093
+ }
3094
+ if (expected !== actual) {
3095
+ differences.push({
3096
+ path,
3097
+ kind: "PROP_MISMATCH",
3098
+ message: `Value mismatch at "${path}": expected "${String(expected)}", got "${String(actual)}".`,
3099
+ expected,
3100
+ actual
3101
+ });
3102
+ }
3103
+ }
3104
+ function compareStyles(nodePath, expected, actual, differences = []) {
3105
+ const breakpoints = ["base", "desktop", "tablet", "mobile"];
3106
+ const expStyles = expected || {};
3107
+ const actStyles = actual || {};
3108
+ for (const bp of breakpoints) {
3109
+ const expBp = expStyles[bp] || {};
3110
+ const actBp = actStyles[bp] || {};
3111
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(expBp), ...Object.keys(actBp)]);
3112
+ for (const key of allKeys) {
3113
+ const expVal = expBp[key];
3114
+ const actVal = actBp[key];
3115
+ const expNorm = expVal === void 0 || expVal === "" ? void 0 : expVal;
3116
+ const actNorm = actVal === void 0 || actVal === "" ? void 0 : actVal;
3117
+ if (expNorm !== actNorm) {
3118
+ differences.push({
3119
+ path: `${nodePath}.styles.${bp}.${key}`,
3120
+ kind: "STYLE_MISMATCH",
3121
+ message: `Style mismatch at "${nodePath}.styles.${bp}.${key}": expected "${String(expVal)}", got "${String(actVal)}".`,
3122
+ expected: expVal,
3123
+ actual: actVal
3124
+ });
3125
+ }
3126
+ }
3127
+ }
3128
+ }
3129
+ function compareNodes(expected, actual, nodePath, differences, options) {
3130
+ if (expected.id !== actual.id) {
3131
+ differences.push({
3132
+ path: `${nodePath}.id`,
3133
+ kind: "NODE_MISSING",
3134
+ message: `Node ID mismatch at "${nodePath}": expected "${expected.id}", got "${actual.id}".`,
3135
+ expected: expected.id,
3136
+ actual: actual.id
3137
+ });
3138
+ }
3139
+ if (expected.type !== actual.type) {
3140
+ differences.push({
3141
+ path: `${nodePath}.type`,
3142
+ kind: "NODE_TYPE",
3143
+ message: `Node type mismatch for "${expected.id}": expected "${expected.type}", got "${actual.type}".`,
3144
+ expected: expected.type,
3145
+ actual: actual.type
3146
+ });
3147
+ }
3148
+ comparePropValues(`${nodePath}.props`, expected.props || {}, actual.props || {}, differences, options);
3149
+ compareStyles(nodePath, expected.styles, actual.styles, differences);
3150
+ const expChildren = expected.children || [];
3151
+ const actChildren = actual.children || [];
3152
+ if (expChildren.length !== actChildren.length) {
3153
+ differences.push({
3154
+ path: `${nodePath}.children`,
3155
+ kind: "NODE_ORDER",
3156
+ message: `Children count mismatch for node "${expected.id}": expected ${expChildren.length}, got ${actChildren.length}.`,
3157
+ expected: expChildren.map((c) => c.id),
3158
+ actual: actChildren.map((c) => c.id)
3159
+ });
3160
+ }
3161
+ const maxLen = Math.max(expChildren.length, actChildren.length);
3162
+ for (let i = 0; i < maxLen; i++) {
3163
+ const expChild = expChildren[i];
3164
+ const actChild = actChildren[i];
3165
+ if (!expChild && actChild) {
3166
+ differences.push({
3167
+ path: `${nodePath}.children[${i}]`,
3168
+ kind: "NODE_EXTRA",
3169
+ message: `Unexpected extra child node "${actChild.id}" at index ${i}.`,
3170
+ actual: actChild.id
3171
+ });
3172
+ } else if (expChild && !actChild) {
3173
+ differences.push({
3174
+ path: `${nodePath}.children[${i}]`,
3175
+ kind: "NODE_MISSING",
3176
+ message: `Missing child node "${expChild.id}" at index ${i}.`,
3177
+ expected: expChild.id
3178
+ });
3179
+ } else if (expChild && actChild) {
3180
+ if (expChild.id !== actChild.id) {
3181
+ differences.push({
3182
+ path: `${nodePath}.children[${i}]`,
3183
+ kind: "NODE_ORDER",
3184
+ message: `Node order mismatch at index ${i}: expected node "${expChild.id}", got "${actChild.id}".`,
3185
+ expected: expChild.id,
3186
+ actual: actChild.id
3187
+ });
3188
+ }
3189
+ compareNodes(expChild, actChild, `${nodePath}.children[${i}](${expChild.id})`, differences, options);
3190
+ }
3191
+ }
3192
+ }
3193
+ function compareDocumentsSemantically(expected, actual, options = {}) {
3194
+ const differences = [];
3195
+ const { ignoreTimestamps = true } = options;
3196
+ if (expected.schema !== actual.schema) {
3197
+ differences.push({
3198
+ path: "schema",
3199
+ kind: "METADATA_MISMATCH",
3200
+ message: `Schema mismatch: expected "${expected.schema}", got "${actual.schema}".`,
3201
+ expected: expected.schema,
3202
+ actual: actual.schema
3203
+ });
3204
+ }
3205
+ if (expected.version !== actual.version) {
3206
+ differences.push({
3207
+ path: "version",
3208
+ kind: "METADATA_MISMATCH",
3209
+ message: `Schema version mismatch: expected "${expected.version}", got "${actual.version}".`,
3210
+ expected: expected.version,
3211
+ actual: actual.version
3212
+ });
3213
+ }
3214
+ const expMeta = expected.metadata || {};
3215
+ const actMeta = actual.metadata || {};
3216
+ const metaKeys = /* @__PURE__ */ new Set([...Object.keys(expMeta), ...Object.keys(actMeta)]);
3217
+ for (const k of metaKeys) {
3218
+ if (ignoreTimestamps && (k === "updatedAt" || k === "createdAt")) {
3219
+ continue;
3220
+ }
3221
+ const expVal = expMeta[k];
3222
+ const actVal = actMeta[k];
3223
+ if (!deepEqual(expVal, actVal)) {
3224
+ differences.push({
3225
+ path: `metadata.${k}`,
3226
+ kind: "METADATA_MISMATCH",
3227
+ message: `Metadata mismatch at "metadata.${k}": expected "${String(expVal)}", got "${String(actVal)}".`,
3228
+ expected: expVal,
3229
+ actual: actVal
3230
+ });
3231
+ }
3232
+ }
3233
+ compareNodes(
3234
+ expected.document,
3235
+ actual.document,
3236
+ `root(${expected.document.id})`,
3237
+ differences,
3238
+ options
3239
+ );
3240
+ return {
3241
+ equivalent: differences.length === 0,
3242
+ differences
3243
+ };
3244
+ }
3245
+ function compareManifestsSemantically(expected, actual, options = {}) {
3246
+ const differences = [];
3247
+ const { ignoreTimestamps = true } = options;
3248
+ if (expected.schema !== actual.schema) {
3249
+ differences.push({
3250
+ path: "manifest.schema",
3251
+ kind: "METADATA_MISMATCH",
3252
+ message: `Manifest schema mismatch: expected "${expected.schema}", got "${actual.schema}".`,
3253
+ expected: expected.schema,
3254
+ actual: actual.schema
3255
+ });
3256
+ }
3257
+ if (expected.schemaVersion !== actual.schemaVersion) {
3258
+ differences.push({
3259
+ path: "manifest.schemaVersion",
3260
+ kind: "METADATA_MISMATCH",
3261
+ message: `Manifest schemaVersion mismatch: expected "${expected.schemaVersion}", got "${actual.schemaVersion}".`,
3262
+ expected: expected.schemaVersion,
3263
+ actual: actual.schemaVersion
3264
+ });
3265
+ }
3266
+ const expComponents = new Set(expected.requiredComponents || []);
3267
+ const actComponents = new Set(actual.requiredComponents || []);
3268
+ for (const c of expComponents) {
3269
+ if (!actComponents.has(c)) {
3270
+ differences.push({
3271
+ path: "manifest.requiredComponents",
3272
+ kind: "DEPENDENCY_MISMATCH",
3273
+ message: `Missing required component in manifest: "${c}".`,
3274
+ expected: c
3275
+ });
3276
+ }
3277
+ }
3278
+ for (const c of actComponents) {
3279
+ if (!expComponents.has(c)) {
3280
+ differences.push({
3281
+ path: "manifest.requiredComponents",
3282
+ kind: "DEPENDENCY_MISMATCH",
3283
+ message: `Unexpected extra required component in manifest: "${c}".`,
3284
+ actual: c
3285
+ });
3286
+ }
3287
+ }
3288
+ const expCaps = new Set(expected.requiredCapabilities || []);
3289
+ const actCaps = new Set(actual.requiredCapabilities || []);
3290
+ for (const c of expCaps) {
3291
+ if (!actCaps.has(c)) {
3292
+ differences.push({
3293
+ path: "manifest.requiredCapabilities",
3294
+ kind: "DEPENDENCY_MISMATCH",
3295
+ message: `Missing required capability in manifest: "${c}".`,
3296
+ expected: c
3297
+ });
3298
+ }
3299
+ }
3300
+ for (const c of actCaps) {
3301
+ if (!expCaps.has(c)) {
3302
+ differences.push({
3303
+ path: "manifest.requiredCapabilities",
3304
+ kind: "DEPENDENCY_MISMATCH",
3305
+ message: `Unexpected extra required capability in manifest: "${c}".`,
3306
+ actual: c
3307
+ });
3308
+ }
3309
+ }
3310
+ if (expected.assets.length !== actual.assets.length) {
3311
+ differences.push({
3312
+ path: "manifest.assets",
3313
+ kind: "ASSET_MISMATCH",
3314
+ message: `Manifest asset count mismatch: expected ${expected.assets.length}, got ${actual.assets.length}.`,
3315
+ expected: expected.assets.length,
3316
+ actual: actual.assets.length
3317
+ });
3318
+ }
3319
+ return {
3320
+ equivalent: differences.length === 0,
3321
+ differences
3322
+ };
3323
+ }
3324
+
3325
+ // src/template-utils.ts
3326
+ import {
3327
+ TemplateRecordSchema,
3328
+ collectNodeIds as collectNodeIds2,
3329
+ isTemplateRecord
3330
+ } from "@kubuild/schema";
3331
+ var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
3332
+ "page",
3333
+ "section",
3334
+ "container",
3335
+ "columns",
3336
+ "column",
3337
+ "heading",
3338
+ "text",
3339
+ "image",
3340
+ "button",
3341
+ "collection"
3342
+ ]);
3343
+ function validateTemplate(value) {
3344
+ const parseResult = TemplateRecordSchema.safeParse(value);
3345
+ if (parseResult.success) {
3346
+ return {
3347
+ valid: true,
3348
+ data: parseResult.data
3349
+ };
3350
+ }
3351
+ const errors = parseResult.error.issues.map((issue) => ({
3352
+ code: issue.code,
3353
+ message: issue.message,
3354
+ path: `/${issue.path.join("/")}`
3355
+ }));
3356
+ return {
3357
+ valid: false,
3358
+ errors
3359
+ };
3360
+ }
3361
+ function collectComponentTypes(node, types) {
3362
+ if (node.type) {
3363
+ types.add(node.type);
3364
+ }
3365
+ if (node.children && Array.isArray(node.children)) {
3366
+ for (const child of node.children) {
3367
+ collectComponentTypes(child, types);
3368
+ }
3369
+ }
3370
+ }
3371
+ function extractTemplateRequirements(doc, builtinComponents = CORE_BUILTIN_COMPONENTS) {
3372
+ const componentTypes = /* @__PURE__ */ new Set();
3373
+ if (doc?.document) {
3374
+ collectComponentTypes(doc.document, componentTypes);
3375
+ }
3376
+ const customComponents = [];
3377
+ for (const type of componentTypes) {
3378
+ if (!builtinComponents.has(type)) {
3379
+ customComponents.push(type);
3380
+ }
3381
+ }
3382
+ return {
3383
+ requiredComponents: customComponents.sort(),
3384
+ requiredCapabilities: []
3385
+ };
3386
+ }
3387
+ function createTemplateRecord(params) {
3388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3389
+ let requirements = params.requirements;
3390
+ if (!requirements && params.document) {
3391
+ requirements = extractTemplateRequirements(params.document);
3392
+ }
3393
+ const candidate = {
3394
+ id: params.id,
3395
+ name: params.name,
3396
+ description: params.description ?? "",
3397
+ category: params.category ?? "general",
3398
+ tags: params.tags ?? [],
3399
+ thumbnail: params.thumbnail,
3400
+ author: params.author ?? "",
3401
+ version: params.version ?? "1.0.0",
3402
+ document: params.document,
3403
+ packageReference: params.packageReference,
3404
+ requirements: requirements ?? { requiredComponents: [], requiredCapabilities: [] },
3405
+ createdAt: params.createdAt ?? now,
3406
+ updatedAt: params.updatedAt ?? now,
3407
+ custom: params.custom
3408
+ };
3409
+ const validation = validateTemplate(candidate);
3410
+ if (!validation.valid || !validation.data) {
3411
+ const errorDetails = validation.errors?.map((e) => `${e.path}: ${e.message}`).join(", ");
3412
+ throw new Error(`Invalid template record: ${errorDetails}`);
3413
+ }
3414
+ return validation.data;
3415
+ }
3416
+ function saveDraftAsTemplate(draft, metadata, options = {}) {
3417
+ if (!metadata || typeof metadata !== "object") {
3418
+ throw new Error("Save as template requires metadata with id and name.");
3419
+ }
3420
+ const cleanId = metadata.id?.trim();
3421
+ if (!cleanId) {
3422
+ throw new Error("Template ID is required and cannot be empty.");
3423
+ }
3424
+ const cleanName = metadata.name?.trim();
3425
+ if (!cleanName) {
3426
+ throw new Error("Template name is required and cannot be empty.");
3427
+ }
3428
+ if (!draft || !draft.document) {
3429
+ throw new Error("Invalid draft document: document tree is missing.");
3430
+ }
3431
+ if (options.validateDraft !== false) {
3432
+ const validation = validateDocument(draft);
3433
+ if (!validation.valid) {
3434
+ const errorMsg = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
3435
+ throw new Error(`Cannot save invalid draft as template: ${errorMsg}`);
3436
+ }
3437
+ }
3438
+ const draftSnapshot = deepClone(draft);
3439
+ return createTemplateRecord({
3440
+ id: cleanId,
3441
+ name: cleanName,
3442
+ description: metadata.description?.trim() ?? "",
3443
+ category: metadata.category?.trim() ?? "general",
3444
+ tags: metadata.tags ? [...metadata.tags] : [],
3445
+ thumbnail: metadata.thumbnail,
3446
+ author: metadata.author?.trim() ?? "",
3447
+ version: metadata.version?.trim() ?? "1.0.0",
3448
+ document: draftSnapshot,
3449
+ requirements: options.requirements,
3450
+ custom: metadata.custom ? deepClone(metadata.custom) : void 0
3451
+ });
3452
+ }
3453
+ function cloneTreeWithFreshIds(root, idGen) {
3454
+ function cloneRec(node) {
3455
+ const newId = idGen(node.id, node);
3456
+ const clonedProps = node.props ? deepClone(node.props) : void 0;
3457
+ const clonedStyles = node.styles ? deepClone(node.styles) : void 0;
3458
+ const clonedChildren = node.children ? node.children.map((child) => cloneRec(child)) : [];
3459
+ return {
3460
+ id: newId,
3461
+ type: node.type,
3462
+ ...clonedProps ? { props: clonedProps } : {},
3463
+ ...clonedStyles ? { styles: clonedStyles } : {},
3464
+ children: clonedChildren
3465
+ };
3466
+ }
3467
+ return cloneRec(root);
3468
+ }
3469
+ function cloneTemplateAsPage(templateOrDoc, options = {}) {
3470
+ let sourceDoc;
3471
+ let templateOrigin = null;
3472
+ if (isTemplateRecord(templateOrDoc)) {
3473
+ if (!templateOrDoc.document) {
3474
+ throw new Error(
3475
+ `Template "${templateOrDoc.name}" (${templateOrDoc.id}) does not contain an inline document snapshot to clone from.`
3476
+ );
3477
+ }
3478
+ sourceDoc = templateOrDoc.document;
3479
+ templateOrigin = {
3480
+ id: templateOrDoc.id,
3481
+ name: templateOrDoc.name,
3482
+ version: templateOrDoc.version ?? "1.0.0"
3483
+ };
3484
+ } else if ("schema" in templateOrDoc && "document" in templateOrDoc) {
3485
+ sourceDoc = templateOrDoc;
3486
+ } else {
3487
+ throw new Error("Invalid input: expected a TemplateRecord or PageDocument.");
3488
+ }
3489
+ const existingIdsInSource = new Set(collectNodeIds2(sourceDoc.document));
3490
+ const assignedNewIds = /* @__PURE__ */ new Set();
3491
+ const prefix = options.idPrefix || `page_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;
3492
+ let counter = 1;
3493
+ const defaultIdGen = (oldId, node) => {
3494
+ let candidate = `${node.type}_${prefix}_${counter}`;
3495
+ while (existingIdsInSource.has(candidate) || assignedNewIds.has(candidate)) {
3496
+ counter++;
3497
+ candidate = `${node.type}_${prefix}_${counter}`;
3498
+ }
3499
+ counter++;
3500
+ assignedNewIds.add(candidate);
3501
+ return candidate;
3502
+ };
3503
+ const idGen = options.idGenerator || defaultIdGen;
3504
+ const clonedRootNode = cloneTreeWithFreshIds(sourceDoc.document, idGen);
3505
+ const rootPageNode = {
3506
+ ...clonedRootNode,
3507
+ type: "page"
3508
+ };
3509
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3510
+ const pageTitle = options.title ?? (templateOrigin ? `${templateOrigin.name}` : sourceDoc.metadata?.title ?? "Untitled Page");
3511
+ const customMetadata = {
3512
+ ...sourceDoc.metadata?.custom ? deepClone(sourceDoc.metadata.custom) : {},
3513
+ ...options.customMetadata ? deepClone(options.customMetadata) : {}
3514
+ };
3515
+ if (options.recordOrigin !== false && templateOrigin) {
3516
+ customMetadata.originTemplate = templateOrigin;
3517
+ }
3518
+ const newDoc = {
3519
+ schema: sourceDoc.schema,
3520
+ version: sourceDoc.version,
3521
+ metadata: {
3522
+ title: pageTitle,
3523
+ description: sourceDoc.metadata?.description ?? "",
3524
+ author: options.author ?? sourceDoc.metadata?.author ?? "",
3525
+ tags: sourceDoc.metadata?.tags ? [...sourceDoc.metadata.tags] : [],
3526
+ category: sourceDoc.metadata?.category ?? "general",
3527
+ version: "1.0.0",
3528
+ createdAt: now,
3529
+ updatedAt: now,
3530
+ custom: customMetadata
3531
+ },
3532
+ document: rootPageNode
3533
+ };
3534
+ return newDoc;
3535
+ }
3536
+ function areNodeIdsCompletelyDistinct(docA, docB) {
3537
+ if (!docA?.document || !docB?.document) return true;
3538
+ const idsA = new Set(collectNodeIds2(docA.document));
3539
+ const idsB = collectNodeIds2(docB.document);
3540
+ for (const id of idsB) {
3541
+ if (idsA.has(id)) {
3542
+ return false;
3543
+ }
3544
+ }
3545
+ return true;
3546
+ }
3547
+ export {
3548
+ CORE_BUILTIN_COMPONENTS,
3549
+ DEFAULT_DOCUMENT_SECURITY_LIMITS,
3550
+ DEFAULT_MAX_HISTORY,
3551
+ DEFAULT_SECURITY_LIMITS,
3552
+ DocumentHistoryManager,
3553
+ HistoryEngine,
3554
+ MigrationRegistry,
3555
+ areNodeIdsCompletelyDistinct,
3556
+ buildSampleVariablesFromCatalog,
3557
+ calculateChecksum,
3558
+ canMigrate,
3559
+ checkZipBomb,
3560
+ cloneTemplateAsPage,
3561
+ cloneTreeWithNewIds,
3562
+ collectAssetReferences,
3563
+ collectNodeIdSet,
3564
+ compareDocumentsSemantically,
3565
+ compareManifestsSemantically,
3566
+ containsProhibitedKeys,
3567
+ createBlankDocument,
3568
+ createTemplateRecord,
3569
+ deepClone,
3570
+ defaultIdGenerator,
3571
+ defaultMigrationRegistry,
3572
+ defaultRenameAssetStrategy,
3573
+ duplicateNode,
3574
+ exportPackage,
3575
+ exportStoraPackage,
3576
+ extractRequirementsFromTree,
3577
+ extractTemplateRequirements,
3578
+ findMissingComponentNodes,
3579
+ findNodeById,
3580
+ findNodeLocation,
3581
+ getAncestorChain,
3582
+ getMigrationPath,
3583
+ getNavigationTarget,
3584
+ getNodeAncestors,
3585
+ getParentNodeId,
3586
+ importPackage,
3587
+ importStoraPackage,
3588
+ insertNode,
3589
+ inspectPackage,
3590
+ isDangerousAssetFilename,
3591
+ isDangerousPath,
3592
+ isDescendantOf,
3593
+ isSafeUrl,
3594
+ migrateDocument,
3595
+ moveNode,
3596
+ preflightPackage,
3597
+ previewImportPackage,
3598
+ remapAssetReferences,
3599
+ remapDocumentAssetReferences,
3600
+ removeNode,
3601
+ resolveBinding,
3602
+ resolveBindingValue,
3603
+ sanitizeFilename,
3604
+ sanitizeHtml,
3605
+ sanitizeUrl,
3606
+ saveDraftAsTemplate,
3607
+ sha256Sync,
3608
+ updateProps,
3609
+ updateStyle,
3610
+ validateDocument,
3611
+ validateDocumentSecurity,
3612
+ validateTemplate
3613
+ };
3614
+ //# sourceMappingURL=index.js.map