@kubuild/core 0.5.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,10 +54,12 @@ __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,
60
61
  defaultRenameAssetStrategy: () => defaultRenameAssetStrategy,
62
+ dispatchServerTracking: () => dispatchServerTracking,
61
63
  duplicateNode: () => duplicateNode,
62
64
  evaluateActionCondition: () => evaluateActionCondition,
63
65
  evaluateCondition: () => evaluateCondition,
@@ -77,6 +79,7 @@ __export(index_exports, {
77
79
  findMissingComponentNodes: () => findMissingComponentNodes,
78
80
  findNodeById: () => findNodeById,
79
81
  findNodeLocation: () => findNodeLocation,
82
+ generateTrackingEventId: () => generateTrackingEventId,
80
83
  getActiveArtboard: () => getActiveArtboard,
81
84
  getAncestorChain: () => getAncestorChain,
82
85
  getComponentArtboards: () => getComponentArtboards,
@@ -86,6 +89,7 @@ __export(index_exports, {
86
89
  getPageArtboards: () => getPageArtboards,
87
90
  getParentNodeId: () => getParentNodeId,
88
91
  hasTemplateExpressions: () => hasTemplateExpressions,
92
+ hashSha256: () => hashSha256,
89
93
  importPackage: () => importPackage,
90
94
  importStoraPackage: () => importStoraPackage,
91
95
  insertNode: () => insertNode,
@@ -103,6 +107,9 @@ __export(index_exports, {
103
107
  loadProjectDocument: () => loadProjectDocument,
104
108
  migrateDocument: () => migrateDocument,
105
109
  moveNode: () => moveNode,
110
+ normalizeEmail: () => normalizeEmail,
111
+ normalizePhone: () => normalizePhone,
112
+ normalizeText: () => normalizeText,
106
113
  preflightPackage: () => preflightPackage,
107
114
  previewImportPackage: () => previewImportPackage,
108
115
  remapAssetReferences: () => remapAssetReferences,
@@ -114,12 +121,19 @@ __export(index_exports, {
114
121
  resolveBinding: () => resolveBinding,
115
122
  resolveBindingValue: () => resolveBindingValue,
116
123
  resolvePropertyPath: () => resolvePropertyPath,
124
+ sanitizeDocumentTracking: () => sanitizeDocumentTracking,
117
125
  sanitizeFilename: () => sanitizeFilename,
118
126
  sanitizeHtml: () => sanitizeHtml,
119
127
  sanitizeUrl: () => sanitizeUrl,
120
128
  saveDraftAsTemplate: () => saveDraftAsTemplate,
129
+ sendCustomWebhookEvent: () => sendCustomWebhookEvent,
130
+ sendGa4MeasurementEvent: () => sendGa4MeasurementEvent,
131
+ sendMetaCapiEvent: () => sendMetaCapiEvent,
132
+ sendTikTokEventsApi: () => sendTikTokEventsApi,
121
133
  setActiveArtboard: () => setActiveArtboard,
122
134
  sha256Sync: () => sha256Sync,
135
+ stripDocumentTrackingSecretsInPlace: () => stripDocumentTrackingSecretsInPlace,
136
+ stripTrackingSecretsInPlace: () => stripTrackingSecretsInPlace,
123
137
  ungroupNodeFrame: () => ungroupNodeFrame,
124
138
  updateActions: () => updateActions,
125
139
  updateAnimation: () => updateAnimation,
@@ -145,7 +159,7 @@ var import_schema = require("@kubuild/schema");
145
159
  function createBlankDocument(title = "Untitled Page") {
146
160
  return {
147
161
  schema: import_schema.SCHEMA_NAME,
148
- version: "1.0.0",
162
+ version: import_schema.CURRENT_SCHEMA_VERSION,
149
163
  metadata: {
150
164
  title,
151
165
  description: "",
@@ -1541,6 +1555,15 @@ var DocumentHistoryManager = class {
1541
1555
  var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1542
1556
  function resolveBinding(binding, context) {
1543
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
+ }
1544
1567
  let current = context?.variables;
1545
1568
  for (const segment of segments) {
1546
1569
  if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
@@ -2112,14 +2135,14 @@ var ActionPipelineExecutor = class {
2112
2135
  async executeSingleStep(step, context, parentSignal, options) {
2113
2136
  const stepStartTime = Date.now();
2114
2137
  if (step.condition && !evaluateActionCondition(step.condition, context)) {
2115
- const skippedResult = {
2138
+ const skippedResult2 = {
2116
2139
  stepId: step.id,
2117
2140
  stepType: step.type,
2118
2141
  status: "skipped",
2119
2142
  durationMs: Date.now() - stepStartTime
2120
2143
  };
2121
- options?.onStepComplete?.(step, skippedResult, context);
2122
- return skippedResult;
2144
+ options?.onStepComplete?.(step, skippedResult2, context);
2145
+ return skippedResult2;
2123
2146
  }
2124
2147
  options?.onStepStart?.(step, context);
2125
2148
  const stepTimeout = step.timeout;
@@ -2584,12 +2607,559 @@ function createRuntimeStore(initialState) {
2584
2607
  return new RuntimeStateStore(initialState);
2585
2608
  }
2586
2609
 
2610
+ // src/runtime/server-tracking.ts
2611
+ function normalizeEmail(email) {
2612
+ return (email || "").trim().toLowerCase();
2613
+ }
2614
+ function normalizePhone(phone) {
2615
+ return (phone || "").replace(/[^0-9]/g, "");
2616
+ }
2617
+ function normalizeText(text) {
2618
+ return (text || "").trim().toLowerCase();
2619
+ }
2620
+ async function hashSha256(value) {
2621
+ const normalized = (value || "").trim();
2622
+ if (!normalized) return "";
2623
+ if (typeof globalThis.crypto?.subtle?.digest === "function") {
2624
+ const encoder = new TextEncoder();
2625
+ const data = encoder.encode(normalized);
2626
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
2627
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2628
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2629
+ }
2630
+ throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
2631
+ }
2632
+ function generateTrackingEventId(prefix = "evt") {
2633
+ const timestamp = Date.now().toString(36);
2634
+ const randomPart = Math.random().toString(36).substring(2, 10);
2635
+ return `${prefix}_${timestamp}_${randomPart}`;
2636
+ }
2637
+ async function sendMetaCapiEvent(event, config, secrets, options) {
2638
+ const fetcher = options?.fetchFn || globalThis.fetch;
2639
+ if (!config.pixelId) {
2640
+ return {
2641
+ provider: "meta",
2642
+ success: false,
2643
+ error: "Meta Pixel ID is missing"
2644
+ };
2645
+ }
2646
+ const rawUserData = event.userData || {};
2647
+ const hashedUserData = {};
2648
+ if (rawUserData.email) {
2649
+ hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
2650
+ }
2651
+ if (rawUserData.phone) {
2652
+ hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
2653
+ }
2654
+ if (rawUserData.firstName) {
2655
+ hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
2656
+ }
2657
+ if (rawUserData.lastName) {
2658
+ hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
2659
+ }
2660
+ if (rawUserData.city) {
2661
+ hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
2662
+ }
2663
+ if (rawUserData.state) {
2664
+ hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
2665
+ }
2666
+ if (rawUserData.zip) {
2667
+ hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
2668
+ }
2669
+ if (rawUserData.country) {
2670
+ hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
2671
+ }
2672
+ const clientIp = rawUserData.clientIp || options?.clientIp;
2673
+ if (clientIp) hashedUserData.client_ip_address = clientIp;
2674
+ const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
2675
+ if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
2676
+ if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
2677
+ if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
2678
+ if (rawUserData.externalId) {
2679
+ hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
2680
+ }
2681
+ const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
2682
+ const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
2683
+ const capiPayload = {
2684
+ data: [
2685
+ {
2686
+ event_name: event.eventName,
2687
+ event_time: eventTime,
2688
+ event_id: event.eventId,
2689
+ event_source_url: eventSourceUrl,
2690
+ action_source: event.actionSource || "website",
2691
+ user_data: hashedUserData,
2692
+ custom_data: {
2693
+ ...event.params || {},
2694
+ ...event.customData || {}
2695
+ }
2696
+ }
2697
+ ]
2698
+ };
2699
+ if (config.testEventCode) {
2700
+ capiPayload.test_event_code = config.testEventCode;
2701
+ }
2702
+ if (options?.simulateInDebug) {
2703
+ options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
2704
+ return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
2705
+ }
2706
+ try {
2707
+ const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
2708
+ const headers = {
2709
+ "Content-Type": "application/json"
2710
+ };
2711
+ if (!secrets?.capiAccessToken) {
2712
+ return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
2713
+ }
2714
+ headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
2715
+ const res = await fetcher(url, {
2716
+ method: "POST",
2717
+ headers,
2718
+ body: JSON.stringify(capiPayload)
2719
+ });
2720
+ const resJson = await res.json().catch(() => ({}));
2721
+ if (!res.ok) {
2722
+ return {
2723
+ provider: "meta",
2724
+ success: false,
2725
+ status: res.status,
2726
+ error: resJson?.error?.message || `HTTP ${res.status}`,
2727
+ data: resJson
2728
+ };
2729
+ }
2730
+ return {
2731
+ provider: "meta",
2732
+ success: true,
2733
+ status: res.status,
2734
+ data: resJson
2735
+ };
2736
+ } catch (err) {
2737
+ return {
2738
+ provider: "meta",
2739
+ success: false,
2740
+ error: err instanceof Error ? err.message : String(err)
2741
+ };
2742
+ }
2743
+ }
2744
+ async function sendTikTokEventsApi(event, config, secrets, options) {
2745
+ const fetcher = options?.fetchFn || globalThis.fetch;
2746
+ if (!config.pixelId) {
2747
+ return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
2748
+ }
2749
+ const rawUserData = event.userData || {};
2750
+ const user = {};
2751
+ if (rawUserData.email) {
2752
+ user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
2753
+ }
2754
+ if (rawUserData.phone) {
2755
+ user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
2756
+ }
2757
+ if (rawUserData.clientIp || options?.clientIp) {
2758
+ user.ip = rawUserData.clientIp || options?.clientIp;
2759
+ }
2760
+ if (rawUserData.clientUserAgent || options?.clientUserAgent) {
2761
+ user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
2762
+ }
2763
+ const tiktokPayload = {
2764
+ event_source: "web",
2765
+ event_source_id: config.pixelId,
2766
+ data: [
2767
+ {
2768
+ event: event.eventName,
2769
+ event_id: event.eventId,
2770
+ timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
2771
+ user,
2772
+ properties: {
2773
+ ...event.params || {},
2774
+ ...event.customData || {}
2775
+ },
2776
+ page: {
2777
+ url: event.eventSourceUrl || options?.sourceUrl
2778
+ }
2779
+ }
2780
+ ]
2781
+ };
2782
+ if (config.testEventCode) {
2783
+ tiktokPayload.test_event_code = config.testEventCode;
2784
+ }
2785
+ if (options?.simulateInDebug) {
2786
+ options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
2787
+ return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
2788
+ }
2789
+ try {
2790
+ const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
2791
+ const headers = {
2792
+ "Content-Type": "application/json"
2793
+ };
2794
+ if (!secrets?.accessToken) {
2795
+ return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
2796
+ }
2797
+ headers["Access-Token"] = secrets.accessToken;
2798
+ const res = await fetcher(url, {
2799
+ method: "POST",
2800
+ headers,
2801
+ body: JSON.stringify(tiktokPayload)
2802
+ });
2803
+ const resJson = await res.json().catch(() => ({}));
2804
+ if (!res.ok) {
2805
+ return {
2806
+ provider: "tiktok",
2807
+ success: false,
2808
+ status: res.status,
2809
+ error: resJson?.message || `HTTP ${res.status}`,
2810
+ data: resJson
2811
+ };
2812
+ }
2813
+ return { provider: "tiktok", success: true, status: res.status, data: resJson };
2814
+ } catch (err) {
2815
+ return {
2816
+ provider: "tiktok",
2817
+ success: false,
2818
+ error: err instanceof Error ? err.message : String(err)
2819
+ };
2820
+ }
2821
+ }
2822
+ async function sendGa4MeasurementEvent(event, config, secrets, options) {
2823
+ const fetcher = options?.fetchFn || globalThis.fetch;
2824
+ if (!config.measurementId) {
2825
+ return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
2826
+ }
2827
+ const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
2828
+ const ga4Payload = {
2829
+ client_id: clientId,
2830
+ events: [
2831
+ {
2832
+ name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
2833
+ params: {
2834
+ ...event.params || {},
2835
+ ...event.customData || {},
2836
+ event_id: event.eventId
2837
+ }
2838
+ }
2839
+ ]
2840
+ };
2841
+ if (options?.simulateInDebug) {
2842
+ options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
2843
+ return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2844
+ }
2845
+ try {
2846
+ if (!secrets?.measurementProtocolSecret) {
2847
+ return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
2848
+ }
2849
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
2850
+ const res = await fetcher(url, {
2851
+ method: "POST",
2852
+ headers: { "Content-Type": "application/json" },
2853
+ body: JSON.stringify(ga4Payload)
2854
+ });
2855
+ return {
2856
+ provider: "google",
2857
+ success: res.ok,
2858
+ status: res.status,
2859
+ data: { status: res.status }
2860
+ };
2861
+ } catch (err) {
2862
+ return {
2863
+ provider: "google",
2864
+ success: false,
2865
+ error: err instanceof Error ? err.message : String(err)
2866
+ };
2867
+ }
2868
+ }
2869
+ async function sendCustomWebhookEvent(event, _config, secrets, options) {
2870
+ const fetcher = options?.fetchFn || globalThis.fetch;
2871
+ const webhookPayload = {
2872
+ event: event.eventName,
2873
+ eventId: event.eventId,
2874
+ timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
2875
+ userData: event.userData,
2876
+ params: event.params,
2877
+ customData: event.customData,
2878
+ sourceUrl: event.eventSourceUrl || options?.sourceUrl
2879
+ };
2880
+ if (options?.simulateInDebug) {
2881
+ options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2882
+ return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2883
+ }
2884
+ if (!secrets?.endpointUrl) {
2885
+ return skippedResult("custom", "No custom webhook destination resolved for this credential");
2886
+ }
2887
+ try {
2888
+ const res = await fetcher(secrets.endpointUrl, {
2889
+ method: "POST",
2890
+ headers: {
2891
+ "Content-Type": "application/json",
2892
+ ...secrets.headers || {}
2893
+ },
2894
+ body: JSON.stringify(webhookPayload)
2895
+ });
2896
+ const resJson = await res.json().catch(() => ({}));
2897
+ return {
2898
+ provider: "custom",
2899
+ success: res.ok,
2900
+ status: res.status,
2901
+ data: resJson
2902
+ };
2903
+ } catch (err) {
2904
+ return {
2905
+ provider: "custom",
2906
+ success: false,
2907
+ error: err instanceof Error ? err.message : String(err)
2908
+ };
2909
+ }
2910
+ }
2911
+ function skippedResult(provider, reason) {
2912
+ return { provider, success: true, skipped: true, reason };
2913
+ }
2914
+ async function dispatchServerTracking(event, config, options) {
2915
+ const eventId = event.eventId || generateTrackingEventId();
2916
+ const eventWithId = { ...event, eventId };
2917
+ if (!config || config.enabled === false) {
2918
+ return {
2919
+ success: true,
2920
+ eventId,
2921
+ skipped: true,
2922
+ reason: "Tracking is disabled globally",
2923
+ results: {}
2924
+ };
2925
+ }
2926
+ const isDebug = Boolean(config.debugMode);
2927
+ const simulate = isDebug && options?.simulateInDebug !== false;
2928
+ const mergedOptions = {
2929
+ ...options,
2930
+ simulateInDebug: simulate,
2931
+ onLog: (msg, data) => {
2932
+ if (isDebug) {
2933
+ console.log(`[KUBUILD Tracking] ${msg}`, data || "");
2934
+ }
2935
+ options?.onLog?.(msg, data);
2936
+ }
2937
+ };
2938
+ const target = options?.provider || "all";
2939
+ const wants = (key) => target === "all" || target === key;
2940
+ const providers = config.providers || {};
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
+ });
2949
+ }
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
+ });
2957
+ }
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
+ });
2965
+ }
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
+ };
2982
+ }
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)));
3005
+ const results = {};
3006
+ let overallSuccess = true;
3007
+ let delivered = 0;
3008
+ settled.forEach((item, index) => {
3009
+ const key = tasks[index].key;
3010
+ if (item.status === "fulfilled") {
3011
+ results[key] = item.value;
3012
+ if (!item.value.success) overallSuccess = false;
3013
+ if (!item.value.skipped) delivered++;
3014
+ } else {
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
+ };
3021
+ }
3022
+ });
3023
+ const allSkipped = delivered === 0 && overallSuccess;
3024
+ return {
3025
+ success: overallSuccess,
3026
+ eventId,
3027
+ ...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
3028
+ results
3029
+ };
3030
+ }
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
+
2587
3157
  // src/io/exporter.ts
2588
3158
  var import_fflate = require("fflate");
2589
- var import_schema6 = require("@kubuild/schema");
3159
+ var import_schema8 = require("@kubuild/schema");
2590
3160
 
2591
3161
  // src/validation/validator.ts
2592
- var import_schema5 = require("@kubuild/schema");
3162
+ var import_schema6 = require("@kubuild/schema");
2593
3163
 
2594
3164
  // src/validation/security.ts
2595
3165
  var DEFAULT_DOCUMENT_SECURITY_LIMITS = {
@@ -2878,10 +3448,10 @@ function validateDocument(input, options = {}) {
2878
3448
  };
2879
3449
  }
2880
3450
  }
