@funnelsgrove/cli 0.1.146 → 0.1.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,7 @@ for the same local diff.
68
68
 
69
69
  Use `fgrove env pull` from a synced folder to refresh only the ignored local `.env` file after project settings change, without replacing source files. The generated file identifies the current hosted draft through `NEXT_PUBLIC_FUNNEL_VERSION_ID`, so transactional steps such as email capture work during local QA.
70
70
 
71
- `fgrove offer-sets sync` reports the applied offer set and written generated files before waiting for GitHub. By default it waits for the exact GitHub sync job. If that job is still pending or running when the wait times out, the command continues with a warning on stderr and asks you to check the eventual result with `fgrove github status`. Terminal job failures, missing jobs, and API failures still exit non-zero. Pass `--no-github-sync` to skip the wait entirely when the generated file will be committed through the normal repository workflow.
71
+ Offer sets and payment mappings are runtime configuration. They are resolved remotely for preview and published funnels; `sync down` does not generate or download an offer-set source file.
72
72
 
73
73
  Create an experiment draft from a strict JSON spec:
74
74
 
package/dist/cli.d.ts CHANGED
@@ -149,9 +149,8 @@ type SyncTargetIdInput = {
149
149
  active?: Pick<ActiveContext, 'workspaceId' | 'funnelId'> | null;
150
150
  defaultWorkspaceId?: string;
151
151
  };
