@appfunnel-dev/sdk 2.0.0-canary.5 → 2.0.0-canary.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{checkout-Dz8cGkB_.d.cts → checkout-CMEdxpgo.d.cts} +13 -13
- package/dist/{checkout-7Dy6IedP.d.ts → checkout-D1NUPv4p.d.ts} +13 -13
- package/dist/{chunk-AXXG3ODP.js → chunk-7YQLPPPG.js} +58 -90
- package/dist/chunk-7YQLPPPG.js.map +1 -0
- package/dist/{chunk-G44BUG6G.cjs → chunk-BPOAWI4G.cjs} +60 -93
- package/dist/chunk-BPOAWI4G.cjs.map +1 -0
- package/dist/driver-paddle.d.cts +1 -1
- package/dist/driver-paddle.d.ts +1 -1
- package/dist/driver-stripe.cjs +241 -189
- package/dist/driver-stripe.cjs.map +1 -1
- package/dist/driver-stripe.d.cts +18 -2
- package/dist/driver-stripe.d.ts +18 -2
- package/dist/driver-stripe.js +242 -191
- package/dist/driver-stripe.js.map +1 -1
- package/dist/index.cjs +298 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -24
- package/dist/index.d.ts +24 -24
- package/dist/index.js +260 -80
- package/dist/index.js.map +1 -1
- package/dist/{manifest-Dl7faaKe.d.cts → manifest-CMe8yVkr.d.cts} +97 -88
- package/dist/{manifest-Dl7faaKe.d.ts → manifest-CMe8yVkr.d.ts} +97 -88
- package/dist/manifest-entry.cjs +38 -38
- package/dist/manifest-entry.cjs.map +1 -1
- package/dist/manifest-entry.d.cts +15 -15
- package/dist/manifest-entry.d.ts +15 -15
- package/dist/manifest-entry.js +16 -12
- package/dist/manifest-entry.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AXXG3ODP.js.map +0 -1
- package/dist/chunk-G44BUG6G.cjs.map +0 -1
|
@@ -140,16 +140,20 @@ interface SessionContext {
|
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
142
|
* Durable identity for **stable experiment bucketing** (phase-4 §4.2). The
|
|
143
|
-
* preferred seed is
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
143
|
+
* preferred seed is the signed anonymous `visitorId` (stable from the first
|
|
144
|
+
* pageload, matching the server-side FUNNEL split in resolve.ts); `customerId` is
|
|
145
|
+
* only the fallback for cookieless SDK embeds. visitorId-FIRST is deliberate — the
|
|
146
|
+
* anon customerId is minted server-side AFTER the first tracked event, so seeding
|
|
147
|
+
* on it would flip a returning visitor's arm between visit 1 and 2 (see
|
|
148
|
+
* {@link ../flow/experiments}.bucketingSeed). It is deliberately **not** the
|
|
149
|
+
* session id or fingerprint — a returning visitor (e.g. via winback) must keep
|
|
150
|
+
* their variant across sessions (landmine #4). Both null = unidentifiable traffic,
|
|
151
|
+
* and the experiment layer falls back to control rather than bucket on nothing.
|
|
148
152
|
*/
|
|
149
153
|
interface IdentityContext {
|
|
150
|
-
/** Known customer id (post-identification). The
|
|
154
|
+
/** Known customer id (post-identification). The fallback bucketing seed (cookieless embeds). */
|
|
151
155
|
customerId: string | null;
|
|
152
|
-
/** Signed anonymous visitor id. The
|
|
156
|
+
/** Signed anonymous visitor id. The preferred bucketing seed (stable from visit 1). */
|
|
153
157
|
visitorId: string | null;
|
|
154
158
|
}
|
|
155
159
|
interface SystemContext {
|
|
@@ -203,14 +207,14 @@ declare function buildContext(opts?: BuildContextOptions): FunnelContext;
|
|
|
203
207
|
/**
|
|
204
208
|
* Pure catalog math — no React, no I/O. Split out of {@link ./catalog} so the
|
|
205
209
|
* server/tooling entry (`@appfunnel-dev/sdk/manifest`) can compile/resolve
|
|
206
|
-
*
|
|
210
|
+
* offerings without pulling react-dom or any component code into its graph.
|
|
207
211
|
* {@link ./catalog} re-exports everything here and adds the React wiring.
|
|
208
212
|
*/
|
|
209
213
|
type Interval = 'day' | 'week' | 'month' | 'year' | 'one_time';
|
|
210
|
-
/** What the platform hands the funnel per
|
|
211
|
-
interface
|
|
214
|
+
/** What the platform hands the funnel per offering (a single provider-resolved price). */
|
|
215
|
+
interface OfferingInput {
|
|
212
216
|
id: string;
|
|
213
|
-
/** The catalog
|
|
217
|
+
/** The catalog charge KEY this input charges (the slot's assignment). Defaults to `id` (legacy/identity slots). */
|
|
214
218
|
catalogKey?: string;
|
|
215
219
|
name?: string;
|
|
216
220
|
displayName?: string;
|
|
@@ -237,12 +241,12 @@ interface Money {
|
|
|
237
241
|
formatted: string;
|
|
238
242
|
}
|
|
239
243
|
/**
|
|
240
|
-
* The
|
|
241
|
-
* locale formatting — what the catalog stores. `
|
|
244
|
+
* The offering with its amounts resolved (currency + period math), **before**
|
|
245
|
+
* locale formatting — what the catalog stores. `useOffering` formats it.
|
|
242
246
|
*/
|
|
243
|
-
interface
|
|
247
|
+
interface ResolvedOffering {
|
|
244
248
|
id: string;
|
|
245
|
-
/** The catalog
|
|
249
|
+
/** The catalog charge KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
|
|
246
250
|
catalogKey: string;
|
|
247
251
|
name: string;
|
|
248
252
|
displayName: string;
|
|
@@ -259,10 +263,10 @@ interface ResolvedProduct {
|
|
|
259
263
|
trialDays: number;
|
|
260
264
|
trialMinor: number;
|
|
261
265
|
}
|
|
262
|
-
/** Display-ready
|
|
263
|
-
interface
|
|
266
|
+
/** Display-ready offering the author reads (every amount locale-formatted). */
|
|
267
|
+
interface Offering {
|
|
264
268
|
id: string;
|
|
265
|
-
/** The catalog
|
|
269
|
+
/** The catalog charge KEY charged (the CHARGE identity; `id` is the SLOT/display identity). */
|
|
266
270
|
catalogKey: string;
|
|
267
271
|
name: string;
|
|
268
272
|
displayName: string;
|
|
@@ -282,13 +286,13 @@ interface Product {
|
|
|
282
286
|
trialPrice: Money | null;
|
|
283
287
|
}
|
|
284
288
|
/**
|
|
285
|
-
* Resolve
|
|
289
|
+
* Resolve an {@link OfferingInput} into amounts + period math (no formatting yet).
|
|
286
290
|
* Per-period amounts are **integer minor units** (each derived from the exact
|
|
287
291
|
* daily rate, then rounded — never round-then-multiply, so error doesn't compound).
|
|
288
292
|
*/
|
|
289
|
-
declare function
|
|
290
|
-
/** Build the id → {@link
|
|
291
|
-
declare function buildCatalog(inputs:
|
|
293
|
+
declare function resolveOffering(input: OfferingInput): ResolvedOffering;
|
|
294
|
+
/** Build the id → {@link ResolvedOffering} catalog. Formatting happens at read time. */
|
|
295
|
+
declare function buildCatalog(inputs: OfferingInput[]): Map<string, ResolvedOffering>;
|
|
292
296
|
/**
|
|
293
297
|
* The minor-unit exponent for a currency (`USD`→2, `JPY`→0, `KWD`/`BHD`→3),
|
|
294
298
|
* derived from `Intl.NumberFormat(...).resolvedOptions().maximumFractionDigits`
|
|
@@ -297,8 +301,8 @@ declare function buildCatalog(inputs: ProductInput[]): Map<string, ResolvedProdu
|
|
|
297
301
|
declare function currencyExponent(currency: string): number;
|
|
298
302
|
/** Format minor units of `currency` in `locale` (exponent-aware: 7900 JPY = ¥7,900). */
|
|
299
303
|
declare function formatMoney(minor: number, currency: string, locale: string): Money;
|
|
300
|
-
/** Format a {@link
|
|
301
|
-
declare function
|
|
304
|
+
/** Format a {@link ResolvedOffering} into a display {@link Offering} in `locale`. */
|
|
305
|
+
declare function formatOffering(r: ResolvedOffering, locale: string): Offering;
|
|
302
306
|
|
|
303
307
|
/**
|
|
304
308
|
* The funnel **spine** — the constrained, declarative configuration authored as
|
|
@@ -372,6 +376,13 @@ interface PageMeta {
|
|
|
372
376
|
/**
|
|
373
377
|
* Ordered routing edges out of this page (first match wins). Each has a static
|
|
374
378
|
* `to` key + an optional `when` gate. Omit for the default linear flow.
|
|
379
|
+
*
|
|
380
|
+
* OPTIONAL per-variant routing: a `@variant` page (`paywall@b`) MAY carry its own
|
|
381
|
+
* `next`. When that variant is the one served for its slot, its `next` governs
|
|
382
|
+
* routing for that visitor; absent, the variant inherits the slot's routing (the
|
|
383
|
+
* default — a pure superset, no behavior change for existing funnels). Assignment,
|
|
384
|
+
* exposure, and progress still anchor on the slot; only the next-page lookup
|
|
385
|
+
* consults the served variant's routes. See {@link FunnelPage}.
|
|
375
386
|
*/
|
|
376
387
|
next?: Route[];
|
|
377
388
|
/**
|
|
@@ -425,6 +436,15 @@ interface FlowPage {
|
|
|
425
436
|
* AUTHORED shape (funnel.ts owns the page list, order, and routing); the build generates
|
|
426
437
|
* the code-split `mount.tsx` from it. Routing (`next`) must stay DECLARATIVE (Route[] with
|
|
427
438
|
* `{ to, when }` conditions, no predicate functions) so the web builder UI can edit it.
|
|
439
|
+
*
|
|
440
|
+
* OPTIONAL variant routing (default off): a served variant's OWN routing is authored by
|
|
441
|
+
* declaring the variant here with its `@` key and a `next`, e.g.
|
|
442
|
+
* `{ key: 'paywall@b', next: [{ to: 'special-offer' }] }`. A variant declared this way keys
|
|
443
|
+
* on the `@` marker, so it still collapses out of the linear flow (it is NOT a linear step);
|
|
444
|
+
* its `next` is consulted only when that variant is the one served for its slot. Omit the
|
|
445
|
+
* declaration (leave the variant an undeclared `pages/<slot>@<v>.tsx` file) to inherit the
|
|
446
|
+
* slot's routing — the existing behavior, unchanged. The slot's control keeps owning
|
|
447
|
+
* assignment/exposure/progress; only the next-page lookup for a branched variant differs.
|
|
428
448
|
*/
|
|
429
449
|
interface FunnelPage extends PageMeta {
|
|
430
450
|
key: string;
|
|
@@ -483,19 +503,19 @@ interface FunnelDefinition {
|
|
|
483
503
|
/** `data.*` working/scratch variables (default values). */
|
|
484
504
|
data?: Record<string, VariableConfig>;
|
|
485
505
|
/**
|
|
486
|
-
* The catalog
|
|
487
|
-
* references (`
|
|
506
|
+
* The catalog offerings this funnel sells, as SLOTS: a local, stable key each PAGE
|
|
507
|
+
* references (`useOffering('primary')`, `<Checkout offering="primary">`) → the catalog charge
|
|
488
508
|
* KEY it's assigned to. Swap the assignment to change the offer WITHOUT touching page code;
|
|
489
509
|
* `null` = a declared-but-unassigned slot. A bare `string[]` is legacy sugar for IDENTITY
|
|
490
510
|
* slots (`['pro'] ≡ { pro: 'pro' }`) — every existing funnel keeps working. Prices resolve
|
|
491
511
|
* from the project catalog per environment (Live/Test) at render.
|
|
492
512
|
*/
|
|
493
|
-
|
|
513
|
+
offerings?: string[] | Record<string, string | null>;
|
|
494
514
|
locales?: FunnelLocales;
|
|
495
515
|
/**
|
|
496
516
|
* When `true`, the funnel shows the **geo-specific** currency/price the payment
|
|
497
517
|
* provider resolves (Stripe `currency_options`, Paddle preview, Whop, SolidGate,
|
|
498
|
-
* …). When `false` (default), a single fixed currency is used.
|
|
518
|
+
* …). When `false` (default), a single fixed currency is used. Appfunnel does no
|
|
499
519
|
* FX — this only toggles whether the platform asks the provider for the geo
|
|
500
520
|
* price; the SDK just displays whatever resolved `{amount, currency}` it's handed.
|
|
501
521
|
* (See docs/platform-v2 phase-3 §3.6.)
|
|
@@ -504,19 +524,19 @@ interface FunnelDefinition {
|
|
|
504
524
|
}
|
|
505
525
|
/** Identity helper for a funnel definition. */
|
|
506
526
|
declare function defineFunnel(def: FunnelDefinition): FunnelDefinition;
|
|
507
|
-
/** A funnel
|
|
508
|
-
interface
|
|
527
|
+
/** A funnel offering SLOT: the local key pages reference → the assigned catalog charge key (null = unassigned). */
|
|
528
|
+
interface OfferingSlot {
|
|
509
529
|
slot: string;
|
|
510
530
|
catalogKey: string | null;
|
|
511
531
|
}
|
|
512
532
|
/**
|
|
513
|
-
* Normalize `FunnelDefinition.
|
|
533
|
+
* Normalize `FunnelDefinition.offerings` to a slot list. A legacy `string[]` becomes IDENTITY
|
|
514
534
|
* slots (`['pro'] → [{ slot:'pro', catalogKey:'pro' }]`); a `{ slot: key|null }` map becomes its
|
|
515
|
-
* entries (insertion order preserved, so `
|
|
535
|
+
* entries (insertion order preserved, so `useOfferings()` is deterministic).
|
|
516
536
|
*/
|
|
517
|
-
declare function
|
|
537
|
+
declare function normalizeOfferings(offerings: FunnelDefinition['offerings']): OfferingSlot[];
|
|
518
538
|
/** The unique, ASSIGNED catalog keys a funnel charges — for the promote gate + catalog reverse-lookup. */
|
|
519
|
-
declare function funnelCatalogKeys(
|
|
539
|
+
declare function funnelCatalogKeys(offerings: FunnelDefinition['offerings']): string[];
|
|
520
540
|
|
|
521
541
|
/**
|
|
522
542
|
* Pure locale resolution — no React. Split out of {@link ./translation} so the
|
|
@@ -610,8 +630,8 @@ declare function parseSlotKey(key: string): {
|
|
|
610
630
|
};
|
|
611
631
|
/** True if a page key is an off-flow variant (`welcome@b`), by the source-side `@` marker. */
|
|
612
632
|
declare function isVariantKey(key: string): boolean;
|
|
613
|
-
/** One arm of
|
|
614
|
-
interface
|
|
633
|
+
/** One arm of a running experiment: which page renders, at what weight. */
|
|
634
|
+
interface ExperimentArm {
|
|
615
635
|
/** The page key this arm renders (the slot's control, or a `@variant` sibling). */
|
|
616
636
|
page: string;
|
|
617
637
|
/** Relative weight in the split (need not sum to 100; zero = paused arm). */
|
|
@@ -619,19 +639,24 @@ interface ExperimentVariant {
|
|
|
619
639
|
}
|
|
620
640
|
/**
|
|
621
641
|
* A runtime experiment record — the operational wiring the **platform** owns and
|
|
622
|
-
* hands to the SDK (not authored in the funnel source).
|
|
623
|
-
*
|
|
642
|
+
* hands to the SDK (not authored in the funnel source). Projected from the unified
|
|
643
|
+
* `Experiment`/`ExperimentArm` graph (a RUNNING PAGE experiment + its arms): the
|
|
644
|
+
* experiment's `slot` names the base page, and each arm becomes a `variants` entry
|
|
645
|
+
* `{ page: arm.pageKey, weight: arm.weight }` keyed by `arm.label`. The variant
|
|
646
|
+
* *pages* are code in the LIVE build; this is the live split over them.
|
|
647
|
+
*
|
|
648
|
+
* The serve join only ever hands over RUNNING experiments, and the server's
|
|
649
|
+
* draft-write + serve guards make every active arm's page GUARANTEED present in the
|
|
650
|
+
* LIVE build (an experiment owns its arm pages' lifecycle — you end the experiment to
|
|
651
|
+
* remove a page, you can't drift it away). So there is no lifecycle/winner state and
|
|
652
|
+
* no "arm page missing" case to resolve here.
|
|
624
653
|
*/
|
|
625
654
|
interface RuntimeExperiment {
|
|
626
655
|
id: string;
|
|
627
|
-
/** The page key holding the flow position (the anchor / control). */
|
|
656
|
+
/** The page key holding the flow position (the anchor / the control arm's base page). */
|
|
628
657
|
slot: string;
|
|
629
|
-
/** label → arm.
|
|
630
|
-
variants: Record<string,
|
|
631
|
-
/** Lifecycle. Absent = running. */
|
|
632
|
-
status?: 'running' | 'paused' | 'stopped';
|
|
633
|
-
/** When stopped, the variant label everyone sees until promotion bakes it into the slot. */
|
|
634
|
-
winner?: string;
|
|
658
|
+
/** label → arm. The control arm's `page` is the `slot`; others are `@variant` siblings. */
|
|
659
|
+
variants: Record<string, ExperimentArm>;
|
|
635
660
|
/** Primary metric (analytics only; not used for resolution). */
|
|
636
661
|
metric?: string;
|
|
637
662
|
/** Platform record version (immutable-while-running; reweight = new version). Rides on
|
|
@@ -661,43 +686,20 @@ declare function weightsOf(variants: Record<string, {
|
|
|
661
686
|
weight: number;
|
|
662
687
|
}>): Record<string, number>;
|
|
663
688
|
/**
|
|
664
|
-
* Resolve which page version each experiment slot shows for this visitor, and
|
|
665
|
-
*
|
|
689
|
+
* Resolve which page version each experiment slot shows for this visitor, and which
|
|
690
|
+
* page keys remain in the linear flow.
|
|
666
691
|
*
|
|
667
|
-
* Variant pages are detected structurally by the `@` in their key, so they
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
692
|
+
* Variant pages are detected structurally by the `@` in their key, so they collapse
|
|
693
|
+
* out of the linear flow even for a slot with no active experiment. Each served
|
|
694
|
+
* experiment is RUNNING with all its arm pages guaranteed present in the LIVE build
|
|
695
|
+
* (the server draft-write + serve guards enforce the experiment-owned lifecycle
|
|
696
|
+
* invariant), so the visitor is bucketed deterministically on `seed` and the assigned
|
|
697
|
+
* arm's page is swapped in with no presence check. With no `seed` there is nothing
|
|
698
|
+
* stable to bucket on, so every slot renders its own control page.
|
|
671
699
|
*/
|
|
672
700
|
declare function resolveExperiments(pages: {
|
|
673
701
|
key: string;
|
|
674
702
|
}[], experiments: RuntimeExperiment[], seed: string | null): ExperimentResolution;
|
|
675
|
-
/** One referential problem with an experiment record. */
|
|
676
|
-
interface ExperimentIssue {
|
|
677
|
-
/** The offending experiment id (`'*'` for a cross-experiment issue). */
|
|
678
|
-
experimentId: string;
|
|
679
|
-
code: 'slot_missing' | 'slot_is_variant' | 'slot_conflict' | 'duplicate_id' | 'variant_page_missing' | 'variant_slot_mismatch' | 'no_control' | 'no_traffic' | 'negative_weight' | 'bad_winner' | 'single_arm' | 'orphan_variant';
|
|
680
|
-
message: string;
|
|
681
|
-
}
|
|
682
|
-
interface ExperimentValidation {
|
|
683
|
-
ok: boolean;
|
|
684
|
-
errors: ExperimentIssue[];
|
|
685
|
-
warnings: ExperimentIssue[];
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Cross-check experiment records against the funnel's pages — the publish-time /
|
|
689
|
-
* experiment-edit gate that catches the dangling references the runtime can't see
|
|
690
|
-
* (the experiment wiring lives in the DB; the pages live in the build, so they can
|
|
691
|
-
* drift). Pure: hand it the runtime experiments + the funnel's page keys (e.g.
|
|
692
|
-
* `manifest.pages`).
|
|
693
|
-
*
|
|
694
|
-
* Errors block; warnings inform. `orphan_variant` is a warning, not an error — a
|
|
695
|
-
* `@variant` file with no running experiment is dead-but-harmless (a paused test, or
|
|
696
|
-
* one not wired yet).
|
|
697
|
-
*/
|
|
698
|
-
declare function validateExperiments(experiments: RuntimeExperiment[], pages: {
|
|
699
|
-
key: string;
|
|
700
|
-
}[]): ExperimentValidation;
|
|
701
703
|
|
|
702
704
|
/**
|
|
703
705
|
* The **manifest** — the declarative IR a funnel project compiles to (doc 06).
|
|
@@ -773,6 +775,13 @@ interface ManifestEdge {
|
|
|
773
775
|
kind: 'branch' | 'linear';
|
|
774
776
|
/** Absent = unconditional (a fallback route, or the linear fall-through). */
|
|
775
777
|
condition?: EdgeCondition;
|
|
778
|
+
/**
|
|
779
|
+
* True on a **variant-only branch**: an edge whose `from` is a `@variant` key,
|
|
780
|
+
* emitted when that served variant carries its OWN `meta.next` (optional divergent
|
|
781
|
+
* routing). Lets consumers (the editor's Flow canvas) tell a variant's private branch
|
|
782
|
+
* apart from a normal slot edge. Optional/absent on every non-variant edge.
|
|
783
|
+
*/
|
|
784
|
+
variant?: true;
|
|
776
785
|
}
|
|
777
786
|
interface ManifestValidation {
|
|
778
787
|
/**
|
|
@@ -810,9 +819,9 @@ interface ManifestValidation {
|
|
|
810
819
|
* though every page can proceed. Hard error.
|
|
811
820
|
*/
|
|
812
821
|
noReachableFinish: boolean;
|
|
813
|
-
/** Slot names a PAGE references (`
|
|
814
|
-
|
|
815
|
-
/** Declared slots a page references but which have no catalog
|
|
822
|
+
/** Slot names a PAGE references (`useOffering`/`<Checkout offering>`) that the funnel doesn't declare — hard error. */
|
|
823
|
+
unknownOfferingRefs: string[];
|
|
824
|
+
/** Declared slots a page references but which have no catalog charge key assigned (map value `null`) — hard error. */
|
|
816
825
|
unassignedSlots: string[];
|
|
817
826
|
}
|
|
818
827
|
interface FunnelManifest {
|
|
@@ -824,11 +833,11 @@ interface FunnelManifest {
|
|
|
824
833
|
edges: ManifestEdge[];
|
|
825
834
|
};
|
|
826
835
|
/** The unique, ASSIGNED catalog keys the funnel charges (for the promote gate + catalog reverse-lookup). */
|
|
827
|
-
|
|
828
|
-
/**
|
|
829
|
-
|
|
830
|
-
/** Slot names PAGES reference (from the build's scan); validated against `
|
|
831
|
-
|
|
836
|
+
offerings: string[];
|
|
837
|
+
/** Offering SLOTS: slot → assigned catalog key|null. `offerings` above is the unique assigned keys. */
|
|
838
|
+
offeringSlots: OfferingSlot[];
|
|
839
|
+
/** Slot names PAGES reference (from the build's scan); validated against `offeringSlots`. Absent = no scan ran. */
|
|
840
|
+
offeringRefs?: string[];
|
|
832
841
|
locales?: FunnelLocales;
|
|
833
842
|
validation: ManifestValidation;
|
|
834
843
|
}
|
|
@@ -839,10 +848,10 @@ interface CompileInput {
|
|
|
839
848
|
key: string;
|
|
840
849
|
meta?: PageMeta;
|
|
841
850
|
}[];
|
|
842
|
-
/** Slot names the funnel's PAGES reference (from `
|
|
843
|
-
|
|
851
|
+
/** Slot names the funnel's PAGES reference (from `scanOfferingRefs`) — cross-checked against the declared slots. */
|
|
852
|
+
offeringRefs?: string[];
|
|
844
853
|
}
|
|
845
854
|
/** Compile a funnel's spine into its declarative {@link FunnelManifest} IR. */
|
|
846
855
|
declare function compileManifest(input: CompileInput): FunnelManifest;
|
|
847
856
|
|
|
848
|
-
export {
|
|
857
|
+
export { expectedPathLength as $, type Acquisition as A, type BrowserContext as B, type ClickIdContext as C, type Direction as D, type EdgeCondition as E, type FunnelContext as F, type Gate as G, assignVariant as H, type IdentityContext as I, bucketingSeed as J, buildAcquisition as K, type LocaleContext as L, type ManifestEdge as M, buildCatalog as N, type Offering as O, type PageMeta as P, compileManifest as Q, type RuntimeExperiment as R, type SessionContext as S, currencyExponent as T, type UtmContext as U, type VariableValue as V, defineFunnel as W, definePage as X, entryGuard as Y, evaluateCondition as Z, evaluateGate as _, type VariableConfig as a, fnv1a as a0, formatMoney as a1, formatOffering as a2, hashToUnit as a3, isRtl as a4, isVariantKey as a5, nextPage as a6, outgoingKeys as a7, pageMeta as a8, parseSlotKey as a9, pickByWeight as aa, resolveExperiments as ab, resolveLocale as ac, resolveOffering as ad, resolveRoute as ae, type FunnelPage as af, type OfferingSlot as ag, funnelCatalogKeys as ah, normalizeOfferings as ai, weightsOf as aj, type FunnelSnapshot as b, type FunnelDefinition as c, buildContext as d, type OfferingInput as e, type PageContext as f, type BuildContextOptions as g, type CompileInput as h, type Condition as i, type ConditionOp as j, type DeviceContext as k, type ExperimentArm as l, type ExperimentResolution as m, type FlowPage as n, type FunnelLocales as o, type FunnelManifest as p, type Interval as q, type ManifestPage as r, type ManifestValidation as s, type Money as t, type OsContext as u, type PageType as v, type Predicate as w, type ResolvedOffering as x, type Route as y, type SystemContext as z };
|
package/dist/manifest-entry.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkBPOAWI4G_cjs = require('./chunk-BPOAWI4G.cjs');
|
|
4
4
|
var chunkQMAZGLGV_cjs = require('./chunk-QMAZGLGV.cjs');
|
|
5
5
|
require('./chunk-EMMSS5I5.cjs');
|
|
6
6
|
|
|
7
|
-
// src/manifest/
|
|
7
|
+
// src/manifest/offering-refs.ts
|
|
8
8
|
function mask(src) {
|
|
9
9
|
const out = src.split("");
|
|
10
10
|
const n = src.length;
|
|
@@ -60,7 +60,7 @@ function skipWs(masked, i) {
|
|
|
60
60
|
while (i < masked.length && (masked[i] === " " || masked[i] === " " || masked[i] === "\n" || masked[i] === "\r")) i++;
|
|
61
61
|
return i;
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function scanOfferingRefs(files) {
|
|
64
64
|
const seen = /* @__PURE__ */ new Set();
|
|
65
65
|
const order = [];
|
|
66
66
|
let dynamic = false;
|
|
@@ -73,8 +73,8 @@ function scanProductRefs(files) {
|
|
|
73
73
|
for (const [path, source] of Object.entries(files)) {
|
|
74
74
|
if (!/\.(tsx|jsx|ts|js)$/.test(path)) continue;
|
|
75
75
|
const masked = mask(source);
|
|
76
|
-
for (const m of masked.matchAll(/\
|
|
77
|
-
const plural = source.slice(m.index, m.index +
|
|
76
|
+
for (const m of masked.matchAll(/\buseOfferings?\s*\(/g)) {
|
|
77
|
+
const plural = source.slice(m.index, m.index + 12) === "useOfferings";
|
|
78
78
|
const j = skipWs(masked, m.index + m[0].length);
|
|
79
79
|
if (masked[j] === ")") {
|
|
80
80
|
if (plural) dynamic = true;
|
|
@@ -84,7 +84,7 @@ function scanProductRefs(files) {
|
|
|
84
84
|
if (lit !== null) add(lit);
|
|
85
85
|
else dynamic = true;
|
|
86
86
|
}
|
|
87
|
-
for (const m of masked.matchAll(/<(?:Checkout|Upsell)\b[^>]*?\
|
|
87
|
+
for (const m of masked.matchAll(/<(?:Checkout|Upsell)\b[^>]*?\boffering\s*=\s*/g)) {
|
|
88
88
|
const j = m.index + m[0].length;
|
|
89
89
|
const lit = literalAt(source, masked, j);
|
|
90
90
|
if (lit !== null) add(lit);
|
|
@@ -102,7 +102,7 @@ var DRIVERS = {
|
|
|
102
102
|
stripe: { name: "stripeCheckoutDriver", sub: "driver-stripe" },
|
|
103
103
|
paddle: { name: "paddleCheckoutDriver", sub: "driver-paddle" }
|
|
104
104
|
};
|
|
105
|
-
var HEADER = "// GENERATED by
|
|
105
|
+
var HEADER = "// GENERATED by Appfunnel \u2014 do not edit. Built from funnel.ts + the pages/ folder.\n";
|
|
106
106
|
function generateMount(opts) {
|
|
107
107
|
const p = opts.importPrefix ?? "./";
|
|
108
108
|
const driver = opts.checkout ? DRIVERS[opts.checkout] : null;
|
|
@@ -112,7 +112,9 @@ function generateMount(opts) {
|
|
|
112
112
|
driver && `import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,
|
|
113
113
|
`import { config } from '${p}funnel'`,
|
|
114
114
|
opts.hasLayout && `import Layout from '${p}layout'`,
|
|
115
|
-
...locales.map(
|
|
115
|
+
...locales.map(
|
|
116
|
+
(loc) => `import ${msgId(loc)} from '${p}messages/${loc}.json'`
|
|
117
|
+
)
|
|
116
118
|
].filter(Boolean).join("\n");
|
|
117
119
|
const loaders = opts.pageKeys.map(
|
|
118
120
|
(k) => ` ${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`
|
|
@@ -133,8 +135,10 @@ const loaders: Record<string, () => Promise<unknown>> = {
|
|
|
133
135
|
${loaders}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
// Flow pages come from funnel.ts (order + meta + routing).
|
|
137
|
-
//
|
|
138
|
+
// Flow pages come from funnel.ts (order + meta + routing). A \`@variant\` page MAY also be declared
|
|
139
|
+
// there (with its \`@\` key) to carry its OWN optional routing (\`next\`) \u2014 it still collapses out of
|
|
140
|
+
// the linear flow by the \`@\` marker, but its meta rides along here. Any page file NOT declared is an
|
|
141
|
+
// off-flow \`@variant\` with no own routing, appended with empty meta so it's still loadable.
|
|
138
142
|
const decls = config.pages ?? []
|
|
139
143
|
const flowKeys = new Set(decls.map((d) => d.key))
|
|
140
144
|
export const pages: RuntimePage[] = [
|
|
@@ -180,7 +184,7 @@ import { tree, pages, type MountOpts } from './mount'
|
|
|
180
184
|
export const manifest = compileManifest({
|
|
181
185
|
funnel: config,
|
|
182
186
|
pages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),
|
|
183
|
-
|
|
187
|
+
offeringRefs: ${JSON.stringify(opts.offeringRefs ?? [])},
|
|
184
188
|
})
|
|
185
189
|
|
|
186
190
|
export async function ssr(opts: MountOpts): Promise<string> {
|
|
@@ -204,83 +208,79 @@ if (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))
|
|
|
204
208
|
|
|
205
209
|
Object.defineProperty(exports, "assignVariant", {
|
|
206
210
|
enumerable: true,
|
|
207
|
-
get: function () { return
|
|
211
|
+
get: function () { return chunkBPOAWI4G_cjs.assignVariant; }
|
|
208
212
|
});
|
|
209
213
|
Object.defineProperty(exports, "bucketingSeed", {
|
|
210
214
|
enumerable: true,
|
|
211
|
-
get: function () { return
|
|
215
|
+
get: function () { return chunkBPOAWI4G_cjs.bucketingSeed; }
|
|
212
216
|
});
|
|
213
217
|
Object.defineProperty(exports, "buildCatalog", {
|
|
214
218
|
enumerable: true,
|
|
215
|
-
get: function () { return
|
|
219
|
+
get: function () { return chunkBPOAWI4G_cjs.buildCatalog; }
|
|
216
220
|
});
|
|
217
221
|
Object.defineProperty(exports, "compileManifest", {
|
|
218
222
|
enumerable: true,
|
|
219
|
-
get: function () { return
|
|
223
|
+
get: function () { return chunkBPOAWI4G_cjs.compileManifest; }
|
|
220
224
|
});
|
|
221
225
|
Object.defineProperty(exports, "currencyExponent", {
|
|
222
226
|
enumerable: true,
|
|
223
|
-
get: function () { return
|
|
227
|
+
get: function () { return chunkBPOAWI4G_cjs.currencyExponent; }
|
|
224
228
|
});
|
|
225
229
|
Object.defineProperty(exports, "defineFunnel", {
|
|
226
230
|
enumerable: true,
|
|
227
|
-
get: function () { return
|
|
231
|
+
get: function () { return chunkBPOAWI4G_cjs.defineFunnel; }
|
|
228
232
|
});
|
|
229
233
|
Object.defineProperty(exports, "entryGuard", {
|
|
230
234
|
enumerable: true,
|
|
231
|
-
get: function () { return
|
|
235
|
+
get: function () { return chunkBPOAWI4G_cjs.entryGuard; }
|
|
232
236
|
});
|
|
233
237
|
Object.defineProperty(exports, "evaluateCondition", {
|
|
234
238
|
enumerable: true,
|
|
235
|
-
get: function () { return
|
|
239
|
+
get: function () { return chunkBPOAWI4G_cjs.evaluateCondition; }
|
|
236
240
|
});
|
|
237
241
|
Object.defineProperty(exports, "formatMoney", {
|
|
238
242
|
enumerable: true,
|
|
239
|
-
get: function () { return
|
|
243
|
+
get: function () { return chunkBPOAWI4G_cjs.formatMoney; }
|
|
240
244
|
});
|
|
241
|
-
Object.defineProperty(exports, "
|
|
245
|
+
Object.defineProperty(exports, "formatOffering", {
|
|
242
246
|
enumerable: true,
|
|
243
|
-
get: function () { return
|
|
247
|
+
get: function () { return chunkBPOAWI4G_cjs.formatOffering; }
|
|
244
248
|
});
|
|
245
249
|
Object.defineProperty(exports, "funnelCatalogKeys", {
|
|
246
250
|
enumerable: true,
|
|
247
|
-
get: function () { return
|
|
251
|
+
get: function () { return chunkBPOAWI4G_cjs.funnelCatalogKeys; }
|
|
248
252
|
});
|
|
249
253
|
Object.defineProperty(exports, "isRtl", {
|
|
250
254
|
enumerable: true,
|
|
251
|
-
get: function () { return
|
|
255
|
+
get: function () { return chunkBPOAWI4G_cjs.isRtl; }
|
|
252
256
|
});
|
|
253
257
|
Object.defineProperty(exports, "isVariantKey", {
|
|
254
258
|
enumerable: true,
|
|
255
|
-
get: function () { return
|
|
259
|
+
get: function () { return chunkBPOAWI4G_cjs.isVariantKey; }
|
|
256
260
|
});
|
|
257
|
-
Object.defineProperty(exports, "
|
|
261
|
+
Object.defineProperty(exports, "normalizeOfferings", {
|
|
258
262
|
enumerable: true,
|
|
259
|
-
get: function () { return
|
|
263
|
+
get: function () { return chunkBPOAWI4G_cjs.normalizeOfferings; }
|
|
260
264
|
});
|
|
261
265
|
Object.defineProperty(exports, "pageMeta", {
|
|
262
266
|
enumerable: true,
|
|
263
|
-
get: function () { return
|
|
267
|
+
get: function () { return chunkBPOAWI4G_cjs.pageMeta; }
|
|
264
268
|
});
|
|
265
269
|
Object.defineProperty(exports, "parseSlotKey", {
|
|
266
270
|
enumerable: true,
|
|
267
|
-
get: function () { return
|
|
271
|
+
get: function () { return chunkBPOAWI4G_cjs.parseSlotKey; }
|
|
268
272
|
});
|
|
269
273
|
Object.defineProperty(exports, "resolveLocale", {
|
|
270
274
|
enumerable: true,
|
|
271
|
-
get: function () { return
|
|
275
|
+
get: function () { return chunkBPOAWI4G_cjs.resolveLocale; }
|
|
272
276
|
});
|
|
273
|
-
Object.defineProperty(exports, "
|
|
277
|
+
Object.defineProperty(exports, "resolveOffering", {
|
|
274
278
|
enumerable: true,
|
|
275
|
-
get: function () { return
|
|
276
|
-
});
|
|
277
|
-
Object.defineProperty(exports, "validateExperiments", {
|
|
278
|
-
enumerable: true,
|
|
279
|
-
get: function () { return chunkG44BUG6G_cjs.validateExperiments; }
|
|
279
|
+
get: function () { return chunkBPOAWI4G_cjs.resolveOffering; }
|
|
280
280
|
});
|
|
281
281
|
Object.defineProperty(exports, "weightsOf", {
|
|
282
282
|
enumerable: true,
|
|
283
|
-
get: function () { return
|
|
283
|
+
get: function () { return chunkBPOAWI4G_cjs.weightsOf; }
|
|
284
284
|
});
|
|
285
285
|
Object.defineProperty(exports, "INLINE_SURFACES", {
|
|
286
286
|
enumerable: true,
|
|
@@ -322,6 +322,6 @@ exports.generateDevEntry = generateDevEntry;
|
|
|
322
322
|
exports.generateEntryClient = generateEntryClient;
|
|
323
323
|
exports.generateEntryServer = generateEntryServer;
|
|
324
324
|
exports.generateMount = generateMount;
|
|
325
|
-
exports.
|
|
325
|
+
exports.scanOfferingRefs = scanOfferingRefs;
|
|
326
326
|
//# sourceMappingURL=manifest-entry.cjs.map
|
|
327
327
|
//# sourceMappingURL=manifest-entry.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manifest/product-refs.ts","../src/flow/scaffold.ts"],"names":[],"mappings":"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","file":"manifest-entry.cjs","sourcesContent":["/**\n * Pure, React-free page scan: find which product SLOTS a funnel's pages reference — the first\n * string-literal arg of `useProduct(...)` and the `product` prop of `<Checkout>` / `<Upsell>`.\n * Ships via `@appfunnel-dev/sdk/manifest` so the BUILD, the EDITOR, and the AI all run the SAME\n * check. The result feeds `compileManifest({ productRefs })`, which flags references to slots the\n * funnel doesn't declare (unknown) or hasn't assigned a catalog product to (unassigned).\n *\n * Non-literal args (`useProduct(selectedId)`, `product={expr}`) and the `useProducts()` wildcard are\n * DYNAMIC — they can't be statically enumerated, so they're reported via `dynamic` and never turned\n * into a hard error (validation relaxes when a funnel is dynamic). Mirrors the mask-then-read\n * discipline the funnel.ts code-mods use, so a `useProduct('x')` inside a comment or string literal\n * can't false-positive.\n */\n\nexport interface ProductRefScan {\n /** Concrete slot names referenced by LITERAL args, deduped, in first-seen order. */\n slots: string[]\n /** True if any ref is a non-literal arg or a `useProducts()` wildcard (not statically enumerable). */\n dynamic: boolean\n}\n\n/** Blank comment + string/template CONTENTS (keep quote delimiters + length) so scans don't fire inside them. */\nfunction mask(src: string): string {\n const out = src.split('')\n const n = src.length\n let i = 0\n while (i < n) {\n const c = src[i]\n const d = src[i + 1]\n if (c === '/' && d === '/') {\n while (i < n && src[i] !== '\\n') { out[i] = ' '; i++ }\n } else if (c === '/' && d === '*') {\n out[i] = ' '; out[i + 1] = ' '; i += 2\n while (i < n) {\n if (src[i] === '*' && src[i + 1] === '/') { out[i] = ' '; out[i + 1] = ' '; i += 2; break }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n } else if (c === \"'\" || c === '\"' || c === '`') {\n i++\n while (i < n && src[i] !== c) {\n if (src[i] === '\\\\') { out[i] = ' '; i++; if (i < n && src[i] !== '\\n') out[i] = ' '; i++; continue }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n i++ // closing delimiter (kept)\n } else i++\n }\n return out.join('')\n}\n\n/** A quoted string literal starting at `open` (a quote char in the MASK). Value read from the ORIGINAL. */\nfunction literalAt(src: string, masked: string, open: number): string | null {\n const q = masked[open]\n if (q !== \"'\" && q !== '\"' && q !== '`') return null\n const close = masked.indexOf(q, open + 1)\n if (close === -1) return null\n return src.slice(open + 1, close)\n}\n\n/** Advance past whitespace in `masked` from `i`. */\nfunction skipWs(masked: string, i: number): number {\n while (i < masked.length && (masked[i] === ' ' || masked[i] === '\\t' || masked[i] === '\\n' || masked[i] === '\\r')) i++\n return i\n}\n\nexport function scanProductRefs(files: Record<string, string>): ProductRefScan {\n const seen = new Set<string>()\n const order: string[] = []\n let dynamic = false\n const add = (v: string) => { if (v && !seen.has(v)) { seen.add(v); order.push(v) } }\n\n for (const [path, source] of Object.entries(files)) {\n if (!/\\.(tsx|jsx|ts|js)$/.test(path)) continue\n const masked = mask(source)\n\n // useProduct( … ) / useProducts( … ) — positions on the MASK (a call inside a string won't match).\n for (const m of masked.matchAll(/\\buseProducts?\\s*\\(/g)) {\n const plural = source.slice(m.index!, m.index! + 11) === 'useProducts'\n const j = skipWs(masked, m.index! + m[0].length)\n if (masked[j] === ')') { if (plural) dynamic = true; continue } // useProducts() = whole-catalog wildcard\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else dynamic = true // useProduct(variable) — can't resolve statically\n }\n\n // <Checkout product=\"…\"> / <Upsell product=\"…\"> — the `product` attribute value.\n for (const m of masked.matchAll(/<(?:Checkout|Upsell)\\b[^>]*?\\bproduct\\s*=\\s*/g)) {\n const j = m.index! + m[0].length\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else if (masked[j] === '{') dynamic = true // product={expr}\n }\n }\n return { slots: order, dynamic }\n}\n","/**\n * Source generators for the funnel build \"machinery\" authors no longer write. `funnel.ts` (the\n * spine: id, responses, products, checkout, and the page list + declarative routing) plus the\n * `pages/` folder are the ONLY authored source. The build (renderer/builder) and the CLI dev server\n * call these to synthesize:\n * - `mount.tsx` — code-split page loaders + `createFunnelTree`\n * - `entry.client.tsx` — hydrate the SSR'd markup from injected data\n * - `entry.server.tsx` — `ssr()` + the compiled `manifest`\n *\n * All string templates, zero runtime deps — this module lives in the pure `@appfunnel-dev/sdk/\n * manifest` entry so tooling imports it without the funnel runtime.\n */\n\nexport interface ScaffoldOptions {\n\t/** Page component keys = `pages/<key>.tsx` filenames (base flow pages AND `@variant` pages). */\n\tpageKeys: string[]\n\t/** Import prefix to reach the funnel root: `'./'` at the root (build), `'../../'` in a subdir (CLI dev). */\n\timportPrefix?: string\n\t/** Whether a `layout.tsx` exists to wrap the pages. */\n\thasLayout?: boolean\n\t/** Whether a `styles.css` exists (client entry imports it). */\n\thasStyles?: boolean\n\t/** Checkout provider from `config.checkout` — wires the driver; omit for none. */\n\tcheckout?: 'stripe' | 'paddle' | null\n\t/** Locales with a `messages/<locale>.json` catalog to bake into the mount (so `t()` resolves at render). */\n\tmessageLocales?: string[]\n\t/** Slot names the pages reference (from `scanProductRefs`), baked into the manifest for validation. */\n\tproductRefs?: string[]\n}\n\n/** A safe JS identifier for a message-catalog import (`en` → `msg_en`, `pt-BR` → `msg_pt_BR`). */\nfunction msgId(locale: string): string {\n\treturn 'msg_' + locale.replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nconst DRIVERS = {\n\tstripe: { name: 'stripeCheckoutDriver', sub: 'driver-stripe' },\n\tpaddle: { name: 'paddleCheckoutDriver', sub: 'driver-paddle' },\n} as const\n\nconst HEADER = '// GENERATED by AppFunnel — do not edit. Built from funnel.ts + the pages/ folder.\\n'\n\n/** `mount.tsx` — the code-split loaders + tree, derived from `config.pages` and the `pages/` files. */\nexport function generateMount(opts: ScaffoldOptions): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst driver = opts.checkout ? DRIVERS[opts.checkout] : null\n\n\tconst locales = opts.messageLocales ?? []\n\tconst imports = [\n\t\t`import { createFunnelTree, type MountOpts, type RuntimePage } from '@appfunnel-dev/sdk'`,\n\t\tdriver && `import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,\n\t\t`import { config } from '${p}funnel'`,\n\t\topts.hasLayout && `import Layout from '${p}layout'`,\n\t\t...locales.map((loc) => `import ${msgId(loc)} from '${p}messages/${loc}.json'`),\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n')\n\n\t// A static import() per page file so Vite emits one code-split chunk each.\n\tconst loaders = opts.pageKeys\n\t\t.map(\n\t\t\t(k) => `\\t${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`\n\t\t)\n\t\t.join('\\n')\n\n\tconst treeArgs = [\n\t\t'config',\n\t\t'pages',\n\t\topts.hasLayout && 'layout: Layout',\n\t\tdriver && `checkoutDriver: ${driver.name}`,\n\t\tlocales.length > 0 && 'messages',\n\t]\n\t\t.filter(Boolean)\n\t\t.join(', ')\n\n\t// The baked message catalog: locale → messages/<locale>.json (so `t()` resolves at render).\n\tconst messagesConst =\n\t\tlocales.length > 0\n\t\t\t? `\\nconst messages = { ${locales.map((loc) => `${JSON.stringify(loc)}: ${msgId(loc)}`).join(', ')} }\\n`\n\t\t\t: ''\n\n\treturn `${HEADER}${imports}\n\nconst loaders: Record<string, () => Promise<unknown>> = {\n${loaders}\n}\n\n// Flow pages come from funnel.ts (order + meta + routing). Any remaining page file is an off-flow\n// \\`@variant\\` page (wired by experiment records at render time), appended so it's loadable.\nconst decls = config.pages ?? []\nconst flowKeys = new Set(decls.map((d) => d.key))\nexport const pages: RuntimePage[] = [\n\t...decls.map((d) => ({ key: d.key, meta: d, load: loaders[d.key] })),\n\t...Object.keys(loaders)\n\t\t.filter((k) => !flowKeys.has(k))\n\t\t.map((k) => ({ key: k, meta: {}, load: loaders[k] })),\n] as RuntimePage[]\n${messagesConst}\nexport const tree = createFunnelTree({ ${treeArgs} })\n\nexport type { MountOpts }\n`\n}\n\n/** `entry.client.tsx` — hydrate the SSR'd markup from the injected `#__af_data`. */\nexport function generateEntryClient(opts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'>): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { hydrateRoot } from 'react-dom/client'\nimport { tree, type MountOpts } from './mount'\n\nconst dataEl = document.getElementById('__af_data')\nconst data = (dataEl?.textContent ? JSON.parse(dataEl.textContent) : {}) as MountOpts & {\n\tinitialKey?: string | null\n}\nconst root = document.getElementById('root')\n// FLASH-FREE HYDRATION (SSR-isolation Option B): always DEFER sessionValues on the client. The\n// markup we hydrate against is DEFAULT-state (build-time prerendered, or an SSR render with empty\n// sessionValues), so seeding the store with defaults for the first render is a byte-clean match —\n// no hydration mismatch. The visitor's saved answers (data.sessionValues, present on the \"tail\"\n// path) are then applied post-hydration, popping into already-visible controls with no empty\n// flash. On the cold path sessionValues is absent, so deferring is a no-op (identical output).\nif (root) hydrateRoot(root, tree({ ...data, initialKey: data.initialKey ?? undefined, deferSessionValues: true }))\n`\n}\n\n/** `entry.server.tsx` — `ssr(opts)` HTML + the compiled `manifest` (read by the build). */\nexport function generateEntryServer(opts: Pick<ScaffoldOptions, 'importPrefix' | 'productRefs'>): string {\n\tconst p = opts.importPrefix ?? './'\n\treturn `${HEADER}import { renderToReadableStream } from 'react-dom/server.browser'\nimport { compileManifest } from '@appfunnel-dev/sdk'\nimport { config } from '${p}funnel'\nimport { tree, pages, type MountOpts } from './mount'\n\nexport const manifest = compileManifest({\n\tfunnel: config,\n\tpages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),\n\tproductRefs: ${JSON.stringify(opts.productRefs ?? [])},\n})\n\nexport async function ssr(opts: MountOpts): Promise<string> {\n\tconst stream = await renderToReadableStream(tree(opts))\n\tawait stream.allReady\n\treturn await new Response(stream).text()\n}\n`\n}\n\n/** Client-only dev entry (createRoot, no SSR) — for the CLI `dev` server. */\nexport function generateDevEntry(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'> & { data: unknown }\n): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { createRoot } from 'react-dom/client'\nimport { tree } from './mount'\n\nconst el = document.getElementById('root')\nif (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))\n`\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/manifest/offering-refs.ts","../src/flow/scaffold.ts"],"names":[],"mappings":";;;;;;;AAsBA,SAAS,KAAK,GAAA,EAAqB;AACjC,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACxB,EAAA,MAAM,IAAI,GAAA,CAAI,MAAA;AACd,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,IAAI,CAAA,EAAG;AACZ,IAAA,MAAM,CAAA,GAAI,IAAI,CAAC,CAAA;AACf,IAAA,MAAM,CAAA,GAAI,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA;AACnB,IAAA,IAAI,CAAA,KAAM,GAAA,IAAO,CAAA,KAAM,GAAA,EAAK;AAC1B,MAAA,OAAO,CAAA,GAAI,CAAA,IAAK,GAAA,CAAI,CAAC,MAAM,IAAA,EAAM;AAAE,QAAA,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAAK,QAAA,CAAA,EAAA;AAAA,MAAI;AAAA,IACvD,CAAA,MAAA,IAAW,CAAA,KAAM,GAAA,IAAO,CAAA,KAAM,GAAA,EAAK;AACjC,MAAA,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAAK,MAAA,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA,GAAI,GAAA;AAAK,MAAA,CAAA,IAAK,CAAA;AACrC,MAAA,OAAO,IAAI,CAAA,EAAG;AACZ,QAAA,IAAI,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,IAAO,IAAI,CAAA,GAAI,CAAC,MAAM,GAAA,EAAK;AAAE,UAAA,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAAK,UAAA,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA,GAAI,GAAA;AAAK,UAAA,CAAA,IAAK,CAAA;AAAG,UAAA;AAAA,QAAM;AAC1F,QAAA,IAAI,IAAI,CAAC,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAC9B,QAAA,CAAA,EAAA;AAAA,MACF;AAAA,IACF,WAAW,CAAA,KAAM,GAAA,IAAO,CAAA,KAAM,GAAA,IAAO,MAAM,GAAA,EAAK;AAC9C,MAAA,CAAA,EAAA;AACA,MAAA,OAAO,CAAA,GAAI,CAAA,IAAK,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG;AAC5B,QAAA,IAAI,GAAA,CAAI,CAAC,CAAA,KAAM,IAAA,EAAM;AAAE,UAAA,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAAK,UAAA,CAAA,EAAA;AAAK,UAAA,IAAI,CAAA,GAAI,KAAK,GAAA,CAAI,CAAC,MAAM,IAAA,EAAM,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAAK,UAAA,CAAA,EAAA;AAAK,UAAA;AAAA,QAAS;AACpG,QAAA,IAAI,IAAI,CAAC,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA;AAC9B,QAAA,CAAA,EAAA;AAAA,MACF;AACA,MAAA,CAAA,EAAA;AAAA,IACF,CAAA,MAAO,CAAA,EAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,EAAE,CAAA;AACpB;AAGA,SAAS,SAAA,CAAU,GAAA,EAAa,MAAA,EAAgB,IAAA,EAA6B;AAC3E,EAAA,MAAM,CAAA,GAAI,OAAO,IAAI,CAAA;AACrB,EAAA,IAAI,MAAM,GAAA,IAAO,CAAA,KAAM,GAAA,IAAO,CAAA,KAAM,KAAK,OAAO,IAAA;AAChD,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAC,CAAA;AACxC,EAAA,IAAI,KAAA,KAAU,IAAI,OAAO,IAAA;AACzB,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,IAAA,GAAO,CAAA,EAAG,KAAK,CAAA;AAClC;AAGA,SAAS,MAAA,CAAO,QAAgB,CAAA,EAAmB;AACjD,EAAA,OAAO,IAAI,MAAA,CAAO,MAAA,KAAW,OAAO,CAAC,CAAA,KAAM,OAAO,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,IAAQ,OAAO,CAAC,CAAA,KAAM,QAAQ,MAAA,CAAO,CAAC,MAAM,IAAA,CAAA,EAAO,CAAA,EAAA;AACnH,EAAA,OAAO,CAAA;AACT;AAEO,SAAS,iBAAiB,KAAA,EAAgD;AAC/E,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,MAAM,GAAA,GAAM,CAAC,CAAA,KAAc;AAAE,IAAA,IAAI,CAAA,IAAK,CAAC,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,EAAG;AAAE,MAAA,IAAA,CAAK,IAAI,CAAC,CAAA;AAAG,MAAA,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,IAAE;AAAA,EAAE,CAAA;AAEnF,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,MAAM,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAClD,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAA,CAAK,IAAI,CAAA,EAAG;AACtC,IAAA,MAAM,MAAA,GAAS,KAAK,MAAM,CAAA;AAG1B,IAAA,KAAA,MAAW,CAAA,IAAK,MAAA,CAAO,QAAA,CAAS,uBAAuB,CAAA,EAAG;AACxD,MAAA,MAAM,MAAA,GAAS,OAAO,KAAA,CAAM,CAAA,CAAE,OAAQ,CAAA,CAAE,KAAA,GAAS,EAAE,CAAA,KAAM,cAAA;AACzD,MAAA,MAAM,CAAA,GAAI,OAAO,MAAA,EAAQ,CAAA,CAAE,QAAS,CAAA,CAAE,CAAC,EAAE,MAAM,CAAA;AAC/C,MAAA,IAAI,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,EAAK;AAAE,QAAA,IAAI,QAAQ,OAAA,GAAU,IAAA;AAAM,QAAA;AAAA,MAAS;AAC9D,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,MAAA,EAAQ,MAAA,EAAQ,CAAC,CAAA;AACvC,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,GAAA,CAAI,GAAG,CAAA;AAAA,WACpB,OAAA,GAAU,IAAA;AAAA,IACjB;AAGA,IAAA,KAAA,MAAW,CAAA,IAAK,MAAA,CAAO,QAAA,CAAS,gDAAgD,CAAA,EAAG;AACjF,MAAA,MAAM,CAAA,GAAI,CAAA,CAAE,KAAA,GAAS,CAAA,CAAE,CAAC,CAAA,CAAE,MAAA;AAC1B,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,MAAA,EAAQ,MAAA,EAAQ,CAAC,CAAA;AACvC,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,GAAA,CAAI,GAAG,CAAA;AAAA,WAAA,IAChB,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,EAAK,OAAA,GAAU,IAAA;AAAA,IACxC;AAAA,EACF;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,OAAA,EAAQ;AACjC;;;AChEA,SAAS,MAAM,MAAA,EAAwB;AACtC,EAAA,OAAO,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,eAAA,EAAiB,GAAG,CAAA;AACpD;AAEA,IAAM,OAAA,GAAU;AAAA,EACf,MAAA,EAAQ,EAAE,IAAA,EAAM,sBAAA,EAAwB,KAAK,eAAA,EAAgB;AAAA,EAC7D,MAAA,EAAQ,EAAE,IAAA,EAAM,sBAAA,EAAwB,KAAK,eAAA;AAC9C,CAAA;AAEA,IAAM,MAAA,GACL,2FAAA;AAGM,SAAS,cAAc,IAAA,EAA+B;AAC5D,EAAA,MAAM,CAAA,GAAI,KAAK,YAAA,IAAgB,IAAA;AAC/B,EAAA,MAAM,SAAS,IAAA,CAAK,QAAA,GAAW,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAA,GAAI,IAAA;AAExD,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,IAAkB,EAAC;AACxC,EAAA,MAAM,OAAA,GAAU;AAAA,IACf,CAAA,uFAAA,CAAA;AAAA,IACA,UACC,CAAA,SAAA,EAAY,MAAA,CAAO,IAAI,CAAA,4BAAA,EAA+B,OAAO,GAAG,CAAA,CAAA,CAAA;AAAA,IACjE,2BAA2B,CAAC,CAAA,OAAA,CAAA;AAAA,IAC5B,IAAA,CAAK,SAAA,IAAa,CAAA,oBAAA,EAAuB,CAAC,CAAA,OAAA,CAAA;AAAA,IAC1C,GAAG,OAAA,CAAQ,GAAA;AAAA,MACV,CAAC,QAAQ,CAAA,OAAA,EAAU,KAAA,CAAM,GAAG,CAAC,CAAA,OAAA,EAAU,CAAC,CAAA,SAAA,EAAY,GAAG,CAAA,MAAA;AAAA;AACxD,GACD,CACE,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA;AAGX,EAAA,MAAM,OAAA,GAAU,KAAK,QAAA,CACnB,GAAA;AAAA,IACA,CAAC,CAAA,KACA,CAAA,CAAA,EAAK,IAAA,CAAK,UAAU,CAAC,CAAC,CAAA,eAAA,EAAkB,IAAA,CAAK,UAAU,CAAA,EAAG,CAAC,CAAA,MAAA,EAAS,CAAC,EAAE,CAAC,CAAA,EAAA;AAAA,GAC1E,CACC,KAAK,IAAI,CAAA;AAEX,EAAA,MAAM,QAAA,GAAW;AAAA,IAChB,QAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAK,SAAA,IAAa,gBAAA;AAAA,IAClB,MAAA,IAAU,CAAA,gBAAA,EAAmB,MAAA,CAAO,IAAI,CAAA,CAAA;AAAA,IACxC,OAAA,CAAQ,SAAS,CAAA,IAAK;AAAA,GACvB,CACE,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA;AAGX,EAAA,MAAM,aAAA,GACL,OAAA,CAAQ,MAAA,GAAS,CAAA,GACd;AAAA,mBAAA,EAAwB,QAAQ,GAAA,CAAI,CAAC,GAAA,KAAQ,CAAA,EAAG,KAAK,SAAA,CAAU,GAAG,CAAC,CAAA,EAAA,EAAK,MAAM,GAAG,CAAC,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,CAAA,GAChG,EAAA;AAEJ,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,OAAO;;AAAA;AAAA,EAGzB,OAAO;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeP,aAAa;AAAA,uCAAA,EAC0B,QAAQ,CAAA;;AAAA;AAAA,CAAA;AAIjD;AAGO,SAAS,oBACf,IAAA,EACS;AACT,EAAA,MAAM,CAAA,GAAI,KAAK,YAAA,IAAgB,IAAA;AAC/B,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,GAAY,CAAA,QAAA,EAAW,CAAC,CAAA;AAAA,CAAA,GAAkB,EAAA;AAC9D,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,MAAM,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAgB1B;AAGO,SAAS,oBACf,IAAA,EACS;AACT,EAAA,MAAM,CAAA,GAAI,KAAK,YAAA,IAAgB,IAAA;AAC/B,EAAA,OAAO,GAAG,MAAM,CAAA;AAAA;AAAA,wBAAA,EAES,CAAC,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,eAAA,EAMV,KAAK,SAAA,CAAU,IAAA,CAAK,YAAA,IAAgB,EAAE,CAAC,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AASxD;AAGO,SAAS,iBACf,IAAA,EAGS;AACT,EAAA,MAAM,CAAA,GAAI,KAAK,YAAA,IAAgB,IAAA;AAC/B,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,GAAY,CAAA,QAAA,EAAW,CAAC,CAAA;AAAA,CAAA,GAAkB,EAAA;AAC9D,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,MAAM,CAAA;AAAA;;AAAA;AAAA,mCAAA,EAIW,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,CAAA;AAE9D","file":"manifest-entry.cjs","sourcesContent":["/**\n * Pure, React-free page scan: find which offering SLOTS a funnel's pages reference — the first\n * string-literal arg of `useOffering(...)` and the `offering` prop of `<Checkout>` / `<Upsell>`.\n * Ships via `@appfunnel-dev/sdk/manifest` so the BUILD, the EDITOR, and the AI all run the SAME\n * check. The result feeds `compileManifest({ offeringRefs })`, which flags references to slots the\n * funnel doesn't declare (unknown) or hasn't assigned a catalog charge key to (unassigned).\n *\n * Non-literal args (`useOffering(selectedId)`, `offering={expr}`) and the `useOfferings()` wildcard are\n * DYNAMIC — they can't be statically enumerated, so they're reported via `dynamic` and never turned\n * into a hard error (validation relaxes when a funnel is dynamic). Mirrors the mask-then-read\n * discipline the funnel.ts code-mods use, so a `useOffering('x')` inside a comment or string literal\n * can't false-positive.\n */\n\nexport interface OfferingRefScan {\n /** Concrete slot names referenced by LITERAL args, deduped, in first-seen order. */\n slots: string[]\n /** True if any ref is a non-literal arg or a `useOfferings()` wildcard (not statically enumerable). */\n dynamic: boolean\n}\n\n/** Blank comment + string/template CONTENTS (keep quote delimiters + length) so scans don't fire inside them. */\nfunction mask(src: string): string {\n const out = src.split('')\n const n = src.length\n let i = 0\n while (i < n) {\n const c = src[i]\n const d = src[i + 1]\n if (c === '/' && d === '/') {\n while (i < n && src[i] !== '\\n') { out[i] = ' '; i++ }\n } else if (c === '/' && d === '*') {\n out[i] = ' '; out[i + 1] = ' '; i += 2\n while (i < n) {\n if (src[i] === '*' && src[i + 1] === '/') { out[i] = ' '; out[i + 1] = ' '; i += 2; break }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n } else if (c === \"'\" || c === '\"' || c === '`') {\n i++\n while (i < n && src[i] !== c) {\n if (src[i] === '\\\\') { out[i] = ' '; i++; if (i < n && src[i] !== '\\n') out[i] = ' '; i++; continue }\n if (src[i] !== '\\n') out[i] = ' '\n i++\n }\n i++ // closing delimiter (kept)\n } else i++\n }\n return out.join('')\n}\n\n/** A quoted string literal starting at `open` (a quote char in the MASK). Value read from the ORIGINAL. */\nfunction literalAt(src: string, masked: string, open: number): string | null {\n const q = masked[open]\n if (q !== \"'\" && q !== '\"' && q !== '`') return null\n const close = masked.indexOf(q, open + 1)\n if (close === -1) return null\n return src.slice(open + 1, close)\n}\n\n/** Advance past whitespace in `masked` from `i`. */\nfunction skipWs(masked: string, i: number): number {\n while (i < masked.length && (masked[i] === ' ' || masked[i] === '\\t' || masked[i] === '\\n' || masked[i] === '\\r')) i++\n return i\n}\n\nexport function scanOfferingRefs(files: Record<string, string>): OfferingRefScan {\n const seen = new Set<string>()\n const order: string[] = []\n let dynamic = false\n const add = (v: string) => { if (v && !seen.has(v)) { seen.add(v); order.push(v) } }\n\n for (const [path, source] of Object.entries(files)) {\n if (!/\\.(tsx|jsx|ts|js)$/.test(path)) continue\n const masked = mask(source)\n\n // useOffering( … ) / useOfferings( … ) — positions on the MASK (a call inside a string won't match).\n for (const m of masked.matchAll(/\\buseOfferings?\\s*\\(/g)) {\n const plural = source.slice(m.index!, m.index! + 12) === 'useOfferings'\n const j = skipWs(masked, m.index! + m[0].length)\n if (masked[j] === ')') { if (plural) dynamic = true; continue } // useOfferings() = whole-catalog wildcard\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else dynamic = true // useOffering(variable) — can't resolve statically\n }\n\n // <Checkout offering=\"…\"> / <Upsell offering=\"…\"> — the `offering` attribute value.\n for (const m of masked.matchAll(/<(?:Checkout|Upsell)\\b[^>]*?\\boffering\\s*=\\s*/g)) {\n const j = m.index! + m[0].length\n const lit = literalAt(source, masked, j)\n if (lit !== null) add(lit)\n else if (masked[j] === '{') dynamic = true // offering={expr}\n }\n }\n return { slots: order, dynamic }\n}\n","/**\n * Source generators for the funnel build \"machinery\" authors no longer write. `funnel.ts` (the\n * spine: id, responses, offerings, checkout, and the page list + declarative routing) plus the\n * `pages/` folder are the ONLY authored source. The build (renderer/builder) and the CLI dev server\n * call these to synthesize:\n * - `mount.tsx` — code-split page loaders + `createFunnelTree`\n * - `entry.client.tsx` — hydrate the SSR'd markup from injected data\n * - `entry.server.tsx` — `ssr()` + the compiled `manifest`\n *\n * All string templates, zero runtime deps — this module lives in the pure `@appfunnel-dev/sdk/\n * manifest` entry so tooling imports it without the funnel runtime.\n */\n\nexport interface ScaffoldOptions {\n\t/** Page component keys = `pages/<key>.tsx` filenames (base flow pages AND `@variant` pages). */\n\tpageKeys: string[]\n\t/** Import prefix to reach the funnel root: `'./'` at the root (build), `'../../'` in a subdir (CLI dev). */\n\timportPrefix?: string\n\t/** Whether a `layout.tsx` exists to wrap the pages. */\n\thasLayout?: boolean\n\t/** Whether a `styles.css` exists (client entry imports it). */\n\thasStyles?: boolean\n\t/** Checkout provider from `config.checkout` — wires the driver; omit for none. */\n\tcheckout?: 'stripe' | 'paddle' | null\n\t/** Locales with a `messages/<locale>.json` catalog to bake into the mount (so `t()` resolves at render). */\n\tmessageLocales?: string[]\n\t/** Slot names the pages reference (from `scanOfferingRefs`), baked into the manifest for validation. */\n\tofferingRefs?: string[]\n}\n\n/** A safe JS identifier for a message-catalog import (`en` → `msg_en`, `pt-BR` → `msg_pt_BR`). */\nfunction msgId(locale: string): string {\n\treturn 'msg_' + locale.replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nconst DRIVERS = {\n\tstripe: { name: 'stripeCheckoutDriver', sub: 'driver-stripe' },\n\tpaddle: { name: 'paddleCheckoutDriver', sub: 'driver-paddle' },\n} as const\n\nconst HEADER =\n\t'// GENERATED by Appfunnel — do not edit. Built from funnel.ts + the pages/ folder.\\n'\n\n/** `mount.tsx` — the code-split loaders + tree, derived from `config.pages` and the `pages/` files. */\nexport function generateMount(opts: ScaffoldOptions): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst driver = opts.checkout ? DRIVERS[opts.checkout] : null\n\n\tconst locales = opts.messageLocales ?? []\n\tconst imports = [\n\t\t`import { createFunnelTree, type MountOpts, type RuntimePage } from '@appfunnel-dev/sdk'`,\n\t\tdriver &&\n\t\t\t`import { ${driver.name} } from '@appfunnel-dev/sdk/${driver.sub}'`,\n\t\t`import { config } from '${p}funnel'`,\n\t\topts.hasLayout && `import Layout from '${p}layout'`,\n\t\t...locales.map(\n\t\t\t(loc) => `import ${msgId(loc)} from '${p}messages/${loc}.json'`\n\t\t),\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n')\n\n\t// A static import() per page file so Vite emits one code-split chunk each.\n\tconst loaders = opts.pageKeys\n\t\t.map(\n\t\t\t(k) =>\n\t\t\t\t`\\t${JSON.stringify(k)}: () => import(${JSON.stringify(`${p}pages/${k}`)}),`\n\t\t)\n\t\t.join('\\n')\n\n\tconst treeArgs = [\n\t\t'config',\n\t\t'pages',\n\t\topts.hasLayout && 'layout: Layout',\n\t\tdriver && `checkoutDriver: ${driver.name}`,\n\t\tlocales.length > 0 && 'messages',\n\t]\n\t\t.filter(Boolean)\n\t\t.join(', ')\n\n\t// The baked message catalog: locale → messages/<locale>.json (so `t()` resolves at render).\n\tconst messagesConst =\n\t\tlocales.length > 0\n\t\t\t? `\\nconst messages = { ${locales.map((loc) => `${JSON.stringify(loc)}: ${msgId(loc)}`).join(', ')} }\\n`\n\t\t\t: ''\n\n\treturn `${HEADER}${imports}\n\nconst loaders: Record<string, () => Promise<unknown>> = {\n${loaders}\n}\n\n// Flow pages come from funnel.ts (order + meta + routing). A \\`@variant\\` page MAY also be declared\n// there (with its \\`@\\` key) to carry its OWN optional routing (\\`next\\`) — it still collapses out of\n// the linear flow by the \\`@\\` marker, but its meta rides along here. Any page file NOT declared is an\n// off-flow \\`@variant\\` with no own routing, appended with empty meta so it's still loadable.\nconst decls = config.pages ?? []\nconst flowKeys = new Set(decls.map((d) => d.key))\nexport const pages: RuntimePage[] = [\n\t...decls.map((d) => ({ key: d.key, meta: d, load: loaders[d.key] })),\n\t...Object.keys(loaders)\n\t\t.filter((k) => !flowKeys.has(k))\n\t\t.map((k) => ({ key: k, meta: {}, load: loaders[k] })),\n] as RuntimePage[]\n${messagesConst}\nexport const tree = createFunnelTree({ ${treeArgs} })\n\nexport type { MountOpts }\n`\n}\n\n/** `entry.client.tsx` — hydrate the SSR'd markup from the injected `#__af_data`. */\nexport function generateEntryClient(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'>\n): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { hydrateRoot } from 'react-dom/client'\nimport { tree, type MountOpts } from './mount'\n\nconst dataEl = document.getElementById('__af_data')\nconst data = (dataEl?.textContent ? JSON.parse(dataEl.textContent) : {}) as MountOpts & {\n\tinitialKey?: string | null\n}\nconst root = document.getElementById('root')\n// FLASH-FREE HYDRATION (SSR-isolation Option B): always DEFER sessionValues on the client. The\n// markup we hydrate against is DEFAULT-state (build-time prerendered, or an SSR render with empty\n// sessionValues), so seeding the store with defaults for the first render is a byte-clean match —\n// no hydration mismatch. The visitor's saved answers (data.sessionValues, present on the \"tail\"\n// path) are then applied post-hydration, popping into already-visible controls with no empty\n// flash. On the cold path sessionValues is absent, so deferring is a no-op (identical output).\nif (root) hydrateRoot(root, tree({ ...data, initialKey: data.initialKey ?? undefined, deferSessionValues: true }))\n`\n}\n\n/** `entry.server.tsx` — `ssr(opts)` HTML + the compiled `manifest` (read by the build). */\nexport function generateEntryServer(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'offeringRefs'>\n): string {\n\tconst p = opts.importPrefix ?? './'\n\treturn `${HEADER}import { renderToReadableStream } from 'react-dom/server.browser'\nimport { compileManifest } from '@appfunnel-dev/sdk'\nimport { config } from '${p}funnel'\nimport { tree, pages, type MountOpts } from './mount'\n\nexport const manifest = compileManifest({\n\tfunnel: config,\n\tpages: pages.map((pg) => ({ key: pg.key, meta: pg.meta })),\n\tofferingRefs: ${JSON.stringify(opts.offeringRefs ?? [])},\n})\n\nexport async function ssr(opts: MountOpts): Promise<string> {\n\tconst stream = await renderToReadableStream(tree(opts))\n\tawait stream.allReady\n\treturn await new Response(stream).text()\n}\n`\n}\n\n/** Client-only dev entry (createRoot, no SSR) — for the CLI `dev` server. */\nexport function generateDevEntry(\n\topts: Pick<ScaffoldOptions, 'importPrefix' | 'hasStyles'> & {\n\t\tdata: unknown\n\t}\n): string {\n\tconst p = opts.importPrefix ?? './'\n\tconst styles = opts.hasStyles ? `import '${p}styles.css'\\n` : ''\n\treturn `${HEADER}${styles}import { createRoot } from 'react-dom/client'\nimport { tree } from './mount'\n\nconst el = document.getElementById('root')\nif (el) createRoot(el).render(tree(${JSON.stringify(opts.data)}))\n`\n}\n"]}
|