@koda-sl/baker-cli 0.309.1 → 0.311.0-dev.64b243bc4
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-ZBC4NFST.js} +4 -4
- package/dist/chunk-ZBC4NFST.js.map +1 -0
- package/dist/cli.js +231 -89
- 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-ZBC4NFST.js";
|
|
71
71
|
import {
|
|
72
72
|
csvOrJson,
|
|
73
73
|
daysAgoIso,
|
|
@@ -10684,14 +10684,14 @@ function collectionLine(collection) {
|
|
|
10684
10684
|
}
|
|
10685
10685
|
function accountsInTree(nodes) {
|
|
10686
10686
|
const accounts = /* @__PURE__ */ new Set();
|
|
10687
|
-
const
|
|
10687
|
+
const walk = (node) => {
|
|
10688
10688
|
accounts.add(node.customerId);
|
|
10689
10689
|
for (const child of node.children) {
|
|
10690
|
-
|
|
10690
|
+
walk(child);
|
|
10691
10691
|
}
|
|
10692
10692
|
};
|
|
10693
10693
|
for (const node of nodes) {
|
|
10694
|
-
|
|
10694
|
+
walk(node);
|
|
10695
10695
|
}
|
|
10696
10696
|
return accounts;
|
|
10697
10697
|
}
|
|
@@ -13654,7 +13654,7 @@ var SEND_PLATFORM_SPECS = {
|
|
|
13654
13654
|
},
|
|
13655
13655
|
googleAds: {
|
|
13656
13656
|
title: "Google Ads",
|
|
13657
|
-
eventMeans: "
|
|
13657
|
+
eventMeans: "The id of one of the account's conversion actions \u2014 copy it from `conversionActions`, never type one",
|
|
13658
13658
|
eventKind: "id",
|
|
13659
13659
|
credentialFields: ["oauthProviderId"],
|
|
13660
13660
|
setupHint: "Connect the Google Ads conversion tag to a Google Ads account in Tracking"
|
|
@@ -13856,12 +13856,9 @@ var sendsResponseSchema = z28.object({
|
|
|
13856
13856
|
* The conversions the platform already holds, by the id a send's `event`
|
|
13857
13857
|
* takes and the name the person gave them. Google Ads only: its `event`
|
|
13858
13858
|
* is an id Google issued, and an id the account does not hold is
|
|
13859
|
-
* accepted and files against nothing.
|
|
13860
|
-
* the account's tag setup shows (`AW-…/<label>`) — how the person knows it.
|
|
13859
|
+
* accepted and files against nothing.
|
|
13861
13860
|
*/
|
|
13862
|
-
conversionActions: z28.array(
|
|
13863
|
-
z28.object({ id: z28.string(), name: z28.string(), primaryForGoal: z28.boolean(), label: z28.string().optional() })
|
|
13864
|
-
).optional()
|
|
13861
|
+
conversionActions: z28.array(z28.object({ id: z28.string(), name: z28.string(), primaryForGoal: z28.boolean() })).optional()
|
|
13865
13862
|
})
|
|
13866
13863
|
),
|
|
13867
13864
|
/** Every field a send forwards. Nothing to map. */
|
|
@@ -21318,14 +21315,14 @@ function collectionLine2(collection) {
|
|
|
21318
21315
|
}
|
|
21319
21316
|
function accountsInTree2(nodes) {
|
|
21320
21317
|
const accounts = /* @__PURE__ */ new Set();
|
|
21321
|
-
const
|
|
21318
|
+
const walk = (node) => {
|
|
21322
21319
|
accounts.add(node.accountId);
|
|
21323
21320
|
for (const child of node.children) {
|
|
21324
|
-
|
|
21321
|
+
walk(child);
|
|
21325
21322
|
}
|
|
21326
21323
|
};
|
|
21327
21324
|
for (const node of nodes) {
|
|
21328
|
-
|
|
21325
|
+
walk(node);
|
|
21329
21326
|
}
|
|
21330
21327
|
return accounts;
|
|
21331
21328
|
}
|
|
@@ -26635,14 +26632,14 @@ function collectionLine3(collection) {
|
|
|
26635
26632
|
}
|
|
26636
26633
|
function accountsInTree3(nodes) {
|
|
26637
26634
|
const accounts = /* @__PURE__ */ new Set();
|
|
26638
|
-
const
|
|
26635
|
+
const walk = (node) => {
|
|
26639
26636
|
accounts.add(node.accountId);
|
|
26640
26637
|
for (const child of node.children) {
|
|
26641
|
-
|
|
26638
|
+
walk(child);
|
|
26642
26639
|
}
|
|
26643
26640
|
};
|
|
26644
26641
|
for (const node of nodes) {
|
|
26645
|
-
|
|
26642
|
+
walk(node);
|
|
26646
26643
|
}
|
|
26647
26644
|
return accounts;
|
|
26648
26645
|
}
|
|
@@ -28460,6 +28457,55 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
|
|
|
28460
28457
|
// src/commands/analytics/index.ts
|
|
28461
28458
|
import { readFileSync as readFileSync12 } from "fs";
|
|
28462
28459
|
|
|
28460
|
+
// ../api/src/analytics/flowEventKeys.ts
|
|
28461
|
+
function flowNodes(node, acc = []) {
|
|
28462
|
+
if (!node || typeof node !== "object") return acc;
|
|
28463
|
+
const record = node;
|
|
28464
|
+
acc.push(record);
|
|
28465
|
+
if (Array.isArray(record.children)) for (const child of record.children) flowNodes(child, acc);
|
|
28466
|
+
return acc;
|
|
28467
|
+
}
|
|
28468
|
+
function flowEventKeys(tree, slug) {
|
|
28469
|
+
const primary = [];
|
|
28470
|
+
const secondary = [];
|
|
28471
|
+
const nodes = flowNodes(tree).filter((node) => typeof node.id === "string" && node.id !== "");
|
|
28472
|
+
if (nodes.length === 0) return [];
|
|
28473
|
+
for (const node of nodes) {
|
|
28474
|
+
const id = node.id;
|
|
28475
|
+
const triggers = flowNodeTriggers(node.nodeData);
|
|
28476
|
+
for (const triggerId of triggers.available) {
|
|
28477
|
+
const candidate = {
|
|
28478
|
+
eventKey: formEventKey(slug, id, triggerId),
|
|
28479
|
+
nodeId: id,
|
|
28480
|
+
nodeName: typeof node.name === "string" ? node.name : id,
|
|
28481
|
+
nodeType: triggers.type,
|
|
28482
|
+
triggerId,
|
|
28483
|
+
isCompletion: triggers.completion === triggerId
|
|
28484
|
+
};
|
|
28485
|
+
(candidate.isCompletion ? primary : secondary).push(candidate);
|
|
28486
|
+
}
|
|
28487
|
+
}
|
|
28488
|
+
return [
|
|
28489
|
+
...primary,
|
|
28490
|
+
...secondary,
|
|
28491
|
+
{
|
|
28492
|
+
eventKey: `submit:${slug}`,
|
|
28493
|
+
nodeId: null,
|
|
28494
|
+
nodeName: "the Form ran out of steps",
|
|
28495
|
+
nodeType: null,
|
|
28496
|
+
triggerId: null,
|
|
28497
|
+
// A Form reaching its last node is as primary as an outcome gets for a
|
|
28498
|
+
// Form with no widget in it — but it never fires on one that ends in a
|
|
28499
|
+
// booking widget or a redirect, so it does not lead the list.
|
|
28500
|
+
isCompletion: false
|
|
28501
|
+
}
|
|
28502
|
+
];
|
|
28503
|
+
}
|
|
28504
|
+
function suggestedEnding(endings) {
|
|
28505
|
+
const deepestFirst = [...endings].reverse();
|
|
28506
|
+
return deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? endings[endings.length - 1] ?? null;
|
|
28507
|
+
}
|
|
28508
|
+
|
|
28463
28509
|
// ../api/src/event-ingest/arrivals.ts
|
|
28464
28510
|
import { z as z33 } from "zod";
|
|
28465
28511
|
|
|
@@ -29639,56 +29685,14 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
29639
29685
|
}
|
|
29640
29686
|
|
|
29641
29687
|
// src/commands/analytics/flowConversionKeys.ts
|
|
29642
|
-
function walk(node, acc = []) {
|
|
29643
|
-
if (!node || typeof node !== "object") return acc;
|
|
29644
|
-
const record = node;
|
|
29645
|
-
acc.push(record);
|
|
29646
|
-
if (Array.isArray(record.children)) for (const child of record.children) walk(child, acc);
|
|
29647
|
-
return acc;
|
|
29648
|
-
}
|
|
29649
|
-
function flowConversionCandidates(tree, slug) {
|
|
29650
|
-
const primary = [];
|
|
29651
|
-
const secondary = [];
|
|
29652
|
-
for (const node of walk(tree)) {
|
|
29653
|
-
const id = typeof node.id === "string" ? node.id : null;
|
|
29654
|
-
if (!id) continue;
|
|
29655
|
-
const triggers = flowNodeTriggers(node.nodeData);
|
|
29656
|
-
for (const triggerId of triggers.available) {
|
|
29657
|
-
const candidate = {
|
|
29658
|
-
eventKey: formEventKey(slug, id, triggerId),
|
|
29659
|
-
nodeId: id,
|
|
29660
|
-
nodeName: typeof node.name === "string" ? node.name : id,
|
|
29661
|
-
nodeType: triggers.type,
|
|
29662
|
-
triggerId,
|
|
29663
|
-
isCompletion: triggers.completion === triggerId
|
|
29664
|
-
};
|
|
29665
|
-
(candidate.isCompletion ? primary : secondary).push(candidate);
|
|
29666
|
-
}
|
|
29667
|
-
}
|
|
29668
|
-
return [
|
|
29669
|
-
...primary,
|
|
29670
|
-
...secondary,
|
|
29671
|
-
{
|
|
29672
|
-
eventKey: `submit:${slug}`,
|
|
29673
|
-
nodeId: null,
|
|
29674
|
-
nodeName: "the Form ran out of steps",
|
|
29675
|
-
nodeType: null,
|
|
29676
|
-
triggerId: null,
|
|
29677
|
-
// A Form reaching its last node is as primary as an outcome gets for a
|
|
29678
|
-
// Form with no widget in it — but it never fires on one that ends in a
|
|
29679
|
-
// booking widget or a redirect, so it does not lead the list.
|
|
29680
|
-
isCompletion: false
|
|
29681
|
-
}
|
|
29682
|
-
];
|
|
29683
|
-
}
|
|
29684
29688
|
function formKeyRefusal(eventKey, tree, slug) {
|
|
29685
29689
|
const parsed = parseEventKey(eventKey);
|
|
29686
29690
|
if (!parsed || parsed.family !== "form" || parsed.flowSlug !== slug) return null;
|
|
29687
|
-
const node =
|
|
29691
|
+
const node = flowNodes(tree).find((candidate) => candidate.id === parsed.nodeId);
|
|
29688
29692
|
if (!node) {
|
|
29689
29693
|
return {
|
|
29690
29694
|
message: `"${slug}" has no step with id "${parsed.nodeId}", so this key would be stored and count zero forever.`,
|
|
29691
|
-
fix: `Steps in this Form: ${
|
|
29695
|
+
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.`
|
|
29692
29696
|
};
|
|
29693
29697
|
}
|
|
29694
29698
|
const triggers = flowNodeTriggers(node.nodeData);
|
|
@@ -29719,8 +29723,7 @@ function flowConversionHints(slug, rows, noConversionReason) {
|
|
|
29719
29723
|
`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.`
|
|
29720
29724
|
];
|
|
29721
29725
|
}
|
|
29722
|
-
const
|
|
29723
|
-
const suggestion = deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? rows[rows.length - 1];
|
|
29726
|
+
const suggestion = suggestedEnding(rows);
|
|
29724
29727
|
return [
|
|
29725
29728
|
`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.`,
|
|
29726
29729
|
...suggestion ? [
|
|
@@ -29731,7 +29734,7 @@ function flowConversionHints(slug, rows, noConversionReason) {
|
|
|
29731
29734
|
}
|
|
29732
29735
|
function flowCoverage(forms, definitions) {
|
|
29733
29736
|
return forms.map(({ slug, tree }) => {
|
|
29734
|
-
const rows = joinFlowConversions(
|
|
29737
|
+
const rows = joinFlowConversions(flowEventKeys(tree, slug), definitions);
|
|
29735
29738
|
const declared = tree?.noConversion?.reason;
|
|
29736
29739
|
const seen = /* @__PURE__ */ new Set();
|
|
29737
29740
|
return {
|
|
@@ -30351,7 +30354,7 @@ var args = {
|
|
|
30351
30354
|
},
|
|
30352
30355
|
event: {
|
|
30353
30356
|
type: "string",
|
|
30354
|
-
description: 'What the platform calls it \u2014 `platforms[].eventMeans` says what each one takes: a Meta or Google Analytics event name (Purchase, Lead, generate_lead), a Google Ads conversion action id copied from `platforms[].conversionActions
|
|
30357
|
+
description: 'What the platform calls it \u2014 `platforms[].eventMeans` says what each one takes: a Meta or Google Analytics event name (Purchase, Lead, generate_lead), a Google Ads conversion action id copied from `platforms[].conversionActions` (never typed: an id the account does not hold is refused), a LinkedIn conversion rule id, an X event tag id. On Meta and Google Analytics it may be WRITTEN FROM THE EVENT with the same {{path}} mentions --parameter takes: --event {{properties.plan}} sends each event under the plan it carries, --event "Purchase {{properties.tier}}" mixes text and a field. An event carrying nothing there is not uploaded. Google Ads, LinkedIn and X take an id THEY issued, so a template is refused there \u2014 pass what varies as a --parameter instead. For a GOOGLE ADS id, list the account\'s conversion actions first and pick by name \u2014 `baker ads google query "SELECT conversion_action.id, conversion_action.name, conversion_action.primary_for_goal FROM conversion_action WHERE conversion_action.status = \'ENABLED\'"` \u2014 never guess: Google accepts an upload against an id the account does not hold and files it against nothing',
|
|
30355
30358
|
required: false
|
|
30356
30359
|
},
|
|
30357
30360
|
account: {
|
|
@@ -31749,7 +31752,7 @@ function readFlowConversions2(slug, definitions) {
|
|
|
31749
31752
|
return {
|
|
31750
31753
|
slug,
|
|
31751
31754
|
noConversionReason: reason,
|
|
31752
|
-
endings: joinFlowConversions(
|
|
31755
|
+
endings: joinFlowConversions(flowEventKeys(tree, slug), definitions)
|
|
31753
31756
|
};
|
|
31754
31757
|
}
|
|
31755
31758
|
function conversionsEnvelope(args4, definitions, candidates) {
|
|
@@ -46853,8 +46856,54 @@ Full guide: __tooling__/docs/tools/baker/flows.md`
|
|
|
46853
46856
|
});
|
|
46854
46857
|
|
|
46855
46858
|
// src/commands/funnel/index.ts
|
|
46859
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
46856
46860
|
import { defineCommand as defineCommand128 } from "citty";
|
|
46857
46861
|
|
|
46862
|
+
// src/commands/funnel/formMoments.ts
|
|
46863
|
+
var MAX_NAMED_FORMS = 8;
|
|
46864
|
+
function formMoments(events, forms) {
|
|
46865
|
+
const seen = new Set(events.map((event) => event.eventKey));
|
|
46866
|
+
const rows = [];
|
|
46867
|
+
for (const form of forms) {
|
|
46868
|
+
const outcome = suggestedEnding(flowEventKeys(form.tree, form.slug));
|
|
46869
|
+
if (outcome === null || seen.has(outcome.eventKey)) continue;
|
|
46870
|
+
seen.add(outcome.eventKey);
|
|
46871
|
+
rows.push({
|
|
46872
|
+
eventKey: outcome.eventKey,
|
|
46873
|
+
origin: "site",
|
|
46874
|
+
flowSlug: form.slug,
|
|
46875
|
+
...outcome.nodeType === null ? {} : { nodeType: outcome.nodeType },
|
|
46876
|
+
events: 0,
|
|
46877
|
+
sessions: 0,
|
|
46878
|
+
step: outcome.nodeName,
|
|
46879
|
+
seen: false,
|
|
46880
|
+
outcome: true
|
|
46881
|
+
});
|
|
46882
|
+
}
|
|
46883
|
+
return rows;
|
|
46884
|
+
}
|
|
46885
|
+
function withFormMoments(moments, forms) {
|
|
46886
|
+
return { ...moments, events: [...moments.events, ...formMoments(moments.events, forms)] };
|
|
46887
|
+
}
|
|
46888
|
+
function unplacedFormHints(steps, forms) {
|
|
46889
|
+
const onAStep = new Set(
|
|
46890
|
+
steps.flatMap((step) => (step.triggers ?? []).flatMap((trigger) => trigger.eventKey ? [trigger.eventKey] : []))
|
|
46891
|
+
);
|
|
46892
|
+
const unplaced = [];
|
|
46893
|
+
for (const form of forms) {
|
|
46894
|
+
const endings = flowEventKeys(form.tree, form.slug);
|
|
46895
|
+
if (endings.some((ending) => onAStep.has(ending.eventKey))) continue;
|
|
46896
|
+
const outcome = suggestedEnding(endings);
|
|
46897
|
+
if (outcome !== null) unplaced.push(`${form.slug} \u2192 \`${outcome.eventKey}\` ("${outcome.nodeName}")`);
|
|
46898
|
+
}
|
|
46899
|
+
if (unplaced.length === 0) return [];
|
|
46900
|
+
const shown = unplaced.slice(0, MAX_NAMED_FORMS);
|
|
46901
|
+
const rest = unplaced.length - shown.length;
|
|
46902
|
+
return [
|
|
46903
|
+
`${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>\`.`
|
|
46904
|
+
];
|
|
46905
|
+
}
|
|
46906
|
+
|
|
46858
46907
|
// ../api/src/funnel/vocabulary.ts
|
|
46859
46908
|
var FUNNEL_STEP_CLASSES = [
|
|
46860
46909
|
/** Saw the offer: a landing opened, an ad clicked through. */
|
|
@@ -47147,6 +47196,18 @@ var funnelRequestSchema = z44.object({
|
|
|
47147
47196
|
step: funnelStepSchema.optional(),
|
|
47148
47197
|
/** One step taken out by key; whatever came after it moves up to whatever came before it. */
|
|
47149
47198
|
removeStep: z44.string().min(1).max(64).optional(),
|
|
47199
|
+
/**
|
|
47200
|
+
* ONE way in added to a step, leaving the rest of that step alone.
|
|
47201
|
+
*
|
|
47202
|
+
* `step` takes a whole step, so adding a way in through it means restating
|
|
47203
|
+
* the step's name, stage, `after[]` and every trigger it already had — and a
|
|
47204
|
+
* trigger left out of that restatement is silently removed. This is the door
|
|
47205
|
+
* for the thing that is actually being asked ("this Form's booking should
|
|
47206
|
+
* count here"), and it cannot drop anything.
|
|
47207
|
+
*/
|
|
47208
|
+
addTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
|
|
47209
|
+
/** ONE way in taken off a step, by the event key it listens for. */
|
|
47210
|
+
removeTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
|
|
47150
47211
|
/** One of the ready-made graphs, instead of stating steps. */
|
|
47151
47212
|
preset: z44.enum(FUNNEL_VERTICALS).optional(),
|
|
47152
47213
|
/** Also return the suggestion library and the presets. */
|
|
@@ -47224,7 +47285,30 @@ var funnelResponseSchema = z44.object({
|
|
|
47224
47285
|
events: z44.number(),
|
|
47225
47286
|
sessions: z44.number(),
|
|
47226
47287
|
/** The name it is counted under as a page conversion, when the company counts it. Informational. */
|
|
47227
|
-
countedAs: z44.string().optional()
|
|
47288
|
+
countedAs: z44.string().optional(),
|
|
47289
|
+
/**
|
|
47290
|
+
* The step of the Form this happens on, in the Form's own words —
|
|
47291
|
+
* never a node id. Present on a Form's ending read from the Form
|
|
47292
|
+
* itself, which is the only place the name is known.
|
|
47293
|
+
*/
|
|
47294
|
+
step: z44.string().optional(),
|
|
47295
|
+
/**
|
|
47296
|
+
* `true` on the ONE ending of a Form that is its outcome — the
|
|
47297
|
+
* booking on a Form that books, the submit on a Form that submits.
|
|
47298
|
+
* A Form raises several events and only one of them is the point;
|
|
47299
|
+
* without this the agent is handed five equal-looking keys and the
|
|
47300
|
+
* lifecycle noise a widget raises about itself sits among them.
|
|
47301
|
+
* Same rule as `baker analytics conversions` recommends, so the
|
|
47302
|
+
* conversion and the funnel step land on the same event.
|
|
47303
|
+
*/
|
|
47304
|
+
outcome: z44.boolean().optional(),
|
|
47305
|
+
/**
|
|
47306
|
+
* `false` on an ending a Form raises natively but nobody has fired
|
|
47307
|
+
* yet — derived from the Form, offered at zero, exactly as a
|
|
47308
|
+
* Source is offered before its first send. Absent means it came
|
|
47309
|
+
* from the event store and therefore happened.
|
|
47310
|
+
*/
|
|
47311
|
+
seen: z44.boolean().optional()
|
|
47228
47312
|
})
|
|
47229
47313
|
),
|
|
47230
47314
|
/** The company's sources: a system of theirs that posts events to Baker, by the event name it raises. */
|
|
@@ -47280,9 +47364,15 @@ function parseFunnelArgs(raw) {
|
|
|
47280
47364
|
const step = parseStep(raw.step);
|
|
47281
47365
|
const removeStep = parseRemoveStep(raw["remove-step"] ?? raw.removeStep);
|
|
47282
47366
|
const preset = parsePreset(raw.preset);
|
|
47283
|
-
const
|
|
47367
|
+
const addTrigger = parseWayIn(raw["add-trigger"] ?? raw.addTrigger, raw.event, "--add-trigger");
|
|
47368
|
+
const removeTrigger = parseWayIn(raw["remove-trigger"] ?? raw.removeTrigger, raw.event, "--remove-trigger");
|
|
47369
|
+
const doors = [steps, step, removeStep, preset, addTrigger, removeTrigger].filter(
|
|
47370
|
+
(door) => door !== void 0
|
|
47371
|
+
).length;
|
|
47284
47372
|
if (doors > 1) {
|
|
47285
|
-
throw new FunnelArgsError(
|
|
47373
|
+
throw new FunnelArgsError(
|
|
47374
|
+
"Give one of --steps, --step, --remove-step, --preset, --add-trigger or --remove-trigger, not two."
|
|
47375
|
+
);
|
|
47286
47376
|
}
|
|
47287
47377
|
const days = raw.days === void 0 ? void 0 : Number(raw.days);
|
|
47288
47378
|
if (days !== void 0 && (!Number.isInteger(days) || days <= 0 || days > 400)) {
|
|
@@ -47293,6 +47383,8 @@ function parseFunnelArgs(raw) {
|
|
|
47293
47383
|
...step === void 0 ? {} : { step },
|
|
47294
47384
|
...removeStep === void 0 ? {} : { removeStep },
|
|
47295
47385
|
...preset === void 0 ? {} : { preset },
|
|
47386
|
+
...addTrigger === void 0 ? {} : { addTrigger },
|
|
47387
|
+
...removeTrigger === void 0 ? {} : { removeTrigger },
|
|
47296
47388
|
templates: raw.templates === true,
|
|
47297
47389
|
moments: raw.moments === true,
|
|
47298
47390
|
...days === void 0 ? {} : { days }
|
|
@@ -47304,6 +47396,8 @@ function funnelWrite(args4) {
|
|
|
47304
47396
|
...args4.step === void 0 ? {} : { step: args4.step },
|
|
47305
47397
|
...args4.removeStep === void 0 ? {} : { removeStep: args4.removeStep },
|
|
47306
47398
|
...args4.preset === void 0 ? {} : { preset: args4.preset },
|
|
47399
|
+
...args4.addTrigger === void 0 ? {} : { addTrigger: args4.addTrigger },
|
|
47400
|
+
...args4.removeTrigger === void 0 ? {} : { removeTrigger: args4.removeTrigger },
|
|
47307
47401
|
...args4.templates ? { templates: true } : {},
|
|
47308
47402
|
...args4.moments ? { moments: true } : {},
|
|
47309
47403
|
...args4.days === void 0 ? {} : { days: args4.days }
|
|
@@ -47413,6 +47507,26 @@ function parsePreset(raw) {
|
|
|
47413
47507
|
}
|
|
47414
47508
|
return raw;
|
|
47415
47509
|
}
|
|
47510
|
+
function parseWayIn(stepKey, eventKey, flag) {
|
|
47511
|
+
if (stepKey === void 0) return void 0;
|
|
47512
|
+
if (typeof stepKey !== "string" || stepKey.trim() === "") {
|
|
47513
|
+
throw new FunnelArgsError(`${flag} takes the key of the step to change: \`${flag} lead\`.`);
|
|
47514
|
+
}
|
|
47515
|
+
if (typeof eventKey !== "string" || eventKey.trim() === "") {
|
|
47516
|
+
throw new FunnelArgsError(`${flag} needs --event, naming what gets somebody there.`, {
|
|
47517
|
+
action: "read_the_moments",
|
|
47518
|
+
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>"\`.`
|
|
47519
|
+
});
|
|
47520
|
+
}
|
|
47521
|
+
const key = eventKey.trim();
|
|
47522
|
+
if (flag === "--add-trigger" && parseEventKey(key) === null) {
|
|
47523
|
+
throw new FunnelArgsError(`"${key}" is not an event key, so nothing would ever reach that step through it.`, {
|
|
47524
|
+
action: "read_the_moments",
|
|
47525
|
+
explanation: "Run `baker funnel --moments` and copy an `events[].eventKey`. They look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or view:<landing>."
|
|
47526
|
+
});
|
|
47527
|
+
}
|
|
47528
|
+
return { stepKey: stepKey.trim(), eventKey: key };
|
|
47529
|
+
}
|
|
47416
47530
|
|
|
47417
47531
|
// src/commands/funnel/index.ts
|
|
47418
47532
|
var args3 = {
|
|
@@ -47426,6 +47540,21 @@ var args3 = {
|
|
|
47426
47540
|
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"}]}\'',
|
|
47427
47541
|
required: false
|
|
47428
47542
|
},
|
|
47543
|
+
"add-trigger": {
|
|
47544
|
+
type: "string",
|
|
47545
|
+
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',
|
|
47546
|
+
required: false
|
|
47547
|
+
},
|
|
47548
|
+
"remove-trigger": {
|
|
47549
|
+
type: "string",
|
|
47550
|
+
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',
|
|
47551
|
+
required: false
|
|
47552
|
+
},
|
|
47553
|
+
event: {
|
|
47554
|
+
type: "string",
|
|
47555
|
+
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",
|
|
47556
|
+
required: false
|
|
47557
|
+
},
|
|
47429
47558
|
"remove-step": {
|
|
47430
47559
|
type: "string",
|
|
47431
47560
|
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`",
|
|
@@ -47438,7 +47567,7 @@ var args3 = {
|
|
|
47438
47567
|
},
|
|
47439
47568
|
moments: {
|
|
47440
47569
|
type: "boolean",
|
|
47441
|
-
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
|
|
47570
|
+
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; PLUS **one row per Form in this workspace \u2014 its OUTCOME** \u2014 at zero, with `outcome: true`, `seen: false` and the `step` it happens on, 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 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 when they are before anybody has filled it in), `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",
|
|
47442
47571
|
required: false
|
|
47443
47572
|
},
|
|
47444
47573
|
days: {
|
|
@@ -47460,17 +47589,20 @@ registerSchema({
|
|
|
47460
47589
|
var funnelCommand2 = defineCommand128({
|
|
47461
47590
|
meta: {
|
|
47462
47591
|
name: "funnel",
|
|
47463
|
-
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
|
|
47592
|
+
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.'
|
|
47464
47593
|
},
|
|
47465
47594
|
args: args3,
|
|
47466
47595
|
run: async ({ args: raw }) => {
|
|
47467
47596
|
try {
|
|
47468
47597
|
const parsed = parseFunnelArgs(raw);
|
|
47469
47598
|
const response = await apiPost("/api/funnel", funnelWrite(parsed));
|
|
47599
|
+
const forms = workspaceForms();
|
|
47600
|
+
const data = response.data.moments === void 0 ? response.data : { ...response.data, moments: withFormMoments(response.data.moments, forms) };
|
|
47601
|
+
const hints2 = [...response.hints ?? [], ...unplacedFormHints(response.data.steps ?? [], forms)];
|
|
47470
47602
|
writeJsonEnvelope({
|
|
47471
47603
|
ok: true,
|
|
47472
|
-
data
|
|
47473
|
-
...
|
|
47604
|
+
data,
|
|
47605
|
+
...hints2.length > 0 ? { hints: hints2 } : {}
|
|
47474
47606
|
});
|
|
47475
47607
|
} catch (err) {
|
|
47476
47608
|
handleError4(err);
|
|
@@ -47495,6 +47627,16 @@ function handleError4(err) {
|
|
|
47495
47627
|
});
|
|
47496
47628
|
process.exit(1);
|
|
47497
47629
|
}
|
|
47630
|
+
function workspaceForms() {
|
|
47631
|
+
const forms = [];
|
|
47632
|
+
for (const slug of listFlowSlugs()) {
|
|
47633
|
+
try {
|
|
47634
|
+
forms.push({ slug, tree: JSON.parse(readFileSync15(flowDataPath(slug), "utf-8")) });
|
|
47635
|
+
} catch {
|
|
47636
|
+
}
|
|
47637
|
+
}
|
|
47638
|
+
return forms;
|
|
47639
|
+
}
|
|
47498
47640
|
|
|
47499
47641
|
// src/commands/ga4/index.ts
|
|
47500
47642
|
import { defineCommand as defineCommand135 } from "citty";
|
|
@@ -47624,7 +47766,7 @@ Examples:
|
|
|
47624
47766
|
import { defineCommand as defineCommand130 } from "citty";
|
|
47625
47767
|
|
|
47626
47768
|
// src/commands/ga4/shared.ts
|
|
47627
|
-
import { readFileSync as
|
|
47769
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
47628
47770
|
function failValidation3(message) {
|
|
47629
47771
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
47630
47772
|
process.exit(1);
|
|
@@ -47640,7 +47782,7 @@ function requireTarget5(args4, entity) {
|
|
|
47640
47782
|
function readJsonSource(args4, flag) {
|
|
47641
47783
|
const inline = args4[flag];
|
|
47642
47784
|
const file = args4.file;
|
|
47643
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
47785
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync16(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
47644
47786
|
if (raw === void 0) {
|
|
47645
47787
|
failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
47646
47788
|
}
|
|
@@ -47991,7 +48133,7 @@ Examples:
|
|
|
47991
48133
|
});
|
|
47992
48134
|
|
|
47993
48135
|
// src/commands/ga4/query.ts
|
|
47994
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as
|
|
48136
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
|
|
47995
48137
|
import { resolve as resolve2 } from "path";
|
|
47996
48138
|
import { defineCommand as defineCommand133 } from "citty";
|
|
47997
48139
|
|
|
@@ -48082,7 +48224,7 @@ function writeRowsToFile2(filePath, rows, append) {
|
|
|
48082
48224
|
writeFileSync4(filePath, content, "utf-8");
|
|
48083
48225
|
}
|
|
48084
48226
|
} else if (append && existsSync7(filePath)) {
|
|
48085
|
-
const existing = JSON.parse(
|
|
48227
|
+
const existing = JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
48086
48228
|
writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
48087
48229
|
} else {
|
|
48088
48230
|
writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -48934,7 +49076,7 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
|
|
|
48934
49076
|
import { defineCommand as defineCommand139 } from "citty";
|
|
48935
49077
|
|
|
48936
49078
|
// src/commands/gsc/query.ts
|
|
48937
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as
|
|
49079
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
48938
49080
|
import { resolve as resolve3 } from "path";
|
|
48939
49081
|
import { defineCommand as defineCommand136 } from "citty";
|
|
48940
49082
|
|
|
@@ -49082,7 +49224,7 @@ function writeRowsToFile3(filePath, rows, append) {
|
|
|
49082
49224
|
writeFileSync5(filePath, content, "utf-8");
|
|
49083
49225
|
}
|
|
49084
49226
|
} else if (append && existsSync8(filePath)) {
|
|
49085
|
-
const existing = JSON.parse(
|
|
49227
|
+
const existing = JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
49086
49228
|
writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
49087
49229
|
} else {
|
|
49088
49230
|
writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -55960,7 +56102,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
55960
56102
|
}
|
|
55961
56103
|
return false;
|
|
55962
56104
|
};
|
|
55963
|
-
const
|
|
56105
|
+
const walk = (rules, sink) => {
|
|
55964
56106
|
for (const rule of Array.from(rules)) {
|
|
55965
56107
|
totalRules++;
|
|
55966
56108
|
if (rule instanceof CSSStyleRule) {
|
|
@@ -55981,7 +56123,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
55981
56123
|
const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
|
|
55982
56124
|
if (grouping) {
|
|
55983
56125
|
const inner = [];
|
|
55984
|
-
|
|
56126
|
+
walk(rule.cssRules, inner);
|
|
55985
56127
|
if (inner.length === 0) continue;
|
|
55986
56128
|
const condition = rule.conditionText ?? "";
|
|
55987
56129
|
const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
|
|
@@ -55995,7 +56137,7 @@ ${inner.join("\n")}
|
|
|
55995
56137
|
const owner = sheet.ownerNode;
|
|
55996
56138
|
if (owner?.hasAttribute?.("data-baker-freeze")) continue;
|
|
55997
56139
|
try {
|
|
55998
|
-
|
|
56140
|
+
walk(sheet.cssRules, kept);
|
|
55999
56141
|
} catch {
|
|
56000
56142
|
if (sheet.href) unreadableHrefs.push(sheet.href);
|
|
56001
56143
|
}
|
|
@@ -56446,7 +56588,7 @@ var inPageCollectMotion = (selector) => {
|
|
|
56446
56588
|
else if (infinite) loop.push(effect);
|
|
56447
56589
|
else entrance.push(effect);
|
|
56448
56590
|
};
|
|
56449
|
-
const
|
|
56591
|
+
const walk = (rules, insideScrollTimeline) => {
|
|
56450
56592
|
for (const rule of Array.from(rules)) {
|
|
56451
56593
|
if (rule instanceof CSSKeyframesRule) {
|
|
56452
56594
|
keyframesByName.set(rule.name, rule.cssText);
|
|
@@ -56464,17 +56606,17 @@ var inPageCollectMotion = (selector) => {
|
|
|
56464
56606
|
respectsReducedMotion = true;
|
|
56465
56607
|
continue;
|
|
56466
56608
|
}
|
|
56467
|
-
|
|
56609
|
+
walk(rule.cssRules, insideScrollTimeline);
|
|
56468
56610
|
continue;
|
|
56469
56611
|
}
|
|
56470
56612
|
const grouping = rule;
|
|
56471
|
-
if (grouping.cssRules)
|
|
56613
|
+
if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
|
|
56472
56614
|
}
|
|
56473
56615
|
};
|
|
56474
56616
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
56475
56617
|
if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
|
|
56476
56618
|
try {
|
|
56477
|
-
|
|
56619
|
+
walk(sheet.cssRules, false);
|
|
56478
56620
|
} catch {
|
|
56479
56621
|
}
|
|
56480
56622
|
}
|
|
@@ -62313,7 +62455,7 @@ import { defineCommand as defineCommand216 } from "citty";
|
|
|
62313
62455
|
import { defineCommand as defineCommand213 } from "citty";
|
|
62314
62456
|
|
|
62315
62457
|
// src/commands/tag-manager/shared.ts
|
|
62316
|
-
import { readFileSync as
|
|
62458
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
62317
62459
|
function failValidation5(message) {
|
|
62318
62460
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
62319
62461
|
process.exit(1);
|
|
@@ -62329,7 +62471,7 @@ function requireTarget6(args4, entity) {
|
|
|
62329
62471
|
function loadJsonArg2(args4, flag = "json") {
|
|
62330
62472
|
const inline = args4[flag];
|
|
62331
62473
|
const file = args4.file;
|
|
62332
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
62474
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync19(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
62333
62475
|
if (raw === void 0) {
|
|
62334
62476
|
failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
62335
62477
|
}
|
|
@@ -65608,7 +65750,7 @@ function refuseUnknownFlags(root, argv) {
|
|
|
65608
65750
|
}
|
|
65609
65751
|
|
|
65610
65752
|
// src/version.ts
|
|
65611
|
-
import { readFileSync as
|
|
65753
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
65612
65754
|
function packageJsonUrl() {
|
|
65613
65755
|
return new URL("../package.json", import.meta.url);
|
|
65614
65756
|
}
|
|
@@ -65620,7 +65762,7 @@ function parsePackageVersion(raw) {
|
|
|
65620
65762
|
throw new Error("Invalid CLI package.json: missing version");
|
|
65621
65763
|
}
|
|
65622
65764
|
function getCliVersion() {
|
|
65623
|
-
return parsePackageVersion(
|
|
65765
|
+
return parsePackageVersion(readFileSync20(packageJsonUrl(), "utf8"));
|
|
65624
65766
|
}
|
|
65625
65767
|
|
|
65626
65768
|
// src/cli.ts
|