2881
- if (doc.schema !== import_schema5.SCHEMA_NAME) {
3451
+ if (doc.schema !== import_schema6.SCHEMA_NAME) {
2882
3452
  errors.push({
2883
3453
  code: "GLOBAL_SCHEMA_INVALID",
2884
- 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)}"`,
2885
3455
  path: "/schema"
2886
3456
  });
2887
3457
  }
@@ -2945,7 +3515,7 @@ function validateDocument(input, options = {}) {
2945
3515
  warnings
2946
3516
  );
2947
3517
  if (errors.length === 0) {
2948
- const zodResult = import_schema5.PageDocumentSchema.safeParse(input);
3518
+ const zodResult = import_schema6.PageDocumentSchema.safeParse(input);
2949
3519
  if (!zodResult.success) {
2950
3520
  for (const issue of zodResult.error.issues) {
2951
3521
  const jsonPath = "/" + issue.path.join("/");
@@ -3125,7 +3695,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3125
3695
  `${currentPath}/props`,
3126
3696
  effectiveNodeId,
3127
3697
  options,
3128
- errors
3698
+ errors,
3699
+ warnings
3129
3700
  );
3130
3701
  }
3131
3702
  }
@@ -3140,7 +3711,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3140
3711
  }
3141
3712
  }
3142
3713
  }
