@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 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 = args5.output === void 0 ? "json" : String(args5.output);
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
- writeJsonEnvelope(
33078
- conversionsEnvelope(
33079
- args6,
33080
- mergeStoredWithCounts(stored, report.data.conversions ?? []),
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));