@kubuild/core 0.4.0 → 0.6.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
@@ -2454,6 +2454,385 @@ function createRuntimeStore(initialState) {
2454
2454
  return new RuntimeStateStore(initialState);
2455
2455
  }
2456
2456
 
2457
+ // src/runtime/server-tracking.ts
2458
+ function normalizeEmail(email) {
2459
+ return (email || "").trim().toLowerCase();
2460
+ }
2461
+ function normalizePhone(phone) {
2462
+ return (phone || "").replace(/[^0-9]/g, "");
2463
+ }
2464
+ function normalizeText(text) {
2465
+ return (text || "").trim().toLowerCase();
2466
+ }
2467
+ async function hashSha256(value) {
2468
+ const normalized = (value || "").trim();
2469
+ if (!normalized) return "";
2470
+ if (typeof globalThis.crypto?.subtle?.digest === "function") {
2471
+ const encoder = new TextEncoder();
2472
+ const data = encoder.encode(normalized);
2473
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
2474
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2475
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2476
+ }
2477
+ throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
2478
+ }
2479
+ function generateTrackingEventId(prefix = "evt") {
2480
+ const timestamp = Date.now().toString(36);
2481
+ const randomPart = Math.random().toString(36).substring(2, 10);
2482
+ return `${prefix}_${timestamp}_${randomPart}`;
2483
+ }
2484
+ async function sendMetaCapiEvent(event, config, options) {
2485
+ const fetcher = options?.fetchFn || globalThis.fetch;
2486
+ if (!config.pixelId) {
2487
+ return {
2488
+ provider: "meta",
2489
+ success: false,
2490
+ error: "Meta Pixel ID is missing"
2491
+ };
2492
+ }
2493
+ const rawUserData = event.userData || {};
2494
+ const hashedUserData = {};
2495
+ if (rawUserData.email) {
2496
+ hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
2497
+ }
2498
+ if (rawUserData.phone) {
2499
+ hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
2500
+ }
2501
+ if (rawUserData.firstName) {
2502
+ hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
2503
+ }
2504
+ if (rawUserData.lastName) {
2505
+ hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
2506
+ }
2507
+ if (rawUserData.city) {
2508
+ hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
2509
+ }
2510
+ if (rawUserData.state) {
2511
+ hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
2512
+ }
2513
+ if (rawUserData.zip) {
2514
+ hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
2515
+ }
2516
+ if (rawUserData.country) {
2517
+ hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
2518
+ }
2519
+ const clientIp = rawUserData.clientIp || options?.clientIp;
2520
+ if (clientIp) hashedUserData.client_ip_address = clientIp;
2521
+ const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
2522
+ if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
2523
+ if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
2524
+ if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
2525
+ if (rawUserData.externalId) {
2526
+ hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
2527
+ }
2528
+ const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
2529
+ const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
2530
+ const capiPayload = {
2531
+ data: [
2532
+ {
2533
+ event_name: event.eventName,
2534
+ event_time: eventTime,
2535
+ event_id: event.eventId,
2536
+ event_source_url: eventSourceUrl,
2537
+ action_source: event.actionSource || "website",
2538
+ user_data: hashedUserData,
2539
+ custom_data: {
2540
+ ...event.params || {},
2541
+ ...event.customData || {}
2542
+ }
2543
+ }
2544
+ ]
2545
+ };
2546
+ if (config.testEventCode) {
2547
+ capiPayload.test_event_code = config.testEventCode;
2548
+ }
2549
+ if (options?.simulateInDebug) {
2550
+ options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
2551
+ return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
2552
+ }
2553
+ try {
2554
+ const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
2555
+ const headers = {
2556
+ "Content-Type": "application/json"
2557
+ };
2558
+ if (config.capiAccessToken) {
2559
+ headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2560
+ }
2561
+ const res = await fetcher(url, {
2562
+ method: "POST",
2563
+ headers,
2564
+ body: JSON.stringify(capiPayload)
2565
+ });
2566
+ const resJson = await res.json().catch(() => ({}));
2567
+ if (!res.ok) {
2568
+ return {
2569
+ provider: "meta",
2570
+ success: false,
2571
+ status: res.status,
2572
+ error: resJson?.error?.message || `HTTP ${res.status}`,
2573
+ data: resJson
2574
+ };
2575
+ }
2576
+ return {
2577
+ provider: "meta",
2578
+ success: true,
2579
+ status: res.status,
2580
+ data: resJson
2581
+ };
2582
+ } catch (err) {
2583
+ return {
2584
+ provider: "meta",
2585
+ success: false,
2586
+ error: err instanceof Error ? err.message : String(err)
2587
+ };
2588
+ }
2589
+ }
2590
+ async function sendTikTokEventsApi(event, config, options) {
2591
+ const fetcher = options?.fetchFn || globalThis.fetch;
2592
+ if (!config.pixelId) {
2593
+ return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
2594
+ }
2595
+ const rawUserData = event.userData || {};
2596
+ const user = {};
2597
+ if (rawUserData.email) {
2598
+ user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
2599
+ }
2600
+ if (rawUserData.phone) {
2601
+ user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
2602
+ }
2603
+ if (rawUserData.clientIp || options?.clientIp) {
2604
+ user.ip = rawUserData.clientIp || options?.clientIp;
2605
+ }
2606
+ if (rawUserData.clientUserAgent || options?.clientUserAgent) {
2607
+ user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
2608
+ }
2609
+ const tiktokPayload = {
2610
+ event_source: "web",
2611
+ event_source_id: config.pixelId,
2612
+ data: [
2613
+ {
2614
+ event: event.eventName,
2615
+ event_id: event.eventId,
2616
+ timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
2617
+ user,
2618
+ properties: {
2619
+ ...event.params || {},
2620
+ ...event.customData || {}
2621
+ },
2622
+ page: {
2623
+ url: event.eventSourceUrl || options?.sourceUrl
2624
+ }
2625
+ }
2626
+ ]
2627
+ };
2628
+ if (config.testEventCode) {
2629
+ tiktokPayload.test_event_code = config.testEventCode;
2630
+ }
2631
+ if (options?.simulateInDebug) {
2632
+ options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
2633
+ return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
2634
+ }
2635
+ try {
2636
+ const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
2637
+ const headers = {
2638
+ "Content-Type": "application/json"
2639
+ };
2640
+ if (config.accessToken) {
2641
+ headers["Access-Token"] = config.accessToken;
2642
+ }
2643
+ const res = await fetcher(url, {
2644
+ method: "POST",
2645
+ headers,
2646
+ body: JSON.stringify(tiktokPayload)
2647
+ });
2648
+ const resJson = await res.json().catch(() => ({}));
2649
+ if (!res.ok) {
2650
+ return {
2651
+ provider: "tiktok",
2652
+ success: false,
2653
+ status: res.status,
2654
+ error: resJson?.message || `HTTP ${res.status}`,
2655
+ data: resJson
2656
+ };
2657
+ }
2658
+ return { provider: "tiktok", success: true, status: res.status, data: resJson };
2659
+ } catch (err) {
2660
+ return {
2661
+ provider: "tiktok",
2662
+ success: false,
2663
+ error: err instanceof Error ? err.message : String(err)
2664
+ };
2665
+ }
2666
+ }
2667
+ async function sendGa4MeasurementEvent(event, config, options) {
2668
+ const fetcher = options?.fetchFn || globalThis.fetch;
2669
+ if (!config.measurementId) {
2670
+ return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
2671
+ }
2672
+ const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
2673
+ const ga4Payload = {
2674
+ client_id: clientId,
2675
+ events: [
2676
+ {
2677
+ name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
2678
+ params: {
2679
+ ...event.params || {},
2680
+ ...event.customData || {},
2681
+ event_id: event.eventId
2682
+ }
2683
+ }
2684
+ ]
2685
+ };
2686
+ if (options?.simulateInDebug) {
2687
+ options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
2688
+ return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2689
+ }
2690
+ 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)}`;
2696
+ }
2697
+ const res = await fetcher(url, {
2698
+ method: "POST",
2699
+ headers: { "Content-Type": "application/json" },
2700
+ body: JSON.stringify(ga4Payload)
2701
+ });
2702
+ return {
2703
+ provider: "google",
2704
+ success: res.ok,
2705
+ status: res.status,
2706
+ data: { status: res.status }
2707
+ };
2708
+ } catch (err) {
2709
+ return {
2710
+ provider: "google",
2711
+ success: false,
2712
+ error: err instanceof Error ? err.message : String(err)
2713
+ };
2714
+ }
2715
+ }
2716
+ async function sendCustomWebhookEvent(event, config, options) {
2717
+ const fetcher = options?.fetchFn || globalThis.fetch;
2718
+ if (!config.endpointUrl) {
2719
+ return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2720
+ }
2721
+ const webhookPayload = {
2722
+ event: event.eventName,
2723
+ eventId: event.eventId,
2724
+ timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
2725
+ userData: event.userData,
2726
+ params: event.params,
2727
+ customData: event.customData,
2728
+ sourceUrl: event.eventSourceUrl || options?.sourceUrl
2729
+ };
2730
+ if (options?.simulateInDebug) {
2731
+ options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2732
+ return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2733
+ }
2734
+ try {
2735
+ const res = await fetcher(config.endpointUrl, {
2736
+ method: "POST",
2737
+ headers: {
2738
+ "Content-Type": "application/json",
2739
+ ...config.headers || {}
2740
+ },
2741
+ body: JSON.stringify(webhookPayload)
2742
+ });
2743
+ const resJson = await res.json().catch(() => ({}));
2744
+ return {
2745
+ provider: "custom",
2746
+ success: res.ok,
2747
+ status: res.status,
2748
+ data: resJson
2749
+ };
2750
+ } catch (err) {
2751
+ return {
2752
+ provider: "custom",
2753
+ success: false,
2754
+ error: err instanceof Error ? err.message : String(err)
2755
+ };
2756
+ }
2757
+ }
2758
+ async function dispatchServerTracking(event, config, options) {
2759
+ const eventId = event.eventId || generateTrackingEventId();
2760
+ const eventWithId = { ...event, eventId };
2761
+ if (!config || config.enabled === false) {
2762
+ return {
2763
+ success: true,
2764
+ eventId,
2765
+ skipped: true,
2766
+ reason: "Tracking is disabled globally",
2767
+ results: {}
2768
+ };
2769
+ }
2770
+ const isDebug = Boolean(config.debugMode);
2771
+ const simulate = isDebug && options?.simulateInDebug !== false;
2772
+ const mergedOptions = {
2773
+ ...options,
2774
+ simulateInDebug: simulate,
2775
+ onLog: (msg, data) => {
2776
+ if (isDebug) {
2777
+ console.log(`[KUBUILD Tracking] ${msg}`, data || "");
2778
+ }
2779
+ options?.onLog?.(msg, data);
2780
+ }
2781
+ };
2782
+ 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
+ );
2791
+ }
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
+ );
2799
+ }
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
+ );
2807
+ }
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
+ );
2815
+ }
2816
+ const settled = await Promise.allSettled(dispatchPromises);
2817
+ const results = {};
2818
+ let overallSuccess = true;
2819
+ for (const item of settled) {
2820
+ if (item.status === "fulfilled") {
2821
+ results[item.value.key] = item.value.result;
2822
+ if (!item.value.result.success) {
2823
+ overallSuccess = false;
2824
+ }
2825
+ } else {
2826
+ overallSuccess = false;
2827
+ }
2828
+ }
2829
+ return {
2830
+ success: overallSuccess,
2831
+ eventId,
2832
+ results
2833
+ };
2834
+ }
2835
+
2457
2836
  // src/io/exporter.ts
2458
2837
  import { zipSync, strToU8 } from "fflate";
2459
2838
  import {
@@ -2719,6 +3098,7 @@ function sanitizeHtml(rawHtml) {
2719
3098
  // src/validation/validator.ts
2720
3099
  function validateDocument(input, options = {}) {
2721
3100
  const errors = [];
3101
+ const warnings = [];
2722
3102
  if (!input || typeof input !== "object" || Array.isArray(input)) {
2723
3103
  return {
2724
3104
  valid: false,
@@ -2729,7 +3109,8 @@ function validateDocument(input, options = {}) {
2729
3109
  message: "Document must be a valid non-null object",
2730
3110
  path: ""
2731
3111
  }
2732
- ]
3112
+ ],
3113
+ warnings: []
2733
3114
  };
2734
3115
  }
2735
3116
  const doc = input;
@@ -2747,7 +3128,8 @@ function validateDocument(input, options = {}) {
2747
3128
  return {
2748
3129
  valid: false,
2749
3130
  success: false,
2750
- errors
3131
+ errors,
3132
+ warnings: []
2751
3133
  };
2752
3134
  }
2753
3135
  }
@@ -2774,7 +3156,8 @@ function validateDocument(input, options = {}) {
2774
3156
  return {
2775
3157
  valid: false,
2776
3158
  success: false,
2777
- errors
3159
+ errors,
3160
+ warnings
2778
3161
  };
2779
3162
  }
2780
3163
  const rootNode = doc.document;
@@ -2813,7 +3196,8 @@ function validateDocument(input, options = {}) {
2813
3196
  seenIds,
2814
3197
  visitedObjects,
2815
3198
  options,
2816
- errors
3199
+ errors,
3200
+ warnings
2817
3201
  );
2818
3202
  if (errors.length === 0) {
2819
3203
  const zodResult = PageDocumentSchema.safeParse(input);
@@ -2831,6 +3215,7 @@ function validateDocument(input, options = {}) {
2831
3215
  valid: true,
2832
3216
  success: true,
2833
3217
  errors: [],
3218
+ warnings,
2834
3219
  data: zodResult.data
2835
3220
  };
2836
3221
  }
@@ -2838,10 +3223,11 @@ function validateDocument(input, options = {}) {
2838
3223
  return {
2839
3224
  valid: errors.length === 0,
2840
3225
  success: errors.length === 0,
2841
- errors
3226
+ errors,
3227
+ warnings
2842
3228
  };
2843
3229
  }
2844
- function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visitedObjects, options, errors) {
3230
+ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visitedObjects, options, errors, warnings) {
2845
3231
  if (visitedObjects.has(nodeObj)) {
2846
3232
  errors.push({
2847
3233
  code: "TREE_CYCLE_DETECTED",
@@ -2950,12 +3336,22 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
2950
3336
  const childIsKnown = !options.componentRegistry || options.componentRegistry.has(childType);
2951
3337
  const childCategory = options.componentRegistry?.get(childType)?.category;
2952
3338
  if (componentDef?.allowedChildren && childType && childIsKnown && !componentDef.allowedChildren.includes(childType) && !componentDef.allowedChildren.includes("*") && !(childCategory && componentDef.allowedChildren.includes(childCategory))) {
2953
- errors.push({
2954
- code: "CHILD_POLICY_VIOLATION",
2955
- message: `Component type "${childType}" is not allowed as a child of "${effectiveNodeType}". Allowed types: ${componentDef.allowedChildren.join(", ")}`,
2956
- path: `${childPath}/type`,
2957
- nodeId: typeof childRecord.id === "string" ? childRecord.id : void 0
2958
- });
3339
+ const violationMessage = `Component type "${childType}" is not allowed as a child of "${effectiveNodeType}". Allowed types: ${componentDef.allowedChildren.join(", ")}`;
3340
+ if (options.strictChildPolicy) {
3341
+ errors.push({
3342
+ code: "CHILD_POLICY_VIOLATION",
3343
+ message: violationMessage,
3344
+ path: `${childPath}/type`,
3345
+ nodeId: typeof childRecord.id === "string" ? childRecord.id : void 0
3346
+ });
3347
+ } else {
3348
+ warnings.push({
3349
+ code: "CHILD_POLICY_VIOLATION",
3350
+ message: violationMessage,
3351
+ path: `${childPath}/type`,
3352
+ nodeId: typeof childRecord.id === "string" ? childRecord.id : void 0
3353
+ });
3354
+ }
2959
3355
  }
2960
3356
  validateNodeRecursive(
2961
3357
  childRecord,
@@ -2964,7 +3360,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
2964
3360
  seenIds,
2965
3361
  visitedObjects,
2966
3362
  options,
2967
- errors
3363
+ errors,
3364
+ warnings
2968
3365
  );
2969
3366
  }
2970
3367
  }
@@ -5624,6 +6021,7 @@ export {
5624
6021
  defaultIdGenerator,
5625
6022
  defaultMigrationRegistry,
5626
6023
  defaultRenameAssetStrategy,
6024
+ dispatchServerTracking,
5627
6025
  duplicateNode,
5628
6026
  evaluateActionCondition,
5629
6027
  evaluateCondition,
@@ -5643,6 +6041,7 @@ export {
5643
6041
  findMissingComponentNodes,
5644
6042
  findNodeById,
5645
6043
  findNodeLocation,
6044
+ generateTrackingEventId,
5646
6045
  getActiveArtboard,
5647
6046
  getAncestorChain,
5648
6047
  getComponentArtboards,
@@ -5652,6 +6051,7 @@ export {
5652
6051
  getPageArtboards,
5653
6052
  getParentNodeId,
5654
6053
  hasTemplateExpressions,
6054
+ hashSha256,
5655
6055
  importPackage,
5656
6056
  importStoraPackage,
5657
6057
  insertNode,
@@ -5669,6 +6069,9 @@ export {
5669
6069
  loadProjectDocument,
5670
6070
  migrateDocument,
5671
6071
  moveNode,
6072
+ normalizeEmail,
6073
+ normalizePhone,
6074
+ normalizeText,
5672
6075
  preflightPackage,
5673
6076
  previewImportPackage,
5674
6077
  remapAssetReferences,
@@ -5684,6 +6087,10 @@ export {
5684
6087
  sanitizeHtml,
5685
6088
  sanitizeUrl,
5686
6089
  saveDraftAsTemplate,
6090
+ sendCustomWebhookEvent,
6091
+ sendGa4MeasurementEvent,
6092
+ sendMetaCapiEvent,
6093
+ sendTikTokEventsApi,
5687
6094
  setActiveArtboard,
5688
6095
  sha256Sync,
5689
6096
  ungroupNodeFrame,