@koda-sl/baker-cli 0.236.0 → 0.237.0-dev.b9732c021
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 +2 -0
- package/dist/{chunk-EKLAHWSF.js → chunk-CMPAHYLB.js} +4 -4
- package/dist/chunk-CMPAHYLB.js.map +1 -0
- package/dist/cli.js +128 -9
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EKLAHWSF.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-CMPAHYLB.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -11347,6 +11347,62 @@ var AD_PARAM_ROLES = [
|
|
|
11347
11347
|
"other",
|
|
11348
11348
|
"ignore"
|
|
11349
11349
|
];
|
|
11350
|
+
var AD_PARAM_ROLE_SPECS = {
|
|
11351
|
+
campaign_id: { role: "campaign_id", label: "Campaign ID", description: "The top level of this account's structure." },
|
|
11352
|
+
campaign_name: { role: "campaign_name", label: "Campaign name", description: "The campaign's name." },
|
|
11353
|
+
adgroup_id: { role: "adgroup_id", label: "Ad group ID", description: "The targeting container inside a campaign." },
|
|
11354
|
+
adgroup_name: { role: "adgroup_name", label: "Ad group name", description: "The ad group's name." },
|
|
11355
|
+
ad_id: { role: "ad_id", label: "Ad ID", description: "The individual ad that was clicked." },
|
|
11356
|
+
ad_name: { role: "ad_name", label: "Ad name", description: "The individual ad's name." },
|
|
11357
|
+
keyword: {
|
|
11358
|
+
role: "keyword",
|
|
11359
|
+
label: "Keyword",
|
|
11360
|
+
description: "The keyword that matched, as it is written in the account \u2014 not what the visitor typed."
|
|
11361
|
+
},
|
|
11362
|
+
match_type: {
|
|
11363
|
+
role: "match_type",
|
|
11364
|
+
label: "Match type",
|
|
11365
|
+
description: "How loosely the keyword matched: broad, phrase or exact."
|
|
11366
|
+
},
|
|
11367
|
+
search_query: { role: "search_query", label: "Search query", description: "What the visitor actually typed." },
|
|
11368
|
+
audience: {
|
|
11369
|
+
role: "audience",
|
|
11370
|
+
label: "Audience",
|
|
11371
|
+
description: "The remarketing list or interest segment the visitor was in."
|
|
11372
|
+
},
|
|
11373
|
+
placement: {
|
|
11374
|
+
role: "placement",
|
|
11375
|
+
label: "Placement",
|
|
11376
|
+
description: "Where the ad appeared \u2014 a feed, a story, a site on the display network."
|
|
11377
|
+
},
|
|
11378
|
+
network: {
|
|
11379
|
+
role: "network",
|
|
11380
|
+
label: "Network",
|
|
11381
|
+
description: "Which network served it: search, a search partner, display, or a video surface."
|
|
11382
|
+
},
|
|
11383
|
+
site_source: {
|
|
11384
|
+
role: "site_source",
|
|
11385
|
+
label: "App",
|
|
11386
|
+
description: "Which app the click came from, on platforms that span several."
|
|
11387
|
+
},
|
|
11388
|
+
device: { role: "device", label: "Device", description: "Mobile, tablet or desktop, as the platform saw it." },
|
|
11389
|
+
product_id: { role: "product_id", label: "Product ID", description: "The shopping item that was clicked." },
|
|
11390
|
+
account_name: {
|
|
11391
|
+
role: "account_name",
|
|
11392
|
+
label: "Account name",
|
|
11393
|
+
description: "Which ad account paid for the click, where an agency runs several."
|
|
11394
|
+
},
|
|
11395
|
+
other: {
|
|
11396
|
+
role: "other",
|
|
11397
|
+
label: "Something else",
|
|
11398
|
+
description: "Keep the values and let me filter by this name, without saying what it means."
|
|
11399
|
+
},
|
|
11400
|
+
ignore: {
|
|
11401
|
+
role: "ignore",
|
|
11402
|
+
label: "Ignore it",
|
|
11403
|
+
description: "Not campaign information. Baker stops storing it and stops asking about it."
|
|
11404
|
+
}
|
|
11405
|
+
};
|
|
11350
11406
|
var RESERVED_PARAM_NAMES = [
|
|
11351
11407
|
"utm_source",
|
|
11352
11408
|
"utm_medium",
|
|
@@ -11365,6 +11421,9 @@ var AD_PLATFORMS = [
|
|
|
11365
11421
|
"pinterest",
|
|
11366
11422
|
"snapchat"
|
|
11367
11423
|
];
|
|
11424
|
+
function isAdPlatform(value) {
|
|
11425
|
+
return AD_PLATFORMS.includes(value);
|
|
11426
|
+
}
|
|
11368
11427
|
var GOOGLE = {
|
|
11369
11428
|
platform: "google",
|
|
11370
11429
|
label: "Google Ads",
|
|
@@ -11557,6 +11616,10 @@ var AD_PLATFORM_DEFINITIONS = {
|
|
|
11557
11616
|
pinterest: PINTEREST,
|
|
11558
11617
|
snapchat: SNAPCHAT
|
|
11559
11618
|
};
|
|
11619
|
+
var ESSENTIAL_ROLES = ["campaign_id", "adgroup_id", "ad_id", "keyword", "match_type"];
|
|
11620
|
+
function essentialAdDimensions(platform) {
|
|
11621
|
+
return AD_PLATFORM_DEFINITIONS[platform].dimensions.filter((entry) => ESSENTIAL_ROLES.includes(entry.role));
|
|
11622
|
+
}
|
|
11560
11623
|
function normalizeAdParamName(raw) {
|
|
11561
11624
|
return raw.trim().toLowerCase().slice(0, 64);
|
|
11562
11625
|
}
|
|
@@ -12361,10 +12424,19 @@ var analyticsLandingRowSchema = z24.object({
|
|
|
12361
12424
|
*
|
|
12362
12425
|
* Derived from the click ids a visit arrived with, never from `utm_source`:
|
|
12363
12426
|
* a label is whatever somebody typed into a tracking template, a `gclid` is a
|
|
12364
|
-
* click.
|
|
12365
|
-
|
|
12427
|
+
* click. Ordered by traffic, busiest first.
|
|
12428
|
+
*/
|
|
12429
|
+
channels: z24.array(z24.string()),
|
|
12430
|
+
/**
|
|
12431
|
+
* Visits per entry of {@link channels}, in the same order.
|
|
12432
|
+
*
|
|
12433
|
+
* The half that makes a mark readable. A mark on its own says "advertised
|
|
12434
|
+
* here", which is why it used to be withheld until three visits had arrived —
|
|
12435
|
+
* and withholding it is what made a page whose whole day was five Meta clicks
|
|
12436
|
+
* show nothing at all. The count says how much, so one test click and a live
|
|
12437
|
+
* campaign are distinguishable by reading rather than by absence.
|
|
12366
12438
|
*/
|
|
12367
|
-
|
|
12439
|
+
channelSessions: z24.array(z24.number().int().nonnegative())
|
|
12368
12440
|
});
|
|
12369
12441
|
var analyticsTagRowSchema = z24.object({
|
|
12370
12442
|
tag: z24.string(),
|
|
@@ -12487,7 +12559,9 @@ var analyticsDimensionRowSchema = z24.object({
|
|
|
12487
12559
|
var analyticsFunnelStepSchema = z24.object({
|
|
12488
12560
|
stepId: z24.string(),
|
|
12489
12561
|
stepIndex: z24.number().int().nonnegative(),
|
|
12562
|
+
/** Visits that moved forward into this step. Never events — see `visits`. */
|
|
12490
12563
|
views: z24.number().int().nonnegative(),
|
|
12564
|
+
/** Visits that finished this step and moved on. */
|
|
12491
12565
|
completions: z24.number().int().nonnegative(),
|
|
12492
12566
|
/** Share of viewers who did not complete this step, 0–1. */
|
|
12493
12567
|
dropRate: z24.number().min(0).max(1).nullable(),
|
|
@@ -12503,9 +12577,25 @@ var analyticsFunnelStepSchema = z24.object({
|
|
|
12503
12577
|
});
|
|
12504
12578
|
var analyticsFunnelSchema = z24.object({
|
|
12505
12579
|
flowSlug: z24.string(),
|
|
12580
|
+
/**
|
|
12581
|
+
* Visits that saw the Form at all — the denominator for every share here.
|
|
12582
|
+
*
|
|
12583
|
+
* Read this, not `starts`, whenever the question is "out of how many". A
|
|
12584
|
+
* start is recorded at the visitor's FIRST INTERACTION, so the two differ by
|
|
12585
|
+
* exactly the people who looked at the Form and never touched it — usually
|
|
12586
|
+
* most of them, and always the group a drop-off chart exists to find.
|
|
12587
|
+
*
|
|
12588
|
+
* Every count in this object is distinct visits, including each step's
|
|
12589
|
+
* `views` and `completions`. That is load-bearing rather than incidental:
|
|
12590
|
+
* the screen divides these by each other, and while the steps counted events
|
|
12591
|
+
* a Form four people had opened reported one step reached fifteen times and
|
|
12592
|
+
* fourteen people lost.
|
|
12593
|
+
*/
|
|
12594
|
+
visits: z24.number().int().nonnegative(),
|
|
12595
|
+
/** Visits that interacted with the Form at all — typed, picked, or advanced. */
|
|
12506
12596
|
starts: z24.number().int().nonnegative(),
|
|
12507
12597
|
submits: z24.number().int().nonnegative(),
|
|
12508
|
-
/**
|
|
12598
|
+
/** Conversions per visit that opened the Form, 0–1. */
|
|
12509
12599
|
completionRate: z24.number().min(0).max(1).nullable(),
|
|
12510
12600
|
/** The step losing the most people. The single most useful field here. */
|
|
12511
12601
|
worstStep: analyticsFunnelStepSchema.nullable(),
|
|
@@ -12923,7 +13013,14 @@ var analyticsVisitProfileSchema = z24.object({
|
|
|
12923
13013
|
utmSource: z24.string(),
|
|
12924
13014
|
utmMedium: z24.string(),
|
|
12925
13015
|
utmCampaign: z24.string(),
|
|
12926
|
-
/**
|
|
13016
|
+
/**
|
|
13017
|
+
* Which platform the click id proves, in the `AD_PLATFORMS` vocabulary —
|
|
13018
|
+
* `google` | `meta` | `tiktok` | `x` | `linkedin` | `microsoft` | `""`.
|
|
13019
|
+
*
|
|
13020
|
+
* The same spelling every mark, mapping and report uses. It briefly said
|
|
13021
|
+
* `google_ads` / `meta_ads` here alone, which meant every logo lookup on the
|
|
13022
|
+
* People screen missed and a paid visit drew a neutral globe.
|
|
13023
|
+
*/
|
|
12927
13024
|
clickPlatform: z24.string(),
|
|
12928
13025
|
/** Every ValueTrack-style parameter the URL carried, unresolved. */
|
|
12929
13026
|
campaignParams: z24.record(z24.string(), z24.string()),
|
|
@@ -23060,7 +23157,22 @@ function shapeHints(data) {
|
|
|
23060
23157
|
}
|
|
23061
23158
|
return hints;
|
|
23062
23159
|
}
|
|
23063
|
-
function
|
|
23160
|
+
function adHierarchyHints(data, platform) {
|
|
23161
|
+
if (platform === void 0) return [];
|
|
23162
|
+
const traffic = (data.adPlatformTraffic ?? []).find((entry) => entry.platform === platform);
|
|
23163
|
+
if (traffic === void 0 || traffic.sessions < MIN_SESSIONS_FOR_TAGGING_CLAIM) return [];
|
|
23164
|
+
if (traffic.taggedSessions === 0) return [];
|
|
23165
|
+
const carried = new Set(
|
|
23166
|
+
(data.adParams ?? []).filter((row) => row.role !== void 0 && row.awaitingTraffic !== true).map((row) => row.role)
|
|
23167
|
+
);
|
|
23168
|
+
const missing = essentialAdDimensions(platform).filter((entry) => !carried.has(entry.role));
|
|
23169
|
+
if (missing.length === 0) return [];
|
|
23170
|
+
const suffix = missing.map((entry) => `${entry.names[0] ?? entry.role}=${entry.macro}`).join("&");
|
|
23171
|
+
return [
|
|
23172
|
+
`${platform} traffic carries no ${missing.map((entry) => AD_PARAM_ROLE_SPECS[entry.role].label.toLowerCase()).join(", ")}, so no report can say which ad sent a visit \u2014 only which platform. Add \`${suffix}\` to that account's tracking template. This cannot be backfilled: a parameter's values are only stored once it starts arriving.`
|
|
23173
|
+
];
|
|
23174
|
+
}
|
|
23175
|
+
function buildAnalyticsHints(data, platform) {
|
|
23064
23176
|
const hints = [];
|
|
23065
23177
|
for (const funnel of data.funnels ?? []) {
|
|
23066
23178
|
const worst = funnel.worstStep;
|
|
@@ -23086,6 +23198,7 @@ function buildAnalyticsHints(data) {
|
|
|
23086
23198
|
);
|
|
23087
23199
|
}
|
|
23088
23200
|
hints.push(...shapeHints(data));
|
|
23201
|
+
hints.push(...adHierarchyHints(data, platform));
|
|
23089
23202
|
if ((data.topCampaigns ?? []).length === 0 && (data.topSources ?? []).length > 0) {
|
|
23090
23203
|
hints.push(
|
|
23091
23204
|
"No campaign tagging on any traffic \u2014 ad clicks cannot be attributed to a campaign until the destination URLs carry utm parameters."
|
|
@@ -23514,6 +23627,8 @@ function requestBody(args, options) {
|
|
|
23514
23627
|
async function runPreset(args, options) {
|
|
23515
23628
|
try {
|
|
23516
23629
|
const response = await apiPost("/api/analytics/query", requestBody(args, options));
|
|
23630
|
+
const requestedPlatform = args.platform === void 0 ? "" : String(args.platform);
|
|
23631
|
+
const adPlatformScope = isAdPlatform(requestedPlatform) ? requestedPlatform : void 0;
|
|
23517
23632
|
const format = args.output === void 0 ? "json" : String(args.output);
|
|
23518
23633
|
if (!isAnalyticsOutputFormat(format)) {
|
|
23519
23634
|
writeJsonEnvelope({
|
|
@@ -23527,7 +23642,11 @@ async function runPreset(args, options) {
|
|
|
23527
23642
|
}
|
|
23528
23643
|
const data = pruneForAgent(response.data, args.full === true);
|
|
23529
23644
|
const hints = [
|
|
23530
|
-
|
|
23645
|
+
// The platform the ad blocks were scoped to, so the tagging hint can
|
|
23646
|
+
// only speak about the inventory it was actually given. `adParams` is one
|
|
23647
|
+
// platform's list; a hint drawn from it without knowing which platform
|
|
23648
|
+
// would be a claim about Meta made from Google's URLs.
|
|
23649
|
+
...buildAnalyticsHints(response.data, adPlatformScope),
|
|
23531
23650
|
...missingBreakdownHint(response.data, {
|
|
23532
23651
|
eventName: options.eventName,
|
|
23533
23652
|
property: options.property
|
|
@@ -23586,7 +23705,7 @@ var funnelCommand = presetCommand({
|
|
|
23586
23705
|
var flowCommand = presetCommand({
|
|
23587
23706
|
name: "flow",
|
|
23588
23707
|
preset: "flow",
|
|
23589
|
-
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
|
|
23708
|
+
description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
|
|
23590
23709
|
extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
|
|
23591
23710
|
resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
|
|
23592
23711
|
});
|