@koda-sl/baker-cli 0.312.0 → 0.313.0-dev.ca75dc08a
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/README.md +1 -1
- package/dist/{chunk-WONWYE7B.js → chunk-PY7R67NE.js} +4 -4
- package/dist/chunk-PY7R67NE.js.map +1 -0
- package/dist/cli.js +250 -84
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-WONWYE7B.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
validateCanvasDeep,
|
|
68
68
|
withoutCastName,
|
|
69
69
|
ytDlpBlockSignal
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-PY7R67NE.js";
|
|
71
71
|
import {
|
|
72
72
|
csvOrJson,
|
|
73
73
|
daysAgoIso,
|
|
@@ -10749,14 +10749,14 @@ function collectionLine(collection) {
|
|
|
10749
10749
|
}
|
|
10750
10750
|
function accountsInTree(nodes) {
|
|
10751
10751
|
const accounts = /* @__PURE__ */ new Set();
|
|
10752
|
-
const
|
|
10752
|
+
const walk = (node) => {
|
|
10753
10753
|
accounts.add(node.customerId);
|
|
10754
10754
|
for (const child of node.children) {
|
|
10755
|
-
|
|
10755
|
+
walk(child);
|
|
10756
10756
|
}
|
|
10757
10757
|
};
|
|
10758
10758
|
for (const node of nodes) {
|
|
10759
|
-
|
|
10759
|
+
walk(node);
|
|
10760
10760
|
}
|
|
10761
10761
|
return accounts;
|
|
10762
10762
|
}
|
|
@@ -21383,14 +21383,14 @@ function collectionLine2(collection) {
|
|
|
21383
21383
|
}
|
|
21384
21384
|
function accountsInTree2(nodes) {
|
|
21385
21385
|
const accounts = /* @__PURE__ */ new Set();
|
|
21386
|
-
const
|
|
21386
|
+
const walk = (node) => {
|
|
21387
21387
|
accounts.add(node.accountId);
|
|
21388
21388
|
for (const child of node.children) {
|
|
21389
|
-
|
|
21389
|
+
walk(child);
|
|
21390
21390
|
}
|
|
21391
21391
|
};
|
|
21392
21392
|
for (const node of nodes) {
|
|
21393
|
-
|
|
21393
|
+
walk(node);
|
|
21394
21394
|
}
|
|
21395
21395
|
return accounts;
|
|
21396
21396
|
}
|
|
@@ -26995,14 +26995,14 @@ function collectionLine3(collection) {
|
|
|
26995
26995
|
}
|
|
26996
26996
|
function accountsInTree3(nodes) {
|
|
26997
26997
|
const accounts = /* @__PURE__ */ new Set();
|
|
26998
|
-
const
|
|
26998
|
+
const walk = (node) => {
|
|
26999
26999
|
accounts.add(node.accountId);
|
|
27000
27000
|
for (const child of node.children) {
|
|
27001
|
-
|
|
27001
|
+
walk(child);
|
|
27002
27002
|
}
|
|
27003
27003
|
};
|
|
27004
27004
|
for (const node of nodes) {
|
|
27005
|
-
|
|
27005
|
+
walk(node);
|
|
27006
27006
|
}
|
|
27007
27007
|
return accounts;
|
|
27008
27008
|
}
|
|
@@ -28820,6 +28820,55 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
|
|
|
28820
28820
|
// src/commands/analytics/index.ts
|
|
28821
28821
|
import { readFileSync as readFileSync12 } from "fs";
|
|
28822
28822
|
|
|
28823
|
+
// ../api/src/analytics/flowEventKeys.ts
|
|
28824
|
+
function flowNodes(node, acc = []) {
|
|
28825
|
+
if (!node || typeof node !== "object") return acc;
|
|
28826
|
+
const record = node;
|
|
28827
|
+
acc.push(record);
|
|
28828
|
+
if (Array.isArray(record.children)) for (const child of record.children) flowNodes(child, acc);
|
|
28829
|
+
return acc;
|
|
28830
|
+
}
|
|
28831
|
+
function flowEventKeys(tree, slug) {
|
|
28832
|
+
const primary = [];
|
|
28833
|
+
const secondary = [];
|
|
28834
|
+
const nodes = flowNodes(tree).filter((node) => typeof node.id === "string" && node.id !== "");
|
|
28835
|
+
if (nodes.length === 0) return [];
|
|
28836
|
+
for (const node of nodes) {
|
|
28837
|
+
const id = node.id;
|
|
28838
|
+
const triggers = flowNodeTriggers(node.nodeData);
|
|
28839
|
+
for (const triggerId of triggers.available) {
|
|
28840
|
+
const candidate = {
|
|
28841
|
+
eventKey: formEventKey(slug, id, triggerId),
|
|
28842
|
+
nodeId: id,
|
|
28843
|
+
nodeName: typeof node.name === "string" ? node.name : id,
|
|
28844
|
+
nodeType: triggers.type,
|
|
28845
|
+
triggerId,
|
|
28846
|
+
isCompletion: triggers.completion === triggerId
|
|
28847
|
+
};
|
|
28848
|
+
(candidate.isCompletion ? primary : secondary).push(candidate);
|
|
28849
|
+
}
|
|
28850
|
+
}
|
|
28851
|
+
return [
|
|
28852
|
+
...primary,
|
|
28853
|
+
...secondary,
|
|
28854
|
+
{
|
|
28855
|
+
eventKey: `submit:${slug}`,
|
|
28856
|
+
nodeId: null,
|
|
28857
|
+
nodeName: "the Form ran out of steps",
|
|
28858
|
+
nodeType: null,
|
|
28859
|
+
triggerId: null,
|
|
28860
|
+
// A Form reaching its last node is as primary as an outcome gets for a
|
|
28861
|
+
// Form with no widget in it — but it never fires on one that ends in a
|
|
28862
|
+
// booking widget or a redirect, so it does not lead the list.
|
|
28863
|
+
isCompletion: false
|
|
28864
|
+
}
|
|
28865
|
+
];
|
|
28866
|
+
}
|
|
28867
|
+
function suggestedEnding(endings) {
|
|
28868
|
+
const deepestFirst = [...endings].reverse();
|
|
28869
|
+
return deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? endings[endings.length - 1] ?? null;
|
|
28870
|
+
}
|
|
28871
|
+
|
|
28823
28872
|
// ../api/src/event-ingest/arrivals.ts
|
|
28824
28873
|
import { z as z33 } from "zod";
|
|
28825
28874
|
|
|
@@ -29999,56 +30048,14 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
29999
30048
|
}
|
|
30000
30049
|
|
|
30001
30050
|
// src/commands/analytics/flowConversionKeys.ts
|
|
30002
|
-
function walk(node, acc = []) {
|
|
30003
|
-
if (!node || typeof node !== "object") return acc;
|
|
30004
|
-
const record = node;
|
|
30005
|
-
acc.push(record);
|
|
30006
|
-
if (Array.isArray(record.children)) for (const child of record.children) walk(child, acc);
|
|
30007
|
-
return acc;
|
|
30008
|
-
}
|
|
30009
|
-
function flowConversionCandidates(tree, slug) {
|
|
30010
|
-
const primary = [];
|
|
30011
|
-
const secondary = [];
|
|
30012
|
-
for (const node of walk(tree)) {
|
|
30013
|
-
const id = typeof node.id === "string" ? node.id : null;
|
|
30014
|
-
if (!id) continue;
|
|
30015
|
-
const triggers = flowNodeTriggers(node.nodeData);
|
|
30016
|
-
for (const triggerId of triggers.available) {
|
|
30017
|
-
const candidate = {
|
|
30018
|
-
eventKey: formEventKey(slug, id, triggerId),
|
|
30019
|
-
nodeId: id,
|
|
30020
|
-
nodeName: typeof node.name === "string" ? node.name : id,
|
|
30021
|
-
nodeType: triggers.type,
|
|
30022
|
-
triggerId,
|
|
30023
|
-
isCompletion: triggers.completion === triggerId
|
|
30024
|
-
};
|
|
30025
|
-
(candidate.isCompletion ? primary : secondary).push(candidate);
|
|
30026
|
-
}
|
|
30027
|
-
}
|
|
30028
|
-
return [
|
|
30029
|
-
...primary,
|
|
30030
|
-
...secondary,
|
|
30031
|
-
{
|
|
30032
|
-
eventKey: `submit:${slug}`,
|
|
30033
|
-
nodeId: null,
|
|
30034
|
-
nodeName: "the Form ran out of steps",
|
|
30035
|
-
nodeType: null,
|
|
30036
|
-
triggerId: null,
|
|
30037
|
-
// A Form reaching its last node is as primary as an outcome gets for a
|
|
30038
|
-
// Form with no widget in it — but it never fires on one that ends in a
|
|
30039
|
-
// booking widget or a redirect, so it does not lead the list.
|
|
30040
|
-
isCompletion: false
|
|
30041
|
-
}
|
|
30042
|
-
];
|
|
30043
|
-
}
|
|
30044
30051
|
function formKeyRefusal(eventKey, tree, slug) {
|
|
30045
30052
|
const parsed = parseEventKey(eventKey);
|
|
30046
30053
|
if (!parsed || parsed.family !== "form" || parsed.flowSlug !== slug) return null;
|
|
30047
|
-
const node =
|
|
30054
|
+
const node = flowNodes(tree).find((candidate) => candidate.id === parsed.nodeId);
|
|
30048
30055
|
if (!node) {
|
|
30049
30056
|
return {
|
|
30050
30057
|
message: `"${slug}" has no step with id "${parsed.nodeId}", so this key would be stored and count zero forever.`,
|
|
30051
|
-
fix: `Steps in this Form: ${
|
|
30058
|
+
fix: `Steps in this Form: ${flowNodes(tree).map((n) => `${String(n.id)} (${String(n.name ?? "unnamed")})`).join(", ")}. \`baker analytics conversions --flow ${slug}\` prints each one already spelled as an event key.`
|
|
30052
30059
|
};
|
|
30053
30060
|
}
|
|
30054
30061
|
const triggers = flowNodeTriggers(node.nodeData);
|
|
@@ -30079,8 +30086,7 @@ function flowConversionHints(slug, rows, noConversionReason) {
|
|
|
30079
30086
|
`Nothing here is counted, and that is on purpose: this Form declares no outcome \u2014 "${noConversionReason}". Leave it alone unless that has stopped being true.`
|
|
30080
30087
|
];
|
|
30081
30088
|
}
|
|
30082
|
-
const
|
|
30083
|
-
const suggestion = deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? rows[rows.length - 1];
|
|
30089
|
+
const suggestion = suggestedEnding(rows);
|
|
30084
30090
|
return [
|
|
30085
30091
|
`Nothing on "${slug}" is counted, so it converts nobody on every report however many people finish it \u2014 Baker never guesses which ending is the point of a Form.`,
|
|
30086
30092
|
...suggestion ? [
|
|
@@ -30091,7 +30097,7 @@ function flowConversionHints(slug, rows, noConversionReason) {
|
|
|
30091
30097
|
}
|
|
30092
30098
|
function flowCoverage(forms, definitions) {
|
|
30093
30099
|
return forms.map(({ slug, tree }) => {
|
|
30094
|
-
const rows = joinFlowConversions(
|
|
30100
|
+
const rows = joinFlowConversions(flowEventKeys(tree, slug), definitions);
|
|
30095
30101
|
const declared = tree?.noConversion?.reason;
|
|
30096
30102
|
const seen = /* @__PURE__ */ new Set();
|
|
30097
30103
|
return {
|
|
@@ -32109,7 +32115,7 @@ function readFlowConversions2(slug, definitions) {
|
|
|
32109
32115
|
return {
|
|
32110
32116
|
slug,
|
|
32111
32117
|
noConversionReason: reason,
|
|
32112
|
-
endings: joinFlowConversions(
|
|
32118
|
+
endings: joinFlowConversions(flowEventKeys(tree, slug), definitions)
|
|
32113
32119
|
};
|
|
32114
32120
|
}
|
|
32115
32121
|
function conversionsEnvelope(args4, definitions, candidates) {
|
|
@@ -47226,8 +47232,63 @@ Full guide: __tooling__/docs/tools/baker/flows.md`
|
|
|
47226
47232
|
});
|
|
47227
47233
|
|
|
47228
47234
|
// src/commands/funnel/index.ts
|
|
47235
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
47229
47236
|
import { defineCommand as defineCommand129 } from "citty";
|
|
47230
47237
|
|
|
47238
|
+
// src/commands/funnel/formMoments.ts
|
|
47239
|
+
var MAX_NAMED_FORMS = 8;
|
|
47240
|
+
function formMoments(events, forms) {
|
|
47241
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
47242
|
+
for (const form of forms) {
|
|
47243
|
+
const outcome = suggestedEnding(flowEventKeys(form.tree, form.slug));
|
|
47244
|
+
if (outcome !== null) outcomes.set(outcome.eventKey, { slug: form.slug, outcome });
|
|
47245
|
+
}
|
|
47246
|
+
const marked = events.map((event) => {
|
|
47247
|
+
const found = outcomes.get(event.eventKey);
|
|
47248
|
+
if (found === void 0) return event;
|
|
47249
|
+
outcomes.delete(event.eventKey);
|
|
47250
|
+
return { ...event, step: found.outcome.nodeName, outcome: true };
|
|
47251
|
+
});
|
|
47252
|
+
const unfired = [...outcomes.values()].map(
|
|
47253
|
+
({ slug, outcome }) => ({
|
|
47254
|
+
eventKey: outcome.eventKey,
|
|
47255
|
+
origin: "site",
|
|
47256
|
+
flowSlug: slug,
|
|
47257
|
+
...outcome.nodeType === null ? {} : { nodeType: outcome.nodeType },
|
|
47258
|
+
events: 0,
|
|
47259
|
+
sessions: 0,
|
|
47260
|
+
step: outcome.nodeName,
|
|
47261
|
+
seen: false,
|
|
47262
|
+
outcome: true
|
|
47263
|
+
})
|
|
47264
|
+
);
|
|
47265
|
+
return [...marked, ...unfired];
|
|
47266
|
+
}
|
|
47267
|
+
function withFormMoments(moments, forms) {
|
|
47268
|
+
return { ...moments, events: formMoments(moments.events, forms) };
|
|
47269
|
+
}
|
|
47270
|
+
function formsNamedHere(forms) {
|
|
47271
|
+
return forms.filter((form) => suggestedEnding(flowEventKeys(form.tree, form.slug)) !== null).map((form) => form.slug);
|
|
47272
|
+
}
|
|
47273
|
+
function unplacedFormHints(steps, forms) {
|
|
47274
|
+
const onAStep = new Set(
|
|
47275
|
+
steps.flatMap((step) => (step.triggers ?? []).flatMap((trigger) => trigger.eventKey ? [trigger.eventKey] : []))
|
|
47276
|
+
);
|
|
47277
|
+
const unplaced = [];
|
|
47278
|
+
for (const form of forms) {
|
|
47279
|
+
const endings = flowEventKeys(form.tree, form.slug);
|
|
47280
|
+
if (endings.some((ending) => onAStep.has(ending.eventKey))) continue;
|
|
47281
|
+
const outcome = suggestedEnding(endings);
|
|
47282
|
+
if (outcome !== null) unplaced.push(`${form.slug} \u2192 \`${outcome.eventKey}\` ("${outcome.nodeName}")`);
|
|
47283
|
+
}
|
|
47284
|
+
if (unplaced.length === 0) return [];
|
|
47285
|
+
const shown = unplaced.slice(0, MAX_NAMED_FORMS);
|
|
47286
|
+
const rest = unplaced.length - shown.length;
|
|
47287
|
+
return [
|
|
47288
|
+
`${unplaced.length} Form(s) in this workspace reach no funnel step, so they move nobody along it however many people finish them: ${shown.join("; ")}${rest > 0 ? `; and ${rest} more` : ""}. Place one with \`baker funnel --add-trigger <step> --event <key>\`, which adds that one way in and leaves the rest of the step alone (\`baker funnel\` with no flags lists the step keys). If you also count a Form as a page conversion, count THAT SAME key, or the two numbers disagree for ever. A Form's other endings \u2014 a booking widget mid-flow, an embed's own signals \u2014 are listed by \`baker analytics conversions --flow <slug>\`.`
|
|
47289
|
+
];
|
|
47290
|
+
}
|
|
47291
|
+
|
|
47231
47292
|
// ../api/src/funnel/vocabulary.ts
|
|
47232
47293
|
var FUNNEL_STEP_CLASSES = [
|
|
47233
47294
|
/** Saw the offer: a landing opened, an ad clicked through. */
|
|
@@ -47520,6 +47581,18 @@ var funnelRequestSchema = z44.object({
|
|
|
47520
47581
|
step: funnelStepSchema.optional(),
|
|
47521
47582
|
/** One step taken out by key; whatever came after it moves up to whatever came before it. */
|
|
47522
47583
|
removeStep: z44.string().min(1).max(64).optional(),
|
|
47584
|
+
/**
|
|
47585
|
+
* ONE way in added to a step, leaving the rest of that step alone.
|
|
47586
|
+
*
|
|
47587
|
+
* `step` takes a whole step, so adding a way in through it means restating
|
|
47588
|
+
* the step's name, stage, `after[]` and every trigger it already had — and a
|
|
47589
|
+
* trigger left out of that restatement is silently removed. This is the door
|
|
47590
|
+
* for the thing that is actually being asked ("this Form's booking should
|
|
47591
|
+
* count here"), and it cannot drop anything.
|
|
47592
|
+
*/
|
|
47593
|
+
addTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
|
|
47594
|
+
/** ONE way in taken off a step, by the event key it listens for. */
|
|
47595
|
+
removeTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
|
|
47523
47596
|
/** One of the ready-made graphs, instead of stating steps. */
|
|
47524
47597
|
preset: z44.enum(FUNNEL_VERTICALS).optional(),
|
|
47525
47598
|
/** Also return the suggestion library and the presets. */
|
|
@@ -47531,7 +47604,14 @@ var funnelRequestSchema = z44.object({
|
|
|
47531
47604
|
*/
|
|
47532
47605
|
moments: z44.boolean().optional(),
|
|
47533
47606
|
/** How many days the counts beside each step cover. Default 30. */
|
|
47534
|
-
days: z44.number().int().min(1).max(400).optional()
|
|
47607
|
+
days: z44.number().int().min(1).max(400).optional(),
|
|
47608
|
+
/**
|
|
47609
|
+
* The Forms, by slug, the caller names itself when no step counts them —
|
|
47610
|
+
* `baker funnel` reads its own workspace and names each with the key that
|
|
47611
|
+
* places it. The backend leaves them out of its own hint, so no Form is
|
|
47612
|
+
* named twice.
|
|
47613
|
+
*/
|
|
47614
|
+
namedByCaller: z44.array(z44.string().min(1).max(200)).max(1e3).optional()
|
|
47535
47615
|
});
|
|
47536
47616
|
var funnelResponseSchema = z44.object({
|
|
47537
47617
|
ok: z44.literal(true),
|
|
@@ -47597,7 +47677,30 @@ var funnelResponseSchema = z44.object({
|
|
|
47597
47677
|
events: z44.number(),
|
|
47598
47678
|
sessions: z44.number(),
|
|
47599
47679
|
/** The name it is counted under as a page conversion, when the company counts it. Informational. */
|
|
47600
|
-
countedAs: z44.string().optional()
|
|
47680
|
+
countedAs: z44.string().optional(),
|
|
47681
|
+
/**
|
|
47682
|
+
* The step of the Form this happens on, in the Form's own words —
|
|
47683
|
+
* never a node id. Present on a Form's ending read from the Form
|
|
47684
|
+
* itself, which is the only place the name is known.
|
|
47685
|
+
*/
|
|
47686
|
+
step: z44.string().optional(),
|
|
47687
|
+
/**
|
|
47688
|
+
* `true` on the ONE ending of a Form that is its outcome — the
|
|
47689
|
+
* booking on a Form that books, the submit on a Form that submits.
|
|
47690
|
+
* A Form raises several events and only one of them is the point;
|
|
47691
|
+
* without this the agent is handed five equal-looking keys and the
|
|
47692
|
+
* lifecycle noise a widget raises about itself sits among them.
|
|
47693
|
+
* Same rule as `baker analytics conversions` recommends, so the
|
|
47694
|
+
* conversion and the funnel step land on the same event.
|
|
47695
|
+
*/
|
|
47696
|
+
outcome: z44.boolean().optional(),
|
|
47697
|
+
/**
|
|
47698
|
+
* `false` on an ending a Form raises natively but nobody has fired
|
|
47699
|
+
* yet — derived from the Form, offered at zero, exactly as a
|
|
47700
|
+
* Source is offered before its first send. Absent means it came
|
|
47701
|
+
* from the event store and therefore happened.
|
|
47702
|
+
*/
|
|
47703
|
+
seen: z44.boolean().optional()
|
|
47601
47704
|
})
|
|
47602
47705
|
),
|
|
47603
47706
|
/** The company's sources: a system of theirs that posts events to Baker, by the event name it raises. */
|
|
@@ -47653,9 +47756,15 @@ function parseFunnelArgs(raw) {
|
|
|
47653
47756
|
const step = parseStep(raw.step);
|
|
47654
47757
|
const removeStep = parseRemoveStep(raw["remove-step"] ?? raw.removeStep);
|
|
47655
47758
|
const preset = parsePreset(raw.preset);
|
|
47656
|
-
const
|
|
47759
|
+
const addTrigger = parseWayIn(raw["add-trigger"] ?? raw.addTrigger, raw.event, "--add-trigger");
|
|
47760
|
+
const removeTrigger = parseWayIn(raw["remove-trigger"] ?? raw.removeTrigger, raw.event, "--remove-trigger");
|
|
47761
|
+
const doors = [steps, step, removeStep, preset, addTrigger, removeTrigger].filter(
|
|
47762
|
+
(door) => door !== void 0
|
|
47763
|
+
).length;
|
|
47657
47764
|
if (doors > 1) {
|
|
47658
|
-
throw new FunnelArgsError(
|
|
47765
|
+
throw new FunnelArgsError(
|
|
47766
|
+
"Give one of --steps, --step, --remove-step, --preset, --add-trigger or --remove-trigger, not two."
|
|
47767
|
+
);
|
|
47659
47768
|
}
|
|
47660
47769
|
const days = raw.days === void 0 ? void 0 : Number(raw.days);
|
|
47661
47770
|
if (days !== void 0 && (!Number.isInteger(days) || days <= 0 || days > 400)) {
|
|
@@ -47666,6 +47775,8 @@ function parseFunnelArgs(raw) {
|
|
|
47666
47775
|
...step === void 0 ? {} : { step },
|
|
47667
47776
|
...removeStep === void 0 ? {} : { removeStep },
|
|
47668
47777
|
...preset === void 0 ? {} : { preset },
|
|
47778
|
+
...addTrigger === void 0 ? {} : { addTrigger },
|
|
47779
|
+
...removeTrigger === void 0 ? {} : { removeTrigger },
|
|
47669
47780
|
templates: raw.templates === true,
|
|
47670
47781
|
moments: raw.moments === true,
|
|
47671
47782
|
...days === void 0 ? {} : { days }
|
|
@@ -47677,6 +47788,8 @@ function funnelWrite(args4) {
|
|
|
47677
47788
|
...args4.step === void 0 ? {} : { step: args4.step },
|
|
47678
47789
|
...args4.removeStep === void 0 ? {} : { removeStep: args4.removeStep },
|
|
47679
47790
|
...args4.preset === void 0 ? {} : { preset: args4.preset },
|
|
47791
|
+
...args4.addTrigger === void 0 ? {} : { addTrigger: args4.addTrigger },
|
|
47792
|
+
...args4.removeTrigger === void 0 ? {} : { removeTrigger: args4.removeTrigger },
|
|
47680
47793
|
...args4.templates ? { templates: true } : {},
|
|
47681
47794
|
...args4.moments ? { moments: true } : {},
|
|
47682
47795
|
...args4.days === void 0 ? {} : { days: args4.days }
|
|
@@ -47786,6 +47899,26 @@ function parsePreset(raw) {
|
|
|
47786
47899
|
}
|
|
47787
47900
|
return raw;
|
|
47788
47901
|
}
|
|
47902
|
+
function parseWayIn(stepKey, eventKey, flag) {
|
|
47903
|
+
if (stepKey === void 0) return void 0;
|
|
47904
|
+
if (typeof stepKey !== "string" || stepKey.trim() === "") {
|
|
47905
|
+
throw new FunnelArgsError(`${flag} takes the key of the step to change: \`${flag} lead\`.`);
|
|
47906
|
+
}
|
|
47907
|
+
if (typeof eventKey !== "string" || eventKey.trim() === "") {
|
|
47908
|
+
throw new FunnelArgsError(`${flag} needs --event, naming what gets somebody there.`, {
|
|
47909
|
+
action: "read_the_moments",
|
|
47910
|
+
explanation: `Run \`baker funnel --moments\` and copy an \`events[].eventKey\` \u2014 a Form's own outcome is listed there at zero before anybody has filled it in. Then: \`${flag} ${stepKey.trim()} --event "<key>"\`.`
|
|
47911
|
+
});
|
|
47912
|
+
}
|
|
47913
|
+
const key = eventKey.trim();
|
|
47914
|
+
if (flag === "--add-trigger" && parseEventKey(key) === null) {
|
|
47915
|
+
throw new FunnelArgsError(`"${key}" is not an event key, so nothing would ever reach that step through it.`, {
|
|
47916
|
+
action: "read_the_moments",
|
|
47917
|
+
explanation: "Run `baker funnel --moments` and copy an `events[].eventKey`. They look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or view:<landing>."
|
|
47918
|
+
});
|
|
47919
|
+
}
|
|
47920
|
+
return { stepKey: stepKey.trim(), eventKey: key };
|
|
47921
|
+
}
|
|
47789
47922
|
|
|
47790
47923
|
// src/commands/funnel/index.ts
|
|
47791
47924
|
var args3 = {
|
|
@@ -47799,6 +47932,21 @@ var args3 = {
|
|
|
47799
47932
|
description: 'ONE step as a JSON object, in the same shape as an entry of --steps. It is added to the funnel as it now stands, or replaces the step with the same `key`, and every other step is left exactly as it is \u2014 the door for adding or changing a stage without restating the rest. Example: --step \'{"key":"demo","displayName":"Demo held","stepClass":"engaged","after":["lead"],"triggers":[{"id":"t1","kind":"event","eventKey":"page:demo_held"}]}\'',
|
|
47800
47933
|
required: false
|
|
47801
47934
|
},
|
|
47935
|
+
"add-trigger": {
|
|
47936
|
+
type: "string",
|
|
47937
|
+
description: 'The key of ONE step to add a way in to, with `--event` naming what gets somebody there: `--add-trigger lead --event "submit:contact"`. Start here for "this Form should count as a lead" \u2014 it adds that one way in and leaves the step\'s name, stage, `after[]` and every other trigger exactly as they are. Prefer it to `--step`, which takes a WHOLE step and therefore silently removes any trigger the restatement forgets. Adding a key the step already listens for changes nothing. Take the key from `--moments`, never from memory',
|
|
47938
|
+
required: false
|
|
47939
|
+
},
|
|
47940
|
+
"remove-trigger": {
|
|
47941
|
+
type: "string",
|
|
47942
|
+
description: 'The key of ONE step to take a way in OFF, with `--event` naming which: `--remove-trigger lead --event "submit:contact"`. By event key rather than by trigger id, because the id is not something you ever saw. Everything else about the step is untouched',
|
|
47943
|
+
required: false
|
|
47944
|
+
},
|
|
47945
|
+
event: {
|
|
47946
|
+
type: "string",
|
|
47947
|
+
description: "The event key `--add-trigger` or `--remove-trigger` acts on \u2014 an `events[].eventKey` from `--moments`, never invented. A Form's own outcome is listed there at zero, marked `outcome: true`, before anybody has filled the Form in",
|
|
47948
|
+
required: false
|
|
47949
|
+
},
|
|
47802
47950
|
"remove-step": {
|
|
47803
47951
|
type: "string",
|
|
47804
47952
|
description: "The key of one step to take off the funnel. Whatever came after it moves up to whatever came before it, so the chain stays joined. The response lists it under `removed`",
|
|
@@ -47811,7 +47959,7 @@ var args3 = {
|
|
|
47811
47959
|
},
|
|
47812
47960
|
moments: {
|
|
47813
47961
|
type: "boolean",
|
|
47814
|
-
description: "Also return everything a trigger can name, so a trigger is PICKED rather than invented: `events` (every event this company's Forms, pages and systems
|
|
47962
|
+
description: "Also return everything a trigger can name, so a trigger is PICKED rather than invented: `events` (every event this company's Forms, pages and systems produced in the window, with volume, the Form it belongs to, `origin` \u2014 `site` for a page or Form, `systems` for the client's own server, `both` \u2014 and `countedAs` when the company also counts it as a page conversion, which a trigger does not need; with **each Form's OUTCOME in this workspace marked** `outcome: true` and the `step` it happens on \u2014 a Form nobody has filled in yet gets that row at zero, `seen: false`, so a Form built minutes ago is pickable with nothing set up first. It is the booking on a Form that books, the submit on a Form that submits, and it is the same ending `baker analytics conversions` recommends counting, so the step and the conversion land on the same event. One marked row and not all seven a Form raises: the rest are rarely what a step wants, and `baker analytics conversions --flow <slug>` lists every ending of one Form, fired or not), `sources` (the client's own systems that post events to Baker, by the `page:<name>` key each raises, pickable before its first send arrives) and `landings` (its pages, by the id a `landing` trigger takes). Use this before writing any `triggers`: a key typed from memory saves, publishes and counts nobody forever. A Form's events need NOTHING set up first \u2014 no Source, no conversion: a Baker page raises them by itself",
|
|
47815
47963
|
required: false
|
|
47816
47964
|
},
|
|
47817
47965
|
days: {
|
|
@@ -47833,17 +47981,25 @@ registerSchema({
|
|
|
47833
47981
|
var funnelCommand2 = defineCommand129({
|
|
47834
47982
|
meta: {
|
|
47835
47983
|
name: "funnel",
|
|
47836
|
-
description: 'Start here: run with no flags to see the funnel this company has, with the last 30 days of PEOPLE counted beside every step \u2014 `people` reached it, `atLeast` got at least that far, `before` got as far as the step before, `share` is the ratio, and `worstStep` names where the most people are lost. Every company has one from the day it exists: Visited (any landing) \u2192 Became a lead \u2192 Became a customer, the last two with no way in until somebody gives them one. Then `--moments` for everything a trigger can name (every event the company\'s Forms, pages and systems
|
|
47984
|
+
description: 'Start here: run with no flags to see the funnel this company has, with the last 30 days of PEOPLE counted beside every step \u2014 `people` reached it, `atLeast` got at least that far, `before` got as far as the step before, `share` is the ratio, and `worstStep` names where the most people are lost. Every company has one from the day it exists: Visited (any landing) \u2192 Became a lead \u2192 Became a customer, the last two with no way in until somebody gives them one. Then `--moments` for everything a trigger can name (every event the company\'s Forms, pages and systems produced, its sources, its landings, plus each Form\'s own outcome at zero). **To make an event count on a step \u2014 the usual job \u2014 use `--add-trigger <step> --event <key>`:** it adds that one way in and touches nothing else about the step. `--step \'<json>\'` is for adding or renaming a STEP; it replaces the step with that key, so it means restating its `after` and every trigger it keeps, and one left out is gone. A step is reached by three kinds of thing and nothing else: an event by its own key (`{"id":"t1","kind":"event","eventKey":"<from moments.events or moments.sources>"}`), one landing being viewed (`{"id":"t2","kind":"landing","landingId":"<from moments.landings>"}`) or any landing (`{"id":"t3","kind":"any_landing"}`). Nothing has to be named first. Pick every trigger from `--moments`, never from memory. A step with no `triggers` advances NOBODY and reports zero forever without warning \u2014 that is what the hints keep pointing at. There is no default door: a Form is only counted where a trigger names it. Keep it true: a chat that adds a Form, a Source or an event that moves people forward places it on its step in the same chat. Each step comes back with `sends[]`, read-only: where the people who reach it are sent and what happened, read off the system events behind its ways in. A send is set on the event with `baker analytics sends`, never on a step, and once published it spends ad budget \u2014 a failed one is named in the hints. Everything here is STAGED: it becomes the company\'s funnel in one piece when the chat is published \u2014 the client sees it on the canvas in this chat and can edit it there too \u2014 and the previous version stays on file so what was already measured under it is not lost.'
|
|
47837
47985
|
},
|
|
47838
47986
|
args: args3,
|
|
47839
47987
|
run: async ({ args: raw }) => {
|
|
47840
47988
|
try {
|
|
47841
47989
|
const parsed = parseFunnelArgs(raw);
|
|
47842
|
-
const
|
|
47990
|
+
const forms = workspaceForms();
|
|
47991
|
+
const namedByCaller = formsNamedHere(forms);
|
|
47992
|
+
const request = {
|
|
47993
|
+
...funnelWrite(parsed),
|
|
47994
|
+
...namedByCaller.length === 0 ? {} : { namedByCaller }
|
|
47995
|
+
};
|
|
47996
|
+
const response = await apiPost("/api/funnel", request);
|
|
47997
|
+
const data = response.data.moments === void 0 ? response.data : { ...response.data, moments: withFormMoments(response.data.moments, forms) };
|
|
47998
|
+
const hints2 = [...response.hints ?? [], ...unplacedFormHints(response.data.steps ?? [], forms)];
|
|
47843
47999
|
writeJsonEnvelope({
|
|
47844
48000
|
ok: true,
|
|
47845
|
-
data
|
|
47846
|
-
...
|
|
48001
|
+
data,
|
|
48002
|
+
...hints2.length > 0 ? { hints: hints2 } : {}
|
|
47847
48003
|
});
|
|
47848
48004
|
} catch (err) {
|
|
47849
48005
|
handleError4(err);
|
|
@@ -47868,6 +48024,16 @@ function handleError4(err) {
|
|
|
47868
48024
|
});
|
|
47869
48025
|
process.exit(1);
|
|
47870
48026
|
}
|
|
48027
|
+
function workspaceForms() {
|
|
48028
|
+
const forms = [];
|
|
48029
|
+
for (const slug of listFlowSlugs()) {
|
|
48030
|
+
try {
|
|
48031
|
+
forms.push({ slug, tree: JSON.parse(readFileSync15(flowDataPath(slug), "utf-8")) });
|
|
48032
|
+
} catch {
|
|
48033
|
+
}
|
|
48034
|
+
}
|
|
48035
|
+
return forms;
|
|
48036
|
+
}
|
|
47871
48037
|
|
|
47872
48038
|
// src/commands/ga4/index.ts
|
|
47873
48039
|
import { defineCommand as defineCommand136 } from "citty";
|
|
@@ -47997,7 +48163,7 @@ Examples:
|
|
|
47997
48163
|
import { defineCommand as defineCommand131 } from "citty";
|
|
47998
48164
|
|
|
47999
48165
|
// src/commands/ga4/shared.ts
|
|
48000
|
-
import { readFileSync as
|
|
48166
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
48001
48167
|
function failValidation3(message) {
|
|
48002
48168
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
48003
48169
|
process.exit(1);
|
|
@@ -48013,7 +48179,7 @@ function requireTarget5(args4, entity) {
|
|
|
48013
48179
|
function readJsonSource(args4, flag) {
|
|
48014
48180
|
const inline = args4[flag];
|
|
48015
48181
|
const file = args4.file;
|
|
48016
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
48182
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync16(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
48017
48183
|
if (raw === void 0) {
|
|
48018
48184
|
failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
48019
48185
|
}
|
|
@@ -48364,7 +48530,7 @@ Examples:
|
|
|
48364
48530
|
});
|
|
48365
48531
|
|
|
48366
48532
|
// src/commands/ga4/query.ts
|
|
48367
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as
|
|
48533
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
|
|
48368
48534
|
import { resolve as resolve2 } from "path";
|
|
48369
48535
|
import { defineCommand as defineCommand134 } from "citty";
|
|
48370
48536
|
|
|
@@ -48455,7 +48621,7 @@ function writeRowsToFile2(filePath, rows, append) {
|
|
|
48455
48621
|
writeFileSync4(filePath, content, "utf-8");
|
|
48456
48622
|
}
|
|
48457
48623
|
} else if (append && existsSync7(filePath)) {
|
|
48458
|
-
const existing = JSON.parse(
|
|
48624
|
+
const existing = JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
48459
48625
|
writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
48460
48626
|
} else {
|
|
48461
48627
|
writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -49307,7 +49473,7 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
|
|
|
49307
49473
|
import { defineCommand as defineCommand140 } from "citty";
|
|
49308
49474
|
|
|
49309
49475
|
// src/commands/gsc/query.ts
|
|
49310
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as
|
|
49476
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
49311
49477
|
import { resolve as resolve3 } from "path";
|
|
49312
49478
|
import { defineCommand as defineCommand137 } from "citty";
|
|
49313
49479
|
|
|
@@ -49455,7 +49621,7 @@ function writeRowsToFile3(filePath, rows, append) {
|
|
|
49455
49621
|
writeFileSync5(filePath, content, "utf-8");
|
|
49456
49622
|
}
|
|
49457
49623
|
} else if (append && existsSync8(filePath)) {
|
|
49458
|
-
const existing = JSON.parse(
|
|
49624
|
+
const existing = JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
49459
49625
|
writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
49460
49626
|
} else {
|
|
49461
49627
|
writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -56333,7 +56499,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
56333
56499
|
}
|
|
56334
56500
|
return false;
|
|
56335
56501
|
};
|
|
56336
|
-
const
|
|
56502
|
+
const walk = (rules, sink) => {
|
|
56337
56503
|
for (const rule of Array.from(rules)) {
|
|
56338
56504
|
totalRules++;
|
|
56339
56505
|
if (rule instanceof CSSStyleRule) {
|
|
@@ -56354,7 +56520,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
56354
56520
|
const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
|
|
56355
56521
|
if (grouping) {
|
|
56356
56522
|
const inner = [];
|
|
56357
|
-
|
|
56523
|
+
walk(rule.cssRules, inner);
|
|
56358
56524
|
if (inner.length === 0) continue;
|
|
56359
56525
|
const condition = rule.conditionText ?? "";
|
|
56360
56526
|
const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
|
|
@@ -56368,7 +56534,7 @@ ${inner.join("\n")}
|
|
|
56368
56534
|
const owner = sheet.ownerNode;
|
|
56369
56535
|
if (owner?.hasAttribute?.("data-baker-freeze")) continue;
|
|
56370
56536
|
try {
|
|
56371
|
-
|
|
56537
|
+
walk(sheet.cssRules, kept);
|
|
56372
56538
|
} catch {
|
|
56373
56539
|
if (sheet.href) unreadableHrefs.push(sheet.href);
|
|
56374
56540
|
}
|
|
@@ -56819,7 +56985,7 @@ var inPageCollectMotion = (selector) => {
|
|
|
56819
56985
|
else if (infinite) loop.push(effect);
|
|
56820
56986
|
else entrance.push(effect);
|
|
56821
56987
|
};
|
|
56822
|
-
const
|
|
56988
|
+
const walk = (rules, insideScrollTimeline) => {
|
|
56823
56989
|
for (const rule of Array.from(rules)) {
|
|
56824
56990
|
if (rule instanceof CSSKeyframesRule) {
|
|
56825
56991
|
keyframesByName.set(rule.name, rule.cssText);
|
|
@@ -56837,17 +57003,17 @@ var inPageCollectMotion = (selector) => {
|
|
|
56837
57003
|
respectsReducedMotion = true;
|
|
56838
57004
|
continue;
|
|
56839
57005
|
}
|
|
56840
|
-
|
|
57006
|
+
walk(rule.cssRules, insideScrollTimeline);
|
|
56841
57007
|
continue;
|
|
56842
57008
|
}
|
|
56843
57009
|
const grouping = rule;
|
|
56844
|
-
if (grouping.cssRules)
|
|
57010
|
+
if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
|
|
56845
57011
|
}
|
|
56846
57012
|
};
|
|
56847
57013
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
56848
57014
|
if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
|
|
56849
57015
|
try {
|
|
56850
|
-
|
|
57016
|
+
walk(sheet.cssRules, false);
|
|
56851
57017
|
} catch {
|
|
56852
57018
|
}
|
|
56853
57019
|
}
|
|
@@ -62686,7 +62852,7 @@ import { defineCommand as defineCommand217 } from "citty";
|
|
|
62686
62852
|
import { defineCommand as defineCommand214 } from "citty";
|
|
62687
62853
|
|
|
62688
62854
|
// src/commands/tag-manager/shared.ts
|
|
62689
|
-
import { readFileSync as
|
|
62855
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
62690
62856
|
function failValidation5(message) {
|
|
62691
62857
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
62692
62858
|
process.exit(1);
|
|
@@ -62702,7 +62868,7 @@ function requireTarget6(args4, entity) {
|
|
|
62702
62868
|
function loadJsonArg2(args4, flag = "json") {
|
|
62703
62869
|
const inline = args4[flag];
|
|
62704
62870
|
const file = args4.file;
|
|
62705
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
62871
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync19(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
62706
62872
|
if (raw === void 0) {
|
|
62707
62873
|
failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
62708
62874
|
}
|
|
@@ -65981,7 +66147,7 @@ function refuseUnknownFlags(root, argv) {
|
|
|
65981
66147
|
}
|
|
65982
66148
|
|
|
65983
66149
|
// src/version.ts
|
|
65984
|
-
import { readFileSync as
|
|
66150
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
65985
66151
|
function packageJsonUrl() {
|
|
65986
66152
|
return new URL("../package.json", import.meta.url);
|
|
65987
66153
|
}
|
|
@@ -65993,7 +66159,7 @@ function parsePackageVersion(raw) {
|
|
|
65993
66159
|
throw new Error("Invalid CLI package.json: missing version");
|
|
65994
66160
|
}
|
|
65995
66161
|
function getCliVersion() {
|
|
65996
|
-
return parsePackageVersion(
|
|
66162
|
+
return parsePackageVersion(readFileSync20(packageJsonUrl(), "utf8"));
|
|
65997
66163
|
}
|
|
65998
66164
|
|
|
65999
66165
|
// src/cli.ts
|