@funnelsgrove/cli 0.1.263 → 0.1.265
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 -1
- package/dist/analyticsOutput.d.ts +104 -52
- package/dist/analyticsOutput.js +106 -34
- package/dist/cli.d.ts +28 -3
- package/dist/cli.js +167 -69
- package/funnel-contract-compatibility.json +29 -29
- package/package.json +1 -1
- package/template_docs/.funnelsgrove-docs.json +3 -3
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +3 -3
- package/template_scaffold/.funnelsgrove-scaffold.json +5 -5
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ Analytics workflow:
|
|
|
123
123
|
fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
124
124
|
fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
125
125
|
fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
126
|
+
fgrove analytics versions --project claimbee --funnel claimbee-ios
|
|
126
127
|
fgrove analytics cohort --project claimbee --date 2026-06-11
|
|
127
128
|
```
|
|
128
129
|
|
|
@@ -132,7 +133,16 @@ Each analytics command accepts `--format table|json`, `--out <path>`, `--workspa
|
|
|
132
133
|
fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out conversions.json
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
`conversions` downloads
|
|
136
|
+
`conversions` downloads conversion totals, primary conversion metrics, full funnel path rows, and step transitions. `funnel-path` focuses on the ordered path report. `transitions` focuses on step-by-step advanced/drop-off counts. `cohort` downloads synced marketing cohort economics for the day. If the requested period has no synced data, or cohort source data is incomplete, the CLI exits non-zero with a human-readable explanation.
|
|
137
|
+
|
|
138
|
+
`conversions`, `funnel-path`, and `transitions` report one day with `--date`, or a range with `--from` and `--to` (inclusive, totals for the whole range). They report the current Journey Version by default, like Funnel Analytics in the console. Traffic from before the last structural change belongs to an older Journey Version: list the versions with `versions`, then pass `--journey-version <id>`:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
fgrove analytics versions --project claimbee --funnel claimbee-ios
|
|
142
|
+
fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14 --journey-version <journey-version-id>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`versions` lists each Journey Version newest first with its id, start time, active days, and visitors on those days. Active days are dates in `--timezone` (the API default, America/Los_Angeles, when omitted); the day a version replaced its predecessor belongs to both, and each version counts only its own visitors. An explicit version reports its own metrics and its own step structure. `--journey-version all` counts every version and orders `funnel-path` by the current published flow, so steps that exist only in older versions appear in `transitions` but not in `funnel-path`. JSON reports include `from`, `to`, and a `journeyVersion` object (`id`, `seq`, `label`, `startedAt`, `isCurrent`); `date` is set only for a one-day report. Table titles end with the reported version, for example `Journey 2 (current)`. The `overview` block of `conversions` always covers all Journey Versions.
|
|
136
146
|
|
|
137
147
|
The package also keeps the longer `funnelsgrove` command as a compatibility alias.
|
|
138
148
|
|
|
@@ -53,20 +53,8 @@ export type AnalyticsStepDropoffResponse = {
|
|
|
53
53
|
slug: string;
|
|
54
54
|
};
|
|
55
55
|
version: {
|
|
56
|
-
selected:
|
|
57
|
-
|
|
58
|
-
seq: number;
|
|
59
|
-
message: string | null;
|
|
60
|
-
publishedAt: string | null;
|
|
61
|
-
isCurrent: boolean;
|
|
62
|
-
} | null;
|
|
63
|
-
options: Array<{
|
|
64
|
-
id: string;
|
|
65
|
-
seq: number;
|
|
66
|
-
message: string | null;
|
|
67
|
-
publishedAt: string | null;
|
|
68
|
-
isCurrent: boolean;
|
|
69
|
-
}>;
|
|
56
|
+
selected: AnalyticsJourneyVersionOption | null;
|
|
57
|
+
options: AnalyticsJourneyVersionOption[];
|
|
70
58
|
};
|
|
71
59
|
totals: Record<string, number>;
|
|
72
60
|
metrics: Array<{
|
|
@@ -83,6 +71,30 @@ export type AnalyticsStepDropoffResponse = {
|
|
|
83
71
|
steps: AnalyticsStepTransition[];
|
|
84
72
|
flow: AnalyticsFunnelFlow | null;
|
|
85
73
|
};
|
|
74
|
+
export type AnalyticsJourneyVersionOption = {
|
|
75
|
+
id: string;
|
|
76
|
+
seq: number;
|
|
77
|
+
message: string | null;
|
|
78
|
+
publishedAt: string | null;
|
|
79
|
+
isCurrent: boolean;
|
|
80
|
+
};
|
|
81
|
+
export type AnalyticsReportJourneyVersion = {
|
|
82
|
+
id: string;
|
|
83
|
+
seq: number | null;
|
|
84
|
+
label: string;
|
|
85
|
+
startedAt: string | null;
|
|
86
|
+
isCurrent: boolean;
|
|
87
|
+
};
|
|
88
|
+
export type AnalyticsJourneyVersionRow = {
|
|
89
|
+
id: string;
|
|
90
|
+
seq: number;
|
|
91
|
+
label: string;
|
|
92
|
+
isCurrent: boolean;
|
|
93
|
+
startedAt: string | null;
|
|
94
|
+
activeFrom: string | null;
|
|
95
|
+
activeTo: string | null;
|
|
96
|
+
visitors: number | null;
|
|
97
|
+
};
|
|
86
98
|
export type AnalyticsStepTransition = {
|
|
87
99
|
stepId: string;
|
|
88
100
|
stepName: string | null;
|
|
@@ -153,11 +165,32 @@ type ReportContext = {
|
|
|
153
165
|
workspaceId: string;
|
|
154
166
|
project: AnalyticsProjectSummary;
|
|
155
167
|
};
|
|
156
|
-
|
|
168
|
+
type FunnelReportContext = {
|
|
169
|
+
period: AnalyticsPeriod;
|
|
170
|
+
journeyVersion: string;
|
|
171
|
+
workspaceId: string;
|
|
172
|
+
project: AnalyticsProjectSummary;
|
|
173
|
+
stepDropoff: AnalyticsStepDropoffResponse;
|
|
174
|
+
};
|
|
175
|
+
export declare const parseAnalyticsDate: (value: string, flag?: string) => ParsedAnalyticsDate;
|
|
176
|
+
export type AnalyticsPeriod = {
|
|
177
|
+
date: string | null;
|
|
178
|
+
from: string;
|
|
179
|
+
to: string;
|
|
180
|
+
label: string;
|
|
181
|
+
};
|
|
182
|
+
export declare const parseAnalyticsPeriod: (input: {
|
|
183
|
+
date?: string;
|
|
184
|
+
from?: string;
|
|
185
|
+
to?: string;
|
|
186
|
+
}) => AnalyticsPeriod & {
|
|
187
|
+
fromIso: string;
|
|
188
|
+
toIso: string;
|
|
189
|
+
};
|
|
157
190
|
export declare const normalizeAnalyticsOutputFormat: (value: string | undefined) => AnalyticsOutputFormat;
|
|
158
191
|
export declare const assertHasConversionData: (date: string, overview: AnalyticsOverviewResponse, stepDropoff: AnalyticsStepDropoffResponse) => void;
|
|
159
192
|
export declare const assertHasTransitionData: (date: string, stepDropoff: AnalyticsStepDropoffResponse) => void;
|
|
160
|
-
export declare const assertHasFunnelPathData: (date: string, stepDropoff: AnalyticsStepDropoffResponse,
|
|
193
|
+
export declare const assertHasFunnelPathData: (date: string, stepDropoff: AnalyticsStepDropoffResponse, pathFlow: AnalyticsFunnelFlow | null) => void;
|
|
161
194
|
export declare const assertHasCohortData: (date: string, report: MarketingCohortPerformanceReport) => void;
|
|
162
195
|
export declare const buildTransitionRows: (stepDropoff: AnalyticsStepDropoffResponse) => {
|
|
163
196
|
stepId: string;
|
|
@@ -168,7 +201,7 @@ export declare const buildTransitionRows: (stepDropoff: AnalyticsStepDropoffResp
|
|
|
168
201
|
dropoffs: number;
|
|
169
202
|
dropoffRate: number;
|
|
170
203
|
}[];
|
|
171
|
-
export declare const buildFunnelPathRows: (stepDropoff: AnalyticsStepDropoffResponse,
|
|
204
|
+
export declare const buildFunnelPathRows: (stepDropoff: AnalyticsStepDropoffResponse, pathFlow: AnalyticsFunnelFlow | null) => {
|
|
172
205
|
stepId: string;
|
|
173
206
|
path: string;
|
|
174
207
|
label: string;
|
|
@@ -178,20 +211,11 @@ export declare const buildFunnelPathRows: (stepDropoff: AnalyticsStepDropoffResp
|
|
|
178
211
|
dropoffs: number;
|
|
179
212
|
dropoffRate: number;
|
|
180
213
|
}[];
|
|
181
|
-
export declare const
|
|
214
|
+
export declare const journeyVersionLabel: (version: Pick<AnalyticsJourneyVersionOption, "seq">) => string;
|
|
215
|
+
export declare const buildConversionReportPayload: (input: FunnelReportContext & {
|
|
182
216
|
overview: AnalyticsOverviewResponse;
|
|
183
|
-
|
|
184
|
-
publishedFunnelFlow: PublishedFunnelFlowResponse;
|
|
217
|
+
pathFlow: AnalyticsFunnelFlow | null;
|
|
185
218
|
}) => {
|
|
186
|
-
generatedAt: string;
|
|
187
|
-
date: string;
|
|
188
|
-
workspaceId: string;
|
|
189
|
-
project: AnalyticsProjectSummary;
|
|
190
|
-
funnel: {
|
|
191
|
-
id: string;
|
|
192
|
-
name: string;
|
|
193
|
-
slug: string;
|
|
194
|
-
};
|
|
195
219
|
overview: AnalyticsOverviewResponse;
|
|
196
220
|
totals: Record<string, number>;
|
|
197
221
|
metrics: {
|
|
@@ -224,13 +248,10 @@ export declare const buildConversionReportPayload: (input: ReportContext & {
|
|
|
224
248
|
dropoffs: number;
|
|
225
249
|
dropoffRate: number;
|
|
226
250
|
}[];
|
|
227
|
-
};
|
|
228
|
-
export declare const buildFunnelPathReportPayload: (input: ReportContext & {
|
|
229
|
-
stepDropoff: AnalyticsStepDropoffResponse;
|
|
230
|
-
publishedFunnelFlow: PublishedFunnelFlowResponse;
|
|
231
|
-
}) => {
|
|
232
251
|
generatedAt: string;
|
|
233
|
-
date: string;
|
|
252
|
+
date: string | null;
|
|
253
|
+
from: string;
|
|
254
|
+
to: string;
|
|
234
255
|
workspaceId: string;
|
|
235
256
|
project: AnalyticsProjectSummary;
|
|
236
257
|
funnel: {
|
|
@@ -238,6 +259,11 @@ export declare const buildFunnelPathReportPayload: (input: ReportContext & {
|
|
|
238
259
|
name: string;
|
|
239
260
|
slug: string;
|
|
240
261
|
};
|
|
262
|
+
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
263
|
+
};
|
|
264
|
+
export declare const buildFunnelPathReportPayload: (input: FunnelReportContext & {
|
|
265
|
+
pathFlow: AnalyticsFunnelFlow | null;
|
|
266
|
+
}) => {
|
|
241
267
|
funnelPath: {
|
|
242
268
|
stepId: string;
|
|
243
269
|
path: string;
|
|
@@ -248,12 +274,10 @@ export declare const buildFunnelPathReportPayload: (input: ReportContext & {
|
|
|
248
274
|
dropoffs: number;
|
|
249
275
|
dropoffRate: number;
|
|
250
276
|
}[];
|
|
251
|
-
};
|
|
252
|
-
export declare const buildTransitionReportPayload: (input: ReportContext & {
|
|
253
|
-
stepDropoff: AnalyticsStepDropoffResponse;
|
|
254
|
-
}) => {
|
|
255
277
|
generatedAt: string;
|
|
256
|
-
date: string;
|
|
278
|
+
date: string | null;
|
|
279
|
+
from: string;
|
|
280
|
+
to: string;
|
|
257
281
|
workspaceId: string;
|
|
258
282
|
project: AnalyticsProjectSummary;
|
|
259
283
|
funnel: {
|
|
@@ -261,6 +285,9 @@ export declare const buildTransitionReportPayload: (input: ReportContext & {
|
|
|
261
285
|
name: string;
|
|
262
286
|
slug: string;
|
|
263
287
|
};
|
|
288
|
+
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
289
|
+
};
|
|
290
|
+
export declare const buildTransitionReportPayload: (input: FunnelReportContext) => {
|
|
264
291
|
transitions: {
|
|
265
292
|
stepId: string;
|
|
266
293
|
step: string;
|
|
@@ -270,6 +297,18 @@ export declare const buildTransitionReportPayload: (input: ReportContext & {
|
|
|
270
297
|
dropoffs: number;
|
|
271
298
|
dropoffRate: number;
|
|
272
299
|
}[];
|
|
300
|
+
generatedAt: string;
|
|
301
|
+
date: string | null;
|
|
302
|
+
from: string;
|
|
303
|
+
to: string;
|
|
304
|
+
workspaceId: string;
|
|
305
|
+
project: AnalyticsProjectSummary;
|
|
306
|
+
funnel: {
|
|
307
|
+
id: string;
|
|
308
|
+
name: string;
|
|
309
|
+
slug: string;
|
|
310
|
+
};
|
|
311
|
+
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
273
312
|
};
|
|
274
313
|
export declare const buildCohortReportPayload: (input: ReportContext & {
|
|
275
314
|
cohort: MarketingCohortPerformanceReport;
|
|
@@ -285,21 +324,34 @@ export declare const buildCohortReportPayload: (input: ReportContext & {
|
|
|
285
324
|
} | null;
|
|
286
325
|
};
|
|
287
326
|
export declare const formatAnalyticsJson: (payload: unknown) => string;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
327
|
+
type FunnelReportTableInput = Pick<FunnelReportContext, 'period' | 'journeyVersion' | 'stepDropoff'>;
|
|
328
|
+
export declare const formatTransitionsTable: (input: FunnelReportTableInput) => string;
|
|
329
|
+
export declare const formatFunnelPathTable: (input: FunnelReportTableInput & {
|
|
330
|
+
pathFlow: AnalyticsFunnelFlow | null;
|
|
291
331
|
}) => string;
|
|
292
|
-
export declare const
|
|
293
|
-
date: string;
|
|
294
|
-
stepDropoff: AnalyticsStepDropoffResponse;
|
|
295
|
-
publishedFunnelFlow: PublishedFunnelFlowResponse;
|
|
296
|
-
}) => string;
|
|
297
|
-
export declare const formatConversionsTable: (input: {
|
|
298
|
-
date: string;
|
|
332
|
+
export declare const formatConversionsTable: (input: FunnelReportTableInput & {
|
|
299
333
|
overview: AnalyticsOverviewResponse;
|
|
300
|
-
|
|
301
|
-
publishedFunnelFlow: PublishedFunnelFlowResponse;
|
|
334
|
+
pathFlow: AnalyticsFunnelFlow | null;
|
|
302
335
|
}) => string;
|
|
336
|
+
type JourneyVersionsReport = {
|
|
337
|
+
funnel: AnalyticsStepDropoffResponse['funnel'];
|
|
338
|
+
versions: AnalyticsJourneyVersionRow[];
|
|
339
|
+
};
|
|
340
|
+
export declare const buildJourneyVersionsReportPayload: (input: JourneyVersionsReport & {
|
|
341
|
+
workspaceId: string;
|
|
342
|
+
project: AnalyticsProjectSummary;
|
|
343
|
+
}) => {
|
|
344
|
+
generatedAt: string;
|
|
345
|
+
workspaceId: string;
|
|
346
|
+
project: AnalyticsProjectSummary;
|
|
347
|
+
funnel: {
|
|
348
|
+
id: string;
|
|
349
|
+
name: string;
|
|
350
|
+
slug: string;
|
|
351
|
+
};
|
|
352
|
+
versions: AnalyticsJourneyVersionRow[];
|
|
353
|
+
};
|
|
354
|
+
export declare const formatJourneyVersionsTable: (input: JourneyVersionsReport) => string;
|
|
303
355
|
export declare const formatCohortTable: (input: {
|
|
304
356
|
date: string;
|
|
305
357
|
cohort: MarketingCohortPerformanceReport;
|
package/dist/analyticsOutput.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export const parseAnalyticsDate = (value) => {
|
|
1
|
+
export const parseAnalyticsDate = (value, flag = '--date') => {
|
|
2
2
|
const trimmed = value.trim();
|
|
3
3
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
|
4
4
|
if (!match) {
|
|
5
|
-
throw new Error(
|
|
5
|
+
throw new Error(`Expected ${flag} in YYYY-MM-DD format.`);
|
|
6
6
|
}
|
|
7
7
|
const year = Number(match[1]);
|
|
8
8
|
const month = Number(match[2]);
|
|
@@ -13,7 +13,7 @@ export const parseAnalyticsDate = (value) => {
|
|
|
13
13
|
parsed.getUTCMonth() + 1 === month &&
|
|
14
14
|
parsed.getUTCDate() === day;
|
|
15
15
|
if (!isRealDate) {
|
|
16
|
-
throw new Error(
|
|
16
|
+
throw new Error(`Expected ${flag} to be a real calendar date.`);
|
|
17
17
|
}
|
|
18
18
|
return {
|
|
19
19
|
date: trimmed,
|
|
@@ -21,6 +21,32 @@ export const parseAnalyticsDate = (value) => {
|
|
|
21
21
|
toIso: `${trimmed}T23:59:59.999Z`,
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
+
export const parseAnalyticsPeriod = (input) => {
|
|
25
|
+
if (input.date !== undefined) {
|
|
26
|
+
if (input.from !== undefined || input.to !== undefined) {
|
|
27
|
+
throw new Error('Use either --date or --from with --to, not both.');
|
|
28
|
+
}
|
|
29
|
+
const day = parseAnalyticsDate(input.date);
|
|
30
|
+
return { date: day.date, from: day.date, to: day.date, label: day.date, fromIso: day.fromIso, toIso: day.toIso };
|
|
31
|
+
}
|
|
32
|
+
if (input.from === undefined || input.to === undefined) {
|
|
33
|
+
throw new Error('Pass --date, or --from together with --to.');
|
|
34
|
+
}
|
|
35
|
+
const from = parseAnalyticsDate(input.from, '--from');
|
|
36
|
+
const to = parseAnalyticsDate(input.to, '--to');
|
|
37
|
+
if (from.date > to.date) {
|
|
38
|
+
throw new Error('--from must be on or before --to.');
|
|
39
|
+
}
|
|
40
|
+
const date = from.date === to.date ? from.date : null;
|
|
41
|
+
return {
|
|
42
|
+
date,
|
|
43
|
+
from: from.date,
|
|
44
|
+
to: to.date,
|
|
45
|
+
label: date ?? `${from.date}..${to.date}`,
|
|
46
|
+
fromIso: from.fromIso,
|
|
47
|
+
toIso: to.toIso,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
24
50
|
export const normalizeAnalyticsOutputFormat = (value) => {
|
|
25
51
|
const normalized = (value || 'table').trim().toLowerCase();
|
|
26
52
|
if (normalized === 'table' || normalized === 'json') {
|
|
@@ -40,6 +66,9 @@ const hasPositiveNumber = (value) => {
|
|
|
40
66
|
}
|
|
41
67
|
return false;
|
|
42
68
|
};
|
|
69
|
+
const olderJourneyVersionHint = (stepDropoff) => (stepDropoff.version.selected?.isCurrent && stepDropoff.version.options.length > 1
|
|
70
|
+
? ' Traffic on these dates may belong to an older journey version: list them with `fgrove analytics versions` and pass --journey-version.'
|
|
71
|
+
: '');
|
|
43
72
|
export const assertHasConversionData = (date, overview, stepDropoff) => {
|
|
44
73
|
if (hasPositiveNumber(overview.totals) ||
|
|
45
74
|
hasPositiveNumber(overview.byDay) ||
|
|
@@ -47,19 +76,19 @@ export const assertHasConversionData = (date, overview, stepDropoff) => {
|
|
|
47
76
|
hasPositiveNumber(stepDropoff.steps)) {
|
|
48
77
|
return;
|
|
49
78
|
}
|
|
50
|
-
throw new Error(`No synced conversion data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked traffic that
|
|
79
|
+
throw new Error(`No synced conversion data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked traffic in that period.${olderJourneyVersionHint(stepDropoff)}`);
|
|
51
80
|
};
|
|
52
81
|
export const assertHasTransitionData = (date, stepDropoff) => {
|
|
53
82
|
if (stepDropoff.steps.length > 0 && hasPositiveNumber(stepDropoff.steps)) {
|
|
54
83
|
return;
|
|
55
84
|
}
|
|
56
|
-
throw new Error(`No synced step transition data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked step traffic that
|
|
85
|
+
throw new Error(`No synced step transition data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked step traffic in that period.${olderJourneyVersionHint(stepDropoff)}`);
|
|
57
86
|
};
|
|
58
|
-
export const assertHasFunnelPathData = (date, stepDropoff,
|
|
59
|
-
if (buildFunnelPathRows(stepDropoff,
|
|
87
|
+
export const assertHasFunnelPathData = (date, stepDropoff, pathFlow) => {
|
|
88
|
+
if (buildFunnelPathRows(stepDropoff, pathFlow).length > 0) {
|
|
60
89
|
return;
|
|
61
90
|
}
|
|
62
|
-
throw new Error(`No synced full funnel path data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked path traffic that
|
|
91
|
+
throw new Error(`No synced full funnel path data found for ${date}. Analytics may not have synced yet, or the funnel had no tracked path traffic in that period.${olderJourneyVersionHint(stepDropoff)}`);
|
|
63
92
|
};
|
|
64
93
|
export const assertHasCohortData = (date, report) => {
|
|
65
94
|
if (report.sourceHealth && !report.sourceHealth.complete) {
|
|
@@ -96,12 +125,12 @@ export const buildTransitionRows = (stepDropoff) => [...stepDropoff.steps]
|
|
|
96
125
|
dropoffs: step.dropoffs,
|
|
97
126
|
dropoffRate: step.dropoffRate,
|
|
98
127
|
}));
|
|
99
|
-
export const buildFunnelPathRows = (stepDropoff,
|
|
100
|
-
if (
|
|
128
|
+
export const buildFunnelPathRows = (stepDropoff, pathFlow) => {
|
|
129
|
+
if (!pathFlow) {
|
|
101
130
|
return [];
|
|
102
131
|
}
|
|
103
132
|
const transitionsByStepId = new Map(stepDropoff.steps.map((step) => [step.stepId, step]));
|
|
104
|
-
return [...
|
|
133
|
+
return [...pathFlow.steps]
|
|
105
134
|
.sort((left, right) => left.stepOrder - right.stepOrder || left.stepId.localeCompare(right.stepId))
|
|
106
135
|
.map((flowStep) => {
|
|
107
136
|
const step = transitionsByStepId.get(flowStep.stepId);
|
|
@@ -118,32 +147,55 @@ export const buildFunnelPathRows = (stepDropoff, publishedFunnelFlow) => {
|
|
|
118
147
|
};
|
|
119
148
|
});
|
|
120
149
|
};
|
|
121
|
-
export const
|
|
150
|
+
export const journeyVersionLabel = (version) => `Journey ${version.seq}`;
|
|
151
|
+
const describeReportJourneyVersion = (input) => {
|
|
152
|
+
if (input.journeyVersion === 'all') {
|
|
153
|
+
return { id: 'all', seq: null, label: 'All journey versions', startedAt: null, isCurrent: false };
|
|
154
|
+
}
|
|
155
|
+
const selected = input.stepDropoff.version.selected;
|
|
156
|
+
return selected
|
|
157
|
+
? {
|
|
158
|
+
id: selected.id,
|
|
159
|
+
seq: selected.seq,
|
|
160
|
+
label: journeyVersionLabel(selected),
|
|
161
|
+
startedAt: selected.publishedAt,
|
|
162
|
+
isCurrent: selected.isCurrent,
|
|
163
|
+
}
|
|
164
|
+
: null;
|
|
165
|
+
};
|
|
166
|
+
const buildFunnelReportHeader = (input) => ({
|
|
122
167
|
generatedAt: new Date().toISOString(),
|
|
123
|
-
date: input.date,
|
|
168
|
+
date: input.period.date,
|
|
169
|
+
from: input.period.from,
|
|
170
|
+
to: input.period.to,
|
|
124
171
|
workspaceId: input.workspaceId,
|
|
125
172
|
project: input.project,
|
|
126
173
|
funnel: input.stepDropoff.funnel,
|
|
174
|
+
journeyVersion: describeReportJourneyVersion(input),
|
|
175
|
+
});
|
|
176
|
+
const formatFunnelReportTitle = (title, input) => {
|
|
177
|
+
const version = describeReportJourneyVersion(input);
|
|
178
|
+
return [
|
|
179
|
+
title,
|
|
180
|
+
input.period.label,
|
|
181
|
+
input.stepDropoff.funnel.name,
|
|
182
|
+
...(version ? [version.isCurrent ? `${version.label} (current)` : version.label] : []),
|
|
183
|
+
].join('\t');
|
|
184
|
+
};
|
|
185
|
+
export const buildConversionReportPayload = (input) => ({
|
|
186
|
+
...buildFunnelReportHeader(input),
|
|
127
187
|
overview: input.overview,
|
|
128
188
|
totals: input.stepDropoff.totals,
|
|
129
189
|
metrics: input.stepDropoff.metrics,
|
|
130
|
-
funnelPath: buildFunnelPathRows(input.stepDropoff, input.
|
|
190
|
+
funnelPath: buildFunnelPathRows(input.stepDropoff, input.pathFlow),
|
|
131
191
|
transitions: buildTransitionRows(input.stepDropoff),
|
|
132
192
|
});
|
|
133
193
|
export const buildFunnelPathReportPayload = (input) => ({
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
workspaceId: input.workspaceId,
|
|
137
|
-
project: input.project,
|
|
138
|
-
funnel: input.stepDropoff.funnel,
|
|
139
|
-
funnelPath: buildFunnelPathRows(input.stepDropoff, input.publishedFunnelFlow),
|
|
194
|
+
...buildFunnelReportHeader(input),
|
|
195
|
+
funnelPath: buildFunnelPathRows(input.stepDropoff, input.pathFlow),
|
|
140
196
|
});
|
|
141
197
|
export const buildTransitionReportPayload = (input) => ({
|
|
142
|
-
|
|
143
|
-
date: input.date,
|
|
144
|
-
workspaceId: input.workspaceId,
|
|
145
|
-
project: input.project,
|
|
146
|
-
funnel: input.stepDropoff.funnel,
|
|
198
|
+
...buildFunnelReportHeader(input),
|
|
147
199
|
transitions: buildTransitionRows(input.stepDropoff),
|
|
148
200
|
});
|
|
149
201
|
export const buildCohortReportPayload = (input) => ({
|
|
@@ -157,7 +209,7 @@ export const buildCohortReportPayload = (input) => ({
|
|
|
157
209
|
export const formatAnalyticsJson = (payload) => `${JSON.stringify(payload, null, 2)}\n`;
|
|
158
210
|
export const formatTransitionsTable = (input) => {
|
|
159
211
|
const lines = [
|
|
160
|
-
|
|
212
|
+
formatFunnelReportTitle('Step transitions', input),
|
|
161
213
|
'step\tnextStep\tentrants\tadvanced\tdropoffs\tdropoffRate',
|
|
162
214
|
];
|
|
163
215
|
for (const row of buildTransitionRows(input.stepDropoff)) {
|
|
@@ -174,10 +226,10 @@ export const formatTransitionsTable = (input) => {
|
|
|
174
226
|
};
|
|
175
227
|
export const formatFunnelPathTable = (input) => {
|
|
176
228
|
const lines = [
|
|
177
|
-
|
|
229
|
+
formatFunnelReportTitle('Full funnel path', input),
|
|
178
230
|
'step\tpath\tusers\tadvanced\tconversionToNext\tdropoffs\tdropoffRate',
|
|
179
231
|
];
|
|
180
|
-
for (const row of buildFunnelPathRows(input.stepDropoff, input.
|
|
232
|
+
for (const row of buildFunnelPathRows(input.stepDropoff, input.pathFlow)) {
|
|
181
233
|
lines.push([
|
|
182
234
|
row.label,
|
|
183
235
|
row.path,
|
|
@@ -194,7 +246,7 @@ export const formatConversionsTable = (input) => {
|
|
|
194
246
|
const totals = input.stepDropoff.totals;
|
|
195
247
|
const overviewTotals = input.overview.totals;
|
|
196
248
|
const lines = [
|
|
197
|
-
|
|
249
|
+
formatFunnelReportTitle('Conversions', input),
|
|
198
250
|
'metric\tvalue',
|
|
199
251
|
['visitors', formatNumber(totals.visitors ?? overviewTotals.visitors ?? 0)].join('\t'),
|
|
200
252
|
['quizEntries', formatNumber(totals.quizEntries ?? 0)].join('\t'),
|
|
@@ -217,11 +269,31 @@ export const formatConversionsTable = (input) => {
|
|
|
217
269
|
formatPercent(metric.rate),
|
|
218
270
|
].join('\t'));
|
|
219
271
|
}
|
|
220
|
-
lines.push('', formatFunnelPathTable(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
272
|
+
lines.push('', formatFunnelPathTable(input).trimEnd());
|
|
273
|
+
return `${lines.join('\n')}\n`;
|
|
274
|
+
};
|
|
275
|
+
export const buildJourneyVersionsReportPayload = (input) => ({
|
|
276
|
+
generatedAt: new Date().toISOString(),
|
|
277
|
+
workspaceId: input.workspaceId,
|
|
278
|
+
project: input.project,
|
|
279
|
+
funnel: input.funnel,
|
|
280
|
+
versions: input.versions,
|
|
281
|
+
});
|
|
282
|
+
export const formatJourneyVersionsTable = (input) => {
|
|
283
|
+
const lines = [
|
|
284
|
+
`Journey versions\t${input.funnel.name}`,
|
|
285
|
+
'version\tid\tstartedAt\tactiveFrom\tactiveTo\tvisitors',
|
|
286
|
+
];
|
|
287
|
+
for (const version of input.versions) {
|
|
288
|
+
lines.push([
|
|
289
|
+
version.isCurrent ? `${version.label} (current)` : version.label,
|
|
290
|
+
version.id,
|
|
291
|
+
version.startedAt || '',
|
|
292
|
+
version.activeFrom || '',
|
|
293
|
+
version.activeTo || '',
|
|
294
|
+
version.visitors === null ? '' : formatNumber(version.visitors),
|
|
295
|
+
].join('\t'));
|
|
296
|
+
}
|
|
225
297
|
return `${lines.join('\n')}\n`;
|
|
226
298
|
};
|
|
227
299
|
const flattenCohortRows = (rows) => rows.flatMap((row) => [row, ...(row.segments || []), ...(row.children || [])]);
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { type DocsExitCode } from './docsOutput.js';
|
|
|
8
8
|
import { type DocsDiagnostic, type DocsStatus } from './docsTransaction.js';
|
|
9
9
|
import { type FunnelValidationExitCode, type FunnelValidationResult } from './diagnosticOutput.js';
|
|
10
10
|
import { type StepContractMigrationApplyResult, type StepContractMigrationCheckResult, type StepContractMigrationTargetVersion, type StepContractMigrationValidationResult } from './stepContractMigration.js';
|
|
11
|
-
import { type
|
|
11
|
+
import { type AnalyticsFunnelFlow, type AnalyticsJourneyVersionRow, type AnalyticsPeriod, type AnalyticsOutputFormat, type AnalyticsStepDropoffResponse } from './analyticsOutput.js';
|
|
12
12
|
export declare const CANONICAL_CREATE_TEMPLATE_SLUG = "template";
|
|
13
13
|
export declare const SCAFFOLD_TRANSIENT_PATHS: readonly ["node_modules", ".next", "out", "tsconfig.tsbuildinfo", ".funnelsgrove-scaffold.json"];
|
|
14
14
|
export declare class CliContractError extends Error {
|
|
@@ -142,6 +142,15 @@ type AnalyticsCommandInput = {
|
|
|
142
142
|
format: AnalyticsOutputFormat;
|
|
143
143
|
timezone?: string;
|
|
144
144
|
};
|
|
145
|
+
type FunnelAnalyticsCommandInput = {
|
|
146
|
+
period: AnalyticsPeriod & {
|
|
147
|
+
fromIso: string;
|
|
148
|
+
toIso: string;
|
|
149
|
+
};
|
|
150
|
+
format: AnalyticsOutputFormat;
|
|
151
|
+
timezone?: string;
|
|
152
|
+
journeyVersion: string;
|
|
153
|
+
};
|
|
145
154
|
type SyncTargetIdInput = {
|
|
146
155
|
explicitWorkspaceId?: string;
|
|
147
156
|
explicitFunnelId?: string;
|
|
@@ -161,6 +170,14 @@ export declare function buildAnalyticsCommandInput(options: {
|
|
|
161
170
|
format?: string;
|
|
162
171
|
timezone?: string;
|
|
163
172
|
}): AnalyticsCommandInput;
|
|
173
|
+
export declare function buildFunnelAnalyticsCommandInput(options: {
|
|
174
|
+
date?: string;
|
|
175
|
+
from?: string;
|
|
176
|
+
to?: string;
|
|
177
|
+
journeyVersion?: string;
|
|
178
|
+
format?: string;
|
|
179
|
+
timezone?: string;
|
|
180
|
+
}): FunnelAnalyticsCommandInput;
|
|
164
181
|
export declare function buildPatchSourceInput(input: {
|
|
165
182
|
workspaceId: string;
|
|
166
183
|
funnelId: string;
|
|
@@ -289,10 +306,18 @@ type FunnelPathAnalyticsInput = {
|
|
|
289
306
|
workspaceId: string;
|
|
290
307
|
projectId: string;
|
|
291
308
|
funnelId: string;
|
|
292
|
-
commandInput:
|
|
309
|
+
commandInput: FunnelAnalyticsCommandInput;
|
|
293
310
|
};
|
|
294
311
|
export declare const fetchFunnelPathAnalytics: (input: FunnelPathAnalyticsInput, apiCall?: AnalyticsApiCall) => Promise<{
|
|
295
312
|
stepDropoff: AnalyticsStepDropoffResponse;
|
|
296
|
-
|
|
313
|
+
pathFlow: AnalyticsFunnelFlow | null;
|
|
314
|
+
}>;
|
|
315
|
+
type JourneyVersionsInput = Pick<FunnelPathAnalyticsInput, 'token' | 'workspaceId' | 'projectId' | 'funnelId'> & {
|
|
316
|
+
timezone?: string;
|
|
317
|
+
today: string;
|
|
318
|
+
};
|
|
319
|
+
export declare const fetchJourneyVersions: (input: JourneyVersionsInput, apiCall?: AnalyticsApiCall) => Promise<{
|
|
320
|
+
funnel: AnalyticsStepDropoffResponse["funnel"];
|
|
321
|
+
versions: AnalyticsJourneyVersionRow[];
|
|
297
322
|
}>;
|
|
298
323
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import { applyTemplateDocsUpdate, inspectTemplateDocs, loadBundledDocs, planTemp
|
|
|
29
29
|
import { renderValidationHuman, renderValidationJson, } from './diagnosticOutput.js';
|
|
30
30
|
import { validateFunnel } from './funnelValidation.js';
|
|
31
31
|
import { StepContractMigrationError, applyStepContractV2Migration, checkStepContractV2Migration, recoverStepContractV2Migration, resolveStepContractMigrationMode, stepContractMigrationReportPath, } from './stepContractMigration.js';
|
|
32
|
-
import { assertHasCohortData, assertHasConversionData, assertHasFunnelPathData, assertHasTransitionData, buildCohortReportPayload, buildConversionReportPayload, buildFunnelPathReportPayload, buildTransitionReportPayload, formatAnalyticsJson, formatCohortTable, formatConversionsTable, formatFunnelPathTable, formatTransitionsTable, normalizeAnalyticsOutputFormat, parseAnalyticsDate, } from './analyticsOutput.js';
|
|
32
|
+
import { assertHasCohortData, assertHasConversionData, assertHasFunnelPathData, assertHasTransitionData, buildCohortReportPayload, buildConversionReportPayload, buildFunnelPathReportPayload, buildJourneyVersionsReportPayload, buildTransitionReportPayload, formatAnalyticsJson, formatCohortTable, formatConversionsTable, formatFunnelPathTable, formatJourneyVersionsTable, formatTransitionsTable, journeyVersionLabel, normalizeAnalyticsOutputFormat, parseAnalyticsDate, parseAnalyticsPeriod, } from './analyticsOutput.js';
|
|
33
33
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
34
|
const toKebabCase = (value) => value
|
|
35
35
|
.trim()
|
|
@@ -1115,6 +1115,21 @@ export function buildAnalyticsCommandInput(options) {
|
|
|
1115
1115
|
timezone,
|
|
1116
1116
|
};
|
|
1117
1117
|
}
|
|
1118
|
+
const parseAnalyticsJourneyVersion = (value) => {
|
|
1119
|
+
const normalized = (value || 'current').trim().toLowerCase();
|
|
1120
|
+
if (normalized === 'current' || normalized === 'all' || UUID_PATTERN.test(normalized)) {
|
|
1121
|
+
return normalized;
|
|
1122
|
+
}
|
|
1123
|
+
throw new Error('--journey-version must be current, all, or a journey version id from `fgrove analytics versions`.');
|
|
1124
|
+
};
|
|
1125
|
+
export function buildFunnelAnalyticsCommandInput(options) {
|
|
1126
|
+
return {
|
|
1127
|
+
period: parseAnalyticsPeriod(options),
|
|
1128
|
+
format: normalizeAnalyticsOutputFormat(options.format),
|
|
1129
|
+
timezone: options.timezone?.trim() || undefined,
|
|
1130
|
+
journeyVersion: parseAnalyticsJourneyVersion(options.journeyVersion),
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1118
1133
|
export function buildPatchSourceInput(input) {
|
|
1119
1134
|
return {
|
|
1120
1135
|
workspaceId: input.workspaceId,
|
|
@@ -2169,9 +2184,10 @@ const analyticsCommand = addExamples(program.command('analytics').description('D
|
|
|
2169
2184
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out analytics.json',
|
|
2170
2185
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2171
2186
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2187
|
+
'fgrove analytics versions --project claimbee --funnel claimbee-ios',
|
|
2172
2188
|
'fgrove analytics cohort --project claimbee --date 2026-06-11 --format json',
|
|
2173
2189
|
]);
|
|
2174
|
-
const
|
|
2190
|
+
const resolveFunnelAnalyticsTarget = async (options) => {
|
|
2175
2191
|
const token = await readAuthToken();
|
|
2176
2192
|
const workspace = await resolveWorkspace(token, options.workspace);
|
|
2177
2193
|
const project = await resolveAnalyticsProject({
|
|
@@ -2189,7 +2205,13 @@ const loadFunnelAnalyticsContext = async (options) => {
|
|
|
2189
2205
|
workspace,
|
|
2190
2206
|
project,
|
|
2191
2207
|
funnel,
|
|
2192
|
-
|
|
2208
|
+
};
|
|
2209
|
+
};
|
|
2210
|
+
const loadFunnelAnalyticsContext = async (options) => {
|
|
2211
|
+
const commandInput = buildFunnelAnalyticsCommandInput(options);
|
|
2212
|
+
return {
|
|
2213
|
+
...await resolveFunnelAnalyticsTarget(options),
|
|
2214
|
+
commandInput,
|
|
2193
2215
|
};
|
|
2194
2216
|
};
|
|
2195
2217
|
const loadProjectAnalyticsContext = async (options) => {
|
|
@@ -2216,8 +2238,9 @@ const fetchStepDropoffAnalytics = async (input, apiCall = callApi) => {
|
|
|
2216
2238
|
workspaceId: input.workspaceId,
|
|
2217
2239
|
projectId: input.projectId,
|
|
2218
2240
|
funnelId: input.funnelId,
|
|
2219
|
-
|
|
2220
|
-
|
|
2241
|
+
...(input.commandInput.journeyVersion === 'current' ? {} : { versionId: input.commandInput.journeyVersion }),
|
|
2242
|
+
from: input.commandInput.period.fromIso,
|
|
2243
|
+
to: input.commandInput.period.toIso,
|
|
2221
2244
|
timezone: input.commandInput.timezone,
|
|
2222
2245
|
},
|
|
2223
2246
|
});
|
|
@@ -2235,24 +2258,79 @@ const fetchPublishedFunnelFlow = async (input, apiCall = callApi) => {
|
|
|
2235
2258
|
});
|
|
2236
2259
|
};
|
|
2237
2260
|
export const fetchFunnelPathAnalytics = async (input, apiCall = callApi) => {
|
|
2261
|
+
const { journeyVersion } = input.commandInput;
|
|
2262
|
+
if (journeyVersion !== 'current' && journeyVersion !== 'all') {
|
|
2263
|
+
// An explicit Journey Version reports its own structure and is never
|
|
2264
|
+
// paired with the current published path.
|
|
2265
|
+
const stepDropoff = await fetchStepDropoffAnalytics(input, apiCall);
|
|
2266
|
+
return { stepDropoff, pathFlow: stepDropoff.flow };
|
|
2267
|
+
}
|
|
2238
2268
|
const [stepDropoff, publishedFunnelFlow] = await Promise.all([
|
|
2239
2269
|
fetchStepDropoffAnalytics(input, apiCall),
|
|
2240
2270
|
fetchPublishedFunnelFlow(input, apiCall),
|
|
2241
2271
|
]);
|
|
2242
|
-
return {
|
|
2272
|
+
return {
|
|
2273
|
+
stepDropoff,
|
|
2274
|
+
pathFlow: publishedFunnelFlow.status === 'ready' ? publishedFunnelFlow.flow : null,
|
|
2275
|
+
};
|
|
2243
2276
|
};
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2277
|
+
// The API reads analytics dates in this timezone when a request names none.
|
|
2278
|
+
const DEFAULT_ANALYTICS_TIMEZONE = 'America/Los_Angeles';
|
|
2279
|
+
const toAnalyticsDate = (value, timezone) => new Intl.DateTimeFormat('en-CA', { timeZone: timezone || DEFAULT_ANALYTICS_TIMEZONE }).format(value);
|
|
2280
|
+
export const fetchJourneyVersions = async (input, apiCall = callApi) => {
|
|
2281
|
+
const { today, timezone, ...target } = input;
|
|
2282
|
+
const current = await fetchStepDropoffAnalytics({
|
|
2283
|
+
...target,
|
|
2284
|
+
commandInput: buildFunnelAnalyticsCommandInput({ date: today, timezone }),
|
|
2285
|
+
}, apiCall);
|
|
2286
|
+
const newestFirst = [...current.version.options].sort((left, right) => right.seq - left.seq);
|
|
2287
|
+
// A version is active from its start day until the next version starts;
|
|
2288
|
+
// version-scoped counts make the shared switch day safe to include in both.
|
|
2289
|
+
const startDate = (startedAt) => (startedAt ? toAnalyticsDate(new Date(startedAt), timezone) : null);
|
|
2290
|
+
const versions = await Promise.all(newestFirst.map(async (version, index) => {
|
|
2291
|
+
const activeFrom = startDate(version.publishedAt);
|
|
2292
|
+
const activeTo = index === 0 ? null : startDate(newestFirst[index - 1].publishedAt);
|
|
2293
|
+
const report = activeFrom
|
|
2294
|
+
? await fetchStepDropoffAnalytics({
|
|
2295
|
+
...target,
|
|
2296
|
+
commandInput: buildFunnelAnalyticsCommandInput({
|
|
2297
|
+
from: activeFrom,
|
|
2298
|
+
to: activeTo ?? today,
|
|
2299
|
+
journeyVersion: version.id,
|
|
2300
|
+
timezone,
|
|
2301
|
+
}),
|
|
2302
|
+
}, apiCall)
|
|
2303
|
+
: null;
|
|
2304
|
+
return {
|
|
2305
|
+
id: version.id,
|
|
2306
|
+
seq: version.seq,
|
|
2307
|
+
label: journeyVersionLabel(version),
|
|
2308
|
+
isCurrent: version.isCurrent,
|
|
2309
|
+
startedAt: version.publishedAt,
|
|
2310
|
+
activeFrom,
|
|
2311
|
+
activeTo,
|
|
2312
|
+
visitors: report ? report.totals.visitors ?? 0 : null,
|
|
2313
|
+
};
|
|
2314
|
+
}));
|
|
2315
|
+
return { funnel: current.funnel, versions };
|
|
2316
|
+
};
|
|
2317
|
+
const addFunnelReportOptions = (command) => command
|
|
2247
2318
|
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2248
2319
|
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
2249
2320
|
.option('--funnel <id-or-slug-or-name>', 'Funnel id, slug, or name')
|
|
2250
|
-
.
|
|
2321
|
+
.option('--date <yyyy-mm-dd>', 'Analytics date; use instead of --from/--to for one day')
|
|
2322
|
+
.option('--from <yyyy-mm-dd>', 'First analytics date of a range, used with --to')
|
|
2323
|
+
.option('--to <yyyy-mm-dd>', 'Last analytics date of a range, used with --from')
|
|
2324
|
+
.option('--journey-version <id|current|all>', 'Journey version: current, all, or an id from `fgrove analytics versions`', 'current')
|
|
2251
2325
|
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2252
2326
|
.option('--out <path>', 'Write output to a file')
|
|
2253
|
-
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles')
|
|
2327
|
+
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles');
|
|
2328
|
+
addExamples(addFunnelReportOptions(analyticsCommand
|
|
2329
|
+
.command('conversions')
|
|
2330
|
+
.description('Download conversion data, full funnel path, and step transitions for a day or date range')), [
|
|
2254
2331
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2255
2332
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out analytics.json',
|
|
2333
|
+
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14 --journey-version all',
|
|
2256
2334
|
])
|
|
2257
2335
|
.action(async (options) => {
|
|
2258
2336
|
const context = await loadFunnelAnalyticsContext(options);
|
|
@@ -2265,8 +2343,8 @@ addExamples(analyticsCommand
|
|
|
2265
2343
|
workspaceId: context.workspace.id,
|
|
2266
2344
|
projectId: context.project.id,
|
|
2267
2345
|
funnelId: context.funnel.id,
|
|
2268
|
-
from: context.commandInput.
|
|
2269
|
-
to: context.commandInput.
|
|
2346
|
+
from: context.commandInput.period.fromIso,
|
|
2347
|
+
to: context.commandInput.period.toIso,
|
|
2270
2348
|
timezone: context.commandInput.timezone,
|
|
2271
2349
|
limit: 20_000,
|
|
2272
2350
|
},
|
|
@@ -2279,81 +2357,66 @@ addExamples(analyticsCommand
|
|
|
2279
2357
|
commandInput: context.commandInput,
|
|
2280
2358
|
}),
|
|
2281
2359
|
]);
|
|
2282
|
-
const { stepDropoff,
|
|
2283
|
-
assertHasConversionData(context.commandInput.
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
project: context.project,
|
|
2288
|
-
overview,
|
|
2360
|
+
const { stepDropoff, pathFlow } = pathAnalytics;
|
|
2361
|
+
assertHasConversionData(context.commandInput.period.label, overview, stepDropoff);
|
|
2362
|
+
const report = {
|
|
2363
|
+
period: context.commandInput.period,
|
|
2364
|
+
journeyVersion: context.commandInput.journeyVersion,
|
|
2289
2365
|
stepDropoff,
|
|
2290
|
-
|
|
2291
|
-
|
|
2366
|
+
overview,
|
|
2367
|
+
pathFlow,
|
|
2368
|
+
};
|
|
2292
2369
|
await writeOrPrintAnalyticsOutput({
|
|
2293
2370
|
format: context.commandInput.format,
|
|
2294
|
-
payload
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
stepDropoff,
|
|
2299
|
-
publishedFunnelFlow,
|
|
2371
|
+
payload: buildConversionReportPayload({
|
|
2372
|
+
...report,
|
|
2373
|
+
workspaceId: context.workspace.id,
|
|
2374
|
+
project: context.project,
|
|
2300
2375
|
}),
|
|
2376
|
+
table: formatConversionsTable(report),
|
|
2301
2377
|
out: options.out,
|
|
2302
2378
|
});
|
|
2303
2379
|
});
|
|
2304
|
-
addExamples(analyticsCommand
|
|
2380
|
+
addExamples(addFunnelReportOptions(analyticsCommand
|
|
2305
2381
|
.command('funnel-path')
|
|
2306
|
-
.description('Download the full
|
|
2307
|
-
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2308
|
-
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
2309
|
-
.option('--funnel <id-or-slug-or-name>', 'Funnel id, slug, or name')
|
|
2310
|
-
.requiredOption('--date <yyyy-mm-dd>', 'Analytics date')
|
|
2311
|
-
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2312
|
-
.option('--out <path>', 'Write output to a file')
|
|
2313
|
-
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles'), [
|
|
2382
|
+
.description('Download the full funnel path report for a day or date range')), [
|
|
2314
2383
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2315
2384
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json',
|
|
2385
|
+
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14 --journey-version <journey-version-id>',
|
|
2316
2386
|
])
|
|
2317
2387
|
.action(async (options) => {
|
|
2318
2388
|
const context = await loadFunnelAnalyticsContext(options);
|
|
2319
|
-
const { stepDropoff,
|
|
2389
|
+
const { stepDropoff, pathFlow } = await fetchFunnelPathAnalytics({
|
|
2320
2390
|
token: context.token,
|
|
2321
2391
|
workspaceId: context.workspace.id,
|
|
2322
2392
|
projectId: context.project.id,
|
|
2323
2393
|
funnelId: context.funnel.id,
|
|
2324
2394
|
commandInput: context.commandInput,
|
|
2325
2395
|
});
|
|
2326
|
-
assertHasFunnelPathData(context.commandInput.
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
project: context.project,
|
|
2396
|
+
assertHasFunnelPathData(context.commandInput.period.label, stepDropoff, pathFlow);
|
|
2397
|
+
const report = {
|
|
2398
|
+
period: context.commandInput.period,
|
|
2399
|
+
journeyVersion: context.commandInput.journeyVersion,
|
|
2331
2400
|
stepDropoff,
|
|
2332
|
-
|
|
2333
|
-
}
|
|
2401
|
+
pathFlow,
|
|
2402
|
+
};
|
|
2334
2403
|
await writeOrPrintAnalyticsOutput({
|
|
2335
2404
|
format: context.commandInput.format,
|
|
2336
|
-
payload
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
publishedFunnelFlow,
|
|
2405
|
+
payload: buildFunnelPathReportPayload({
|
|
2406
|
+
...report,
|
|
2407
|
+
workspaceId: context.workspace.id,
|
|
2408
|
+
project: context.project,
|
|
2341
2409
|
}),
|
|
2410
|
+
table: formatFunnelPathTable(report),
|
|
2342
2411
|
out: options.out,
|
|
2343
2412
|
});
|
|
2344
2413
|
});
|
|
2345
|
-
addExamples(analyticsCommand
|
|
2414
|
+
addExamples(addFunnelReportOptions(analyticsCommand
|
|
2346
2415
|
.command('transitions')
|
|
2347
|
-
.description('Download
|
|
2348
|
-
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2349
|
-
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
2350
|
-
.option('--funnel <id-or-slug-or-name>', 'Funnel id, slug, or name')
|
|
2351
|
-
.requiredOption('--date <yyyy-mm-dd>', 'Analytics date')
|
|
2352
|
-
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2353
|
-
.option('--out <path>', 'Write output to a file')
|
|
2354
|
-
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles'), [
|
|
2416
|
+
.description('Download step transitions and drop-offs for a day or date range')), [
|
|
2355
2417
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2356
2418
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json',
|
|
2419
|
+
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14 --journey-version <journey-version-id>',
|
|
2357
2420
|
])
|
|
2358
2421
|
.action(async (options) => {
|
|
2359
2422
|
const context = await loadFunnelAnalyticsContext(options);
|
|
@@ -2364,20 +2427,55 @@ addExamples(analyticsCommand
|
|
|
2364
2427
|
funnelId: context.funnel.id,
|
|
2365
2428
|
commandInput: context.commandInput,
|
|
2366
2429
|
});
|
|
2367
|
-
assertHasTransitionData(context.commandInput.
|
|
2368
|
-
const
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
project: context.project,
|
|
2430
|
+
assertHasTransitionData(context.commandInput.period.label, stepDropoff);
|
|
2431
|
+
const report = {
|
|
2432
|
+
period: context.commandInput.period,
|
|
2433
|
+
journeyVersion: context.commandInput.journeyVersion,
|
|
2372
2434
|
stepDropoff,
|
|
2373
|
-
}
|
|
2435
|
+
};
|
|
2374
2436
|
await writeOrPrintAnalyticsOutput({
|
|
2375
2437
|
format: context.commandInput.format,
|
|
2376
|
-
payload
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2438
|
+
payload: buildTransitionReportPayload({
|
|
2439
|
+
...report,
|
|
2440
|
+
workspaceId: context.workspace.id,
|
|
2441
|
+
project: context.project,
|
|
2442
|
+
}),
|
|
2443
|
+
table: formatTransitionsTable(report),
|
|
2444
|
+
out: options.out,
|
|
2445
|
+
});
|
|
2446
|
+
});
|
|
2447
|
+
addExamples(analyticsCommand
|
|
2448
|
+
.command('versions')
|
|
2449
|
+
.description('List journey versions with their active days and visitors')
|
|
2450
|
+
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2451
|
+
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
2452
|
+
.option('--funnel <id-or-slug-or-name>', 'Funnel id, slug, or name')
|
|
2453
|
+
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2454
|
+
.option('--out <path>', 'Write output to a file')
|
|
2455
|
+
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles'), [
|
|
2456
|
+
'fgrove analytics versions --project claimbee --funnel claimbee-ios',
|
|
2457
|
+
'fgrove analytics versions --project claimbee --funnel claimbee-ios --format json',
|
|
2458
|
+
])
|
|
2459
|
+
.action(async (options) => {
|
|
2460
|
+
const format = normalizeAnalyticsOutputFormat(options.format);
|
|
2461
|
+
const timezone = options.timezone?.trim() || undefined;
|
|
2462
|
+
const target = await resolveFunnelAnalyticsTarget(options);
|
|
2463
|
+
const report = await fetchJourneyVersions({
|
|
2464
|
+
token: target.token,
|
|
2465
|
+
workspaceId: target.workspace.id,
|
|
2466
|
+
projectId: target.project.id,
|
|
2467
|
+
funnelId: target.funnel.id,
|
|
2468
|
+
timezone,
|
|
2469
|
+
today: toAnalyticsDate(new Date(), timezone),
|
|
2470
|
+
});
|
|
2471
|
+
await writeOrPrintAnalyticsOutput({
|
|
2472
|
+
format,
|
|
2473
|
+
payload: buildJourneyVersionsReportPayload({
|
|
2474
|
+
...report,
|
|
2475
|
+
workspaceId: target.workspace.id,
|
|
2476
|
+
project: target.project,
|
|
2380
2477
|
}),
|
|
2478
|
+
table: formatJourneyVersionsTable(report),
|
|
2381
2479
|
out: options.out,
|
|
2382
2480
|
});
|
|
2383
2481
|
});
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"minimumCliVersion": "0.1.20",
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
|
-
"repositoryCliVersion": "0.1.
|
|
7
|
+
"repositoryCliVersion": "0.1.265",
|
|
8
8
|
"manifest": {
|
|
9
9
|
"schemaVersion": 1,
|
|
10
|
-
"bundleVersion": "2.0.
|
|
10
|
+
"bundleVersion": "2.0.258",
|
|
11
11
|
"stepContractVersion": 3,
|
|
12
12
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
13
13
|
"managedFiles": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "e529cbfc0ab172d5168301efcb09e2f7ed7459d3c8c7f27db9198b4ccce069ba"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -145,16 +145,16 @@
|
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"path": "funnel-docs.config.json",
|
|
148
|
-
"sha256": "
|
|
148
|
+
"sha256": "4cc4c05d6f0048162c5fc0a534050e11a8c14585783a7fae61a98462e880cc65"
|
|
149
149
|
}
|
|
150
150
|
]
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
|
-
"repositoryCliVersion": "0.1.
|
|
154
|
+
"repositoryCliVersion": "0.1.264",
|
|
155
155
|
"manifest": {
|
|
156
156
|
"schemaVersion": 1,
|
|
157
|
-
"bundleVersion": "2.0.
|
|
157
|
+
"bundleVersion": "2.0.257",
|
|
158
158
|
"stepContractVersion": 3,
|
|
159
159
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
160
160
|
"managedFiles": [
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
195
|
-
"sha256": "
|
|
195
|
+
"sha256": "a299775572c2aac26afb8905d770417e1dd2f5a0ff1026d5212ba080ef1d5e82"
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -292,16 +292,16 @@
|
|
|
292
292
|
},
|
|
293
293
|
{
|
|
294
294
|
"path": "funnel-docs.config.json",
|
|
295
|
-
"sha256": "
|
|
295
|
+
"sha256": "693f0649aedbb45a62b88661d482cbafeb01eb3fdb5279b473732b1a0c83e8ac"
|
|
296
296
|
}
|
|
297
297
|
]
|
|
298
298
|
}
|
|
299
299
|
},
|
|
300
300
|
{
|
|
301
|
-
"repositoryCliVersion": "0.1.
|
|
301
|
+
"repositoryCliVersion": "0.1.263",
|
|
302
302
|
"manifest": {
|
|
303
303
|
"schemaVersion": 1,
|
|
304
|
-
"bundleVersion": "2.0.
|
|
304
|
+
"bundleVersion": "2.0.256",
|
|
305
305
|
"stepContractVersion": 3,
|
|
306
306
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
307
307
|
"managedFiles": [
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
342
|
-
"sha256": "
|
|
342
|
+
"sha256": "afba67a5679827efbd37d706d290e393558d4abf13271b5606e069b1694960e8"
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -439,16 +439,16 @@
|
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
441
|
"path": "funnel-docs.config.json",
|
|
442
|
-
"sha256": "
|
|
442
|
+
"sha256": "ac03af2c7446ffefd0acd9e28db1c4d6fa7a04848b1c2a562a3e722d16f9a98a"
|
|
443
443
|
}
|
|
444
444
|
]
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
|
-
"repositoryCliVersion": "0.1.
|
|
448
|
+
"repositoryCliVersion": "0.1.262",
|
|
449
449
|
"manifest": {
|
|
450
450
|
"schemaVersion": 1,
|
|
451
|
-
"bundleVersion": "2.0.
|
|
451
|
+
"bundleVersion": "2.0.255",
|
|
452
452
|
"stepContractVersion": 3,
|
|
453
453
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
454
454
|
"managedFiles": [
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
},
|
|
487
487
|
{
|
|
488
488
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
489
|
-
"sha256": "
|
|
489
|
+
"sha256": "d733607091b48f87b667e43c68984116b4f5c82cdc5ca04af688ccc7160c0e36"
|
|
490
490
|
},
|
|
491
491
|
{
|
|
492
492
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -586,16 +586,16 @@
|
|
|
586
586
|
},
|
|
587
587
|
{
|
|
588
588
|
"path": "funnel-docs.config.json",
|
|
589
|
-
"sha256": "
|
|
589
|
+
"sha256": "3bb975a7ced201ae60030e863b150e51fc8b5d496e0d0fb93fc92c48953e0c39"
|
|
590
590
|
}
|
|
591
591
|
]
|
|
592
592
|
}
|
|
593
593
|
},
|
|
594
594
|
{
|
|
595
|
-
"repositoryCliVersion": "0.1.
|
|
595
|
+
"repositoryCliVersion": "0.1.261",
|
|
596
596
|
"manifest": {
|
|
597
597
|
"schemaVersion": 1,
|
|
598
|
-
"bundleVersion": "2.0.
|
|
598
|
+
"bundleVersion": "2.0.254",
|
|
599
599
|
"stepContractVersion": 3,
|
|
600
600
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
601
601
|
"managedFiles": [
|
|
@@ -633,7 +633,7 @@
|
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
635
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
636
|
-
"sha256": "
|
|
636
|
+
"sha256": "92a56af5007b51ced39aec37795aeab3a94552e1d242c13f3dbd056a353ebdef"
|
|
637
637
|
},
|
|
638
638
|
{
|
|
639
639
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -733,16 +733,16 @@
|
|
|
733
733
|
},
|
|
734
734
|
{
|
|
735
735
|
"path": "funnel-docs.config.json",
|
|
736
|
-
"sha256": "
|
|
736
|
+
"sha256": "68a262476d89d22cf3206316ccbf935ae3cf1686a7d951f2024cfffc9e93384d"
|
|
737
737
|
}
|
|
738
738
|
]
|
|
739
739
|
}
|
|
740
740
|
},
|
|
741
741
|
{
|
|
742
|
-
"repositoryCliVersion": "0.1.
|
|
742
|
+
"repositoryCliVersion": "0.1.260",
|
|
743
743
|
"manifest": {
|
|
744
744
|
"schemaVersion": 1,
|
|
745
|
-
"bundleVersion": "2.0.
|
|
745
|
+
"bundleVersion": "2.0.253",
|
|
746
746
|
"stepContractVersion": 3,
|
|
747
747
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
748
748
|
"managedFiles": [
|
|
@@ -780,7 +780,7 @@
|
|
|
780
780
|
},
|
|
781
781
|
{
|
|
782
782
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
783
|
-
"sha256": "
|
|
783
|
+
"sha256": "3f4c093fce58c01bd66604a5dd371bdd1f3cf3743bc7e556e2a1e871e0b24866"
|
|
784
784
|
},
|
|
785
785
|
{
|
|
786
786
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -880,16 +880,16 @@
|
|
|
880
880
|
},
|
|
881
881
|
{
|
|
882
882
|
"path": "funnel-docs.config.json",
|
|
883
|
-
"sha256": "
|
|
883
|
+
"sha256": "dea21f974e2601112ec12aac3493ece0c0955eb612c6eb790874add999a11822"
|
|
884
884
|
}
|
|
885
885
|
]
|
|
886
886
|
}
|
|
887
887
|
},
|
|
888
888
|
{
|
|
889
|
-
"repositoryCliVersion": "0.1.
|
|
889
|
+
"repositoryCliVersion": "0.1.259",
|
|
890
890
|
"manifest": {
|
|
891
891
|
"schemaVersion": 1,
|
|
892
|
-
"bundleVersion": "2.0.
|
|
892
|
+
"bundleVersion": "2.0.252",
|
|
893
893
|
"stepContractVersion": 3,
|
|
894
894
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
895
895
|
"managedFiles": [
|
|
@@ -927,7 +927,7 @@
|
|
|
927
927
|
},
|
|
928
928
|
{
|
|
929
929
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
930
|
-
"sha256": "
|
|
930
|
+
"sha256": "d1ca070960305b60bc78dae50355a59c8c068b59ea161b4acc9403ec1848b04c"
|
|
931
931
|
},
|
|
932
932
|
{
|
|
933
933
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -935,7 +935,7 @@
|
|
|
935
935
|
},
|
|
936
936
|
{
|
|
937
937
|
"path": "docs/funnelsgrove/qa/local.md",
|
|
938
|
-
"sha256": "
|
|
938
|
+
"sha256": "c8a83c33cd33bea6d5efde91d7c479505f77077f16de8073064baacbfad98e5f"
|
|
939
939
|
},
|
|
940
940
|
{
|
|
941
941
|
"path": "docs/funnelsgrove/qa/paywall-checkout.md",
|
|
@@ -1027,7 +1027,7 @@
|
|
|
1027
1027
|
},
|
|
1028
1028
|
{
|
|
1029
1029
|
"path": "funnel-docs.config.json",
|
|
1030
|
-
"sha256": "
|
|
1030
|
+
"sha256": "b683d23440209016d94de8319f46040e4e174567d3cc0a1f8ede194c29cc3e76"
|
|
1031
1031
|
}
|
|
1032
1032
|
]
|
|
1033
1033
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion": "2.0.
|
|
3
|
+
"bundleVersion": "2.0.258",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
5
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
6
6
|
"managedFiles": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
41
|
-
"sha256": "
|
|
41
|
+
"sha256": "e529cbfc0ab172d5168301efcb09e2f7ed7459d3c8c7f27db9198b4ccce069ba"
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"path": "funnel-docs.config.json",
|
|
141
|
-
"sha256": "
|
|
141
|
+
"sha256": "4cc4c05d6f0048162c5fc0a534050e11a8c14585783a7fae61a98462e880cc65"
|
|
142
142
|
}
|
|
143
143
|
]
|
|
144
144
|
}
|
|
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
|
|
|
17
17
|
|
|
18
18
|
### Package release order
|
|
19
19
|
|
|
20
|
-
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.25.0`, then `@funnelsgrove/analytics` `0.7.0`, then `@funnelsgrove/payments` `0.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.25.0`, then `@funnelsgrove/analytics` `0.7.0`, then `@funnelsgrove/payments` `0.28.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.265`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. Publishing packages and deploying production remain separately approved operational actions.
|
|
21
21
|
<!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
|
|
22
22
|
|
|
23
23
|
## Version-last policy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion": "2.0.
|
|
3
|
+
"bundleVersion": "2.0.258",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
5
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
6
6
|
"managedFiles": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
41
|
-
"sha256": "
|
|
41
|
+
"sha256": "e529cbfc0ab172d5168301efcb09e2f7ed7459d3c8c7f27db9198b4ccce069ba"
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"path": "funnel-docs.config.json",
|
|
141
|
-
"sha256": "
|
|
141
|
+
"sha256": "4cc4c05d6f0048162c5fc0a534050e11a8c14585783a7fae61a98462e880cc65"
|
|
142
142
|
}
|
|
143
143
|
]
|
|
144
144
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sourceTreeHash": "
|
|
3
|
+
"sourceTreeHash": "c027378d488e07eb04386f9f4111db2be90090aa72d368bee4862ba1fd27cfd3",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
|
-
"docsBundleVersion": "2.0.
|
|
5
|
+
"docsBundleVersion": "2.0.258",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": ".env.example",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"path": ".funnelsgrove-docs.json",
|
|
19
|
-
"sha256": "
|
|
19
|
+
"sha256": "9e51428cc1e4241c2ce35a2240051e453e0c021297a093e300ff01a911eb8acf",
|
|
20
20
|
"mode": "100644"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
79
|
-
"sha256": "
|
|
79
|
+
"sha256": "e529cbfc0ab172d5168301efcb09e2f7ed7459d3c8c7f27db9198b4ccce069ba",
|
|
80
80
|
"mode": "100644"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
},
|
|
207
207
|
{
|
|
208
208
|
"path": "funnel-docs.config.json",
|
|
209
|
-
"sha256": "
|
|
209
|
+
"sha256": "4cc4c05d6f0048162c5fc0a534050e11a8c14585783a7fae61a98462e880cc65",
|
|
210
210
|
"mode": "100644"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
|
|
|
17
17
|
|
|
18
18
|
### Package release order
|
|
19
19
|
|
|
20
|
-
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.25.0`, then `@funnelsgrove/analytics` `0.7.0`, then `@funnelsgrove/payments` `0.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.25.0`, then `@funnelsgrove/analytics` `0.7.0`, then `@funnelsgrove/payments` `0.28.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.265`, and only then promotes the candidate to production traffic. The serving API must never advertise an unpublished preferred CLI. Publishing packages and deploying production remain separately approved operational actions.
|
|
21
21
|
<!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
|
|
22
22
|
|
|
23
23
|
## Version-last policy
|