@funnelsgrove/cli 0.1.290 → 0.1.293
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 +3 -7
- package/dist/analyticsOutput.d.ts +13 -51
- package/dist/analyticsOutput.js +49 -102
- package/dist/cli.d.ts +1 -11
- package/dist/cli.js +4 -100
- package/funnel-contract-compatibility.json +32 -32
- package/package.json +4 -4
- 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 +7 -7
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/package-lock.json +15 -15
- package/template_scaffold/package.json +3 -3
package/README.md
CHANGED
|
@@ -174,7 +174,6 @@ Analytics workflow:
|
|
|
174
174
|
fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
175
175
|
fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
176
176
|
fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11
|
|
177
|
-
fgrove analytics versions --project claimbee --funnel claimbee-ios
|
|
178
177
|
fgrove analytics cohort --project claimbee --date 2026-06-11
|
|
179
178
|
```
|
|
180
179
|
|
|
@@ -184,17 +183,14 @@ Each analytics command accepts `--format table|json`, `--out <path>`, `--workspa
|
|
|
184
183
|
fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out conversions.json
|
|
185
184
|
```
|
|
186
185
|
|
|
187
|
-
`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.
|
|
186
|
+
`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. Its annual predictions use the same saved-range midpoint as Cohort Marketing and daily reports. Missing valid prediction ranges yield null JSON values and empty table cells; legacy model points are never a fallback. Cohort Marketing must be enabled for the project. If the requested period has no synced data, or cohort source data is incomplete, the CLI exits non-zero with a human-readable explanation.
|
|
188
187
|
|
|
189
|
-
`conversions`, `funnel-path`, and `transitions` report one day with `--date`, or a range with `--from` and `--to` (inclusive, totals for the whole range). They
|
|
188
|
+
`conversions`, `funnel-path`, and `transitions` report one day with `--date`, or a range with `--from` and `--to` (inclusive, totals for the whole range). They use the selected date range across all published funnel versions that had traffic. `funnel-path` and `transitions` follow current published flow order. Steps with selected-period traffic that are absent from the current flow appear at the end under “Not in current funnel”; JSON rows mark them with `section: "not-in-current-funnel"`.
|
|
190
189
|
|
|
191
190
|
```bash
|
|
192
|
-
fgrove analytics
|
|
193
|
-
fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14 --journey-version <journey-version-id>
|
|
191
|
+
fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14
|
|
194
192
|
```
|
|
195
193
|
|
|
196
|
-
`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.
|
|
197
|
-
|
|
198
194
|
The package also keeps the longer `funnelsgrove` command as a compatibility alias.
|
|
199
195
|
|
|
200
196
|
Edit profitability expenses through a local JSON file:
|
|
@@ -53,8 +53,8 @@ export type AnalyticsStepDropoffResponse = {
|
|
|
53
53
|
slug: string;
|
|
54
54
|
};
|
|
55
55
|
version: {
|
|
56
|
-
selected:
|
|
57
|
-
options:
|
|
56
|
+
selected: null;
|
|
57
|
+
options: [];
|
|
58
58
|
};
|
|
59
59
|
totals: Record<string, number>;
|
|
60
60
|
metrics: Array<{
|
|
@@ -71,30 +71,6 @@ export type AnalyticsStepDropoffResponse = {
|
|
|
71
71
|
steps: AnalyticsStepTransition[];
|
|
72
72
|
flow: AnalyticsFunnelFlow | null;
|
|
73
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
|
-
};
|
|
98
74
|
export type AnalyticsStepTransition = {
|
|
99
75
|
stepId: string;
|
|
100
76
|
stepName: string | null;
|
|
@@ -137,6 +113,10 @@ export type MarketingCohortPerformanceRow = {
|
|
|
137
113
|
predictedNetRevenueDay365Currency: string | null;
|
|
138
114
|
pLtvCurrency: string | null;
|
|
139
115
|
pLtv: number | null;
|
|
116
|
+
predictedRevenue?: number | null;
|
|
117
|
+
predictedProfit?: number | null;
|
|
118
|
+
predictedRoiPercent?: number | null;
|
|
119
|
+
roasDay365Percent?: number | null;
|
|
140
120
|
pLtvPredictedNetRevenueDay365Minor: number | null;
|
|
141
121
|
pLtvPredictedCustomerCount: number;
|
|
142
122
|
campaign: string;
|
|
@@ -167,7 +147,6 @@ type ReportContext = {
|
|
|
167
147
|
};
|
|
168
148
|
type FunnelReportContext = {
|
|
169
149
|
period: AnalyticsPeriod;
|
|
170
|
-
journeyVersion: string;
|
|
171
150
|
workspaceId: string;
|
|
172
151
|
project: AnalyticsProjectSummary;
|
|
173
152
|
stepDropoff: AnalyticsStepDropoffResponse;
|
|
@@ -195,6 +174,7 @@ export declare const assertHasCohortData: (date: string, report: MarketingCohort
|
|
|
195
174
|
export declare const buildTransitionRows: (stepDropoff: AnalyticsStepDropoffResponse) => {
|
|
196
175
|
stepId: string;
|
|
197
176
|
step: string;
|
|
177
|
+
section: string | null;
|
|
198
178
|
nextStep: string;
|
|
199
179
|
entrants: number;
|
|
200
180
|
advancedToNextStep: number;
|
|
@@ -205,13 +185,13 @@ export declare const buildFunnelPathRows: (stepDropoff: AnalyticsStepDropoffResp
|
|
|
205
185
|
stepId: string;
|
|
206
186
|
path: string;
|
|
207
187
|
label: string;
|
|
188
|
+
section: string;
|
|
208
189
|
users: number;
|
|
209
190
|
advancedToNextStep: number;
|
|
210
191
|
conversionToNextStep: number;
|
|
211
192
|
dropoffs: number;
|
|
212
193
|
dropoffRate: number;
|
|
213
194
|
}[];
|
|
214
|
-
export declare const journeyVersionLabel: (version: Pick<AnalyticsJourneyVersionOption, "seq">) => string;
|
|
215
195
|
export declare const buildConversionReportPayload: (input: FunnelReportContext & {
|
|
216
196
|
overview: AnalyticsOverviewResponse;
|
|
217
197
|
pathFlow: AnalyticsFunnelFlow | null;
|
|
@@ -233,6 +213,7 @@ export declare const buildConversionReportPayload: (input: FunnelReportContext &
|
|
|
233
213
|
stepId: string;
|
|
234
214
|
path: string;
|
|
235
215
|
label: string;
|
|
216
|
+
section: string;
|
|
236
217
|
users: number;
|
|
237
218
|
advancedToNextStep: number;
|
|
238
219
|
conversionToNextStep: number;
|
|
@@ -242,6 +223,7 @@ export declare const buildConversionReportPayload: (input: FunnelReportContext &
|
|
|
242
223
|
transitions: {
|
|
243
224
|
stepId: string;
|
|
244
225
|
step: string;
|
|
226
|
+
section: string | null;
|
|
245
227
|
nextStep: string;
|
|
246
228
|
entrants: number;
|
|
247
229
|
advancedToNextStep: number;
|
|
@@ -259,7 +241,6 @@ export declare const buildConversionReportPayload: (input: FunnelReportContext &
|
|
|
259
241
|
name: string;
|
|
260
242
|
slug: string;
|
|
261
243
|
};
|
|
262
|
-
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
263
244
|
};
|
|
264
245
|
export declare const buildFunnelPathReportPayload: (input: FunnelReportContext & {
|
|
265
246
|
pathFlow: AnalyticsFunnelFlow | null;
|
|
@@ -268,6 +249,7 @@ export declare const buildFunnelPathReportPayload: (input: FunnelReportContext &
|
|
|
268
249
|
stepId: string;
|
|
269
250
|
path: string;
|
|
270
251
|
label: string;
|
|
252
|
+
section: string;
|
|
271
253
|
users: number;
|
|
272
254
|
advancedToNextStep: number;
|
|
273
255
|
conversionToNextStep: number;
|
|
@@ -285,12 +267,12 @@ export declare const buildFunnelPathReportPayload: (input: FunnelReportContext &
|
|
|
285
267
|
name: string;
|
|
286
268
|
slug: string;
|
|
287
269
|
};
|
|
288
|
-
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
289
270
|
};
|
|
290
271
|
export declare const buildTransitionReportPayload: (input: FunnelReportContext) => {
|
|
291
272
|
transitions: {
|
|
292
273
|
stepId: string;
|
|
293
274
|
step: string;
|
|
275
|
+
section: string | null;
|
|
294
276
|
nextStep: string;
|
|
295
277
|
entrants: number;
|
|
296
278
|
advancedToNextStep: number;
|
|
@@ -308,7 +290,6 @@ export declare const buildTransitionReportPayload: (input: FunnelReportContext)
|
|
|
308
290
|
name: string;
|
|
309
291
|
slug: string;
|
|
310
292
|
};
|
|
311
|
-
journeyVersion: AnalyticsReportJourneyVersion | null;
|
|
312
293
|
};
|
|
313
294
|
export declare const buildCohortReportPayload: (input: ReportContext & {
|
|
314
295
|
cohort: MarketingCohortPerformanceReport;
|
|
@@ -324,7 +305,7 @@ export declare const buildCohortReportPayload: (input: ReportContext & {
|
|
|
324
305
|
} | null;
|
|
325
306
|
};
|
|
326
307
|
export declare const formatAnalyticsJson: (payload: unknown) => string;
|
|
327
|
-
type FunnelReportTableInput = Pick<FunnelReportContext, 'period' | '
|
|
308
|
+
type FunnelReportTableInput = Pick<FunnelReportContext, 'period' | 'stepDropoff'>;
|
|
328
309
|
export declare const formatTransitionsTable: (input: FunnelReportTableInput) => string;
|
|
329
310
|
export declare const formatFunnelPathTable: (input: FunnelReportTableInput & {
|
|
330
311
|
pathFlow: AnalyticsFunnelFlow | null;
|
|
@@ -333,25 +314,6 @@ export declare const formatConversionsTable: (input: FunnelReportTableInput & {
|
|
|
333
314
|
overview: AnalyticsOverviewResponse;
|
|
334
315
|
pathFlow: AnalyticsFunnelFlow | null;
|
|
335
316
|
}) => 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;
|
|
355
317
|
export declare const formatCohortTable: (input: {
|
|
356
318
|
date: string;
|
|
357
319
|
cohort: MarketingCohortPerformanceReport;
|
package/dist/analyticsOutput.js
CHANGED
|
@@ -66,9 +66,6 @@ const hasPositiveNumber = (value) => {
|
|
|
66
66
|
}
|
|
67
67
|
return false;
|
|
68
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
|
-
: '');
|
|
72
69
|
export const assertHasConversionData = (date, overview, stepDropoff) => {
|
|
73
70
|
if (hasPositiveNumber(overview.totals) ||
|
|
74
71
|
hasPositiveNumber(overview.byDay) ||
|
|
@@ -76,19 +73,19 @@ export const assertHasConversionData = (date, overview, stepDropoff) => {
|
|
|
76
73
|
hasPositiveNumber(stepDropoff.steps)) {
|
|
77
74
|
return;
|
|
78
75
|
}
|
|
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
|
|
76
|
+
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.`);
|
|
80
77
|
};
|
|
81
78
|
export const assertHasTransitionData = (date, stepDropoff) => {
|
|
82
79
|
if (stepDropoff.steps.length > 0 && hasPositiveNumber(stepDropoff.steps)) {
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
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
|
|
82
|
+
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.`);
|
|
86
83
|
};
|
|
87
84
|
export const assertHasFunnelPathData = (date, stepDropoff, pathFlow) => {
|
|
88
85
|
if (buildFunnelPathRows(stepDropoff, pathFlow).length > 0) {
|
|
89
86
|
return;
|
|
90
87
|
}
|
|
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
|
|
88
|
+
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.`);
|
|
92
89
|
};
|
|
93
90
|
export const assertHasCohortData = (date, report) => {
|
|
94
91
|
if (report.sourceHealth && !report.sourceHealth.complete) {
|
|
@@ -114,31 +111,45 @@ const formatCurrency = (value, currency = 'USD') => new Intl.NumberFormat('en-US
|
|
|
114
111
|
}).format(value);
|
|
115
112
|
const formatMinorCurrency = (value) => formatCurrency((Number(value) || 0) / 100);
|
|
116
113
|
const stepDisplayName = (step) => step.stepName || step.stepId;
|
|
117
|
-
export const buildTransitionRows = (stepDropoff) =>
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
export const buildTransitionRows = (stepDropoff) => {
|
|
115
|
+
const currentOrder = new Map(stepDropoff.flow?.steps.map(step => [step.stepId, step.stepOrder]));
|
|
116
|
+
return stepDropoff.steps
|
|
117
|
+
.filter(step => step.entrants > 0)
|
|
118
|
+
.sort((left, right) => ((currentOrder.get(left.stepId) ?? Number.MAX_SAFE_INTEGER)
|
|
119
|
+
- (currentOrder.get(right.stepId) ?? Number.MAX_SAFE_INTEGER)
|
|
120
|
+
|| left.stepOrder - right.stepOrder
|
|
121
|
+
|| left.stepId.localeCompare(right.stepId)))
|
|
122
|
+
.map(step => ({
|
|
123
|
+
stepId: step.stepId,
|
|
124
|
+
step: stepDisplayName(step),
|
|
125
|
+
section: !stepDropoff.flow ? null : currentOrder.has(step.stepId) ? 'current' : 'not-in-current-funnel',
|
|
126
|
+
nextStep: step.nextStepId || '',
|
|
127
|
+
entrants: step.entrants,
|
|
128
|
+
advancedToNextStep: step.advancedToNextStep,
|
|
129
|
+
dropoffs: step.dropoffs,
|
|
130
|
+
dropoffRate: step.dropoffRate,
|
|
131
|
+
}));
|
|
132
|
+
};
|
|
128
133
|
export const buildFunnelPathRows = (stepDropoff, pathFlow) => {
|
|
129
|
-
if (!pathFlow)
|
|
134
|
+
if (!pathFlow)
|
|
130
135
|
return [];
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
.
|
|
136
|
+
const transitionsByStepId = new Map(stepDropoff.steps.map(step => [step.stepId, step]));
|
|
137
|
+
const currentStepIds = new Set(pathFlow.steps.map(step => step.stepId));
|
|
138
|
+
const removedSteps = stepDropoff.steps
|
|
139
|
+
.filter(step => step.entrants > 0 && !currentStepIds.has(step.stepId))
|
|
140
|
+
.sort((left, right) => left.stepOrder - right.stepOrder || left.stepId.localeCompare(right.stepId));
|
|
141
|
+
const orderedSteps = [
|
|
142
|
+
...[...pathFlow.steps].sort((left, right) => left.stepOrder - right.stepOrder || left.stepId.localeCompare(right.stepId)),
|
|
143
|
+
...removedSteps.map(step => ({ ...step, stepTitle: null, stepPath: null })),
|
|
144
|
+
];
|
|
145
|
+
return orderedSteps.map(flowStep => {
|
|
136
146
|
const step = transitionsByStepId.get(flowStep.stepId);
|
|
137
147
|
const entrants = step?.entrants ?? 0;
|
|
138
148
|
return {
|
|
139
149
|
stepId: flowStep.stepId,
|
|
140
150
|
path: flowStep.stepPath || flowStep.stepId,
|
|
141
151
|
label: flowStep.stepTitle || flowStep.stepName || flowStep.stepId,
|
|
152
|
+
section: currentStepIds.has(flowStep.stepId) ? 'current' : 'not-in-current-funnel',
|
|
142
153
|
users: entrants,
|
|
143
154
|
advancedToNextStep: step?.advancedToNextStep ?? 0,
|
|
144
155
|
conversionToNextStep: entrants > 0 ? (step?.advancedToNextStep ?? 0) / entrants : 0,
|
|
@@ -147,22 +158,6 @@ export const buildFunnelPathRows = (stepDropoff, pathFlow) => {
|
|
|
147
158
|
};
|
|
148
159
|
});
|
|
149
160
|
};
|
|
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
161
|
const buildFunnelReportHeader = (input) => ({
|
|
167
162
|
generatedAt: new Date().toISOString(),
|
|
168
163
|
date: input.period.date,
|
|
@@ -171,16 +166,9 @@ const buildFunnelReportHeader = (input) => ({
|
|
|
171
166
|
workspaceId: input.workspaceId,
|
|
172
167
|
project: input.project,
|
|
173
168
|
funnel: input.stepDropoff.funnel,
|
|
174
|
-
journeyVersion: describeReportJourneyVersion(input),
|
|
175
169
|
});
|
|
176
170
|
const formatFunnelReportTitle = (title, input) => {
|
|
177
|
-
|
|
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');
|
|
171
|
+
return [title, input.period.label, input.stepDropoff.funnel.name].join('\t');
|
|
184
172
|
};
|
|
185
173
|
export const buildConversionReportPayload = (input) => ({
|
|
186
174
|
...buildFunnelReportHeader(input),
|
|
@@ -212,7 +200,12 @@ export const formatTransitionsTable = (input) => {
|
|
|
212
200
|
formatFunnelReportTitle('Step transitions', input),
|
|
213
201
|
'step\tnextStep\tentrants\tadvanced\tdropoffs\tdropoffRate',
|
|
214
202
|
];
|
|
203
|
+
let removedSectionStarted = false;
|
|
215
204
|
for (const row of buildTransitionRows(input.stepDropoff)) {
|
|
205
|
+
if (row.section === 'not-in-current-funnel' && !removedSectionStarted) {
|
|
206
|
+
lines.push('Not in current funnel');
|
|
207
|
+
removedSectionStarted = true;
|
|
208
|
+
}
|
|
216
209
|
lines.push([
|
|
217
210
|
row.step,
|
|
218
211
|
row.nextStep,
|
|
@@ -229,7 +222,12 @@ export const formatFunnelPathTable = (input) => {
|
|
|
229
222
|
formatFunnelReportTitle('Full funnel path', input),
|
|
230
223
|
'step\tpath\tusers\tadvanced\tconversionToNext\tdropoffs\tdropoffRate',
|
|
231
224
|
];
|
|
225
|
+
let removedSectionStarted = false;
|
|
232
226
|
for (const row of buildFunnelPathRows(input.stepDropoff, input.pathFlow)) {
|
|
227
|
+
if (row.section === 'not-in-current-funnel' && !removedSectionStarted) {
|
|
228
|
+
lines.push('Not in current funnel');
|
|
229
|
+
removedSectionStarted = true;
|
|
230
|
+
}
|
|
233
231
|
lines.push([
|
|
234
232
|
row.label,
|
|
235
233
|
row.path,
|
|
@@ -272,30 +270,6 @@ export const formatConversionsTable = (input) => {
|
|
|
272
270
|
lines.push('', formatFunnelPathTable(input).trimEnd());
|
|
273
271
|
return `${lines.join('\n')}\n`;
|
|
274
272
|
};
|
|
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
|
-
}
|
|
297
|
-
return `${lines.join('\n')}\n`;
|
|
298
|
-
};
|
|
299
273
|
const flattenCohortRows = (rows) => rows.flatMap((row) => [row, ...(row.segments || []), ...(row.children || [])]);
|
|
300
274
|
const normalizeCurrency = (value) => {
|
|
301
275
|
if (typeof value !== 'string') {
|
|
@@ -308,27 +282,6 @@ const sameCurrency = (left, right) => {
|
|
|
308
282
|
const normalizedLeft = normalizeCurrency(left);
|
|
309
283
|
return normalizedLeft !== null && normalizedLeft === normalizeCurrency(right);
|
|
310
284
|
};
|
|
311
|
-
const predictedAverageLtv = (row) => {
|
|
312
|
-
if (!normalizeCurrency(row.pLtvCurrency)) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
if (row.pLtvPredictedCustomerCount > 0) {
|
|
316
|
-
return typeof row.pLtvPredictedNetRevenueDay365Minor === 'number'
|
|
317
|
-
&& Number.isFinite(row.pLtvPredictedNetRevenueDay365Minor)
|
|
318
|
-
? row.pLtvPredictedNetRevenueDay365Minor
|
|
319
|
-
/ row.pLtvPredictedCustomerCount
|
|
320
|
-
/ 100
|
|
321
|
-
: null;
|
|
322
|
-
}
|
|
323
|
-
return typeof row.pLtv === 'number' && Number.isFinite(row.pLtv)
|
|
324
|
-
? row.pLtv
|
|
325
|
-
: null;
|
|
326
|
-
};
|
|
327
|
-
const ratioToSpend = (value, spend) => (Number.isFinite(value) && Number.isFinite(spend) && spend > 0 ? value / spend : null);
|
|
328
|
-
const formatRoi = (profit, spend) => {
|
|
329
|
-
const ratio = ratioToSpend(profit, spend);
|
|
330
|
-
return ratio === null ? '' : `${(ratio * 100).toFixed(2)}%`;
|
|
331
|
-
};
|
|
332
285
|
const forecastDefinitions = {
|
|
333
286
|
day3: {
|
|
334
287
|
minor: 'predictedNetRevenueDay3Minor',
|
|
@@ -370,14 +323,8 @@ export const formatCohortTable = (input) => {
|
|
|
370
323
|
];
|
|
371
324
|
for (const row of flattenCohortRows(input.cohort.rows)) {
|
|
372
325
|
const cpa = row.subscribers > 0 ? row.spend / row.subscribers : 0;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
? null
|
|
376
|
-
: averageLtv * row.subscribers;
|
|
377
|
-
const predictedProfit = predictedRevenue !== null
|
|
378
|
-
&& sameCurrency(row.pLtvCurrency, row.spendCurrency)
|
|
379
|
-
? predictedRevenue - row.spend
|
|
380
|
-
: null;
|
|
326
|
+
const predictedRevenue = row.predictedRevenue ?? null;
|
|
327
|
+
const predictedProfit = row.predictedProfit ?? null;
|
|
381
328
|
const spendCurrency = normalizeCurrency(row.spendCurrency);
|
|
382
329
|
const revenueCurrency = row.revenueCurrencySafe === true
|
|
383
330
|
? normalizeCurrency(row.revenueCurrency)
|
|
@@ -408,8 +355,8 @@ export const formatCohortTable = (input) => {
|
|
|
408
355
|
predictedProfit === null
|
|
409
356
|
? ''
|
|
410
357
|
: formatCurrency(predictedProfit, pLtvCurrency || 'USD'),
|
|
411
|
-
|
|
412
|
-
|
|
358
|
+
formatPercentPoints(row.predictedRoiPercent ?? null),
|
|
359
|
+
formatPercentPoints(row.roasDay365Percent ?? null),
|
|
413
360
|
formatForecastRoas(row, forecastDefinitions.day3),
|
|
414
361
|
formatForecastRoas(row, forecastDefinitions.day90),
|
|
415
362
|
formatForecastRoas(row, forecastDefinitions.day180),
|
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 AnalyticsFunnelFlow, type
|
|
11
|
+
import { type AnalyticsFunnelFlow, 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 {
|
|
@@ -149,7 +149,6 @@ type FunnelAnalyticsCommandInput = {
|
|
|
149
149
|
};
|
|
150
150
|
format: AnalyticsOutputFormat;
|
|
151
151
|
timezone?: string;
|
|
152
|
-
journeyVersion: string;
|
|
153
152
|
};
|
|
154
153
|
type SyncTargetIdInput = {
|
|
155
154
|
explicitWorkspaceId?: string;
|
|
@@ -174,7 +173,6 @@ export declare function buildFunnelAnalyticsCommandInput(options: {
|
|
|
174
173
|
date?: string;
|
|
175
174
|
from?: string;
|
|
176
175
|
to?: string;
|
|
177
|
-
journeyVersion?: string;
|
|
178
176
|
format?: string;
|
|
179
177
|
timezone?: string;
|
|
180
178
|
}): FunnelAnalyticsCommandInput;
|
|
@@ -312,12 +310,4 @@ export declare const fetchFunnelPathAnalytics: (input: FunnelPathAnalyticsInput,
|
|
|
312
310
|
stepDropoff: AnalyticsStepDropoffResponse;
|
|
313
311
|
pathFlow: AnalyticsFunnelFlow | null;
|
|
314
312
|
}>;
|
|
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[];
|
|
322
|
-
}>;
|
|
323
313
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ import { applyTemplateDocsUpdate, inspectTemplateDocs, loadBundledDocs, planTemp
|
|
|
32
32
|
import { renderValidationHuman, renderValidationJson, } from './diagnosticOutput.js';
|
|
33
33
|
import { validateFunnel } from './funnelValidation.js';
|
|
34
34
|
import { StepContractMigrationError, applyStepContractV2Migration, checkStepContractV2Migration, recoverStepContractV2Migration, resolveStepContractMigrationMode, stepContractMigrationReportPath, } from './stepContractMigration.js';
|
|
35
|
-
import { assertHasCohortData, assertHasConversionData, assertHasFunnelPathData, assertHasTransitionData, buildCohortReportPayload, buildConversionReportPayload, buildFunnelPathReportPayload,
|
|
35
|
+
import { assertHasCohortData, assertHasConversionData, assertHasFunnelPathData, assertHasTransitionData, buildCohortReportPayload, buildConversionReportPayload, buildFunnelPathReportPayload, buildTransitionReportPayload, formatAnalyticsJson, formatCohortTable, formatConversionsTable, formatFunnelPathTable, formatTransitionsTable, normalizeAnalyticsOutputFormat, parseAnalyticsDate, parseAnalyticsPeriod, } from './analyticsOutput.js';
|
|
36
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
37
37
|
const toKebabCase = (value) => value
|
|
38
38
|
.trim()
|
|
@@ -1118,19 +1118,11 @@ export function buildAnalyticsCommandInput(options) {
|
|
|
1118
1118
|
timezone,
|
|
1119
1119
|
};
|
|
1120
1120
|
}
|
|
1121
|
-
const parseAnalyticsJourneyVersion = (value) => {
|
|
1122
|
-
const normalized = (value || 'current').trim().toLowerCase();
|
|
1123
|
-
if (normalized === 'current' || normalized === 'all' || UUID_PATTERN.test(normalized)) {
|
|
1124
|
-
return normalized;
|
|
1125
|
-
}
|
|
1126
|
-
throw new Error('--journey-version must be current, all, or a journey version id from `fgrove analytics versions`.');
|
|
1127
|
-
};
|
|
1128
1121
|
export function buildFunnelAnalyticsCommandInput(options) {
|
|
1129
1122
|
return {
|
|
1130
1123
|
period: parseAnalyticsPeriod(options),
|
|
1131
1124
|
format: normalizeAnalyticsOutputFormat(options.format),
|
|
1132
1125
|
timezone: options.timezone?.trim() || undefined,
|
|
1133
|
-
journeyVersion: parseAnalyticsJourneyVersion(options.journeyVersion),
|
|
1134
1126
|
};
|
|
1135
1127
|
}
|
|
1136
1128
|
export function buildPatchSourceInput(input) {
|
|
@@ -2335,7 +2327,6 @@ const analyticsCommand = addExamples(program.command('analytics').description('D
|
|
|
2335
2327
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out analytics.json',
|
|
2336
2328
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2337
2329
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2338
|
-
'fgrove analytics versions --project claimbee --funnel claimbee-ios',
|
|
2339
2330
|
'fgrove analytics cohort --project claimbee --date 2026-06-11 --format json',
|
|
2340
2331
|
]);
|
|
2341
2332
|
const resolveFunnelAnalyticsTarget = async (options) => {
|
|
@@ -2389,7 +2380,6 @@ const fetchStepDropoffAnalytics = async (input, apiCall = callApi) => {
|
|
|
2389
2380
|
workspaceId: input.workspaceId,
|
|
2390
2381
|
projectId: input.projectId,
|
|
2391
2382
|
funnelId: input.funnelId,
|
|
2392
|
-
...(input.commandInput.journeyVersion === 'current' ? {} : { versionId: input.commandInput.journeyVersion }),
|
|
2393
2383
|
from: input.commandInput.period.fromIso,
|
|
2394
2384
|
to: input.commandInput.period.toIso,
|
|
2395
2385
|
timezone: input.commandInput.timezone,
|
|
@@ -2409,13 +2399,6 @@ const fetchPublishedFunnelFlow = async (input, apiCall = callApi) => {
|
|
|
2409
2399
|
});
|
|
2410
2400
|
};
|
|
2411
2401
|
export const fetchFunnelPathAnalytics = async (input, apiCall = callApi) => {
|
|
2412
|
-
const { journeyVersion } = input.commandInput;
|
|
2413
|
-
if (journeyVersion !== 'current' && journeyVersion !== 'all') {
|
|
2414
|
-
// An explicit Journey Version reports its own structure and is never
|
|
2415
|
-
// paired with the current published path.
|
|
2416
|
-
const stepDropoff = await fetchStepDropoffAnalytics(input, apiCall);
|
|
2417
|
-
return { stepDropoff, pathFlow: stepDropoff.flow };
|
|
2418
|
-
}
|
|
2419
2402
|
const [stepDropoff, publishedFunnelFlow] = await Promise.all([
|
|
2420
2403
|
fetchStepDropoffAnalytics(input, apiCall),
|
|
2421
2404
|
fetchPublishedFunnelFlow(input, apiCall),
|
|
@@ -2425,46 +2408,6 @@ export const fetchFunnelPathAnalytics = async (input, apiCall = callApi) => {
|
|
|
2425
2408
|
pathFlow: publishedFunnelFlow.status === 'ready' ? publishedFunnelFlow.flow : null,
|
|
2426
2409
|
};
|
|
2427
2410
|
};
|
|
2428
|
-
// The API reads analytics dates in this timezone when a request names none.
|
|
2429
|
-
const DEFAULT_ANALYTICS_TIMEZONE = 'America/Los_Angeles';
|
|
2430
|
-
const toAnalyticsDate = (value, timezone) => new Intl.DateTimeFormat('en-CA', { timeZone: timezone || DEFAULT_ANALYTICS_TIMEZONE }).format(value);
|
|
2431
|
-
export const fetchJourneyVersions = async (input, apiCall = callApi) => {
|
|
2432
|
-
const { today, timezone, ...target } = input;
|
|
2433
|
-
const current = await fetchStepDropoffAnalytics({
|
|
2434
|
-
...target,
|
|
2435
|
-
commandInput: buildFunnelAnalyticsCommandInput({ date: today, timezone }),
|
|
2436
|
-
}, apiCall);
|
|
2437
|
-
const newestFirst = [...current.version.options].sort((left, right) => right.seq - left.seq);
|
|
2438
|
-
// A version is active from its start day until the next version starts;
|
|
2439
|
-
// version-scoped counts make the shared switch day safe to include in both.
|
|
2440
|
-
const startDate = (startedAt) => (startedAt ? toAnalyticsDate(new Date(startedAt), timezone) : null);
|
|
2441
|
-
const versions = await Promise.all(newestFirst.map(async (version, index) => {
|
|
2442
|
-
const activeFrom = startDate(version.publishedAt);
|
|
2443
|
-
const activeTo = index === 0 ? null : startDate(newestFirst[index - 1].publishedAt);
|
|
2444
|
-
const report = activeFrom
|
|
2445
|
-
? await fetchStepDropoffAnalytics({
|
|
2446
|
-
...target,
|
|
2447
|
-
commandInput: buildFunnelAnalyticsCommandInput({
|
|
2448
|
-
from: activeFrom,
|
|
2449
|
-
to: activeTo ?? today,
|
|
2450
|
-
journeyVersion: version.id,
|
|
2451
|
-
timezone,
|
|
2452
|
-
}),
|
|
2453
|
-
}, apiCall)
|
|
2454
|
-
: null;
|
|
2455
|
-
return {
|
|
2456
|
-
id: version.id,
|
|
2457
|
-
seq: version.seq,
|
|
2458
|
-
label: journeyVersionLabel(version),
|
|
2459
|
-
isCurrent: version.isCurrent,
|
|
2460
|
-
startedAt: version.publishedAt,
|
|
2461
|
-
activeFrom,
|
|
2462
|
-
activeTo,
|
|
2463
|
-
visitors: report ? report.totals.visitors ?? 0 : null,
|
|
2464
|
-
};
|
|
2465
|
-
}));
|
|
2466
|
-
return { funnel: current.funnel, versions };
|
|
2467
|
-
};
|
|
2468
2411
|
const addFunnelReportOptions = (command) => command
|
|
2469
2412
|
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2470
2413
|
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
@@ -2472,7 +2415,6 @@ const addFunnelReportOptions = (command) => command
|
|
|
2472
2415
|
.option('--date <yyyy-mm-dd>', 'Analytics date; use instead of --from/--to for one day')
|
|
2473
2416
|
.option('--from <yyyy-mm-dd>', 'First analytics date of a range, used with --to')
|
|
2474
2417
|
.option('--to <yyyy-mm-dd>', 'Last analytics date of a range, used with --from')
|
|
2475
|
-
.option('--journey-version <id|current|all>', 'Journey version: current, all, or an id from `fgrove analytics versions`', 'current')
|
|
2476
2418
|
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2477
2419
|
.option('--out <path>', 'Write output to a file')
|
|
2478
2420
|
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles');
|
|
@@ -2481,7 +2423,7 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2481
2423
|
.description('Download conversion data, full funnel path, and step transitions for a day or date range')), [
|
|
2482
2424
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2483
2425
|
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json --out analytics.json',
|
|
2484
|
-
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14
|
|
2426
|
+
'fgrove analytics conversions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14',
|
|
2485
2427
|
])
|
|
2486
2428
|
.action(async (options) => {
|
|
2487
2429
|
const context = await loadFunnelAnalyticsContext(options);
|
|
@@ -2512,7 +2454,6 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2512
2454
|
assertHasConversionData(context.commandInput.period.label, overview, stepDropoff);
|
|
2513
2455
|
const report = {
|
|
2514
2456
|
period: context.commandInput.period,
|
|
2515
|
-
journeyVersion: context.commandInput.journeyVersion,
|
|
2516
2457
|
stepDropoff,
|
|
2517
2458
|
overview,
|
|
2518
2459
|
pathFlow,
|
|
@@ -2533,7 +2474,7 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2533
2474
|
.description('Download the full funnel path report for a day or date range')), [
|
|
2534
2475
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2535
2476
|
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json',
|
|
2536
|
-
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14
|
|
2477
|
+
'fgrove analytics funnel-path --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14',
|
|
2537
2478
|
])
|
|
2538
2479
|
.action(async (options) => {
|
|
2539
2480
|
const context = await loadFunnelAnalyticsContext(options);
|
|
@@ -2547,7 +2488,6 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2547
2488
|
assertHasFunnelPathData(context.commandInput.period.label, stepDropoff, pathFlow);
|
|
2548
2489
|
const report = {
|
|
2549
2490
|
period: context.commandInput.period,
|
|
2550
|
-
journeyVersion: context.commandInput.journeyVersion,
|
|
2551
2491
|
stepDropoff,
|
|
2552
2492
|
pathFlow,
|
|
2553
2493
|
};
|
|
@@ -2567,7 +2507,7 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2567
2507
|
.description('Download step transitions and drop-offs for a day or date range')), [
|
|
2568
2508
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11',
|
|
2569
2509
|
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --date 2026-06-11 --format json',
|
|
2570
|
-
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14
|
|
2510
|
+
'fgrove analytics transitions --project claimbee --funnel claimbee-ios --from 2026-06-01 --to 2026-06-14',
|
|
2571
2511
|
])
|
|
2572
2512
|
.action(async (options) => {
|
|
2573
2513
|
const context = await loadFunnelAnalyticsContext(options);
|
|
@@ -2581,7 +2521,6 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2581
2521
|
assertHasTransitionData(context.commandInput.period.label, stepDropoff);
|
|
2582
2522
|
const report = {
|
|
2583
2523
|
period: context.commandInput.period,
|
|
2584
|
-
journeyVersion: context.commandInput.journeyVersion,
|
|
2585
2524
|
stepDropoff,
|
|
2586
2525
|
};
|
|
2587
2526
|
await writeOrPrintAnalyticsOutput({
|
|
@@ -2595,41 +2534,6 @@ addExamples(addFunnelReportOptions(analyticsCommand
|
|
|
2595
2534
|
out: options.out,
|
|
2596
2535
|
});
|
|
2597
2536
|
});
|
|
2598
|
-
addExamples(analyticsCommand
|
|
2599
|
-
.command('versions')
|
|
2600
|
-
.description('List journey versions with their active days and visitors')
|
|
2601
|
-
.option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
|
|
2602
|
-
.option('--project <id-or-slug-or-name>', 'Project id, slug, or name')
|
|
2603
|
-
.option('--funnel <id-or-slug-or-name>', 'Funnel id, slug, or name')
|
|
2604
|
-
.option('--format <table-or-json>', 'Output format: table or json', 'table')
|
|
2605
|
-
.option('--out <path>', 'Write output to a file')
|
|
2606
|
-
.option('--timezone <iana-timezone>', 'Analytics timezone, for example America/Los_Angeles'), [
|
|
2607
|
-
'fgrove analytics versions --project claimbee --funnel claimbee-ios',
|
|
2608
|
-
'fgrove analytics versions --project claimbee --funnel claimbee-ios --format json',
|
|
2609
|
-
])
|
|
2610
|
-
.action(async (options) => {
|
|
2611
|
-
const format = normalizeAnalyticsOutputFormat(options.format);
|
|
2612
|
-
const timezone = options.timezone?.trim() || undefined;
|
|
2613
|
-
const target = await resolveFunnelAnalyticsTarget(options);
|
|
2614
|
-
const report = await fetchJourneyVersions({
|
|
2615
|
-
token: target.token,
|
|
2616
|
-
workspaceId: target.workspace.id,
|
|
2617
|
-
projectId: target.project.id,
|
|
2618
|
-
funnelId: target.funnel.id,
|
|
2619
|
-
timezone,
|
|
2620
|
-
today: toAnalyticsDate(new Date(), timezone),
|
|
2621
|
-
});
|
|
2622
|
-
await writeOrPrintAnalyticsOutput({
|
|
2623
|
-
format,
|
|
2624
|
-
payload: buildJourneyVersionsReportPayload({
|
|
2625
|
-
...report,
|
|
2626
|
-
workspaceId: target.workspace.id,
|
|
2627
|
-
project: target.project,
|
|
2628
|
-
}),
|
|
2629
|
-
table: formatJourneyVersionsTable(report),
|
|
2630
|
-
out: options.out,
|
|
2631
|
-
});
|
|
2632
|
-
});
|
|
2633
2537
|
addExamples(analyticsCommand
|
|
2634
2538
|
.command('cohort')
|
|
2635
2539
|
.description('Download one-day cohort marketing data')
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"minimumCliVersion": "0.1.20",
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
|
-
"repositoryCliVersion": "0.1.
|
|
7
|
+
"repositoryCliVersion": "0.1.293",
|
|
8
8
|
"manifest": {
|
|
9
9
|
"schemaVersion": 1,
|
|
10
|
-
"bundleVersion": "2.0.
|
|
10
|
+
"bundleVersion": "2.0.286",
|
|
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": "f1831876670eae6a6029f8f48d12038de408d4fb31a3332f7cec5cb3c7e4ff1d"
|
|
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": "53e951dfdadfc634fa7bbd53615a5e2e3019cd1bce24b3a4d667e50557268dd7"
|
|
149
149
|
}
|
|
150
150
|
]
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
|
-
"repositoryCliVersion": "0.1.
|
|
154
|
+
"repositoryCliVersion": "0.1.292",
|
|
155
155
|
"manifest": {
|
|
156
156
|
"schemaVersion": 1,
|
|
157
|
-
"bundleVersion": "2.0.
|
|
157
|
+
"bundleVersion": "2.0.285",
|
|
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": "9f91fab6b6af7456e614811d75fc4007e35b0c26086135ddd6dbf5d0491253e7"
|
|
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": "2c4bd0e6e5677772fcb6889d63833d9fcdd9d66da167571219aa5145567b335f"
|
|
296
296
|
}
|
|
297
297
|
]
|
|
298
298
|
}
|
|
299
299
|
},
|
|
300
300
|
{
|
|
301
|
-
"repositoryCliVersion": "0.1.
|
|
301
|
+
"repositoryCliVersion": "0.1.291",
|
|
302
302
|
"manifest": {
|
|
303
303
|
"schemaVersion": 1,
|
|
304
|
-
"bundleVersion": "2.0.
|
|
304
|
+
"bundleVersion": "2.0.284",
|
|
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": "32c4dc6493b9195c1d0c601e593975bfbd029eb348e986c87f9823b7d390e406"
|
|
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": "bac474b7e4180ebc96f0b0ed2f1391b291d7065e3c626f90a7dac4aed4c567d8"
|
|
443
443
|
}
|
|
444
444
|
]
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
|
-
"repositoryCliVersion": "0.1.
|
|
448
|
+
"repositoryCliVersion": "0.1.290",
|
|
449
449
|
"manifest": {
|
|
450
450
|
"schemaVersion": 1,
|
|
451
|
-
"bundleVersion": "2.0.
|
|
451
|
+
"bundleVersion": "2.0.283",
|
|
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": "ca5a4836a51320df2216d317108e39384aa732dd92bd8f9085b41d88e4cfcc9d"
|
|
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": "f999b566c6188943f384dc6fac60fcbb26ffc071d316c78bd264ef0f04319e5b"
|
|
590
590
|
}
|
|
591
591
|
]
|
|
592
592
|
}
|
|
593
593
|
},
|
|
594
594
|
{
|
|
595
|
-
"repositoryCliVersion": "0.1.
|
|
595
|
+
"repositoryCliVersion": "0.1.289",
|
|
596
596
|
"manifest": {
|
|
597
597
|
"schemaVersion": 1,
|
|
598
|
-
"bundleVersion": "2.0.
|
|
598
|
+
"bundleVersion": "2.0.282",
|
|
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": "474e05021e90bef7f781a96542c45c409e04b97d13fb8027d3d4b760d90ae336"
|
|
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": "7b8e363b1ad3d48aebcfa1d4c2606a61d8e18a809b42ff19ceffcb800bc6e3fd"
|
|
737
737
|
}
|
|
738
738
|
]
|
|
739
739
|
}
|
|
740
740
|
},
|
|
741
741
|
{
|
|
742
|
-
"repositoryCliVersion": "0.1.
|
|
742
|
+
"repositoryCliVersion": "0.1.288",
|
|
743
743
|
"manifest": {
|
|
744
744
|
"schemaVersion": 1,
|
|
745
|
-
"bundleVersion": "2.0.
|
|
745
|
+
"bundleVersion": "2.0.281",
|
|
746
746
|
"stepContractVersion": 3,
|
|
747
747
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
748
748
|
"managedFiles": [
|
|
@@ -760,7 +760,7 @@
|
|
|
760
760
|
},
|
|
761
761
|
{
|
|
762
762
|
"path": "docs/funnelsgrove/contracts/analytics-events.md",
|
|
763
|
-
"sha256": "
|
|
763
|
+
"sha256": "30a445a2042549fe4bc87e0635785b5ff7876cbba0c8c27db2fcb04de3976f47"
|
|
764
764
|
},
|
|
765
765
|
{
|
|
766
766
|
"path": "docs/funnelsgrove/contracts/content-answers.md",
|
|
@@ -772,7 +772,7 @@
|
|
|
772
772
|
},
|
|
773
773
|
{
|
|
774
774
|
"path": "docs/funnelsgrove/contracts/payments.md",
|
|
775
|
-
"sha256": "
|
|
775
|
+
"sha256": "6f9c9782340fef18a39fb6321ee2c80f5dcfaf210114a4dd0351b6f27aeacc11"
|
|
776
776
|
},
|
|
777
777
|
{
|
|
778
778
|
"path": "docs/funnelsgrove/contracts/step-metadata.md",
|
|
@@ -780,7 +780,7 @@
|
|
|
780
780
|
},
|
|
781
781
|
{
|
|
782
782
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
783
|
-
"sha256": "
|
|
783
|
+
"sha256": "bed96c808dba9780b3ea3cefba296e650e08d8d30708e5daecee2b00b633c4ef"
|
|
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": "616b91c1bd60184c3ca34f4b9e0aecd5773667998a50fcd50dc7d374e0b71d71"
|
|
884
884
|
}
|
|
885
885
|
]
|
|
886
886
|
}
|
|
887
887
|
},
|
|
888
888
|
{
|
|
889
|
-
"repositoryCliVersion": "0.1.
|
|
889
|
+
"repositoryCliVersion": "0.1.287",
|
|
890
890
|
"manifest": {
|
|
891
891
|
"schemaVersion": 1,
|
|
892
|
-
"bundleVersion": "2.0.
|
|
892
|
+
"bundleVersion": "2.0.280",
|
|
893
893
|
"stepContractVersion": 3,
|
|
894
894
|
"contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
|
|
895
895
|
"managedFiles": [
|
|
@@ -907,7 +907,7 @@
|
|
|
907
907
|
},
|
|
908
908
|
{
|
|
909
909
|
"path": "docs/funnelsgrove/contracts/analytics-events.md",
|
|
910
|
-
"sha256": "
|
|
910
|
+
"sha256": "30a445a2042549fe4bc87e0635785b5ff7876cbba0c8c27db2fcb04de3976f47"
|
|
911
911
|
},
|
|
912
912
|
{
|
|
913
913
|
"path": "docs/funnelsgrove/contracts/content-answers.md",
|
|
@@ -919,7 +919,7 @@
|
|
|
919
919
|
},
|
|
920
920
|
{
|
|
921
921
|
"path": "docs/funnelsgrove/contracts/payments.md",
|
|
922
|
-
"sha256": "
|
|
922
|
+
"sha256": "6f9c9782340fef18a39fb6321ee2c80f5dcfaf210114a4dd0351b6f27aeacc11"
|
|
923
923
|
},
|
|
924
924
|
{
|
|
925
925
|
"path": "docs/funnelsgrove/contracts/step-metadata.md",
|
|
@@ -927,7 +927,7 @@
|
|
|
927
927
|
},
|
|
928
928
|
{
|
|
929
929
|
"path": "docs/funnelsgrove/migrations/step-contract-v3.md",
|
|
930
|
-
"sha256": "
|
|
930
|
+
"sha256": "8be689adbc24bc411eb7675e6938060d7fdb4588d8dd0175e763a487017c3422"
|
|
931
931
|
},
|
|
932
932
|
{
|
|
933
933
|
"path": "docs/funnelsgrove/qa/analytics.md",
|
|
@@ -1027,7 +1027,7 @@
|
|
|
1027
1027
|
},
|
|
1028
1028
|
{
|
|
1029
1029
|
"path": "funnel-docs.config.json",
|
|
1030
|
-
"sha256": "
|
|
1030
|
+
"sha256": "fc505f7191aba1e820f935e33e729fe9fca4b3d529f0fd79e9c9a0692892a935"
|
|
1031
1031
|
}
|
|
1032
1032
|
]
|
|
1033
1033
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.293",
|
|
4
4
|
"description": "FunnelsGrove command-line tools for editing, syncing, and publishing funnels",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"test": "vitest run"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@funnelsgrove/runtime": "0.
|
|
37
|
+
"@funnelsgrove/runtime": "0.32.0",
|
|
38
38
|
"commander": "^12.0.0",
|
|
39
39
|
"ignore": "^7.0.8",
|
|
40
40
|
"typescript": "^5.8.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@funnelsgrove/analytics": "0.
|
|
44
|
-
"@funnelsgrove/payments": "0.
|
|
43
|
+
"@funnelsgrove/analytics": "0.14.0",
|
|
44
|
+
"@funnelsgrove/payments": "0.41.0",
|
|
45
45
|
"vitest": "^3.0.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion": "2.0.
|
|
3
|
+
"bundleVersion": "2.0.286",
|
|
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": "f1831876670eae6a6029f8f48d12038de408d4fb31a3332f7cec5cb3c7e4ff1d"
|
|
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": "53e951dfdadfc634fa7bbd53615a5e2e3019cd1bce24b3a4d667e50557268dd7"
|
|
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.32.0`, then `@funnelsgrove/analytics` `0.14.0`, then `@funnelsgrove/payments` `0.41.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.32.0`, then `@funnelsgrove/analytics` `0.14.0`, then `@funnelsgrove/payments` `0.41.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.293`, 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.286",
|
|
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": "f1831876670eae6a6029f8f48d12038de408d4fb31a3332f7cec5cb3c7e4ff1d"
|
|
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": "53e951dfdadfc634fa7bbd53615a5e2e3019cd1bce24b3a4d667e50557268dd7"
|
|
142
142
|
}
|
|
143
143
|
]
|
|
144
144
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sourceTreeHash": "
|
|
3
|
+
"sourceTreeHash": "ebc3c4ac23c100ff03effd89fadfa645870e8724242aa8799e0e2802e4137c49",
|
|
4
4
|
"stepContractVersion": 3,
|
|
5
|
-
"docsBundleVersion": "2.0.
|
|
5
|
+
"docsBundleVersion": "2.0.286",
|
|
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": "3b05e2aff2f517b672fdcaaa0c0bb7e9e1e4e3fbfc684918ea43151cb21c44e8",
|
|
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": "f1831876670eae6a6029f8f48d12038de408d4fb31a3332f7cec5cb3c7e4ff1d",
|
|
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": "53e951dfdadfc634fa7bbd53615a5e2e3019cd1bce24b3a4d667e50557268dd7",
|
|
210
210
|
"mode": "100644"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
@@ -236,12 +236,12 @@
|
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
"path": "package-lock.json",
|
|
239
|
-
"sha256": "
|
|
239
|
+
"sha256": "5c960e77de9d286112a09eae806b2bf66539d2ff24754d18ab0f4b509b7f3591",
|
|
240
240
|
"mode": "100644"
|
|
241
241
|
},
|
|
242
242
|
{
|
|
243
243
|
"path": "package.json",
|
|
244
|
-
"sha256": "
|
|
244
|
+
"sha256": "b9a27ad44b8b27c874635e718cf88d05543ccedfc5719d1ae74c6a4453522d3d",
|
|
245
245
|
"mode": "100644"
|
|
246
246
|
},
|
|
247
247
|
{
|
|
@@ -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.32.0`, then `@funnelsgrove/analytics` `0.14.0`, then `@funnelsgrove/payments` `0.41.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.
|
|
20
|
+
Release `@funnelsgrove/sdk` `0.4.0` first, then `@funnelsgrove/runtime` `0.32.0`, then `@funnelsgrove/analytics` `0.14.0`, then `@funnelsgrove/payments` `0.41.0`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.293`, 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
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"name": "funnel-template",
|
|
9
9
|
"version": "0.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@funnelsgrove/analytics": "^0.
|
|
12
|
-
"@funnelsgrove/payments": "^0.
|
|
13
|
-
"@funnelsgrove/runtime": "^0.
|
|
11
|
+
"@funnelsgrove/analytics": "^0.14.0",
|
|
12
|
+
"@funnelsgrove/payments": "^0.41.0",
|
|
13
|
+
"@funnelsgrove/runtime": "^0.32.0",
|
|
14
14
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
15
15
|
"@stripe/stripe-js": "^8.7.0",
|
|
16
16
|
"lucide-react": "^0.553.0",
|
|
@@ -940,17 +940,17 @@
|
|
|
940
940
|
}
|
|
941
941
|
},
|
|
942
942
|
"node_modules/@funnelsgrove/analytics": {
|
|
943
|
-
"version": "0.
|
|
944
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.
|
|
945
|
-
"integrity": "sha512-
|
|
943
|
+
"version": "0.14.0",
|
|
944
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.14.0.tgz",
|
|
945
|
+
"integrity": "sha512-oG0WwMCgbTP9A7iU6187t3ejr4pJKvYOQIP3FYXmB26TH7tsl+pGz39By/3aALX/Vbl3V9QVs2nroa3vchgnfA==",
|
|
946
946
|
"peerDependencies": {
|
|
947
|
-
"@funnelsgrove/runtime": "^0.
|
|
947
|
+
"@funnelsgrove/runtime": "^0.32.0"
|
|
948
948
|
}
|
|
949
949
|
},
|
|
950
950
|
"node_modules/@funnelsgrove/payments": {
|
|
951
|
-
"version": "0.
|
|
952
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.
|
|
953
|
-
"integrity": "sha512
|
|
951
|
+
"version": "0.41.0",
|
|
952
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.41.0.tgz",
|
|
953
|
+
"integrity": "sha512-+pGg2f4FZxR2AWCvIPX+NXD1X2V6EV/+CVX2Fu1ailNZEmvuNtNMz20/biM1hX+ZgDb8Lt+nG0rCLc6KazqkwA==",
|
|
954
954
|
"dependencies": {
|
|
955
955
|
"@solidgate/react-sdk": "1.34.0",
|
|
956
956
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
@@ -960,14 +960,14 @@
|
|
|
960
960
|
"stripe": "^20.3.1"
|
|
961
961
|
},
|
|
962
962
|
"peerDependencies": {
|
|
963
|
-
"@funnelsgrove/analytics": "^0.
|
|
964
|
-
"@funnelsgrove/runtime": "^0.
|
|
963
|
+
"@funnelsgrove/analytics": "^0.14.0",
|
|
964
|
+
"@funnelsgrove/runtime": "^0.32.0"
|
|
965
965
|
}
|
|
966
966
|
},
|
|
967
967
|
"node_modules/@funnelsgrove/runtime": {
|
|
968
|
-
"version": "0.
|
|
969
|
-
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.
|
|
970
|
-
"integrity": "sha512-
|
|
968
|
+
"version": "0.32.0",
|
|
969
|
+
"resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.32.0.tgz",
|
|
970
|
+
"integrity": "sha512-pWneYsT/ET05o4jTb7uevajarmMwLjty0bfASpMvudeNAUtK1BVsE19K0IVY+FYci81a3Sm0wRN72mK8oRUhdw==",
|
|
971
971
|
"dependencies": {
|
|
972
972
|
"@funnelsgrove/sdk": "0.4.0",
|
|
973
973
|
"posthog-js": "^1.369.2",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"validate:funnel": "vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@funnelsgrove/analytics": "^0.
|
|
16
|
-
"@funnelsgrove/payments": "^0.
|
|
17
|
-
"@funnelsgrove/runtime": "^0.
|
|
15
|
+
"@funnelsgrove/analytics": "^0.14.0",
|
|
16
|
+
"@funnelsgrove/payments": "^0.41.0",
|
|
17
|
+
"@funnelsgrove/runtime": "^0.32.0",
|
|
18
18
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
19
19
|
"@stripe/stripe-js": "^8.7.0",
|
|
20
20
|
"lucide-react": "^0.553.0",
|