@koda-sl/baker-cli 0.280.0 → 0.281.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +632 -249
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10482,14 +10482,14 @@ function collectionLine(collection) {
|
|
|
10482
10482
|
}
|
|
10483
10483
|
function accountsInTree(nodes) {
|
|
10484
10484
|
const accounts = /* @__PURE__ */ new Set();
|
|
10485
|
-
const
|
|
10485
|
+
const walk2 = (node) => {
|
|
10486
10486
|
accounts.add(node.customerId);
|
|
10487
10487
|
for (const child of node.children) {
|
|
10488
|
-
|
|
10488
|
+
walk2(child);
|
|
10489
10489
|
}
|
|
10490
10490
|
};
|
|
10491
10491
|
for (const node of nodes) {
|
|
10492
|
-
|
|
10492
|
+
walk2(node);
|
|
10493
10493
|
}
|
|
10494
10494
|
return accounts;
|
|
10495
10495
|
}
|
|
@@ -12772,6 +12772,25 @@ var CAPTURED_WITHOUT_ROLE = [
|
|
|
12772
12772
|
"targetid"
|
|
12773
12773
|
].sort();
|
|
12774
12774
|
|
|
12775
|
+
// ../api/src/analytics/conversionNames.ts
|
|
12776
|
+
function normalizeConversionName(name) {
|
|
12777
|
+
return name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word) => word !== "").map((word) => word.length > 3 && word.endsWith("s") ? word.slice(0, -1) : word).join(" ");
|
|
12778
|
+
}
|
|
12779
|
+
function findNameCollision(name, existing) {
|
|
12780
|
+
const normalized = normalizeConversionName(name);
|
|
12781
|
+
return existing.find((candidate) => candidate !== name && normalizeConversionName(candidate) === normalized) ?? null;
|
|
12782
|
+
}
|
|
12783
|
+
var STANDARD_CONVERSION_NAMES = [
|
|
12784
|
+
{ name: "Lead", meaning: "Somebody handed over contact details" },
|
|
12785
|
+
{ name: "Booked a call", meaning: "A meeting or demo was scheduled" },
|
|
12786
|
+
{ name: "Quote requested", meaning: "Somebody asked what it would cost" },
|
|
12787
|
+
{ name: "Purchase", meaning: "Money changed hands" },
|
|
12788
|
+
{ name: "Signed up", meaning: "An account or trial was created" },
|
|
12789
|
+
{ name: "Subscribed", meaning: "Somebody opted in to hear from them again" },
|
|
12790
|
+
{ name: "Downloaded", meaning: "A guide, price list or asset was taken" },
|
|
12791
|
+
{ name: "Contacted us", meaning: "A call, WhatsApp or email started from the page" }
|
|
12792
|
+
];
|
|
12793
|
+
|
|
12775
12794
|
// ../api/src/analytics/conversions.ts
|
|
12776
12795
|
var CONVERSION_COUNT_MODES = ["once_per_visit", "every_time"];
|
|
12777
12796
|
var PRIMARY_CONVERSION_TRIGGER = {
|
|
@@ -12793,6 +12812,9 @@ var MAX_CONVERSION_NAME = 80;
|
|
|
12793
12812
|
|
|
12794
12813
|
// ../api/src/analytics/eventKey.ts
|
|
12795
12814
|
var SEP = ":";
|
|
12815
|
+
function formEventKey(flowSlug, nodeId, triggerId) {
|
|
12816
|
+
return ["form", flowSlug, nodeId, triggerId].join(SEP);
|
|
12817
|
+
}
|
|
12796
12818
|
function parseEventKey(key) {
|
|
12797
12819
|
const cut = key.indexOf(SEP);
|
|
12798
12820
|
if (cut <= 0) return null;
|
|
@@ -20318,14 +20340,14 @@ function collectionLine2(collection) {
|
|
|
20318
20340
|
}
|
|
20319
20341
|
function accountsInTree2(nodes) {
|
|
20320
20342
|
const accounts = /* @__PURE__ */ new Set();
|
|
20321
|
-
const
|
|
20343
|
+
const walk2 = (node) => {
|
|
20322
20344
|
accounts.add(node.accountId);
|
|
20323
20345
|
for (const child of node.children) {
|
|
20324
|
-
|
|
20346
|
+
walk2(child);
|
|
20325
20347
|
}
|
|
20326
20348
|
};
|
|
20327
20349
|
for (const node of nodes) {
|
|
20328
|
-
|
|
20350
|
+
walk2(node);
|
|
20329
20351
|
}
|
|
20330
20352
|
return accounts;
|
|
20331
20353
|
}
|
|
@@ -25348,14 +25370,14 @@ function collectionLine3(collection) {
|
|
|
25348
25370
|
}
|
|
25349
25371
|
function accountsInTree3(nodes) {
|
|
25350
25372
|
const accounts = /* @__PURE__ */ new Set();
|
|
25351
|
-
const
|
|
25373
|
+
const walk2 = (node) => {
|
|
25352
25374
|
accounts.add(node.accountId);
|
|
25353
25375
|
for (const child of node.children) {
|
|
25354
|
-
|
|
25376
|
+
walk2(child);
|
|
25355
25377
|
}
|
|
25356
25378
|
};
|
|
25357
25379
|
for (const node of nodes) {
|
|
25358
|
-
|
|
25380
|
+
walk2(node);
|
|
25359
25381
|
}
|
|
25360
25382
|
return accounts;
|
|
25361
25383
|
}
|
|
@@ -27170,6 +27192,9 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
|
|
|
27170
27192
|
}
|
|
27171
27193
|
});
|
|
27172
27194
|
|
|
27195
|
+
// src/commands/analytics/index.ts
|
|
27196
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
27197
|
+
|
|
27173
27198
|
// ../api/src/event-ingest/arrivals.ts
|
|
27174
27199
|
import { z as z31 } from "zod";
|
|
27175
27200
|
|
|
@@ -27983,50 +28008,325 @@ var ingestEnvelopeSchema = z32.object({ ok: z32.literal(true), data: ingestRespo
|
|
|
27983
28008
|
// src/commands/analytics/index.ts
|
|
27984
28009
|
import { defineCommand as defineCommand95 } from "citty";
|
|
27985
28010
|
|
|
28011
|
+
// src/commands/flows/shared.ts
|
|
28012
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
28013
|
+
import { join as join2 } from "path";
|
|
28014
|
+
var FLOWS_DIR = "src/lib/flow-engine/flows";
|
|
28015
|
+
function flowsDir() {
|
|
28016
|
+
let dir = process.cwd();
|
|
28017
|
+
for (let i = 0; i < 6; i++) {
|
|
28018
|
+
const candidate = join2(dir, FLOWS_DIR);
|
|
28019
|
+
if (existsSync3(candidate)) {
|
|
28020
|
+
return candidate;
|
|
28021
|
+
}
|
|
28022
|
+
const parent = join2(dir, "..");
|
|
28023
|
+
if (parent === dir) {
|
|
28024
|
+
break;
|
|
28025
|
+
}
|
|
28026
|
+
dir = parent;
|
|
28027
|
+
}
|
|
28028
|
+
return join2(process.cwd(), FLOWS_DIR);
|
|
28029
|
+
}
|
|
28030
|
+
function failLocal(message) {
|
|
28031
|
+
writeJson({ ok: false, error: { code: "NOT_FOUND", message } });
|
|
28032
|
+
process.exit(1);
|
|
28033
|
+
}
|
|
28034
|
+
function listFlowSlugs() {
|
|
28035
|
+
const dir = flowsDir();
|
|
28036
|
+
if (!existsSync3(dir)) {
|
|
28037
|
+
return [];
|
|
28038
|
+
}
|
|
28039
|
+
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
28040
|
+
}
|
|
28041
|
+
function flowDataPath(slug) {
|
|
28042
|
+
return join2(flowsDir(), slug, "_data.json");
|
|
28043
|
+
}
|
|
28044
|
+
function writeFlowTree(slug, tree) {
|
|
28045
|
+
writeFileSync3(flowDataPath(slug), `${JSON.stringify(tree, null, 2)}
|
|
28046
|
+
`, "utf-8");
|
|
28047
|
+
}
|
|
28048
|
+
function walkNodes(node, acc = []) {
|
|
28049
|
+
if (!node || typeof node !== "object") return acc;
|
|
28050
|
+
acc.push(node);
|
|
28051
|
+
if (Array.isArray(node.children)) {
|
|
28052
|
+
for (const child of node.children) walkNodes(child, acc);
|
|
28053
|
+
}
|
|
28054
|
+
return acc;
|
|
28055
|
+
}
|
|
28056
|
+
function collectSideEffects(tree) {
|
|
28057
|
+
return walkNodes(tree).flatMap(
|
|
28058
|
+
(node) => Array.isArray(node.sideEffects) ? node.sideEffects.filter((sideEffect) => Boolean(sideEffect) && typeof sideEffect === "object").map((sideEffect) => ({ node, sideEffect })) : []
|
|
28059
|
+
);
|
|
28060
|
+
}
|
|
28061
|
+
function flowExists(slug) {
|
|
28062
|
+
return existsSync3(join2(flowsDir(), slug, "_data.json"));
|
|
28063
|
+
}
|
|
28064
|
+
function readFlowTree(slug) {
|
|
28065
|
+
const path38 = join2(flowsDir(), slug, "_data.json");
|
|
28066
|
+
if (!existsSync3(path38)) {
|
|
28067
|
+
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
28068
|
+
}
|
|
28069
|
+
try {
|
|
28070
|
+
return JSON.parse(readFileSync11(path38, "utf-8"));
|
|
28071
|
+
} catch (error) {
|
|
28072
|
+
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
28073
|
+
}
|
|
28074
|
+
}
|
|
28075
|
+
function isConfigured(value) {
|
|
28076
|
+
return typeof value === "string" && value.length > 0;
|
|
28077
|
+
}
|
|
28078
|
+
function nodeMeta(record) {
|
|
28079
|
+
const nodeId = typeof record.id === "string" ? record.id : "";
|
|
28080
|
+
const nodeName = typeof record.name === "string" ? record.name : void 0;
|
|
28081
|
+
return { nodeId, ...nodeName ? { nodeName } : {} };
|
|
28082
|
+
}
|
|
28083
|
+
function widgetNodeStatus(record, meta) {
|
|
28084
|
+
const nodeData = record.nodeData;
|
|
28085
|
+
const nodeType = nodeData?.type;
|
|
28086
|
+
if (typeof nodeType !== "string" || !FLOW_RESOURCE_NODE_TYPES.includes(nodeType)) {
|
|
28087
|
+
return null;
|
|
28088
|
+
}
|
|
28089
|
+
const external = nodeData?.form?.external;
|
|
28090
|
+
const selected = external != null && typeof external === "object";
|
|
28091
|
+
const name = selected ? external.name : void 0;
|
|
28092
|
+
return {
|
|
28093
|
+
...meta,
|
|
28094
|
+
target: "node",
|
|
28095
|
+
kind: nodeType,
|
|
28096
|
+
status: selected ? `${nodeType} \u2014 selected${typeof name === "string" ? ` "${name}"` : ""}` : `${nodeType} \u2014 not selected`,
|
|
28097
|
+
needsInput: !selected
|
|
28098
|
+
};
|
|
28099
|
+
}
|
|
28100
|
+
function sideEffectNeedsInput(raw) {
|
|
28101
|
+
const effect = raw;
|
|
28102
|
+
const type = effect?.type;
|
|
28103
|
+
if (typeof type !== "string" || !FLOW_SECRET_SIDE_EFFECT_TYPES.includes(type)) {
|
|
28104
|
+
return false;
|
|
28105
|
+
}
|
|
28106
|
+
const configured = isConfigured(effect?.encryptedConfig);
|
|
28107
|
+
if (!isFlowOauthSideEffect(type)) return !configured;
|
|
28108
|
+
return !isConfigured(effect?.oauthProviderId) || !configured;
|
|
28109
|
+
}
|
|
28110
|
+
function sideEffectStatus(raw, meta) {
|
|
28111
|
+
const effect = raw;
|
|
28112
|
+
const type = effect?.type;
|
|
28113
|
+
if (typeof type !== "string" || !FLOW_SECRET_SIDE_EFFECT_TYPES.includes(type)) {
|
|
28114
|
+
return null;
|
|
28115
|
+
}
|
|
28116
|
+
const configured = isConfigured(effect?.encryptedConfig);
|
|
28117
|
+
const oauth = isFlowOauthSideEffect(type);
|
|
28118
|
+
const connected = isConfigured(effect?.oauthProviderId);
|
|
28119
|
+
const needs = FLOW_SECRET_FIELDS[type];
|
|
28120
|
+
const status = oauth ? `${type} \u2014 connection ${connected ? "[connected]" : "[needs connection]"}, config ${configured ? "[set]" : "[missing]"}` : configured ? `${type} \u2014 [configured]` : `${type} \u2014 [missing]${needs.length > 0 ? ` (needs: ${needs.join(", ")})` : ""}`;
|
|
28121
|
+
return {
|
|
28122
|
+
...meta,
|
|
28123
|
+
target: "sideEffect",
|
|
28124
|
+
...typeof effect?.id === "string" ? { sideEffectId: effect.id } : {},
|
|
28125
|
+
kind: type,
|
|
28126
|
+
status,
|
|
28127
|
+
needsInput: sideEffectNeedsInput(effect)
|
|
28128
|
+
};
|
|
28129
|
+
}
|
|
28130
|
+
function requestFlowInputCall(slug, status) {
|
|
28131
|
+
const args = status.target === "node" ? `{ target: "node", flowSlug: "${slug}", nodeId: "${status.nodeId}", nodeType: "${status.kind}" }` : `{ target: "sideEffect", flowSlug: "${slug}", nodeId: "${status.nodeId}", sideEffectId: "${status.sideEffectId}", sideEffectType: "${status.kind}" }`;
|
|
28132
|
+
return `request_flow_input with ${args}`;
|
|
28133
|
+
}
|
|
28134
|
+
function collectConfigStatus(node, acc) {
|
|
28135
|
+
if (!node || typeof node !== "object") {
|
|
28136
|
+
return;
|
|
28137
|
+
}
|
|
28138
|
+
const record = node;
|
|
28139
|
+
const meta = nodeMeta(record);
|
|
28140
|
+
const widget = widgetNodeStatus(record, meta);
|
|
28141
|
+
if (widget) {
|
|
28142
|
+
acc.push(widget);
|
|
28143
|
+
}
|
|
28144
|
+
if (Array.isArray(record.sideEffects)) {
|
|
28145
|
+
for (const raw of record.sideEffects) {
|
|
28146
|
+
const status = sideEffectStatus(raw, meta);
|
|
28147
|
+
if (status) {
|
|
28148
|
+
acc.push(status);
|
|
28149
|
+
}
|
|
28150
|
+
}
|
|
28151
|
+
}
|
|
28152
|
+
if (Array.isArray(record.children)) {
|
|
28153
|
+
for (const child of record.children) {
|
|
28154
|
+
collectConfigStatus(child, acc);
|
|
28155
|
+
}
|
|
28156
|
+
}
|
|
28157
|
+
}
|
|
28158
|
+
function redactTree(node) {
|
|
28159
|
+
if (Array.isArray(node)) {
|
|
28160
|
+
return node.map(redactTree);
|
|
28161
|
+
}
|
|
28162
|
+
if (!node || typeof node !== "object") {
|
|
28163
|
+
return node;
|
|
28164
|
+
}
|
|
28165
|
+
const out = {};
|
|
28166
|
+
for (const [key, value] of Object.entries(node)) {
|
|
28167
|
+
if (key === "encryptedConfig") {
|
|
28168
|
+
out[key] = isConfigured(value) ? "[configured]" : value;
|
|
28169
|
+
} else {
|
|
28170
|
+
out[key] = redactTree(value);
|
|
28171
|
+
}
|
|
28172
|
+
}
|
|
28173
|
+
return out;
|
|
28174
|
+
}
|
|
28175
|
+
function stepNameKey(name) {
|
|
28176
|
+
return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
28177
|
+
}
|
|
28178
|
+
function isUsableStepNameKey(key) {
|
|
28179
|
+
return /^[\p{ID_Start}$_][\p{ID_Continue}$]*$/u.test(key);
|
|
28180
|
+
}
|
|
28181
|
+
|
|
27986
28182
|
// src/commands/analytics/conversionArgs.ts
|
|
27987
28183
|
var ConversionArgsError = class extends Error {
|
|
27988
28184
|
};
|
|
28185
|
+
function allValues(flag, argv) {
|
|
28186
|
+
const values = [];
|
|
28187
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
28188
|
+
const arg = argv[i];
|
|
28189
|
+
if (arg === flag) {
|
|
28190
|
+
const next = argv[i + 1];
|
|
28191
|
+
if (next !== void 0 && !next.startsWith("--")) values.push(next);
|
|
28192
|
+
continue;
|
|
28193
|
+
}
|
|
28194
|
+
if (arg.startsWith(`${flag}=`)) values.push(arg.slice(flag.length + 1));
|
|
28195
|
+
}
|
|
28196
|
+
return values.map((value) => value.trim()).filter((value) => value !== "");
|
|
28197
|
+
}
|
|
27989
28198
|
function onlyOnce(value, flag, argv) {
|
|
27990
28199
|
if (value === void 0) return void 0;
|
|
27991
28200
|
const given = argv.filter((arg) => arg === flag || arg.startsWith(`${flag}=`)).length;
|
|
27992
28201
|
if (given > 1) {
|
|
27993
28202
|
throw new ConversionArgsError(
|
|
27994
|
-
`${flag} was given ${given} times and only the last would have been used.
|
|
28203
|
+
`${flag} was given ${given} times and only the last would have been used. It takes one value.`
|
|
27995
28204
|
);
|
|
27996
28205
|
}
|
|
27997
28206
|
const trimmed = String(value).trim();
|
|
27998
28207
|
return trimmed === "" ? void 0 : trimmed;
|
|
27999
28208
|
}
|
|
28000
|
-
function
|
|
28001
|
-
const
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
const
|
|
28005
|
-
|
|
28006
|
-
|
|
28209
|
+
function repeatable(flag, cittyValue, argv) {
|
|
28210
|
+
const fromArgv = allValues(flag, argv);
|
|
28211
|
+
if (fromArgv.length > 0) return fromArgv;
|
|
28212
|
+
if (cittyValue === void 0) return [];
|
|
28213
|
+
const trimmed = String(cittyValue).trim();
|
|
28214
|
+
return trimmed === "" ? [] : [trimmed];
|
|
28215
|
+
}
|
|
28216
|
+
function checkFlagShape(args) {
|
|
28217
|
+
if (args.eventKeys.length > 0 && args.name === void 0 && args.countMode === void 0) {
|
|
28218
|
+
throw new ConversionArgsError(
|
|
28219
|
+
"--event needs --name: a conversion nobody named is a row labelled with a UUID. (--event with only --count-mode changes how an event already counted is counted.)"
|
|
28220
|
+
);
|
|
28007
28221
|
}
|
|
28008
|
-
if (name !== void 0 &&
|
|
28222
|
+
if (args.name !== void 0 && args.eventKeys.length === 0 && args.renameTo === void 0) {
|
|
28009
28223
|
throw new ConversionArgsError("--name needs --event, naming which event to count.");
|
|
28010
28224
|
}
|
|
28011
|
-
if (
|
|
28225
|
+
if (args.renameFrom !== void 0 && args.renameTo === void 0) {
|
|
28226
|
+
throw new ConversionArgsError('--rename needs --to: `--rename "Lead form" --to "Lead"`.');
|
|
28227
|
+
}
|
|
28228
|
+
if (args.renameTo !== void 0 && args.renameFrom === void 0) {
|
|
28012
28229
|
throw new ConversionArgsError(
|
|
28013
|
-
|
|
28230
|
+
'--to needs --rename, naming which outcome to rename: `--rename "Lead form" --to "Lead"`.'
|
|
28014
28231
|
);
|
|
28015
28232
|
}
|
|
28233
|
+
}
|
|
28234
|
+
function checkEventKeys(eventKeys) {
|
|
28235
|
+
for (const eventKey of eventKeys) {
|
|
28236
|
+
if (parseEventKey(eventKey) === null) {
|
|
28237
|
+
throw new ConversionArgsError(
|
|
28238
|
+
`"${eventKey}" is not an event key. Run with --candidates (or --flow <slug> for one Form) and copy one; they look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or exit:<host>.`
|
|
28239
|
+
);
|
|
28240
|
+
}
|
|
28241
|
+
}
|
|
28242
|
+
}
|
|
28243
|
+
function readDays(raw) {
|
|
28244
|
+
if (raw === void 0) return void 0;
|
|
28245
|
+
const days = Number(raw);
|
|
28246
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
28247
|
+
throw new ConversionArgsError("--days must be a positive number of days.");
|
|
28248
|
+
}
|
|
28249
|
+
return days;
|
|
28250
|
+
}
|
|
28251
|
+
function parseConversionsArgs(raw, argv) {
|
|
28252
|
+
const countMode = onlyOnce(raw["count-mode"], "--count-mode", argv);
|
|
28016
28253
|
if (countMode !== void 0 && !CONVERSION_COUNT_MODES.includes(countMode)) {
|
|
28017
28254
|
throw new ConversionArgsError(`--count-mode must be one of ${CONVERSION_COUNT_MODES.join(", ")}.`);
|
|
28018
28255
|
}
|
|
28019
|
-
const
|
|
28020
|
-
|
|
28021
|
-
|
|
28256
|
+
const args = {
|
|
28257
|
+
eventKeys: repeatable("--event", raw.event, argv),
|
|
28258
|
+
removeKeys: repeatable("--remove", raw.remove, argv),
|
|
28259
|
+
name: onlyOnce(raw.name, "--name", argv),
|
|
28260
|
+
countMode,
|
|
28261
|
+
renameFrom: onlyOnce(raw.rename, "--rename", argv),
|
|
28262
|
+
renameTo: onlyOnce(raw.to, "--to", argv),
|
|
28263
|
+
removeName: onlyOnce(raw["remove-name"], "--remove-name", argv),
|
|
28264
|
+
flowSlug: onlyOnce(raw.flow, "--flow", argv),
|
|
28265
|
+
candidates: raw.candidates === true,
|
|
28266
|
+
days: readDays(raw.days)
|
|
28267
|
+
};
|
|
28268
|
+
checkFlagShape(args);
|
|
28269
|
+
checkEventKeys(args.eventKeys);
|
|
28270
|
+
return args;
|
|
28271
|
+
}
|
|
28272
|
+
function existingNames(existing) {
|
|
28273
|
+
return [...new Set(existing.map((row) => row.name))];
|
|
28274
|
+
}
|
|
28275
|
+
function checkNameCollision(name, existing) {
|
|
28276
|
+
const collision = findNameCollision(name, existingNames(existing));
|
|
28277
|
+
if (collision === null) return;
|
|
28278
|
+
throw new ConversionArgsError(
|
|
28279
|
+
`This company already counts "${collision}", and "${name}" would be a second row for the same outcome \u2014 Baker groups by exact name, so the number would be split in two. Use "${collision}" to make them one number, or pick a name that means something different.`
|
|
28280
|
+
);
|
|
28281
|
+
}
|
|
28282
|
+
function under(name, existing) {
|
|
28283
|
+
const rows = existing.filter((row) => row.name === name);
|
|
28284
|
+
if (rows.length === 0) {
|
|
28285
|
+
throw new ConversionArgsError(
|
|
28286
|
+
`Nothing is counted under "${name}". This company counts: ${existingNames(existing).join(", ") || "nothing yet"}.`
|
|
28287
|
+
);
|
|
28022
28288
|
}
|
|
28023
|
-
return
|
|
28289
|
+
return rows;
|
|
28024
28290
|
}
|
|
28025
|
-
function
|
|
28026
|
-
|
|
28027
|
-
|
|
28028
|
-
|
|
28029
|
-
|
|
28291
|
+
function setFromEvents(args, existing) {
|
|
28292
|
+
if (args.eventKeys.length === 0) return [];
|
|
28293
|
+
if (args.name !== void 0) {
|
|
28294
|
+
checkNameCollision(args.name, existing);
|
|
28295
|
+
const name = args.name;
|
|
28296
|
+
return args.eventKeys.map((eventKey) => ({
|
|
28297
|
+
eventKey,
|
|
28298
|
+
name,
|
|
28299
|
+
...args.countMode ? { countMode: args.countMode } : {}
|
|
28300
|
+
}));
|
|
28301
|
+
}
|
|
28302
|
+
return args.eventKeys.map((eventKey) => {
|
|
28303
|
+
const current = existing.find((row) => row.eventKey === eventKey);
|
|
28304
|
+
if (!current) {
|
|
28305
|
+
throw new ConversionArgsError(
|
|
28306
|
+
`"${eventKey}" is not counted, so there is no count mode to change. Add it with --event "${eventKey}" --name "<what a person calls it>" --count-mode ${args.countMode}.`
|
|
28307
|
+
);
|
|
28308
|
+
}
|
|
28309
|
+
return { eventKey, name: current.name, countMode: args.countMode };
|
|
28310
|
+
});
|
|
28311
|
+
}
|
|
28312
|
+
function setFromRename(args, existing) {
|
|
28313
|
+
if (args.renameFrom === void 0 || args.renameTo === void 0) return [];
|
|
28314
|
+
const renameTo = args.renameTo;
|
|
28315
|
+
checkNameCollision(renameTo, existing);
|
|
28316
|
+
return under(args.renameFrom, existing).map((row) => ({
|
|
28317
|
+
eventKey: row.eventKey,
|
|
28318
|
+
name: renameTo,
|
|
28319
|
+
countMode: row.countMode
|
|
28320
|
+
}));
|
|
28321
|
+
}
|
|
28322
|
+
function conversionsWrite(args, existing) {
|
|
28323
|
+
const set = [...setFromEvents(args, existing), ...setFromRename(args, existing)];
|
|
28324
|
+
const remove = [
|
|
28325
|
+
...args.removeKeys,
|
|
28326
|
+
...args.removeName === void 0 ? [] : under(args.removeName, existing).map((row) => row.eventKey)
|
|
28327
|
+
];
|
|
28328
|
+
if (set.length === 0 && remove.length === 0) return null;
|
|
28329
|
+
return { ...set.length > 0 ? { set } : {}, ...remove.length > 0 ? { remove } : {} };
|
|
28030
28330
|
}
|
|
28031
28331
|
|
|
28032
28332
|
// src/commands/analytics/conversionHints.ts
|
|
@@ -28064,6 +28364,163 @@ function conversionHints(definitions, candidates, askedForCandidates) {
|
|
|
28064
28364
|
}
|
|
28065
28365
|
return hints2;
|
|
28066
28366
|
}
|
|
28367
|
+
function namingHints(definitions) {
|
|
28368
|
+
const names = [...new Set(definitions.map((row) => row.name))];
|
|
28369
|
+
if (names.length === 0) {
|
|
28370
|
+
return [
|
|
28371
|
+
`Nothing is named yet, so the first name sets the vocabulary for this company. Start from the standard set \u2014 ${STANDARD_CONVERSION_NAMES.map((entry) => entry.name).join(", ")} \u2014 and stay in the company's own language once one exists.`
|
|
28372
|
+
];
|
|
28373
|
+
}
|
|
28374
|
+
return [
|
|
28375
|
+
`Names in use here: ${names.join(", ")}. Reuse one EXACTLY when another Form produces the same outcome \u2014 that is what makes them one number \u2014 and use \`--rename "<old>" --to "<existing>"\` to merge two that should always have been one.`
|
|
28376
|
+
];
|
|
28377
|
+
}
|
|
28378
|
+
|
|
28379
|
+
// src/commands/analytics/conversionList.ts
|
|
28380
|
+
function mergeStoredWithCounts(stored, reported) {
|
|
28381
|
+
const counts = new Map(reported.map((row) => [row.name, row]));
|
|
28382
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28383
|
+
for (const row of stored) {
|
|
28384
|
+
const group = groups.get(row.name) ?? [];
|
|
28385
|
+
group.push(row);
|
|
28386
|
+
groups.set(row.name, group);
|
|
28387
|
+
}
|
|
28388
|
+
return [...groups.entries()].map(([name, rows]) => {
|
|
28389
|
+
const modes = new Set(rows.map((row) => row.countMode));
|
|
28390
|
+
const counted = counts.get(name);
|
|
28391
|
+
return {
|
|
28392
|
+
name,
|
|
28393
|
+
eventKeys: rows.map((row) => row.eventKey).sort(),
|
|
28394
|
+
// A real state and usually an editing accident, which is why it is said
|
|
28395
|
+
// out loud rather than resolved to one of them.
|
|
28396
|
+
countMode: modes.size === 1 ? [...modes][0] : "mixed",
|
|
28397
|
+
// Zero rather than absent: an outcome that is counted and has never
|
|
28398
|
+
// fired is the most actionable row on this report, and dropping it
|
|
28399
|
+
// would make it indistinguishable from one nobody has named.
|
|
28400
|
+
conversions: counted?.conversions ?? 0,
|
|
28401
|
+
convertingVisits: counted?.convertingVisits ?? 0,
|
|
28402
|
+
lastSeen: counted?.lastSeen ?? null
|
|
28403
|
+
};
|
|
28404
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
28405
|
+
}
|
|
28406
|
+
|
|
28407
|
+
// src/commands/analytics/flowConversionKeys.ts
|
|
28408
|
+
function walk(node, acc = []) {
|
|
28409
|
+
if (!node || typeof node !== "object") return acc;
|
|
28410
|
+
const record = node;
|
|
28411
|
+
acc.push(record);
|
|
28412
|
+
if (Array.isArray(record.children)) for (const child of record.children) walk(child, acc);
|
|
28413
|
+
return acc;
|
|
28414
|
+
}
|
|
28415
|
+
function flowConversionCandidates(tree, slug) {
|
|
28416
|
+
const primary = [];
|
|
28417
|
+
const secondary = [];
|
|
28418
|
+
for (const node of walk(tree)) {
|
|
28419
|
+
const id = typeof node.id === "string" ? node.id : null;
|
|
28420
|
+
if (!id) continue;
|
|
28421
|
+
const triggers = flowNodeTriggers(node.nodeData);
|
|
28422
|
+
for (const triggerId of triggers.available) {
|
|
28423
|
+
const candidate = {
|
|
28424
|
+
eventKey: formEventKey(slug, id, triggerId),
|
|
28425
|
+
nodeId: id,
|
|
28426
|
+
nodeName: typeof node.name === "string" ? node.name : id,
|
|
28427
|
+
nodeType: triggers.type,
|
|
28428
|
+
triggerId,
|
|
28429
|
+
isCompletion: triggers.completion === triggerId
|
|
28430
|
+
};
|
|
28431
|
+
(candidate.isCompletion ? primary : secondary).push(candidate);
|
|
28432
|
+
}
|
|
28433
|
+
}
|
|
28434
|
+
return [
|
|
28435
|
+
...primary,
|
|
28436
|
+
...secondary,
|
|
28437
|
+
{
|
|
28438
|
+
eventKey: `submit:${slug}`,
|
|
28439
|
+
nodeId: null,
|
|
28440
|
+
nodeName: "the Form ran out of steps",
|
|
28441
|
+
nodeType: null,
|
|
28442
|
+
triggerId: null,
|
|
28443
|
+
// A Form reaching its last node is as primary as an outcome gets for a
|
|
28444
|
+
// Form with no widget in it — but it never fires on one that ends in a
|
|
28445
|
+
// booking widget or a redirect, so it does not lead the list.
|
|
28446
|
+
isCompletion: false
|
|
28447
|
+
}
|
|
28448
|
+
];
|
|
28449
|
+
}
|
|
28450
|
+
function formKeyRefusal(eventKey, tree, slug) {
|
|
28451
|
+
const parsed = parseEventKey(eventKey);
|
|
28452
|
+
if (!parsed || parsed.family !== "form" || parsed.flowSlug !== slug) return null;
|
|
28453
|
+
const node = walk(tree).find((candidate) => candidate.id === parsed.nodeId);
|
|
28454
|
+
if (!node) {
|
|
28455
|
+
return {
|
|
28456
|
+
message: `"${slug}" has no step with id "${parsed.nodeId}", so this key would be stored and count zero forever.`,
|
|
28457
|
+
fix: `Steps in this Form: ${walk(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.`
|
|
28458
|
+
};
|
|
28459
|
+
}
|
|
28460
|
+
const triggers = flowNodeTriggers(node.nodeData);
|
|
28461
|
+
if (!triggers.known || triggers.available.includes(parsed.triggerId)) return null;
|
|
28462
|
+
return {
|
|
28463
|
+
message: `Step "${String(node.name ?? node.id)}" never raises \`${parsed.triggerId}\`, so counting it would score zero however many people reach that step.`,
|
|
28464
|
+
fix: triggers.available.length > 0 ? `It raises: ${triggers.available.join(", ")}.` : triggers.why ?? "That step raises nothing a conversion could be counted on."
|
|
28465
|
+
};
|
|
28466
|
+
}
|
|
28467
|
+
function joinFlowConversions(candidates, definitions) {
|
|
28468
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
28469
|
+
for (const definition of definitions) {
|
|
28470
|
+
for (const key of definition.eventKeys) byKey.set(key, definition.name);
|
|
28471
|
+
}
|
|
28472
|
+
return candidates.map((candidate) => ({ ...candidate, countedAs: byKey.get(candidate.eventKey) ?? null }));
|
|
28473
|
+
}
|
|
28474
|
+
function flowConversionHints(slug, rows, noConversionReason) {
|
|
28475
|
+
const counted = rows.filter((row) => row.countedAs !== null);
|
|
28476
|
+
if (counted.length > 0) {
|
|
28477
|
+
return [
|
|
28478
|
+
`Baker counts ${counted.length} of this Form's endings: ${counted.map((row) => `${row.countedAs} (${row.nodeName})`).join(
|
|
28479
|
+
", "
|
|
28480
|
+
)}. Every other ending here scores nothing \u2014 count another with \`--event <key> --name <outcome>\`, reusing the SAME name to make two Forms one number.`
|
|
28481
|
+
];
|
|
28482
|
+
}
|
|
28483
|
+
if (noConversionReason) {
|
|
28484
|
+
return [
|
|
28485
|
+
`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.`
|
|
28486
|
+
];
|
|
28487
|
+
}
|
|
28488
|
+
const deepestFirst = [...rows].reverse();
|
|
28489
|
+
const suggestion = deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? rows[rows.length - 1];
|
|
28490
|
+
return [
|
|
28491
|
+
`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.`,
|
|
28492
|
+
...suggestion ? [
|
|
28493
|
+
`Name its outcome: \`baker analytics conversions --event "${suggestion.eventKey}" --name "<what a person calls it>"\` (that key is "${suggestion.nodeName}"). It applies at once and rescores the whole history; nothing is staged and there is nothing to publish.`
|
|
28494
|
+
] : [],
|
|
28495
|
+
`If this Form has no outcome of its own \u2014 it hands the visitor to a shop, a portal or a funnel that converts them there \u2014 say so instead, with \`noConversion: { "reason": "\u2026" }\` on its first step.`
|
|
28496
|
+
];
|
|
28497
|
+
}
|
|
28498
|
+
function flowCoverage(forms, definitions) {
|
|
28499
|
+
return forms.map(({ slug, tree }) => {
|
|
28500
|
+
const rows = joinFlowConversions(flowConversionCandidates(tree, slug), definitions);
|
|
28501
|
+
const declared = tree?.noConversion?.reason;
|
|
28502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28503
|
+
return {
|
|
28504
|
+
slug,
|
|
28505
|
+
counted: rows.filter((row) => row.countedAs !== null).filter((row) => {
|
|
28506
|
+
const key = `${row.countedAs} ${row.nodeName}`;
|
|
28507
|
+
if (seen.has(key)) return false;
|
|
28508
|
+
seen.add(key);
|
|
28509
|
+
return true;
|
|
28510
|
+
}).map((row) => ({ name: String(row.countedAs), step: row.nodeName })),
|
|
28511
|
+
noConversion: typeof declared === "string" && declared.trim() !== "" ? declared.trim() : null
|
|
28512
|
+
};
|
|
28513
|
+
});
|
|
28514
|
+
}
|
|
28515
|
+
function flowCoverageHints(coverage) {
|
|
28516
|
+
const unset = coverage.filter((row) => row.counted.length === 0 && row.noConversion === null);
|
|
28517
|
+
if (unset.length === 0) return [];
|
|
28518
|
+
return [
|
|
28519
|
+
`${unset.length} of ${coverage.length} Form(s) here count nothing, so every report shows them converting nobody: ${unset.map((row) => row.slug).join(
|
|
28520
|
+
", "
|
|
28521
|
+
)}. Read each one's endings with \`baker analytics conversions --flow <slug>\` and name the outcome, or declare on its first step why it has none.`
|
|
28522
|
+
];
|
|
28523
|
+
}
|
|
28067
28524
|
|
|
28068
28525
|
// src/commands/analytics/hints.ts
|
|
28069
28526
|
var SEVERE_STEP_DROP = 0.6;
|
|
@@ -28946,26 +29403,93 @@ Examples:
|
|
|
28946
29403
|
}
|
|
28947
29404
|
});
|
|
28948
29405
|
})();
|
|
29406
|
+
function localFormKeyRefusal(eventKey) {
|
|
29407
|
+
const parsed = parseEventKey(eventKey);
|
|
29408
|
+
if (!parsed || parsed.family !== "form") return null;
|
|
29409
|
+
if (!flowExists(parsed.flowSlug)) return null;
|
|
29410
|
+
return formKeyRefusal(eventKey, readFlowTree(parsed.flowSlug), parsed.flowSlug);
|
|
29411
|
+
}
|
|
29412
|
+
function refuseUnmatchableKey(eventKey) {
|
|
29413
|
+
const refusal = eventKey === void 0 ? null : localFormKeyRefusal(eventKey);
|
|
29414
|
+
if (!refusal) return;
|
|
29415
|
+
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message: refusal.message, fix: refusal.fix } });
|
|
29416
|
+
process.exit(1);
|
|
29417
|
+
}
|
|
29418
|
+
function readWorkspaceFlows() {
|
|
29419
|
+
return listFlowSlugs().flatMap((slug) => {
|
|
29420
|
+
try {
|
|
29421
|
+
return [{ slug, tree: JSON.parse(readFileSync12(flowDataPath(slug), "utf-8")) }];
|
|
29422
|
+
} catch {
|
|
29423
|
+
return [];
|
|
29424
|
+
}
|
|
29425
|
+
});
|
|
29426
|
+
}
|
|
29427
|
+
function readFlowConversions2(slug, definitions) {
|
|
29428
|
+
const tree = readFlowTree(slug);
|
|
29429
|
+
const declared = tree.noConversion;
|
|
29430
|
+
const reason = declared && typeof declared === "object" && typeof declared.reason === "string" ? String(declared.reason) : null;
|
|
29431
|
+
return {
|
|
29432
|
+
slug,
|
|
29433
|
+
noConversionReason: reason,
|
|
29434
|
+
endings: joinFlowConversions(flowConversionCandidates(tree, slug), definitions)
|
|
29435
|
+
};
|
|
29436
|
+
}
|
|
29437
|
+
function conversionsEnvelope(args, definitions, candidates) {
|
|
29438
|
+
const flow = args.flowSlug === void 0 ? null : readFlowConversions2(args.flowSlug, definitions);
|
|
29439
|
+
const forms = flow === null ? readWorkspaceFlows() : [];
|
|
29440
|
+
const coverage = flowCoverage(forms, definitions);
|
|
29441
|
+
const hints2 = [
|
|
29442
|
+
...flow ? flowConversionHints(flow.slug, flow.endings, flow.noConversionReason) : [],
|
|
29443
|
+
...flowCoverageHints(coverage),
|
|
29444
|
+
...conversionHints(definitions, candidates, args.candidates),
|
|
29445
|
+
...namingHints(definitions)
|
|
29446
|
+
];
|
|
29447
|
+
return {
|
|
29448
|
+
ok: true,
|
|
29449
|
+
data: {
|
|
29450
|
+
definitions,
|
|
29451
|
+
...flow ? { flow } : {},
|
|
29452
|
+
...coverage.length > 0 ? { flows: coverage } : {},
|
|
29453
|
+
...args.candidates ? { candidates } : {}
|
|
29454
|
+
},
|
|
29455
|
+
...hints2.length > 0 ? { hints: hints2 } : {}
|
|
29456
|
+
};
|
|
29457
|
+
}
|
|
28949
29458
|
var conversionsCommand3 = (() => {
|
|
28950
29459
|
const args = {
|
|
28951
29460
|
event: {
|
|
28952
29461
|
type: "string",
|
|
28953
|
-
description: "The event to start counting, from the `candidates` list: form:<form>:<step>:<trigger>, submit:<form>, page:<name>, or exit:<host>. Requires --name. Applies immediately and scores the WHOLE history, not just from now on
|
|
29462
|
+
description: "The event to start counting, from the `candidates` or `--flow` list: form:<form>:<step>:<trigger>, submit:<form>, page:<name>, or exit:<host>. Requires --name. REPEATABLE \u2014 pass it several times with one --name to count all of them as that single outcome, which is the whole point of the name. Applies immediately and scores the WHOLE history, not just from now on, so counting the right event today also corrects last month's reports",
|
|
28954
29463
|
required: false
|
|
28955
29464
|
},
|
|
28956
29465
|
name: {
|
|
28957
29466
|
type: "string",
|
|
28958
|
-
description: "What to call this outcome on every report \u2014 'Booked a call', 'Quote requested'.
|
|
29467
|
+
description: "What to call this outcome on every report \u2014 'Booked a call', 'Quote requested'. Baker groups by EXACT name: reuse one this company already counts and the two become one row and one number, which is how a call booked on three Forms reads as one outcome. Read the existing names first (run with no flags) and reuse one before inventing another; a name that only differs by case, accent, punctuation or a plural is refused, because it would split the number in two",
|
|
29468
|
+
required: false
|
|
29469
|
+
},
|
|
29470
|
+
rename: {
|
|
29471
|
+
type: "string",
|
|
29472
|
+
description: "An outcome's current name. With --to, renames every event counted under it. Renaming ONTO a name that already exists MERGES them into one row and one number \u2014 the way to fix a company whose Forms each invented their own word for the same outcome",
|
|
29473
|
+
required: false
|
|
29474
|
+
},
|
|
29475
|
+
to: {
|
|
29476
|
+
type: "string",
|
|
29477
|
+
description: "The new name for --rename",
|
|
29478
|
+
required: false
|
|
29479
|
+
},
|
|
29480
|
+
"remove-name": {
|
|
29481
|
+
type: "string",
|
|
29482
|
+
description: "Stop counting an outcome entirely, by name \u2014 removes every event counted under it. Use --remove for a single event key instead",
|
|
28959
29483
|
required: false
|
|
28960
29484
|
},
|
|
28961
29485
|
"count-mode": {
|
|
28962
29486
|
type: "string",
|
|
28963
|
-
description: "once_per_visit (default) or every_time. Ask whether doing it twice in one visit is one outcome or two: a booking clicked twice is one booking, a guide downloaded twice is two downloads. Getting this wrong inflates the rate every budget is set by",
|
|
29487
|
+
description: "once_per_visit (default) or every_time. Ask whether doing it twice in one visit is one outcome or two: a booking clicked twice is one booking, a guide downloaded twice is two downloads. Getting this wrong inflates the rate every budget is set by. Pass it with --event and no --name to change the mode of something already counted, keeping its name",
|
|
28964
29488
|
required: false
|
|
28965
29489
|
},
|
|
28966
29490
|
remove: {
|
|
28967
29491
|
type: "string",
|
|
28968
|
-
description: "An event key to stop counting. Removes it from the history too, for the same reason adding it fills the history in",
|
|
29492
|
+
description: "An event key to stop counting. REPEATABLE. Removes it from the history too, for the same reason adding it fills the history in",
|
|
28969
29493
|
required: false
|
|
28970
29494
|
},
|
|
28971
29495
|
candidates: {
|
|
@@ -28973,6 +29497,11 @@ var conversionsCommand3 = (() => {
|
|
|
28973
29497
|
description: "Also list every event these pages actually produced, ranked by volume, with whether it is already counted. This is where an event key comes from \u2014 do not invent one. Off by default because a busy company produces hundreds of rows",
|
|
28974
29498
|
required: false
|
|
28975
29499
|
},
|
|
29500
|
+
flow: {
|
|
29501
|
+
type: "string",
|
|
29502
|
+
description: "A Form slug. Lists that Form's own endings already spelled as event keys, each with the name Baker counts it under or nothing \u2014 read off the Form's file, so it works on a Form built minutes ago that has never been visited, which is exactly when --candidates is empty. Use it whenever you create or edit a Form: it is the only way to see that the Form counts nothing before it is published counting nothing",
|
|
29503
|
+
required: false
|
|
29504
|
+
},
|
|
28976
29505
|
days: {
|
|
28977
29506
|
type: "string",
|
|
28978
29507
|
description: "Lookback for the counts and for --candidates. Default 30",
|
|
@@ -28981,34 +29510,33 @@ var conversionsCommand3 = (() => {
|
|
|
28981
29510
|
};
|
|
28982
29511
|
registerSchema({
|
|
28983
29512
|
command: "analytics.conversions",
|
|
28984
|
-
description: "Read or change what this company counts as a conversion",
|
|
29513
|
+
description: "Read or change what this company counts as a conversion. Also `--flow <slug>` for one Form's own endings as event keys, whether or not any of them has fired.",
|
|
28985
29514
|
args
|
|
28986
29515
|
});
|
|
28987
29516
|
return defineCommand95({
|
|
28988
29517
|
meta: {
|
|
28989
29518
|
name: "conversions",
|
|
28990
|
-
description: "What this company counts as a conversion, and what each one produced. Run with no flags first: an empty `definitions` list means every conversion number in every other report is zero \u2014 not because nobody converted, but because nothing is marked as converting, and Baker never guesses. Add `--candidates` to see everything these pages actually do (every Form step, every data-baker-* event, every outbound destination) with counts and whether it is already counted; that list is where an event key comes from, so never invent one. Then `--event <key> --name 'Booked a call'` to count it. It applies IMMEDIATELY and retroactively \u2014 the whole history is rescored, so a company that has been running for a month gets a month of correct numbers the moment you name the right event. Nothing here is staged and publishing is not involved. Two events given the SAME --name are one row and one number. `--remove <key>` stops counting one."
|
|
29519
|
+
description: "What this company counts as a conversion, and what each one produced. Run with no flags first: an empty `definitions` list means every conversion number in every other report is zero \u2014 not because nobody converted, but because nothing is marked as converting, and Baker never guesses. Add `--candidates` to see everything these pages actually do (every Form step, every data-baker-* event, every outbound destination) with counts and whether it is already counted; that list is where an event key comes from, so never invent one. Then `--event <key> --name 'Booked a call'` to count it. It applies IMMEDIATELY and retroactively \u2014 the whole history is rescored, so a company that has been running for a month gets a month of correct numbers the moment you name the right event. Nothing here is staged and publishing is not involved. Two events given the SAME --name are one row and one number. `--remove <key>` stops counting one. `--flow <slug>` answers the other direction \u2014 what one Form counts, with every ending it has already spelled as an event key, read from the Form itself so it works before a single visitor has seen it. Run it whenever you create or edit a Form: a Form nobody named an outcome for is published converting nobody, and no report says so."
|
|
28991
29520
|
},
|
|
28992
29521
|
args,
|
|
28993
29522
|
run: async ({ args: raw }) => {
|
|
28994
29523
|
try {
|
|
28995
29524
|
const args2 = parseConversionsArgs(raw, process.argv);
|
|
28996
|
-
const
|
|
28997
|
-
|
|
28998
|
-
|
|
28999
|
-
|
|
29525
|
+
for (const eventKey of args2.eventKeys) refuseUnmatchableKey(eventKey);
|
|
29526
|
+
const before = await apiPost("/api/analytics/conversions", {});
|
|
29527
|
+
const write2 = conversionsWrite(args2, before.data.definitions);
|
|
29528
|
+
const stored = write2 ? (await apiPost("/api/analytics/conversions", write2)).data.definitions : before.data.definitions;
|
|
29000
29529
|
const report = await apiPost("/api/analytics/query", {
|
|
29001
29530
|
preset: "conversions",
|
|
29002
29531
|
days: args2.days
|
|
29003
29532
|
});
|
|
29004
|
-
|
|
29005
|
-
|
|
29006
|
-
|
|
29007
|
-
|
|
29008
|
-
|
|
29009
|
-
|
|
29010
|
-
|
|
29011
|
-
});
|
|
29533
|
+
writeJsonEnvelope(
|
|
29534
|
+
conversionsEnvelope(
|
|
29535
|
+
args2,
|
|
29536
|
+
mergeStoredWithCounts(stored, report.data.conversions ?? []),
|
|
29537
|
+
report.data.eventCatalog ?? []
|
|
29538
|
+
)
|
|
29539
|
+
);
|
|
29012
29540
|
} catch (err) {
|
|
29013
29541
|
if (err instanceof ConversionArgsError) {
|
|
29014
29542
|
handleError(new ApiError("VALIDATION_ERROR", err.message));
|
|
@@ -31310,10 +31838,10 @@ import path15 from "path";
|
|
|
31310
31838
|
import { defineCommand as defineCommand107 } from "citty";
|
|
31311
31839
|
|
|
31312
31840
|
// src/commands/canvas/normalize-paths.ts
|
|
31313
|
-
import { existsSync as
|
|
31841
|
+
import { existsSync as existsSync4, realpathSync } from "fs";
|
|
31314
31842
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
31315
31843
|
import path5 from "path";
|
|
31316
|
-
function findWorkspaceRoot(startDir, exists =
|
|
31844
|
+
function findWorkspaceRoot(startDir, exists = existsSync4, maxDepth = 12) {
|
|
31317
31845
|
let dir = path5.resolve(startDir);
|
|
31318
31846
|
for (let i = 0; i < maxDepth; i++) {
|
|
31319
31847
|
if (exists(path5.join(dir, "package.json"))) return dir;
|
|
@@ -36958,7 +37486,7 @@ function routeVideoModel(input) {
|
|
|
36958
37486
|
import { execFile as execFile2 } from "child_process";
|
|
36959
37487
|
import { mkdtemp, readdir as readdir7, readFile as readFile14, rm as rm5 } from "fs/promises";
|
|
36960
37488
|
import { tmpdir } from "os";
|
|
36961
|
-
import { join as
|
|
37489
|
+
import { join as join3 } from "path";
|
|
36962
37490
|
import { promisify as promisify2 } from "util";
|
|
36963
37491
|
var execFileAsync2 = promisify2(execFile2);
|
|
36964
37492
|
var PYSCENEDETECT_THRESHOLD = 18;
|
|
@@ -37011,7 +37539,7 @@ function parsePySceneDetectCsvCuts(csv) {
|
|
|
37011
37539
|
return [...new Set(cuts)].sort((a, b) => a - b);
|
|
37012
37540
|
}
|
|
37013
37541
|
async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs) {
|
|
37014
|
-
const outDir = await mkdtemp(
|
|
37542
|
+
const outDir = await mkdtemp(join3(tmpdir(), "baker-scenedetect-"));
|
|
37015
37543
|
try {
|
|
37016
37544
|
await execFileAsync2(
|
|
37017
37545
|
"scenedetect",
|
|
@@ -37032,7 +37560,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
37032
37560
|
);
|
|
37033
37561
|
const csvName = (await readdir7(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
37034
37562
|
if (!csvName) return [];
|
|
37035
|
-
return parsePySceneDetectCsvCuts(await readFile14(
|
|
37563
|
+
return parsePySceneDetectCsvCuts(await readFile14(join3(outDir, csvName), "utf-8"));
|
|
37036
37564
|
} finally {
|
|
37037
37565
|
await rm5(outDir, { recursive: true, force: true });
|
|
37038
37566
|
}
|
|
@@ -37056,9 +37584,9 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
37056
37584
|
}
|
|
37057
37585
|
|
|
37058
37586
|
// src/commands/canvas/composition-path.ts
|
|
37059
|
-
import { existsSync as
|
|
37587
|
+
import { existsSync as existsSync5 } from "fs";
|
|
37060
37588
|
import path21 from "path";
|
|
37061
|
-
function resolveShippedCanvasDir(name, startDir, exists =
|
|
37589
|
+
function resolveShippedCanvasDir(name, startDir, exists = existsSync5, maxDepth = 8) {
|
|
37062
37590
|
const rel = path21.join("canvas", name);
|
|
37063
37591
|
let dir = startDir;
|
|
37064
37592
|
for (let i = 0; i < maxDepth; i++) {
|
|
@@ -38432,176 +38960,6 @@ import { defineCommand as defineCommand123 } from "citty";
|
|
|
38432
38960
|
// src/commands/flows/add.ts
|
|
38433
38961
|
import { randomUUID } from "crypto";
|
|
38434
38962
|
import { defineCommand as defineCommand119 } from "citty";
|
|
38435
|
-
|
|
38436
|
-
// src/commands/flows/shared.ts
|
|
38437
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
38438
|
-
import { join as join3 } from "path";
|
|
38439
|
-
var FLOWS_DIR = "src/lib/flow-engine/flows";
|
|
38440
|
-
function flowsDir() {
|
|
38441
|
-
let dir = process.cwd();
|
|
38442
|
-
for (let i = 0; i < 6; i++) {
|
|
38443
|
-
const candidate = join3(dir, FLOWS_DIR);
|
|
38444
|
-
if (existsSync5(candidate)) {
|
|
38445
|
-
return candidate;
|
|
38446
|
-
}
|
|
38447
|
-
const parent = join3(dir, "..");
|
|
38448
|
-
if (parent === dir) {
|
|
38449
|
-
break;
|
|
38450
|
-
}
|
|
38451
|
-
dir = parent;
|
|
38452
|
-
}
|
|
38453
|
-
return join3(process.cwd(), FLOWS_DIR);
|
|
38454
|
-
}
|
|
38455
|
-
function failLocal(message) {
|
|
38456
|
-
writeJson({ ok: false, error: { code: "NOT_FOUND", message } });
|
|
38457
|
-
process.exit(1);
|
|
38458
|
-
}
|
|
38459
|
-
function listFlowSlugs() {
|
|
38460
|
-
const dir = flowsDir();
|
|
38461
|
-
if (!existsSync5(dir)) {
|
|
38462
|
-
return [];
|
|
38463
|
-
}
|
|
38464
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
38465
|
-
}
|
|
38466
|
-
function flowDataPath(slug) {
|
|
38467
|
-
return join3(flowsDir(), slug, "_data.json");
|
|
38468
|
-
}
|
|
38469
|
-
function writeFlowTree(slug, tree) {
|
|
38470
|
-
writeFileSync3(flowDataPath(slug), `${JSON.stringify(tree, null, 2)}
|
|
38471
|
-
`, "utf-8");
|
|
38472
|
-
}
|
|
38473
|
-
function walkNodes(node, acc = []) {
|
|
38474
|
-
if (!node || typeof node !== "object") return acc;
|
|
38475
|
-
acc.push(node);
|
|
38476
|
-
if (Array.isArray(node.children)) {
|
|
38477
|
-
for (const child of node.children) walkNodes(child, acc);
|
|
38478
|
-
}
|
|
38479
|
-
return acc;
|
|
38480
|
-
}
|
|
38481
|
-
function collectSideEffects(tree) {
|
|
38482
|
-
return walkNodes(tree).flatMap(
|
|
38483
|
-
(node) => Array.isArray(node.sideEffects) ? node.sideEffects.filter((sideEffect) => Boolean(sideEffect) && typeof sideEffect === "object").map((sideEffect) => ({ node, sideEffect })) : []
|
|
38484
|
-
);
|
|
38485
|
-
}
|
|
38486
|
-
function readFlowTree(slug) {
|
|
38487
|
-
const path38 = join3(flowsDir(), slug, "_data.json");
|
|
38488
|
-
if (!existsSync5(path38)) {
|
|
38489
|
-
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
38490
|
-
}
|
|
38491
|
-
try {
|
|
38492
|
-
return JSON.parse(readFileSync11(path38, "utf-8"));
|
|
38493
|
-
} catch (error) {
|
|
38494
|
-
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
38495
|
-
}
|
|
38496
|
-
}
|
|
38497
|
-
function isConfigured(value) {
|
|
38498
|
-
return typeof value === "string" && value.length > 0;
|
|
38499
|
-
}
|
|
38500
|
-
function nodeMeta(record) {
|
|
38501
|
-
const nodeId = typeof record.id === "string" ? record.id : "";
|
|
38502
|
-
const nodeName = typeof record.name === "string" ? record.name : void 0;
|
|
38503
|
-
return { nodeId, ...nodeName ? { nodeName } : {} };
|
|
38504
|
-
}
|
|
38505
|
-
function widgetNodeStatus(record, meta) {
|
|
38506
|
-
const nodeData = record.nodeData;
|
|
38507
|
-
const nodeType = nodeData?.type;
|
|
38508
|
-
if (typeof nodeType !== "string" || !FLOW_RESOURCE_NODE_TYPES.includes(nodeType)) {
|
|
38509
|
-
return null;
|
|
38510
|
-
}
|
|
38511
|
-
const external = nodeData?.form?.external;
|
|
38512
|
-
const selected = external != null && typeof external === "object";
|
|
38513
|
-
const name = selected ? external.name : void 0;
|
|
38514
|
-
return {
|
|
38515
|
-
...meta,
|
|
38516
|
-
target: "node",
|
|
38517
|
-
kind: nodeType,
|
|
38518
|
-
status: selected ? `${nodeType} \u2014 selected${typeof name === "string" ? ` "${name}"` : ""}` : `${nodeType} \u2014 not selected`,
|
|
38519
|
-
needsInput: !selected
|
|
38520
|
-
};
|
|
38521
|
-
}
|
|
38522
|
-
function sideEffectNeedsInput(raw) {
|
|
38523
|
-
const effect = raw;
|
|
38524
|
-
const type = effect?.type;
|
|
38525
|
-
if (typeof type !== "string" || !FLOW_SECRET_SIDE_EFFECT_TYPES.includes(type)) {
|
|
38526
|
-
return false;
|
|
38527
|
-
}
|
|
38528
|
-
const configured = isConfigured(effect?.encryptedConfig);
|
|
38529
|
-
if (!isFlowOauthSideEffect(type)) return !configured;
|
|
38530
|
-
return !isConfigured(effect?.oauthProviderId) || !configured;
|
|
38531
|
-
}
|
|
38532
|
-
function sideEffectStatus(raw, meta) {
|
|
38533
|
-
const effect = raw;
|
|
38534
|
-
const type = effect?.type;
|
|
38535
|
-
if (typeof type !== "string" || !FLOW_SECRET_SIDE_EFFECT_TYPES.includes(type)) {
|
|
38536
|
-
return null;
|
|
38537
|
-
}
|
|
38538
|
-
const configured = isConfigured(effect?.encryptedConfig);
|
|
38539
|
-
const oauth = isFlowOauthSideEffect(type);
|
|
38540
|
-
const connected = isConfigured(effect?.oauthProviderId);
|
|
38541
|
-
const needs = FLOW_SECRET_FIELDS[type];
|
|
38542
|
-
const status = oauth ? `${type} \u2014 connection ${connected ? "[connected]" : "[needs connection]"}, config ${configured ? "[set]" : "[missing]"}` : configured ? `${type} \u2014 [configured]` : `${type} \u2014 [missing]${needs.length > 0 ? ` (needs: ${needs.join(", ")})` : ""}`;
|
|
38543
|
-
return {
|
|
38544
|
-
...meta,
|
|
38545
|
-
target: "sideEffect",
|
|
38546
|
-
...typeof effect?.id === "string" ? { sideEffectId: effect.id } : {},
|
|
38547
|
-
kind: type,
|
|
38548
|
-
status,
|
|
38549
|
-
needsInput: sideEffectNeedsInput(effect)
|
|
38550
|
-
};
|
|
38551
|
-
}
|
|
38552
|
-
function requestFlowInputCall(slug, status) {
|
|
38553
|
-
const args = status.target === "node" ? `{ target: "node", flowSlug: "${slug}", nodeId: "${status.nodeId}", nodeType: "${status.kind}" }` : `{ target: "sideEffect", flowSlug: "${slug}", nodeId: "${status.nodeId}", sideEffectId: "${status.sideEffectId}", sideEffectType: "${status.kind}" }`;
|
|
38554
|
-
return `request_flow_input with ${args}`;
|
|
38555
|
-
}
|
|
38556
|
-
function collectConfigStatus(node, acc) {
|
|
38557
|
-
if (!node || typeof node !== "object") {
|
|
38558
|
-
return;
|
|
38559
|
-
}
|
|
38560
|
-
const record = node;
|
|
38561
|
-
const meta = nodeMeta(record);
|
|
38562
|
-
const widget = widgetNodeStatus(record, meta);
|
|
38563
|
-
if (widget) {
|
|
38564
|
-
acc.push(widget);
|
|
38565
|
-
}
|
|
38566
|
-
if (Array.isArray(record.sideEffects)) {
|
|
38567
|
-
for (const raw of record.sideEffects) {
|
|
38568
|
-
const status = sideEffectStatus(raw, meta);
|
|
38569
|
-
if (status) {
|
|
38570
|
-
acc.push(status);
|
|
38571
|
-
}
|
|
38572
|
-
}
|
|
38573
|
-
}
|
|
38574
|
-
if (Array.isArray(record.children)) {
|
|
38575
|
-
for (const child of record.children) {
|
|
38576
|
-
collectConfigStatus(child, acc);
|
|
38577
|
-
}
|
|
38578
|
-
}
|
|
38579
|
-
}
|
|
38580
|
-
function redactTree(node) {
|
|
38581
|
-
if (Array.isArray(node)) {
|
|
38582
|
-
return node.map(redactTree);
|
|
38583
|
-
}
|
|
38584
|
-
if (!node || typeof node !== "object") {
|
|
38585
|
-
return node;
|
|
38586
|
-
}
|
|
38587
|
-
const out = {};
|
|
38588
|
-
for (const [key, value] of Object.entries(node)) {
|
|
38589
|
-
if (key === "encryptedConfig") {
|
|
38590
|
-
out[key] = isConfigured(value) ? "[configured]" : value;
|
|
38591
|
-
} else {
|
|
38592
|
-
out[key] = redactTree(value);
|
|
38593
|
-
}
|
|
38594
|
-
}
|
|
38595
|
-
return out;
|
|
38596
|
-
}
|
|
38597
|
-
function stepNameKey(name) {
|
|
38598
|
-
return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
38599
|
-
}
|
|
38600
|
-
function isUsableStepNameKey(key) {
|
|
38601
|
-
return /^[\p{ID_Start}$_][\p{ID_Continue}$]*$/u.test(key);
|
|
38602
|
-
}
|
|
38603
|
-
|
|
38604
|
-
// src/commands/flows/add.ts
|
|
38605
38963
|
function unknownType(kind, type) {
|
|
38606
38964
|
writeJson({
|
|
38607
38965
|
ok: false,
|
|
@@ -38692,6 +39050,10 @@ var addNodeCommand = defineCommand119({
|
|
|
38692
39050
|
data: { slug, nodeId: node.id, type, parentId: parent.id ?? null, path: flowDataPath(slug) },
|
|
38693
39051
|
hints: [
|
|
38694
39052
|
`Now fill in this step's \`ai\` field and its copy by editing ${flowDataPath(slug)}.`,
|
|
39053
|
+
// Said on every step added, because the moment a Form's shape changes
|
|
39054
|
+
// is the moment its outcome can move — and a Form nobody named an
|
|
39055
|
+
// outcome for reports zero conversions rather than an error.
|
|
39056
|
+
`Say what this Form counts before you finish: \`baker analytics conversions --flow ${slug}\` lists every ending already spelled as an event key, including this step's. Nothing in _data.json decides it.`,
|
|
38695
39057
|
...entry.owners?.["nodeData.form.external"] === "request_flow_input" ? [
|
|
38696
39058
|
`A ${type} node's resource is picked by the user: call request_flow_input with { target: "node", flowSlug: "${slug}", nodeId: "${node.id}", nodeType: "${type}" }.`
|
|
38697
39059
|
] : [],
|
|
@@ -38805,6 +39167,13 @@ var addSideEffectCommand = defineCommand119({
|
|
|
38805
39167
|
data: { slug, nodeId: node.id, sideEffectId: id, type, triggerId, path: flowDataPath(slug) },
|
|
38806
39168
|
hints: [
|
|
38807
39169
|
`Fires on \`${triggerId}\` \u2014 the moment "${String(node.name ?? node.id)}" raises. A side effect only runs when its triggerId is the one its own step raises, so this is set from the step, never from the side-effect type.`,
|
|
39170
|
+
// A pixel side effect is the thing most often mistaken for "setting the
|
|
39171
|
+
// conversion". It tells the ad platform; it tells Baker nothing, and a
|
|
39172
|
+
// Form can fire every pixel it has while every Baker report says it
|
|
39173
|
+
// converted nobody.
|
|
39174
|
+
...needsUser.some(([, owner]) => owner === "tags") ? [
|
|
39175
|
+
`This reports the conversion to ${type}. It does NOT make Baker count anything: what Baker counts is a named event, set with \`baker analytics conversions --flow ${slug}\`. Do both \u2014 they are two different systems asked the same question.`
|
|
39176
|
+
] : [],
|
|
38808
39177
|
...needsUser.some(([, owner]) => owner === "request_flow_input") ? [
|
|
38809
39178
|
`Configure it: request_flow_input with { target: "sideEffect", flowSlug: "${slug}", nodeId: "${node.id}", sideEffectId: "${id}", sideEffectType: "${type}" }. Read the destination straight off that call's result.`
|
|
38810
39179
|
] : [],
|
|
@@ -38818,11 +39187,11 @@ var addSideEffectCommand = defineCommand119({
|
|
|
38818
39187
|
});
|
|
38819
39188
|
|
|
38820
39189
|
// src/commands/flows/map.ts
|
|
38821
|
-
import { readFileSync as
|
|
39190
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
38822
39191
|
import { defineCommand as defineCommand120 } from "citty";
|
|
38823
39192
|
|
|
38824
39193
|
// src/commands/flows/value-expression.ts
|
|
38825
|
-
import { existsSync as existsSync6, readFileSync as
|
|
39194
|
+
import { existsSync as existsSync6, readFileSync as readFileSync13 } from "fs";
|
|
38826
39195
|
import { join as join4 } from "path";
|
|
38827
39196
|
var PREFIXES = ["field", "tracking", "global", "code"];
|
|
38828
39197
|
function splitParts(raw) {
|
|
@@ -38884,7 +39253,7 @@ function trackingFieldIds() {
|
|
|
38884
39253
|
const path38 = join4(flowsDir(), "..", "tracking.ts");
|
|
38885
39254
|
if (!existsSync6(path38)) return null;
|
|
38886
39255
|
try {
|
|
38887
|
-
const source =
|
|
39256
|
+
const source = readFileSync13(path38, "utf-8");
|
|
38888
39257
|
const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
|
|
38889
39258
|
if (!block2) return null;
|
|
38890
39259
|
const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
|
|
@@ -39428,7 +39797,7 @@ function specsFromFile(parsed) {
|
|
|
39428
39797
|
function readSpecFile(path38) {
|
|
39429
39798
|
let raw;
|
|
39430
39799
|
try {
|
|
39431
|
-
raw = path38 === "-" ?
|
|
39800
|
+
raw = path38 === "-" ? readFileSync14(0, "utf-8") : readFileSync14(path38, "utf-8");
|
|
39432
39801
|
} catch (error) {
|
|
39433
39802
|
refuse(
|
|
39434
39803
|
`Could not read ${path38 === "-" ? "the mapping from stdin" : `"${path38}"`}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -39969,9 +40338,15 @@ var listCommand13 = defineCommand123({
|
|
|
39969
40338
|
writeJson({
|
|
39970
40339
|
ok: true,
|
|
39971
40340
|
data: { flows },
|
|
39972
|
-
hints:
|
|
39973
|
-
|
|
39974
|
-
|
|
40341
|
+
hints: [
|
|
40342
|
+
...pending.length > 0 ? [
|
|
40343
|
+
`Still waiting on the user: ${pending.map((flow) => `${flow.slug} (${flow.needsConfig})`).join(", ")}. \`baker flows show <slug>\` returns the request_flow_input call for each.`
|
|
40344
|
+
] : [],
|
|
40345
|
+
// "Set the conversions on the Forms" lands here first, and nothing in
|
|
40346
|
+
// this family can do it — the conversion side effects it does offer
|
|
40347
|
+
// fire a pixel and are not what any Baker report counts.
|
|
40348
|
+
"Setting what a Form COUNTS is not in this family: a conversion is a named event held in Baker. `baker analytics conversions` lists what this company counts; `--flow <slug>` spells one Form's endings as keys you can count, traffic or no traffic."
|
|
40349
|
+
]
|
|
39975
40350
|
});
|
|
39976
40351
|
}
|
|
39977
40352
|
});
|
|
@@ -39999,7 +40374,15 @@ var showCommand4 = defineCommand123({
|
|
|
39999
40374
|
data: response,
|
|
40000
40375
|
// Inert side effects first: an unconfigured destination is work still to
|
|
40001
40376
|
// do, but a side effect wired to the wrong moment reads as done.
|
|
40002
|
-
hints: [
|
|
40377
|
+
hints: [
|
|
40378
|
+
...inertSideEffectHints(tree),
|
|
40379
|
+
...pendingInputHints(slug, config),
|
|
40380
|
+
// The question this command is asked — "is this Form wired up?" — has
|
|
40381
|
+
// an answer that is not in this file at all. What a Form counts is a
|
|
40382
|
+
// named event in Baker, and a Form can be perfectly configured, fire
|
|
40383
|
+
// every pixel it has, and still report converting nobody.
|
|
40384
|
+
`What this Form counts as a conversion is NOT in _data.json and not in any side effect here: it is a named event held in Baker. Read it, and this Form's own endings spelled as event keys, with \`baker analytics conversions --flow ${slug}\`.`
|
|
40385
|
+
]
|
|
40003
40386
|
});
|
|
40004
40387
|
}
|
|
40005
40388
|
});
|
|
@@ -40164,7 +40547,7 @@ Examples:
|
|
|
40164
40547
|
import { defineCommand as defineCommand125 } from "citty";
|
|
40165
40548
|
|
|
40166
40549
|
// src/commands/ga4/shared.ts
|
|
40167
|
-
import { readFileSync as
|
|
40550
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
40168
40551
|
function failValidation3(message) {
|
|
40169
40552
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
40170
40553
|
process.exit(1);
|
|
@@ -40180,7 +40563,7 @@ function requireTarget5(args, entity) {
|
|
|
40180
40563
|
function readJsonSource(args, flag) {
|
|
40181
40564
|
const inline = args[flag];
|
|
40182
40565
|
const file = args.file;
|
|
40183
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
40566
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync15(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
40184
40567
|
if (raw === void 0) {
|
|
40185
40568
|
failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
40186
40569
|
}
|
|
@@ -40531,7 +40914,7 @@ Examples:
|
|
|
40531
40914
|
});
|
|
40532
40915
|
|
|
40533
40916
|
// src/commands/ga4/query.ts
|
|
40534
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as
|
|
40917
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
|
|
40535
40918
|
import { resolve as resolve2 } from "path";
|
|
40536
40919
|
import { defineCommand as defineCommand128 } from "citty";
|
|
40537
40920
|
|
|
@@ -40622,7 +41005,7 @@ function writeRowsToFile2(filePath, rows, append) {
|
|
|
40622
41005
|
writeFileSync4(filePath, content, "utf-8");
|
|
40623
41006
|
}
|
|
40624
41007
|
} else if (append && existsSync7(filePath)) {
|
|
40625
|
-
const existing = JSON.parse(
|
|
41008
|
+
const existing = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
40626
41009
|
writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
40627
41010
|
} else {
|
|
40628
41011
|
writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -41474,7 +41857,7 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
|
|
|
41474
41857
|
import { defineCommand as defineCommand134 } from "citty";
|
|
41475
41858
|
|
|
41476
41859
|
// src/commands/gsc/query.ts
|
|
41477
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as
|
|
41860
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
41478
41861
|
import { resolve as resolve3 } from "path";
|
|
41479
41862
|
import { defineCommand as defineCommand131 } from "citty";
|
|
41480
41863
|
|
|
@@ -41622,7 +42005,7 @@ function writeRowsToFile3(filePath, rows, append) {
|
|
|
41622
42005
|
writeFileSync5(filePath, content, "utf-8");
|
|
41623
42006
|
}
|
|
41624
42007
|
} else if (append && existsSync8(filePath)) {
|
|
41625
|
-
const existing = JSON.parse(
|
|
42008
|
+
const existing = JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
41626
42009
|
writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
41627
42010
|
} else {
|
|
41628
42011
|
writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
|
|
@@ -48289,7 +48672,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
48289
48672
|
}
|
|
48290
48673
|
return false;
|
|
48291
48674
|
};
|
|
48292
|
-
const
|
|
48675
|
+
const walk2 = (rules, sink) => {
|
|
48293
48676
|
for (const rule of Array.from(rules)) {
|
|
48294
48677
|
totalRules++;
|
|
48295
48678
|
if (rule instanceof CSSStyleRule) {
|
|
@@ -48310,7 +48693,7 @@ var inPageCollectUsedCss = (selector) => {
|
|
|
48310
48693
|
const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
|
|
48311
48694
|
if (grouping) {
|
|
48312
48695
|
const inner = [];
|
|
48313
|
-
|
|
48696
|
+
walk2(rule.cssRules, inner);
|
|
48314
48697
|
if (inner.length === 0) continue;
|
|
48315
48698
|
const condition = rule.conditionText ?? "";
|
|
48316
48699
|
const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
|
|
@@ -48324,7 +48707,7 @@ ${inner.join("\n")}
|
|
|
48324
48707
|
const owner = sheet.ownerNode;
|
|
48325
48708
|
if (owner?.hasAttribute?.("data-baker-freeze")) continue;
|
|
48326
48709
|
try {
|
|
48327
|
-
|
|
48710
|
+
walk2(sheet.cssRules, kept);
|
|
48328
48711
|
} catch {
|
|
48329
48712
|
if (sheet.href) unreadableHrefs.push(sheet.href);
|
|
48330
48713
|
}
|
|
@@ -48775,7 +49158,7 @@ var inPageCollectMotion = (selector) => {
|
|
|
48775
49158
|
else if (infinite) loop.push(effect);
|
|
48776
49159
|
else entrance.push(effect);
|
|
48777
49160
|
};
|
|
48778
|
-
const
|
|
49161
|
+
const walk2 = (rules, insideScrollTimeline) => {
|
|
48779
49162
|
for (const rule of Array.from(rules)) {
|
|
48780
49163
|
if (rule instanceof CSSKeyframesRule) {
|
|
48781
49164
|
keyframesByName.set(rule.name, rule.cssText);
|
|
@@ -48793,17 +49176,17 @@ var inPageCollectMotion = (selector) => {
|
|
|
48793
49176
|
respectsReducedMotion = true;
|
|
48794
49177
|
continue;
|
|
48795
49178
|
}
|
|
48796
|
-
|
|
49179
|
+
walk2(rule.cssRules, insideScrollTimeline);
|
|
48797
49180
|
continue;
|
|
48798
49181
|
}
|
|
48799
49182
|
const grouping = rule;
|
|
48800
|
-
if (grouping.cssRules)
|
|
49183
|
+
if (grouping.cssRules) walk2(grouping.cssRules, insideScrollTimeline);
|
|
48801
49184
|
}
|
|
48802
49185
|
};
|
|
48803
49186
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
48804
49187
|
if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
|
|
48805
49188
|
try {
|
|
48806
|
-
|
|
49189
|
+
walk2(sheet.cssRules, false);
|
|
48807
49190
|
} catch {
|
|
48808
49191
|
}
|
|
48809
49192
|
}
|
|
@@ -54256,7 +54639,7 @@ import { defineCommand as defineCommand209 } from "citty";
|
|
|
54256
54639
|
import { defineCommand as defineCommand206 } from "citty";
|
|
54257
54640
|
|
|
54258
54641
|
// src/commands/tag-manager/shared.ts
|
|
54259
|
-
import { readFileSync as
|
|
54642
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
54260
54643
|
function failValidation5(message) {
|
|
54261
54644
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
54262
54645
|
process.exit(1);
|
|
@@ -54272,7 +54655,7 @@ function requireTarget6(args, entity) {
|
|
|
54272
54655
|
function loadJsonArg2(args, flag = "json") {
|
|
54273
54656
|
const inline = args[flag];
|
|
54274
54657
|
const file = args.file;
|
|
54275
|
-
const raw = typeof file === "string" && file.length > 0 ?
|
|
54658
|
+
const raw = typeof file === "string" && file.length > 0 ? readFileSync18(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
|
|
54276
54659
|
if (raw === void 0) {
|
|
54277
54660
|
failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
|
|
54278
54661
|
}
|
|
@@ -57463,7 +57846,7 @@ function refuseUnknownFlags(root, argv) {
|
|
|
57463
57846
|
}
|
|
57464
57847
|
|
|
57465
57848
|
// src/version.ts
|
|
57466
|
-
import { readFileSync as
|
|
57849
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
57467
57850
|
function packageJsonUrl() {
|
|
57468
57851
|
return new URL("../package.json", import.meta.url);
|
|
57469
57852
|
}
|
|
@@ -57475,7 +57858,7 @@ function parsePackageVersion(raw) {
|
|
|
57475
57858
|
throw new Error("Invalid CLI package.json: missing version");
|
|
57476
57859
|
}
|
|
57477
57860
|
function getCliVersion() {
|
|
57478
|
-
return parsePackageVersion(
|
|
57861
|
+
return parsePackageVersion(readFileSync19(packageJsonUrl(), "utf8"));
|
|
57479
57862
|
}
|
|
57480
57863
|
|
|
57481
57864
|
// src/cli.ts
|