@koda-sl/baker-cli 0.293.0 → 0.294.0-dev.ae1dcf96d
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 +13 -0
- package/dist/{chunk-NNQDWCFG.js → chunk-2IHIUOJX.js} +4 -4
- package/dist/chunk-2IHIUOJX.js.map +1 -0
- package/dist/cli.js +251 -16
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-NNQDWCFG.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-2IHIUOJX.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -11431,6 +11431,45 @@ import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync
|
|
|
11431
11431
|
import { resolve } from "path";
|
|
11432
11432
|
import { defineCommand as defineCommand28 } from "citty";
|
|
11433
11433
|
|
|
11434
|
+
// src/commands/ads/google/conversion-mix.ts
|
|
11435
|
+
var CONVERSION_METRICS = [
|
|
11436
|
+
"METRICS.CONVERSIONS",
|
|
11437
|
+
"METRICS.ALL_CONVERSIONS",
|
|
11438
|
+
"METRICS.CONVERSIONS_VALUE",
|
|
11439
|
+
"METRICS.ALL_CONVERSIONS_VALUE",
|
|
11440
|
+
"METRICS.COST_PER_CONVERSION",
|
|
11441
|
+
"METRICS.COST_PER_ALL_CONVERSIONS",
|
|
11442
|
+
"METRICS.CONVERSIONS_FROM_INTERACTIONS_RATE",
|
|
11443
|
+
"METRICS.ALL_CONVERSIONS_FROM_INTERACTIONS_RATE",
|
|
11444
|
+
"METRICS.VALUE_PER_CONVERSION",
|
|
11445
|
+
"METRICS.VALUE_PER_ALL_CONVERSIONS"
|
|
11446
|
+
];
|
|
11447
|
+
var CONVERSION_SEGMENTS = ["SEGMENTS.CONVERSION_ACTION_NAME", "SEGMENTS.CONVERSION_ACTION="];
|
|
11448
|
+
var CAMPAIGN_EQUALITY = /\bCAMPAIGN\.(ID|NAME|RESOURCE_NAME)\s*=/;
|
|
11449
|
+
function whereClause(upperQuery) {
|
|
11450
|
+
const whereIdx = upperQuery.indexOf(" WHERE ");
|
|
11451
|
+
if (whereIdx === -1) return "";
|
|
11452
|
+
let rest = upperQuery.slice(whereIdx + 7);
|
|
11453
|
+
for (const trailing of [" ORDER BY ", " GROUP BY ", " LIMIT ", " PARAMETERS "]) {
|
|
11454
|
+
const idx = rest.indexOf(trailing);
|
|
11455
|
+
if (idx !== -1) rest = rest.slice(0, idx);
|
|
11456
|
+
}
|
|
11457
|
+
return rest;
|
|
11458
|
+
}
|
|
11459
|
+
function conversionMixWarnings(finalQuery) {
|
|
11460
|
+
const upper2 = ` ${finalQuery.toUpperCase().replace(/\s+/g, " ")} `;
|
|
11461
|
+
const readsConversions = CONVERSION_METRICS.some((metric) => upper2.includes(metric));
|
|
11462
|
+
if (!readsConversions) return [];
|
|
11463
|
+
if (CONVERSION_SEGMENTS.some((segment) => upper2.includes(segment))) return [];
|
|
11464
|
+
if (CAMPAIGN_EQUALITY.test(whereClause(upper2))) return [];
|
|
11465
|
+
return [
|
|
11466
|
+
{
|
|
11467
|
+
code: "CONVERSION_MIX",
|
|
11468
|
+
message: "MIXED CONVERSION ACTIONS \u2014 this read covers more than one campaign, and a campaign's conversion figures count whatever conversion actions are primary for it. Those differ campaign by campaign (a webinar registration, an info request, a CRM-stage SQL), so a total across these rows adds different events and a conversion rate from one campaign is not comparable with another's. Before totalling or comparing, re-run segmented by segments.conversion_action_name and check the campaigns you are putting side by side count the same action. If they do not, compare only within one action \u2014 and carry that caveat into anything you write for the user, not just the analysis."
|
|
11469
|
+
}
|
|
11470
|
+
];
|
|
11471
|
+
}
|
|
11472
|
+
|
|
11434
11473
|
// src/commands/ads/google/keyword-serving.ts
|
|
11435
11474
|
var BELOW_FIRST_PAGE_BID = "AD_GROUP_CRITERION_BELOW_FIRST_PAGE_BID";
|
|
11436
11475
|
var ACTIONABLE_REASONS = {
|
|
@@ -11584,6 +11623,14 @@ var PRESETS = [
|
|
|
11584
11623
|
defaultLimit: 200,
|
|
11585
11624
|
statusFields: ["campaign.status"]
|
|
11586
11625
|
},
|
|
11626
|
+
{
|
|
11627
|
+
name: "conversions-by-action",
|
|
11628
|
+
description: "Which conversion action each campaign's conversions actually are \u2014 the read that says whether two campaigns' conversion rates are measuring the same event",
|
|
11629
|
+
gaqlTemplate: `SELECT campaign.id, campaign.name, segments.conversion_action_name, segments.conversion_action_category, metrics.conversions, metrics.all_conversions FROM campaign WHERE segments.date DURING {dateRange}{statusScope} ORDER BY metrics.conversions DESC LIMIT {limit}`,
|
|
11630
|
+
defaultDateRange: "LAST_30_DAYS",
|
|
11631
|
+
defaultLimit: 200,
|
|
11632
|
+
statusFields: ["campaign.status"]
|
|
11633
|
+
},
|
|
11587
11634
|
{
|
|
11588
11635
|
name: "keyword-analysis",
|
|
11589
11636
|
description: "Keyword performance with match type and quality",
|
|
@@ -11705,7 +11752,7 @@ var RUNG_PIN_FIELDS = {
|
|
|
11705
11752
|
"campaign_asset.status": ["campaign_asset.resource_name"],
|
|
11706
11753
|
"ad_group_asset.status": ["ad_group_asset.resource_name"]
|
|
11707
11754
|
};
|
|
11708
|
-
function
|
|
11755
|
+
function whereClause2(query) {
|
|
11709
11756
|
const match = query.match(/\bWHERE\b([\s\S]*)$/i);
|
|
11710
11757
|
if (!match?.[1]) return "";
|
|
11711
11758
|
let rest = match[1];
|
|
@@ -11725,7 +11772,7 @@ function analyzeServingChain(query) {
|
|
|
11725
11772
|
const resource = fromResource(query);
|
|
11726
11773
|
const chain = resource ? SERVING_CHAINS[resource] : void 0;
|
|
11727
11774
|
if (!chain) return null;
|
|
11728
|
-
const where =
|
|
11775
|
+
const where = whereClause2(query);
|
|
11729
11776
|
if (/ad_group_criterion\.negative\s*=\s*TRUE/i.test(where)) return null;
|
|
11730
11777
|
const unscoped = chain.filter((rung) => {
|
|
11731
11778
|
if (mentions(where, rung)) return false;
|
|
@@ -11965,7 +12012,7 @@ registerSchema({
|
|
|
11965
12012
|
},
|
|
11966
12013
|
preset: {
|
|
11967
12014
|
type: "string",
|
|
11968
|
-
description: "Named query preset. Reads (campaign-performance, keyword-analysis, keyword-serving, positive-keywords, negative-keywords, negative-keyword-lists, negative-list-attachments, search-terms, ad-copy-performance, asset-performance, shopping-products, account-summary). Presets return only actually-serving entities (enabled campaign AND ad group AND ad) by default \u2014 pass --include-paused to include paused ones.",
|
|
12015
|
+
description: "Named query preset. Reads (campaign-performance, conversions-by-action, keyword-analysis, keyword-serving, positive-keywords, negative-keywords, negative-keyword-lists, negative-list-attachments, search-terms, ad-copy-performance, asset-performance, shopping-products, account-summary). Presets return only actually-serving entities (enabled campaign AND ad group AND ad) by default \u2014 pass --include-paused to include paused ones.",
|
|
11969
12016
|
required: false
|
|
11970
12017
|
},
|
|
11971
12018
|
"date-range": {
|
|
@@ -12203,7 +12250,7 @@ var SERVING_HIERARCHY_RESOURCES = [
|
|
|
12203
12250
|
"shopping_performance_view",
|
|
12204
12251
|
"asset_group_asset"
|
|
12205
12252
|
].filter((resource) => !(resource in SERVING_CHAINS));
|
|
12206
|
-
function
|
|
12253
|
+
function whereClause3(upperQuery) {
|
|
12207
12254
|
const whereIdx = upperQuery.indexOf(" WHERE ");
|
|
12208
12255
|
if (whereIdx === -1) return "";
|
|
12209
12256
|
let rest = upperQuery.slice(whereIdx + 7);
|
|
@@ -12227,7 +12274,7 @@ function servingScopeWarnings(args, finalQuery) {
|
|
|
12227
12274
|
const fromMatch = upper2.match(/\bFROM\s+([A-Z_]+)/);
|
|
12228
12275
|
const resource = fromMatch?.[1]?.toLowerCase();
|
|
12229
12276
|
const hierarchical = resource ? SERVING_HIERARCHY_RESOURCES.includes(resource) : false;
|
|
12230
|
-
const filtersStatus =
|
|
12277
|
+
const filtersStatus = whereClause3(upper2).includes(".STATUS");
|
|
12231
12278
|
if (hierarchical && !filtersStatus) {
|
|
12232
12279
|
return [
|
|
12233
12280
|
{
|
|
@@ -12329,6 +12376,7 @@ async function executeQuery(finalQuery, args, customerId, limit, warnings, useCa
|
|
|
12329
12376
|
...warnings,
|
|
12330
12377
|
...renderKeywordLimits(keywordLimits),
|
|
12331
12378
|
...missingFieldWarningsFor(finalQuery, observedFields, totalRowsWritten),
|
|
12379
|
+
...conversionMixWarnings(finalQuery),
|
|
12332
12380
|
...truncation
|
|
12333
12381
|
];
|
|
12334
12382
|
emitToStderr(warningLines(fileWarnings, "csv"));
|
|
@@ -12350,7 +12398,12 @@ async function executeQuery(finalQuery, args, customerId, limit, warnings, useCa
|
|
|
12350
12398
|
outputResults(
|
|
12351
12399
|
allRows,
|
|
12352
12400
|
getFieldDescriptions(fields),
|
|
12353
|
-
[
|
|
12401
|
+
[
|
|
12402
|
+
...warnings,
|
|
12403
|
+
...missingFieldWarnings(finalQuery, allRows),
|
|
12404
|
+
...keywordServingWarnings(allRows, servingOptions),
|
|
12405
|
+
...conversionMixWarnings(finalQuery)
|
|
12406
|
+
],
|
|
12354
12407
|
args,
|
|
12355
12408
|
false,
|
|
12356
12409
|
{
|
|
@@ -12466,7 +12519,8 @@ Examples:
|
|
|
12466
12519
|
[
|
|
12467
12520
|
...warnings,
|
|
12468
12521
|
...missingFieldWarnings(finalQuery, cached.data),
|
|
12469
|
-
...keywordServingWarnings(cached.data, keywordServingOptions(finalQuery))
|
|
12522
|
+
...keywordServingWarnings(cached.data, keywordServingOptions(finalQuery)),
|
|
12523
|
+
...conversionMixWarnings(finalQuery)
|
|
12470
12524
|
],
|
|
12471
12525
|
args,
|
|
12472
12526
|
true,
|
|
@@ -13273,10 +13327,52 @@ function mappingCommandFor(platform, proposed) {
|
|
|
13273
13327
|
|
|
13274
13328
|
// ../api/src/analytics/websiteTag.ts
|
|
13275
13329
|
import { z as z27 } from "zod";
|
|
13330
|
+
var websiteTagRequestSchema = z27.object({
|
|
13331
|
+
measure: z27.enum(["campaigns", "outcomes", "all"]).optional()
|
|
13332
|
+
});
|
|
13333
|
+
var analyticsSitesRequestSchema = z27.object({
|
|
13334
|
+
add: z27.array(z27.string()).optional(),
|
|
13335
|
+
remove: z27.array(z27.string()).optional()
|
|
13336
|
+
});
|
|
13337
|
+
var analyticsSitesResponseSchema = z27.object({
|
|
13338
|
+
/** Every declared website after the change, which is the answer to a bare read. */
|
|
13339
|
+
origins: z27.array(z27.string()),
|
|
13340
|
+
/** Normalized and actually new — an entry already present is reported in neither list. */
|
|
13341
|
+
added: z27.array(z27.string()),
|
|
13342
|
+
removed: z27.array(z27.string()),
|
|
13343
|
+
/** Still-undeclared domains this company owns. See {@link suggestedPixelOrigins}. */
|
|
13344
|
+
suggestedOrigins: z27.array(z27.string())
|
|
13345
|
+
});
|
|
13276
13346
|
var websiteTagResponseSchema = z27.object({
|
|
13277
13347
|
host: z27.string(),
|
|
13278
13348
|
siteKey: z27.string(),
|
|
13279
13349
|
origins: z27.array(z27.string()),
|
|
13350
|
+
/** Echoed back, so a caller that asked for nothing can see what it got. */
|
|
13351
|
+
measure: z27.enum(["campaigns", "outcomes", "all"]),
|
|
13352
|
+
/**
|
|
13353
|
+
* Whether `campaigns` scope can recognise anybody here — see
|
|
13354
|
+
* {@link campaignsScopeCanMatch}. `false` means the default measures nobody
|
|
13355
|
+
* on this company's install, however correctly the tag is pasted.
|
|
13356
|
+
*/
|
|
13357
|
+
campaignsCanMatch: z27.boolean(),
|
|
13358
|
+
/**
|
|
13359
|
+
* Whether `host` is one of the company's own domains rather than Baker's.
|
|
13360
|
+
*
|
|
13361
|
+
* Worth answering because it is the difference between an install a content
|
|
13362
|
+
* blocker removes and one it has no list entry for — the pixel takes its
|
|
13363
|
+
* collect endpoint from its own `src`, so this decides the fate of the event
|
|
13364
|
+
* stream and not just the script load. See {@link pixelHost}.
|
|
13365
|
+
*/
|
|
13366
|
+
firstParty: z27.boolean(),
|
|
13367
|
+
/**
|
|
13368
|
+
* Websites this company plainly owns and has not declared — the registrable
|
|
13369
|
+
* domains of its own landing domains. See {@link suggestedPixelOrigins}.
|
|
13370
|
+
*
|
|
13371
|
+
* A suggestion for somebody to accept, never a scope already granted: nothing
|
|
13372
|
+
* in Baker reads this list, and `baker analytics sites --add` is what acts on
|
|
13373
|
+
* it.
|
|
13374
|
+
*/
|
|
13375
|
+
suggestedOrigins: z27.array(z27.string()),
|
|
13280
13376
|
tag: z27.string(),
|
|
13281
13377
|
brief: z27.string()
|
|
13282
13378
|
});
|
|
@@ -27901,7 +27997,7 @@ one marked "Already counted", so a retry is visible without being counted.
|
|
|
27901
27997
|
|
|
27902
27998
|
The same holds **across connections**, which is the reason to use it. If the page also
|
|
27903
27999
|
reports this outcome with Baker's website tag, pass one string as \`event_id\` here and as
|
|
27904
|
-
\`eventId\` in \`window.baker.
|
|
28000
|
+
\`eventId\` in \`window.baker.track(name, { eventId })\`: the browser event and the
|
|
27905
28001
|
server event are then one event and count once, whichever arrives first and whether or not
|
|
27906
28002
|
the other ever does. Report an important outcome from both \u2014 a browser can be blocked, a
|
|
27907
28003
|
server cannot \u2014 and always pair them with an id. Send no id and the two are two conversions,
|
|
@@ -28990,6 +29086,73 @@ function formatHint(data, format) {
|
|
|
28990
29086
|
];
|
|
28991
29087
|
}
|
|
28992
29088
|
|
|
29089
|
+
// src/commands/analytics/websiteTagHints.ts
|
|
29090
|
+
function asTyped(origin) {
|
|
29091
|
+
return origin.replace(/^https?:\/\//, "");
|
|
29092
|
+
}
|
|
29093
|
+
function suggestionHint(suggested) {
|
|
29094
|
+
if (suggested.length === 0) return [];
|
|
29095
|
+
const commands = suggested.map((origin) => `baker analytics sites --add ${asTyped(origin)}`).join(" \xB7 ");
|
|
29096
|
+
return [
|
|
29097
|
+
`Baker publishes landing pages on ${suggested.map(asTyped).join(", ")} for this company, and no declared website covers ${suggested.length === 1 ? "it" : "them"}. Declaring the domain covers every subdomain of it \u2014 the marketing site, the shop, the booking page, and the ones that do not exist yet: ${commands}`
|
|
29098
|
+
];
|
|
29099
|
+
}
|
|
29100
|
+
function websiteTagHints(response) {
|
|
29101
|
+
const hints2 = [];
|
|
29102
|
+
if (response.origins.length === 0) {
|
|
29103
|
+
hints2.push(
|
|
29104
|
+
"NO WEBSITE DECLARED \u2014 this tag will be ignored everywhere it is installed, silently, with no failing request to find. Declare the site before installing anything: baker analytics sites --add <the client's domain>. It takes effect at once and needs no republish."
|
|
29105
|
+
);
|
|
29106
|
+
}
|
|
29107
|
+
hints2.push(...suggestionHint(response.suggestedOrigins));
|
|
29108
|
+
if (response.firstParty) {
|
|
29109
|
+
hints2.push(
|
|
29110
|
+
`This tag loads from ${response.host} \u2014 the client's own domain, not Baker's \u2014 so the script and every event it sends travel first-party, which a content blocker has no list entry for. The trade is a dependency: if that domain is ever removed from Baker, this tag 404s and measurement stops. Re-run this command after any domain change.`
|
|
29111
|
+
);
|
|
29112
|
+
}
|
|
29113
|
+
if (response.measure === "all") {
|
|
29114
|
+
hints2.push(
|
|
29115
|
+
"This tag measures the client's WHOLE WEBSITE \u2014 every visitor and every page view, not only the people your campaigns brought. Their reports will describe their business rather than their campaigns, and outcomes their site produces from organic, email and direct traffic will land in their conversion numbers. Only correct if somebody asked for that; otherwise re-run without --measure."
|
|
29116
|
+
);
|
|
29117
|
+
}
|
|
29118
|
+
if (response.measure === "campaigns" && !response.campaignsCanMatch) {
|
|
29119
|
+
hints2.push(
|
|
29120
|
+
"THIS TAG WILL MEASURE NOBODY \u2014 and will look installed. `campaigns` scope recognises a visitor by a first-party cookie, and a browser does not carry one between two different registrable domains. This company's landings are not on a subdomain of the site this tag goes on, so no visitor can ever match. Two fixes: put the landings on a subdomain of the client's own domain (a custom domain in Baker), or install with --measure outcomes, which measures no browsing and reports the outcomes the site tells Baker about."
|
|
29121
|
+
);
|
|
29122
|
+
} else if (response.measure === "campaigns") {
|
|
29123
|
+
hints2.push(
|
|
29124
|
+
"This tag measures only visitors who have been on one of this company's Baker landing pages \u2014 their whole journey through the site, to the outcome. Anybody else is not measured at all. Use --measure all only if the client asked for their whole website measured."
|
|
29125
|
+
);
|
|
29126
|
+
}
|
|
29127
|
+
hints2.push(
|
|
29128
|
+
'Reporting an outcome is not the same as counting it. After a `window.baker.track("name")` call is live, name it with: baker analytics conversions --event page:<name> --name "<what to call it>". That applies immediately and counts everything already collected.'
|
|
29129
|
+
);
|
|
29130
|
+
return hints2;
|
|
29131
|
+
}
|
|
29132
|
+
function analyticsSitesHints(response) {
|
|
29133
|
+
const hints2 = [];
|
|
29134
|
+
if (response.origins.length === 0) {
|
|
29135
|
+
hints2.push(
|
|
29136
|
+
"No website is declared, so this company's measurement tag is ignored wherever it is installed \u2014 silently, with no failing request to find. Add one with: baker analytics sites --add <the client's domain>"
|
|
29137
|
+
);
|
|
29138
|
+
}
|
|
29139
|
+
hints2.push(...suggestionHint(response.suggestedOrigins));
|
|
29140
|
+
if (response.added.length > 0) {
|
|
29141
|
+
hints2.push(
|
|
29142
|
+
`Live now. A tag already installed on ${response.added.map(asTyped).join(", ")} starts being accepted immediately \u2014 there is no new tag to paste and nothing to publish. Only events sent from here on are kept; the ones refused before this were never stored and cannot be recovered.`
|
|
29143
|
+
);
|
|
29144
|
+
}
|
|
29145
|
+
if (response.removed.length > 0) {
|
|
29146
|
+
hints2.push(
|
|
29147
|
+
`Measurement from ${response.removed.map(asTyped).join(", ")} will stop at once, with no error on that site \u2014 a tag still installed there goes quiet. Everything already collected from it is kept.`
|
|
29148
|
+
);
|
|
29149
|
+
}
|
|
29150
|
+
hints2.push(
|
|
29151
|
+
"One entry covers everything under it: example.com answers for www., go. and shop.eu. It never widens upward, so declaring go.example.com leaves example.com uncovered \u2014 declare the domain, not the subdomain you are installing on today."
|
|
29152
|
+
);
|
|
29153
|
+
return hints2;
|
|
29154
|
+
}
|
|
29155
|
+
|
|
28993
29156
|
// src/commands/analytics/index.ts
|
|
28994
29157
|
var SHARED_ARGS = {
|
|
28995
29158
|
days: { type: "string", description: "Lookback window in days (default: 30)", required: false },
|
|
@@ -29718,7 +29881,13 @@ var websiteTagCommand = (() => {
|
|
|
29718
29881
|
registerSchema({
|
|
29719
29882
|
command: "analytics.website-tag",
|
|
29720
29883
|
description: "The measurement tag for a website Baker does not publish, with this company's key in it",
|
|
29721
|
-
args: {
|
|
29884
|
+
args: {
|
|
29885
|
+
measure: {
|
|
29886
|
+
type: "string",
|
|
29887
|
+
required: false,
|
|
29888
|
+
description: 'Whose activity to measure: "campaigns" (default), "outcomes" or "all"'
|
|
29889
|
+
}
|
|
29890
|
+
}
|
|
29722
29891
|
});
|
|
29723
29892
|
return defineCommand95({
|
|
29724
29893
|
meta: {
|
|
@@ -29727,18 +29896,83 @@ var websiteTagCommand = (() => {
|
|
|
29727
29896
|
|
|
29728
29897
|
Reach for it whenever measurement has to reach a page Baker did not build. What it returns is real and complete: the tag, the websites the key is accepted from, and the rules for the hidden form fields, the identify call and the conversion call.
|
|
29729
29898
|
|
|
29730
|
-
|
|
29899
|
+
Three things worth knowing before you install it:
|
|
29731
29900
|
the origins list \u2014 the key is IGNORED from any origin not on it, silently. An empty list means the tag will measure nothing.
|
|
29732
29901
|
Tag Manager is usually the way in \u2014 a Custom HTML tag on All Pages. If the client has a container connected, you can stage that yourself (baker tag-manager).
|
|
29902
|
+
--measure decides the BILL \u2014 the tag goes on every page whatever you do; this decides whose activity is measured. Leave it alone unless the client asked for their whole website.
|
|
29903
|
+
|
|
29904
|
+
--measure campaigns (default) people your Baker campaigns brought to the site. Their whole journey, through to the outcome. Nobody else is measured at all.
|
|
29905
|
+
--measure outcomes nobody's browsing. Only what the site reports with window.baker.track(...), and forms it wired to Baker.
|
|
29906
|
+
--measure all every visitor and every page view of the whole website. Their reports then describe their business, not their campaigns, and outcomes from organic, email and direct land in their conversion numbers. A decision, never a default.
|
|
29733
29907
|
|
|
29734
29908
|
Examples:
|
|
29735
29909
|
baker analytics website-tag \u2014 the tag, the origins and the install brief
|
|
29910
|
+
baker analytics website-tag --measure all \u2014 measure their whole website, because they asked for that
|
|
29736
29911
|
baker analytics events --origin site \u2014 after it is live, what that website is reporting`
|
|
29737
29912
|
},
|
|
29738
|
-
args: {
|
|
29739
|
-
|
|
29913
|
+
args: {
|
|
29914
|
+
measure: {
|
|
29915
|
+
type: "string",
|
|
29916
|
+
description: 'Whose activity to measure: "campaigns" (default), "outcomes" or "all". Widening is a decision \u2014 see above.'
|
|
29917
|
+
}
|
|
29918
|
+
},
|
|
29919
|
+
run: async (ctx) => {
|
|
29740
29920
|
try {
|
|
29741
|
-
|
|
29921
|
+
const measure = typeof ctx.args.measure === "string" ? ctx.args.measure : void 0;
|
|
29922
|
+
const envelope = await apiPost(
|
|
29923
|
+
"/api/analytics/website-tag",
|
|
29924
|
+
measure ? { measure } : {}
|
|
29925
|
+
);
|
|
29926
|
+
writeJsonEnvelope({ ...envelope, hints: websiteTagHints(envelope.data) });
|
|
29927
|
+
} catch (err) {
|
|
29928
|
+
handleError(err);
|
|
29929
|
+
}
|
|
29930
|
+
}
|
|
29931
|
+
});
|
|
29932
|
+
})();
|
|
29933
|
+
var sitesCommand = (() => {
|
|
29934
|
+
registerSchema({
|
|
29935
|
+
command: "analytics.sites",
|
|
29936
|
+
description: "The websites this company's measurement tag is accepted from, and adding or removing one",
|
|
29937
|
+
args: {
|
|
29938
|
+
add: { type: "string", required: false, description: "Website address to accept measurement from" },
|
|
29939
|
+
remove: { type: "string", required: false, description: "Website address to stop accepting measurement from" }
|
|
29940
|
+
}
|
|
29941
|
+
});
|
|
29942
|
+
return defineCommand95({
|
|
29943
|
+
meta: {
|
|
29944
|
+
name: "sites",
|
|
29945
|
+
description: `Which websites this company's measurement tag is accepted from \u2014 and the command that changes it.
|
|
29946
|
+
|
|
29947
|
+
The site key ships in the source of every page it is pasted on, so this list is the whole of what stops a stranger who read it appending forged conversions to these numbers. A tag on a website that is NOT on this list is ignored, silently, with no failing request anywhere. That is the single most common reason an install measures nothing.
|
|
29948
|
+
|
|
29949
|
+
One entry covers the estate under it: example.com answers for www.example.com, go.example.com and shop.eu.example.com. It never widens upward \u2014 declaring go.example.com does NOT cover example.com \u2014 so declare the DOMAIN, not the subdomain you happen to be installing on today.
|
|
29950
|
+
|
|
29951
|
+
Start here:
|
|
29952
|
+
baker analytics sites \u2014 what is declared now, and what is worth adding
|
|
29953
|
+
baker analytics sites --add example.com \u2014 accept measurement from that site and everything under it
|
|
29954
|
+
baker analytics sites --remove old-site.com
|
|
29955
|
+
|
|
29956
|
+
Examples:
|
|
29957
|
+
baker analytics website-tag \u2014 get the tag; if its origins are empty, come here first
|
|
29958
|
+
baker analytics sites --add foodforjoe.es \u2014 one entry covering www., go. and shop.`
|
|
29959
|
+
},
|
|
29960
|
+
args: {
|
|
29961
|
+
add: {
|
|
29962
|
+
type: "string",
|
|
29963
|
+
description: "Website address to accept measurement from, e.g. example.com \u2014 covers every subdomain of it. Repeat for several"
|
|
29964
|
+
},
|
|
29965
|
+
remove: { type: "string", description: "Website address to stop accepting measurement from" }
|
|
29966
|
+
},
|
|
29967
|
+
run: async (ctx) => {
|
|
29968
|
+
try {
|
|
29969
|
+
const raw = ctx.rawArgs;
|
|
29970
|
+
const body = {
|
|
29971
|
+
add: repeatedValues(raw, "add", ctx.args.add),
|
|
29972
|
+
remove: repeatedValues(raw, "remove", ctx.args.remove)
|
|
29973
|
+
};
|
|
29974
|
+
const envelope = await apiPost("/api/analytics/sites", body);
|
|
29975
|
+
writeJsonEnvelope({ ...envelope, hints: analyticsSitesHints(envelope.data) });
|
|
29742
29976
|
} catch (err) {
|
|
29743
29977
|
handleError(err);
|
|
29744
29978
|
}
|
|
@@ -29815,6 +30049,7 @@ Full guide: __tooling__/docs/tools/baker/analytics.md`
|
|
|
29815
30049
|
arrivals: arrivalsCommand,
|
|
29816
30050
|
"sending-events": sendingEventsCommand,
|
|
29817
30051
|
"website-tag": websiteTagCommand,
|
|
30052
|
+
sites: sitesCommand,
|
|
29818
30053
|
presets: presetsCommand
|
|
29819
30054
|
}
|
|
29820
30055
|
});
|
|
@@ -44110,7 +44345,7 @@ function siteHints(sites) {
|
|
|
44110
44345
|
resources: sites.map((site) => ({ id: site.siteUrl, label: site.permissionLevel }))
|
|
44111
44346
|
});
|
|
44112
44347
|
}
|
|
44113
|
-
var
|
|
44348
|
+
var sitesCommand2 = defineCommand134({
|
|
44114
44349
|
meta: {
|
|
44115
44350
|
name: "sites",
|
|
44116
44351
|
description: `List verified Search Console sites.
|
|
@@ -44175,7 +44410,7 @@ Examples:
|
|
|
44175
44410
|
Full guide: __tooling__/docs/tools/baker/gsc.md`
|
|
44176
44411
|
},
|
|
44177
44412
|
subCommands: {
|
|
44178
|
-
sites:
|
|
44413
|
+
sites: sitesCommand2,
|
|
44179
44414
|
query: queryCommand3,
|
|
44180
44415
|
sitemaps: sitemapsCommand
|
|
44181
44416
|
}
|