@koda-sl/baker-cli 0.346.0 → 0.347.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 +1 -1
- package/dist/cli.js +304 -235
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1281,7 +1281,7 @@ baker analytics tracking --platform meta # the same for one platform, read from
|
|
|
1281
1281
|
baker analytics map --platform google --set kw=keyword # name a parameter, seen or not yet
|
|
1282
1282
|
baker analytics map --platform google --remove kw # take that answer back
|
|
1283
1283
|
baker analytics conversions # what counts, what each produced, and which Forms count nothing
|
|
1284
|
-
baker analytics conversions --candidates # everything these pages do, and which of it is counted
|
|
1284
|
+
baker analytics conversions --candidates --output md # everything these pages do, and which of it is counted, as tables
|
|
1285
1285
|
baker analytics conversions --flow contact # one Form's endings as event keys, traffic or not
|
|
1286
1286
|
baker analytics conversions --event page:request_demo --name "Demo requested"
|
|
1287
1287
|
baker analytics conversions --event page:a --event submit:b --name "Lead" # both, as one number
|
package/dist/cli.js
CHANGED
|
@@ -30627,6 +30627,286 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
30627
30627
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
30628
30628
|
}
|
|
30629
30629
|
|
|
30630
|
+
// src/commands/analytics/render.ts
|
|
30631
|
+
var SPARKLINE_POINTS = 30;
|
|
30632
|
+
var DASHBOARD_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
30633
|
+
// The Convex row id. It exists so a dashboard can link to the row; an agent
|
|
30634
|
+
// addresses a landing by `landingId`, which is the id in its `_definition.md`.
|
|
30635
|
+
"landingRef"
|
|
30636
|
+
]);
|
|
30637
|
+
function isAbsent(value) {
|
|
30638
|
+
if (value === "") return true;
|
|
30639
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
30640
|
+
if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
|
|
30641
|
+
return false;
|
|
30642
|
+
}
|
|
30643
|
+
function resample(series, points) {
|
|
30644
|
+
if (series.length <= points || points < 1) return [...series];
|
|
30645
|
+
const out = [];
|
|
30646
|
+
for (let i = 0; i < points; i += 1) {
|
|
30647
|
+
const from = Math.floor(i * series.length / points);
|
|
30648
|
+
const to = Math.floor((i + 1) * series.length / points);
|
|
30649
|
+
let sum = 0;
|
|
30650
|
+
for (let j = from; j < to; j += 1) sum += series[j] ?? 0;
|
|
30651
|
+
out.push(sum);
|
|
30652
|
+
}
|
|
30653
|
+
return out;
|
|
30654
|
+
}
|
|
30655
|
+
function pruneValue(value, full) {
|
|
30656
|
+
if (Array.isArray(value)) return value.map((entry) => pruneValue(entry, full));
|
|
30657
|
+
if (typeof value === "object" && value !== null) {
|
|
30658
|
+
return pruneRow(value, full);
|
|
30659
|
+
}
|
|
30660
|
+
return value;
|
|
30661
|
+
}
|
|
30662
|
+
function pruneRow(row, full) {
|
|
30663
|
+
const out = {};
|
|
30664
|
+
for (const [key, value] of Object.entries(row)) {
|
|
30665
|
+
if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
|
|
30666
|
+
if (key === "sparkline" && Array.isArray(value)) {
|
|
30667
|
+
if (!full) continue;
|
|
30668
|
+
out[key] = resample(value, SPARKLINE_POINTS);
|
|
30669
|
+
continue;
|
|
30670
|
+
}
|
|
30671
|
+
if (isAbsent(value)) continue;
|
|
30672
|
+
out[key] = pruneValue(value, full);
|
|
30673
|
+
}
|
|
30674
|
+
return out;
|
|
30675
|
+
}
|
|
30676
|
+
function pruneForAgent(data, full) {
|
|
30677
|
+
return pruneRow(data, full);
|
|
30678
|
+
}
|
|
30679
|
+
function isTabular(rows) {
|
|
30680
|
+
return rows.every(
|
|
30681
|
+
(row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
|
|
30682
|
+
(value) => value === null || typeof value !== "object" || Array.isArray(value) && value.every((e) => typeof e !== "object")
|
|
30683
|
+
)
|
|
30684
|
+
);
|
|
30685
|
+
}
|
|
30686
|
+
function cell2(value, key) {
|
|
30687
|
+
if (value === null || value === void 0) return "";
|
|
30688
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
30689
|
+
if (typeof value === "number" && !Number.isInteger(value)) {
|
|
30690
|
+
if (key?.endsWith("Ms")) return String(Math.round(value));
|
|
30691
|
+
return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
30692
|
+
}
|
|
30693
|
+
return String(value);
|
|
30694
|
+
}
|
|
30695
|
+
var IDENTITY_COLUMNS = [
|
|
30696
|
+
"landingId",
|
|
30697
|
+
"name",
|
|
30698
|
+
"slug",
|
|
30699
|
+
"path",
|
|
30700
|
+
"flowSlug",
|
|
30701
|
+
"stepId",
|
|
30702
|
+
"value",
|
|
30703
|
+
"key",
|
|
30704
|
+
"bucket",
|
|
30705
|
+
"tag",
|
|
30706
|
+
"eventName",
|
|
30707
|
+
"release",
|
|
30708
|
+
"place",
|
|
30709
|
+
"bot",
|
|
30710
|
+
"source",
|
|
30711
|
+
"medium",
|
|
30712
|
+
"campaign",
|
|
30713
|
+
"platform",
|
|
30714
|
+
"sessionId",
|
|
30715
|
+
"eventId"
|
|
30716
|
+
];
|
|
30717
|
+
function byIdentityFirst(a, b) {
|
|
30718
|
+
const ia = IDENTITY_COLUMNS.indexOf(a);
|
|
30719
|
+
const ib = IDENTITY_COLUMNS.indexOf(b);
|
|
30720
|
+
if (ia !== -1 || ib !== -1)
|
|
30721
|
+
return (ia === -1 ? Number.MAX_SAFE_INTEGER : ia) - (ib === -1 ? Number.MAX_SAFE_INTEGER : ib);
|
|
30722
|
+
return 0;
|
|
30723
|
+
}
|
|
30724
|
+
var BLOCK_ORDER = ["window", "totals", "previous", "pageInfo"];
|
|
30725
|
+
function byBlockOrder(a, b) {
|
|
30726
|
+
const rank = (key) => {
|
|
30727
|
+
const at = BLOCK_ORDER.indexOf(key);
|
|
30728
|
+
if (at !== -1) return at;
|
|
30729
|
+
return key === "preset" ? BLOCK_ORDER.length + 1 : BLOCK_ORDER.length;
|
|
30730
|
+
};
|
|
30731
|
+
return rank(a) - rank(b);
|
|
30732
|
+
}
|
|
30733
|
+
function columnsOf(rows) {
|
|
30734
|
+
const seen = [];
|
|
30735
|
+
for (const row of rows) {
|
|
30736
|
+
for (const key of Object.keys(row)) {
|
|
30737
|
+
if (!seen.includes(key)) seen.push(key);
|
|
30738
|
+
}
|
|
30739
|
+
}
|
|
30740
|
+
return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
|
|
30741
|
+
}
|
|
30742
|
+
function table2(rows) {
|
|
30743
|
+
const cols = columnsOf(rows);
|
|
30744
|
+
if (cols.length === 0) return "";
|
|
30745
|
+
const head = `| ${cols.join(" | ")} |`;
|
|
30746
|
+
const rule = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
30747
|
+
const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
|
|
30748
|
+
return [head, rule, ...body].join("\n");
|
|
30749
|
+
}
|
|
30750
|
+
function isRowList(value) {
|
|
30751
|
+
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
|
|
30752
|
+
}
|
|
30753
|
+
function labelOf(row, index) {
|
|
30754
|
+
for (const key of IDENTITY_COLUMNS) {
|
|
30755
|
+
const value = row[key];
|
|
30756
|
+
if (typeof value === "string" && value !== "") return value;
|
|
30757
|
+
}
|
|
30758
|
+
return `#${index + 1}`;
|
|
30759
|
+
}
|
|
30760
|
+
function nestedBlock(name, rows) {
|
|
30761
|
+
const parents = rows.map(
|
|
30762
|
+
(row) => Object.fromEntries(Object.entries(row).filter(([, value]) => !isRowList(value) && !isPlainRow(value)))
|
|
30763
|
+
);
|
|
30764
|
+
const parts = [`### ${name}`, table2(parents)];
|
|
30765
|
+
rows.forEach((row, index) => {
|
|
30766
|
+
for (const [key, value] of Object.entries(row)) {
|
|
30767
|
+
if (isRowList(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value));
|
|
30768
|
+
else if (isPlainRow(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value]));
|
|
30769
|
+
}
|
|
30770
|
+
});
|
|
30771
|
+
return parts.filter((part) => part !== "").join("\n");
|
|
30772
|
+
}
|
|
30773
|
+
function isPlainRow(value) {
|
|
30774
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30775
|
+
}
|
|
30776
|
+
function block(name, value) {
|
|
30777
|
+
if (Array.isArray(value)) {
|
|
30778
|
+
if (value.length === 0) return "";
|
|
30779
|
+
if (isTabular(value)) return `### ${name}
|
|
30780
|
+
${table2(value)}`;
|
|
30781
|
+
if (isRowList(value)) return nestedBlock(name, value);
|
|
30782
|
+
return `### ${name}
|
|
30783
|
+
${JSON.stringify(value)}`;
|
|
30784
|
+
}
|
|
30785
|
+
if (typeof value === "object" && value !== null) {
|
|
30786
|
+
const entries = Object.entries(value);
|
|
30787
|
+
if (entries.length === 0) return "";
|
|
30788
|
+
if (entries.every(([, v]) => v === null || typeof v !== "object")) {
|
|
30789
|
+
return `### ${name}
|
|
30790
|
+
${entries.map(([k, v]) => `- ${k}: ${v === null ? "null" : cell2(v, k)}`).join("\n")}`;
|
|
30791
|
+
}
|
|
30792
|
+
return `### ${name}
|
|
30793
|
+
${JSON.stringify(value)}`;
|
|
30794
|
+
}
|
|
30795
|
+
return `### ${name}
|
|
30796
|
+
${cell2(value)}`;
|
|
30797
|
+
}
|
|
30798
|
+
function renderMarkdown3(envelope) {
|
|
30799
|
+
const parts = [];
|
|
30800
|
+
for (const warning of envelope.warnings ?? []) parts.push(`> **${warning.code}** \u2014 ${warning.message}`);
|
|
30801
|
+
for (const hint of envelope.hints ?? []) parts.push(`> ${hint}`);
|
|
30802
|
+
for (const [name, value] of Object.entries(envelope.data).sort(([a], [b]) => byBlockOrder(a, b))) {
|
|
30803
|
+
const rendered = block(name, value);
|
|
30804
|
+
if (rendered !== "") parts.push(rendered);
|
|
30805
|
+
}
|
|
30806
|
+
return `${parts.join("\n\n")}
|
|
30807
|
+
`;
|
|
30808
|
+
}
|
|
30809
|
+
function missingBreakdownHint(data, asked) {
|
|
30810
|
+
if (asked.eventName === void 0) return [];
|
|
30811
|
+
const breakdown = data.eventBreakdown;
|
|
30812
|
+
if (Array.isArray(breakdown) && breakdown.length > 0) return [];
|
|
30813
|
+
const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
|
|
30814
|
+
return [
|
|
30815
|
+
`No rows came back for --event ${asked.eventName}${asked.property ? ` --property ${asked.property}` : ""}. That event or property does not exist on these pages${known.length > 0 ? `; the ones that do are: ${known.slice(0, 12).join(", ")}${known.length > 12 ? ", \u2026" : ""}` : ""}.`
|
|
30816
|
+
];
|
|
30817
|
+
}
|
|
30818
|
+
var ANALYTICS_OUTPUT_FORMATS = ["json", "md"];
|
|
30819
|
+
function isAnalyticsOutputFormat(value) {
|
|
30820
|
+
return ANALYTICS_OUTPUT_FORMATS.includes(value);
|
|
30821
|
+
}
|
|
30822
|
+
function writeAnalyticsReport(envelope, format) {
|
|
30823
|
+
if (format === "md") {
|
|
30824
|
+
process.stdout.write(renderMarkdown3(envelope));
|
|
30825
|
+
return;
|
|
30826
|
+
}
|
|
30827
|
+
process.stdout.write(`${JSON.stringify(envelope)}
|
|
30828
|
+
`);
|
|
30829
|
+
}
|
|
30830
|
+
var ROWS_BEFORE_FORMAT_HINT = 25;
|
|
30831
|
+
function rowCount(data) {
|
|
30832
|
+
let total = 0;
|
|
30833
|
+
for (const value of Object.values(data)) {
|
|
30834
|
+
if (Array.isArray(value)) total += value.length;
|
|
30835
|
+
}
|
|
30836
|
+
return total;
|
|
30837
|
+
}
|
|
30838
|
+
function formatHint(data, format) {
|
|
30839
|
+
if (format === "md") return [];
|
|
30840
|
+
const rows = rowCount(data);
|
|
30841
|
+
if (rows < ROWS_BEFORE_FORMAT_HINT) return [];
|
|
30842
|
+
return [
|
|
30843
|
+
`This report is ${rows} rows. If you are going to read the numbers rather than pick one field out of them, --output md returns the same answer as tables, typically a third to a half smaller.`
|
|
30844
|
+
];
|
|
30845
|
+
}
|
|
30846
|
+
|
|
30847
|
+
// src/commands/analytics/conversionsMarkdown.ts
|
|
30848
|
+
function doorsCell(doors) {
|
|
30849
|
+
return doors.map((door) => `${door.door}=${door.events}/${door.sessions}`).join(" ");
|
|
30850
|
+
}
|
|
30851
|
+
function eventKeysCell(eventKeys) {
|
|
30852
|
+
const formKeys = eventKeys.filter((key) => key.startsWith("form:")).length;
|
|
30853
|
+
const others = eventKeys.filter((key) => !key.startsWith("form:"));
|
|
30854
|
+
return [...others, ...formKeys > 0 ? [`${formKeys} Form ending(s)`] : []].join(" + ");
|
|
30855
|
+
}
|
|
30856
|
+
function trafficByForm(candidates) {
|
|
30857
|
+
const byForm = /* @__PURE__ */ new Map();
|
|
30858
|
+
for (const row of candidates) {
|
|
30859
|
+
const form = byForm.get(row.flowSlug) ?? { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
|
|
30860
|
+
if (row.events > 0) form["endings seen"] += 1;
|
|
30861
|
+
if (row.isCounted) form["endings counted"] += 1;
|
|
30862
|
+
form.events += row.events;
|
|
30863
|
+
if (row.lastSeen && row.lastSeen.slice(0, 10) > form.lastSeen) form.lastSeen = row.lastSeen.slice(0, 10);
|
|
30864
|
+
byForm.set(row.flowSlug, form);
|
|
30865
|
+
}
|
|
30866
|
+
return byForm;
|
|
30867
|
+
}
|
|
30868
|
+
var NO_TRAFFIC = { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
|
|
30869
|
+
var FORM_KEYS_HINT = "A Form's candidates are one row per Form: `baker analytics conversions --flow <slug>` lists that Form's event keys, and `--candidates --output json` lists every key.";
|
|
30870
|
+
function renderConversionsMarkdown(envelope) {
|
|
30871
|
+
const { definitions, flow, flows, candidates } = envelope.data;
|
|
30872
|
+
const data = {
|
|
30873
|
+
definitions: definitions.map(({ name, eventKeys, ...row }) => ({
|
|
30874
|
+
name,
|
|
30875
|
+
eventKeys: eventKeysCell(eventKeys),
|
|
30876
|
+
...row
|
|
30877
|
+
}))
|
|
30878
|
+
};
|
|
30879
|
+
if (flow) {
|
|
30880
|
+
data.flow = { slug: flow.slug, noConversionReason: flow.noConversionReason };
|
|
30881
|
+
data.flowEndings = flow.endings;
|
|
30882
|
+
}
|
|
30883
|
+
const traffic = candidates ? trafficByForm(candidates.filter((row) => row.flowSlug !== "")) : /* @__PURE__ */ new Map();
|
|
30884
|
+
if (flows) {
|
|
30885
|
+
data.flows = flows.map((row) => ({
|
|
30886
|
+
slug: row.slug,
|
|
30887
|
+
counted: row.counted.map((entry) => `${entry.name} @ ${entry.step}`).join("; "),
|
|
30888
|
+
noConversion: row.noConversion,
|
|
30889
|
+
...candidates ? traffic.get(row.slug) ?? NO_TRAFFIC : {}
|
|
30890
|
+
}));
|
|
30891
|
+
}
|
|
30892
|
+
const hints2 = [...envelope.hints ?? []];
|
|
30893
|
+
if (candidates) {
|
|
30894
|
+
const listed2 = new Set(flows?.map((row) => row.slug));
|
|
30895
|
+
const unlisted = [...traffic].filter(([slug]) => !listed2.has(slug));
|
|
30896
|
+
if (unlisted.length > 0) data.formCandidates = unlisted.map(([form, row]) => ({ form, ...row }));
|
|
30897
|
+
data.candidates = candidates.filter((row) => row.flowSlug === "").map(({ doors, flowSlug: _flowSlug, nodeType: _nodeType, ...row }) => ({
|
|
30898
|
+
...row,
|
|
30899
|
+
"doors (events/sessions)": doorsCell(doors)
|
|
30900
|
+
}));
|
|
30901
|
+
if (traffic.size > 0) hints2.push(FORM_KEYS_HINT);
|
|
30902
|
+
}
|
|
30903
|
+
return renderMarkdown3({ data, ...hints2.length > 0 ? { hints: hints2 } : {} });
|
|
30904
|
+
}
|
|
30905
|
+
function withFormatHint(envelope, format) {
|
|
30906
|
+
const hints2 = [...envelope.hints ?? [], ...formatHint(envelope.data, format)];
|
|
30907
|
+
return { ...envelope, ...hints2.length > 0 ? { hints: hints2 } : {} };
|
|
30908
|
+
}
|
|
30909
|
+
|
|
30630
30910
|
// src/commands/analytics/errorEnvelope.ts
|
|
30631
30911
|
function analyticsErrorEnvelope(err) {
|
|
30632
30912
|
if (err instanceof ApiError) {
|
|
@@ -31118,223 +31398,6 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
31118
31398
|
}
|
|
31119
31399
|
];
|
|
31120
31400
|
|
|
31121
|
-
// src/commands/analytics/render.ts
|
|
31122
|
-
var SPARKLINE_POINTS = 30;
|
|
31123
|
-
var DASHBOARD_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
31124
|
-
// The Convex row id. It exists so a dashboard can link to the row; an agent
|
|
31125
|
-
// addresses a landing by `landingId`, which is the id in its `_definition.md`.
|
|
31126
|
-
"landingRef"
|
|
31127
|
-
]);
|
|
31128
|
-
function isAbsent(value) {
|
|
31129
|
-
if (value === "") return true;
|
|
31130
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
31131
|
-
if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
|
|
31132
|
-
return false;
|
|
31133
|
-
}
|
|
31134
|
-
function resample(series, points) {
|
|
31135
|
-
if (series.length <= points || points < 1) return [...series];
|
|
31136
|
-
const out = [];
|
|
31137
|
-
for (let i = 0; i < points; i += 1) {
|
|
31138
|
-
const from = Math.floor(i * series.length / points);
|
|
31139
|
-
const to = Math.floor((i + 1) * series.length / points);
|
|
31140
|
-
let sum = 0;
|
|
31141
|
-
for (let j = from; j < to; j += 1) sum += series[j] ?? 0;
|
|
31142
|
-
out.push(sum);
|
|
31143
|
-
}
|
|
31144
|
-
return out;
|
|
31145
|
-
}
|
|
31146
|
-
function pruneValue(value, full) {
|
|
31147
|
-
if (Array.isArray(value)) return value.map((entry) => pruneValue(entry, full));
|
|
31148
|
-
if (typeof value === "object" && value !== null) {
|
|
31149
|
-
return pruneRow(value, full);
|
|
31150
|
-
}
|
|
31151
|
-
return value;
|
|
31152
|
-
}
|
|
31153
|
-
function pruneRow(row, full) {
|
|
31154
|
-
const out = {};
|
|
31155
|
-
for (const [key, value] of Object.entries(row)) {
|
|
31156
|
-
if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
|
|
31157
|
-
if (key === "sparkline" && Array.isArray(value)) {
|
|
31158
|
-
if (!full) continue;
|
|
31159
|
-
out[key] = resample(value, SPARKLINE_POINTS);
|
|
31160
|
-
continue;
|
|
31161
|
-
}
|
|
31162
|
-
if (isAbsent(value)) continue;
|
|
31163
|
-
out[key] = pruneValue(value, full);
|
|
31164
|
-
}
|
|
31165
|
-
return out;
|
|
31166
|
-
}
|
|
31167
|
-
function pruneForAgent(data, full) {
|
|
31168
|
-
return pruneRow(data, full);
|
|
31169
|
-
}
|
|
31170
|
-
function isTabular(rows) {
|
|
31171
|
-
return rows.every(
|
|
31172
|
-
(row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
|
|
31173
|
-
(value) => value === null || typeof value !== "object" || Array.isArray(value) && value.every((e) => typeof e !== "object")
|
|
31174
|
-
)
|
|
31175
|
-
);
|
|
31176
|
-
}
|
|
31177
|
-
function cell2(value, key) {
|
|
31178
|
-
if (value === null || value === void 0) return "";
|
|
31179
|
-
if (Array.isArray(value)) return value.join(" ");
|
|
31180
|
-
if (typeof value === "number" && !Number.isInteger(value)) {
|
|
31181
|
-
if (key?.endsWith("Ms")) return String(Math.round(value));
|
|
31182
|
-
return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
31183
|
-
}
|
|
31184
|
-
return String(value);
|
|
31185
|
-
}
|
|
31186
|
-
var IDENTITY_COLUMNS = [
|
|
31187
|
-
"landingId",
|
|
31188
|
-
"name",
|
|
31189
|
-
"slug",
|
|
31190
|
-
"path",
|
|
31191
|
-
"flowSlug",
|
|
31192
|
-
"stepId",
|
|
31193
|
-
"value",
|
|
31194
|
-
"key",
|
|
31195
|
-
"bucket",
|
|
31196
|
-
"tag",
|
|
31197
|
-
"eventName",
|
|
31198
|
-
"release",
|
|
31199
|
-
"place",
|
|
31200
|
-
"bot",
|
|
31201
|
-
"source",
|
|
31202
|
-
"medium",
|
|
31203
|
-
"campaign",
|
|
31204
|
-
"platform",
|
|
31205
|
-
"sessionId",
|
|
31206
|
-
"eventId"
|
|
31207
|
-
];
|
|
31208
|
-
function byIdentityFirst(a, b) {
|
|
31209
|
-
const ia = IDENTITY_COLUMNS.indexOf(a);
|
|
31210
|
-
const ib = IDENTITY_COLUMNS.indexOf(b);
|
|
31211
|
-
if (ia !== -1 || ib !== -1)
|
|
31212
|
-
return (ia === -1 ? Number.MAX_SAFE_INTEGER : ia) - (ib === -1 ? Number.MAX_SAFE_INTEGER : ib);
|
|
31213
|
-
return 0;
|
|
31214
|
-
}
|
|
31215
|
-
var BLOCK_ORDER = ["window", "totals", "previous", "pageInfo"];
|
|
31216
|
-
function byBlockOrder(a, b) {
|
|
31217
|
-
const rank = (key) => {
|
|
31218
|
-
const at = BLOCK_ORDER.indexOf(key);
|
|
31219
|
-
if (at !== -1) return at;
|
|
31220
|
-
return key === "preset" ? BLOCK_ORDER.length + 1 : BLOCK_ORDER.length;
|
|
31221
|
-
};
|
|
31222
|
-
return rank(a) - rank(b);
|
|
31223
|
-
}
|
|
31224
|
-
function columnsOf(rows) {
|
|
31225
|
-
const seen = [];
|
|
31226
|
-
for (const row of rows) {
|
|
31227
|
-
for (const key of Object.keys(row)) {
|
|
31228
|
-
if (!seen.includes(key)) seen.push(key);
|
|
31229
|
-
}
|
|
31230
|
-
}
|
|
31231
|
-
return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
|
|
31232
|
-
}
|
|
31233
|
-
function table2(rows) {
|
|
31234
|
-
const cols = columnsOf(rows);
|
|
31235
|
-
if (cols.length === 0) return "";
|
|
31236
|
-
const head = `| ${cols.join(" | ")} |`;
|
|
31237
|
-
const rule = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
31238
|
-
const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
|
|
31239
|
-
return [head, rule, ...body].join("\n");
|
|
31240
|
-
}
|
|
31241
|
-
function isRowList(value) {
|
|
31242
|
-
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
|
|
31243
|
-
}
|
|
31244
|
-
function labelOf(row, index) {
|
|
31245
|
-
for (const key of IDENTITY_COLUMNS) {
|
|
31246
|
-
const value = row[key];
|
|
31247
|
-
if (typeof value === "string" && value !== "") return value;
|
|
31248
|
-
}
|
|
31249
|
-
return `#${index + 1}`;
|
|
31250
|
-
}
|
|
31251
|
-
function nestedBlock(name, rows) {
|
|
31252
|
-
const parents = rows.map(
|
|
31253
|
-
(row) => Object.fromEntries(Object.entries(row).filter(([, value]) => !isRowList(value) && !isPlainRow(value)))
|
|
31254
|
-
);
|
|
31255
|
-
const parts = [`### ${name}`, table2(parents)];
|
|
31256
|
-
rows.forEach((row, index) => {
|
|
31257
|
-
for (const [key, value] of Object.entries(row)) {
|
|
31258
|
-
if (isRowList(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value));
|
|
31259
|
-
else if (isPlainRow(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value]));
|
|
31260
|
-
}
|
|
31261
|
-
});
|
|
31262
|
-
return parts.filter((part) => part !== "").join("\n");
|
|
31263
|
-
}
|
|
31264
|
-
function isPlainRow(value) {
|
|
31265
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31266
|
-
}
|
|
31267
|
-
function block(name, value) {
|
|
31268
|
-
if (Array.isArray(value)) {
|
|
31269
|
-
if (value.length === 0) return "";
|
|
31270
|
-
if (isTabular(value)) return `### ${name}
|
|
31271
|
-
${table2(value)}`;
|
|
31272
|
-
if (isRowList(value)) return nestedBlock(name, value);
|
|
31273
|
-
return `### ${name}
|
|
31274
|
-
${JSON.stringify(value)}`;
|
|
31275
|
-
}
|
|
31276
|
-
if (typeof value === "object" && value !== null) {
|
|
31277
|
-
const entries = Object.entries(value);
|
|
31278
|
-
if (entries.length === 0) return "";
|
|
31279
|
-
if (entries.every(([, v]) => v === null || typeof v !== "object")) {
|
|
31280
|
-
return `### ${name}
|
|
31281
|
-
${entries.map(([k, v]) => `- ${k}: ${v === null ? "null" : cell2(v, k)}`).join("\n")}`;
|
|
31282
|
-
}
|
|
31283
|
-
return `### ${name}
|
|
31284
|
-
${JSON.stringify(value)}`;
|
|
31285
|
-
}
|
|
31286
|
-
return `### ${name}
|
|
31287
|
-
${cell2(value)}`;
|
|
31288
|
-
}
|
|
31289
|
-
function renderMarkdown3(envelope) {
|
|
31290
|
-
const parts = [];
|
|
31291
|
-
for (const warning of envelope.warnings ?? []) parts.push(`> **${warning.code}** \u2014 ${warning.message}`);
|
|
31292
|
-
for (const hint of envelope.hints ?? []) parts.push(`> ${hint}`);
|
|
31293
|
-
for (const [name, value] of Object.entries(envelope.data).sort(([a], [b]) => byBlockOrder(a, b))) {
|
|
31294
|
-
const rendered = block(name, value);
|
|
31295
|
-
if (rendered !== "") parts.push(rendered);
|
|
31296
|
-
}
|
|
31297
|
-
return `${parts.join("\n\n")}
|
|
31298
|
-
`;
|
|
31299
|
-
}
|
|
31300
|
-
function missingBreakdownHint(data, asked) {
|
|
31301
|
-
if (asked.eventName === void 0) return [];
|
|
31302
|
-
const breakdown = data.eventBreakdown;
|
|
31303
|
-
if (Array.isArray(breakdown) && breakdown.length > 0) return [];
|
|
31304
|
-
const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
|
|
31305
|
-
return [
|
|
31306
|
-
`No rows came back for --event ${asked.eventName}${asked.property ? ` --property ${asked.property}` : ""}. That event or property does not exist on these pages${known.length > 0 ? `; the ones that do are: ${known.slice(0, 12).join(", ")}${known.length > 12 ? ", \u2026" : ""}` : ""}.`
|
|
31307
|
-
];
|
|
31308
|
-
}
|
|
31309
|
-
var ANALYTICS_OUTPUT_FORMATS = ["json", "md"];
|
|
31310
|
-
function isAnalyticsOutputFormat(value) {
|
|
31311
|
-
return ANALYTICS_OUTPUT_FORMATS.includes(value);
|
|
31312
|
-
}
|
|
31313
|
-
function writeAnalyticsReport(envelope, format) {
|
|
31314
|
-
if (format === "md") {
|
|
31315
|
-
process.stdout.write(renderMarkdown3(envelope));
|
|
31316
|
-
return;
|
|
31317
|
-
}
|
|
31318
|
-
process.stdout.write(`${JSON.stringify(envelope)}
|
|
31319
|
-
`);
|
|
31320
|
-
}
|
|
31321
|
-
var ROWS_BEFORE_FORMAT_HINT = 25;
|
|
31322
|
-
function rowCount(data) {
|
|
31323
|
-
let total = 0;
|
|
31324
|
-
for (const value of Object.values(data)) {
|
|
31325
|
-
if (Array.isArray(value)) total += value.length;
|
|
31326
|
-
}
|
|
31327
|
-
return total;
|
|
31328
|
-
}
|
|
31329
|
-
function formatHint(data, format) {
|
|
31330
|
-
if (format === "md") return [];
|
|
31331
|
-
const rows = rowCount(data);
|
|
31332
|
-
if (rows < ROWS_BEFORE_FORMAT_HINT) return [];
|
|
31333
|
-
return [
|
|
31334
|
-
`This report is ${rows} rows. If you are going to read the numbers rather than pick one field out of them, --output md returns the same answer as tables, typically a third to a half smaller.`
|
|
31335
|
-
];
|
|
31336
|
-
}
|
|
31337
|
-
|
|
31338
31401
|
// src/commands/analytics/sends.ts
|
|
31339
31402
|
import { defineCommand as defineCommand97 } from "citty";
|
|
31340
31403
|
var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
|
|
@@ -32509,17 +32572,7 @@ async function runPreset(args5, options) {
|
|
|
32509
32572
|
const response = await apiPost("/api/analytics/query", requestBody(args5, options));
|
|
32510
32573
|
const requestedPlatform = args5.platform === void 0 ? "" : String(args5.platform);
|
|
32511
32574
|
const adPlatformScope = isAdPlatform(requestedPlatform) ? requestedPlatform : void 0;
|
|
32512
|
-
const format =
|
|
32513
|
-
if (!isAnalyticsOutputFormat(format)) {
|
|
32514
|
-
writeJsonEnvelope({
|
|
32515
|
-
ok: false,
|
|
32516
|
-
error: {
|
|
32517
|
-
code: "VALIDATION_ERROR",
|
|
32518
|
-
message: `Unknown --output "${format}". Use one of: ${ANALYTICS_OUTPUT_FORMATS.join(", ")}.`
|
|
32519
|
-
}
|
|
32520
|
-
});
|
|
32521
|
-
process.exit(1);
|
|
32522
|
-
}
|
|
32575
|
+
const format = outputFormatOrExit(args5.output);
|
|
32523
32576
|
const data = pruneForAgent(response.data, args5.full === true);
|
|
32524
32577
|
const feed = "stream" in data ? shapeFeed(data.stream, args5.everything === true) : void 0;
|
|
32525
32578
|
if (feed !== void 0) data.stream = feed.rows;
|
|
@@ -32549,6 +32602,20 @@ async function runPreset(args5, options) {
|
|
|
32549
32602
|
handleError3(err);
|
|
32550
32603
|
}
|
|
32551
32604
|
}
|
|
32605
|
+
function outputFormatOrExit(raw) {
|
|
32606
|
+
const format = raw === void 0 ? "json" : String(raw);
|
|
32607
|
+
if (!isAnalyticsOutputFormat(format)) {
|
|
32608
|
+
writeJsonEnvelope({
|
|
32609
|
+
ok: false,
|
|
32610
|
+
error: {
|
|
32611
|
+
code: "VALIDATION_ERROR",
|
|
32612
|
+
message: `Unknown --output "${format}". Use one of: ${ANALYTICS_OUTPUT_FORMATS.join(", ")}.`
|
|
32613
|
+
}
|
|
32614
|
+
});
|
|
32615
|
+
process.exit(1);
|
|
32616
|
+
}
|
|
32617
|
+
return format;
|
|
32618
|
+
}
|
|
32552
32619
|
function presetCommand(options) {
|
|
32553
32620
|
registerSchema({
|
|
32554
32621
|
command: `analytics.${options.name}`,
|
|
@@ -33050,7 +33117,8 @@ var conversionsCommand3 = (() => {
|
|
|
33050
33117
|
type: "string",
|
|
33051
33118
|
description: "Lookback for the counts and for --candidates. Default 30",
|
|
33052
33119
|
required: false
|
|
33053
|
-
}
|
|
33120
|
+
},
|
|
33121
|
+
output: SHARED_ARGS.output
|
|
33054
33122
|
};
|
|
33055
33123
|
registerSchema({
|
|
33056
33124
|
command: "analytics.conversions",
|
|
@@ -33065,6 +33133,7 @@ var conversionsCommand3 = (() => {
|
|
|
33065
33133
|
args: args5,
|
|
33066
33134
|
run: async ({ args: raw }) => {
|
|
33067
33135
|
try {
|
|
33136
|
+
const format = outputFormatOrExit(raw.output);
|
|
33068
33137
|
const args6 = parseConversionsArgs(raw, process.argv);
|
|
33069
33138
|
for (const eventKey of args6.eventKeys) refuseUnmatchableKey(eventKey);
|
|
33070
33139
|
const before = await apiPost("/api/analytics/conversions", {});
|
|
@@ -33074,13 +33143,13 @@ var conversionsCommand3 = (() => {
|
|
|
33074
33143
|
preset: "conversions",
|
|
33075
33144
|
days: args6.days
|
|
33076
33145
|
});
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
|
|
33081
|
-
report.data.eventCatalog ?? []
|
|
33082
|
-
)
|
|
33146
|
+
const envelope = conversionsEnvelope(
|
|
33147
|
+
args6,
|
|
33148
|
+
mergeStoredWithCounts(stored, report.data.conversions ?? []),
|
|
33149
|
+
report.data.eventCatalog ?? []
|
|
33083
33150
|
);
|
|
33151
|
+
if (format === "md") process.stdout.write(renderConversionsMarkdown(envelope));
|
|
33152
|
+
else writeJsonEnvelope(withFormatHint(envelope, format));
|
|
33084
33153
|
} catch (err) {
|
|
33085
33154
|
if (err instanceof ConversionArgsError) {
|
|
33086
33155
|
handleError3(new ApiError("VALIDATION_ERROR", err.message));
|