@kubuild/core 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/document/document-utils.ts
2
- import { SCHEMA_NAME } from "@kubuild/schema";
2
+ import { SCHEMA_NAME, CURRENT_SCHEMA_VERSION } from "@kubuild/schema";
3
3
  function createBlankDocument(title = "Untitled Page") {
4
4
  return {
5
5
  schema: SCHEMA_NAME,
6
- version: "1.0.0",
6
+ version: CURRENT_SCHEMA_VERSION,
7
7
  metadata: {
8
8
  title,
9
9
  description: "",
@@ -316,26 +316,84 @@ function defaultIdGenerator(oldId, existingIds) {
316
316
  }
317
317
  return candidate;
318
318
  }
319
- function cloneTreeWithNewIds(root, idGenerator, existingIds) {
319
+ var NODE_ID_REFERENCE_KEYS = /* @__PURE__ */ new Set([
320
+ "modalId",
321
+ "modalNodeId",
322
+ "targetModalId",
323
+ "targetNodeId",
324
+ "nodeId",
325
+ "formId",
326
+ "targetId"
327
+ ]);
328
+ var ANCHOR_REFERENCE_KEYS = /* @__PURE__ */ new Set(["url", "href"]);
329
+ function remapReferenceValue(key, value, idMap) {
330
+ if (typeof value !== "string") return value;
331
+ if (NODE_ID_REFERENCE_KEYS.has(key)) {
332
+ return idMap.get(value) ?? idMap.get(value.trim()) ?? value;
333
+ }
334
+ if (ANCHOR_REFERENCE_KEYS.has(key) && value.startsWith("#")) {
335
+ const mapped = idMap.get(value.slice(1));
336
+ return mapped ? `#${mapped}` : value;
337
+ }
338
+ return value;
339
+ }
340
+ function remapReferenceRecord(record, idMap) {
341
+ for (const [key, value] of Object.entries(record)) {
342
+ record[key] = remapReferenceValue(key, value, idMap);
343
+ }
344
+ }
345
+ function remapStepReferences(steps, idMap) {
346
+ if (!Array.isArray(steps)) return;
347
+ for (const step of steps) {
348
+ if (step.payload && typeof step.payload === "object") {
349
+ remapReferenceRecord(step.payload, idMap);
350
+ }
351
+ remapStepReferences(step.onSuccess, idMap);
352
+ remapStepReferences(step.onError, idMap);
353
+ }
354
+ }
355
+ function remapNodeReferences(node, idMap) {
356
+ if (node.props && typeof node.props === "object") {
357
+ remapReferenceRecord(node.props, idMap);
358
+ const legacyAction = node.props.action;
359
+ if (legacyAction && typeof legacyAction === "object" && legacyAction.payload && typeof legacyAction.payload === "object" && !Array.isArray(legacyAction.payload)) {
360
+ remapReferenceRecord(legacyAction.payload, idMap);
361
+ }
362
+ }
363
+ if (node.formConfig && typeof node.formConfig.formId === "string") {
364
+ node.formConfig.formId = idMap.get(node.formConfig.formId) ?? node.formConfig.formId;
365
+ }
366
+ if (Array.isArray(node.actions)) {
367
+ for (const pipeline of node.actions) {
368
+ remapStepReferences(pipeline.steps, idMap);
369
+ }
370
+ }
371
+ }
372
+ function cloneNodeTreeWithFreshIds(root, idGen) {
320
373
  const idMap = /* @__PURE__ */ new Map();
321
- const idGen = idGenerator || ((oldId) => defaultIdGenerator(oldId, existingIds));
374
+ const clonedNodes = [];
322
375
  function cloneRecursive(node) {
323
- const newId = idGen(node.id);
376
+ const newId = idGen(node.id, node);
324
377
  idMap.set(node.id, newId);
325
- const clonedProps = node.props ? deepClone(node.props) : void 0;
326
- const clonedStyles = node.styles ? deepClone(node.styles) : void 0;
327
- const clonedChildren = node.children ? node.children.map((child) => cloneRecursive(child)) : [];
328
- return {
378
+ const { children, ...rest } = node;
379
+ const cloned = {
380
+ ...deepClone(rest),
329
381
  id: newId,
330
- type: node.type,
331
- ...clonedProps ? { props: clonedProps } : {},
332
- ...clonedStyles ? { styles: clonedStyles } : {},
333
- children: clonedChildren
382
+ children: children ? children.map((child) => cloneRecursive(child)) : []
334
383
  };
384
+ clonedNodes.push(cloned);
385
+ return cloned;
335
386
  }
336
387
  const clonedNode = cloneRecursive(root);
388
+ for (const cloned of clonedNodes) {
389
+ remapNodeReferences(cloned, idMap);
390
+ }
337
391
  return { clonedNode, idMap };
338
392
  }
393
+ function cloneTreeWithNewIds(root, idGenerator, existingIds) {
394
+ const idGen = idGenerator || ((oldId) => defaultIdGenerator(oldId, existingIds));
395
+ return cloneNodeTreeWithFreshIds(root, (oldId) => idGen(oldId));
396
+ }
339
397
 
340
398
  // src/document/commands.ts
341
399
  import {
@@ -343,7 +401,9 @@ import {
343
401
  ResponsiveStylesSchema,
344
402
  AnimationConfigSchema,
345
403
  ActionPipelineSchema,
346
- FormConfigSchema
404
+ FormConfigSchema,
405
+ ThemeSchema,
406
+ THEME_TOKEN_GROUPS
347
407
  } from "@kubuild/schema";
348
408
  function insertNode(document, params) {
349
409
  const { parentId, node, index } = params;
@@ -883,13 +943,60 @@ function replaceNode(document, params) {
883
943
  }
884
944
  };
885
945
  }
946
+ function updateTheme(document, params) {
947
+ const { theme, merge = true } = params;
948
+ const newDoc = deepClone(document);
949
+ const previousTheme = newDoc.theme ? deepClone(newDoc.theme) : void 0;
950
+ if (theme === null) {
951
+ delete newDoc.theme;
952
+ } else {
953
+ const next = {};
954
+ for (const group of THEME_TOKEN_GROUPS) {
955
+ const base = merge ? { ...previousTheme?.[group] ?? {} } : {};
956
+ const patch = theme[group];
957
+ if (patch === null) {
958
+ continue;
959
+ }
960
+ if (patch) {
961
+ for (const [key, value] of Object.entries(patch)) {
962
+ if (value === null) {
963
+ delete base[key];
964
+ } else {
965
+ base[key] = value;
966
+ }
967
+ }
968
+ }
969
+ if (Object.keys(base).length > 0) {
970
+ next[group] = base;
971
+ }
972
+ }
973
+ const parsed = ThemeSchema.parse(next);
974
+ if (Object.keys(parsed).length > 0) {
975
+ newDoc.theme = parsed;
976
+ } else {
977
+ delete newDoc.theme;
978
+ }
979
+ }
980
+ return {
981
+ document: newDoc,
982
+ event: {
983
+ type: "THEME_UPDATED",
984
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
985
+ nodeId: newDoc.document.id,
986
+ payload: {
987
+ theme: newDoc.theme,
988
+ previousTheme
989
+ }
990
+ }
991
+ };
992
+ }
886
993
 
887
994
  // src/document/artboards.ts
888
995
  import {
889
996
  SCHEMA_NAME as SCHEMA_NAME2,
890
997
  PROJECT_SCHEMA_NAME,
891
998
  CURRENT_PROJECT_SCHEMA_VERSION,
892
- CURRENT_SCHEMA_VERSION,
999
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2,
893
1000
  ARTBOARD_REFERENCE_NODE_TYPE
894
1001
  } from "@kubuild/schema";
895
1002
  var now = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -947,14 +1054,14 @@ function createComponentArtboard(node, options = {}) {
947
1054
  const triggerId = options.triggerId ?? (typeof node.props?.modalId === "string" && node.props.modalId.trim().length > 0 ? node.props.modalId : node.id);
948
1055
  const document = {
949
1056
  schema: SCHEMA_NAME2,
950
- version: CURRENT_SCHEMA_VERSION,
1057
+ version: CURRENT_SCHEMA_VERSION2,
951
1058
  metadata: {
952
1059
  title: name,
953
1060
  description: "",
954
1061
  author: "",
955
1062
  tags: [],
956
1063
  category: "component",
957
- version: CURRENT_SCHEMA_VERSION,
1064
+ version: CURRENT_SCHEMA_VERSION2,
958
1065
  createdAt: now(),
959
1066
  updatedAt: now()
960
1067
  },
@@ -1411,6 +1518,15 @@ var DocumentHistoryManager = class {
1411
1518
  var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1412
1519
  function resolveBinding(binding, context) {
1413
1520
  const segments = binding.key.split(".").filter(Boolean);
1521
+ if (segments.some((segment) => FORBIDDEN_KEY_SEGMENTS.has(segment))) {
1522
+ return applyMissingPolicy(binding);
1523
+ }
1524
+ if (context?.variables && typeof context.variables === "object" && Object.prototype.hasOwnProperty.call(context.variables, binding.key)) {
1525
+ const directValue = context.variables[binding.key];
1526
+ if (directValue !== void 0 && typeof directValue !== "function") {
1527
+ return { status: "resolved", value: directValue };
1528
+ }
1529
+ }
1414
1530
  let current = context?.variables;
1415
1531
  for (const segment of segments) {
1416
1532
  if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
@@ -1982,14 +2098,14 @@ var ActionPipelineExecutor = class {
1982
2098
  async executeSingleStep(step, context, parentSignal, options) {
1983
2099
  const stepStartTime = Date.now();
1984
2100
  if (step.condition && !evaluateActionCondition(step.condition, context)) {
1985
- const skippedResult = {
2101
+ const skippedResult2 = {
1986
2102
  stepId: step.id,
1987
2103
  stepType: step.type,
1988
2104
  status: "skipped",
1989
2105
  durationMs: Date.now() - stepStartTime
1990
2106
  };
1991
- options?.onStepComplete?.(step, skippedResult, context);
1992
- return skippedResult;
2107
+ options?.onStepComplete?.(step, skippedResult2, context);
2108
+ return skippedResult2;
1993
2109
  }
1994
2110
  options?.onStepStart?.(step, context);
1995
2111
  const stepTimeout = step.timeout;
@@ -2481,7 +2597,7 @@ function generateTrackingEventId(prefix = "evt") {
2481
2597
  const randomPart = Math.random().toString(36).substring(2, 10);
2482
2598
  return `${prefix}_${timestamp}_${randomPart}`;
2483
2599
  }
2484
- async function sendMetaCapiEvent(event, config, options) {
2600
+ async function sendMetaCapiEvent(event, config, secrets, options) {
2485
2601
  const fetcher = options?.fetchFn || globalThis.fetch;
2486
2602
  if (!config.pixelId) {
2487
2603
  return {
@@ -2555,9 +2671,10 @@ async function sendMetaCapiEvent(event, config, options) {
2555
2671
  const headers = {
2556
2672
  "Content-Type": "application/json"
2557
2673
  };
2558
- if (config.capiAccessToken) {
2559
- headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2674
+ if (!secrets?.capiAccessToken) {
2675
+ return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
2560
2676
  }
2677
+ headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
2561
2678
  const res = await fetcher(url, {
2562
2679
  method: "POST",
2563
2680
  headers,
@@ -2587,7 +2704,7 @@ async function sendMetaCapiEvent(event, config, options) {
2587
2704
  };
2588
2705
  }
2589
2706
  }
2590
- async function sendTikTokEventsApi(event, config, options) {
2707
+ async function sendTikTokEventsApi(event, config, secrets, options) {
2591
2708
  const fetcher = options?.fetchFn || globalThis.fetch;
2592
2709
  if (!config.pixelId) {
2593
2710
  return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
@@ -2637,9 +2754,10 @@ async function sendTikTokEventsApi(event, config, options) {
2637
2754
  const headers = {
2638
2755
  "Content-Type": "application/json"
2639
2756
  };
2640
- if (config.accessToken) {
2641
- headers["Access-Token"] = config.accessToken;
2757
+ if (!secrets?.accessToken) {
2758
+ return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
2642
2759
  }
2760
+ headers["Access-Token"] = secrets.accessToken;
2643
2761
  const res = await fetcher(url, {
2644
2762
  method: "POST",
2645
2763
  headers,
@@ -2664,7 +2782,7 @@ async function sendTikTokEventsApi(event, config, options) {
2664
2782
  };
2665
2783
  }
2666
2784
  }
2667
- async function sendGa4MeasurementEvent(event, config, options) {
2785
+ async function sendGa4MeasurementEvent(event, config, secrets, options) {
2668
2786
  const fetcher = options?.fetchFn || globalThis.fetch;
2669
2787
  if (!config.measurementId) {
2670
2788
  return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
@@ -2688,12 +2806,10 @@ async function sendGa4MeasurementEvent(event, config, options) {
2688
2806
  return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2689
2807
  }
2690
2808
  try {
2691
- let url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(
2692
- config.measurementId
2693
- )}`;
2694
- if (config.measurementProtocolSecret) {
2695
- url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
2809
+ if (!secrets?.measurementProtocolSecret) {
2810
+ return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
2696
2811
  }
2812
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
2697
2813
  const res = await fetcher(url, {
2698
2814
  method: "POST",
2699
2815
  headers: { "Content-Type": "application/json" },
@@ -2713,11 +2829,8 @@ async function sendGa4MeasurementEvent(event, config, options) {
2713
2829
  };
2714
2830
  }
2715
2831
  }
2716
- async function sendCustomWebhookEvent(event, config, options) {
2832
+ async function sendCustomWebhookEvent(event, _config, secrets, options) {
2717
2833
  const fetcher = options?.fetchFn || globalThis.fetch;
2718
- if (!config.endpointUrl) {
2719
- return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2720
- }
2721
2834
  const webhookPayload = {
2722
2835
  event: event.eventName,
2723
2836
  eventId: event.eventId,
@@ -2731,12 +2844,15 @@ async function sendCustomWebhookEvent(event, config, options) {
2731
2844
  options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2732
2845
  return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2733
2846
  }
2847
+ if (!secrets?.endpointUrl) {
2848
+ return skippedResult("custom", "No custom webhook destination resolved for this credential");
2849
+ }
2734
2850
  try {
2735
- const res = await fetcher(config.endpointUrl, {
2851
+ const res = await fetcher(secrets.endpointUrl, {
2736
2852
  method: "POST",
2737
2853
  headers: {
2738
2854
  "Content-Type": "application/json",
2739
- ...config.headers || {}
2855
+ ...secrets.headers || {}
2740
2856
  },
2741
2857
  body: JSON.stringify(webhookPayload)
2742
2858
  });
@@ -2755,6 +2871,9 @@ async function sendCustomWebhookEvent(event, config, options) {
2755
2871
  };
2756
2872
  }
2757
2873
  }
2874
+ function skippedResult(provider, reason) {
2875
+ return { provider, success: true, skipped: true, reason };
2876
+ }
2758
2877
  async function dispatchServerTracking(event, config, options) {
2759
2878
  const eventId = event.eventId || generateTrackingEventId();
2760
2879
  const eventWithId = { ...event, eventId };
@@ -2779,65 +2898,233 @@ async function dispatchServerTracking(event, config, options) {
2779
2898
  options?.onLog?.(msg, data);
2780
2899
  }
2781
2900
  };
2901
+ const target = options?.provider || "all";
2902
+ const wants = (key) => target === "all" || target === key;
2782
2903
  const providers = config.providers || {};
2783
- const dispatchPromises = [];
2784
- if (providers.meta && providers.meta.enabled !== false && providers.meta.capiEnabled) {
2785
- dispatchPromises.push(
2786
- sendMetaCapiEvent(eventWithId, providers.meta, mergedOptions).then((result) => ({
2787
- key: "meta",
2788
- result
2789
- }))
2790
- );
2904
+ const tasks = [];
2905
+ const meta = providers.meta;
2906
+ if (wants("meta") && meta && meta.enabled !== false && meta.capiEnabled) {
2907
+ tasks.push({
2908
+ key: "meta",
2909
+ credentialId: meta.credentialId,
2910
+ run: (s) => sendMetaCapiEvent(eventWithId, meta, s, mergedOptions)
2911
+ });
2791
2912
  }
2792
- if (providers.tiktok && providers.tiktok.enabled !== false && providers.tiktok.eventsApiEnabled) {
2793
- dispatchPromises.push(
2794
- sendTikTokEventsApi(eventWithId, providers.tiktok, mergedOptions).then((result) => ({
2795
- key: "tiktok",
2796
- result
2797
- }))
2798
- );
2913
+ const tiktok = providers.tiktok;
2914
+ if (wants("tiktok") && tiktok && tiktok.enabled !== false && tiktok.eventsApiEnabled) {
2915
+ tasks.push({
2916
+ key: "tiktok",
2917
+ credentialId: tiktok.credentialId,
2918
+ run: (s) => sendTikTokEventsApi(eventWithId, tiktok, s, mergedOptions)
2919
+ });
2799
2920
  }
2800
- if (providers.google && providers.google.enabled !== false && providers.google.measurementId && providers.google.measurementProtocolSecret) {
2801
- dispatchPromises.push(
2802
- sendGa4MeasurementEvent(eventWithId, providers.google, mergedOptions).then((result) => ({
2803
- key: "google",
2804
- result
2805
- }))
2806
- );
2921
+ const google = providers.google;
2922
+ if (wants("google") && google && google.enabled !== false && google.measurementId) {
2923
+ tasks.push({
2924
+ key: "google",
2925
+ credentialId: google.credentialId,
2926
+ run: (s) => sendGa4MeasurementEvent(eventWithId, google, s, mergedOptions)
2927
+ });
2807
2928
  }
2808
- if (providers.custom && providers.custom.enabled !== false && providers.custom.endpointUrl) {
2809
- dispatchPromises.push(
2810
- sendCustomWebhookEvent(eventWithId, providers.custom, mergedOptions).then((result) => ({
2811
- key: "custom",
2812
- result
2813
- }))
2814
- );
2929
+ const custom = providers.custom;
2930
+ if (wants("custom") && custom && custom.enabled !== false) {
2931
+ tasks.push({
2932
+ key: "custom",
2933
+ credentialId: custom.credentialId,
2934
+ run: (s) => sendCustomWebhookEvent(eventWithId, custom, s, mergedOptions)
2935
+ });
2815
2936
  }
2816
- const settled = await Promise.allSettled(dispatchPromises);
2937
+ if (tasks.length === 0) {
2938
+ return {
2939
+ success: true,
2940
+ eventId,
2941
+ skipped: true,
2942
+ reason: target === "gtm" ? "GTM is client-side only (dataLayer); no server delivery" : "No server-side tracking provider is enabled",
2943
+ results: {}
2944
+ };
2945
+ }
2946
+ const resolve = options?.resolveSecrets;
2947
+ const runTask = async (task) => {
2948
+ let secrets = null;
2949
+ if (resolve) {
2950
+ try {
2951
+ secrets = await resolve({ provider: task.key, credentialId: task.credentialId, documentId: options?.documentId }) ?? null;
2952
+ } catch (err) {
2953
+ return {
2954
+ provider: task.key,
2955
+ success: false,
2956
+ error: `Secret resolution failed: ${err instanceof Error ? err.message : String(err)}`
2957
+ };
2958
+ }
2959
+ }
2960
+ if (!secrets && !simulate) {
2961
+ const reason = resolve ? `No secret resolved for provider "${task.key}"${task.credentialId ? ` (credentialId "${task.credentialId}")` : " (no credentialId linked)"}` : "No secret resolver configured (options.resolveSecrets)";
2962
+ mergedOptions.onLog?.(`[${task.key}] skipped: ${reason}`);
2963
+ return skippedResult(task.key, reason);
2964
+ }
2965
+ return task.run(secrets);
2966
+ };
2967
+ const settled = await Promise.allSettled(tasks.map((task) => runTask(task)));
2817
2968
  const results = {};
2818
2969
  let overallSuccess = true;
2819
- for (const item of settled) {
2970
+ let delivered = 0;
2971
+ settled.forEach((item, index) => {
2972
+ const key = tasks[index].key;
2820
2973
  if (item.status === "fulfilled") {
2821
- results[item.value.key] = item.value.result;
2822
- if (!item.value.result.success) {
2823
- overallSuccess = false;
2824
- }
2974
+ results[key] = item.value;
2975
+ if (!item.value.success) overallSuccess = false;
2976
+ if (!item.value.skipped) delivered++;
2825
2977
  } else {
2826
2978
  overallSuccess = false;
2979
+ results[key] = {
2980
+ provider: key,
2981
+ success: false,
2982
+ error: item.reason instanceof Error ? item.reason.message : String(item.reason)
2983
+ };
2827
2984
  }
2828
- }
2985
+ });
2986
+ const allSkipped = delivered === 0 && overallSuccess;
2829
2987
  return {
2830
2988
  success: overallSuccess,
2831
2989
  eventId,
2990
+ ...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
2832
2991
  results
2833
2992
  };
2834
2993
  }
2835
2994
 
2995
+ // src/runtime/tracking-relay.ts
2996
+ import {
2997
+ TRACKING_RELAY_PROTOCOL_VERSION,
2998
+ TrackingRelayRequestSchema
2999
+ } from "@kubuild/schema";
3000
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
3001
+ function defaultClientIp(request) {
3002
+ const forwarded = request.headers.get("x-forwarded-for");
3003
+ if (forwarded) {
3004
+ const first = forwarded.split(",")[0]?.trim();
3005
+ if (first) return first;
3006
+ }
3007
+ return request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
3008
+ }
3009
+ function isOriginAllowed(origin, allowed) {
3010
+ if (!allowed) return true;
3011
+ if (!origin) return false;
3012
+ if (typeof allowed === "function") return allowed(origin);
3013
+ return allowed.includes(origin);
3014
+ }
3015
+ function createTrackingRelayHandler(options) {
3016
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
3017
+ return async (request) => {
3018
+ const origin = request.headers.get("origin");
3019
+ const corsHeaders = {};
3020
+ if (options.allowedOrigins && origin && isOriginAllowed(origin, options.allowedOrigins)) {
3021
+ corsHeaders["Access-Control-Allow-Origin"] = origin;
3022
+ corsHeaders["Vary"] = "Origin";
3023
+ }
3024
+ const json = (body, status, extra) => new Response(JSON.stringify(body), {
3025
+ status,
3026
+ headers: { "Content-Type": "application/json", ...corsHeaders, ...extra || {} }
3027
+ });
3028
+ const fail = (code, message, status, extra) => json({ version: TRACKING_RELAY_PROTOCOL_VERSION, success: false, error: { code, message } }, status, extra);
3029
+ if (!isOriginAllowed(origin, options.allowedOrigins)) {
3030
+ return fail("FORBIDDEN_ORIGIN", "Origin is not allowed", 403);
3031
+ }
3032
+ if (request.method === "OPTIONS") {
3033
+ return new Response(null, {
3034
+ status: 204,
3035
+ headers: {
3036
+ ...corsHeaders,
3037
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
3038
+ "Access-Control-Allow-Headers": "Content-Type",
3039
+ "Access-Control-Max-Age": "600"
3040
+ }
3041
+ });
3042
+ }
3043
+ if (request.method !== "POST") {
3044
+ return fail("METHOD_NOT_ALLOWED", "Only POST requests are accepted", 405, { Allow: "POST, OPTIONS" });
3045
+ }
3046
+ try {
3047
+ const text = await request.text();
3048
+ if (text.length > maxBodyBytes) {
3049
+ return fail("INVALID_REQUEST", `Request body exceeds ${maxBodyBytes} bytes`, 413);
3050
+ }
3051
+ let raw;
3052
+ try {
3053
+ raw = JSON.parse(text);
3054
+ } catch {
3055
+ return fail("INVALID_REQUEST", "Request body must be valid JSON", 400);
3056
+ }
3057
+ if (raw && typeof raw === "object" && "version" in raw && raw.version !== TRACKING_RELAY_PROTOCOL_VERSION) {
3058
+ return fail(
3059
+ "UNSUPPORTED_VERSION",
3060
+ `Unsupported relay protocol version; expected ${TRACKING_RELAY_PROTOCOL_VERSION}`,
3061
+ 400
3062
+ );
3063
+ }
3064
+ const parsed = TrackingRelayRequestSchema.safeParse(raw);
3065
+ if (!parsed.success) {
3066
+ const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
3067
+ return fail("INVALID_REQUEST", `Invalid relay request: ${issues}`, 400);
3068
+ }
3069
+ const body = parsed.data;
3070
+ const config = await options.getConfig({
3071
+ request,
3072
+ documentId: body.documentId,
3073
+ credentialId: body.credentialId,
3074
+ provider: body.provider
3075
+ });
3076
+ if (!config) {
3077
+ return fail("CONFIG_NOT_FOUND", "No tracking configuration found for this page", 404);
3078
+ }
3079
+ const clientIp = (options.getClientIp ?? defaultClientIp)(request);
3080
+ const clientUserAgent = request.headers.get("user-agent") || void 0;
3081
+ const userData = { ...body.event.userData || {} };
3082
+ delete userData.clientIp;
3083
+ delete userData.clientUserAgent;
3084
+ const event = { ...body.event, userData };
3085
+ const outcome = await dispatchServerTracking(event, config, {
3086
+ resolveSecrets: options.resolveSecrets,
3087
+ documentId: body.documentId,
3088
+ provider: body.provider,
3089
+ fetchFn: options.fetchFn,
3090
+ clientIp,
3091
+ clientUserAgent,
3092
+ onLog: options.onLog
3093
+ });
3094
+ const results = {};
3095
+ for (const [key, r] of Object.entries(outcome.results)) {
3096
+ results[key] = {
3097
+ provider: r.provider,
3098
+ success: r.success,
3099
+ ...r.status !== void 0 ? { status: r.status } : {},
3100
+ ...r.skipped ? { skipped: true } : {},
3101
+ ...r.reason ? { reason: r.reason } : {},
3102
+ ...r.error ? { error: r.error } : {}
3103
+ };
3104
+ }
3105
+ return json(
3106
+ {
3107
+ version: TRACKING_RELAY_PROTOCOL_VERSION,
3108
+ success: outcome.success,
3109
+ eventId: outcome.eventId,
3110
+ ...outcome.skipped ? { skipped: true } : {},
3111
+ ...outcome.reason ? { reason: outcome.reason } : {},
3112
+ results
3113
+ },
3114
+ 200
3115
+ );
3116
+ } catch (err) {
3117
+ options.onLog?.("[Tracking relay] internal error", err);
3118
+ return fail("INTERNAL_ERROR", "Tracking relay failed", 500);
3119
+ }
3120
+ };
3121
+ }
3122
+
2836
3123
  // src/io/exporter.ts
2837
3124
  import { zipSync, strToU8 } from "fflate";
2838
3125
  import {
2839
3126
  SCHEMA_NAME as SCHEMA_NAME4,
2840
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2
3127
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
2841
3128
  } from "@kubuild/schema";
2842
3129
 
2843
3130
  // src/validation/validator.ts
@@ -3380,7 +3667,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3380
3667
  `${currentPath}/props`,
3381
3668
  effectiveNodeId,
3382
3669
  options,
3383
- errors
3670
+ errors,
3671
+ warnings
3384
3672
  );
3385
3673
  }
3386
3674
  }
@@ -3395,7 +3683,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3395
3683
  }
3396
3684
  }
3397
3685
  }
3398
- function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3686
+ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors, warnings) {
3687
+ const checkAssets = options.checkAssetReferences !== false;
3688
+ const checkVariables = options.checkVariableBindings !== false;
3689
+ const checkActions = options.checkActionBindings !== false;
3399
3690
  for (const [key, value] of Object.entries(propsObj)) {
3400
3691
  const currentPath = `${propsPath}/${key}`;
3401
3692
  if (!value || typeof value !== "object") {
@@ -3409,7 +3700,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3409
3700
  `${currentPath}/${index}`,
3410
3701
  nodeId,
3411
3702
  options,
3412
- errors
3703
+ errors,
3704
+ warnings
3413
3705
  );
3414
3706
  }
3415
3707
  });
@@ -3417,7 +3709,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3417
3709
  }
3418
3710
  const record = value;
3419
3711
  if (record.type === "asset") {
3420
- if (typeof record.assetId !== "string" || record.assetId.trim().length === 0) {
3712
+ if (!checkAssets) continue;
3713
+ const assetId = typeof record.assetId === "string" && record.assetId.trim().length > 0 ? record.assetId : void 0;
3714
+ if (assetId === void 0) {
3421
3715
  errors.push({
3422
3716
  code: "INVALID_ASSET_REFERENCE",
3423
3717
  message: 'Asset reference must have a non-empty "assetId"',
@@ -3433,7 +3727,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3433
3727
  nodeId
3434
3728
  });
3435
3729
  }
3730
+ if (assetId !== void 0 && options.knownAssetIds && !isKnownId(options.knownAssetIds, assetId)) {
3731
+ if (typeof record.fallbackUrl === "string" && record.fallbackUrl.length > 0) {
3732
+ warnings.push({
3733
+ code: "UNRESOLVED_ASSET_REFERENCE",
3734
+ message: `Asset "${assetId}" is not among the known assets; its fallbackUrl will be used`,
3735
+ path: `${currentPath}/assetId`,
3736
+ nodeId,
3737
+ details: { assetId }
3738
+ });
3739
+ } else {
3740
+ errors.push({
3741
+ code: "INVALID_ASSET_REFERENCE",
3742
+ message: `Asset "${assetId}" is not among the known assets and has no fallbackUrl`,
3743
+ path: `${currentPath}/assetId`,
3744
+ nodeId,
3745
+ details: { assetId }
3746
+ });
3747
+ }
3748
+ }
3436
3749
  } else if (record.type === "variable") {
3750
+ if (!checkVariables) continue;
3437
3751
  if (typeof record.key !== "string" || record.key.trim().length === 0) {
3438
3752
  errors.push({
3439
3753
  code: "INVALID_VARIABLE_BINDING",
@@ -3443,6 +3757,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3443
3757
  });
3444
3758
  }
3445
3759
  } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
3760
+ if (!checkActions) continue;
3446
3761
  if (typeof record.type !== "string" || record.type.trim().length === 0) {
3447
3762
  errors.push({
3448
3763
  code: "INVALID_ACTION_BINDING",
@@ -3452,10 +3767,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3452
3767
  });
3453
3768
  }
3454
3769
  } else {
3455
- validatePropsBindings(record, currentPath, nodeId, options, errors);
3770
+ validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
3456
3771
  }
3457
3772
  }
3458
3773
  }
3774
+ function isKnownId(ids, id) {
3775
+ return Array.isArray(ids) ? ids.includes(id) : ids.has(id);
3776
+ }
3777
+
3778
+ // src/io/tracking-sanitizer.ts
3779
+ import { LEGACY_TRACKING_SECRET_KEYS } from "@kubuild/schema";
3780
+ function stripTrackingSecretsInPlace(tracking, basePath = "tracking") {
3781
+ const removed = [];
3782
+ if (!tracking || typeof tracking !== "object" || Array.isArray(tracking)) return removed;
3783
+ const providers = tracking.providers;
3784
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) return removed;
3785
+ for (const provider of Object.keys(LEGACY_TRACKING_SECRET_KEYS)) {
3786
+ const cfg = providers[provider];
3787
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) continue;
3788
+ for (const key of LEGACY_TRACKING_SECRET_KEYS[provider]) {
3789
+ if (Object.prototype.hasOwnProperty.call(cfg, key)) {
3790
+ delete cfg[key];
3791
+ removed.push(`${basePath}.providers.${provider}.${key}`);
3792
+ }
3793
+ }
3794
+ }
3795
+ return removed;
3796
+ }
3797
+ function stripDocumentTrackingSecretsInPlace(doc) {
3798
+ const removed = [];
3799
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return removed;
3800
+ const record = doc;
3801
+ removed.push(...stripTrackingSecretsInPlace(record.tracking, "tracking"));
3802
+ const metadata = record.metadata;
3803
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
3804
+ removed.push(
3805
+ ...stripTrackingSecretsInPlace(metadata.tracking, "metadata.tracking")
3806
+ );
3807
+ }
3808
+ if (Array.isArray(record.artboards)) {
3809
+ record.artboards.forEach((artboard, index) => {
3810
+ if (artboard && typeof artboard === "object") {
3811
+ const inner = artboard.document;
3812
+ for (const path of stripDocumentTrackingSecretsInPlace(inner)) {
3813
+ removed.push(`artboards.${index}.document.${path}`);
3814
+ }
3815
+ }
3816
+ });
3817
+ }
3818
+ return removed;
3819
+ }
3820
+ function sanitizeDocumentTracking(doc) {
3821
+ if (!doc || typeof doc !== "object") return { document: doc, removed: [] };
3822
+ const copy = JSON.parse(JSON.stringify(doc));
3823
+ const removed = stripDocumentTrackingSecretsInPlace(copy);
3824
+ return { document: copy, removed };
3825
+ }
3459
3826
 
3460
3827
  // src/io/exporter.ts
3461
3828
  function sha256Sync(data) {
@@ -3676,6 +4043,7 @@ async function exportPackage(document, options = {}) {
3676
4043
  };
3677
4044
  }
3678
4045
  const pageDoc = JSON.parse(JSON.stringify(validation.data));
4046
+ stripDocumentTrackingSecretsInPlace(pageDoc);
3679
4047
  if (options.metadata) {
3680
4048
  pageDoc.metadata = {
3681
4049
  ...pageDoc.metadata || {
@@ -3800,7 +4168,7 @@ async function exportPackage(document, options = {}) {
3800
4168
  };
3801
4169
  const manifest = {
3802
4170
  schema: SCHEMA_NAME4,
3803
- schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION2,
4171
+ schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION3,
3804
4172
  packageVersion: options.packageVersion || "1.0.0",
3805
4173
  builderCompatibility: options.builderCompatibility || ">=0.1.0",
3806
4174
  requiredComponents: requirements.requiredComponents,
@@ -3827,13 +4195,14 @@ var exportStoraPackage = exportPackage;
3827
4195
  import { unzipSync, strFromU8 } from "fflate";
3828
4196
  import {
3829
4197
  ManifestSchema,
3830
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4
4198
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION5
3831
4199
  } from "@kubuild/schema";
3832
4200
 
3833
4201
  // src/io/migration.ts
3834
4202
  import {
3835
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3,
3836
- SCHEMA_NAME as SCHEMA_NAME5
4203
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4,
4204
+ SCHEMA_NAME as SCHEMA_NAME5,
4205
+ getCanonicalTextPropRule
3837
4206
  } from "@kubuild/schema";
3838
4207
  var MigrationRegistry = class {
3839
4208
  steps = /* @__PURE__ */ new Map();
@@ -3987,17 +4356,86 @@ defaultMigrationRegistry.register({
3987
4356
  return migrated;
3988
4357
  }
3989
4358
  });
3990
- function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4359
+ defaultMigrationRegistry.register({
4360
+ fromVersion: "1.0.0",
4361
+ toVersion: "1.1.0",
4362
+ description: "Remove tracking secrets and server destinations (capiAccessToken, accessToken, measurementProtocolSecret, serverRelayUrl, custom endpointUrl/headers) from the document",
4363
+ migrate: (rawDoc, context) => {
4364
+ const migrated = deepClone(rawDoc);
4365
+ migrated.version = "1.1.0";
4366
+ const removed = stripDocumentTrackingSecretsInPlace(migrated);
4367
+ if (removed.length > 0) {
4368
+ context.warn({
4369
+ code: "TRACKING_SECRET_REMOVED",
4370
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are now stored by the host and resolved server-side).",
4371
+ step: "1.0.0->1.1.0",
4372
+ paths: removed
4373
+ });
4374
+ }
4375
+ return migrated;
4376
+ }
4377
+ });
4378
+ function canonicalizeTextPropsInPlace(root, basePath = "document") {
4379
+ const changed = [];
4380
+ const walk2 = (value, path) => {
4381
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
4382
+ const node = value;
4383
+ const props = node.props;
4384
+ const rule = typeof node.type === "string" ? getCanonicalTextPropRule(node.type) : void 0;
4385
+ if (rule && props && typeof props === "object" && !Array.isArray(props)) {
4386
+ const record = props;
4387
+ const presentAliases = rule.aliases.filter((alias) => record[alias] !== void 0);
4388
+ if (presentAliases.length > 0) {
4389
+ const displayedKey = rule.legacyReadOrder.find((key) => record[key] !== void 0);
4390
+ if (node.type === "text" && displayedKey === "content" && record.as === void 0 && record.tag === void 0) {
4391
+ record.as = "p";
4392
+ }
4393
+ if (displayedKey !== void 0 && displayedKey !== rule.canonical) {
4394
+ record[rule.canonical] = record[displayedKey];
4395
+ }
4396
+ for (const alias of presentAliases) {
4397
+ delete record[alias];
4398
+ changed.push(`${path}.props.${alias}`);
4399
+ }
4400
+ }
4401
+ }
4402
+ if (Array.isArray(node.children)) {
4403
+ node.children.forEach((child, index) => walk2(child, `${path}.children.${index}`));
4404
+ }
4405
+ };
4406
+ walk2(root, basePath);
4407
+ return changed;
4408
+ }
4409
+ defaultMigrationRegistry.register({
4410
+ fromVersion: "1.1.0",
4411
+ toVersion: "1.2.0",
4412
+ description: "Rename deprecated text prop aliases to canonical names (heading/text/paragraph/link/badge/blockquote -> text, button -> label)",
4413
+ migrate: (rawDoc, context) => {
4414
+ const migrated = deepClone(rawDoc);
4415
+ migrated.version = "1.2.0";
4416
+ const changed = canonicalizeTextPropsInPlace(migrated.document);
4417
+ if (changed.length > 0) {
4418
+ context.warn({
4419
+ code: "PROP_ALIAS_MIGRATED",
4420
+ message: "Deprecated text prop aliases were renamed to their canonical names.",
4421
+ step: "1.1.0->1.2.0",
4422
+ paths: changed
4423
+ });
4424
+ }
4425
+ return migrated;
4426
+ }
4427
+ });
4428
+ function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3991
4429
  if (!sourceVersion || !targetVersion) return false;
3992
4430
  if (sourceVersion === targetVersion) return true;
3993
4431
  return registry.hasPath(sourceVersion, targetVersion);
3994
4432
  }
3995
- function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4433
+ function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3996
4434
  if (!sourceVersion || !targetVersion) return null;
3997
4435
  return registry.findPath(sourceVersion, targetVersion);
3998
4436
  }
3999
4437
  function migrateDocument(rawDocument, options = {}) {
4000
- const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION3;
4438
+ const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION4;
4001
4439
  const dryRun = options.dryRun ?? false;
4002
4440
  const registry = options.registry ?? defaultMigrationRegistry;
4003
4441
  const validate = options.validate ?? true;
@@ -4025,6 +4463,14 @@ function migrateDocument(rawDocument, options = {}) {
4025
4463
  const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
4026
4464
  if (sourceVersion === targetVersion) {
4027
4465
  const cloned = deepClone(doc);
4466
+ const removedSecrets = stripDocumentTrackingSecretsInPlace(cloned);
4467
+ const currentWarnings = removedSecrets.length > 0 ? [
4468
+ {
4469
+ code: "TRACKING_SECRET_REMOVED",
4470
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are stored by the host and resolved server-side).",
4471
+ paths: removedSecrets
4472
+ }
4473
+ ] : [];
4028
4474
  if (validate) {
4029
4475
  const validation = validateDocument(cloned);
4030
4476
  if (!validation.valid) {
@@ -4056,7 +4502,8 @@ function migrateDocument(rawDocument, options = {}) {
4056
4502
  targetVersion,
4057
4503
  migrationPath: [sourceVersion],
4058
4504
  stepsApplied: 0,
4059
- dryRun
4505
+ dryRun,
4506
+ ...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
4060
4507
  }
4061
4508
  };
4062
4509
  }
@@ -4082,6 +4529,16 @@ function migrateDocument(rawDocument, options = {}) {
4082
4529
  };
4083
4530
  }
4084
4531
  if (dryRun) {
4532
+ const simulatedWarnings = [];
4533
+ try {
4534
+ let simulated = deepClone(doc);
4535
+ for (let i = 0; i < path.length - 1; i++) {
4536
+ const step = registry.getStep(path[i], path[i + 1]);
4537
+ if (!step) break;
4538
+ simulated = step.migrate(simulated, { warn: (w) => simulatedWarnings.push(w) });
4539
+ }
4540
+ } catch {
4541
+ }
4085
4542
  return {
4086
4543
  success: true,
4087
4544
  diagnostic: {
@@ -4090,12 +4547,15 @@ function migrateDocument(rawDocument, options = {}) {
4090
4547
  targetVersion,
4091
4548
  migrationPath: path,
4092
4549
  stepsApplied: path.length - 1,
4093
- dryRun: true
4550
+ dryRun: true,
4551
+ ...simulatedWarnings.length > 0 ? { warnings: simulatedWarnings } : {}
4094
4552
  }
4095
4553
  };
4096
4554
  }
4097
4555
  let currentDoc = deepClone(doc);
4098
4556
  let stepsApplied = 0;
4557
+ const warnings = [];
4558
+ const stepContext = { warn: (w) => warnings.push(w) };
4099
4559
  for (let i = 0; i < path.length - 1; i++) {
4100
4560
  const fromVer = path[i];
4101
4561
  const toVer = path[i + 1];
@@ -4121,7 +4581,7 @@ function migrateDocument(rawDocument, options = {}) {
4121
4581
  };
4122
4582
  }
4123
4583
  try {
4124
- currentDoc = step.migrate(currentDoc);
4584
+ currentDoc = step.migrate(currentDoc, stepContext);
4125
4585
  stepsApplied++;
4126
4586
  } catch (err) {
4127
4587
  const error = {
@@ -4177,7 +4637,8 @@ function migrateDocument(rawDocument, options = {}) {
4177
4637
  targetVersion,
4178
4638
  migrationPath: path,
4179
4639
  stepsApplied,
4180
- dryRun: false
4640
+ dryRun: false,
4641
+ ...warnings.length > 0 ? { warnings } : {}
4181
4642
  }
4182
4643
  };
4183
4644
  }
@@ -4212,7 +4673,7 @@ async function preflightPackage(archiveData, options = {}) {
4212
4673
  ...DEFAULT_SECURITY_LIMITS,
4213
4674
  ...options.securityLimits
4214
4675
  };
4215
- const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4;
4676
+ const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5;
4216
4677
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4217
4678
  const diagnostics = [];
4218
4679
  let dependencyPolicy = options.dependencyPolicy || "cancel";
@@ -4375,6 +4836,16 @@ async function preflightPackage(archiveData, options = {}) {
4375
4836
  let rawPageDoc;
4376
4837
  try {
4377
4838
  rawPageDoc = JSON.parse(strFromU8(pageEntry));
4839
+ const secretScan = sanitizeDocumentTracking(rawPageDoc);
4840
+ if (secretScan.removed.length > 0) {
4841
+ diagnostics.push({
4842
+ code: "TRACKING_SECRET_REMOVED",
4843
+ severity: "warning",
4844
+ message: "Tracking secrets found in page.json will be removed on import; re-link credentials via credentialId.",
4845
+ path: "page.json",
4846
+ details: { paths: secretScan.removed }
4847
+ });
4848
+ }
4378
4849
  const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
4379
4850
  if (pageProtoCheck.found) {
4380
4851
  diagnostics.push({
@@ -4621,7 +5092,7 @@ function buildReport(valid, canImport, fields) {
4621
5092
  return {
4622
5093
  valid,
4623
5094
  canImport,
4624
- targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION4,
5095
+ targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION5,
4625
5096
  requiresMigration: fields.requiresMigration || false,
4626
5097
  missingComponents: fields.missingComponents || [],
4627
5098
  missingCapabilities: fields.missingCapabilities || [],
@@ -4694,7 +5165,7 @@ async function importPackage(archiveData, options = {}) {
4694
5165
  if (preflight.requiresMigration) {
4695
5166
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4696
5167
  const migrationRes = migrateDocument(pageRaw, {
4697
- targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4,
5168
+ targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5,
4698
5169
  registry: migrationRegistry,
4699
5170
  validate: true
4700
5171
  });
@@ -4716,13 +5187,24 @@ async function importPackage(archiveData, options = {}) {
4716
5187
  } else {
4717
5188
  finalDocument = pageRaw;
4718
5189
  }
5190
+ const importWarnings = [];
5191
+ const strippedSecrets = stripDocumentTrackingSecretsInPlace(finalDocument);
5192
+ if (strippedSecrets.length > 0) {
5193
+ importWarnings.push({
5194
+ code: "TRACKING_SECRET_REMOVED",
5195
+ severity: "warning",
5196
+ message: "Tracking secret removed from imported document; re-link credential via credentialId.",
5197
+ path: "page.json",
5198
+ details: { paths: strippedSecrets }
5199
+ });
5200
+ }
4719
5201
  let metadata = finalDocument.metadata || {
4720
5202
  title: "Imported Page",
4721
5203
  description: "",
4722
5204
  author: "",
4723
5205
  tags: [],
4724
5206
  category: "general",
4725
- version: finalDocument.version || CURRENT_SCHEMA_VERSION4
5207
+ version: finalDocument.version || CURRENT_SCHEMA_VERSION5
4726
5208
  };
4727
5209
  if (unzipped["metadata.json"]) {
4728
5210
  try {
@@ -4874,7 +5356,8 @@ async function importPackage(archiveData, options = {}) {
4874
5356
  metadata,
4875
5357
  extractedAssets,
4876
5358
  renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
4877
- preflight
5359
+ preflight,
5360
+ ...importWarnings.length > 0 ? { warnings: importWarnings } : {}
4878
5361
  };
4879
5362
  }
4880
5363
  var importStoraPackage = importPackage;
@@ -4884,8 +5367,21 @@ import {
4884
5367
  ProjectDocumentSchema,
4885
5368
  PROJECT_SCHEMA_NAME as PROJECT_SCHEMA_NAME2,
4886
5369
  CURRENT_PROJECT_SCHEMA_VERSION as CURRENT_PROJECT_SCHEMA_VERSION2,
5370
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION6,
4887
5371
  looksLikeProjectDocument
4888
5372
  } from "@kubuild/schema";
5373
+ function migrateProjectArtboards(project) {
5374
+ let changed = false;
5375
+ const artboards = project.artboards.map((artboard) => {
5376
+ const version = artboard.document.version;
5377
+ if (version === CURRENT_SCHEMA_VERSION6 || !canMigrate(version)) return artboard;
5378
+ const migration = migrateDocument(artboard.document);
5379
+ if (!migration.success || !migration.document) return artboard;
5380
+ changed = true;
5381
+ return { ...artboard, document: migration.document };
5382
+ });
5383
+ return changed ? { ...project, artboards } : project;
5384
+ }
4889
5385
  var DEFAULT_PAGE_ARTBOARD_ID = "artboard-page-1";
4890
5386
  function wrapPageDocumentAsProject(document, options = {}) {
4891
5387
  const artboardId = options.artboardId ?? DEFAULT_PAGE_ARTBOARD_ID;
@@ -4935,7 +5431,7 @@ function loadProjectDocument(raw) {
4935
5431
  }
4936
5432
  return {
4937
5433
  success: true,
4938
- project: parsed.data,
5434
+ project: migrateProjectArtboards(parsed.data),
4939
5435
  wrappedFromLegacyPage: false,
4940
5436
  errors: []
4941
5437
  };
@@ -5406,21 +5902,11 @@ function compareManifestsSemantically(expected, actual, options = {}) {
5406
5902
  // src/io/template-utils.ts
5407
5903
  import {
5408
5904
  TemplateRecordSchema,
5905
+ BUILTIN_COMPONENT_TYPES,
5409
5906
  collectNodeIds as collectNodeIds2,
5410
5907
  isTemplateRecord
5411
5908
  } from "@kubuild/schema";
5412
- var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
5413
- "page",
5414
- "section",
5415
- "container",
5416
- "columns",
5417
- "column",
5418
- "heading",
5419
- "text",
5420
- "image",
5421
- "button",
5422
- "collection"
5423
- ]);
5909
+ var CORE_BUILTIN_COMPONENTS = new Set(BUILTIN_COMPONENT_TYPES);
5424
5910
  function validateTemplate(value) {
5425
5911
  const parseResult = TemplateRecordSchema.safeParse(value);
5426
5912
  if (parseResult.success) {
@@ -5531,22 +6017,6 @@ function saveDraftAsTemplate(draft, metadata, options = {}) {
5531
6017
  custom: metadata.custom ? deepClone(metadata.custom) : void 0
5532
6018
  });
5533
6019
  }
5534
- function cloneTreeWithFreshIds(root, idGen) {
5535
- function cloneRec(node) {
5536
- const newId = idGen(node.id, node);
5537
- const clonedProps = node.props ? deepClone(node.props) : void 0;
5538
- const clonedStyles = node.styles ? deepClone(node.styles) : void 0;
5539
- const clonedChildren = node.children ? node.children.map((child) => cloneRec(child)) : [];
5540
- return {
5541
- id: newId,
5542
- type: node.type,
5543
- ...clonedProps ? { props: clonedProps } : {},
5544
- ...clonedStyles ? { styles: clonedStyles } : {},
5545
- children: clonedChildren
5546
- };
5547
- }
5548
- return cloneRec(root);
5549
- }
5550
6020
  function cloneTemplateAsPage(templateOrDoc, options = {}) {
5551
6021
  let sourceDoc;
5552
6022
  let templateOrigin = null;
@@ -5582,7 +6052,7 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
5582
6052
  return candidate;
5583
6053
  };
5584
6054
  const idGen = options.idGenerator || defaultIdGen;
5585
- const clonedRootNode = cloneTreeWithFreshIds(sourceDoc.document, idGen);
6055
+ const { clonedNode: clonedRootNode } = cloneNodeTreeWithFreshIds(sourceDoc.document, idGen);
5586
6056
  const rootPageNode = {
5587
6057
  ...clonedRootNode,
5588
6058
  type: "page"
@@ -5993,6 +6463,7 @@ export {
5993
6463
  DocumentHistoryManager,
5994
6464
  HistoryEngine,
5995
6465
  MigrationRegistry,
6466
+ NODE_ID_REFERENCE_KEYS,
5996
6467
  RuntimeStateStore,
5997
6468
  addArtboard,
5998
6469
  applyFieldTransform,
@@ -6000,7 +6471,9 @@ export {
6000
6471
  buildSampleVariablesFromCatalog,
6001
6472
  calculateChecksum,
6002
6473
  canMigrate,
6474
+ canonicalizeTextPropsInPlace,
6003
6475
  checkZipBomb,
6476
+ cloneNodeTreeWithFreshIds,
6004
6477
  cloneTemplateAsPage,
6005
6478
  cloneTreeWithNewIds,
6006
6479
  collectArtboardReferenceNodes,
@@ -6017,6 +6490,7 @@ export {
6017
6490
  createPageArtboard,
6018
6491
  createRuntimeStore,
6019
6492
  createTemplateRecord,
6493
+ createTrackingRelayHandler,
6020
6494
  deepClone,
6021
6495
  defaultIdGenerator,
6022
6496
  defaultMigrationRegistry,
@@ -6076,6 +6550,7 @@ export {
6076
6550
  previewImportPackage,
6077
6551
  remapAssetReferences,
6078
6552
  remapDocumentAssetReferences,
6553
+ remapNodeReferences,
6079
6554
  removeArtboard,
6080
6555
  removeNode,
6081
6556
  renameArtboard,
@@ -6083,6 +6558,7 @@ export {
6083
6558
  resolveBinding,
6084
6559
  resolveBindingValue,
6085
6560
  resolvePropertyPath,
6561
+ sanitizeDocumentTracking,
6086
6562
  sanitizeFilename,
6087
6563
  sanitizeHtml,
6088
6564
  sanitizeUrl,
@@ -6093,6 +6569,8 @@ export {
6093
6569
  sendTikTokEventsApi,
6094
6570
  setActiveArtboard,
6095
6571
  sha256Sync,
6572
+ stripDocumentTrackingSecretsInPlace,
6573
+ stripTrackingSecretsInPlace,
6096
6574
  ungroupNodeFrame,
6097
6575
  updateActions,
6098
6576
  updateAnimation,
@@ -6100,6 +6578,7 @@ export {
6100
6578
  updateFormConfig,
6101
6579
  updateProps,
6102
6580
  updateStyle,
6581
+ updateTheme,
6103
6582
  validateDocument,
6104
6583
  validateDocumentSecurity,
6105
6584
  validateFieldValue,