@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/document/document-utils.d.ts.map +1 -1
- package/dist/index.cjs +767 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +735 -28
- package/dist/index.js.map +1 -1
- package/dist/io/exporter.d.ts.map +1 -1
- package/dist/io/importer.d.ts +4 -2
- package/dist/io/importer.d.ts.map +1 -1
- package/dist/io/index.d.ts +1 -0
- package/dist/io/index.d.ts.map +1 -1
- package/dist/io/migration.d.ts +21 -2
- package/dist/io/migration.d.ts.map +1 -1
- package/dist/io/tracking-sanitizer.d.ts +23 -0
- package/dist/io/tracking-sanitizer.d.ts.map +1 -0
- package/dist/runtime/binding-resolver.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/server-tracking.d.ts +143 -0
- package/dist/runtime/server-tracking.d.ts.map +1 -0
- package/dist/runtime/tracking-relay.d.ts +44 -0
- package/dist/runtime/tracking-relay.d.ts.map +1 -0
- package/dist/types/interfaces.d.ts +37 -1
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/validation/validator.d.ts +24 -1
- package/dist/validation/validator.d.ts.map +1 -1
- package/package.json +2 -2
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:
|
|
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:
|
|
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:
|
|
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
|
|
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,
|
|
1992
|
-
return
|
|
2000
|
+
options?.onStepComplete?.(step, skippedResult2, context);
|
|
2001
|
+
return skippedResult2;
|
|
1993
2002
|
}
|
|
1994
2003
|
options?.onStepStart?.(step, context);
|
|
1995
2004
|
const stepTimeout = step.timeout;
|
|
@@ -2454,11 +2463,561 @@ function createRuntimeStore(initialState) {
|
|
|
2454
2463
|
return new RuntimeStateStore(initialState);
|
|
2455
2464
|
}
|
|
2456
2465
|
|
|
2466
|
+
// src/runtime/server-tracking.ts
|
|
2467
|
+
function normalizeEmail(email) {
|
|
2468
|
+
return (email || "").trim().toLowerCase();
|
|
2469
|
+
}
|
|
2470
|
+
function normalizePhone(phone) {
|
|
2471
|
+
return (phone || "").replace(/[^0-9]/g, "");
|
|
2472
|
+
}
|
|
2473
|
+
function normalizeText(text) {
|
|
2474
|
+
return (text || "").trim().toLowerCase();
|
|
2475
|
+
}
|
|
2476
|
+
async function hashSha256(value) {
|
|
2477
|
+
const normalized = (value || "").trim();
|
|
2478
|
+
if (!normalized) return "";
|
|
2479
|
+
if (typeof globalThis.crypto?.subtle?.digest === "function") {
|
|
2480
|
+
const encoder = new TextEncoder();
|
|
2481
|
+
const data = encoder.encode(normalized);
|
|
2482
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
2483
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
2484
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2485
|
+
}
|
|
2486
|
+
throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
|
|
2487
|
+
}
|
|
2488
|
+
function generateTrackingEventId(prefix = "evt") {
|
|
2489
|
+
const timestamp = Date.now().toString(36);
|
|
2490
|
+
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
2491
|
+
return `${prefix}_${timestamp}_${randomPart}`;
|
|
2492
|
+
}
|
|
2493
|
+
async function sendMetaCapiEvent(event, config, secrets, options) {
|
|
2494
|
+
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2495
|
+
if (!config.pixelId) {
|
|
2496
|
+
return {
|
|
2497
|
+
provider: "meta",
|
|
2498
|
+
success: false,
|
|
2499
|
+
error: "Meta Pixel ID is missing"
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
const rawUserData = event.userData || {};
|
|
2503
|
+
const hashedUserData = {};
|
|
2504
|
+
if (rawUserData.email) {
|
|
2505
|
+
hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
|
|
2506
|
+
}
|
|
2507
|
+
if (rawUserData.phone) {
|
|
2508
|
+
hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
|
|
2509
|
+
}
|
|
2510
|
+
if (rawUserData.firstName) {
|
|
2511
|
+
hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
|
|
2512
|
+
}
|
|
2513
|
+
if (rawUserData.lastName) {
|
|
2514
|
+
hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
|
|
2515
|
+
}
|
|
2516
|
+
if (rawUserData.city) {
|
|
2517
|
+
hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
|
|
2518
|
+
}
|
|
2519
|
+
if (rawUserData.state) {
|
|
2520
|
+
hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
|
|
2521
|
+
}
|
|
2522
|
+
if (rawUserData.zip) {
|
|
2523
|
+
hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
|
|
2524
|
+
}
|
|
2525
|
+
if (rawUserData.country) {
|
|
2526
|
+
hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
|
|
2527
|
+
}
|
|
2528
|
+
const clientIp = rawUserData.clientIp || options?.clientIp;
|
|
2529
|
+
if (clientIp) hashedUserData.client_ip_address = clientIp;
|
|
2530
|
+
const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
|
|
2531
|
+
if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
|
|
2532
|
+
if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
|
|
2533
|
+
if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
|
|
2534
|
+
if (rawUserData.externalId) {
|
|
2535
|
+
hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
|
|
2536
|
+
}
|
|
2537
|
+
const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
|
|
2538
|
+
const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
|
|
2539
|
+
const capiPayload = {
|
|
2540
|
+
data: [
|
|
2541
|
+
{
|
|
2542
|
+
event_name: event.eventName,
|
|
2543
|
+
event_time: eventTime,
|
|
2544
|
+
event_id: event.eventId,
|
|
2545
|
+
event_source_url: eventSourceUrl,
|
|
2546
|
+
action_source: event.actionSource || "website",
|
|
2547
|
+
user_data: hashedUserData,
|
|
2548
|
+
custom_data: {
|
|
2549
|
+
...event.params || {},
|
|
2550
|
+
...event.customData || {}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
]
|
|
2554
|
+
};
|
|
2555
|
+
if (config.testEventCode) {
|
|
2556
|
+
capiPayload.test_event_code = config.testEventCode;
|
|
2557
|
+
}
|
|
2558
|
+
if (options?.simulateInDebug) {
|
|
2559
|
+
options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
|
|
2560
|
+
return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
|
|
2561
|
+
}
|
|
2562
|
+
try {
|
|
2563
|
+
const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
|
|
2564
|
+
const headers = {
|
|
2565
|
+
"Content-Type": "application/json"
|
|
2566
|
+
};
|
|
2567
|
+
if (!secrets?.capiAccessToken) {
|
|
2568
|
+
return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
|
|
2569
|
+
}
|
|
2570
|
+
headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
|
|
2571
|
+
const res = await fetcher(url, {
|
|
2572
|
+
method: "POST",
|
|
2573
|
+
headers,
|
|
2574
|
+
body: JSON.stringify(capiPayload)
|
|
2575
|
+
});
|
|
2576
|
+
const resJson = await res.json().catch(() => ({}));
|
|
2577
|
+
if (!res.ok) {
|
|
2578
|
+
return {
|
|
2579
|
+
provider: "meta",
|
|
2580
|
+
success: false,
|
|
2581
|
+
status: res.status,
|
|
2582
|
+
error: resJson?.error?.message || `HTTP ${res.status}`,
|
|
2583
|
+
data: resJson
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
return {
|
|
2587
|
+
provider: "meta",
|
|
2588
|
+
success: true,
|
|
2589
|
+
status: res.status,
|
|
2590
|
+
data: resJson
|
|
2591
|
+
};
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
return {
|
|
2594
|
+
provider: "meta",
|
|
2595
|
+
success: false,
|
|
2596
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
async function sendTikTokEventsApi(event, config, secrets, options) {
|
|
2601
|
+
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2602
|
+
if (!config.pixelId) {
|
|
2603
|
+
return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
|
|
2604
|
+
}
|
|
2605
|
+
const rawUserData = event.userData || {};
|
|
2606
|
+
const user = {};
|
|
2607
|
+
if (rawUserData.email) {
|
|
2608
|
+
user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
|
|
2609
|
+
}
|
|
2610
|
+
if (rawUserData.phone) {
|
|
2611
|
+
user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
|
|
2612
|
+
}
|
|
2613
|
+
if (rawUserData.clientIp || options?.clientIp) {
|
|
2614
|
+
user.ip = rawUserData.clientIp || options?.clientIp;
|
|
2615
|
+
}
|
|
2616
|
+
if (rawUserData.clientUserAgent || options?.clientUserAgent) {
|
|
2617
|
+
user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
|
|
2618
|
+
}
|
|
2619
|
+
const tiktokPayload = {
|
|
2620
|
+
event_source: "web",
|
|
2621
|
+
event_source_id: config.pixelId,
|
|
2622
|
+
data: [
|
|
2623
|
+
{
|
|
2624
|
+
event: event.eventName,
|
|
2625
|
+
event_id: event.eventId,
|
|
2626
|
+
timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
|
|
2627
|
+
user,
|
|
2628
|
+
properties: {
|
|
2629
|
+
...event.params || {},
|
|
2630
|
+
...event.customData || {}
|
|
2631
|
+
},
|
|
2632
|
+
page: {
|
|
2633
|
+
url: event.eventSourceUrl || options?.sourceUrl
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
]
|
|
2637
|
+
};
|
|
2638
|
+
if (config.testEventCode) {
|
|
2639
|
+
tiktokPayload.test_event_code = config.testEventCode;
|
|
2640
|
+
}
|
|
2641
|
+
if (options?.simulateInDebug) {
|
|
2642
|
+
options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
|
|
2643
|
+
return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
|
|
2644
|
+
}
|
|
2645
|
+
try {
|
|
2646
|
+
const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
|
|
2647
|
+
const headers = {
|
|
2648
|
+
"Content-Type": "application/json"
|
|
2649
|
+
};
|
|
2650
|
+
if (!secrets?.accessToken) {
|
|
2651
|
+
return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
|
|
2652
|
+
}
|
|
2653
|
+
headers["Access-Token"] = secrets.accessToken;
|
|
2654
|
+
const res = await fetcher(url, {
|
|
2655
|
+
method: "POST",
|
|
2656
|
+
headers,
|
|
2657
|
+
body: JSON.stringify(tiktokPayload)
|
|
2658
|
+
});
|
|
2659
|
+
const resJson = await res.json().catch(() => ({}));
|
|
2660
|
+
if (!res.ok) {
|
|
2661
|
+
return {
|
|
2662
|
+
provider: "tiktok",
|
|
2663
|
+
success: false,
|
|
2664
|
+
status: res.status,
|
|
2665
|
+
error: resJson?.message || `HTTP ${res.status}`,
|
|
2666
|
+
data: resJson
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
return { provider: "tiktok", success: true, status: res.status, data: resJson };
|
|
2670
|
+
} catch (err) {
|
|
2671
|
+
return {
|
|
2672
|
+
provider: "tiktok",
|
|
2673
|
+
success: false,
|
|
2674
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
async function sendGa4MeasurementEvent(event, config, secrets, options) {
|
|
2679
|
+
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2680
|
+
if (!config.measurementId) {
|
|
2681
|
+
return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
|
|
2682
|
+
}
|
|
2683
|
+
const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
|
|
2684
|
+
const ga4Payload = {
|
|
2685
|
+
client_id: clientId,
|
|
2686
|
+
events: [
|
|
2687
|
+
{
|
|
2688
|
+
name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
|
|
2689
|
+
params: {
|
|
2690
|
+
...event.params || {},
|
|
2691
|
+
...event.customData || {},
|
|
2692
|
+
event_id: event.eventId
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
]
|
|
2696
|
+
};
|
|
2697
|
+
if (options?.simulateInDebug) {
|
|
2698
|
+
options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
|
|
2699
|
+
return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
|
|
2700
|
+
}
|
|
2701
|
+
try {
|
|
2702
|
+
if (!secrets?.measurementProtocolSecret) {
|
|
2703
|
+
return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
|
|
2704
|
+
}
|
|
2705
|
+
const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
|
|
2706
|
+
const res = await fetcher(url, {
|
|
2707
|
+
method: "POST",
|
|
2708
|
+
headers: { "Content-Type": "application/json" },
|
|
2709
|
+
body: JSON.stringify(ga4Payload)
|
|
2710
|
+
});
|
|
2711
|
+
return {
|
|
2712
|
+
provider: "google",
|
|
2713
|
+
success: res.ok,
|
|
2714
|
+
status: res.status,
|
|
2715
|
+
data: { status: res.status }
|
|
2716
|
+
};
|
|
2717
|
+
} catch (err) {
|
|
2718
|
+
return {
|
|
2719
|
+
provider: "google",
|
|
2720
|
+
success: false,
|
|
2721
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
async function sendCustomWebhookEvent(event, _config, secrets, options) {
|
|
2726
|
+
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2727
|
+
const webhookPayload = {
|
|
2728
|
+
event: event.eventName,
|
|
2729
|
+
eventId: event.eventId,
|
|
2730
|
+
timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
|
|
2731
|
+
userData: event.userData,
|
|
2732
|
+
params: event.params,
|
|
2733
|
+
customData: event.customData,
|
|
2734
|
+
sourceUrl: event.eventSourceUrl || options?.sourceUrl
|
|
2735
|
+
};
|
|
2736
|
+
if (options?.simulateInDebug) {
|
|
2737
|
+
options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
|
|
2738
|
+
return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
|
|
2739
|
+
}
|
|
2740
|
+
if (!secrets?.endpointUrl) {
|
|
2741
|
+
return skippedResult("custom", "No custom webhook destination resolved for this credential");
|
|
2742
|
+
}
|
|
2743
|
+
try {
|
|
2744
|
+
const res = await fetcher(secrets.endpointUrl, {
|
|
2745
|
+
method: "POST",
|
|
2746
|
+
headers: {
|
|
2747
|
+
"Content-Type": "application/json",
|
|
2748
|
+
...secrets.headers || {}
|
|
2749
|
+
},
|
|
2750
|
+
body: JSON.stringify(webhookPayload)
|
|
2751
|
+
});
|
|
2752
|
+
const resJson = await res.json().catch(() => ({}));
|
|
2753
|
+
return {
|
|
2754
|
+
provider: "custom",
|
|
2755
|
+
success: res.ok,
|
|
2756
|
+
status: res.status,
|
|
2757
|
+
data: resJson
|
|
2758
|
+
};
|
|
2759
|
+
} catch (err) {
|
|
2760
|
+
return {
|
|
2761
|
+
provider: "custom",
|
|
2762
|
+
success: false,
|
|
2763
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function skippedResult(provider, reason) {
|
|
2768
|
+
return { provider, success: true, skipped: true, reason };
|
|
2769
|
+
}
|
|
2770
|
+
async function dispatchServerTracking(event, config, options) {
|
|
2771
|
+
const eventId = event.eventId || generateTrackingEventId();
|
|
2772
|
+
const eventWithId = { ...event, eventId };
|
|
2773
|
+
if (!config || config.enabled === false) {
|
|
2774
|
+
return {
|
|
2775
|
+
success: true,
|
|
2776
|
+
eventId,
|
|
2777
|
+
skipped: true,
|
|
2778
|
+
reason: "Tracking is disabled globally",
|
|
2779
|
+
results: {}
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
const isDebug = Boolean(config.debugMode);
|
|
2783
|
+
const simulate = isDebug && options?.simulateInDebug !== false;
|
|
2784
|
+
const mergedOptions = {
|
|
2785
|
+
...options,
|
|
2786
|
+
simulateInDebug: simulate,
|
|
2787
|
+
onLog: (msg, data) => {
|
|
2788
|
+
if (isDebug) {
|
|
2789
|
+
console.log(`[KUBUILD Tracking] ${msg}`, data || "");
|
|
2790
|
+
}
|
|
2791
|
+
options?.onLog?.(msg, data);
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
const target = options?.provider || "all";
|
|
2795
|
+
const wants = (key) => target === "all" || target === key;
|
|
2796
|
+
const providers = config.providers || {};
|
|
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
|
+
});
|
|
2805
|
+
}
|
|
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
|
+
});
|
|
2813
|
+
}
|
|
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
|
+
});
|
|
2821
|
+
}
|
|
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
|
+
};
|
|
2838
|
+
}
|
|
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)));
|
|
2861
|
+
const results = {};
|
|
2862
|
+
let overallSuccess = true;
|
|
2863
|
+
let delivered = 0;
|
|
2864
|
+
settled.forEach((item, index) => {
|
|
2865
|
+
const key = tasks[index].key;
|
|
2866
|
+
if (item.status === "fulfilled") {
|
|
2867
|
+
results[key] = item.value;
|
|
2868
|
+
if (!item.value.success) overallSuccess = false;
|
|
2869
|
+
if (!item.value.skipped) delivered++;
|
|
2870
|
+
} else {
|
|
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
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2879
|
+
const allSkipped = delivered === 0 && overallSuccess;
|
|
2880
|
+
return {
|
|
2881
|
+
success: overallSuccess,
|
|
2882
|
+
eventId,
|
|
2883
|
+
...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
|
|
2884
|
+
results
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
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
|
+
|
|
2457
3016
|
// src/io/exporter.ts
|
|
2458
3017
|
import { zipSync, strToU8 } from "fflate";
|
|
2459
3018
|
import {
|
|
2460
3019
|
SCHEMA_NAME as SCHEMA_NAME4,
|
|
2461
|
-
CURRENT_SCHEMA_VERSION as
|
|
3020
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
|
|
2462
3021
|
} from "@kubuild/schema";
|
|
2463
3022
|
|
|
2464
3023
|
// src/validation/validator.ts
|
|
@@ -3001,7 +3560,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
|
|
|
3001
3560
|
`${currentPath}/props`,
|
|
3002
3561
|
effectiveNodeId,
|
|
3003
3562
|
options,
|
|
3004
|
-
errors
|
|
3563
|
+
errors,
|
|
3564
|
+
warnings
|
|
3005
3565
|
);
|
|
3006
3566
|
}
|
|
3007
3567
|
}
|
|
@@ -3016,7 +3576,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
|
|
|
3016
3576
|
}
|
|
3017
3577
|
}
|
|
3018
3578
|
}
|
|
3019
|
-
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;
|
|
3020
3583
|
for (const [key, value] of Object.entries(propsObj)) {
|
|
3021
3584
|
const currentPath = `${propsPath}/${key}`;
|
|
3022
3585
|
if (!value || typeof value !== "object") {
|
|
@@ -3030,7 +3593,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3030
3593
|
`${currentPath}/${index}`,
|
|
3031
3594
|
nodeId,
|
|
3032
3595
|
options,
|
|
3033
|
-
errors
|
|
3596
|
+
errors,
|
|
3597
|
+
warnings
|
|
3034
3598
|
);
|
|
3035
3599
|
}
|
|
3036
3600
|
});
|
|
@@ -3038,7 +3602,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3038
3602
|
}
|
|
3039
3603
|
const record = value;
|
|
3040
3604
|
if (record.type === "asset") {
|
|
3041
|
-
if (
|
|
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) {
|
|
3042
3608
|
errors.push({
|
|
3043
3609
|
code: "INVALID_ASSET_REFERENCE",
|
|
3044
3610
|
message: 'Asset reference must have a non-empty "assetId"',
|
|
@@ -3054,7 +3620,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3054
3620
|
nodeId
|
|
3055
3621
|
});
|
|
3056
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
|
+
}
|
|
3057
3642
|
} else if (record.type === "variable") {
|
|
3643
|
+
if (!checkVariables) continue;
|
|
3058
3644
|
if (typeof record.key !== "string" || record.key.trim().length === 0) {
|
|
3059
3645
|
errors.push({
|
|
3060
3646
|
code: "INVALID_VARIABLE_BINDING",
|
|
@@ -3064,6 +3650,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3064
3650
|
});
|
|
3065
3651
|
}
|
|
3066
3652
|
} else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
|
|
3653
|
+
if (!checkActions) continue;
|
|
3067
3654
|
if (typeof record.type !== "string" || record.type.trim().length === 0) {
|
|
3068
3655
|
errors.push({
|
|
3069
3656
|
code: "INVALID_ACTION_BINDING",
|
|
@@ -3073,10 +3660,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3073
3660
|
});
|
|
3074
3661
|
}
|
|
3075
3662
|
} else {
|
|
3076
|
-
validatePropsBindings(record, currentPath, nodeId, options, errors);
|
|
3663
|
+
validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
|
|
3077
3664
|
}
|
|
3078
3665
|
}
|
|
3079
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
|
+
}
|
|
3080
3719
|
|
|
3081
3720
|
// src/io/exporter.ts
|
|
3082
3721
|
function sha256Sync(data) {
|
|
@@ -3297,6 +3936,7 @@ async function exportPackage(document, options = {}) {
|
|
|
3297
3936
|
};
|
|
3298
3937
|
}
|
|
3299
3938
|
const pageDoc = JSON.parse(JSON.stringify(validation.data));
|
|
3939
|
+
stripDocumentTrackingSecretsInPlace(pageDoc);
|
|
3300
3940
|
if (options.metadata) {
|
|
3301
3941
|
pageDoc.metadata = {
|
|
3302
3942
|
...pageDoc.metadata || {
|
|
@@ -3421,7 +4061,7 @@ async function exportPackage(document, options = {}) {
|
|
|
3421
4061
|
};
|
|
3422
4062
|
const manifest = {
|
|
3423
4063
|
schema: SCHEMA_NAME4,
|
|
3424
|
-
schemaVersion: pageDoc.version ||
|
|
4064
|
+
schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION3,
|
|
3425
4065
|
packageVersion: options.packageVersion || "1.0.0",
|
|
3426
4066
|
builderCompatibility: options.builderCompatibility || ">=0.1.0",
|
|
3427
4067
|
requiredComponents: requirements.requiredComponents,
|
|
@@ -3448,12 +4088,12 @@ var exportStoraPackage = exportPackage;
|
|
|
3448
4088
|
import { unzipSync, strFromU8 } from "fflate";
|
|
3449
4089
|
import {
|
|
3450
4090
|
ManifestSchema,
|
|
3451
|
-
CURRENT_SCHEMA_VERSION as
|
|
4091
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION5
|
|
3452
4092
|
} from "@kubuild/schema";
|
|
3453
4093
|
|
|
3454
4094
|
// src/io/migration.ts
|
|
3455
4095
|
import {
|
|
3456
|
-
CURRENT_SCHEMA_VERSION as
|
|
4096
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4,
|
|
3457
4097
|
SCHEMA_NAME as SCHEMA_NAME5
|
|
3458
4098
|
} from "@kubuild/schema";
|
|
3459
4099
|
var MigrationRegistry = class {
|
|
@@ -3608,17 +4248,36 @@ defaultMigrationRegistry.register({
|
|
|
3608
4248
|
return migrated;
|
|
3609
4249
|
}
|
|
3610
4250
|
});
|
|
3611
|
-
|
|
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) {
|
|
3612
4271
|
if (!sourceVersion || !targetVersion) return false;
|
|
3613
4272
|
if (sourceVersion === targetVersion) return true;
|
|
3614
4273
|
return registry.hasPath(sourceVersion, targetVersion);
|
|
3615
4274
|
}
|
|
3616
|
-
function getMigrationPath(sourceVersion, targetVersion =
|
|
4275
|
+
function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
|
|
3617
4276
|
if (!sourceVersion || !targetVersion) return null;
|
|
3618
4277
|
return registry.findPath(sourceVersion, targetVersion);
|
|
3619
4278
|
}
|
|
3620
4279
|
function migrateDocument(rawDocument, options = {}) {
|
|
3621
|
-
const targetVersion = options.targetVersion ??
|
|
4280
|
+
const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION4;
|
|
3622
4281
|
const dryRun = options.dryRun ?? false;
|
|
3623
4282
|
const registry = options.registry ?? defaultMigrationRegistry;
|
|
3624
4283
|
const validate = options.validate ?? true;
|
|
@@ -3646,6 +4305,14 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
3646
4305
|
const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
|
|
3647
4306
|
if (sourceVersion === targetVersion) {
|
|
3648
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
|
+
] : [];
|
|
3649
4316
|
if (validate) {
|
|
3650
4317
|
const validation = validateDocument(cloned);
|
|
3651
4318
|
if (!validation.valid) {
|
|
@@ -3677,7 +4344,8 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
3677
4344
|
targetVersion,
|
|
3678
4345
|
migrationPath: [sourceVersion],
|
|
3679
4346
|
stepsApplied: 0,
|
|
3680
|
-
dryRun
|
|
4347
|
+
dryRun,
|
|
4348
|
+
...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
|
|
3681
4349
|
}
|
|
3682
4350
|
};
|
|
3683
4351
|
}
|
|
@@ -3717,6 +4385,8 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
3717
4385
|
}
|
|
3718
4386
|
let currentDoc = deepClone(doc);
|
|
3719
4387
|
let stepsApplied = 0;
|
|
4388
|
+
const warnings = [];
|
|
4389
|
+
const stepContext = { warn: (w) => warnings.push(w) };
|
|
3720
4390
|
for (let i = 0; i < path.length - 1; i++) {
|
|
3721
4391
|
const fromVer = path[i];
|
|
3722
4392
|
const toVer = path[i + 1];
|
|
@@ -3742,7 +4412,7 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
3742
4412
|
};
|
|
3743
4413
|
}
|
|
3744
4414
|
try {
|
|
3745
|
-
currentDoc = step.migrate(currentDoc);
|
|
4415
|
+
currentDoc = step.migrate(currentDoc, stepContext);
|
|
3746
4416
|
stepsApplied++;
|
|
3747
4417
|
} catch (err) {
|
|
3748
4418
|
const error = {
|
|
@@ -3798,7 +4468,8 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
3798
4468
|
targetVersion,
|
|
3799
4469
|
migrationPath: path,
|
|
3800
4470
|
stepsApplied,
|
|
3801
|
-
dryRun: false
|
|
4471
|
+
dryRun: false,
|
|
4472
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
3802
4473
|
}
|
|
3803
4474
|
};
|
|
3804
4475
|
}
|
|
@@ -3833,7 +4504,7 @@ async function preflightPackage(archiveData, options = {}) {
|
|
|
3833
4504
|
...DEFAULT_SECURITY_LIMITS,
|
|
3834
4505
|
...options.securityLimits
|
|
3835
4506
|
};
|
|
3836
|
-
const targetVersion = options.targetSchemaVersion ||
|
|
4507
|
+
const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5;
|
|
3837
4508
|
const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
|
|
3838
4509
|
const diagnostics = [];
|
|
3839
4510
|
let dependencyPolicy = options.dependencyPolicy || "cancel";
|
|
@@ -3996,6 +4667,16 @@ async function preflightPackage(archiveData, options = {}) {
|
|
|
3996
4667
|
let rawPageDoc;
|
|
3997
4668
|
try {
|
|
3998
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
|
+
}
|
|
3999
4680
|
const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
|
|
4000
4681
|
if (pageProtoCheck.found) {
|
|
4001
4682
|
diagnostics.push({
|
|
@@ -4242,7 +4923,7 @@ function buildReport(valid, canImport, fields) {
|
|
|
4242
4923
|
return {
|
|
4243
4924
|
valid,
|
|
4244
4925
|
canImport,
|
|
4245
|
-
targetVersion: fields.targetVersion ||
|
|
4926
|
+
targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION5,
|
|
4246
4927
|
requiresMigration: fields.requiresMigration || false,
|
|
4247
4928
|
missingComponents: fields.missingComponents || [],
|
|
4248
4929
|
missingCapabilities: fields.missingCapabilities || [],
|
|
@@ -4315,7 +4996,7 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4315
4996
|
if (preflight.requiresMigration) {
|
|
4316
4997
|
const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
|
|
4317
4998
|
const migrationRes = migrateDocument(pageRaw, {
|
|
4318
|
-
targetVersion: options.targetSchemaVersion ||
|
|
4999
|
+
targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5,
|
|
4319
5000
|
registry: migrationRegistry,
|
|
4320
5001
|
validate: true
|
|
4321
5002
|
});
|
|
@@ -4337,13 +5018,24 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4337
5018
|
} else {
|
|
4338
5019
|
finalDocument = pageRaw;
|
|
4339
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
|
+
}
|
|
4340
5032
|
let metadata = finalDocument.metadata || {
|
|
4341
5033
|
title: "Imported Page",
|
|
4342
5034
|
description: "",
|
|
4343
5035
|
author: "",
|
|
4344
5036
|
tags: [],
|
|
4345
5037
|
category: "general",
|
|
4346
|
-
version: finalDocument.version ||
|
|
5038
|
+
version: finalDocument.version || CURRENT_SCHEMA_VERSION5
|
|
4347
5039
|
};
|
|
4348
5040
|
if (unzipped["metadata.json"]) {
|
|
4349
5041
|
try {
|
|
@@ -4495,7 +5187,8 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4495
5187
|
metadata,
|
|
4496
5188
|
extractedAssets,
|
|
4497
5189
|
renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
|
|
4498
|
-
preflight
|
|
5190
|
+
preflight,
|
|
5191
|
+
...importWarnings.length > 0 ? { warnings: importWarnings } : {}
|
|
4499
5192
|
};
|
|
4500
5193
|
}
|
|
4501
5194
|
var importStoraPackage = importPackage;
|
|
@@ -5638,10 +6331,12 @@ export {
|
|
|
5638
6331
|
createPageArtboard,
|
|
5639
6332
|
createRuntimeStore,
|
|
5640
6333
|
createTemplateRecord,
|
|
6334
|
+
createTrackingRelayHandler,
|
|
5641
6335
|
deepClone,
|
|
5642
6336
|
defaultIdGenerator,
|
|
5643
6337
|
defaultMigrationRegistry,
|
|
5644
6338
|
defaultRenameAssetStrategy,
|
|
6339
|
+
dispatchServerTracking,
|
|
5645
6340
|
duplicateNode,
|
|
5646
6341
|
evaluateActionCondition,
|
|
5647
6342
|
evaluateCondition,
|
|
@@ -5661,6 +6356,7 @@ export {
|
|
|
5661
6356
|
findMissingComponentNodes,
|
|
5662
6357
|
findNodeById,
|
|
5663
6358
|
findNodeLocation,
|
|
6359
|
+
generateTrackingEventId,
|
|
5664
6360
|
getActiveArtboard,
|
|
5665
6361
|
getAncestorChain,
|
|
5666
6362
|
getComponentArtboards,
|
|
@@ -5670,6 +6366,7 @@ export {
|
|
|
5670
6366
|
getPageArtboards,
|
|
5671
6367
|
getParentNodeId,
|
|
5672
6368
|
hasTemplateExpressions,
|
|
6369
|
+
hashSha256,
|
|
5673
6370
|
importPackage,
|
|
5674
6371
|
importStoraPackage,
|
|
5675
6372
|
insertNode,
|
|
@@ -5687,6 +6384,9 @@ export {
|
|
|
5687
6384
|
loadProjectDocument,
|
|
5688
6385
|
migrateDocument,
|
|
5689
6386
|
moveNode,
|
|
6387
|
+
normalizeEmail,
|
|
6388
|
+
normalizePhone,
|
|
6389
|
+
normalizeText,
|
|
5690
6390
|
preflightPackage,
|
|
5691
6391
|
previewImportPackage,
|
|
5692
6392
|
remapAssetReferences,
|
|
@@ -5698,12 +6398,19 @@ export {
|
|
|
5698
6398
|
resolveBinding,
|
|
5699
6399
|
resolveBindingValue,
|
|
5700
6400
|
resolvePropertyPath,
|
|
6401
|
+
sanitizeDocumentTracking,
|
|
5701
6402
|
sanitizeFilename,
|
|
5702
6403
|
sanitizeHtml,
|
|
5703
6404
|
sanitizeUrl,
|
|
5704
6405
|
saveDraftAsTemplate,
|
|
6406
|
+
sendCustomWebhookEvent,
|
|
6407
|
+
sendGa4MeasurementEvent,
|
|
6408
|
+
sendMetaCapiEvent,
|
|
6409
|
+
sendTikTokEventsApi,
|
|
5705
6410
|
setActiveArtboard,
|
|
5706
6411
|
sha256Sync,
|
|
6412
|
+
stripDocumentTrackingSecretsInPlace,
|
|
6413
|
+
stripTrackingSecretsInPlace,
|
|
5707
6414
|
ungroupNodeFrame,
|
|
5708
6415
|
updateActions,
|
|
5709
6416
|
updateAnimation,
|