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