@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.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/document/document-utils.ts
2
- import { SCHEMA_NAME } from "@kubuild/schema";
2
+ import { SCHEMA_NAME, CURRENT_SCHEMA_VERSION } from "@kubuild/schema";
3
3
  function createBlankDocument(title = "Untitled Page") {
4
4
  return {
5
5
  schema: SCHEMA_NAME,
6
- version: "1.0.0",
6
+ version: CURRENT_SCHEMA_VERSION,
7
7
  metadata: {
8
8
  title,
9
9
  description: "",
@@ -889,7 +889,7 @@ import {
889
889
  SCHEMA_NAME as SCHEMA_NAME2,
890
890
  PROJECT_SCHEMA_NAME,
891
891
  CURRENT_PROJECT_SCHEMA_VERSION,
892
- CURRENT_SCHEMA_VERSION,
892
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2,
893
893
  ARTBOARD_REFERENCE_NODE_TYPE
894
894
  } from "@kubuild/schema";
895
895
  var now = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -947,14 +947,14 @@ function createComponentArtboard(node, options = {}) {
947
947
  const triggerId = options.triggerId ?? (typeof node.props?.modalId === "string" && node.props.modalId.trim().length > 0 ? node.props.modalId : node.id);
948
948
  const document = {
949
949
  schema: SCHEMA_NAME2,
950
- version: CURRENT_SCHEMA_VERSION,
950
+ version: CURRENT_SCHEMA_VERSION2,
951
951
  metadata: {
952
952
  title: name,
953
953
  description: "",
954
954
  author: "",
955
955
  tags: [],
956
956
  category: "component",
957
- version: CURRENT_SCHEMA_VERSION,
957
+ version: CURRENT_SCHEMA_VERSION2,
958
958
  createdAt: now(),
959
959
  updatedAt: now()
960
960
  },
@@ -1411,6 +1411,15 @@ var DocumentHistoryManager = class {
1411
1411
  var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1412
1412
  function resolveBinding(binding, context) {
1413
1413
  const segments = binding.key.split(".").filter(Boolean);
1414
+ if (segments.some((segment) => FORBIDDEN_KEY_SEGMENTS.has(segment))) {
1415
+ return applyMissingPolicy(binding);
1416
+ }
1417
+ if (context?.variables && typeof context.variables === "object" && Object.prototype.hasOwnProperty.call(context.variables, binding.key)) {
1418
+ const directValue = context.variables[binding.key];
1419
+ if (directValue !== void 0 && typeof directValue !== "function") {
1420
+ return { status: "resolved", value: directValue };
1421
+ }
1422
+ }
1414
1423
  let current = context?.variables;
1415
1424
  for (const segment of segments) {
1416
1425
  if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
@@ -1982,14 +1991,14 @@ var ActionPipelineExecutor = class {
1982
1991
  async executeSingleStep(step, context, parentSignal, options) {
1983
1992
  const stepStartTime = Date.now();
1984
1993
  if (step.condition && !evaluateActionCondition(step.condition, context)) {
1985
- const skippedResult = {
1994
+ const skippedResult2 = {
1986
1995
  stepId: step.id,
1987
1996
  stepType: step.type,
1988
1997
  status: "skipped",
1989
1998
  durationMs: Date.now() - stepStartTime
1990
1999
  };
1991
- options?.onStepComplete?.(step, skippedResult, context);
1992
- return skippedResult;
2000
+ options?.onStepComplete?.(step, skippedResult2, context);
2001
+ return skippedResult2;
1993
2002
  }
1994
2003
  options?.onStepStart?.(step, context);
1995
2004
  const stepTimeout = step.timeout;
@@ -2481,7 +2490,7 @@ function generateTrackingEventId(prefix = "evt") {
2481
2490
  const randomPart = Math.random().toString(36).substring(2, 10);
2482
2491
  return `${prefix}_${timestamp}_${randomPart}`;
2483
2492
  }
2484
- async function sendMetaCapiEvent(event, config, options) {
2493
+ async function sendMetaCapiEvent(event, config, secrets, options) {
2485
2494
  const fetcher = options?.fetchFn || globalThis.fetch;
2486
2495
  if (!config.pixelId) {
2487
2496
  return {
@@ -2555,9 +2564,10 @@ async function sendMetaCapiEvent(event, config, options) {
2555
2564
  const headers = {
2556
2565
  "Content-Type": "application/json"
2557
2566
  };
2558
- if (config.capiAccessToken) {
2559
- headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2567
+ if (!secrets?.capiAccessToken) {
2568
+ return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
2560
2569
  }
2570
+ headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
2561
2571
  const res = await fetcher(url, {
2562
2572
  method: "POST",
2563
2573
  headers,
@@ -2587,7 +2597,7 @@ async function sendMetaCapiEvent(event, config, options) {
2587
2597
  };
2588
2598
  }
2589
2599
  }
2590
- async function sendTikTokEventsApi(event, config, options) {
2600
+ async function sendTikTokEventsApi(event, config, secrets, options) {
2591
2601
  const fetcher = options?.fetchFn || globalThis.fetch;
2592
2602
  if (!config.pixelId) {
2593
2603
  return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
@@ -2637,9 +2647,10 @@ async function sendTikTokEventsApi(event, config, options) {
2637
2647
  const headers = {
2638
2648
  "Content-Type": "application/json"
2639
2649
  };
2640
- if (config.accessToken) {
2641
- headers["Access-Token"] = config.accessToken;
2650
+ if (!secrets?.accessToken) {
2651
+ return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
2642
2652
  }
2653
+ headers["Access-Token"] = secrets.accessToken;
2643
2654
  const res = await fetcher(url, {
2644
2655
  method: "POST",
2645
2656
  headers,
@@ -2664,7 +2675,7 @@ async function sendTikTokEventsApi(event, config, options) {
2664
2675
  };
2665
2676
  }
2666
2677
  }
2667
- async function sendGa4MeasurementEvent(event, config, options) {
2678
+ async function sendGa4MeasurementEvent(event, config, secrets, options) {
2668
2679
  const fetcher = options?.fetchFn || globalThis.fetch;
2669
2680
  if (!config.measurementId) {
2670
2681
  return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
@@ -2688,12 +2699,10 @@ async function sendGa4MeasurementEvent(event, config, options) {
2688
2699
  return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2689
2700
  }
2690
2701
  try {
2691
- let url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(
2692
- config.measurementId
2693
- )}`;
2694
- if (config.measurementProtocolSecret) {
2695
- url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
2702
+ if (!secrets?.measurementProtocolSecret) {
2703
+ return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
2696
2704
  }
2705
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
2697
2706
  const res = await fetcher(url, {
2698
2707
  method: "POST",
2699
2708
  headers: { "Content-Type": "application/json" },
@@ -2713,11 +2722,8 @@ async function sendGa4MeasurementEvent(event, config, options) {
2713
2722
  };
2714
2723
  }
2715
2724
  }
2716
- async function sendCustomWebhookEvent(event, config, options) {
2725
+ async function sendCustomWebhookEvent(event, _config, secrets, options) {
2717
2726
  const fetcher = options?.fetchFn || globalThis.fetch;
2718
- if (!config.endpointUrl) {
2719
- return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2720
- }
2721
2727
  const webhookPayload = {
2722
2728
  event: event.eventName,
2723
2729
  eventId: event.eventId,
@@ -2731,12 +2737,15 @@ async function sendCustomWebhookEvent(event, config, options) {
2731
2737
  options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2732
2738
  return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2733
2739
  }
2740
+ if (!secrets?.endpointUrl) {
2741
+ return skippedResult("custom", "No custom webhook destination resolved for this credential");
2742
+ }
2734
2743
  try {
2735
- const res = await fetcher(config.endpointUrl, {
2744
+ const res = await fetcher(secrets.endpointUrl, {
2736
2745
  method: "POST",
2737
2746
  headers: {
2738
2747
  "Content-Type": "application/json",
2739
- ...config.headers || {}
2748
+ ...secrets.headers || {}
2740
2749
  },
2741
2750
  body: JSON.stringify(webhookPayload)
2742
2751
  });
@@ -2755,6 +2764,9 @@ async function sendCustomWebhookEvent(event, config, options) {
2755
2764
  };
2756
2765
  }
2757
2766
  }
2767
+ function skippedResult(provider, reason) {
2768
+ return { provider, success: true, skipped: true, reason };
2769
+ }
2758
2770
  async function dispatchServerTracking(event, config, options) {
2759
2771
  const eventId = event.eventId || generateTrackingEventId();
2760
2772
  const eventWithId = { ...event, eventId };
@@ -2779,65 +2791,233 @@ async function dispatchServerTracking(event, config, options) {
2779
2791
  options?.onLog?.(msg, data);
2780
2792
  }
2781
2793
  };
2794
+ const target = options?.provider || "all";
2795
+ const wants = (key) => target === "all" || target === key;
2782
2796
  const providers = config.providers || {};
2783
- const dispatchPromises = [];
2784
- if (providers.meta && providers.meta.enabled !== false && providers.meta.capiEnabled) {
2785
- dispatchPromises.push(
2786
- sendMetaCapiEvent(eventWithId, providers.meta, mergedOptions).then((result) => ({
2787
- key: "meta",
2788
- result
2789
- }))
2790
- );
2797
+ const tasks = [];
2798
+ const meta = providers.meta;
2799
+ if (wants("meta") && meta && meta.enabled !== false && meta.capiEnabled) {
2800
+ tasks.push({
2801
+ key: "meta",
2802
+ credentialId: meta.credentialId,
2803
+ run: (s) => sendMetaCapiEvent(eventWithId, meta, s, mergedOptions)
2804
+ });
2791
2805
  }
2792
- if (providers.tiktok && providers.tiktok.enabled !== false && providers.tiktok.eventsApiEnabled) {
2793
- dispatchPromises.push(
2794
- sendTikTokEventsApi(eventWithId, providers.tiktok, mergedOptions).then((result) => ({
2795
- key: "tiktok",
2796
- result
2797
- }))
2798
- );
2806
+ const tiktok = providers.tiktok;
2807
+ if (wants("tiktok") && tiktok && tiktok.enabled !== false && tiktok.eventsApiEnabled) {
2808
+ tasks.push({
2809
+ key: "tiktok",
2810
+ credentialId: tiktok.credentialId,
2811
+ run: (s) => sendTikTokEventsApi(eventWithId, tiktok, s, mergedOptions)
2812
+ });
2799
2813
  }
2800
- if (providers.google && providers.google.enabled !== false && providers.google.measurementId && providers.google.measurementProtocolSecret) {
2801
- dispatchPromises.push(
2802
- sendGa4MeasurementEvent(eventWithId, providers.google, mergedOptions).then((result) => ({
2803
- key: "google",
2804
- result
2805
- }))
2806
- );
2814
+ const google = providers.google;
2815
+ if (wants("google") && google && google.enabled !== false && google.measurementId) {
2816
+ tasks.push({
2817
+ key: "google",
2818
+ credentialId: google.credentialId,
2819
+ run: (s) => sendGa4MeasurementEvent(eventWithId, google, s, mergedOptions)
2820
+ });
2807
2821
  }
2808
- if (providers.custom && providers.custom.enabled !== false && providers.custom.endpointUrl) {
2809
- dispatchPromises.push(
2810
- sendCustomWebhookEvent(eventWithId, providers.custom, mergedOptions).then((result) => ({
2811
- key: "custom",
2812
- result
2813
- }))
2814
- );
2822
+ const custom = providers.custom;
2823
+ if (wants("custom") && custom && custom.enabled !== false) {
2824
+ tasks.push({
2825
+ key: "custom",
2826
+ credentialId: custom.credentialId,
2827
+ run: (s) => sendCustomWebhookEvent(eventWithId, custom, s, mergedOptions)
2828
+ });
2829
+ }
2830
+ if (tasks.length === 0) {
2831
+ return {
2832
+ success: true,
2833
+ eventId,
2834
+ skipped: true,
2835
+ reason: target === "gtm" ? "GTM is client-side only (dataLayer); no server delivery" : "No server-side tracking provider is enabled",
2836
+ results: {}
2837
+ };
2815
2838
  }
2816
- const settled = await Promise.allSettled(dispatchPromises);
2839
+ const resolve = options?.resolveSecrets;
2840
+ const runTask = async (task) => {
2841
+ let secrets = null;
2842
+ if (resolve) {
2843
+ try {
2844
+ secrets = await resolve({ provider: task.key, credentialId: task.credentialId, documentId: options?.documentId }) ?? null;
2845
+ } catch (err) {
2846
+ return {
2847
+ provider: task.key,
2848
+ success: false,
2849
+ error: `Secret resolution failed: ${err instanceof Error ? err.message : String(err)}`
2850
+ };
2851
+ }
2852
+ }
2853
+ if (!secrets && !simulate) {
2854
+ const reason = resolve ? `No secret resolved for provider "${task.key}"${task.credentialId ? ` (credentialId "${task.credentialId}")` : " (no credentialId linked)"}` : "No secret resolver configured (options.resolveSecrets)";
2855
+ mergedOptions.onLog?.(`[${task.key}] skipped: ${reason}`);
2856
+ return skippedResult(task.key, reason);
2857
+ }
2858
+ return task.run(secrets);
2859
+ };
2860
+ const settled = await Promise.allSettled(tasks.map((task) => runTask(task)));
2817
2861
  const results = {};
2818
2862
  let overallSuccess = true;
2819
- for (const item of settled) {
2863
+ let delivered = 0;
2864
+ settled.forEach((item, index) => {
2865
+ const key = tasks[index].key;
2820
2866
  if (item.status === "fulfilled") {
2821
- results[item.value.key] = item.value.result;
2822
- if (!item.value.result.success) {
2823
- overallSuccess = false;
2824
- }
2867
+ results[key] = item.value;
2868
+ if (!item.value.success) overallSuccess = false;
2869
+ if (!item.value.skipped) delivered++;
2825
2870
  } else {
2826
2871
  overallSuccess = false;
2872
+ results[key] = {
2873
+ provider: key,
2874
+ success: false,
2875
+ error: item.reason instanceof Error ? item.reason.message : String(item.reason)
2876
+ };
2827
2877
  }
2828
- }
2878
+ });
2879
+ const allSkipped = delivered === 0 && overallSuccess;
2829
2880
  return {
2830
2881
  success: overallSuccess,
2831
2882
  eventId,
2883
+ ...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
2832
2884
  results
2833
2885
  };
2834
2886
  }
2835
2887
 
2888
+ // src/runtime/tracking-relay.ts
2889
+ import {
2890
+ TRACKING_RELAY_PROTOCOL_VERSION,
2891
+ TrackingRelayRequestSchema
2892
+ } from "@kubuild/schema";
2893
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
2894
+ function defaultClientIp(request) {
2895
+ const forwarded = request.headers.get("x-forwarded-for");
2896
+ if (forwarded) {
2897
+ const first = forwarded.split(",")[0]?.trim();
2898
+ if (first) return first;
2899
+ }
2900
+ return request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
2901
+ }
2902
+ function isOriginAllowed(origin, allowed) {
2903
+ if (!allowed) return true;
2904
+ if (!origin) return false;
2905
+ if (typeof allowed === "function") return allowed(origin);
2906
+ return allowed.includes(origin);
2907
+ }
2908
+ function createTrackingRelayHandler(options) {
2909
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
2910
+ return async (request) => {
2911
+ const origin = request.headers.get("origin");
2912
+ const corsHeaders = {};
2913
+ if (options.allowedOrigins && origin && isOriginAllowed(origin, options.allowedOrigins)) {
2914
+ corsHeaders["Access-Control-Allow-Origin"] = origin;
2915
+ corsHeaders["Vary"] = "Origin";
2916
+ }
2917
+ const json = (body, status, extra) => new Response(JSON.stringify(body), {
2918
+ status,
2919
+ headers: { "Content-Type": "application/json", ...corsHeaders, ...extra || {} }
2920
+ });
2921
+ const fail = (code, message, status, extra) => json({ version: TRACKING_RELAY_PROTOCOL_VERSION, success: false, error: { code, message } }, status, extra);
2922
+ if (!isOriginAllowed(origin, options.allowedOrigins)) {
2923
+ return fail("FORBIDDEN_ORIGIN", "Origin is not allowed", 403);
2924
+ }
2925
+ if (request.method === "OPTIONS") {
2926
+ return new Response(null, {
2927
+ status: 204,
2928
+ headers: {
2929
+ ...corsHeaders,
2930
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
2931
+ "Access-Control-Allow-Headers": "Content-Type",
2932
+ "Access-Control-Max-Age": "600"
2933
+ }
2934
+ });
2935
+ }
2936
+ if (request.method !== "POST") {
2937
+ return fail("METHOD_NOT_ALLOWED", "Only POST requests are accepted", 405, { Allow: "POST, OPTIONS" });
2938
+ }
2939
+ try {
2940
+ const text = await request.text();
2941
+ if (text.length > maxBodyBytes) {
2942
+ return fail("INVALID_REQUEST", `Request body exceeds ${maxBodyBytes} bytes`, 413);
2943
+ }
2944
+ let raw;
2945
+ try {
2946
+ raw = JSON.parse(text);
2947
+ } catch {
2948
+ return fail("INVALID_REQUEST", "Request body must be valid JSON", 400);
2949
+ }
2950
+ if (raw && typeof raw === "object" && "version" in raw && raw.version !== TRACKING_RELAY_PROTOCOL_VERSION) {
2951
+ return fail(
2952
+ "UNSUPPORTED_VERSION",
2953
+ `Unsupported relay protocol version; expected ${TRACKING_RELAY_PROTOCOL_VERSION}`,
2954
+ 400
2955
+ );
2956
+ }
2957
+ const parsed = TrackingRelayRequestSchema.safeParse(raw);
2958
+ if (!parsed.success) {
2959
+ const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
2960
+ return fail("INVALID_REQUEST", `Invalid relay request: ${issues}`, 400);
2961
+ }
2962
+ const body = parsed.data;
2963
+ const config = await options.getConfig({
2964
+ request,
2965
+ documentId: body.documentId,
2966
+ credentialId: body.credentialId,
2967
+ provider: body.provider
2968
+ });
2969
+ if (!config) {
2970
+ return fail("CONFIG_NOT_FOUND", "No tracking configuration found for this page", 404);
2971
+ }
2972
+ const clientIp = (options.getClientIp ?? defaultClientIp)(request);
2973
+ const clientUserAgent = request.headers.get("user-agent") || void 0;
2974
+ const userData = { ...body.event.userData || {} };
2975
+ delete userData.clientIp;
2976
+ delete userData.clientUserAgent;
2977
+ const event = { ...body.event, userData };
2978
+ const outcome = await dispatchServerTracking(event, config, {
2979
+ resolveSecrets: options.resolveSecrets,
2980
+ documentId: body.documentId,
2981
+ provider: body.provider,
2982
+ fetchFn: options.fetchFn,
2983
+ clientIp,
2984
+ clientUserAgent,
2985
+ onLog: options.onLog
2986
+ });
2987
+ const results = {};
2988
+ for (const [key, r] of Object.entries(outcome.results)) {
2989
+ results[key] = {
2990
+ provider: r.provider,
2991
+ success: r.success,
2992
+ ...r.status !== void 0 ? { status: r.status } : {},
2993
+ ...r.skipped ? { skipped: true } : {},
2994
+ ...r.reason ? { reason: r.reason } : {},
2995
+ ...r.error ? { error: r.error } : {}
2996
+ };
2997
+ }
2998
+ return json(
2999
+ {
3000
+ version: TRACKING_RELAY_PROTOCOL_VERSION,
3001
+ success: outcome.success,
3002
+ eventId: outcome.eventId,
3003
+ ...outcome.skipped ? { skipped: true } : {},
3004
+ ...outcome.reason ? { reason: outcome.reason } : {},
3005
+ results
3006
+ },
3007
+ 200
3008
+ );
3009
+ } catch (err) {
3010
+ options.onLog?.("[Tracking relay] internal error", err);
3011
+ return fail("INTERNAL_ERROR", "Tracking relay failed", 500);
3012
+ }
3013
+ };
3014
+ }
3015
+
2836
3016
  // src/io/exporter.ts
2837
3017
  import { zipSync, strToU8 } from "fflate";
2838
3018
  import {
2839
3019
  SCHEMA_NAME as SCHEMA_NAME4,
2840
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2
3020
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
2841
3021
  } from "@kubuild/schema";
2842
3022
 
2843
3023
  // src/validation/validator.ts
@@ -3380,7 +3560,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3380
3560
  `${currentPath}/props`,
3381
3561
  effectiveNodeId,
3382
3562
  options,
3383
- errors
3563
+ errors,
3564
+ warnings
3384
3565
  );
3385
3566
  }
3386
3567
  }
@@ -3395,7 +3576,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3395
3576
  }
3396
3577
  }
3397
3578
  }
3398
- function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3579
+ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors, warnings) {
3580
+ const checkAssets = options.checkAssetReferences !== false;
3581
+ const checkVariables = options.checkVariableBindings !== false;
3582
+ const checkActions = options.checkActionBindings !== false;
3399
3583
  for (const [key, value] of Object.entries(propsObj)) {
3400
3584
  const currentPath = `${propsPath}/${key}`;
3401
3585
  if (!value || typeof value !== "object") {
@@ -3409,7 +3593,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3409
3593
  `${currentPath}/${index}`,
3410
3594
  nodeId,
3411
3595
  options,
3412
- errors
3596
+ errors,
3597
+ warnings
3413
3598
  );
3414
3599
  }
3415
3600
  });
@@ -3417,7 +3602,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3417
3602
  }
3418
3603
  const record = value;
3419
3604
  if (record.type === "asset") {
3420
- if (typeof record.assetId !== "string" || record.assetId.trim().length === 0) {
3605
+ if (!checkAssets) continue;
3606
+ const assetId = typeof record.assetId === "string" && record.assetId.trim().length > 0 ? record.assetId : void 0;
3607
+ if (assetId === void 0) {
3421
3608
  errors.push({
3422
3609
  code: "INVALID_ASSET_REFERENCE",
3423
3610
  message: 'Asset reference must have a non-empty "assetId"',
@@ -3433,7 +3620,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3433
3620
  nodeId
3434
3621
  });
3435
3622
  }
3623
+ if (assetId !== void 0 && options.knownAssetIds && !isKnownId(options.knownAssetIds, assetId)) {
3624
+ if (typeof record.fallbackUrl === "string" && record.fallbackUrl.length > 0) {
3625
+ warnings.push({
3626
+ code: "UNRESOLVED_ASSET_REFERENCE",
3627
+ message: `Asset "${assetId}" is not among the known assets; its fallbackUrl will be used`,
3628
+ path: `${currentPath}/assetId`,
3629
+ nodeId,
3630
+ details: { assetId }
3631
+ });
3632
+ } else {
3633
+ errors.push({
3634
+ code: "INVALID_ASSET_REFERENCE",
3635
+ message: `Asset "${assetId}" is not among the known assets and has no fallbackUrl`,
3636
+ path: `${currentPath}/assetId`,
3637
+ nodeId,
3638
+ details: { assetId }
3639
+ });
3640
+ }
3641
+ }
3436
3642
  } else if (record.type === "variable") {
3643
+ if (!checkVariables) continue;
3437
3644
  if (typeof record.key !== "string" || record.key.trim().length === 0) {
3438
3645
  errors.push({
3439
3646
  code: "INVALID_VARIABLE_BINDING",
@@ -3443,6 +3650,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3443
3650
  });
3444
3651
  }
3445
3652
  } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
3653
+ if (!checkActions) continue;
3446
3654
  if (typeof record.type !== "string" || record.type.trim().length === 0) {
3447
3655
  errors.push({
3448
3656
  code: "INVALID_ACTION_BINDING",
@@ -3452,10 +3660,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3452
3660
  });
3453
3661
  }
3454
3662
  } else {
3455
- validatePropsBindings(record, currentPath, nodeId, options, errors);
3663
+ validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
3456
3664
  }
3457
3665
  }
3458
3666
  }
3667
+ function isKnownId(ids, id) {
3668
+ return Array.isArray(ids) ? ids.includes(id) : ids.has(id);
3669
+ }
3670
+
3671
+ // src/io/tracking-sanitizer.ts
3672
+ import { LEGACY_TRACKING_SECRET_KEYS } from "@kubuild/schema";
3673
+ function stripTrackingSecretsInPlace(tracking, basePath = "tracking") {
3674
+ const removed = [];
3675
+ if (!tracking || typeof tracking !== "object" || Array.isArray(tracking)) return removed;
3676
+ const providers = tracking.providers;
3677
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) return removed;
3678
+ for (const provider of Object.keys(LEGACY_TRACKING_SECRET_KEYS)) {
3679
+ const cfg = providers[provider];
3680
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) continue;
3681
+ for (const key of LEGACY_TRACKING_SECRET_KEYS[provider]) {
3682
+ if (Object.prototype.hasOwnProperty.call(cfg, key)) {
3683
+ delete cfg[key];
3684
+ removed.push(`${basePath}.providers.${provider}.${key}`);
3685
+ }
3686
+ }
3687
+ }
3688
+ return removed;
3689
+ }
3690
+ function stripDocumentTrackingSecretsInPlace(doc) {
3691
+ const removed = [];
3692
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return removed;
3693
+ const record = doc;
3694
+ removed.push(...stripTrackingSecretsInPlace(record.tracking, "tracking"));
3695
+ const metadata = record.metadata;
3696
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
3697
+ removed.push(
3698
+ ...stripTrackingSecretsInPlace(metadata.tracking, "metadata.tracking")
3699
+ );
3700
+ }
3701
+ if (Array.isArray(record.artboards)) {
3702
+ record.artboards.forEach((artboard, index) => {
3703
+ if (artboard && typeof artboard === "object") {
3704
+ const inner = artboard.document;
3705
+ for (const path of stripDocumentTrackingSecretsInPlace(inner)) {
3706
+ removed.push(`artboards.${index}.document.${path}`);
3707
+ }
3708
+ }
3709
+ });
3710
+ }
3711
+ return removed;
3712
+ }
3713
+ function sanitizeDocumentTracking(doc) {
3714
+ if (!doc || typeof doc !== "object") return { document: doc, removed: [] };
3715
+ const copy = JSON.parse(JSON.stringify(doc));
3716
+ const removed = stripDocumentTrackingSecretsInPlace(copy);
3717
+ return { document: copy, removed };
3718
+ }
3459
3719
 
3460
3720
  // src/io/exporter.ts
3461
3721
  function sha256Sync(data) {
@@ -3676,6 +3936,7 @@ async function exportPackage(document, options = {}) {
3676
3936
  };
3677
3937
  }
3678
3938
  const pageDoc = JSON.parse(JSON.stringify(validation.data));
3939
+ stripDocumentTrackingSecretsInPlace(pageDoc);
3679
3940
  if (options.metadata) {
3680
3941
  pageDoc.metadata = {
3681
3942
  ...pageDoc.metadata || {
@@ -3800,7 +4061,7 @@ async function exportPackage(document, options = {}) {
3800
4061
  };
3801
4062
  const manifest = {
3802
4063
  schema: SCHEMA_NAME4,
3803
- schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION2,
4064
+ schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION3,
3804
4065
  packageVersion: options.packageVersion || "1.0.0",
3805
4066
  builderCompatibility: options.builderCompatibility || ">=0.1.0",
3806
4067
  requiredComponents: requirements.requiredComponents,
@@ -3827,12 +4088,12 @@ var exportStoraPackage = exportPackage;
3827
4088
  import { unzipSync, strFromU8 } from "fflate";
3828
4089
  import {
3829
4090
  ManifestSchema,
3830
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4
4091
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION5
3831
4092
  } from "@kubuild/schema";
3832
4093
 
3833
4094
  // src/io/migration.ts
3834
4095
  import {
3835
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3,
4096
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4,
3836
4097
  SCHEMA_NAME as SCHEMA_NAME5
3837
4098
  } from "@kubuild/schema";
3838
4099
  var MigrationRegistry = class {
@@ -3987,17 +4248,36 @@ defaultMigrationRegistry.register({
3987
4248
  return migrated;
3988
4249
  }
3989
4250
  });
3990
- function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4251
+ defaultMigrationRegistry.register({
4252
+ fromVersion: "1.0.0",
4253
+ toVersion: "1.1.0",
4254
+ description: "Remove tracking secrets and server destinations (capiAccessToken, accessToken, measurementProtocolSecret, serverRelayUrl, custom endpointUrl/headers) from the document",
4255
+ migrate: (rawDoc, context) => {
4256
+ const migrated = deepClone(rawDoc);
4257
+ migrated.version = "1.1.0";
4258
+ const removed = stripDocumentTrackingSecretsInPlace(migrated);
4259
+ if (removed.length > 0) {
4260
+ context.warn({
4261
+ code: "TRACKING_SECRET_REMOVED",
4262
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are now stored by the host and resolved server-side).",
4263
+ step: "1.0.0->1.1.0",
4264
+ paths: removed
4265
+ });
4266
+ }
4267
+ return migrated;
4268
+ }
4269
+ });
4270
+ function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3991
4271
  if (!sourceVersion || !targetVersion) return false;
3992
4272
  if (sourceVersion === targetVersion) return true;
3993
4273
  return registry.hasPath(sourceVersion, targetVersion);
3994
4274
  }
3995
- function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4275
+ function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3996
4276
  if (!sourceVersion || !targetVersion) return null;
3997
4277
  return registry.findPath(sourceVersion, targetVersion);
3998
4278
  }
3999
4279
  function migrateDocument(rawDocument, options = {}) {
4000
- const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION3;
4280
+ const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION4;
4001
4281
  const dryRun = options.dryRun ?? false;
4002
4282
  const registry = options.registry ?? defaultMigrationRegistry;
4003
4283
  const validate = options.validate ?? true;
@@ -4025,6 +4305,14 @@ function migrateDocument(rawDocument, options = {}) {
4025
4305
  const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
4026
4306
  if (sourceVersion === targetVersion) {
4027
4307
  const cloned = deepClone(doc);
4308
+ const removedSecrets = stripDocumentTrackingSecretsInPlace(cloned);
4309
+ const currentWarnings = removedSecrets.length > 0 ? [
4310
+ {
4311
+ code: "TRACKING_SECRET_REMOVED",
4312
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are stored by the host and resolved server-side).",
4313
+ paths: removedSecrets
4314
+ }
4315
+ ] : [];
4028
4316
  if (validate) {
4029
4317
  const validation = validateDocument(cloned);
4030
4318
  if (!validation.valid) {
@@ -4056,7 +4344,8 @@ function migrateDocument(rawDocument, options = {}) {
4056
4344
  targetVersion,
4057
4345
  migrationPath: [sourceVersion],
4058
4346
  stepsApplied: 0,
4059
- dryRun
4347
+ dryRun,
4348
+ ...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
4060
4349
  }
4061
4350
  };
4062
4351
  }
@@ -4096,6 +4385,8 @@ function migrateDocument(rawDocument, options = {}) {
4096
4385
  }
4097
4386
  let currentDoc = deepClone(doc);
4098
4387
  let stepsApplied = 0;
4388
+ const warnings = [];
4389
+ const stepContext = { warn: (w) => warnings.push(w) };
4099
4390
  for (let i = 0; i < path.length - 1; i++) {
4100
4391
  const fromVer = path[i];
4101
4392
  const toVer = path[i + 1];
@@ -4121,7 +4412,7 @@ function migrateDocument(rawDocument, options = {}) {
4121
4412
  };
4122
4413
  }
4123
4414
  try {
4124
- currentDoc = step.migrate(currentDoc);
4415
+ currentDoc = step.migrate(currentDoc, stepContext);
4125
4416
  stepsApplied++;
4126
4417
  } catch (err) {
4127
4418
  const error = {
@@ -4177,7 +4468,8 @@ function migrateDocument(rawDocument, options = {}) {
4177
4468
  targetVersion,
4178
4469
  migrationPath: path,
4179
4470
  stepsApplied,
4180
- dryRun: false
4471
+ dryRun: false,
4472
+ ...warnings.length > 0 ? { warnings } : {}
4181
4473
  }
4182
4474
  };
4183
4475
  }
@@ -4212,7 +4504,7 @@ async function preflightPackage(archiveData, options = {}) {
4212
4504
  ...DEFAULT_SECURITY_LIMITS,
4213
4505
  ...options.securityLimits
4214
4506
  };
4215
- const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4;
4507
+ const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5;
4216
4508
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4217
4509
  const diagnostics = [];
4218
4510
  let dependencyPolicy = options.dependencyPolicy || "cancel";
@@ -4375,6 +4667,16 @@ async function preflightPackage(archiveData, options = {}) {
4375
4667
  let rawPageDoc;
4376
4668
  try {
4377
4669
  rawPageDoc = JSON.parse(strFromU8(pageEntry));
4670
+ const secretScan = sanitizeDocumentTracking(rawPageDoc);
4671
+ if (secretScan.removed.length > 0) {
4672
+ diagnostics.push({
4673
+ code: "TRACKING_SECRET_REMOVED",
4674
+ severity: "warning",
4675
+ message: "Tracking secrets found in page.json will be removed on import; re-link credentials via credentialId.",
4676
+ path: "page.json",
4677
+ details: { paths: secretScan.removed }
4678
+ });
4679
+ }
4378
4680
  const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
4379
4681
  if (pageProtoCheck.found) {
4380
4682
  diagnostics.push({
@@ -4621,7 +4923,7 @@ function buildReport(valid, canImport, fields) {
4621
4923
  return {
4622
4924
  valid,
4623
4925
  canImport,
4624
- targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION4,
4926
+ targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION5,
4625
4927
  requiresMigration: fields.requiresMigration || false,
4626
4928
  missingComponents: fields.missingComponents || [],
4627
4929
  missingCapabilities: fields.missingCapabilities || [],
@@ -4694,7 +4996,7 @@ async function importPackage(archiveData, options = {}) {
4694
4996
  if (preflight.requiresMigration) {
4695
4997
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4696
4998
  const migrationRes = migrateDocument(pageRaw, {
4697
- targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4,
4999
+ targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5,
4698
5000
  registry: migrationRegistry,
4699
5001
  validate: true
4700
5002
  });
@@ -4716,13 +5018,24 @@ async function importPackage(archiveData, options = {}) {
4716
5018
  } else {
4717
5019
  finalDocument = pageRaw;
4718
5020
  }
5021
+ const importWarnings = [];
5022
+ const strippedSecrets = stripDocumentTrackingSecretsInPlace(finalDocument);
5023
+ if (strippedSecrets.length > 0) {
5024
+ importWarnings.push({
5025
+ code: "TRACKING_SECRET_REMOVED",
5026
+ severity: "warning",
5027
+ message: "Tracking secret removed from imported document; re-link credential via credentialId.",
5028
+ path: "page.json",
5029
+ details: { paths: strippedSecrets }
5030
+ });
5031
+ }
4719
5032
  let metadata = finalDocument.metadata || {
4720
5033
  title: "Imported Page",
4721
5034
  description: "",
4722
5035
  author: "",
4723
5036
  tags: [],
4724
5037
  category: "general",
4725
- version: finalDocument.version || CURRENT_SCHEMA_VERSION4
5038
+ version: finalDocument.version || CURRENT_SCHEMA_VERSION5
4726
5039
  };
4727
5040
  if (unzipped["metadata.json"]) {
4728
5041
  try {
@@ -4874,7 +5187,8 @@ async function importPackage(archiveData, options = {}) {
4874
5187
  metadata,
4875
5188
  extractedAssets,
4876
5189
  renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
4877
- preflight
5190
+ preflight,
5191
+ ...importWarnings.length > 0 ? { warnings: importWarnings } : {}
4878
5192
  };
4879
5193
  }
4880
5194
  var importStoraPackage = importPackage;
@@ -6017,6 +6331,7 @@ export {
6017
6331
  createPageArtboard,
6018
6332
  createRuntimeStore,
6019
6333
  createTemplateRecord,
6334
+ createTrackingRelayHandler,
6020
6335
  deepClone,
6021
6336
  defaultIdGenerator,
6022
6337
  defaultMigrationRegistry,
@@ -6083,6 +6398,7 @@ export {
6083
6398
  resolveBinding,
6084
6399
  resolveBindingValue,
6085
6400
  resolvePropertyPath,
6401
+ sanitizeDocumentTracking,
6086
6402
  sanitizeFilename,
6087
6403
  sanitizeHtml,
6088
6404
  sanitizeUrl,
@@ -6093,6 +6409,8 @@ export {
6093
6409
  sendTikTokEventsApi,
6094
6410
  setActiveArtboard,
6095
6411
  sha256Sync,
6412
+ stripDocumentTrackingSecretsInPlace,
6413
+ stripTrackingSecretsInPlace,
6096
6414
  ungroupNodeFrame,
6097
6415
  updateActions,
6098
6416
  updateAnimation,