152
- type GeneratedConfigKind = 'offerSets' | 'experiments';
153
- export declare const SYNC_DOWN_GENERATED_CONFIG_KINDS: GeneratedConfigKind[];
154
- export declare const EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS: readonly ["sync down", "sync rebase", "sync up", "experiments sync", "offer-sets sync"];
152
+ type GeneratedConfigKind = 'experiments';
153
+ export declare const EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS: readonly ["sync down", "sync rebase", "sync up", "experiments sync"];
155
154
  export declare function resolveSyncTargetIds(input: SyncTargetIdInput): {
156
155
  workspaceId: string;
157
156
  funnelId?: string;
@@ -182,16 +181,6 @@ export declare function formatSourceCandidateSyncSuccess(input: {
182
181
  deletedFileCount: number;
183
182
  versionId: string;
184
183
  }): string;
185
- export declare function buildSyncOfferSetPlansInput(input: {
186
- workspaceId: string;
187
- funnelId: string;
188
- offerSet: string;
189
- }): {
190
- workspaceId: string;
191
- funnelId: string;
192
- offerSetId?: string;
193
- offerSetKey?: string;
194
- };
195
184
  export declare function buildGeneratedConfigSyncInput(input: {
196
185
  workspaceId: string;
197
186
  funnelId: string;
@@ -262,12 +251,10 @@ export declare const buildCloneFunnelMutationInput: (input: {
262
251
  };
263
252
  export type GitHubSyncTimeoutPolicy = 'strict' | 'continue';
264
253
  export declare const PUBLISH_GITHUB_SYNC_TIMEOUT_POLICY: GitHubSyncTimeoutPolicy;
265
- export declare const OFFER_SETS_GITHUB_SYNC_TIMEOUT_POLICY: GitHubSyncTimeoutPolicy;
266
254
  export declare const SYNC_UP_NO_LOCAL_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY: GitHubSyncTimeoutPolicy;
267
255
  export declare const SYNC_UP_COMMITTED_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY: GitHubSyncTimeoutPolicy;
268
256
  export declare const GITHUB_SYNC_TIMEOUT_POLICIES: {
269
257
  readonly publish: "continue";
270
- readonly offerSetsSync: "continue";
271
258
  readonly syncUpNoLocalChanges: "strict";
272
259
  readonly syncUpCommittedChanges: "strict";
273
260
  };
@@ -282,10 +269,6 @@ export declare const syncGitHubDraftForCli: (input: {
282
269
  export declare const shouldSyncGitHubDraft: (options: {
283
270
  githubSync?: boolean;
284
271
  }) => boolean;
285
- export declare const formatOfferSetSyncSuccess: (input: {
286
- offerSetLabel: string;
287
- generatedFileCount: number;
288
- }) => string[];
289
272
  type ExperimentLifecycleAction = 'start' | 'stop' | 'restart' | 'end';
290
273
  type ExperimentLifecycleResult = {
291
274
  id: string;
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { CURRENT_STEP_CONTRACT_VERSION, FUNNEL_CONTRACT_DIAGNOSTIC_CODES, PREVIO
11
11
  import { abortFunnelSourceCandidate, applyFunnelDraftPatch, beginFunnelSourceCandidate, callTrpcProcedure, createFunnelExperimentFromSpec, finalizeFunnelDirectAssetUpload, finalizeFunnelSourceCandidate, prepareFunnelDirectAssetUpload, stageFunnelSourceCandidate, } from './apiClient.js';
12
12
  import { cliReleaseIdentity } from './cliIdentity.js';
13
13
  import { clearActiveContext, getDefaultAuthConfigPath, loadActiveContext, loadAuthToken, saveActiveContext, saveAuthToken, } from './authStore.js';
14
- import { buildCommittedSyncManifest, buildDownloadedSyncManifest, buildSourceCandidateOperations, collectChangedSourceFiles, collectSourceFiles, collectSourceSnapshot, ensureGitignore, formatSyncUploadSummary, hasLocalSourceChanges, rebaseSourceFiles, readSyncManifest, replaceSourceFiles, runSyncDownLocalLifecycle, runSourceCandidateSync, writeLocalEnvFile, writeSourceFiles, writeSourceRebaseBackup, writeSyncManifest, } from './localSync.js';
14
+ import { buildCommittedSyncManifest, buildDownloadedSyncManifest, buildSourceCandidateOperations, collectChangedSourceFiles, collectSourceFiles, collectSourceSnapshot, ensureGitignore, filterSyncableSourceFiles, formatSyncUploadSummary, hasLocalSourceChanges, rebaseSourceFiles, readSyncManifest, replaceSourceFiles, runSyncDownLocalLifecycle, runSourceCandidateSync, writeLocalEnvFile, writeSourceFiles, writeSourceRebaseBackup, writeSyncManifest, } from './localSync.js';
15
15
  import { mergeTextSourceWithGit } from './sourceRebase.js';
16
16
  import { executeExperimentCreate, formatExperimentCreateSuccess, recoverExperimentCreateTransaction, } from './experimentCreate.js';
17
17
  import { pullEnvFile } from './envSync.js';
@@ -1069,16 +1069,13 @@ export async function executeCreateFunnel(input) {
1069
1069
  }
1070
1070
  }
1071
1071
  const GENERATED_CONFIG_PATH_BY_KIND = {
1072
- offerSets: 'src/config/offer-sets.generated.ts',
1073
1072
  experiments: 'src/config/experiments.generated.ts',
1074
1073
  };
1075
- export const SYNC_DOWN_GENERATED_CONFIG_KINDS = ['offerSets'];
1076
1074
  export const EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS = [
1077
1075
  'sync down',
1078
1076
  'sync rebase',
1079
1077
  'sync up',
1080
1078
  'experiments sync',
1081
- 'offer-sets sync',
1082
1079
  ];
1083
1080
  const experimentTransactionRecoveryCommands = new Set(EXPERIMENT_TRANSACTION_RECOVERY_COMMANDS);
1084
1081
  export function resolveSyncTargetIds(input) {
@@ -1133,17 +1130,6 @@ export function formatSourceCandidateSyncSuccess(input) {
1133
1130
  : '';
1134
1131
  return `Synced ${synced}${deleted} to draft version ${input.versionId}`;
1135
1132
  }
1136
- export function buildSyncOfferSetPlansInput(input) {
1137
- const offerSet = input.offerSet.trim();
1138
- if (!offerSet) {
1139
- throw new Error('--offer-set is required');
1140
- }
1141
- return {
1142
- workspaceId: input.workspaceId,
1143
- funnelId: input.funnelId,
1144
- ...(UUID_PATTERN.test(offerSet) ? { offerSetId: offerSet } : { offerSetKey: offerSet }),
1145
- };
1146
- }
1147
1133
  export function buildGeneratedConfigSyncInput(input) {
1148
1134
  return {
1149
1135
  workspaceId: input.workspaceId,
@@ -1573,12 +1559,10 @@ const summarizeGitHubSyncResult = (result) => {
1573
1559
  : `GitHub already synced${remoteSha}`;
1574
1560
  };
1575
1561
  export const PUBLISH_GITHUB_SYNC_TIMEOUT_POLICY = 'continue';
1576
- export const OFFER_SETS_GITHUB_SYNC_TIMEOUT_POLICY = 'continue';
1577
1562
  export const SYNC_UP_NO_LOCAL_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY = 'strict';
1578
1563
  export const SYNC_UP_COMMITTED_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY = 'strict';
1579
1564
  export const GITHUB_SYNC_TIMEOUT_POLICIES = {
1580
1565
  publish: PUBLISH_GITHUB_SYNC_TIMEOUT_POLICY,
1581
- offerSetsSync: OFFER_SETS_GITHUB_SYNC_TIMEOUT_POLICY,
1582
1566
  syncUpNoLocalChanges: SYNC_UP_NO_LOCAL_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY,
1583
1567
  syncUpCommittedChanges: SYNC_UP_COMMITTED_CHANGES_GITHUB_SYNC_TIMEOUT_POLICY,
1584
1568
  };
@@ -1606,10 +1590,6 @@ export const syncGitHubDraftForCli = async (input) => {
1606
1590
  };
1607
1591
  const formatGeneratedFileCount = (count) => `${count} generated config ${count === 1 ? 'file' : 'files'}`;
1608
1592
  export const shouldSyncGitHubDraft = (options) => options.githubSync !== false;
1609
- export const formatOfferSetSyncSuccess = (input) => [
1610
- `Synced offer set ${input.offerSetLabel} to funnel draft`,
1611
- `Wrote ${formatGeneratedFileCount(input.generatedFileCount)}`,
1612
- ];
1613
1593
  export const formatExperimentLifecycleSuccess = (action, result, json) => {
1614
1594
  const payload = {
1615
1595
  experimentId: result.id,
@@ -2100,58 +2080,6 @@ addExamples(funnelsCommand
2100
2080
  });
2101
2081
  console.log(`${result.funnel.id}\t${result.funnel.name}\t${result.funnel.slug}`);
2102
2082
  });
2103
- const offerSetsCommand = addExamples(program.command('offer-sets').alias('offers').description('Manage project offer sets'), [
2104
- 'fgrove offer-sets sync --offer-set default-paywall',
2105
- 'fgrove offers sync --funnel claimbee-ios --offer-set three-month-test',
2106
- ]);
2107
- addExamples(offerSetsCommand
2108
- .command('sync')
2109
- .description('Apply an offer set to a funnel draft billing snapshot')
2110
- .option('--workspace <id-or-slug-or-name>', 'Workspace id, slug, or name')
2111
- .option('--funnel <id-or-slug>', 'Funnel id or slug')
2112
- .option('--dir <path>', 'Local source directory for reading sync manifest', '.')
2113
- .option('--no-github-sync', 'Skip waiting for an additional GitHub draft push')
2114
- .requiredOption('--offer-set <key-or-id>', 'Offer set key or id'), [
2115
- 'fgrove offer-sets sync --offer-set default-paywall',
2116
- 'fgrove offers sync --funnel claimbee-ios --offer-set quarterly-test',
2117
- ])
2118
- .action(async (options) => {
2119
- const token = await readAuthToken();
2120
- const target = await resolveSyncTarget({
2121
- token,
2122
- workspace: options.workspace,
2123
- funnel: options.funnel,
2124
- dir: options.dir,
2125
- });
2126
- const result = await callApi({
2127
- path: 'funnels.syncOfferSetPlans',
2128
- type: 'mutation',
2129
- token,
2130
- data: buildSyncOfferSetPlansInput({
2131
- workspaceId: target.workspaceId,
2132
- funnelId: target.funnelId,
2133
- offerSet: options.offerSet,
2134
- }),
2135
- });
2136
- const generatedFiles = await syncGeneratedConfigFilesForCli({
2137
- token,
2138
- target,
2139
- kinds: ['offerSets'],
2140
- });
2141
- const offerSetLabel = result.offerSet?.key || result.offerSet?.display_name || options.offerSet.trim();
2142
- console.log(formatOfferSetSyncSuccess({
2143
- offerSetLabel,
2144
- generatedFileCount: generatedFiles.length,
2145
- }).join('\n'));
2146
- if (shouldSyncGitHubDraft(options)) {
2147
- await syncGitHubDraftForCli({
2148
- token,
2149
- workspaceId: target.workspaceId,
2150
- funnelId: target.funnelId,
2151
- timeoutPolicy: OFFER_SETS_GITHUB_SYNC_TIMEOUT_POLICY,
2152
- });
2153
- }
2154
- });
2155
2083
  const experimentsCommand = addExamples(program.command('experiments').description('Manage funnel experiments'), [
2156
2084
  'fgrove experiments create --spec experiment.json --dir .',
2157
2085
  'fgrove experiments sync',
@@ -2541,11 +2469,12 @@ addExamples(syncCommand
2541
2469
  funnelId: target.funnelId,
2542
2470
  },
2543
2471
  });
2472
+ const sourceFiles = filterSyncableSourceFiles(result.files);
2544
2473
  const downloadedSyncManifest = buildDownloadedSyncManifest({
2545
2474
  workspaceId: target.workspaceId,
2546
2475
  funnelId: target.funnelId,
2547
2476
  draftVersionId: result.draftVersionId,
2548
- files: result.files,
2477
+ files: sourceFiles,
2549
2478
  });
2550
2479
  if (options.force && target.manifest && hasLocalChanges) {
2551
2480
  const localFiles = await collectSourceFiles(target.sourceDir);
@@ -2558,12 +2487,7 @@ addExamples(syncCommand
2558
2487
  console.log(`Saved the pre-force source backup to ${path.join(target.sourceDir, backupPath)}`);
2559
2488
  }
2560
2489
  await mkdir(target.sourceDir, { recursive: true });
2561
- await writeSourceFiles(target.sourceDir, result.files);
2562
- const generatedFiles = await syncGeneratedConfigFilesForCli({
2563
- token,
2564
- target,
2565
- kinds: SYNC_DOWN_GENERATED_CONFIG_KINDS,
2566
- });
2490
+ await writeSourceFiles(target.sourceDir, sourceFiles);
2567
2491
  await writeLocalEnvFile(target.sourceDir, result.envFile);
2568
2492
  await ensureGitignore(target.sourceDir);
2569
2493
  await runSyncDownLocalLifecycle({
@@ -2582,8 +2506,7 @@ addExamples(syncCommand
2582
2506
  },
2583
2507
  writeSyncState: async () => writeSyncManifest(target.sourceDir, downloadedSyncManifest),
2584
2508
  });
2585
- console.log(`Synced ${result.files.length} files to ${target.sourceDir}`);
2586
- console.log(`Wrote ${formatGeneratedFileCount(generatedFiles.length)}`);
2509
+ console.log(`Synced ${sourceFiles.length} files to ${target.sourceDir}`);
2587
2510
  });
2588
2511
  addExamples(syncCommand
2589
2512
  .command('rebase')
@@ -2631,12 +2554,14 @@ addExamples(syncCommand
2631
2554
  if (base.draftVersionId !== target.manifest.draftVersionId) {
2632
2555
  throw Object.assign(new Error('The hosted historical export returned the wrong sync baseline. The rebase was not applied.'), { code: 'FG-SYNC-REBASE-BASE' });
2633
2556
  }
2634
- assertHistoricalSyncBaseline({ manifest: target.manifest, files: base.files });
2557
+ const remoteFiles = filterSyncableSourceFiles(remote.files);
2558
+ const baseFiles = filterSyncableSourceFiles(base.files);
2559
+ assertHistoricalSyncBaseline({ manifest: target.manifest, files: baseFiles });
2635
2560
  const localFiles = await collectSourceFiles(target.sourceDir);
2636
2561
  const merged = await rebaseSourceFiles({
2637
- baseFiles: base.files,
2562
+ baseFiles,
2638
2563
  localFiles,
2639
- remoteFiles: remote.files,
2564
+ remoteFiles,
2640
2565
  remoteOwnedPaths: await loadManagedSyncPaths(),
2641
2566
  mergeText: mergeTextSourceWithGit,
2642
2567
  });
@@ -2659,18 +2584,13 @@ addExamples(syncCommand
2659
2584
  workspaceId: target.workspaceId,
2660
2585
  funnelId: target.funnelId,
2661
2586
  draftVersionId: remote.draftVersionId,
2662
- files: remote.files,
2587
+ files: remoteFiles,
2663
2588
  });
2664
2589
  await replaceSourceFiles({
2665
2590
  rootDir: target.sourceDir,
2666
2591
  currentFiles: localFiles,
2667
2592
  nextFiles: merged.files,
2668
2593
  });
2669
- const generatedFiles = await syncGeneratedConfigFilesForCli({
2670
- token,
2671
- target,
2672
- kinds: SYNC_DOWN_GENERATED_CONFIG_KINDS,
2673
- });
2674
2594
  await writeLocalEnvFile(target.sourceDir, remote.envFile);
2675
2595
  await ensureGitignore(target.sourceDir);
2676
2596
  await runSyncDownLocalLifecycle({
@@ -2691,7 +2611,6 @@ addExamples(syncCommand
2691
2611
  });
2692
2612
  const localChanges = await collectChangedSourceFiles(target.sourceDir, downloadedSyncManifest);
2693
2613
  console.log(`Rebased ${localChanges.files.length + localChanges.deletedPaths.length} local changes onto hosted draft ${remote.draftVersionId}.`);
2694
- console.log(`Wrote ${formatGeneratedFileCount(generatedFiles.length)}`);
2695
2614
  console.log('Run `fgrove validate`, review the diff, then run `fgrove sync up`.');
2696
2615
  });
2697
2616
  const envCommand = addExamples(program.command('env').description('Manage local funnel environment files'), [
@@ -1,4 +1,5 @@
1
1
  export declare const SYNC_MANIFEST_FILE = ".funnelsgrove-sync.json";
2
+ export declare const LEGACY_OFFER_SETS_GENERATED_PATH = "src/config/offer-sets.generated.ts";
2
3
  export type SyncManifestFile = {
3
4
  path: string;
4
5
  hash: string;
@@ -109,6 +110,7 @@ export declare const writeSafeRootFile: (rootDir: string, relativePath: string,
109
110
  export declare function normalizeSyncPath(filePath: string): string;
110
111
  export declare function assertSafeSyncPath(filePath: string): string;
111
112
  export declare function shouldSyncFile(filePath: string): boolean;
113
+ export declare function filterSyncableSourceFiles(files: readonly SourceFile[]): SourceFile[];
112
114
  export declare function buildSyncManifest(rootDir: string, input: SyncManifestInput): Promise<SyncManifest>;
113
115
  export declare function buildCommittedSyncManifest(input: {
114
116
  workspaceId: string;
package/dist/localSync.js CHANGED
@@ -3,6 +3,7 @@ import { constants } from 'node:fs';
3
3
  import { lstat, mkdir, open, readdir, realpath, rename, rm, stat } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  export const SYNC_MANIFEST_FILE = '.funnelsgrove-sync.json';
6
+ export const LEGACY_OFFER_SETS_GENERATED_PATH = 'src/config/offer-sets.generated.ts';
6
7
  export const DEFAULT_PATCH_BATCH_CONTENT_CHARS = 12_000_000;
7
8
  export const DEFAULT_SOURCE_CANDIDATE_BATCH_CONTENT_BYTES = 12_000_000;
8
9
  export const MAX_SOURCE_CANDIDATE_OPERATIONS = 200 * 500;
@@ -264,11 +265,17 @@ export function shouldSyncFile(filePath) {
264
265
  if (fileName === SYNC_MANIFEST_FILE) {
265
266
  return false;
266
267
  }
268
+ if (normalized === LEGACY_OFFER_SETS_GENERATED_PATH) {
269
+ return false;
270
+ }
267
271
  if (fileName === '.env' || (fileName.startsWith('.env.') && fileName !== '.env.example')) {
268
272
  return false;
269
273
  }
270
274
  return true;
271
275
  }
276
+ export function filterSyncableSourceFiles(files) {
277
+ return files.filter((file) => shouldSyncFile(file.path));
278
+ }
272
279
  export async function buildSyncManifest(rootDir, input) {
273
280
  const resolvedRoot = await resolveSafeRoot(rootDir, false);
274
281
  const files = await collectSyncFiles(resolvedRoot, resolvedRoot);
@@ -4,10 +4,10 @@
4
4
  "minimumCliVersion": "0.1.20",
5
5
  "entries": [
6
6
  {
7
- "repositoryCliVersion": "0.1.146",
7
+ "repositoryCliVersion": "0.1.148",
8
8
  "manifest": {
9
9
  "schemaVersion": 1,
10
- "bundleVersion": "2.0.136",
10
+ "bundleVersion": "2.0.138",
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": "f0960bc044d9c4e5a550e5bc968fefd0840c03829ecb94348736798fc8fa9d1b"
48
+ "sha256": "ccbc9d8fdcd40ff40b3aed6171f0496d856f8dfc6bd9c6cb96ee5bfeddaaaa01"
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": "57bb326172c83897a6656615a8ddc1b19bc9eeee808cd07f33ec2fb708102942"
148
+ "sha256": "09e75871d05290fe9e44817ab35504638d180217242522893ad68cd516deae0f"
149
149
  }
150
150
  ]
151
151
  }
152
152
  },
153
153
  {
154
- "repositoryCliVersion": "0.1.145",
154
+ "repositoryCliVersion": "0.1.147",
155
155
  "manifest": {
156
156
  "schemaVersion": 1,
157
- "bundleVersion": "2.0.135",
157
+ "bundleVersion": "2.0.137",
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": "0b3708beaa8d63e1c1fe5ab38e39fdf7a3e12fec39166261a1bad7619d872179"
195
+ "sha256": "d540fec73ced6fdcc2d118aac9a2f1f1131dfbd7d4fa6df80d988733725ffcbb"
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": "7fa793bde89dc1106b24eb408f225d156865bffe37f4feb7aeadb553844643c8"
295
+ "sha256": "66145b48217b80e9e5eb0b21d2d4d8de7186ed980a54b01b56b2a23202822503"
296
296
  }
297
297
  ]
298
298
  }
299
299
  },
300
300
  {
301
- "repositoryCliVersion": "0.1.144",
301
+ "repositoryCliVersion": "0.1.146",
302
302
  "manifest": {
303
303
  "schemaVersion": 1,
304
- "bundleVersion": "2.0.134",
304
+ "bundleVersion": "2.0.136",
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": "fec5ced4b8e8e6c8081f5446b3b4174caefa89ac6d441f03e576fb10e961bc98"
342
+ "sha256": "f0960bc044d9c4e5a550e5bc968fefd0840c03829ecb94348736798fc8fa9d1b"
343
343
  },
344
344
  {
345
345
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -355,7 +355,7 @@
355
355
  },
356
356
  {
357
357
  "path": "docs/funnelsgrove/qa/publish.md",
358
- "sha256": "82b05b8c6eebf25d32d2cff6f05b08bc39661dcbb46caa6b912de6b803aed39c"
358
+ "sha256": "67391a8bf5e1ddeab2e7430f48a589c28ce5fb986f06fee74f78f09649086603"
359
359
  },
360
360
  {
361
361
  "path": "docs/funnelsgrove/recipes/add-experiment.md",
@@ -439,16 +439,16 @@
439
439
  },
440
440
  {
441
441
  "path": "funnel-docs.config.json",
442
- "sha256": "efdede3e20f75d3b6f5dc00daa3e8ee1c3c03d56978fa087161b643424add0cc"
442
+ "sha256": "57bb326172c83897a6656615a8ddc1b19bc9eeee808cd07f33ec2fb708102942"
443
443
  }
444
444
  ]
445
445
  }
446
446
  },
447
447
  {
448
- "repositoryCliVersion": "0.1.143",
448
+ "repositoryCliVersion": "0.1.145",
449
449
  "manifest": {
450
450
  "schemaVersion": 1,
451
- "bundleVersion": "2.0.133",
451
+ "bundleVersion": "2.0.135",
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": "bb6f567146d9c70b209eff612ffcfb5e7c20b7ccb357beec47a187ead887a829"
489
+ "sha256": "0b3708beaa8d63e1c1fe5ab38e39fdf7a3e12fec39166261a1bad7619d872179"
490
490
  },
491
491
  {
492
492
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -502,7 +502,7 @@
502
502
  },
503
503
  {
504
504
  "path": "docs/funnelsgrove/qa/publish.md",
505
- "sha256": "82b05b8c6eebf25d32d2cff6f05b08bc39661dcbb46caa6b912de6b803aed39c"
505
+ "sha256": "67391a8bf5e1ddeab2e7430f48a589c28ce5fb986f06fee74f78f09649086603"
506
506
  },
507
507
  {
508
508
  "path": "docs/funnelsgrove/recipes/add-experiment.md",
@@ -586,22 +586,22 @@
586
586
  },
587
587
  {
588
588
  "path": "funnel-docs.config.json",
589
- "sha256": "7a7f556fc144e82469dbc56da7d4ce588c3bcc627ebf4b9aec9961f93d672f1f"
589
+ "sha256": "7fa793bde89dc1106b24eb408f225d156865bffe37f4feb7aeadb553844643c8"
590
590
  }
591
591
  ]
592
592
  }
593
593
  },
594
594
  {
595
- "repositoryCliVersion": "0.1.142",
595
+ "repositoryCliVersion": "0.1.144",
596
596
  "manifest": {
597
597
  "schemaVersion": 1,
598
- "bundleVersion": "2.0.132",
598
+ "bundleVersion": "2.0.134",
599
599
  "stepContractVersion": 3,
600
600
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
601
601
  "managedFiles": [
602
602
  {
603
603
  "path": "AGENTS.md",
604
- "sha256": "952fbafb5841b8f2e6fadab5032374c98390ef90c1ca0ed27747a38d0cc009d7"
604
+ "sha256": "22118f0f909a747f8a27f4eed16ceb5ec2a9283fd9645114fda961c0405a2a6b"
605
605
  },
606
606
  {
607
607
  "path": "CLAUDE.md",
@@ -633,7 +633,7 @@
633
633
  },
634
634
  {
635
635
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
636
- "sha256": "33d225bcc4f6f7aaa473f3bebe81799b3ec4751c374a4c2d9159afc56415ba74"
636
+ "sha256": "fec5ced4b8e8e6c8081f5446b3b4174caefa89ac6d441f03e576fb10e961bc98"
637
637
  },
638
638
  {
639
639
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -733,22 +733,22 @@
733
733
  },
734
734
  {
735
735
  "path": "funnel-docs.config.json",
736
- "sha256": "ea5b05c7f28303245e358598f125a3a31c15175c2ef15ae8873b25be2ded133e"
736
+ "sha256": "efdede3e20f75d3b6f5dc00daa3e8ee1c3c03d56978fa087161b643424add0cc"
737
737
  }
738
738
  ]
739
739
  }
740
740
  },
741
741
  {
742
- "repositoryCliVersion": "0.1.141",
742
+ "repositoryCliVersion": "0.1.143",
743
743
  "manifest": {
744
744
  "schemaVersion": 1,
745
- "bundleVersion": "2.0.131",
745
+ "bundleVersion": "2.0.133",
746
746
  "stepContractVersion": 3,
747
747
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
748
748
  "managedFiles": [
749
749
  {
750
750
  "path": "AGENTS.md",
751
- "sha256": "952fbafb5841b8f2e6fadab5032374c98390ef90c1ca0ed27747a38d0cc009d7"
751
+ "sha256": "22118f0f909a747f8a27f4eed16ceb5ec2a9283fd9645114fda961c0405a2a6b"
752
752
  },
753
753
  {
754
754
  "path": "CLAUDE.md",
@@ -780,7 +780,7 @@
780
780
  },
781
781
  {
782
782
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
783
- "sha256": "015d013594cabb94c288bb39d7c717f531b68a1fe4754e6baefc0e08bbc5b872"
783
+ "sha256": "bb6f567146d9c70b209eff612ffcfb5e7c20b7ccb357beec47a187ead887a829"
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": "7af952690a2b3c592ce6688b8524aa4c5fe111a0c798dc25106eb0d594c2452b"
883
+ "sha256": "7a7f556fc144e82469dbc56da7d4ce588c3bcc627ebf4b9aec9961f93d672f1f"
884
884
  }
885
885
  ]
886
886
  }
887
887
  },
888
888
  {
889
- "repositoryCliVersion": "0.1.140",
889
+ "repositoryCliVersion": "0.1.142",
890
890
  "manifest": {
891
891
  "schemaVersion": 1,
892
- "bundleVersion": "2.0.130",
892
+ "bundleVersion": "2.0.132",
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": "9988e6f7b4ecf3f4a73ff83cf3f3613f82d765c59d09e55e86b0560d1e3eafa5"
930
+ "sha256": "33d225bcc4f6f7aaa473f3bebe81799b3ec4751c374a4c2d9159afc56415ba74"
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": "4e81bd56071fa38708c7f18488420038339c8cc984062ffd7e8a4930ab6b06f0"
1030
+ "sha256": "ea5b05c7f28303245e358598f125a3a31c15175c2ef15ae8873b25be2ded133e"
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.146",
3
+ "version": "0.1.148",
4
4
  "description": "FunnelsGrove command-line tools for editing, syncing, and publishing funnels",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.136",
3
+ "bundleVersion": "2.0.138",
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": "f0960bc044d9c4e5a550e5bc968fefd0840c03829ecb94348736798fc8fa9d1b"
41
+ "sha256": "ccbc9d8fdcd40ff40b3aed6171f0496d856f8dfc6bd9c6cb96ee5bfeddaaaa01"
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": "57bb326172c83897a6656615a8ddc1b19bc9eeee808cd07f33ec2fb708102942"
141
+ "sha256": "09e75871d05290fe9e44817ab35504638d180217242522893ad68cd516deae0f"
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/runtime` `0.7.28` first, then `@funnelsgrove/analytics` `0.1.77`, then `@funnelsgrove/payments` `0.7.19`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.146`, 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.
20
+ Release `@funnelsgrove/runtime` `0.7.28` first, then `@funnelsgrove/analytics` `0.1.77`, then `@funnelsgrove/payments` `0.7.19`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.148`, 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.136",
3
+ "bundleVersion": "2.0.138",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.136",
3
+ "bundleVersion": "2.0.138",
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": "f0960bc044d9c4e5a550e5bc968fefd0840c03829ecb94348736798fc8fa9d1b"
41
+ "sha256": "ccbc9d8fdcd40ff40b3aed6171f0496d856f8dfc6bd9c6cb96ee5bfeddaaaa01"
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": "57bb326172c83897a6656615a8ddc1b19bc9eeee808cd07f33ec2fb708102942"
141
+ "sha256": "09e75871d05290fe9e44817ab35504638d180217242522893ad68cd516deae0f"
142
142
  }
143
143
  ]
144
144
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sourceTreeHash": "5f7015815cec65266d11c6c96aae425d72b251107d1ae4541a6405bcd696bf6b",
3
+ "sourceTreeHash": "05c15c49bd14c9a2d805262acf88621c42e5f6b83e79418248b1efcbe7ac900e",
4
4
  "stepContractVersion": 3,
5
- "docsBundleVersion": "2.0.136",
5
+ "docsBundleVersion": "2.0.138",
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": "3e7fe2377e1251fbfbf00bd8e9fe37b2148e8b43f1b5fa72f3109ae3829cdc9f",
19
+ "sha256": "9e404c0cb39e2e401121d9f381aafde42479ed11ce766ee91e3d4937438d55ad",
20
20
  "mode": "100644"
21
21
  },
22
22
  {
@@ -101,7 +101,7 @@
101
101
  },
102
102
  {
103
103
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
104
- "sha256": "f0960bc044d9c4e5a550e5bc968fefd0840c03829ecb94348736798fc8fa9d1b",
104
+ "sha256": "ccbc9d8fdcd40ff40b3aed6171f0496d856f8dfc6bd9c6cb96ee5bfeddaaaa01",
105
105
  "mode": "100644"
106
106
  },
107
107
  {
@@ -236,7 +236,7 @@
236
236
  },
237
237
  {
238
238
  "path": "funnel-docs.config.json",
239
- "sha256": "57bb326172c83897a6656615a8ddc1b19bc9eeee808cd07f33ec2fb708102942",
239
+ "sha256": "09e75871d05290fe9e44817ab35504638d180217242522893ad68cd516deae0f",
240
240
  "mode": "100644"
241
241
  },
242
242
  {
@@ -551,7 +551,7 @@
551
551
  },
552
552
  {
553
553
  "path": "src/steps/step-32-paywall.tsx",
554
- "sha256": "513af16a9ad06858461976c83ec10910f8ede0466cfc847e6b8982e56fca0caa",
554
+ "sha256": "0d1439a122117ad07b460be3d17490bcd3323d88ea3513b1c182846e4d468ce2",
555
555
  "mode": "100644"
556
556
  },
557
557
  {
@@ -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/runtime` `0.7.28` first, then `@funnelsgrove/analytics` `0.1.77`, then `@funnelsgrove/payments` `0.7.19`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.146`, 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.
20
+ Release `@funnelsgrove/runtime` `0.7.28` first, then `@funnelsgrove/analytics` `0.1.77`, then `@funnelsgrove/payments` `0.7.19`. The production deploy verifies the zero-traffic API candidate, publishes and verifies `@funnelsgrove/cli` `0.1.148`, 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.136",
3
+ "bundleVersion": "2.0.138",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -362,6 +362,9 @@ function StepPaywallRuntime({
362
362
  runtimeConfig: checkoutRuntimeConfig,
363
363
  userId: user.id,
364
364
  });
365
+ const checkoutCardClientSecret = checkoutSession.activeClientSecret;
366
+ const checkoutCardLoading = checkoutSession.loading.card;
367
+ const prepareCardCheckout = checkoutSession.prepareCardCheckout;
365
368
  const walletCheckoutInput = useMemo<StripeSubscriptionWalletSurfaceCheckoutInput>(
366
369
  () => ({
367
370
  analyticsMetadata: checkoutAnalyticsMetadata,
@@ -454,6 +457,27 @@ function StepPaywallRuntime({
454
457
  setError(null);
455
458
  }, [selectedPlanId]);
456
459
 
460
+ useEffect(() => {
461
+ if (
462
+ !discountStateReady ||
463
+ checkoutProvider !== 'stripe' ||
464
+ checkoutDisabled ||
465
+ checkoutCardClientSecret ||
466
+ checkoutCardLoading
467
+ ) {
468
+ return;
469
+ }
470
+
471
+ void prepareCardCheckout({ checkoutStartSource: 'checkout_render' });
472
+ }, [
473
+ checkoutDisabled,
474
+ checkoutProvider,
475
+ checkoutCardClientSecret,
476
+ checkoutCardLoading,
477
+ discountStateReady,
478
+ prepareCardCheckout,
479
+ ]);
480
+
457
481
  useEffect(() => {
458
482
  if (!discountStateReady) {
459
483
  return;
@@ -520,7 +544,7 @@ function StepPaywallRuntime({
520
544
  paywallWalletMethodWrapperClassNames[method];
521
545
 
522
546
  const startCheckout = async () => {
523
- if (checkoutDisabled || (checkoutProvider === 'stripe' && checkoutSession.loading.card)) {
547
+ if (checkoutDisabled) {
524
548
  return;
525
549
  }
526
550
 
@@ -561,7 +585,7 @@ function StepPaywallRuntime({
561
585
  };
562
586
 
563
587
  const openCardCheckoutFromUnavailableWallet = async () => {
564
- if (checkoutDisabled || checkoutSession.loading.card) {
588
+ if (checkoutDisabled) {
565
589
  return;
566
590
  }
567
591
 
@@ -645,24 +669,6 @@ function StepPaywallRuntime({
645
669
  );
646
670
  }
647
671
 
648
- if (checkoutProvider === 'stripe' && checkoutSession.loading.card) {
649
- return (
650
- <>
651
- <div
652
- className='paywall-v2-loading-screen'
653
- aria-label='Loading checkout'
654
- aria-live='polite'
655
- >
656
- <div className='paywall-v2-loading-content'>
657
- <div className='paywall-v2-loading-spinner' aria-hidden='true' />
658
- </div>
659
- </div>
660
-
661
- <style>{stepPaywallStyles}</style>
662
- </>
663
- );
664
- }
665
-
666
672
  return (
667
673
  <>
668
674
  {checkoutProvider === 'solidgate' &&
@@ -987,10 +993,7 @@ function StepPaywallRuntime({
987
993
  type='button'
988
994
  className='paywall-v2-card-button paywall-v2-vibe-cta'
989
995
  onClick={() => void startCheckout()}
990
- disabled={
991
- checkoutDisabled ||
992
- (checkoutProvider === 'stripe' && checkoutSession.loading.card)
993
- }
996
+ disabled={checkoutDisabled}
994
997
  >
995
998
  {content.plans.cardButtonLabel}
996
999
  </button>