@kubuild/core 0.6.0 → 0.7.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.cjs CHANGED
@@ -54,6 +54,7 @@ __export(index_exports, {
54
54
  createPageArtboard: () => createPageArtboard,
55
55
  createRuntimeStore: () => createRuntimeStore,
56
56
  createTemplateRecord: () => createTemplateRecord,
57
+ createTrackingRelayHandler: () => createTrackingRelayHandler,
57
58
  deepClone: () => deepClone,
58
59
  defaultIdGenerator: () => defaultIdGenerator,
59
60
  defaultMigrationRegistry: () => defaultMigrationRegistry,
@@ -120,6 +121,7 @@ __export(index_exports, {
120
121
  resolveBinding: () => resolveBinding,
121
122
  resolveBindingValue: () => resolveBindingValue,
122
123
  resolvePropertyPath: () => resolvePropertyPath,
124
+ sanitizeDocumentTracking: () => sanitizeDocumentTracking,
123
125
  sanitizeFilename: () => sanitizeFilename,
124
126
  sanitizeHtml: () => sanitizeHtml,
125
127
  sanitizeUrl: () => sanitizeUrl,
@@ -130,6 +132,8 @@ __export(index_exports, {
130
132
  sendTikTokEventsApi: () => sendTikTokEventsApi,
131
133
  setActiveArtboard: () => setActiveArtboard,
132
134
  sha256Sync: () => sha256Sync,
135
+ stripDocumentTrackingSecretsInPlace: () => stripDocumentTrackingSecretsInPlace,
136
+ stripTrackingSecretsInPlace: () => stripTrackingSecretsInPlace,
133
137
  ungroupNodeFrame: () => ungroupNodeFrame,
134
138
  updateActions: () => updateActions,
135
139
  updateAnimation: () => updateAnimation,
@@ -155,7 +159,7 @@ var import_schema = require("@kubuild/schema");
155
159
  function createBlankDocument(title = "Untitled Page") {
156
160
  return {
157
161
  schema: import_schema.SCHEMA_NAME,
158
- version: "1.0.0",
162
+ version: import_schema.CURRENT_SCHEMA_VERSION,
159
163
  metadata: {
160
164
  title,
161
165
  description: "",
@@ -1551,6 +1555,15 @@ var DocumentHistoryManager = class {
1551
1555
  var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1552
1556
  function resolveBinding(binding, context) {
1553
1557
  const segments = binding.key.split(".").filter(Boolean);
1558
+ if (segments.some((segment) => FORBIDDEN_KEY_SEGMENTS.has(segment))) {
1559
+ return applyMissingPolicy(binding);
1560
+ }
1561
+ if (context?.variables && typeof context.variables === "object" && Object.prototype.hasOwnProperty.call(context.variables, binding.key)) {
1562
+ const directValue = context.variables[binding.key];
1563
+ if (directValue !== void 0 && typeof directValue !== "function") {
1564
+ return { status: "resolved", value: directValue };
1565
+ }
1566
+ }
1554
1567
  let current = context?.variables;
1555
1568
  for (const segment of segments) {
1556
1569
  if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
@@ -2122,14 +2135,14 @@ var ActionPipelineExecutor = class {
2122
2135
  async executeSingleStep(step, context, parentSignal, options) {
2123
2136
  const stepStartTime = Date.now();
2124
2137
  if (step.condition && !evaluateActionCondition(step.condition, context)) {
2125
- const skippedResult = {
2138
+ const skippedResult2 = {
2126
2139
  stepId: step.id,
2127
2140
  stepType: step.type,
2128
2141
  status: "skipped",
2129
2142
  durationMs: Date.now() - stepStartTime
2130
2143
  };
2131
- options?.onStepComplete?.(step, skippedResult, context);
2132
- return skippedResult;
2144
+ options?.onStepComplete?.(step, skippedResult2, context);
2145
+ return skippedResult2;
2133
2146
  }
2134
2147
  options?.onStepStart?.(step, context);
2135
2148
  const stepTimeout = step.timeout;
@@ -2621,7 +2634,7 @@ function generateTrackingEventId(prefix = "evt") {
2621
2634
  const randomPart = Math.random().toString(36).substring(2, 10);
2622
2635
  return `${prefix}_${timestamp}_${randomPart}`;
2623
2636
  }
2624
- async function sendMetaCapiEvent(event, config, options) {
2637
+ async function sendMetaCapiEvent(event, config, secrets, options) {
2625
2638
  const fetcher = options?.fetchFn || globalThis.fetch;
2626
2639
  if (!config.pixelId) {
2627
2640
  return {
@@ -2695,9 +2708,10 @@ async function sendMetaCapiEvent(event, config, options) {
2695
2708
  const headers = {
2696
2709
  "Content-Type": "application/json"
2697
2710
  };
2698
- if (config.capiAccessToken) {
2699
- headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2711
+ if (!secrets?.capiAccessToken) {
2712
+ return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
2700
2713
  }
2714
+ headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
2701
2715
  const res = await fetcher(url, {
2702
2716
  method: "POST",
2703
2717
  headers,
@@ -2727,7 +2741,7 @@ async function sendMetaCapiEvent(event, config, options) {
2727
2741
  };
2728
2742
  }
2729
2743
  }
2730
- async function sendTikTokEventsApi(event, config, options) {
2744
+ async function sendTikTokEventsApi(event, config, secrets, options) {
2731
2745
  const fetcher = options?.fetchFn || globalThis.fetch;
2732
2746
  if (!config.pixelId) {
2733
2747
  return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
@@ -2777,9 +2791,10 @@ async function sendTikTokEventsApi(event, config, options) {
2777
2791
  const headers = {
2778
2792
  "Content-Type": "application/json"
2779
2793
  };
2780
- if (config.accessToken) {
2781
- headers["Access-Token"] = config.accessToken;
2794
+ if (!secrets?.accessToken) {
2795
+ return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
2782
2796
  }
2797
+ headers["Access-Token"] = secrets.accessToken;
2783
2798
  const res = await fetcher(url, {
2784
2799
  method: "POST",
2785
2800
  headers,
@@ -2804,7 +2819,7 @@ async function sendTikTokEventsApi(event, config, options) {
2804
2819
  };
2805
2820
  }
2806
2821
  }
2807
- async function sendGa4MeasurementEvent(event, config, options) {
2822
+ async function sendGa4MeasurementEvent(event, config, secrets, options) {
2808
2823
  const fetcher = options?.fetchFn || globalThis.fetch;
2809
2824
  if (!config.measurementId) {
2810
2825
  return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
@@ -2828,12 +2843,10 @@ async function sendGa4MeasurementEvent(event, config, options) {
2828
2843
  return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2829
2844
  }
2830
2845
  try {
2831
- let url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(
2832
- config.measurementId
2833
- )}`;
2834
- if (config.measurementProtocolSecret) {
2835
- url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
2846
+ if (!secrets?.measurementProtocolSecret) {
2847
+ return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
2836
2848
  }
2849
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
2837
2850
  const res = await fetcher(url, {
2838
2851
  method: "POST",
2839
2852
  headers: { "Content-Type": "application/json" },
@@ -2853,11 +2866,8 @@ async function sendGa4MeasurementEvent(event, config, options) {
2853
2866
  };
2854
2867
  }
2855
2868
  }
2856
- async function sendCustomWebhookEvent(event, config, options) {
2869
+ async function sendCustomWebhookEvent(event, _config, secrets, options) {
2857
2870
  const fetcher = options?.fetchFn || globalThis.fetch;
2858
- if (!config.endpointUrl) {
2859
- return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2860
- }
2861
2871
  const webhookPayload = {
2862
2872
  event: event.eventName,
2863
2873
  eventId: event.eventId,
@@ -2871,12 +2881,15 @@ async function sendCustomWebhookEvent(event, config, options) {
2871
2881
  options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2872
2882
  return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2873
2883
  }
2884
+ if (!secrets?.endpointUrl) {
2885
+ return skippedResult("custom", "No custom webhook destination resolved for this credential");
2886
+ }
2874
2887
  try {
2875
- const res = await fetcher(config.endpointUrl, {
2888
+ const res = await fetcher(secrets.endpointUrl, {
2876
2889
  method: "POST",
2877
2890
  headers: {
2878
2891
  "Content-Type": "application/json",
2879
- ...config.headers || {}
2892
+ ...secrets.headers || {}
2880
2893
  },
2881
2894
  body: JSON.stringify(webhookPayload)
2882
2895
  });
@@ -2895,6 +2908,9 @@ async function sendCustomWebhookEvent(event, config, options) {
2895
2908
  };
2896
2909
  }
2897
2910
  }
2911
+ function skippedResult(provider, reason) {
2912
+ return { provider, success: true, skipped: true, reason };
2913
+ }
2898
2914
  async function dispatchServerTracking(event, config, options) {
2899
2915
  const eventId = event.eventId || generateTrackingEventId();
2900
2916
  const eventWithId = { ...event, eventId };
@@ -2919,66 +2935,231 @@ async function dispatchServerTracking(event, config, options) {
2919
2935
  options?.onLog?.(msg, data);
2920
2936
  }
2921
2937
  };
2938
+ const target = options?.provider || "all";
2939
+ const wants = (key) => target === "all" || target === key;
2922
2940
  const providers = config.providers || {};
2923
- const dispatchPromises = [];
2924
- if (providers.meta && providers.meta.enabled !== false && providers.meta.capiEnabled) {
2925
- dispatchPromises.push(
2926
- sendMetaCapiEvent(eventWithId, providers.meta, mergedOptions).then((result) => ({
2927
- key: "meta",
2928
- result
2929
- }))
2930
- );
2941
+ const tasks = [];
2942
+ const meta = providers.meta;
2943
+ if (wants("meta") && meta && meta.enabled !== false && meta.capiEnabled) {
2944
+ tasks.push({
2945
+ key: "meta",
2946
+ credentialId: meta.credentialId,
2947
+ run: (s) => sendMetaCapiEvent(eventWithId, meta, s, mergedOptions)
2948
+ });
2931
2949
  }
2932
- if (providers.tiktok && providers.tiktok.enabled !== false && providers.tiktok.eventsApiEnabled) {
2933
- dispatchPromises.push(
2934
- sendTikTokEventsApi(eventWithId, providers.tiktok, mergedOptions).then((result) => ({
2935
- key: "tiktok",
2936
- result
2937
- }))
2938
- );
2950
+ const tiktok = providers.tiktok;
2951
+ if (wants("tiktok") && tiktok && tiktok.enabled !== false && tiktok.eventsApiEnabled) {
2952
+ tasks.push({
2953
+ key: "tiktok",
2954
+ credentialId: tiktok.credentialId,
2955
+ run: (s) => sendTikTokEventsApi(eventWithId, tiktok, s, mergedOptions)
2956
+ });
2939
2957
  }
2940
- if (providers.google && providers.google.enabled !== false && providers.google.measurementId && providers.google.measurementProtocolSecret) {
2941
- dispatchPromises.push(
2942
- sendGa4MeasurementEvent(eventWithId, providers.google, mergedOptions).then((result) => ({
2943
- key: "google",
2944
- result
2945
- }))
2946
- );
2958
+ const google = providers.google;
2959
+ if (wants("google") && google && google.enabled !== false && google.measurementId) {
2960
+ tasks.push({
2961
+ key: "google",
2962
+ credentialId: google.credentialId,
2963
+ run: (s) => sendGa4MeasurementEvent(eventWithId, google, s, mergedOptions)
2964
+ });
2947
2965
  }
2948
- if (providers.custom && providers.custom.enabled !== false && providers.custom.endpointUrl) {
2949
- dispatchPromises.push(
2950
- sendCustomWebhookEvent(eventWithId, providers.custom, mergedOptions).then((result) => ({
2951
- key: "custom",
2952
- result
2953
- }))
2954
- );
2966
+ const custom = providers.custom;
2967
+ if (wants("custom") && custom && custom.enabled !== false) {
2968
+ tasks.push({
2969
+ key: "custom",
2970
+ credentialId: custom.credentialId,
2971
+ run: (s) => sendCustomWebhookEvent(eventWithId, custom, s, mergedOptions)
2972
+ });
2973
+ }
2974
+ if (tasks.length === 0) {
2975
+ return {
2976
+ success: true,
2977
+ eventId,
2978
+ skipped: true,
2979
+ reason: target === "gtm" ? "GTM is client-side only (dataLayer); no server delivery" : "No server-side tracking provider is enabled",
2980
+ results: {}
2981
+ };
2955
2982
  }
2956
- const settled = await Promise.allSettled(dispatchPromises);
2983
+ const resolve = options?.resolveSecrets;
2984
+ const runTask = async (task) => {
2985
+ let secrets = null;
2986
+ if (resolve) {
2987
+ try {
2988
+ secrets = await resolve({ provider: task.key, credentialId: task.credentialId, documentId: options?.documentId }) ?? null;
2989
+ } catch (err) {
2990
+ return {
2991
+ provider: task.key,
2992
+ success: false,
2993
+ error: `Secret resolution failed: ${err instanceof Error ? err.message : String(err)}`
2994
+ };
2995
+ }
2996
+ }
2997
+ if (!secrets && !simulate) {
2998
+ const reason = resolve ? `No secret resolved for provider "${task.key}"${task.credentialId ? ` (credentialId "${task.credentialId}")` : " (no credentialId linked)"}` : "No secret resolver configured (options.resolveSecrets)";
2999
+ mergedOptions.onLog?.(`[${task.key}] skipped: ${reason}`);
3000
+ return skippedResult(task.key, reason);
3001
+ }
3002
+ return task.run(secrets);
3003
+ };
3004
+ const settled = await Promise.allSettled(tasks.map((task) => runTask(task)));
2957
3005
  const results = {};
2958
3006
  let overallSuccess = true;
2959
- for (const item of settled) {
3007
+ let delivered = 0;
3008
+ settled.forEach((item, index) => {
3009
+ const key = tasks[index].key;
2960
3010
  if (item.status === "fulfilled") {
2961
- results[item.value.key] = item.value.result;
2962
- if (!item.value.result.success) {
2963
- overallSuccess = false;
2964
- }
3011
+ results[key] = item.value;
3012
+ if (!item.value.success) overallSuccess = false;
3013
+ if (!item.value.skipped) delivered++;
2965
3014
  } else {
2966
3015
  overallSuccess = false;
3016
+ results[key] = {
3017
+ provider: key,
3018
+ success: false,
3019
+ error: item.reason instanceof Error ? item.reason.message : String(item.reason)
3020
+ };
2967
3021
  }
2968
- }
3022
+ });
3023
+ const allSkipped = delivered === 0 && overallSuccess;
2969
3024
  return {
2970
3025
  success: overallSuccess,
2971
3026
  eventId,
3027
+ ...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
2972
3028
  results
2973
3029
  };
2974
3030
  }
2975
3031
 
3032
+ // src/runtime/tracking-relay.ts
3033
+ var import_schema5 = require("@kubuild/schema");
3034
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
3035
+ function defaultClientIp(request) {
3036
+ const forwarded = request.headers.get("x-forwarded-for");
3037
+ if (forwarded) {
3038
+ const first = forwarded.split(",")[0]?.trim();
3039
+ if (first) return first;
3040
+ }
3041
+ return request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
3042
+ }
3043
+ function isOriginAllowed(origin, allowed) {
3044
+ if (!allowed) return true;
3045
+ if (!origin) return false;
3046
+ if (typeof allowed === "function") return allowed(origin);
3047
+ return allowed.includes(origin);
3048
+ }
3049
+ function createTrackingRelayHandler(options) {
3050
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
3051
+ return async (request) => {
3052
+ const origin = request.headers.get("origin");
3053
+ const corsHeaders = {};
3054
+ if (options.allowedOrigins && origin && isOriginAllowed(origin, options.allowedOrigins)) {
3055
+ corsHeaders["Access-Control-Allow-Origin"] = origin;
3056
+ corsHeaders["Vary"] = "Origin";
3057
+ }
3058
+ const json = (body, status, extra) => new Response(JSON.stringify(body), {
3059
+ status,
3060
+ headers: { "Content-Type": "application/json", ...corsHeaders, ...extra || {} }
3061
+ });
3062
+ const fail = (code, message, status, extra) => json({ version: import_schema5.TRACKING_RELAY_PROTOCOL_VERSION, success: false, error: { code, message } }, status, extra);
3063
+ if (!isOriginAllowed(origin, options.allowedOrigins)) {
3064
+ return fail("FORBIDDEN_ORIGIN", "Origin is not allowed", 403);
3065
+ }
3066
+ if (request.method === "OPTIONS") {
3067
+ return new Response(null, {
3068
+ status: 204,
3069
+ headers: {
3070
+ ...corsHeaders,
3071
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
3072
+ "Access-Control-Allow-Headers": "Content-Type",
3073
+ "Access-Control-Max-Age": "600"
3074
+ }
3075
+ });
3076
+ }
3077
+ if (request.method !== "POST") {
3078
+ return fail("METHOD_NOT_ALLOWED", "Only POST requests are accepted", 405, { Allow: "POST, OPTIONS" });
3079
+ }
3080
+ try {
3081
+ const text = await request.text();
3082
+ if (text.length > maxBodyBytes) {
3083
+ return fail("INVALID_REQUEST", `Request body exceeds ${maxBodyBytes} bytes`, 413);
3084
+ }
3085
+ let raw;
3086
+ try {
3087
+ raw = JSON.parse(text);
3088
+ } catch {
3089
+ return fail("INVALID_REQUEST", "Request body must be valid JSON", 400);
3090
+ }
3091
+ if (raw && typeof raw === "object" && "version" in raw && raw.version !== import_schema5.TRACKING_RELAY_PROTOCOL_VERSION) {
3092
+ return fail(
3093
+ "UNSUPPORTED_VERSION",
3094
+ `Unsupported relay protocol version; expected ${import_schema5.TRACKING_RELAY_PROTOCOL_VERSION}`,
3095
+ 400
3096
+ );
3097
+ }
3098
+ const parsed = import_schema5.TrackingRelayRequestSchema.safeParse(raw);
3099
+ if (!parsed.success) {
3100
+ const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
3101
+ return fail("INVALID_REQUEST", `Invalid relay request: ${issues}`, 400);
3102
+ }
3103
+ const body = parsed.data;
3104
+ const config = await options.getConfig({
3105
+ request,
3106
+ documentId: body.documentId,
3107
+ credentialId: body.credentialId,
3108
+ provider: body.provider
3109
+ });
3110
+ if (!config) {
3111
+ return fail("CONFIG_NOT_FOUND", "No tracking configuration found for this page", 404);
3112
+ }
3113
+ const clientIp = (options.getClientIp ?? defaultClientIp)(request);
3114
+ const clientUserAgent = request.headers.get("user-agent") || void 0;
3115
+ const userData = { ...body.event.userData || {} };
3116
+ delete userData.clientIp;
3117
+ delete userData.clientUserAgent;
3118
+ const event = { ...body.event, userData };
3119
+ const outcome = await dispatchServerTracking(event, config, {
3120
+ resolveSecrets: options.resolveSecrets,
3121
+ documentId: body.documentId,
3122
+ provider: body.provider,
3123
+ fetchFn: options.fetchFn,
3124
+ clientIp,
3125
+ clientUserAgent,
3126
+ onLog: options.onLog
3127
+ });
3128
+ const results = {};
3129
+ for (const [key, r] of Object.entries(outcome.results)) {
3130
+ results[key] = {
3131
+ provider: r.provider,
3132
+ success: r.success,
3133
+ ...r.status !== void 0 ? { status: r.status } : {},
3134
+ ...r.skipped ? { skipped: true } : {},
3135
+ ...r.reason ? { reason: r.reason } : {},
3136
+ ...r.error ? { error: r.error } : {}
3137
+ };
3138
+ }
3139
+ return json(
3140
+ {
3141
+ version: import_schema5.TRACKING_RELAY_PROTOCOL_VERSION,
3142
+ success: outcome.success,
3143
+ eventId: outcome.eventId,
3144
+ ...outcome.skipped ? { skipped: true } : {},
3145
+ ...outcome.reason ? { reason: outcome.reason } : {},
3146
+ results
3147
+ },
3148
+ 200
3149
+ );
3150
+ } catch (err) {
3151
+ options.onLog?.("[Tracking relay] internal error", err);
3152
+ return fail("INTERNAL_ERROR", "Tracking relay failed", 500);
3153
+ }
3154
+ };
3155
+ }
3156
+
2976
3157
  // src/io/exporter.ts
2977
3158
  var import_fflate = require("fflate");
2978
- var import_schema6 = require("@kubuild/schema");
3159
+ var import_schema8 = require("@kubuild/schema");
2979
3160
 
2980
3161
  // src/validation/validator.ts
2981
- var import_schema5 = require("@kubuild/schema");
3162
+ var import_schema6 = require("@kubuild/schema");
2982
3163
 
2983
3164
  // src/validation/security.ts
2984
3165
  var DEFAULT_DOCUMENT_SECURITY_LIMITS = {
@@ -3267,10 +3448,10 @@ function validateDocument(input, options = {}) {
3267
3448
  };
3268
3449
  }
3269
3450
  }
3270
- if (doc.schema !== import_schema5.SCHEMA_NAME) {
3451
+ if (doc.schema !== import_schema6.SCHEMA_NAME) {
3271
3452
  errors.push({
3272
3453
  code: "GLOBAL_SCHEMA_INVALID",
3273
- message: `Invalid schema identifier. Expected "${import_schema5.SCHEMA_NAME}", received "${String(doc.schema)}"`,
3454
+ message: `Invalid schema identifier. Expected "${import_schema6.SCHEMA_NAME}", received "${String(doc.schema)}"`,
3274
3455
  path: "/schema"
3275
3456
  });
3276
3457
  }
@@ -3334,7 +3515,7 @@ function validateDocument(input, options = {}) {
3334
3515
  warnings
3335
3516
  );
3336
3517
  if (errors.length === 0) {
3337
- const zodResult = import_schema5.PageDocumentSchema.safeParse(input);
3518
+ const zodResult = import_schema6.PageDocumentSchema.safeParse(input);
3338
3519
  if (!zodResult.success) {
3339
3520
  for (const issue of zodResult.error.issues) {
3340
3521
  const jsonPath = "/" + issue.path.join("/");
@@ -3514,7 +3695,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3514
3695
  `${currentPath}/props`,
3515
3696
  effectiveNodeId,
3516
3697
  options,
3517
- errors
3698
+ errors,
3699
+ warnings
3518
3700
  );
3519
3701
  }
3520
3702
  }
@@ -3529,7 +3711,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3529
3711
  }
3530
3712
  }
3531
3713
  }
3532
- function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3714
+ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors, warnings) {
3715
+ const checkAssets = options.checkAssetReferences !== false;
3716
+ const checkVariables = options.checkVariableBindings !== false;
3717
+ const checkActions = options.checkActionBindings !== false;
3533
3718
  for (const [key, value] of Object.entries(propsObj)) {
3534
3719
  const currentPath = `${propsPath}/${key}`;
3535
3720
  if (!value || typeof value !== "object") {
@@ -3543,7 +3728,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3543
3728
  `${currentPath}/${index}`,
3544
3729
  nodeId,
3545
3730
  options,
3546
- errors
3731
+ errors,
3732
+ warnings
3547
3733
  );
3548
3734
  }
3549
3735
  });
@@ -3551,7 +3737,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3551
3737
  }
3552
3738
  const record = value;
3553
3739
  if (record.type === "asset") {
3554
- if (typeof record.assetId !== "string" || record.assetId.trim().length === 0) {
3740
+ if (!checkAssets) continue;
3741
+ const assetId = typeof record.assetId === "string" && record.assetId.trim().length > 0 ? record.assetId : void 0;
3742
+ if (assetId === void 0) {
3555
3743
  errors.push({
3556
3744
  code: "INVALID_ASSET_REFERENCE",
3557
3745
  message: 'Asset reference must have a non-empty "assetId"',
@@ -3567,7 +3755,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3567
3755
  nodeId
3568
3756
  });
3569
3757
  }
3758
+ if (assetId !== void 0 && options.knownAssetIds && !isKnownId(options.knownAssetIds, assetId)) {
3759
+ if (typeof record.fallbackUrl === "string" && record.fallbackUrl.length > 0) {
3760
+ warnings.push({
3761
+ code: "UNRESOLVED_ASSET_REFERENCE",
3762
+ message: `Asset "${assetId}" is not among the known assets; its fallbackUrl will be used`,
3763
+ path: `${currentPath}/assetId`,
3764
+ nodeId,
3765
+ details: { assetId }
3766
+ });
3767
+ } else {
3768
+ errors.push({
3769
+ code: "INVALID_ASSET_REFERENCE",
3770
+ message: `Asset "${assetId}" is not among the known assets and has no fallbackUrl`,
3771
+ path: `${currentPath}/assetId`,
3772
+ nodeId,
3773
+ details: { assetId }
3774
+ });
3775
+ }
3776
+ }
3570
3777
  } else if (record.type === "variable") {
3778
+ if (!checkVariables) continue;
3571
3779
  if (typeof record.key !== "string" || record.key.trim().length === 0) {
3572
3780
  errors.push({
3573
3781
  code: "INVALID_VARIABLE_BINDING",
@@ -3577,6 +3785,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3577
3785
  });
3578
3786
  }
3579
3787
  } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
3788
+ if (!checkActions) continue;
3580
3789
  if (typeof record.type !== "string" || record.type.trim().length === 0) {
3581
3790
  errors.push({
3582
3791
  code: "INVALID_ACTION_BINDING",
@@ -3586,10 +3795,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3586
3795
  });
3587
3796
  }
3588
3797
  } else {
3589
- validatePropsBindings(record, currentPath, nodeId, options, errors);
3798
+ validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
3590
3799
  }
3591
3800
  }
3592
3801
  }
3802
+ function isKnownId(ids, id) {
3803
+ return Array.isArray(ids) ? ids.includes(id) : ids.has(id);
3804
+ }
3805
+
3806
+ // src/io/tracking-sanitizer.ts
3807
+ var import_schema7 = require("@kubuild/schema");
3808
+ function stripTrackingSecretsInPlace(tracking, basePath = "tracking") {
3809
+ const removed = [];
3810
+ if (!tracking || typeof tracking !== "object" || Array.isArray(tracking)) return removed;
3811
+ const providers = tracking.providers;
3812
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) return removed;
3813
+ for (const provider of Object.keys(import_schema7.LEGACY_TRACKING_SECRET_KEYS)) {
3814
+ const cfg = providers[provider];
3815
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) continue;
3816
+ for (const key of import_schema7.LEGACY_TRACKING_SECRET_KEYS[provider]) {
3817
+ if (Object.prototype.hasOwnProperty.call(cfg, key)) {
3818
+ delete cfg[key];
3819
+ removed.push(`${basePath}.providers.${provider}.${key}`);
3820
+ }
3821
+ }
3822
+ }
3823
+ return removed;
3824
+ }
3825
+ function stripDocumentTrackingSecretsInPlace(doc) {
3826
+ const removed = [];
3827
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return removed;
3828
+ const record = doc;
3829
+ removed.push(...stripTrackingSecretsInPlace(record.tracking, "tracking"));
3830
+ const metadata = record.metadata;
3831
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
3832
+ removed.push(
3833
+ ...stripTrackingSecretsInPlace(metadata.tracking, "metadata.tracking")
3834
+ );
3835
+ }
3836
+ if (Array.isArray(record.artboards)) {
3837
+ record.artboards.forEach((artboard, index) => {
3838
+ if (artboard && typeof artboard === "object") {
3839
+ const inner = artboard.document;
3840
+ for (const path of stripDocumentTrackingSecretsInPlace(inner)) {
3841
+ removed.push(`artboards.${index}.document.${path}`);
3842
+ }
3843
+ }
3844
+ });
3845
+ }
3846
+ return removed;
3847
+ }
3848
+ function sanitizeDocumentTracking(doc) {
3849
+ if (!doc || typeof doc !== "object") return { document: doc, removed: [] };
3850
+ const copy = JSON.parse(JSON.stringify(doc));
3851
+ const removed = stripDocumentTrackingSecretsInPlace(copy);
3852
+ return { document: copy, removed };
3853
+ }
3593
3854
 
3594
3855
  // src/io/exporter.ts
3595
3856
  function sha256Sync(data) {
@@ -3810,6 +4071,7 @@ async function exportPackage(document, options = {}) {
3810
4071
  };
3811
4072
  }
3812
4073
  const pageDoc = JSON.parse(JSON.stringify(validation.data));
4074
+ stripDocumentTrackingSecretsInPlace(pageDoc);
3813
4075
  if (options.metadata) {
3814
4076
  pageDoc.metadata = {
3815
4077
  ...pageDoc.metadata || {
@@ -3933,8 +4195,8 @@ async function exportPackage(document, options = {}) {
3933
4195
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3934
4196
  };
3935
4197
  const manifest = {
3936
- schema: import_schema6.SCHEMA_NAME,
3937
- schemaVersion: pageDoc.version || import_schema6.CURRENT_SCHEMA_VERSION,
4198
+ schema: import_schema8.SCHEMA_NAME,
4199
+ schemaVersion: pageDoc.version || import_schema8.CURRENT_SCHEMA_VERSION,
3938
4200
  packageVersion: options.packageVersion || "1.0.0",
3939
4201
  builderCompatibility: options.builderCompatibility || ">=0.1.0",
3940
4202
  requiredComponents: requirements.requiredComponents,
@@ -3959,10 +4221,10 @@ var exportStoraPackage = exportPackage;
3959
4221
 
3960
4222
  // src/io/importer.ts
3961
4223
  var import_fflate2 = require("fflate");
3962
- var import_schema8 = require("@kubuild/schema");
4224
+ var import_schema10 = require("@kubuild/schema");
3963
4225
 
3964
4226
  // src/io/migration.ts
3965
- var import_schema7 = require("@kubuild/schema");
4227
+ var import_schema9 = require("@kubuild/schema");
3966
4228
  var MigrationRegistry = class {
3967
4229
  steps = /* @__PURE__ */ new Map();
3968
4230
  /**
@@ -4041,7 +4303,7 @@ defaultMigrationRegistry.register({
4041
4303
  description: "Migrate alpha 0.1.0 schema (root node key, flat styles) to v1.0.0",
4042
4304
  migrate: (rawDoc) => {
4043
4305
  const migrated = deepClone(rawDoc);
4044
- migrated.schema = import_schema7.SCHEMA_NAME;
4306
+ migrated.schema = import_schema9.SCHEMA_NAME;
4045
4307
  migrated.version = "1.0.0";
4046
4308
  if (migrated.root && !migrated.document) {
4047
4309
  migrated.document = migrated.root;
@@ -4076,7 +4338,7 @@ defaultMigrationRegistry.register({
4076
4338
  description: "Migrate 0.2.0 schema to v1.0.0",
4077
4339
  migrate: (rawDoc) => {
4078
4340
  const migrated = deepClone(rawDoc);
4079
- migrated.schema = import_schema7.SCHEMA_NAME;
4341
+ migrated.schema = import_schema9.SCHEMA_NAME;
4080
4342
  migrated.version = "1.0.0";
4081
4343
  if (migrated.document && typeof migrated.document === "object") {
4082
4344
  convertNodeStylesToResponsive(migrated.document);
@@ -4090,7 +4352,7 @@ defaultMigrationRegistry.register({
4090
4352
  description: "Migrate 0.9.0 beta schema to v1.0.0",
4091
4353
  migrate: (rawDoc) => {
4092
4354
  const migrated = deepClone(rawDoc);
4093
- migrated.schema = import_schema7.SCHEMA_NAME;
4355
+ migrated.schema = import_schema9.SCHEMA_NAME;
4094
4356
  migrated.version = "1.0.0";
4095
4357
  if (!migrated.metadata || typeof migrated.metadata !== "object") {
4096
4358
  migrated.metadata = {
@@ -4115,17 +4377,36 @@ defaultMigrationRegistry.register({
4115
4377
  return migrated;
4116
4378
  }
4117
4379
  });
4118
- function canMigrate(sourceVersion, targetVersion = import_schema7.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
4380
+ defaultMigrationRegistry.register({
4381
+ fromVersion: "1.0.0",
4382
+ toVersion: "1.1.0",
4383
+ description: "Remove tracking secrets and server destinations (capiAccessToken, accessToken, measurementProtocolSecret, serverRelayUrl, custom endpointUrl/headers) from the document",
4384
+ migrate: (rawDoc, context) => {
4385
+ const migrated = deepClone(rawDoc);
4386
+ migrated.version = "1.1.0";
4387
+ const removed = stripDocumentTrackingSecretsInPlace(migrated);
4388
+ if (removed.length > 0) {
4389
+ context.warn({
4390
+ code: "TRACKING_SECRET_REMOVED",
4391
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are now stored by the host and resolved server-side).",
4392
+ step: "1.0.0->1.1.0",
4393
+ paths: removed
4394
+ });
4395
+ }
4396
+ return migrated;
4397
+ }
4398
+ });
4399
+ function canMigrate(sourceVersion, targetVersion = import_schema9.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
4119
4400
  if (!sourceVersion || !targetVersion) return false;
4120
4401
  if (sourceVersion === targetVersion) return true;
4121
4402
  return registry.hasPath(sourceVersion, targetVersion);
4122
4403
  }
4123
- function getMigrationPath(sourceVersion, targetVersion = import_schema7.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
4404
+ function getMigrationPath(sourceVersion, targetVersion = import_schema9.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
4124
4405
  if (!sourceVersion || !targetVersion) return null;
4125
4406
  return registry.findPath(sourceVersion, targetVersion);
4126
4407
  }
4127
4408
  function migrateDocument(rawDocument, options = {}) {
4128
- const targetVersion = options.targetVersion ?? import_schema7.CURRENT_SCHEMA_VERSION;
4409
+ const targetVersion = options.targetVersion ?? import_schema9.CURRENT_SCHEMA_VERSION;
4129
4410
  const dryRun = options.dryRun ?? false;
4130
4411
  const registry = options.registry ?? defaultMigrationRegistry;
4131
4412
  const validate = options.validate ?? true;
@@ -4153,6 +4434,14 @@ function migrateDocument(rawDocument, options = {}) {
4153
4434
  const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
4154
4435
  if (sourceVersion === targetVersion) {
4155
4436
  const cloned = deepClone(doc);
4437
+ const removedSecrets = stripDocumentTrackingSecretsInPlace(cloned);
4438
+ const currentWarnings = removedSecrets.length > 0 ? [
4439
+ {
4440
+ code: "TRACKING_SECRET_REMOVED",
4441
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are stored by the host and resolved server-side).",
4442
+ paths: removedSecrets
4443
+ }
4444
+ ] : [];
4156
4445
  if (validate) {
4157
4446
  const validation = validateDocument(cloned);
4158
4447
  if (!validation.valid) {
@@ -4184,7 +4473,8 @@ function migrateDocument(rawDocument, options = {}) {
4184
4473
  targetVersion,
4185
4474
  migrationPath: [sourceVersion],
4186
4475
  stepsApplied: 0,
4187
- dryRun
4476
+ dryRun,
4477
+ ...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
4188
4478
  }
4189
4479
  };
4190
4480
  }
@@ -4224,6 +4514,8 @@ function migrateDocument(rawDocument, options = {}) {
4224
4514
  }
4225
4515
  let currentDoc = deepClone(doc);
4226
4516
  let stepsApplied = 0;
4517
+ const warnings = [];
4518
+ const stepContext = { warn: (w) => warnings.push(w) };
4227
4519
  for (let i = 0; i < path.length - 1; i++) {
4228
4520
  const fromVer = path[i];
4229
4521
  const toVer = path[i + 1];
@@ -4249,7 +4541,7 @@ function migrateDocument(rawDocument, options = {}) {
4249
4541
  };
4250
4542
  }
4251
4543
  try {
4252
- currentDoc = step.migrate(currentDoc);
4544
+ currentDoc = step.migrate(currentDoc, stepContext);
4253
4545
  stepsApplied++;
4254
4546
  } catch (err) {
4255
4547
  const error = {
@@ -4305,7 +4597,8 @@ function migrateDocument(rawDocument, options = {}) {
4305
4597
  targetVersion,
4306
4598
  migrationPath: path,
4307
4599
  stepsApplied,
4308
- dryRun: false
4600
+ dryRun: false,
4601
+ ...warnings.length > 0 ? { warnings } : {}
4309
4602
  }
4310
4603
  };
4311
4604
  }
@@ -4340,7 +4633,7 @@ async function preflightPackage(archiveData, options = {}) {
4340
4633
  ...DEFAULT_SECURITY_LIMITS,
4341
4634
  ...options.securityLimits
4342
4635
  };
4343
- const targetVersion = options.targetSchemaVersion || import_schema8.CURRENT_SCHEMA_VERSION;
4636
+ const targetVersion = options.targetSchemaVersion || import_schema10.CURRENT_SCHEMA_VERSION;
4344
4637
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4345
4638
  const diagnostics = [];
4346
4639
  let dependencyPolicy = options.dependencyPolicy || "cancel";
@@ -4473,7 +4766,7 @@ async function preflightPackage(archiveData, options = {}) {
4473
4766
  path: manifestProtoCheck.path
4474
4767
  });
4475
4768
  }
4476
- const parseResult = import_schema8.ManifestSchema.safeParse(manifestJson);
4769
+ const parseResult = import_schema10.ManifestSchema.safeParse(manifestJson);
4477
4770
  if (!parseResult.success) {
4478
4771
  const issues = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
4479
4772
  diagnostics.push({
@@ -4503,6 +4796,16 @@ async function preflightPackage(archiveData, options = {}) {
4503
4796
  let rawPageDoc;
4504
4797
  try {
4505
4798
  rawPageDoc = JSON.parse((0, import_fflate2.strFromU8)(pageEntry));
4799
+ const secretScan = sanitizeDocumentTracking(rawPageDoc);
4800
+ if (secretScan.removed.length > 0) {
4801
+ diagnostics.push({
4802
+ code: "TRACKING_SECRET_REMOVED",
4803
+ severity: "warning",
4804
+ message: "Tracking secrets found in page.json will be removed on import; re-link credentials via credentialId.",
4805
+ path: "page.json",
4806
+ details: { paths: secretScan.removed }
4807
+ });
4808
+ }
4506
4809
  const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
4507
4810
  if (pageProtoCheck.found) {
4508
4811
  diagnostics.push({
@@ -4749,7 +5052,7 @@ function buildReport(valid, canImport, fields) {
4749
5052
  return {
4750
5053
  valid,
4751
5054
  canImport,
4752
- targetVersion: fields.targetVersion || import_schema8.CURRENT_SCHEMA_VERSION,
5055
+ targetVersion: fields.targetVersion || import_schema10.CURRENT_SCHEMA_VERSION,
4753
5056
  requiresMigration: fields.requiresMigration || false,
4754
5057
  missingComponents: fields.missingComponents || [],
4755
5058
  missingCapabilities: fields.missingCapabilities || [],
@@ -4822,7 +5125,7 @@ async function importPackage(archiveData, options = {}) {
4822
5125
  if (preflight.requiresMigration) {
4823
5126
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4824
5127
  const migrationRes = migrateDocument(pageRaw, {
4825
- targetVersion: options.targetSchemaVersion || import_schema8.CURRENT_SCHEMA_VERSION,
5128
+ targetVersion: options.targetSchemaVersion || import_schema10.CURRENT_SCHEMA_VERSION,
4826
5129
  registry: migrationRegistry,
4827
5130
  validate: true
4828
5131
  });
@@ -4844,13 +5147,24 @@ async function importPackage(archiveData, options = {}) {
4844
5147
  } else {
4845
5148
  finalDocument = pageRaw;
4846
5149
  }
5150
+ const importWarnings = [];
5151
+ const strippedSecrets = stripDocumentTrackingSecretsInPlace(finalDocument);
5152
+ if (strippedSecrets.length > 0) {
5153
+ importWarnings.push({
5154
+ code: "TRACKING_SECRET_REMOVED",
5155
+ severity: "warning",
5156
+ message: "Tracking secret removed from imported document; re-link credential via credentialId.",
5157
+ path: "page.json",
5158
+ details: { paths: strippedSecrets }
5159
+ });
5160
+ }
4847
5161
  let metadata = finalDocument.metadata || {
4848
5162
  title: "Imported Page",
4849
5163
  description: "",
4850
5164
  author: "",
4851
5165
  tags: [],
4852
5166
  category: "general",
4853
- version: finalDocument.version || import_schema8.CURRENT_SCHEMA_VERSION
5167
+ version: finalDocument.version || import_schema10.CURRENT_SCHEMA_VERSION
4854
5168
  };
4855
5169
  if (unzipped["metadata.json"]) {
4856
5170
  try {
@@ -5002,13 +5316,14 @@ async function importPackage(archiveData, options = {}) {
5002
5316
  metadata,
5003
5317
  extractedAssets,
5004
5318
  renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
5005
- preflight
5319
+ preflight,
5320
+ ...importWarnings.length > 0 ? { warnings: importWarnings } : {}
5006
5321
  };
5007
5322
  }
5008
5323
  var importStoraPackage = importPackage;
5009
5324
 
5010
5325
  // src/io/load-project.ts
5011
- var import_schema9 = require("@kubuild/schema");
5326
+ var import_schema11 = require("@kubuild/schema");
5012
5327
  var DEFAULT_PAGE_ARTBOARD_ID = "artboard-page-1";
5013
5328
  function wrapPageDocumentAsProject(document, options = {}) {
5014
5329
  const artboardId = options.artboardId ?? DEFAULT_PAGE_ARTBOARD_ID;
@@ -5021,8 +5336,8 @@ function wrapPageDocumentAsProject(document, options = {}) {
5021
5336
  ...options.width !== void 0 ? { width: options.width } : {}
5022
5337
  };
5023
5338
  return {
5024
- schema: import_schema9.PROJECT_SCHEMA_NAME,
5025
- version: import_schema9.CURRENT_PROJECT_SCHEMA_VERSION,
5339
+ schema: import_schema11.PROJECT_SCHEMA_NAME,
5340
+ version: import_schema11.CURRENT_PROJECT_SCHEMA_VERSION,
5026
5341
  metadata: document.metadata,
5027
5342
  artboards: [artboard],
5028
5343
  activeArtboardId: artboardId
@@ -5041,8 +5356,8 @@ function loadProjectDocument(raw) {
5041
5356
  ]
5042
5357
  };
5043
5358
  }
5044
- if ((0, import_schema9.looksLikeProjectDocument)(raw)) {
5045
- const parsed = import_schema9.ProjectDocumentSchema.safeParse(raw);
5359
+ if ((0, import_schema11.looksLikeProjectDocument)(raw)) {
5360
+ const parsed = import_schema11.ProjectDocumentSchema.safeParse(raw);
5046
5361
  if (!parsed.success) {
5047
5362
  return {
5048
5363
  success: false,
@@ -5527,7 +5842,7 @@ function compareManifestsSemantically(expected, actual, options = {}) {
5527
5842
  }
5528
5843
 
5529
5844
  // src/io/template-utils.ts
5530
- var import_schema10 = require("@kubuild/schema");
5845
+ var import_schema12 = require("@kubuild/schema");
5531
5846
  var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
5532
5847
  "page",
5533
5848
  "section",
@@ -5541,7 +5856,7 @@ var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
5541
5856
  "collection"
5542
5857
  ]);
5543
5858
  function validateTemplate(value) {
5544
- const parseResult = import_schema10.TemplateRecordSchema.safeParse(value);
5859
+ const parseResult = import_schema12.TemplateRecordSchema.safeParse(value);
5545
5860
  if (parseResult.success) {
5546
5861
  return {
5547
5862
  valid: true,
@@ -5669,7 +5984,7 @@ function cloneTreeWithFreshIds(root, idGen) {
5669
5984
  function cloneTemplateAsPage(templateOrDoc, options = {}) {
5670
5985
  let sourceDoc;
5671
5986
  let templateOrigin = null;
5672
- if ((0, import_schema10.isTemplateRecord)(templateOrDoc)) {
5987
+ if ((0, import_schema12.isTemplateRecord)(templateOrDoc)) {
5673
5988
  if (!templateOrDoc.document) {
5674
5989
  throw new Error(
5675
5990
  `Template "${templateOrDoc.name}" (${templateOrDoc.id}) does not contain an inline document snapshot to clone from.`
@@ -5686,7 +6001,7 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
5686
6001
  } else {
5687
6002
  throw new Error("Invalid input: expected a TemplateRecord or PageDocument.");
5688
6003
  }
5689
- const existingIdsInSource = new Set((0, import_schema10.collectNodeIds)(sourceDoc.document));
6004
+ const existingIdsInSource = new Set((0, import_schema12.collectNodeIds)(sourceDoc.document));
5690
6005
  const assignedNewIds = /* @__PURE__ */ new Set();
5691
6006
  const prefix = options.idPrefix || `page_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;
5692
6007
  let counter = 1;
@@ -5735,8 +6050,8 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
5735
6050
  }
5736
6051
  function areNodeIdsCompletelyDistinct(docA, docB) {
5737
6052
  if (!docA?.document || !docB?.document) return true;
5738
- const idsA = new Set((0, import_schema10.collectNodeIds)(docA.document));
5739
- const idsB = (0, import_schema10.collectNodeIds)(docB.document);
6053
+ const idsA = new Set((0, import_schema12.collectNodeIds)(docA.document));
6054
+ const idsB = (0, import_schema12.collectNodeIds)(docB.document);
5740
6055
  for (const id of idsB) {
5741
6056
  if (idsA.has(id)) {
5742
6057
  return false;
@@ -5746,7 +6061,7 @@ function areNodeIdsCompletelyDistinct(docA, docB) {
5746
6061
  }
5747
6062
 
5748
6063
  // src/validation/project-validator.ts
5749
- var import_schema11 = require("@kubuild/schema");
6064
+ var import_schema13 = require("@kubuild/schema");
5750
6065
  function walk(node, visit) {
5751
6066
  visit(node);
5752
6067
  for (const child of node.children ?? []) {
@@ -5756,7 +6071,7 @@ function walk(node, visit) {
5756
6071
  function collectArtboardReferences(artboard) {
5757
6072
  const refs = [];
5758
6073
  walk(artboard.document.document, (node) => {
5759
- if (node.type === import_schema11.ARTBOARD_REFERENCE_NODE_TYPE) {
6074
+ if (node.type === import_schema13.ARTBOARD_REFERENCE_NODE_TYPE) {
5760
6075
  const target = node.props?.artboardId;
5761
6076
  refs.push({
5762
6077
  nodeId: node.id,
@@ -5783,7 +6098,7 @@ function validateProject(input, options = {}) {
5783
6098
  artboardErrors
5784
6099
  };
5785
6100
  }
5786
- const parsed = import_schema11.ProjectDocumentSchema.safeParse(input);
6101
+ const parsed = import_schema13.ProjectDocumentSchema.safeParse(input);
5787
6102
  if (!parsed.success) {
5788
6103
  return {
5789
6104
  valid: false,
@@ -5791,7 +6106,7 @@ function validateProject(input, options = {}) {
5791
6106
  errors: [
5792
6107
  {
5793
6108
  code: "PROJECT_SCHEMA_INVALID",
5794
- message: `Project failed schema validation. Expected schema "${import_schema11.PROJECT_SCHEMA_NAME}".`,
6109
+ message: `Project failed schema validation. Expected schema "${import_schema13.PROJECT_SCHEMA_NAME}".`,
5795
6110
  path: "",
5796
6111
  details: { issues: parsed.error.issues }
5797
6112
  }
@@ -5884,7 +6199,7 @@ function validateProject(input, options = {}) {
5884
6199
  function collectArtboardReferenceNodes(root) {
5885
6200
  const refs = [];
5886
6201
  walk(root, (node) => {
5887
- if (node.type === import_schema11.ARTBOARD_REFERENCE_NODE_TYPE) {
6202
+ if (node.type === import_schema13.ARTBOARD_REFERENCE_NODE_TYPE) {
5888
6203
  const target = node.props?.artboardId;
5889
6204
  refs.push({ nodeId: node.id, artboardId: typeof target === "string" ? target : void 0 });
5890
6205
  }
@@ -6133,6 +6448,7 @@ function isFormValid(errors) {
6133
6448
  createPageArtboard,
6134
6449
  createRuntimeStore,
6135
6450
  createTemplateRecord,
6451
+ createTrackingRelayHandler,
6136
6452
  deepClone,
6137
6453
  defaultIdGenerator,
6138
6454
  defaultMigrationRegistry,
@@ -6199,6 +6515,7 @@ function isFormValid(errors) {
6199
6515
  resolveBinding,
6200
6516
  resolveBindingValue,
6201
6517
  resolvePropertyPath,
6518
+ sanitizeDocumentTracking,
6202
6519
  sanitizeFilename,
6203
6520
  sanitizeHtml,
6204
6521
  sanitizeUrl,
@@ -6209,6 +6526,8 @@ function isFormValid(errors) {
6209
6526
  sendTikTokEventsApi,
6210
6527
  setActiveArtboard,
6211
6528
  sha256Sync,
6529
+ stripDocumentTrackingSecretsInPlace,
6530
+ stripTrackingSecretsInPlace,
6212
6531
  ungroupNodeFrame,
6213
6532
  updateActions,
6214
6533
  updateAnimation,