3143
- 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;
3144
3718
  for (const [key, value] of Object.entries(propsObj)) {
3145
3719
  const currentPath = `${propsPath}/${key}`;
3146
3720
  if (!value || typeof value !== "object") {
@@ -3154,7 +3728,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3154
3728
  `${currentPath}/${index}`,
3155
3729
  nodeId,
3156
3730
  options,
3157
- errors
3731
+ errors,
3732
+ warnings
3158
3733
  );
3159
3734
  }
3160
3735
  });
@@ -3162,7 +3737,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3162
3737
  }
3163
3738
  const record = value;
3164
3739
  if (record.type === "asset") {
3165
- 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) {
3166
3743
  errors.push({
3167
3744
  code: "INVALID_ASSET_REFERENCE",
3168
3745
  message: 'Asset reference must have a non-empty "assetId"',
@@ -3178,7 +3755,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3178
3755
  nodeId
3179
3756
  });
3180
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
+ }
3181
3777
  } else if (record.type === "variable") {
3778
+ if (!checkVariables) continue;
3182
3779
  if (typeof record.key !== "string" || record.key.trim().length === 0) {
3183
3780
  errors.push({
3184
3781
  code: "INVALID_VARIABLE_BINDING",
@@ -3188,6 +3785,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3188
3785
  });
3189
3786
  }
3190
3787
  } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
