@kubuild/core 0.6.0 → 0.8.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/command-tree-utils.d.ts +22 -1
- package/dist/document/command-tree-utils.d.ts.map +1 -1
- package/dist/document/commands.d.ts +19 -2
- package/dist/document/commands.d.ts.map +1 -1
- package/dist/document/document-utils.d.ts.map +1 -1
- package/dist/index.cjs +625 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +605 -126
- 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/load-project.d.ts.map +1 -1
- package/dist/io/migration.d.ts +28 -2
- package/dist/io/migration.d.ts.map +1 -1
- package/dist/io/template-utils.d.ts +8 -3
- package/dist/io/template-utils.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 +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/server-tracking.d.ts +40 -7
- package/dist/runtime/server-tracking.d.ts.map +1 -1
- 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 +79 -2
- 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: "",
|
|
@@ -316,26 +316,84 @@ function defaultIdGenerator(oldId, existingIds) {
|
|
|
316
316
|
}
|
|
317
317
|
return candidate;
|
|
318
318
|
}
|
|
319
|
-
|
|
319
|
+
var NODE_ID_REFERENCE_KEYS = /* @__PURE__ */ new Set([
|
|
320
|
+
"modalId",
|
|
321
|
+
"modalNodeId",
|
|
322
|
+
"targetModalId",
|
|
323
|
+
"targetNodeId",
|
|
324
|
+
"nodeId",
|
|
325
|
+
"formId",
|
|
326
|
+
"targetId"
|
|
327
|
+
]);
|
|
328
|
+
var ANCHOR_REFERENCE_KEYS = /* @__PURE__ */ new Set(["url", "href"]);
|
|
329
|
+
function remapReferenceValue(key, value, idMap) {
|
|
330
|
+
if (typeof value !== "string") return value;
|
|
331
|
+
if (NODE_ID_REFERENCE_KEYS.has(key)) {
|
|
332
|
+
return idMap.get(value) ?? idMap.get(value.trim()) ?? value;
|
|
333
|
+
}
|
|
334
|
+
if (ANCHOR_REFERENCE_KEYS.has(key) && value.startsWith("#")) {
|
|
335
|
+
const mapped = idMap.get(value.slice(1));
|
|
336
|
+
return mapped ? `#${mapped}` : value;
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
function remapReferenceRecord(record, idMap) {
|
|
341
|
+
for (const [key, value] of Object.entries(record)) {
|
|
342
|
+
record[key] = remapReferenceValue(key, value, idMap);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function remapStepReferences(steps, idMap) {
|
|
346
|
+
if (!Array.isArray(steps)) return;
|
|
347
|
+
for (const step of steps) {
|
|
348
|
+
if (step.payload && typeof step.payload === "object") {
|
|
349
|
+
remapReferenceRecord(step.payload, idMap);
|
|
350
|
+
}
|
|
351
|
+
remapStepReferences(step.onSuccess, idMap);
|
|
352
|
+
remapStepReferences(step.onError, idMap);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function remapNodeReferences(node, idMap) {
|
|
356
|
+
if (node.props && typeof node.props === "object") {
|
|
357
|
+
remapReferenceRecord(node.props, idMap);
|
|
358
|
+
const legacyAction = node.props.action;
|
|
359
|
+
if (legacyAction && typeof legacyAction === "object" && legacyAction.payload && typeof legacyAction.payload === "object" && !Array.isArray(legacyAction.payload)) {
|
|
360
|
+
remapReferenceRecord(legacyAction.payload, idMap);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (node.formConfig && typeof node.formConfig.formId === "string") {
|
|
364
|
+
node.formConfig.formId = idMap.get(node.formConfig.formId) ?? node.formConfig.formId;
|
|
365
|
+
}
|
|
366
|
+
if (Array.isArray(node.actions)) {
|
|
367
|
+
for (const pipeline of node.actions) {
|
|
368
|
+
remapStepReferences(pipeline.steps, idMap);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function cloneNodeTreeWithFreshIds(root, idGen) {
|
|
320
373
|
const idMap = /* @__PURE__ */ new Map();
|
|
321
|
-
const
|
|
374
|
+
const clonedNodes = [];
|
|
322
375
|
function cloneRecursive(node) {
|
|
323
|
-
const newId = idGen(node.id);
|
|
376
|
+
const newId = idGen(node.id, node);
|
|
324
377
|
idMap.set(node.id, newId);
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
return {
|
|
378
|
+
const { children, ...rest } = node;
|
|
379
|
+
const cloned = {
|
|
380
|
+
...deepClone(rest),
|
|
329
381
|
id: newId,
|
|
330
|
-
|
|
331
|
-
...clonedProps ? { props: clonedProps } : {},
|
|
332
|
-
...clonedStyles ? { styles: clonedStyles } : {},
|
|
333
|
-
children: clonedChildren
|
|
382
|
+
children: children ? children.map((child) => cloneRecursive(child)) : []
|
|
334
383
|
};
|
|
384
|
+
clonedNodes.push(cloned);
|
|
385
|
+
return cloned;
|
|
335
386
|
}
|
|
336
387
|
const clonedNode = cloneRecursive(root);
|
|
388
|
+
for (const cloned of clonedNodes) {
|
|
389
|
+
remapNodeReferences(cloned, idMap);
|
|
390
|
+
}
|
|
337
391
|
return { clonedNode, idMap };
|
|
338
392
|
}
|
|
393
|
+
function cloneTreeWithNewIds(root, idGenerator, existingIds) {
|
|
394
|
+
const idGen = idGenerator || ((oldId) => defaultIdGenerator(oldId, existingIds));
|
|
395
|
+
return cloneNodeTreeWithFreshIds(root, (oldId) => idGen(oldId));
|
|
396
|
+
}
|
|
339
397
|
|
|
340
398
|
// src/document/commands.ts
|
|
341
399
|
import {
|
|
@@ -343,7 +401,9 @@ import {
|
|
|
343
401
|
ResponsiveStylesSchema,
|
|
344
402
|
AnimationConfigSchema,
|
|
345
403
|
ActionPipelineSchema,
|
|
346
|
-
FormConfigSchema
|
|
404
|
+
FormConfigSchema,
|
|
405
|
+
ThemeSchema,
|
|
406
|
+
THEME_TOKEN_GROUPS
|
|
347
407
|
} from "@kubuild/schema";
|
|
348
408
|
function insertNode(document, params) {
|
|
349
409
|
const { parentId, node, index } = params;
|
|
@@ -883,13 +943,60 @@ function replaceNode(document, params) {
|
|
|
883
943
|
}
|
|
884
944
|
};
|
|
885
945
|
}
|
|
946
|
+
function updateTheme(document, params) {
|
|
947
|
+
const { theme, merge = true } = params;
|
|
948
|
+
const newDoc = deepClone(document);
|
|
949
|
+
const previousTheme = newDoc.theme ? deepClone(newDoc.theme) : void 0;
|
|
950
|
+
if (theme === null) {
|
|
951
|
+
delete newDoc.theme;
|
|
952
|
+
} else {
|
|
953
|
+
const next = {};
|
|
954
|
+
for (const group of THEME_TOKEN_GROUPS) {
|
|
955
|
+
const base = merge ? { ...previousTheme?.[group] ?? {} } : {};
|
|
956
|
+
const patch = theme[group];
|
|
957
|
+
if (patch === null) {
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (patch) {
|
|
961
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
962
|
+
if (value === null) {
|
|
963
|
+
delete base[key];
|
|
964
|
+
} else {
|
|
965
|
+
base[key] = value;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (Object.keys(base).length > 0) {
|
|
970
|
+
next[group] = base;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
const parsed = ThemeSchema.parse(next);
|
|
974
|
+
if (Object.keys(parsed).length > 0) {
|
|
975
|
+
newDoc.theme = parsed;
|
|
976
|
+
} else {
|
|
977
|
+
delete newDoc.theme;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
document: newDoc,
|
|
982
|
+
event: {
|
|
983
|
+
type: "THEME_UPDATED",
|
|
984
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
985
|
+
nodeId: newDoc.document.id,
|
|
986
|
+
payload: {
|
|
987
|
+
theme: newDoc.theme,
|
|
988
|
+
previousTheme
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
886
993
|
|
|
887
994
|
// src/document/artboards.ts
|
|
888
995
|
import {
|
|
889
996
|
SCHEMA_NAME as SCHEMA_NAME2,
|
|
890
997
|
PROJECT_SCHEMA_NAME,
|
|
891
998
|
CURRENT_PROJECT_SCHEMA_VERSION,
|
|
892
|
-
CURRENT_SCHEMA_VERSION,
|
|
999
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2,
|
|
893
1000
|
ARTBOARD_REFERENCE_NODE_TYPE
|
|
894
1001
|
} from "@kubuild/schema";
|
|
895
1002
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -947,14 +1054,14 @@ function createComponentArtboard(node, options = {}) {
|
|
|
947
1054
|
const triggerId = options.triggerId ?? (typeof node.props?.modalId === "string" && node.props.modalId.trim().length > 0 ? node.props.modalId : node.id);
|
|
948
1055
|
const document = {
|
|
949
1056
|
schema: SCHEMA_NAME2,
|
|
950
|
-
version:
|
|
1057
|
+
version: CURRENT_SCHEMA_VERSION2,
|
|
951
1058
|
metadata: {
|
|
952
1059
|
title: name,
|
|
953
1060
|
description: "",
|
|
954
1061
|
author: "",
|
|
955
1062
|
tags: [],
|
|
956
1063
|
category: "component",
|
|
957
|
-
version:
|
|
1064
|
+
version: CURRENT_SCHEMA_VERSION2,
|
|
958
1065
|
createdAt: now(),
|
|
959
1066
|
updatedAt: now()
|
|
960
1067
|
},
|
|
@@ -1411,6 +1518,15 @@ var DocumentHistoryManager = class {
|
|
|
1411
1518
|
var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1412
1519
|
function resolveBinding(binding, context) {
|
|
1413
1520
|
const segments = binding.key.split(".").filter(Boolean);
|
|
1521
|
+
if (segments.some((segment) => FORBIDDEN_KEY_SEGMENTS.has(segment))) {
|
|
1522
|
+
return applyMissingPolicy(binding);
|
|
1523
|
+
}
|
|
1524
|
+
if (context?.variables && typeof context.variables === "object" && Object.prototype.hasOwnProperty.call(context.variables, binding.key)) {
|
|
1525
|
+
const directValue = context.variables[binding.key];
|
|
1526
|
+
if (directValue !== void 0 && typeof directValue !== "function") {
|
|
1527
|
+
return { status: "resolved", value: directValue };
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1414
1530
|
let current = context?.variables;
|
|
1415
1531
|
for (const segment of segments) {
|
|
1416
1532
|
if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
|
|
@@ -1982,14 +2098,14 @@ var ActionPipelineExecutor = class {
|
|
|
1982
2098
|
async executeSingleStep(step, context, parentSignal, options) {
|
|
1983
2099
|
const stepStartTime = Date.now();
|
|
1984
2100
|
if (step.condition && !evaluateActionCondition(step.condition, context)) {
|
|
1985
|
-
const
|
|
2101
|
+
const skippedResult2 = {
|
|
1986
2102
|
stepId: step.id,
|
|
1987
2103
|
stepType: step.type,
|
|
1988
2104
|
status: "skipped",
|
|
1989
2105
|
durationMs: Date.now() - stepStartTime
|
|
1990
2106
|
};
|
|
1991
|
-
options?.onStepComplete?.(step,
|
|
1992
|
-
return
|
|
2107
|
+
options?.onStepComplete?.(step, skippedResult2, context);
|
|
2108
|
+
return skippedResult2;
|
|
1993
2109
|
}
|
|
1994
2110
|
options?.onStepStart?.(step, context);
|
|
1995
2111
|
const stepTimeout = step.timeout;
|
|
@@ -2481,7 +2597,7 @@ function generateTrackingEventId(prefix = "evt") {
|
|
|
2481
2597
|
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
2482
2598
|
return `${prefix}_${timestamp}_${randomPart}`;
|
|
2483
2599
|
}
|
|
2484
|
-
async function sendMetaCapiEvent(event, config, options) {
|
|
2600
|
+
async function sendMetaCapiEvent(event, config, secrets, options) {
|
|
2485
2601
|
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2486
2602
|
if (!config.pixelId) {
|
|
2487
2603
|
return {
|
|
@@ -2555,9 +2671,10 @@ async function sendMetaCapiEvent(event, config, options) {
|
|
|
2555
2671
|
const headers = {
|
|
2556
2672
|
"Content-Type": "application/json"
|
|
2557
2673
|
};
|
|
2558
|
-
if (
|
|
2559
|
-
|
|
2674
|
+
if (!secrets?.capiAccessToken) {
|
|
2675
|
+
return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
|
|
2560
2676
|
}
|
|
2677
|
+
headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
|
|
2561
2678
|
const res = await fetcher(url, {
|
|
2562
2679
|
method: "POST",
|
|
2563
2680
|
headers,
|
|
@@ -2587,7 +2704,7 @@ async function sendMetaCapiEvent(event, config, options) {
|
|
|
2587
2704
|
};
|
|
2588
2705
|
}
|
|
2589
2706
|
}
|
|
2590
|
-
async function sendTikTokEventsApi(event, config, options) {
|
|
2707
|
+
async function sendTikTokEventsApi(event, config, secrets, options) {
|
|
2591
2708
|
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2592
2709
|
if (!config.pixelId) {
|
|
2593
2710
|
return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
|
|
@@ -2637,9 +2754,10 @@ async function sendTikTokEventsApi(event, config, options) {
|
|
|
2637
2754
|
const headers = {
|
|
2638
2755
|
"Content-Type": "application/json"
|
|
2639
2756
|
};
|
|
2640
|
-
if (
|
|
2641
|
-
|
|
2757
|
+
if (!secrets?.accessToken) {
|
|
2758
|
+
return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
|
|
2642
2759
|
}
|
|
2760
|
+
headers["Access-Token"] = secrets.accessToken;
|
|
2643
2761
|
const res = await fetcher(url, {
|
|
2644
2762
|
method: "POST",
|
|
2645
2763
|
headers,
|
|
@@ -2664,7 +2782,7 @@ async function sendTikTokEventsApi(event, config, options) {
|
|
|
2664
2782
|
};
|
|
2665
2783
|
}
|
|
2666
2784
|
}
|
|
2667
|
-
async function sendGa4MeasurementEvent(event, config, options) {
|
|
2785
|
+
async function sendGa4MeasurementEvent(event, config, secrets, options) {
|
|
2668
2786
|
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2669
2787
|
if (!config.measurementId) {
|
|
2670
2788
|
return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
|
|
@@ -2688,12 +2806,10 @@ async function sendGa4MeasurementEvent(event, config, options) {
|
|
|
2688
2806
|
return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
|
|
2689
2807
|
}
|
|
2690
2808
|
try {
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
)}`;
|
|
2694
|
-
if (config.measurementProtocolSecret) {
|
|
2695
|
-
url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
|
|
2809
|
+
if (!secrets?.measurementProtocolSecret) {
|
|
2810
|
+
return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
|
|
2696
2811
|
}
|
|
2812
|
+
const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
|
|
2697
2813
|
const res = await fetcher(url, {
|
|
2698
2814
|
method: "POST",
|
|
2699
2815
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2713,11 +2829,8 @@ async function sendGa4MeasurementEvent(event, config, options) {
|
|
|
2713
2829
|
};
|
|
2714
2830
|
}
|
|
2715
2831
|
}
|
|
2716
|
-
async function sendCustomWebhookEvent(event,
|
|
2832
|
+
async function sendCustomWebhookEvent(event, _config, secrets, options) {
|
|
2717
2833
|
const fetcher = options?.fetchFn || globalThis.fetch;
|
|
2718
|
-
if (!config.endpointUrl) {
|
|
2719
|
-
return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
|
|
2720
|
-
}
|
|
2721
2834
|
const webhookPayload = {
|
|
2722
2835
|
event: event.eventName,
|
|
2723
2836
|
eventId: event.eventId,
|
|
@@ -2731,12 +2844,15 @@ async function sendCustomWebhookEvent(event, config, options) {
|
|
|
2731
2844
|
options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
|
|
2732
2845
|
return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
|
|
2733
2846
|
}
|
|
2847
|
+
if (!secrets?.endpointUrl) {
|
|
2848
|
+
return skippedResult("custom", "No custom webhook destination resolved for this credential");
|
|
2849
|
+
}
|
|
2734
2850
|
try {
|
|
2735
|
-
const res = await fetcher(
|
|
2851
|
+
const res = await fetcher(secrets.endpointUrl, {
|
|
2736
2852
|
method: "POST",
|
|
2737
2853
|
headers: {
|
|
2738
2854
|
"Content-Type": "application/json",
|
|
2739
|
-
...
|
|
2855
|
+
...secrets.headers || {}
|
|
2740
2856
|
},
|
|
2741
2857
|
body: JSON.stringify(webhookPayload)
|
|
2742
2858
|
});
|
|
@@ -2755,6 +2871,9 @@ async function sendCustomWebhookEvent(event, config, options) {
|
|
|
2755
2871
|
};
|
|
2756
2872
|
}
|
|
2757
2873
|
}
|
|
2874
|
+
function skippedResult(provider, reason) {
|
|
2875
|
+
return { provider, success: true, skipped: true, reason };
|
|
2876
|
+
}
|
|
2758
2877
|
async function dispatchServerTracking(event, config, options) {
|
|
2759
2878
|
const eventId = event.eventId || generateTrackingEventId();
|
|
2760
2879
|
const eventWithId = { ...event, eventId };
|
|
@@ -2779,65 +2898,233 @@ async function dispatchServerTracking(event, config, options) {
|
|
|
2779
2898
|
options?.onLog?.(msg, data);
|
|
2780
2899
|
}
|
|
2781
2900
|
};
|
|
2901
|
+
const target = options?.provider || "all";
|
|
2902
|
+
const wants = (key) => target === "all" || target === key;
|
|
2782
2903
|
const providers = config.providers || {};
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
);
|
|
2904
|
+
const tasks = [];
|
|
2905
|
+
const meta = providers.meta;
|
|
2906
|
+
if (wants("meta") && meta && meta.enabled !== false && meta.capiEnabled) {
|
|
2907
|
+
tasks.push({
|
|
2908
|
+
key: "meta",
|
|
2909
|
+
credentialId: meta.credentialId,
|
|
2910
|
+
run: (s) => sendMetaCapiEvent(eventWithId, meta, s, mergedOptions)
|
|
2911
|
+
});
|
|
2791
2912
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
);
|
|
2913
|
+
const tiktok = providers.tiktok;
|
|
2914
|
+
if (wants("tiktok") && tiktok && tiktok.enabled !== false && tiktok.eventsApiEnabled) {
|
|
2915
|
+
tasks.push({
|
|
2916
|
+
key: "tiktok",
|
|
2917
|
+
credentialId: tiktok.credentialId,
|
|
2918
|
+
run: (s) => sendTikTokEventsApi(eventWithId, tiktok, s, mergedOptions)
|
|
2919
|
+
});
|
|
2799
2920
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
);
|
|
2921
|
+
const google = providers.google;
|
|
2922
|
+
if (wants("google") && google && google.enabled !== false && google.measurementId) {
|
|
2923
|
+
tasks.push({
|
|
2924
|
+
key: "google",
|
|
2925
|
+
credentialId: google.credentialId,
|
|
2926
|
+
run: (s) => sendGa4MeasurementEvent(eventWithId, google, s, mergedOptions)
|
|
2927
|
+
});
|
|
2807
2928
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
);
|
|
2929
|
+
const custom = providers.custom;
|
|
2930
|
+
if (wants("custom") && custom && custom.enabled !== false) {
|
|
2931
|
+
tasks.push({
|
|
2932
|
+
key: "custom",
|
|
2933
|
+
credentialId: custom.credentialId,
|
|
2934
|
+
run: (s) => sendCustomWebhookEvent(eventWithId, custom, s, mergedOptions)
|
|
2935
|
+
});
|
|
2815
2936
|
}
|
|
2816
|
-
|
|
2937
|
+
if (tasks.length === 0) {
|
|
2938
|
+
return {
|
|
2939
|
+
success: true,
|
|
2940
|
+
eventId,
|
|
2941
|
+
skipped: true,
|
|
2942
|
+
reason: target === "gtm" ? "GTM is client-side only (dataLayer); no server delivery" : "No server-side tracking provider is enabled",
|
|
2943
|
+
results: {}
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
const resolve = options?.resolveSecrets;
|
|
2947
|
+
const runTask = async (task) => {
|
|
2948
|
+
let secrets = null;
|
|
2949
|
+
if (resolve) {
|
|
2950
|
+
try {
|
|
2951
|
+
secrets = await resolve({ provider: task.key, credentialId: task.credentialId, documentId: options?.documentId }) ?? null;
|
|
2952
|
+
} catch (err) {
|
|
2953
|
+
return {
|
|
2954
|
+
provider: task.key,
|
|
2955
|
+
success: false,
|
|
2956
|
+
error: `Secret resolution failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
if (!secrets && !simulate) {
|
|
2961
|
+
const reason = resolve ? `No secret resolved for provider "${task.key}"${task.credentialId ? ` (credentialId "${task.credentialId}")` : " (no credentialId linked)"}` : "No secret resolver configured (options.resolveSecrets)";
|
|
2962
|
+
mergedOptions.onLog?.(`[${task.key}] skipped: ${reason}`);
|
|
2963
|
+
return skippedResult(task.key, reason);
|
|
2964
|
+
}
|
|
2965
|
+
return task.run(secrets);
|
|
2966
|
+
};
|
|
2967
|
+
const settled = await Promise.allSettled(tasks.map((task) => runTask(task)));
|
|
2817
2968
|
const results = {};
|
|
2818
2969
|
let overallSuccess = true;
|
|
2819
|
-
|
|
2970
|
+
let delivered = 0;
|
|
2971
|
+
settled.forEach((item, index) => {
|
|
2972
|
+
const key = tasks[index].key;
|
|
2820
2973
|
if (item.status === "fulfilled") {
|
|
2821
|
-
results[
|
|
2822
|
-
if (!item.value.
|
|
2823
|
-
|
|
2824
|
-
}
|
|
2974
|
+
results[key] = item.value;
|
|
2975
|
+
if (!item.value.success) overallSuccess = false;
|
|
2976
|
+
if (!item.value.skipped) delivered++;
|
|
2825
2977
|
} else {
|
|
2826
2978
|
overallSuccess = false;
|
|
2979
|
+
results[key] = {
|
|
2980
|
+
provider: key,
|
|
2981
|
+
success: false,
|
|
2982
|
+
error: item.reason instanceof Error ? item.reason.message : String(item.reason)
|
|
2983
|
+
};
|
|
2827
2984
|
}
|
|
2828
|
-
}
|
|
2985
|
+
});
|
|
2986
|
+
const allSkipped = delivered === 0 && overallSuccess;
|
|
2829
2987
|
return {
|
|
2830
2988
|
success: overallSuccess,
|
|
2831
2989
|
eventId,
|
|
2990
|
+
...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
|
|
2832
2991
|
results
|
|
2833
2992
|
};
|
|
2834
2993
|
}
|
|
2835
2994
|
|
|
2995
|
+
// src/runtime/tracking-relay.ts
|
|
2996
|
+
import {
|
|
2997
|
+
TRACKING_RELAY_PROTOCOL_VERSION,
|
|
2998
|
+
TrackingRelayRequestSchema
|
|
2999
|
+
} from "@kubuild/schema";
|
|
3000
|
+
var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
|
|
3001
|
+
function defaultClientIp(request) {
|
|
3002
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
3003
|
+
if (forwarded) {
|
|
3004
|
+
const first = forwarded.split(",")[0]?.trim();
|
|
3005
|
+
if (first) return first;
|
|
3006
|
+
}
|
|
3007
|
+
return request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
|
|
3008
|
+
}
|
|
3009
|
+
function isOriginAllowed(origin, allowed) {
|
|
3010
|
+
if (!allowed) return true;
|
|
3011
|
+
if (!origin) return false;
|
|
3012
|
+
if (typeof allowed === "function") return allowed(origin);
|
|
3013
|
+
return allowed.includes(origin);
|
|
3014
|
+
}
|
|
3015
|
+
function createTrackingRelayHandler(options) {
|
|
3016
|
+
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
3017
|
+
return async (request) => {
|
|
3018
|
+
const origin = request.headers.get("origin");
|
|
3019
|
+
const corsHeaders = {};
|
|
3020
|
+
if (options.allowedOrigins && origin && isOriginAllowed(origin, options.allowedOrigins)) {
|
|
3021
|
+
corsHeaders["Access-Control-Allow-Origin"] = origin;
|
|
3022
|
+
corsHeaders["Vary"] = "Origin";
|
|
3023
|
+
}
|
|
3024
|
+
const json = (body, status, extra) => new Response(JSON.stringify(body), {
|
|
3025
|
+
status,
|
|
3026
|
+
headers: { "Content-Type": "application/json", ...corsHeaders, ...extra || {} }
|
|
3027
|
+
});
|
|
3028
|
+
const fail = (code, message, status, extra) => json({ version: TRACKING_RELAY_PROTOCOL_VERSION, success: false, error: { code, message } }, status, extra);
|
|
3029
|
+
if (!isOriginAllowed(origin, options.allowedOrigins)) {
|
|
3030
|
+
return fail("FORBIDDEN_ORIGIN", "Origin is not allowed", 403);
|
|
3031
|
+
}
|
|
3032
|
+
if (request.method === "OPTIONS") {
|
|
3033
|
+
return new Response(null, {
|
|
3034
|
+
status: 204,
|
|
3035
|
+
headers: {
|
|
3036
|
+
...corsHeaders,
|
|
3037
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
3038
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
3039
|
+
"Access-Control-Max-Age": "600"
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
if (request.method !== "POST") {
|
|
3044
|
+
return fail("METHOD_NOT_ALLOWED", "Only POST requests are accepted", 405, { Allow: "POST, OPTIONS" });
|
|
3045
|
+
}
|
|
3046
|
+
try {
|
|
3047
|
+
const text = await request.text();
|
|
3048
|
+
if (text.length > maxBodyBytes) {
|
|
3049
|
+
return fail("INVALID_REQUEST", `Request body exceeds ${maxBodyBytes} bytes`, 413);
|
|
3050
|
+
}
|
|
3051
|
+
let raw;
|
|
3052
|
+
try {
|
|
3053
|
+
raw = JSON.parse(text);
|
|
3054
|
+
} catch {
|
|
3055
|
+
return fail("INVALID_REQUEST", "Request body must be valid JSON", 400);
|
|
3056
|
+
}
|
|
3057
|
+
if (raw && typeof raw === "object" && "version" in raw && raw.version !== TRACKING_RELAY_PROTOCOL_VERSION) {
|
|
3058
|
+
return fail(
|
|
3059
|
+
"UNSUPPORTED_VERSION",
|
|
3060
|
+
`Unsupported relay protocol version; expected ${TRACKING_RELAY_PROTOCOL_VERSION}`,
|
|
3061
|
+
400
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
const parsed = TrackingRelayRequestSchema.safeParse(raw);
|
|
3065
|
+
if (!parsed.success) {
|
|
3066
|
+
const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
3067
|
+
return fail("INVALID_REQUEST", `Invalid relay request: ${issues}`, 400);
|
|
3068
|
+
}
|
|
3069
|
+
const body = parsed.data;
|
|
3070
|
+
const config = await options.getConfig({
|
|
3071
|
+
request,
|
|
3072
|
+
documentId: body.documentId,
|
|
3073
|
+
credentialId: body.credentialId,
|
|
3074
|
+
provider: body.provider
|
|
3075
|
+
});
|
|
3076
|
+
if (!config) {
|
|
3077
|
+
return fail("CONFIG_NOT_FOUND", "No tracking configuration found for this page", 404);
|
|
3078
|
+
}
|
|
3079
|
+
const clientIp = (options.getClientIp ?? defaultClientIp)(request);
|
|
3080
|
+
const clientUserAgent = request.headers.get("user-agent") || void 0;
|
|
3081
|
+
const userData = { ...body.event.userData || {} };
|
|
3082
|
+
delete userData.clientIp;
|
|
3083
|
+
delete userData.clientUserAgent;
|
|
3084
|
+
const event = { ...body.event, userData };
|
|
3085
|
+
const outcome = await dispatchServerTracking(event, config, {
|
|
3086
|
+
resolveSecrets: options.resolveSecrets,
|
|
3087
|
+
documentId: body.documentId,
|
|
3088
|
+
provider: body.provider,
|
|
3089
|
+
fetchFn: options.fetchFn,
|
|
3090
|
+
clientIp,
|
|
3091
|
+
clientUserAgent,
|
|
3092
|
+
onLog: options.onLog
|
|
3093
|
+
});
|
|
3094
|
+
const results = {};
|
|
3095
|
+
for (const [key, r] of Object.entries(outcome.results)) {
|
|
3096
|
+
results[key] = {
|
|
3097
|
+
provider: r.provider,
|
|
3098
|
+
success: r.success,
|
|
3099
|
+
...r.status !== void 0 ? { status: r.status } : {},
|
|
3100
|
+
...r.skipped ? { skipped: true } : {},
|
|
3101
|
+
...r.reason ? { reason: r.reason } : {},
|
|
3102
|
+
...r.error ? { error: r.error } : {}
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
return json(
|
|
3106
|
+
{
|
|
3107
|
+
version: TRACKING_RELAY_PROTOCOL_VERSION,
|
|
3108
|
+
success: outcome.success,
|
|
3109
|
+
eventId: outcome.eventId,
|
|
3110
|
+
...outcome.skipped ? { skipped: true } : {},
|
|
3111
|
+
...outcome.reason ? { reason: outcome.reason } : {},
|
|
3112
|
+
results
|
|
3113
|
+
},
|
|
3114
|
+
200
|
|
3115
|
+
);
|
|
3116
|
+
} catch (err) {
|
|
3117
|
+
options.onLog?.("[Tracking relay] internal error", err);
|
|
3118
|
+
return fail("INTERNAL_ERROR", "Tracking relay failed", 500);
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
|
|
2836
3123
|
// src/io/exporter.ts
|
|
2837
3124
|
import { zipSync, strToU8 } from "fflate";
|
|
2838
3125
|
import {
|
|
2839
3126
|
SCHEMA_NAME as SCHEMA_NAME4,
|
|
2840
|
-
CURRENT_SCHEMA_VERSION as
|
|
3127
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
|
|
2841
3128
|
} from "@kubuild/schema";
|
|
2842
3129
|
|
|
2843
3130
|
// src/validation/validator.ts
|
|
@@ -3380,7 +3667,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
|
|
|
3380
3667
|
`${currentPath}/props`,
|
|
3381
3668
|
effectiveNodeId,
|
|
3382
3669
|
options,
|
|
3383
|
-
errors
|
|
3670
|
+
errors,
|
|
3671
|
+
warnings
|
|
3384
3672
|
);
|
|
3385
3673
|
}
|
|
3386
3674
|
}
|
|
@@ -3395,7 +3683,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
|
|
|
3395
3683
|
}
|
|
3396
3684
|
}
|
|
3397
3685
|
}
|
|
3398
|
-
function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
3686
|
+
function validatePropsBindings(propsObj, propsPath, nodeId, options, errors, warnings) {
|
|
3687
|
+
const checkAssets = options.checkAssetReferences !== false;
|
|
3688
|
+
const checkVariables = options.checkVariableBindings !== false;
|
|
3689
|
+
const checkActions = options.checkActionBindings !== false;
|
|
3399
3690
|
for (const [key, value] of Object.entries(propsObj)) {
|
|
3400
3691
|
const currentPath = `${propsPath}/${key}`;
|
|
3401
3692
|
if (!value || typeof value !== "object") {
|
|
@@ -3409,7 +3700,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3409
3700
|
`${currentPath}/${index}`,
|
|
3410
3701
|
nodeId,
|
|
3411
3702
|
options,
|
|
3412
|
-
errors
|
|
3703
|
+
errors,
|
|
3704
|
+
warnings
|
|
3413
3705
|
);
|
|
3414
3706
|
}
|
|
3415
3707
|
});
|
|
@@ -3417,7 +3709,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3417
3709
|
}
|
|
3418
3710
|
const record = value;
|
|
3419
3711
|
if (record.type === "asset") {
|
|
3420
|
-
if (
|
|
3712
|
+
if (!checkAssets) continue;
|
|
3713
|
+
const assetId = typeof record.assetId === "string" && record.assetId.trim().length > 0 ? record.assetId : void 0;
|
|
3714
|
+
if (assetId === void 0) {
|
|
3421
3715
|
errors.push({
|
|
3422
3716
|
code: "INVALID_ASSET_REFERENCE",
|
|
3423
3717
|
message: 'Asset reference must have a non-empty "assetId"',
|
|
@@ -3433,7 +3727,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3433
3727
|
nodeId
|
|
3434
3728
|
});
|
|
3435
3729
|
}
|
|
3730
|
+
if (assetId !== void 0 && options.knownAssetIds && !isKnownId(options.knownAssetIds, assetId)) {
|
|
3731
|
+
if (typeof record.fallbackUrl === "string" && record.fallbackUrl.length > 0) {
|
|
3732
|
+
warnings.push({
|
|
3733
|
+
code: "UNRESOLVED_ASSET_REFERENCE",
|
|
3734
|
+
message: `Asset "${assetId}" is not among the known assets; its fallbackUrl will be used`,
|
|
3735
|
+
path: `${currentPath}/assetId`,
|
|
3736
|
+
nodeId,
|
|
3737
|
+
details: { assetId }
|
|
3738
|
+
});
|
|
3739
|
+
} else {
|
|
3740
|
+
errors.push({
|
|
3741
|
+
code: "INVALID_ASSET_REFERENCE",
|
|
3742
|
+
message: `Asset "${assetId}" is not among the known assets and has no fallbackUrl`,
|
|
3743
|
+
path: `${currentPath}/assetId`,
|
|
3744
|
+
nodeId,
|
|
3745
|
+
details: { assetId }
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3436
3749
|
} else if (record.type === "variable") {
|
|
3750
|
+
if (!checkVariables) continue;
|
|
3437
3751
|
if (typeof record.key !== "string" || record.key.trim().length === 0) {
|
|
3438
3752
|
errors.push({
|
|
3439
3753
|
code: "INVALID_VARIABLE_BINDING",
|
|
@@ -3443,6 +3757,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3443
3757
|
});
|
|
3444
3758
|
}
|
|
3445
3759
|
} else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
|
|
3760
|
+
if (!checkActions) continue;
|
|
3446
3761
|
if (typeof record.type !== "string" || record.type.trim().length === 0) {
|
|
3447
3762
|
errors.push({
|
|
3448
3763
|
code: "INVALID_ACTION_BINDING",
|
|
@@ -3452,10 +3767,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
|
|
|
3452
3767
|
});
|
|
3453
3768
|
}
|
|
3454
3769
|
} else {
|
|
3455
|
-
validatePropsBindings(record, currentPath, nodeId, options, errors);
|
|
3770
|
+
validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
|
|
3456
3771
|
}
|
|
3457
3772
|
}
|
|
3458
3773
|
}
|
|
3774
|
+
function isKnownId(ids, id) {
|
|
3775
|
+
return Array.isArray(ids) ? ids.includes(id) : ids.has(id);
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
// src/io/tracking-sanitizer.ts
|
|
3779
|
+
import { LEGACY_TRACKING_SECRET_KEYS } from "@kubuild/schema";
|
|
3780
|
+
function stripTrackingSecretsInPlace(tracking, basePath = "tracking") {
|
|
3781
|
+
const removed = [];
|
|
3782
|
+
if (!tracking || typeof tracking !== "object" || Array.isArray(tracking)) return removed;
|
|
3783
|
+
const providers = tracking.providers;
|
|
3784
|
+
if (!providers || typeof providers !== "object" || Array.isArray(providers)) return removed;
|
|
3785
|
+
for (const provider of Object.keys(LEGACY_TRACKING_SECRET_KEYS)) {
|
|
3786
|
+
const cfg = providers[provider];
|
|
3787
|
+
if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) continue;
|
|
3788
|
+
for (const key of LEGACY_TRACKING_SECRET_KEYS[provider]) {
|
|
3789
|
+
if (Object.prototype.hasOwnProperty.call(cfg, key)) {
|
|
3790
|
+
delete cfg[key];
|
|
3791
|
+
removed.push(`${basePath}.providers.${provider}.${key}`);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
return removed;
|
|
3796
|
+
}
|
|
3797
|
+
function stripDocumentTrackingSecretsInPlace(doc) {
|
|
3798
|
+
const removed = [];
|
|
3799
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return removed;
|
|
3800
|
+
const record = doc;
|
|
3801
|
+
removed.push(...stripTrackingSecretsInPlace(record.tracking, "tracking"));
|
|
3802
|
+
const metadata = record.metadata;
|
|
3803
|
+
if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
|
|
3804
|
+
removed.push(
|
|
3805
|
+
...stripTrackingSecretsInPlace(metadata.tracking, "metadata.tracking")
|
|
3806
|
+
);
|
|
3807
|
+
}
|
|
3808
|
+
if (Array.isArray(record.artboards)) {
|
|
3809
|
+
record.artboards.forEach((artboard, index) => {
|
|
3810
|
+
if (artboard && typeof artboard === "object") {
|
|
3811
|
+
const inner = artboard.document;
|
|
3812
|
+
for (const path of stripDocumentTrackingSecretsInPlace(inner)) {
|
|
3813
|
+
removed.push(`artboards.${index}.document.${path}`);
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
return removed;
|
|
3819
|
+
}
|
|
3820
|
+
function sanitizeDocumentTracking(doc) {
|
|
3821
|
+
if (!doc || typeof doc !== "object") return { document: doc, removed: [] };
|
|
3822
|
+
const copy = JSON.parse(JSON.stringify(doc));
|
|
3823
|
+
const removed = stripDocumentTrackingSecretsInPlace(copy);
|
|
3824
|
+
return { document: copy, removed };
|
|
3825
|
+
}
|
|
3459
3826
|
|
|
3460
3827
|
// src/io/exporter.ts
|
|
3461
3828
|
function sha256Sync(data) {
|
|
@@ -3676,6 +4043,7 @@ async function exportPackage(document, options = {}) {
|
|
|
3676
4043
|
};
|
|
3677
4044
|
}
|
|
3678
4045
|
const pageDoc = JSON.parse(JSON.stringify(validation.data));
|
|
4046
|
+
stripDocumentTrackingSecretsInPlace(pageDoc);
|
|
3679
4047
|
if (options.metadata) {
|
|
3680
4048
|
pageDoc.metadata = {
|
|
3681
4049
|
...pageDoc.metadata || {
|
|
@@ -3800,7 +4168,7 @@ async function exportPackage(document, options = {}) {
|
|
|
3800
4168
|
};
|
|
3801
4169
|
const manifest = {
|
|
3802
4170
|
schema: SCHEMA_NAME4,
|
|
3803
|
-
schemaVersion: pageDoc.version ||
|
|
4171
|
+
schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION3,
|
|
3804
4172
|
packageVersion: options.packageVersion || "1.0.0",
|
|
3805
4173
|
builderCompatibility: options.builderCompatibility || ">=0.1.0",
|
|
3806
4174
|
requiredComponents: requirements.requiredComponents,
|
|
@@ -3827,13 +4195,14 @@ var exportStoraPackage = exportPackage;
|
|
|
3827
4195
|
import { unzipSync, strFromU8 } from "fflate";
|
|
3828
4196
|
import {
|
|
3829
4197
|
ManifestSchema,
|
|
3830
|
-
CURRENT_SCHEMA_VERSION as
|
|
4198
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION5
|
|
3831
4199
|
} from "@kubuild/schema";
|
|
3832
4200
|
|
|
3833
4201
|
// src/io/migration.ts
|
|
3834
4202
|
import {
|
|
3835
|
-
CURRENT_SCHEMA_VERSION as
|
|
3836
|
-
SCHEMA_NAME as SCHEMA_NAME5
|
|
4203
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4,
|
|
4204
|
+
SCHEMA_NAME as SCHEMA_NAME5,
|
|
4205
|
+
getCanonicalTextPropRule
|
|
3837
4206
|
} from "@kubuild/schema";
|
|
3838
4207
|
var MigrationRegistry = class {
|
|
3839
4208
|
steps = /* @__PURE__ */ new Map();
|
|
@@ -3987,17 +4356,86 @@ defaultMigrationRegistry.register({
|
|
|
3987
4356
|
return migrated;
|
|
3988
4357
|
}
|
|
3989
4358
|
});
|
|
3990
|
-
|
|
4359
|
+
defaultMigrationRegistry.register({
|
|
4360
|
+
fromVersion: "1.0.0",
|
|
4361
|
+
toVersion: "1.1.0",
|
|
4362
|
+
description: "Remove tracking secrets and server destinations (capiAccessToken, accessToken, measurementProtocolSecret, serverRelayUrl, custom endpointUrl/headers) from the document",
|
|
4363
|
+
migrate: (rawDoc, context) => {
|
|
4364
|
+
const migrated = deepClone(rawDoc);
|
|
4365
|
+
migrated.version = "1.1.0";
|
|
4366
|
+
const removed = stripDocumentTrackingSecretsInPlace(migrated);
|
|
4367
|
+
if (removed.length > 0) {
|
|
4368
|
+
context.warn({
|
|
4369
|
+
code: "TRACKING_SECRET_REMOVED",
|
|
4370
|
+
message: "Tracking secret removed; re-link credential via credentialId (secrets are now stored by the host and resolved server-side).",
|
|
4371
|
+
step: "1.0.0->1.1.0",
|
|
4372
|
+
paths: removed
|
|
4373
|
+
});
|
|
4374
|
+
}
|
|
4375
|
+
return migrated;
|
|
4376
|
+
}
|
|
4377
|
+
});
|
|
4378
|
+
function canonicalizeTextPropsInPlace(root, basePath = "document") {
|
|
4379
|
+
const changed = [];
|
|
4380
|
+
const walk2 = (value, path) => {
|
|
4381
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
4382
|
+
const node = value;
|
|
4383
|
+
const props = node.props;
|
|
4384
|
+
const rule = typeof node.type === "string" ? getCanonicalTextPropRule(node.type) : void 0;
|
|
4385
|
+
if (rule && props && typeof props === "object" && !Array.isArray(props)) {
|
|
4386
|
+
const record = props;
|
|
4387
|
+
const presentAliases = rule.aliases.filter((alias) => record[alias] !== void 0);
|
|
4388
|
+
if (presentAliases.length > 0) {
|
|
4389
|
+
const displayedKey = rule.legacyReadOrder.find((key) => record[key] !== void 0);
|
|
4390
|
+
if (node.type === "text" && displayedKey === "content" && record.as === void 0 && record.tag === void 0) {
|
|
4391
|
+
record.as = "p";
|
|
4392
|
+
}
|
|
4393
|
+
if (displayedKey !== void 0 && displayedKey !== rule.canonical) {
|
|
4394
|
+
record[rule.canonical] = record[displayedKey];
|
|
4395
|
+
}
|
|
4396
|
+
for (const alias of presentAliases) {
|
|
4397
|
+
delete record[alias];
|
|
4398
|
+
changed.push(`${path}.props.${alias}`);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
if (Array.isArray(node.children)) {
|
|
4403
|
+
node.children.forEach((child, index) => walk2(child, `${path}.children.${index}`));
|
|
4404
|
+
}
|
|
4405
|
+
};
|
|
4406
|
+
walk2(root, basePath);
|
|
4407
|
+
return changed;
|
|
4408
|
+
}
|
|
4409
|
+
defaultMigrationRegistry.register({
|
|
4410
|
+
fromVersion: "1.1.0",
|
|
4411
|
+
toVersion: "1.2.0",
|
|
4412
|
+
description: "Rename deprecated text prop aliases to canonical names (heading/text/paragraph/link/badge/blockquote -> text, button -> label)",
|
|
4413
|
+
migrate: (rawDoc, context) => {
|
|
4414
|
+
const migrated = deepClone(rawDoc);
|
|
4415
|
+
migrated.version = "1.2.0";
|
|
4416
|
+
const changed = canonicalizeTextPropsInPlace(migrated.document);
|
|
4417
|
+
if (changed.length > 0) {
|
|
4418
|
+
context.warn({
|
|
4419
|
+
code: "PROP_ALIAS_MIGRATED",
|
|
4420
|
+
message: "Deprecated text prop aliases were renamed to their canonical names.",
|
|
4421
|
+
step: "1.1.0->1.2.0",
|
|
4422
|
+
paths: changed
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
return migrated;
|
|
4426
|
+
}
|
|
4427
|
+
});
|
|
4428
|
+
function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
|
|
3991
4429
|
if (!sourceVersion || !targetVersion) return false;
|
|
3992
4430
|
if (sourceVersion === targetVersion) return true;
|
|
3993
4431
|
return registry.hasPath(sourceVersion, targetVersion);
|
|
3994
4432
|
}
|
|
3995
|
-
function getMigrationPath(sourceVersion, targetVersion =
|
|
4433
|
+
function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
|
|
3996
4434
|
if (!sourceVersion || !targetVersion) return null;
|
|
3997
4435
|
return registry.findPath(sourceVersion, targetVersion);
|
|
3998
4436
|
}
|
|
3999
4437
|
function migrateDocument(rawDocument, options = {}) {
|
|
4000
|
-
const targetVersion = options.targetVersion ??
|
|
4438
|
+
const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION4;
|
|
4001
4439
|
const dryRun = options.dryRun ?? false;
|
|
4002
4440
|
const registry = options.registry ?? defaultMigrationRegistry;
|
|
4003
4441
|
const validate = options.validate ?? true;
|
|
@@ -4025,6 +4463,14 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4025
4463
|
const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
|
|
4026
4464
|
if (sourceVersion === targetVersion) {
|
|
4027
4465
|
const cloned = deepClone(doc);
|
|
4466
|
+
const removedSecrets = stripDocumentTrackingSecretsInPlace(cloned);
|
|
4467
|
+
const currentWarnings = removedSecrets.length > 0 ? [
|
|
4468
|
+
{
|
|
4469
|
+
code: "TRACKING_SECRET_REMOVED",
|
|
4470
|
+
message: "Tracking secret removed; re-link credential via credentialId (secrets are stored by the host and resolved server-side).",
|
|
4471
|
+
paths: removedSecrets
|
|
4472
|
+
}
|
|
4473
|
+
] : [];
|
|
4028
4474
|
if (validate) {
|
|
4029
4475
|
const validation = validateDocument(cloned);
|
|
4030
4476
|
if (!validation.valid) {
|
|
@@ -4056,7 +4502,8 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4056
4502
|
targetVersion,
|
|
4057
4503
|
migrationPath: [sourceVersion],
|
|
4058
4504
|
stepsApplied: 0,
|
|
4059
|
-
dryRun
|
|
4505
|
+
dryRun,
|
|
4506
|
+
...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
|
|
4060
4507
|
}
|
|
4061
4508
|
};
|
|
4062
4509
|
}
|
|
@@ -4082,6 +4529,16 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4082
4529
|
};
|
|
4083
4530
|
}
|
|
4084
4531
|
if (dryRun) {
|
|
4532
|
+
const simulatedWarnings = [];
|
|
4533
|
+
try {
|
|
4534
|
+
let simulated = deepClone(doc);
|
|
4535
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
4536
|
+
const step = registry.getStep(path[i], path[i + 1]);
|
|
4537
|
+
if (!step) break;
|
|
4538
|
+
simulated = step.migrate(simulated, { warn: (w) => simulatedWarnings.push(w) });
|
|
4539
|
+
}
|
|
4540
|
+
} catch {
|
|
4541
|
+
}
|
|
4085
4542
|
return {
|
|
4086
4543
|
success: true,
|
|
4087
4544
|
diagnostic: {
|
|
@@ -4090,12 +4547,15 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4090
4547
|
targetVersion,
|
|
4091
4548
|
migrationPath: path,
|
|
4092
4549
|
stepsApplied: path.length - 1,
|
|
4093
|
-
dryRun: true
|
|
4550
|
+
dryRun: true,
|
|
4551
|
+
...simulatedWarnings.length > 0 ? { warnings: simulatedWarnings } : {}
|
|
4094
4552
|
}
|
|
4095
4553
|
};
|
|
4096
4554
|
}
|
|
4097
4555
|
let currentDoc = deepClone(doc);
|
|
4098
4556
|
let stepsApplied = 0;
|
|
4557
|
+
const warnings = [];
|
|
4558
|
+
const stepContext = { warn: (w) => warnings.push(w) };
|
|
4099
4559
|
for (let i = 0; i < path.length - 1; i++) {
|
|
4100
4560
|
const fromVer = path[i];
|
|
4101
4561
|
const toVer = path[i + 1];
|
|
@@ -4121,7 +4581,7 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4121
4581
|
};
|
|
4122
4582
|
}
|
|
4123
4583
|
try {
|
|
4124
|
-
currentDoc = step.migrate(currentDoc);
|
|
4584
|
+
currentDoc = step.migrate(currentDoc, stepContext);
|
|
4125
4585
|
stepsApplied++;
|
|
4126
4586
|
} catch (err) {
|
|
4127
4587
|
const error = {
|
|
@@ -4177,7 +4637,8 @@ function migrateDocument(rawDocument, options = {}) {
|
|
|
4177
4637
|
targetVersion,
|
|
4178
4638
|
migrationPath: path,
|
|
4179
4639
|
stepsApplied,
|
|
4180
|
-
dryRun: false
|
|
4640
|
+
dryRun: false,
|
|
4641
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
4181
4642
|
}
|
|
4182
4643
|
};
|
|
4183
4644
|
}
|
|
@@ -4212,7 +4673,7 @@ async function preflightPackage(archiveData, options = {}) {
|
|
|
4212
4673
|
...DEFAULT_SECURITY_LIMITS,
|
|
4213
4674
|
...options.securityLimits
|
|
4214
4675
|
};
|
|
4215
|
-
const targetVersion = options.targetSchemaVersion ||
|
|
4676
|
+
const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5;
|
|
4216
4677
|
const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
|
|
4217
4678
|
const diagnostics = [];
|
|
4218
4679
|
let dependencyPolicy = options.dependencyPolicy || "cancel";
|
|
@@ -4375,6 +4836,16 @@ async function preflightPackage(archiveData, options = {}) {
|
|
|
4375
4836
|
let rawPageDoc;
|
|
4376
4837
|
try {
|
|
4377
4838
|
rawPageDoc = JSON.parse(strFromU8(pageEntry));
|
|
4839
|
+
const secretScan = sanitizeDocumentTracking(rawPageDoc);
|
|
4840
|
+
if (secretScan.removed.length > 0) {
|
|
4841
|
+
diagnostics.push({
|
|
4842
|
+
code: "TRACKING_SECRET_REMOVED",
|
|
4843
|
+
severity: "warning",
|
|
4844
|
+
message: "Tracking secrets found in page.json will be removed on import; re-link credentials via credentialId.",
|
|
4845
|
+
path: "page.json",
|
|
4846
|
+
details: { paths: secretScan.removed }
|
|
4847
|
+
});
|
|
4848
|
+
}
|
|
4378
4849
|
const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
|
|
4379
4850
|
if (pageProtoCheck.found) {
|
|
4380
4851
|
diagnostics.push({
|
|
@@ -4621,7 +5092,7 @@ function buildReport(valid, canImport, fields) {
|
|
|
4621
5092
|
return {
|
|
4622
5093
|
valid,
|
|
4623
5094
|
canImport,
|
|
4624
|
-
targetVersion: fields.targetVersion ||
|
|
5095
|
+
targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION5,
|
|
4625
5096
|
requiresMigration: fields.requiresMigration || false,
|
|
4626
5097
|
missingComponents: fields.missingComponents || [],
|
|
4627
5098
|
missingCapabilities: fields.missingCapabilities || [],
|
|
@@ -4694,7 +5165,7 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4694
5165
|
if (preflight.requiresMigration) {
|
|
4695
5166
|
const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
|
|
4696
5167
|
const migrationRes = migrateDocument(pageRaw, {
|
|
4697
|
-
targetVersion: options.targetSchemaVersion ||
|
|
5168
|
+
targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5,
|
|
4698
5169
|
registry: migrationRegistry,
|
|
4699
5170
|
validate: true
|
|
4700
5171
|
});
|
|
@@ -4716,13 +5187,24 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4716
5187
|
} else {
|
|
4717
5188
|
finalDocument = pageRaw;
|
|
4718
5189
|
}
|
|
5190
|
+
const importWarnings = [];
|
|
5191
|
+
const strippedSecrets = stripDocumentTrackingSecretsInPlace(finalDocument);
|
|
5192
|
+
if (strippedSecrets.length > 0) {
|
|
5193
|
+
importWarnings.push({
|
|
5194
|
+
code: "TRACKING_SECRET_REMOVED",
|
|
5195
|
+
severity: "warning",
|
|
5196
|
+
message: "Tracking secret removed from imported document; re-link credential via credentialId.",
|
|
5197
|
+
path: "page.json",
|
|
5198
|
+
details: { paths: strippedSecrets }
|
|
5199
|
+
});
|
|
5200
|
+
}
|
|
4719
5201
|
let metadata = finalDocument.metadata || {
|
|
4720
5202
|
title: "Imported Page",
|
|
4721
5203
|
description: "",
|
|
4722
5204
|
author: "",
|
|
4723
5205
|
tags: [],
|
|
4724
5206
|
category: "general",
|
|
4725
|
-
version: finalDocument.version ||
|
|
5207
|
+
version: finalDocument.version || CURRENT_SCHEMA_VERSION5
|
|
4726
5208
|
};
|
|
4727
5209
|
if (unzipped["metadata.json"]) {
|
|
4728
5210
|
try {
|
|
@@ -4874,7 +5356,8 @@ async function importPackage(archiveData, options = {}) {
|
|
|
4874
5356
|
metadata,
|
|
4875
5357
|
extractedAssets,
|
|
4876
5358
|
renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
|
|
4877
|
-
preflight
|
|
5359
|
+
preflight,
|
|
5360
|
+
...importWarnings.length > 0 ? { warnings: importWarnings } : {}
|
|
4878
5361
|
};
|
|
4879
5362
|
}
|
|
4880
5363
|
var importStoraPackage = importPackage;
|
|
@@ -4884,8 +5367,21 @@ import {
|
|
|
4884
5367
|
ProjectDocumentSchema,
|
|
4885
5368
|
PROJECT_SCHEMA_NAME as PROJECT_SCHEMA_NAME2,
|
|
4886
5369
|
CURRENT_PROJECT_SCHEMA_VERSION as CURRENT_PROJECT_SCHEMA_VERSION2,
|
|
5370
|
+
CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION6,
|
|
4887
5371
|
looksLikeProjectDocument
|
|
4888
5372
|
} from "@kubuild/schema";
|
|
5373
|
+
function migrateProjectArtboards(project) {
|
|
5374
|
+
let changed = false;
|
|
5375
|
+
const artboards = project.artboards.map((artboard) => {
|
|
5376
|
+
const version = artboard.document.version;
|
|
5377
|
+
if (version === CURRENT_SCHEMA_VERSION6 || !canMigrate(version)) return artboard;
|
|
5378
|
+
const migration = migrateDocument(artboard.document);
|
|
5379
|
+
if (!migration.success || !migration.document) return artboard;
|
|
5380
|
+
changed = true;
|
|
5381
|
+
return { ...artboard, document: migration.document };
|
|
5382
|
+
});
|
|
5383
|
+
return changed ? { ...project, artboards } : project;
|
|
5384
|
+
}
|
|
4889
5385
|
var DEFAULT_PAGE_ARTBOARD_ID = "artboard-page-1";
|
|
4890
5386
|
function wrapPageDocumentAsProject(document, options = {}) {
|
|
4891
5387
|
const artboardId = options.artboardId ?? DEFAULT_PAGE_ARTBOARD_ID;
|
|
@@ -4935,7 +5431,7 @@ function loadProjectDocument(raw) {
|
|
|
4935
5431
|
}
|
|
4936
5432
|
return {
|
|
4937
5433
|
success: true,
|
|
4938
|
-
project: parsed.data,
|
|
5434
|
+
project: migrateProjectArtboards(parsed.data),
|
|
4939
5435
|
wrappedFromLegacyPage: false,
|
|
4940
5436
|
errors: []
|
|
4941
5437
|
};
|
|
@@ -5406,21 +5902,11 @@ function compareManifestsSemantically(expected, actual, options = {}) {
|
|
|
5406
5902
|
// src/io/template-utils.ts
|
|
5407
5903
|
import {
|
|
5408
5904
|
TemplateRecordSchema,
|
|
5905
|
+
BUILTIN_COMPONENT_TYPES,
|
|
5409
5906
|
collectNodeIds as collectNodeIds2,
|
|
5410
5907
|
isTemplateRecord
|
|
5411
5908
|
} from "@kubuild/schema";
|
|
5412
|
-
var CORE_BUILTIN_COMPONENTS =
|
|
5413
|
-
"page",
|
|
5414
|
-
"section",
|
|
5415
|
-
"container",
|
|
5416
|
-
"columns",
|
|
5417
|
-
"column",
|
|
5418
|
-
"heading",
|
|
5419
|
-
"text",
|
|
5420
|
-
"image",
|
|
5421
|
-
"button",
|
|
5422
|
-
"collection"
|
|
5423
|
-
]);
|
|
5909
|
+
var CORE_BUILTIN_COMPONENTS = new Set(BUILTIN_COMPONENT_TYPES);
|
|
5424
5910
|
function validateTemplate(value) {
|
|
5425
5911
|
const parseResult = TemplateRecordSchema.safeParse(value);
|
|
5426
5912
|
if (parseResult.success) {
|
|
@@ -5531,22 +6017,6 @@ function saveDraftAsTemplate(draft, metadata, options = {}) {
|
|
|
5531
6017
|
custom: metadata.custom ? deepClone(metadata.custom) : void 0
|
|
5532
6018
|
});
|
|
5533
6019
|
}
|
|
5534
|
-
function cloneTreeWithFreshIds(root, idGen) {
|
|
5535
|
-
function cloneRec(node) {
|
|
5536
|
-
const newId = idGen(node.id, node);
|
|
5537
|
-
const clonedProps = node.props ? deepClone(node.props) : void 0;
|
|
5538
|
-
const clonedStyles = node.styles ? deepClone(node.styles) : void 0;
|
|
5539
|
-
const clonedChildren = node.children ? node.children.map((child) => cloneRec(child)) : [];
|
|
5540
|
-
return {
|
|
5541
|
-
id: newId,
|
|
5542
|
-
type: node.type,
|
|
5543
|
-
...clonedProps ? { props: clonedProps } : {},
|
|
5544
|
-
...clonedStyles ? { styles: clonedStyles } : {},
|
|
5545
|
-
children: clonedChildren
|
|
5546
|
-
};
|
|
5547
|
-
}
|
|
5548
|
-
return cloneRec(root);
|
|
5549
|
-
}
|
|
5550
6020
|
function cloneTemplateAsPage(templateOrDoc, options = {}) {
|
|
5551
6021
|
let sourceDoc;
|
|
5552
6022
|
let templateOrigin = null;
|
|
@@ -5582,7 +6052,7 @@ function cloneTemplateAsPage(templateOrDoc, options = {}) {
|
|
|
5582
6052
|
return candidate;
|
|
5583
6053
|
};
|
|
5584
6054
|
const idGen = options.idGenerator || defaultIdGen;
|
|
5585
|
-
const clonedRootNode =
|
|
6055
|
+
const { clonedNode: clonedRootNode } = cloneNodeTreeWithFreshIds(sourceDoc.document, idGen);
|
|
5586
6056
|
const rootPageNode = {
|
|
5587
6057
|
...clonedRootNode,
|
|
5588
6058
|
type: "page"
|
|
@@ -5993,6 +6463,7 @@ export {
|
|
|
5993
6463
|
DocumentHistoryManager,
|
|
5994
6464
|
HistoryEngine,
|
|
5995
6465
|
MigrationRegistry,
|
|
6466
|
+
NODE_ID_REFERENCE_KEYS,
|
|
5996
6467
|
RuntimeStateStore,
|
|
5997
6468
|
addArtboard,
|
|
5998
6469
|
applyFieldTransform,
|
|
@@ -6000,7 +6471,9 @@ export {
|
|
|
6000
6471
|
buildSampleVariablesFromCatalog,
|
|
6001
6472
|
calculateChecksum,
|
|
6002
6473
|
canMigrate,
|
|
6474
|
+
canonicalizeTextPropsInPlace,
|
|
6003
6475
|
checkZipBomb,
|
|
6476
|
+
cloneNodeTreeWithFreshIds,
|
|
6004
6477
|
cloneTemplateAsPage,
|
|
6005
6478
|
cloneTreeWithNewIds,
|
|
6006
6479
|
collectArtboardReferenceNodes,
|
|
@@ -6017,6 +6490,7 @@ export {
|
|
|
6017
6490
|
createPageArtboard,
|
|
6018
6491
|
createRuntimeStore,
|
|
6019
6492
|
createTemplateRecord,
|
|
6493
|
+
createTrackingRelayHandler,
|
|
6020
6494
|
deepClone,
|
|
6021
6495
|
defaultIdGenerator,
|
|
6022
6496
|
defaultMigrationRegistry,
|
|
@@ -6076,6 +6550,7 @@ export {
|
|
|
6076
6550
|
previewImportPackage,
|
|
6077
6551
|
remapAssetReferences,
|
|
6078
6552
|
remapDocumentAssetReferences,
|
|
6553
|
+
remapNodeReferences,
|
|
6079
6554
|
removeArtboard,
|
|
6080
6555
|
removeNode,
|
|
6081
6556
|
renameArtboard,
|
|
@@ -6083,6 +6558,7 @@ export {
|
|
|
6083
6558
|
resolveBinding,
|
|
6084
6559
|
resolveBindingValue,
|
|
6085
6560
|
resolvePropertyPath,
|
|
6561
|
+
sanitizeDocumentTracking,
|
|
6086
6562
|
sanitizeFilename,
|
|
6087
6563
|
sanitizeHtml,
|
|
6088
6564
|
sanitizeUrl,
|
|
@@ -6093,6 +6569,8 @@ export {
|
|
|
6093
6569
|
sendTikTokEventsApi,
|
|
6094
6570
|
setActiveArtboard,
|
|
6095
6571
|
sha256Sync,
|
|
6572
|
+
stripDocumentTrackingSecretsInPlace,
|
|
6573
|
+
stripTrackingSecretsInPlace,
|
|
6096
6574
|
ungroupNodeFrame,
|
|
6097
6575
|
updateActions,
|
|
6098
6576
|
updateAnimation,
|
|
@@ -6100,6 +6578,7 @@ export {
|
|
|
6100
6578
|
updateFormConfig,
|
|
6101
6579
|
updateProps,
|
|
6102
6580
|
updateStyle,
|
|
6581
|
+
updateTheme,
|
|
6103
6582
|
validateDocument,
|
|
6104
6583
|
validateDocumentSecurity,
|
|
6105
6584
|
validateFieldValue,
|