3788
+ if (!checkActions) continue;
3191
3789
  if (typeof record.type !== "string" || record.type.trim().length === 0) {
3192
3790
  errors.push({
3193
3791
  code: "INVALID_ACTION_BINDING",
@@ -3197,10 +3795,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3197
3795
  });
3198
3796
  }
3199
3797
  } else {
3200
- validatePropsBindings(record, currentPath, nodeId, options, errors);
3798
+ validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
3201
3799
  }
3202
3800
  }
3203
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
+ }
3204
3854
 
3205
3855
  // src/io/exporter.ts
3206
3856
  function sha256Sync(data) {
@@ -3421,6 +4071,7 @@ async function exportPackage(document, options = {}) {
3421
4071
  };
3422
4072
  }
3423
4073
  const pageDoc = JSON.parse(JSON.stringify(validation.data));
4074
+ stripDocumentTrackingSecretsInPlace(pageDoc);
3424
4075
  if (options.metadata) {
3425
4076
  pageDoc.metadata = {
3426
4077
  ...pageDoc.metadata || {
@@ -3544,8 +4195,8 @@ async function exportPackage(document, options = {}) {
3544
4195
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3545
4196
  };
3546
4197
  const manifest = {
3547
- schema: import_schema6.SCHEMA_NAME,
3548
- schemaVersion: pageDoc.version || import_schema6.CURRENT_SCHEMA_VERSION,
4198
+ schema: import_schema8.SCHEMA_NAME,
4199
+ schemaVersion: pageDoc.version || import_schema8.CURRENT_SCHEMA_VERSION,
3549
4200
  packageVersion: options.packageVersion || "1.0.0",
3550
4201
  builderCompatibility: options.builderCompatibility || ">=0.1.0",
3551
4202
  requiredComponents: requirements.requiredComponents,
@@ -3570,10 +4221,10 @@ var exportStoraPackage = exportPackage;
3570
4221
 
3571
4222
  // src/io/importer.ts
3572
4223
  var import_fflate2 = require("fflate");
3573
- var import_schema8 = require("@kubuild/schema");
4224
+ var import_schema10 = require("@kubuild/schema");
3574
4225
 
3575
4226
  // src/io/migration.ts
3576
- var import_schema7 = require("@kubuild/schema");
4227
+ var import_schema9 = require("@kubuild/schema");
3577
4228
  var MigrationRegistry = class {
3578
4229
  steps = /* @__PURE__ */ new Map();
3579
4230
  /**
@@ -3652,7 +4303,7 @@ defaultMigrationRegistry.register({
3652
4303
  description: "Migrate alpha 0.1.0 schema (root node key, flat styles) to v1.0.0",
3653
4304
  migrate: (rawDoc) => {
3654
4305
  const migrated = deepClone(rawDoc);
3655
- migrated.schema = import_schema7.SCHEMA_NAME;
4306
+ migrated.schema = import_schema9.SCHEMA_NAME;
3656
4307
  migrated.version = "1.0.0";
3657
4308
  if (migrated.root && !migrated.document) {
3658
4309
  migrated.document = migrated.root;
@@ -3687,7 +4338,7 @@ defaultMigrationRegistry.register({
3687
4338
  description: "Migrate 0.2.0 schema to v1.0.0",
3688
4339
  migrate: (rawDoc) => {
3689
4340
  const migrated = deepClone(rawDoc);
3690
- migrated.schema = import_schema7.SCHEMA_NAME;
4341
+ migrated.schema = import_schema9.SCHEMA_NAME;
3691
4342
  migrated.version = "1.0.0";
3692
4343
  if (migrated.document && typeof migrated.document === "object") {
3693
4344
  convertNodeStylesToResponsive(migrated.document);
@@ -3701,7 +4352,7 @@ defaultMigrationRegistry.register({
3701
4352
  description: "Migrate 0.9.0 beta schema to v1.0.0",
3702
4353
  migrate: (rawDoc) => {
3703
4354
  const migrated = deepClone(rawDoc);
3704
- migrated.schema = import_schema7.SCHEMA_NAME;
4355
+ migrated.schema = import_schema9.SCHEMA_NAME;
3705
4356
  migrated.version = "1.0.0";
3706
4357
  if (!migrated.metadata || typeof migrated.metadata !== "object") {
3707
4358
  migrated.metadata = {
@@ -3726,17 +4377,36 @@ defaultMigrationRegistry.register({
3726
4377
  return migrated;
3727
4378
  }
3728
4379
  });
3729
- 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) {
3730
4400
  if (!sourceVersion || !targetVersion) return false;
3731
4401
  if (sourceVersion === targetVersion) return true;
3732
4402
  return registry.hasPath(sourceVersion, targetVersion);
3733
4403
  }
3734
- function getMigrationPath(sourceVersion, targetVersion = import_schema7.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
4404
+ function getMigrationPath(sourceVersion, targetVersion = import_schema9.CURRENT_SCHEMA_VERSION, registry = defaultMigrationRegistry) {
3735
4405
  if (!sourceVersion || !targetVersion) return null;
3736
4406
  return registry.findPath(sourceVersion, targetVersion);
3737
4407
  }
3738
4408
  function migrateDocument(rawDocument, options = {}) {
3739
- const targetVersion = options.targetVersion ?? import_schema7.CURRENT_SCHEMA_VERSION;
4409
+ const targetVersion = options.targetVersion ?? import_schema9.CURRENT_SCHEMA_VERSION;
3740
4410
  const dryRun = options.dryRun ?? false;
3741
4411
  const registry = options.registry ?? defaultMigrationRegistry;
3742
4412
  const validate = options.validate ?? true;
@@ -3764,6 +4434,14 @@ function migrateDocument(rawDocument, options = {}) {
3764
4434
  const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
3765
4435
  if (sourceVersion === targetVersion) {
3766
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
+ ] : [];
3767
4445
  if (validate) {
3768
4446
  const validation = validateDocument(cloned);
3769
4447
  if (!validation.valid) {
@@ -3795,7 +4473,8 @@ function migrateDocument(rawDocument, options = {}) {
3795
4473
  targetVersion,
3796
4474
  migrationPath: [sourceVersion],
3797
4475
  stepsApplied: 0,
3798
- dryRun
4476
+ dryRun,
4477
+ ...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
3799
4478
  }
3800
4479
  };
3801
4480
  }
@@ -3835,6 +4514,8 @@ function migrateDocument(rawDocument, options = {}) {
3835
4514
  }
3836
4515
  let currentDoc = deepClone(doc);
3837
4516
  let stepsApplied = 0;
4517
+ const warnings = [];
4518
+ const stepContext = { warn: (w) => warnings.push(w) };
3838
4519
  for (let i = 0; i < path.length - 1; i++) {
3839
4520
  const fromVer = path[i];
3840
4521
  const toVer = path[i + 1];
@@ -3860,7 +4541,7 @@ function migrateDocument(rawDocument, options = {}) {
3860
4541
  };
3861
4542
  }
3862
4543
  try {
3863
- currentDoc = step.migrate(currentDoc);
4544
+ currentDoc = step.migrate(currentDoc, stepContext);
3864
4545
  stepsApplied++;
3865
4546
  } catch (err) {
3866
4547
  const error = {
@@ -3916,7 +4597,8 @@ function migrateDocument(rawDocument, options = {}) {
3916
4597
  targetVersion,
3917
4598
  migrationPath: path,
3918
4599
  stepsApplied,
3919
- dryRun: false
4600
+ dryRun: false,
4601
+ ...warnings.length > 0 ? { warnings } : {}
3920
4602
  }
3921
4603
  };
3922
4604
  }
@@ -3951,7 +4633,7 @@ async function preflightPackage(archiveData, options = {}) {
3951
4633
  ...DEFAULT_SECURITY_LIMITS,
3952
4634
  ...options.securityLimits
3953
4635
  };
3954
- const targetVersion = options.targetSchemaVersion || import_schema8.CURRENT_SCHEMA_VERSION;
4636
+ const targetVersion = options.targetSchemaVersion || import_schema10.CURRENT_SCHEMA_VERSION;
3955
4637
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
3956
4638
  const diagnostics = [];
3957
4639
  let dependencyPolicy = options.dependencyPolicy || "cancel";
@@ -4084,7 +4766,7 @@ async function preflightPackage(archiveData, options = {}) {
4084
4766
  path: manifestProtoCheck.path
4085
4767
  });
4086
4768
  }
4087
- const parseResult = import_schema8.ManifestSchema.safeParse(manifestJson);
4769
+ const parseResult = import_schema10.ManifestSchema.safeParse(manifestJson);
4088
4770
  if (!parseResult.success) {
4089
4771
  const issues = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
4090
4772
  diagnostics.push({
@@ -4114,6 +4796,16 @@ async function preflightPackage(archiveData, options = {}) {
4114
4796
  let rawPageDoc;
4115
4797
  try {
4116
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
+ }
4117
4809
  const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
4118
4810
  if (pageProtoCheck.found) {
4119
4811
  diagnostics.push({
@@ -4360,7 +5052,7 @@ function buildReport(valid, canImport, fields) {
4360
5052
  return {
4361
5053
  valid,
4362
5054
  canImport,
4363
- targetVersion: fields.targetVersion || import_schema8.CURRENT_SCHEMA_VERSION,
5055
+ targetVersion: fields.targetVersion || import_schema10.CURRENT_SCHEMA_VERSION,
4364
5056
  requiresMigration: fields.requiresMigration || false,
4365
5057
  missingComponents: fields.missingComponents || [],
4366
5058
  missingCapabilities: fields.missingCapabilities || [],
@@ -4433,7 +5125,7 @@ async function importPackage(archiveData, options = {}) {
4433
5125
  if (preflight.requiresMigration) {
4434
5126
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4435
5127
  const migrationRes = migrateDocument(pageRaw, {
4436
- targetVersion: options.targetSchemaVersion || import_schema8.CURRENT_SCHEMA_VERSION,
5128
+ targetVersion: options.targetSchemaVersion || import_schema10.CURRENT_SCHEMA_VERSION,
4437
5129
  registry: migrationRegistry,
4438
5130
  validate: true
4439
5131
  });
@@ -4455,13 +5147,24 @@ async function importPackage(archiveData, options = {}) {
4455
5147
  } else {
4456
5148
  finalDocument = pageRaw;
4457
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
+ }
4458
5161
  let metadata = finalDocument.metadata || {
4459
5162
  title: "Imported Page",
4460
5163
  description: "",
4461
5164
  author: "",
4462
5165
  tags: [],
4463
5166
  category: "general",
4464
- version: finalDocument.version || import_schema8.CURRENT_SCHEMA_VERSION
5167
+ version: finalDocument.version || import_schema10.CURRENT_SCHEMA_VERSION
4465
5168
  };
4466
5169
  if (unzipped["metadata.json"]) {
4467
5170
  try {
@@ -4613,13 +5316,14 @@ async function importPackage(archiveData, options = {}) {
4613
5316
  metadata,
4614
5317
  extractedAssets,
4615
5318
  renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
4616
- preflight
5319
+ preflight,
5320
+ ...importWarnings.length > 0 ? { warnings: importWarnings } : {}
4617
5321
  };
4618
5322
  }
4619
5323
  var importStoraPackage = importPackage;
4620
5324
 
4621
5325
  // src/io/load-project.ts
4622
- var import_schema9 = require("@kubuild/schema");
5326
+ var import_schema11 = require("@kubuild/schema");
4623
5327
  var DEFAULT_PAGE_ARTBOARD_ID = "artboard-page-1";
4624
5328
  function wrapPageDocumentAsProject(document, options = {}) {
4625
5329
  const artboardId = options.artboardId ?? DEFAULT_PAGE_ARTBOARD_ID;
@@ -4632,8 +5336,8 @@ function wrapPageDocumentAsProject(document, options = {}) {
4632
5336
  ...options.width !== void 0 ? { width: options.width } : {}
4633
5337
  };
4634
5338
  return {
4635
- schema: import_schema9.PROJECT_SCHEMA_NAME,
4636
- version: import_schema9.CURRENT_PROJECT_SCHEMA_VERSION,
5339
+ schema: import_schema11.PROJECT_SCHEMA_NAME,
5340
+ version: import_schema11.CURRENT_PROJECT_SCHEMA_VERSION,
4637
5341
  metadata: document.metadata,
4638
5342
  artboards: [artboard],
4639
5343
  activeArtboardId: artboardId
@@ -4652,8 +5356,8 @@ function loadProjectDocument(raw) {
4652
5356
  ]
4653
5357
  };
4654
5358
  }
4655
- if ((0, import_schema9.looksLikeProjectDocument)(raw)) {
4656
- const parsed = import_schema9.ProjectDocumentSchema.safeParse(raw);
5359
+ if ((0, import_schema11.looksLikeProjectDocument)(raw)) {
5360
+ const parsed = import_schema11.ProjectDocumentSchema.safeParse(raw);
4657
5361
  if (!parsed.success) {
4658
5362
  return {
4659
5363
  success: false,
@@ -5138,7 +5842,7 @@ function compareManifestsSemantically(expected, actual, options = {}) {
5138
5842
  }
5139
5843
 
5140
5844
  // src/io/template-utils.ts
5141
- var import_schema10 = require("@kubuild/schema");
5845
+ var import_schema12 = require("@kubuild/schema");
5142
5846
  var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
5143
5847
  "page",
5144
5848
  "section",
@@ -5152,7 +5856,7 @@ var CORE_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
5152
5856
  "collection"
5153
5857
  ]);
5154
5858
  function validateTemplate(value) {
5155
- const parseResult = import_schema10.TemplateRecordSchema.safeParse(value);
5859
+ const parseResult = import_schema12.TemplateRecordSchema.safeParse(value);
5156
5860
  if (parseResult.success) {
5157
5861
  return {
5158
5862
  valid: true,
@@ -5280,7 +5984,7 @@ function cloneTreeWithFreshIds(root, idGen) {
5280
5984
  function cloneTemplateAsPage(templateOrDoc, options = {}) {
5281
5985
  let sourceDoc;
5282
5986
  let templateOrigin = null;
5283
- if ((0, import_schema10.isTemplateRecord)(templateOrDoc)) {
5987
+ if ((0, import_schema12.isTemplateRecord)(templateOrDoc)) {
5284
5988
  if (!templateOrDoc.document) {
5285
5989
  throw new Error(
5286
5990
  `Template "${templateOrDoc.name}" (${templateOrDoc.id}) does not contain an inline document snapshot to clone from.`
@@ -5297,7 +6001,7 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
5297
6001
  } else {
5298
6002
  throw new Error("Invalid input: expected a TemplateRecord or PageDocument.");
5299
6003
  }
5300
- const existingIdsInSource = new Set((0, import_schema10.collectNodeIds)(sourceDoc.document));
6004
+ const existingIdsInSource = new Set((0, import_schema12.collectNodeIds)(sourceDoc.document));
5301
6005
  const assignedNewIds = /* @__PURE__ */ new Set();
5302
6006
  const prefix = options.idPrefix || `page_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;
5303
6007
  let counter = 1;
@@ -5346,8 +6050,8 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
5346
6050
  }
5347
6051
  function areNodeIdsCompletelyDistinct(docA, docB) {
5348
6052
  if (!docA?.document || !docB?.document) return true;
5349
- const idsA = new Set((0, import_schema10.collectNodeIds)(docA.document));
5350
- 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);
5351
6055
  for (const id of idsB) {
5352
6056
  if (idsA.has(id)) {
5353
6057
  return false;
@@ -5357,7 +6061,7 @@ function areNodeIdsCompletelyDistinct(docA, docB) {
5357
6061
  }
5358
6062
 
5359
6063
  // src/validation/project-validator.ts
5360
- var import_schema11 = require("@kubuild/schema");
6064
+ var import_schema13 = require("@kubuild/schema");
5361
6065
  function walk(node, visit) {
5362
6066
  visit(node);
5363
6067
  for (const child of node.children ?? []) {
@@ -5367,7 +6071,7 @@ function walk(node, visit) {
5367
6071
  function collectArtboardReferences(artboard) {
5368
6072
  const refs = [];
5369
6073
  walk(artboard.document.document, (node) => {
5370
- if (node.type === import_schema11.ARTBOARD_REFERENCE_NODE_TYPE) {
6074
+ if (node.type === import_schema13.ARTBOARD_REFERENCE_NODE_TYPE) {
5371
6075
  const target = node.props?.artboardId;
5372
6076
  refs.push({
5373
6077
  nodeId: node.id,
@@ -5394,7 +6098,7 @@ function validateProject(input, options = {}) {
5394
6098
  artboardErrors
5395
6099
  };
5396
6100
  }
5397
- const parsed = import_schema11.ProjectDocumentSchema.safeParse(input);
6101
+ const parsed = import_schema13.ProjectDocumentSchema.safeParse(input);
5398
6102
  if (!parsed.success) {
5399
6103
  return {
5400
6104
  valid: false,
@@ -5402,7 +6106,7 @@ function validateProject(input, options = {}) {
5402
6106
  errors: [
5403
6107
  {
5404
6108
  code: "PROJECT_SCHEMA_INVALID",
5405
- 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}".`,
5406
6110
  path: "",
5407
6111
  details: { issues: parsed.error.issues }
5408
6112
  }
@@ -5495,7 +6199,7 @@ function validateProject(input, options = {}) {
5495
6199
  function collectArtboardReferenceNodes(root) {
5496
6200
  const refs = [];
5497
6201
  walk(root, (node) => {
5498
- if (node.type === import_schema11.ARTBOARD_REFERENCE_NODE_TYPE) {
6202
+ if (node.type === import_schema13.ARTBOARD_REFERENCE_NODE_TYPE) {
5499
6203
  const target = node.props?.artboardId;
5500
6204
  refs.push({ nodeId: node.id, artboardId: typeof target === "string" ? target : void 0 });
5501
6205
  }
@@ -5744,10 +6448,12 @@ function isFormValid(errors) {
5744
6448
  createPageArtboard,
5745
6449
  createRuntimeStore,
5746
6450
  createTemplateRecord,
6451
+ createTrackingRelayHandler,
5747
6452
  deepClone,
5748
6453
  defaultIdGenerator,
5749
6454
  defaultMigrationRegistry,
5750
6455
  defaultRenameAssetStrategy,
6456
+ dispatchServerTracking,
5751
6457
  duplicateNode,
5752
6458
  evaluateActionCondition,
5753
6459
  evaluateCondition,
@@ -5767,6 +6473,7 @@ function isFormValid(errors) {
5767
6473
  findMissingComponentNodes,
5768
6474
  findNodeById,
5769
6475
  findNodeLocation,
6476
+ generateTrackingEventId,
5770
6477
  getActiveArtboard,
5771
6478
  getAncestorChain,
5772
6479
  getComponentArtboards,
@@ -5776,6 +6483,7 @@ function isFormValid(errors) {
5776
6483
  getPageArtboards,
5777
6484
  getParentNodeId,
5778
6485
  hasTemplateExpressions,
6486
+ hashSha256,
5779
6487
  importPackage,
5780
6488
  importStoraPackage,
5781
6489
  insertNode,
@@ -5793,6 +6501,9 @@ function isFormValid(errors) {
5793
6501
  loadProjectDocument,
5794
6502
  migrateDocument,
5795
6503
  moveNode,
6504
+ normalizeEmail,
6505
+ normalizePhone,
6506
+ normalizeText,
5796
6507
  preflightPackage,
5797
6508
  previewImportPackage,
5798
6509
  remapAssetReferences,
@@ -5804,12 +6515,19 @@ function isFormValid(errors) {
5804
6515
  resolveBinding,
5805
6516
  resolveBindingValue,
5806
6517
  resolvePropertyPath,
6518
+ sanitizeDocumentTracking,
5807
6519
  sanitizeFilename,
5808
6520
  sanitizeHtml,
5809
6521
  sanitizeUrl,
5810
6522
  saveDraftAsTemplate,
6523
+ sendCustomWebhookEvent,
6524
+ sendGa4MeasurementEvent,
6525
+ sendMetaCapiEvent,
6526
+ sendTikTokEventsApi,
5811
6527
  setActiveArtboard,
5812
6528
  sha256Sync,
6529
+ stripDocumentTrackingSecretsInPlace,
6530
+ stripTrackingSecretsInPlace,
5813
6531
  ungroupNodeFrame,
5814
6532
  updateActions,
5815
6533
  updateAnimation,