@funnelsgrove/cli 0.1.15 → 0.1.18

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.
Files changed (293) hide show
  1. package/contracts/step-contract-v2.json +733 -0
  2. package/dist/analyticsOutput.d.ts +30 -15
  3. package/dist/analyticsOutput.js +45 -28
  4. package/dist/apiClient.d.ts +85 -0
  5. package/dist/apiClient.js +225 -8
  6. package/dist/cli.d.ts +149 -1
  7. package/dist/cli.js +1232 -71
  8. package/dist/diagnosticOutput.d.ts +42 -0
  9. package/dist/diagnosticOutput.js +83 -0
  10. package/dist/docsOutput.d.ts +19 -0
  11. package/dist/docsOutput.js +73 -0
  12. package/dist/docsTransaction.d.ts +81 -0
  13. package/dist/docsTransaction.js +1755 -0
  14. package/dist/funnelValidation.d.ts +14 -0
  15. package/dist/funnelValidation.js +297 -0
  16. package/dist/localSync.d.ts +79 -0
  17. package/dist/localSync.js +400 -29
  18. package/dist/projectValidator.d.ts +27 -0
  19. package/dist/projectValidator.js +208 -0
  20. package/dist/reskin.js +27 -1
  21. package/dist/stepContractMigration.d.ts +71 -0
  22. package/dist/stepContractMigration.js +1323 -0
  23. package/dist/templateDocs.js +8 -25
  24. package/docs-release-history.json +66 -0
  25. package/legacy-docs-catalog.json +1012 -0
  26. package/package.json +13 -4
  27. package/template_docs/.funnelsgrove-docs.json +144 -0
  28. package/template_docs/AGENTS.md +30 -58
  29. package/template_docs/CLAUDE.md +0 -2
  30. package/template_docs/docs/funnelsgrove/START-HERE.md +73 -0
  31. package/template_docs/docs/funnelsgrove/contracts/analytics-events.md +59 -0
  32. package/template_docs/docs/funnelsgrove/contracts/content-answers.md +44 -0
  33. package/template_docs/docs/funnelsgrove/contracts/flow-routing.md +41 -0
  34. package/template_docs/docs/funnelsgrove/contracts/payments.md +42 -0
  35. package/template_docs/docs/funnelsgrove/contracts/step-metadata.md +57 -0
  36. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
  37. package/template_docs/docs/funnelsgrove/qa/analytics.md +43 -0
  38. package/template_docs/docs/funnelsgrove/qa/local.md +42 -0
  39. package/template_docs/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
  40. package/template_docs/docs/funnelsgrove/qa/publish.md +32 -0
  41. package/template_docs/docs/funnelsgrove/recipes/add-experiment.md +34 -0
  42. package/template_docs/docs/funnelsgrove/recipes/add-step.md +33 -0
  43. package/template_docs/docs/funnelsgrove/recipes/edit-step.md +31 -0
  44. package/template_docs/docs/funnelsgrove/steps/INDEX.md +26 -0
  45. package/template_docs/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
  46. package/template_docs/docs/funnelsgrove/steps/checkout.md +120 -0
  47. package/template_docs/docs/funnelsgrove/steps/form_input.md +115 -0
  48. package/template_docs/docs/funnelsgrove/steps/intro_hero.md +110 -0
  49. package/template_docs/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
  50. package/template_docs/docs/funnelsgrove/steps/paywall_offer.md +115 -0
  51. package/template_docs/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
  52. package/template_docs/docs/funnelsgrove/steps/purchase_completed.md +117 -0
  53. package/template_docs/docs/funnelsgrove/steps/single_step_choice.md +122 -0
  54. package/template_docs/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
  55. package/template_docs/docs/funnelsgrove/steps/social_proof.md +110 -0
  56. package/template_docs/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
  57. package/template_docs/docs/funnelsgrove/steps/subscription_management.md +112 -0
  58. package/template_docs/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
  59. package/template_docs/docs/funnelsgrove/steps/upsell_offer.md +112 -0
  60. package/template_docs/docs/funnelsgrove/steps/value_prop_story.md +110 -0
  61. package/template_docs/funnel-docs.config.json +43 -0
  62. package/template_scaffold/.env.example +41 -0
  63. package/template_scaffold/.funnelsgrove-contract-tools.json +19 -0
  64. package/template_scaffold/.funnelsgrove-docs.json +144 -0
  65. package/template_scaffold/.funnelsgrove-scaffold.json +1078 -0
  66. package/template_scaffold/AGENTS.md +42 -0
  67. package/template_scaffold/CLAUDE.md +1 -0
  68. package/template_scaffold/PLAN.md +81 -0
  69. package/template_scaffold/PRODUCT_SENSE.md +28 -0
  70. package/template_scaffold/context/build-context.md +34 -0
  71. package/template_scaffold/docs/AGENT_FUNNEL_IMPLEMENTATION_SPEC.md +5 -0
  72. package/template_scaffold/docs/ARCHITECTURE.md +5 -0
  73. package/template_scaffold/docs/FLOW_CONFIG_AND_ROUTING.md +5 -0
  74. package/template_scaffold/docs/PAYWALL_AND_PAYMENTS.md +5 -0
  75. package/template_scaffold/docs/README.md +5 -0
  76. package/template_scaffold/docs/funnelsgrove/START-HERE.md +73 -0
  77. package/template_scaffold/docs/funnelsgrove/contracts/analytics-events.md +59 -0
  78. package/template_scaffold/docs/funnelsgrove/contracts/content-answers.md +44 -0
  79. package/template_scaffold/docs/funnelsgrove/contracts/flow-routing.md +41 -0
  80. package/template_scaffold/docs/funnelsgrove/contracts/payments.md +42 -0
  81. package/template_scaffold/docs/funnelsgrove/contracts/step-metadata.md +57 -0
  82. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
  83. package/template_scaffold/docs/funnelsgrove/qa/analytics.md +43 -0
  84. package/template_scaffold/docs/funnelsgrove/qa/local.md +42 -0
  85. package/template_scaffold/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
  86. package/template_scaffold/docs/funnelsgrove/qa/publish.md +32 -0
  87. package/template_scaffold/docs/funnelsgrove/recipes/add-experiment.md +34 -0
  88. package/template_scaffold/docs/funnelsgrove/recipes/add-step.md +33 -0
  89. package/template_scaffold/docs/funnelsgrove/recipes/edit-step.md +31 -0
  90. package/template_scaffold/docs/funnelsgrove/steps/INDEX.md +26 -0
  91. package/template_scaffold/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
  92. package/template_scaffold/docs/funnelsgrove/steps/checkout.md +120 -0
  93. package/template_scaffold/docs/funnelsgrove/steps/form_input.md +115 -0
  94. package/template_scaffold/docs/funnelsgrove/steps/intro_hero.md +110 -0
  95. package/template_scaffold/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
  96. package/template_scaffold/docs/funnelsgrove/steps/paywall_offer.md +115 -0
  97. package/template_scaffold/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
  98. package/template_scaffold/docs/funnelsgrove/steps/purchase_completed.md +117 -0
  99. package/template_scaffold/docs/funnelsgrove/steps/single_step_choice.md +122 -0
  100. package/template_scaffold/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
  101. package/template_scaffold/docs/funnelsgrove/steps/social_proof.md +110 -0
  102. package/template_scaffold/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
  103. package/template_scaffold/docs/funnelsgrove/steps/subscription_management.md +112 -0
  104. package/template_scaffold/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
  105. package/template_scaffold/docs/funnelsgrove/steps/upsell_offer.md +112 -0
  106. package/template_scaffold/docs/funnelsgrove/steps/value_prop_story.md +110 -0
  107. package/template_scaffold/docs/references/funnel-sdk-user-answers.md +5 -0
  108. package/template_scaffold/eslint.config.mjs +16 -0
  109. package/template_scaffold/funnel-agent-docs.test.ts +699 -0
  110. package/template_scaffold/funnel-docs.config.json +43 -0
  111. package/template_scaffold/funnel.config.json +5 -0
  112. package/template_scaffold/gitignore.template +5 -0
  113. package/template_scaffold/next-env.d.ts +6 -0
  114. package/template_scaffold/next.config.ts +53 -0
  115. package/template_scaffold/package-lock.json +7737 -0
  116. package/template_scaffold/package.json +37 -0
  117. package/template_scaffold/package.test.ts +23 -0
  118. package/template_scaffold/public/claimbee/bee.png +0 -0
  119. package/template_scaffold/public/claimbee/device-iphone.png +0 -0
  120. package/template_scaffold/public/claimbee/device-macbook.png +0 -0
  121. package/template_scaffold/public/claimbee/device-none.png +0 -0
  122. package/template_scaffold/public/claimbee/device-watch.png +0 -0
  123. package/template_scaffold/public/claimbee/glow.svg +3 -0
  124. package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
  125. package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
  126. package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
  127. package/template_scaffold/public/claimbee/testimonial-star.svg +3 -0
  128. package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
  129. package/template_scaffold/public/paywall/claim-history.png +0 -0
  130. package/template_scaffold/public/paywall/clear-progress.png +0 -0
  131. package/template_scaffold/public/paywall/hero.png +0 -0
  132. package/template_scaffold/public/paywall/money-alerts.png +0 -0
  133. package/template_scaffold/public/paywall/money-back-badge.png +0 -0
  134. package/template_scaffold/public/paywall/paywall-image.png +0 -0
  135. package/template_scaffold/public/paywall/r1.png +0 -0
  136. package/template_scaffold/public/paywall/r2.png +0 -0
  137. package/template_scaffold/public/paywall/r3.png +0 -0
  138. package/template_scaffold/public/paywall/r4.png +0 -0
  139. package/template_scaffold/public/paywall/r5.png +0 -0
  140. package/template_scaffold/public/paywall/r6.png +0 -0
  141. package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
  142. package/template_scaffold/public/please/bee.png +0 -0
  143. package/template_scaffold/public/please/device-iphone.png +0 -0
  144. package/template_scaffold/public/please/device-macbook.png +0 -0
  145. package/template_scaffold/public/please/device-none.png +0 -0
  146. package/template_scaffold/public/please/device-watch.png +0 -0
  147. package/template_scaffold/public/please/glow.svg +3 -0
  148. package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
  149. package/template_scaffold/public/please/steps-sprite-default.png +0 -0
  150. package/template_scaffold/public/please/testimonial-avatar.png +0 -0
  151. package/template_scaffold/public/please/testimonial-star.svg +3 -0
  152. package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
  153. package/template_scaffold/src/app/[stepId]/page.test.tsx +59 -0
  154. package/template_scaffold/src/app/[stepId]/page.tsx +62 -0
  155. package/template_scaffold/src/app/globals.css +49 -0
  156. package/template_scaffold/src/app/layout.test.ts +26 -0
  157. package/template_scaffold/src/app/layout.tsx +61 -0
  158. package/template_scaffold/src/app/page.tsx +8 -0
  159. package/template_scaffold/src/app/sdk/page.tsx +673 -0
  160. package/template_scaffold/src/app/subscription-dashboard/page.tsx +274 -0
  161. package/template_scaffold/src/assets/claimbee/bee.png +0 -0
  162. package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
  163. package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
  164. package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
  165. package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
  166. package/template_scaffold/src/assets/claimbee/glow.svg +3 -0
  167. package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
  168. package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
  169. package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
  170. package/template_scaffold/src/assets/claimbee/testimonial-star.svg +3 -0
  171. package/template_scaffold/src/components/FunnelEditorPanel.tsx +194 -0
  172. package/template_scaffold/src/components/FunnelFlow.test.tsx +17 -0
  173. package/template_scaffold/src/components/FunnelFlow.tsx +114 -0
  174. package/template_scaffold/src/components/FunnelStepImage.test.ts +15 -0
  175. package/template_scaffold/src/components/FunnelStepImage.tsx +28 -0
  176. package/template_scaffold/src/components/step/FunnelStepHeader.tsx +35 -0
  177. package/template_scaffold/src/config/billing.plans.test.ts +143 -0
  178. package/template_scaffold/src/config/billing.plans.ts +69 -0
  179. package/template_scaffold/src/config/billing.test.plans.ts +32 -0
  180. package/template_scaffold/src/config/experiments.generated.ts +4 -0
  181. package/template_scaffold/src/config/experiments.test.ts +12 -0
  182. package/template_scaffold/src/config/experiments.ts +9 -0
  183. package/template_scaffold/src/config/funnel.config.ts +12 -0
  184. package/template_scaffold/src/config/funnel.manifest.test.ts +166 -0
  185. package/template_scaffold/src/config/funnel.manifest.ts +139 -0
  186. package/template_scaffold/src/config/offer-sets.generated.ts +4 -0
  187. package/template_scaffold/src/contract/funnel-project.validation.test.ts +80 -0
  188. package/template_scaffold/src/contract/funnel-project.validation.ts +44 -0
  189. package/template_scaffold/src/contract/funnel-validator.vite.config.ts +11 -0
  190. package/template_scaffold/src/contract/golden/cancellation_offer.fixture.ts +29 -0
  191. package/template_scaffold/src/contract/golden/checkout.fixture.ts +37 -0
  192. package/template_scaffold/src/contract/golden/form_input.fixture.ts +28 -0
  193. package/template_scaffold/src/contract/golden/golden-fixtures.test.ts +190 -0
  194. package/template_scaffold/src/contract/golden/intro_hero.fixture.ts +27 -0
  195. package/template_scaffold/src/contract/golden/multi_select_choice.fixture.ts +37 -0
  196. package/template_scaffold/src/contract/golden/paywall_offer.fixture.ts +30 -0
  197. package/template_scaffold/src/contract/golden/progress_interstitial.fixture.ts +28 -0
  198. package/template_scaffold/src/contract/golden/purchase_completed.fixture.ts +33 -0
  199. package/template_scaffold/src/contract/golden/single_step_choice.fixture.ts +36 -0
  200. package/template_scaffold/src/contract/golden/single_step_choice_emoji.fixture.ts +36 -0
  201. package/template_scaffold/src/contract/golden/social_proof.fixture.ts +27 -0
  202. package/template_scaffold/src/contract/golden/subscription_handoff.fixture.ts +30 -0
  203. package/template_scaffold/src/contract/golden/subscription_management.fixture.ts +29 -0
  204. package/template_scaffold/src/contract/golden/summary_confirmation.fixture.ts +27 -0
  205. package/template_scaffold/src/contract/golden/upsell_offer.fixture.ts +29 -0
  206. package/template_scaffold/src/contract/golden/value_prop_story.fixture.ts +27 -0
  207. package/template_scaffold/src/contract/validate-funnel.cli.test.ts +180 -0
  208. package/template_scaffold/src/contract/validate-funnel.cli.ts +199 -0
  209. package/template_scaffold/src/runtime/checkout-runtime-config.test.ts +60 -0
  210. package/template_scaffold/src/runtime/checkout-runtime-config.ts +26 -0
  211. package/template_scaffold/src/runtime/funnel-runtime.test.ts +25 -0
  212. package/template_scaffold/src/runtime/funnel-runtime.ts +118 -0
  213. package/template_scaffold/src/runtime/step-content-context.test.ts +38 -0
  214. package/template_scaffold/src/runtime/step-content-context.ts +65 -0
  215. package/template_scaffold/src/runtime/step-registry.ts +66 -0
  216. package/template_scaffold/src/runtime/step-variable-values.ts +31 -0
  217. package/template_scaffold/src/runtime/use-funnel-flow-controller.integration.test.ts +203 -0
  218. package/template_scaffold/src/runtime/use-funnel-flow-controller.test.ts +76 -0
  219. package/template_scaffold/src/runtime/use-funnel-flow-controller.ts +53 -0
  220. package/template_scaffold/src/steps/content/email-capture.content.ts +29 -0
  221. package/template_scaffold/src/steps/content/manage-subscription.content.ts +62 -0
  222. package/template_scaffold/src/steps/content/paywall.content.ts +191 -0
  223. package/template_scaffold/src/steps/content/step-01.content.ts +23 -0
  224. package/template_scaffold/src/steps/content/step-02.content.ts +77 -0
  225. package/template_scaffold/src/steps/content/step-03.content.ts +40 -0
  226. package/template_scaffold/src/steps/content/step-04.content.ts +49 -0
  227. package/template_scaffold/src/steps/content/step-32-paywall.content.ts +352 -0
  228. package/template_scaffold/src/steps/content/subscription-started.content.ts +68 -0
  229. package/template_scaffold/src/steps/editor/email-capture.editor.ts +58 -0
  230. package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +179 -0
  231. package/template_scaffold/src/steps/editor/paywall.editor.ts +232 -0
  232. package/template_scaffold/src/steps/editor/step-01.editor.ts +22 -0
  233. package/template_scaffold/src/steps/editor/step-02.editor.ts +41 -0
  234. package/template_scaffold/src/steps/editor/step-03.editor.ts +58 -0
  235. package/template_scaffold/src/steps/editor/step-04.editor.ts +98 -0
  236. package/template_scaffold/src/steps/editor/step-32-paywall.editor.ts +478 -0
  237. package/template_scaffold/src/steps/editor/subscription-started.editor.ts +131 -0
  238. package/template_scaffold/src/steps/index.ts +25 -0
  239. package/template_scaffold/src/steps/paywall-checkout-options.test.ts +104 -0
  240. package/template_scaffold/src/steps/paywall-checkout-options.ts +130 -0
  241. package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +12 -0
  242. package/template_scaffold/src/steps/pricing/paywall.pricing.ts +12 -0
  243. package/template_scaffold/src/steps/pricing/shared-plan-order.ts +17 -0
  244. package/template_scaffold/src/steps/pricing/upsell-form.pricing.ts +16 -0
  245. package/template_scaffold/src/steps/shared-header-contract.test.ts +27 -0
  246. package/template_scaffold/src/steps/step-01.test.ts +38 -0
  247. package/template_scaffold/src/steps/step-01.tsx +108 -0
  248. package/template_scaffold/src/steps/step-02.test.ts +43 -0
  249. package/template_scaffold/src/steps/step-02.tsx +140 -0
  250. package/template_scaffold/src/steps/step-03.test.ts +15 -0
  251. package/template_scaffold/src/steps/step-03.tsx +262 -0
  252. package/template_scaffold/src/steps/step-04.test.ts +32 -0
  253. package/template_scaffold/src/steps/step-04.tsx +673 -0
  254. package/template_scaffold/src/steps/step-31-email-capture.test.ts +22 -0
  255. package/template_scaffold/src/steps/step-31-email-capture.tsx +195 -0
  256. package/template_scaffold/src/steps/step-32-paywall.test.ts +770 -0
  257. package/template_scaffold/src/steps/step-32-paywall.tsx +2253 -0
  258. package/template_scaffold/src/steps/step-32b-paywall-b.tsx +35 -0
  259. package/template_scaffold/src/steps/step-33-subscription-started.test.ts +26 -0
  260. package/template_scaffold/src/steps/step-33-subscription-started.tsx +86 -0
  261. package/template_scaffold/src/steps/step-34-upsell-form.test.ts +27 -0
  262. package/template_scaffold/src/steps/step-35-manage-subscription.tsx +46 -0
  263. package/template_scaffold/src/steps/step-content.registry.ts +45 -0
  264. package/template_scaffold/src/steps/step-image-migration.test.ts +20 -0
  265. package/template_scaffold/src/steps/styles/index.css +2 -0
  266. package/template_scaffold/src/steps/styles/shared/final-flow-shared.css +55 -0
  267. package/template_scaffold/src/steps/styles/shared/funnel-base.css +543 -0
  268. package/template_scaffold/src/steps/styles/shared/info-shared.css +139 -0
  269. package/template_scaffold/src/steps/styles/shared/progress-shared.css +117 -0
  270. package/template_scaffold/src/steps/styles/shared/question-shared.css +76 -0
  271. package/template_scaffold/src/steps/styles/shared/responsive-container.css +176 -0
  272. package/template_scaffold/src/steps/types.ts +7 -0
  273. package/template_scaffold/src/steps/use-claimbee-step-content.ts +15 -0
  274. package/template_scaffold/src/theme/theme.ts +81 -0
  275. package/template_scaffold/tsconfig.json +38 -0
  276. package/template_scaffold/vitest.config.ts +32 -0
  277. package/template_validation/.funnelsgrove-contract-tools.json +19 -0
  278. package/template_validation/src/contract/funnel-project.validation.ts +44 -0
  279. package/template_validation/src/contract/funnel-validator.vite.config.ts +11 -0
  280. package/template_validation/src/contract/validate-funnel.cli.ts +199 -0
  281. package/template_docs/docs/ab-experiments.md +0 -111
  282. package/template_docs/docs/analytics.md +0 -65
  283. package/template_docs/docs/editing-flow.md +0 -136
  284. package/template_docs/docs/editing-step.md +0 -125
  285. package/template_docs/docs/editor-and-content.md +0 -58
  286. package/template_docs/docs/funnel-runtime-architecture.md +0 -125
  287. package/template_docs/docs/meta-pixel-conversions-api.md +0 -50
  288. package/template_docs/docs/payment-plans-and-discounts.md +0 -106
  289. package/template_docs/docs/publishing-and-versioning.md +0 -117
  290. package/template_docs/docs/qa-checklist.md +0 -80
  291. package/template_docs/docs/sdk-api-endpoints.md +0 -78
  292. package/template_docs/docs/step-ui-guidelines.md +0 -110
  293. package/template_docs/docs/theme.md +0 -35
@@ -0,0 +1,14 @@
1
+ import { type FunnelValidationResult } from './diagnosticOutput.js';
2
+ import { type LegacyDocsCatalogEntry } from './docsTransaction.js';
3
+ import { type RunProjectValidatorOptions } from './projectValidator.js';
4
+ type ProjectValidator = (options: Pick<RunProjectValidatorOptions, 'projectDir'>) => Promise<FunnelValidationResult>;
5
+ export type ValidateFunnelOptions = {
6
+ projectDir: string;
7
+ bundledDocsDir: string;
8
+ bundledValidationDir: string;
9
+ bundledContractPath: string;
10
+ legacyCatalog?: readonly LegacyDocsCatalogEntry[];
11
+ runProjectValidator?: ProjectValidator;
12
+ };
13
+ export declare const validateFunnel: (options: ValidateFunnelOptions) => Promise<FunnelValidationResult>;
14
+ export {};
@@ -0,0 +1,297 @@
1
+ import { lstat, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import ts from 'typescript';
4
+ import { createValidationDiagnostic, isValidationDiagnostic, } from './diagnosticOutput.js';
5
+ import { inspectTemplateDocs, loadBundledDocs, planTemplateDocsUpdate, } from './docsTransaction.js';
6
+ import { PROJECT_VALIDATOR_CONTRACT_VERSION, runProjectValidator as runCheckedInProjectValidator, } from './projectValidator.js';
7
+ const MANIFEST_PATH = 'src/config/funnel.manifest.ts';
8
+ const PROJECT_VALIDATOR_PATH = 'src/contract/funnel-project.validation.ts';
9
+ const VALIDATOR_VITE_CONFIG_PATH = 'src/contract/funnel-validator.vite.config.ts';
10
+ const RUNNER_PATH = 'src/contract/validate-funnel.cli.ts';
11
+ const MANAGED_VALIDATOR_PATHS = [
12
+ PROJECT_VALIDATOR_PATH,
13
+ VALIDATOR_VITE_CONFIG_PATH,
14
+ RUNNER_PATH,
15
+ ];
16
+ const RUNNER_SCRIPT = 'vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts';
17
+ const MIGRATION_COMMAND = 'fgrove migrate step-contract --to 3';
18
+ const resultWith = (diagnostics, exitCode, contractVersion = PROJECT_VALIDATOR_CONTRACT_VERSION) => ({
19
+ schemaVersion: 1,
20
+ contractVersion,
21
+ valid: false,
22
+ diagnostics,
23
+ exitCode,
24
+ });
25
+ const versionDiagnostic = (code, received, file = MANIFEST_PATH) => createValidationDiagnostic({
26
+ code,
27
+ file,
28
+ expected: `step contract version ${PROJECT_VALIDATOR_CONTRACT_VERSION} with the managed validator runner`,
29
+ received,
30
+ reason: code === 'FG-VERSION-001'
31
+ ? 'The project uses a step contract newer than this CLI supports.'
32
+ : `The project is not a complete version-${PROJECT_VALIDATOR_CONTRACT_VERSION} authoring checkout.`,
33
+ guide: 'docs/funnelsgrove/migrations/step-contract-v3.md',
34
+ repair: `Run ${MIGRATION_COMMAND}, then rerun fgrove validate.`,
35
+ });
36
+ const docsDiagnostic = (code, received, file) => createValidationDiagnostic({
37
+ code,
38
+ file,
39
+ expected: 'the current packaged contract and managed documentation bundle',
40
+ received,
41
+ reason: code === 'FG-DOC-002'
42
+ ? 'Managed documentation conflicts with local content.'
43
+ : 'Managed contract documentation is missing, stale, or inconsistent.',
44
+ guide: 'docs/funnelsgrove/START-HERE.md',
45
+ repair: code === 'FG-DOC-002'
46
+ ? 'Resolve the managed documentation conflict explicitly before validation.'
47
+ : 'Run fgrove docs to refresh managed documentation before validation.',
48
+ });
49
+ const clientDiagnostic = (received) => createValidationDiagnostic({
50
+ code: 'FG-CLIENT-001',
51
+ file: RUNNER_PATH,
52
+ expected: 'a complete schemaVersion 1 project validator result',
53
+ received,
54
+ reason: 'The local project validator could not provide a trustworthy result.',
55
+ guide: 'docs/funnelsgrove/qa/local.md',
56
+ repair: 'Restore the managed validator runner and rerun fgrove validate.',
57
+ });
58
+ const unwrapExpression = (input) => {
59
+ let expression = input;
60
+ while (ts.isParenthesizedExpression(expression)
61
+ || ts.isAsExpression(expression)
62
+ || ts.isSatisfiesExpression(expression)) {
63
+ expression = expression.expression;
64
+ }
65
+ return expression;
66
+ };
67
+ const importedCurrentVersionBindings = (sourceFile) => {
68
+ const bindings = new Set();
69
+ for (const statement of sourceFile.statements) {
70
+ if (!ts.isImportDeclaration(statement)
71
+ || !ts.isStringLiteral(statement.moduleSpecifier)
72
+ || statement.moduleSpecifier.text !== '@funnelsgrove/runtime'
73
+ || !statement.importClause?.namedBindings
74
+ || !ts.isNamedImports(statement.importClause.namedBindings))
75
+ continue;
76
+ for (const element of statement.importClause.namedBindings.elements) {
77
+ if ((element.propertyName?.text ?? element.name.text) === 'CURRENT_STEP_CONTRACT_VERSION') {
78
+ bindings.add(element.name.text);
79
+ }
80
+ }
81
+ }
82
+ return bindings;
83
+ };
84
+ const parseAuthoredVersion = (source) => {
85
+ const sourceFile = ts.createSourceFile(MANIFEST_PATH, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
86
+ const parseDiagnostics = sourceFile.parseDiagnostics;
87
+ if (parseDiagnostics?.length)
88
+ return { version: null, received: 'malformed authored manifest' };
89
+ const declarations = [];
90
+ for (const statement of sourceFile.statements) {
91
+ if (!ts.isVariableStatement(statement))
92
+ continue;
93
+ for (const declaration of statement.declarationList.declarations) {
94
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === 'rawFunnelManifest') {
95
+ declarations.push(declaration);
96
+ }
97
+ }
98
+ }
99
+ if (declarations.length !== 1 || !declarations[0].initializer) {
100
+ return { version: null, received: 'missing unique rawFunnelManifest object' };
101
+ }
102
+ const initializer = unwrapExpression(declarations[0].initializer);
103
+ if (!ts.isObjectLiteralExpression(initializer)) {
104
+ return { version: null, received: 'dynamic rawFunnelManifest value' };
105
+ }
106
+ if (initializer.properties.some((property) => {
107
+ if (ts.isSpreadAssignment(property))
108
+ return true;
109
+ const name = 'name' in property ? property.name : undefined;
110
+ return name !== undefined && ts.isComputedPropertyName(name);
111
+ })) {
112
+ return { version: null, received: 'dynamic root manifest property' };
113
+ }
114
+ const versionProperties = initializer.properties.filter((property) => {
115
+ const name = 'name' in property ? property.name : undefined;
116
+ return name !== undefined
117
+ && (ts.isIdentifier(name) || ts.isStringLiteral(name))
118
+ && name.text === 'stepContractVersion';
119
+ });
120
+ if (versionProperties.length !== 1 || !ts.isPropertyAssignment(versionProperties[0])) {
121
+ return { version: null, received: 'missing unique stepContractVersion property' };
122
+ }
123
+ const expression = unwrapExpression(versionProperties[0].initializer);
124
+ if (ts.isNumericLiteral(expression)) {
125
+ const version = Number(expression.text);
126
+ return Number.isSafeInteger(version)
127
+ ? { version, received: version }
128
+ : { version: null, received: 'invalid stepContractVersion literal' };
129
+ }
130
+ if (ts.isIdentifier(expression)
131
+ && importedCurrentVersionBindings(sourceFile).has(expression.text)) {
132
+ return {
133
+ version: PROJECT_VALIDATOR_CONTRACT_VERSION,
134
+ received: PROJECT_VALIDATOR_CONTRACT_VERSION,
135
+ };
136
+ }
137
+ return { version: null, received: 'dynamic stepContractVersion expression' };
138
+ };
139
+ const inspectAuthoredVersion = async (projectDir) => {
140
+ try {
141
+ return parseAuthoredVersion(await readFile(path.join(projectDir, MANIFEST_PATH), 'utf8'));
142
+ }
143
+ catch {
144
+ return { version: null, received: 'missing authored manifest' };
145
+ }
146
+ };
147
+ const missingValidatorReceived = (file) => file === RUNNER_PATH
148
+ ? 'missing checked-in validate:funnel runner'
149
+ : 'missing checked-in managed validator file';
150
+ const inspectCheckedInValidator = async (projectDir, bundledValidationDir) => {
151
+ let packageValue;
152
+ try {
153
+ packageValue = JSON.parse(await readFile(path.join(projectDir, 'package.json'), 'utf8'));
154
+ }
155
+ catch {
156
+ return { valid: false, file: RUNNER_PATH, received: 'missing checked-in validate:funnel runner' };
157
+ }
158
+ if (!packageValue || typeof packageValue !== 'object' || Array.isArray(packageValue)) {
159
+ return { valid: false, file: RUNNER_PATH, received: 'missing checked-in validate:funnel runner' };
160
+ }
161
+ const scripts = packageValue.scripts;
162
+ if (!scripts
163
+ || typeof scripts !== 'object'
164
+ || Array.isArray(scripts)
165
+ || scripts['validate:funnel'] !== RUNNER_SCRIPT) {
166
+ return { valid: false, file: RUNNER_PATH, received: 'missing checked-in validate:funnel runner' };
167
+ }
168
+ for (const file of MANAGED_VALIDATOR_PATHS) {
169
+ const localPath = path.join(projectDir, ...file.split('/'));
170
+ const bundledPath = path.join(bundledValidationDir, ...file.split('/'));
171
+ try {
172
+ const [localEntry, bundledEntry] = await Promise.all([
173
+ lstat(localPath),
174
+ lstat(bundledPath),
175
+ ]);
176
+ if (!localEntry.isFile()
177
+ || localEntry.isSymbolicLink()
178
+ || !bundledEntry.isFile()
179
+ || bundledEntry.isSymbolicLink()) {
180
+ return { valid: false, file, received: missingValidatorReceived(file) };
181
+ }
182
+ const [localBytes, bundledBytes] = await Promise.all([
183
+ readFile(localPath),
184
+ readFile(bundledPath),
185
+ ]);
186
+ if (!localBytes.equals(bundledBytes)) {
187
+ return { valid: false, file, received: 'modified managed validator file' };
188
+ }
189
+ }
190
+ catch {
191
+ return { valid: false, file, received: missingValidatorReceived(file) };
192
+ }
193
+ }
194
+ return { valid: true };
195
+ };
196
+ const loadContractSnapshot = async (contractPath) => {
197
+ let value;
198
+ try {
199
+ value = JSON.parse(await readFile(contractPath, 'utf8'));
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ if (!value || typeof value !== 'object' || Array.isArray(value))
205
+ return null;
206
+ const contract = value;
207
+ if (contract.schemaVersion !== 1
208
+ || contract.stepContractVersion !== PROJECT_VALIDATOR_CONTRACT_VERSION
209
+ || !Array.isArray(contract.supportedStepContractVersions)
210
+ || !contract.supportedStepContractVersions.includes(PROJECT_VALIDATOR_CONTRACT_VERSION)
211
+ || typeof contract.contractHash !== 'string'
212
+ || !/^[a-f0-9]{64}$/.test(contract.contractHash))
213
+ return null;
214
+ return contract;
215
+ };
216
+ const docsFileDiagnostic = (file) => file.action === 'conflict'
217
+ ? docsDiagnostic('FG-DOC-002', file.state, file.path)
218
+ : docsDiagnostic('FG-DOC-001', file.state, file.path);
219
+ const inspectStaticDocs = async (options) => {
220
+ try {
221
+ const [contract, bundle] = await Promise.all([
222
+ loadContractSnapshot(options.bundledContractPath),
223
+ loadBundledDocs(options.bundledDocsDir),
224
+ ]);
225
+ if (!contract
226
+ || contract.stepContractVersion !== bundle.manifest.stepContractVersion
227
+ || contract.contractHash !== bundle.manifest.contractHash) {
228
+ return resultWith([
229
+ docsDiagnostic('FG-DOC-001', 'inconsistent packaged contract bundle', null),
230
+ ], 2);
231
+ }
232
+ const inspection = await inspectTemplateDocs(bundle, options.projectDir, {
233
+ legacyCatalog: options.legacyCatalog,
234
+ });
235
+ const status = planTemplateDocsUpdate(inspection);
236
+ if (status.clean)
237
+ return null;
238
+ const diagnostics = status.files
239
+ .filter((file) => file.action !== 'none')
240
+ .map(docsFileDiagnostic);
241
+ return resultWith(diagnostics.length > 0 ? diagnostics : [
242
+ docsDiagnostic('FG-DOC-001', 'stale managed documentation manifest', null),
243
+ ], 1);
244
+ }
245
+ catch {
246
+ return resultWith([
247
+ docsDiagnostic('FG-DOC-001', 'unreadable packaged or project documentation', null),
248
+ ], 2);
249
+ }
250
+ };
251
+ const isCompleteChildResult = (result) => {
252
+ if (result.schemaVersion !== 1
253
+ || result.contractVersion !== PROJECT_VALIDATOR_CONTRACT_VERSION
254
+ || !Array.isArray(result.diagnostics)
255
+ || !result.diagnostics.every(isValidationDiagnostic))
256
+ return false;
257
+ if (result.exitCode === 0)
258
+ return result.valid && result.diagnostics.length === 0;
259
+ if (result.exitCode === 1 || result.exitCode === 2) {
260
+ return !result.valid && result.diagnostics.length > 0;
261
+ }
262
+ return false;
263
+ };
264
+ export const validateFunnel = async (options) => {
265
+ const projectDir = path.resolve(options.projectDir);
266
+ const authoredVersion = await inspectAuthoredVersion(projectDir);
267
+ if (authoredVersion.version !== PROJECT_VALIDATOR_CONTRACT_VERSION) {
268
+ const code = authoredVersion.version !== null
269
+ && authoredVersion.version > PROJECT_VALIDATOR_CONTRACT_VERSION
270
+ ? 'FG-VERSION-001'
271
+ : 'FG-VERSION-002';
272
+ return resultWith([
273
+ versionDiagnostic(code, authoredVersion.received),
274
+ ], 1, authoredVersion.version ?? PROJECT_VALIDATOR_CONTRACT_VERSION);
275
+ }
276
+ const validator = await inspectCheckedInValidator(projectDir, path.resolve(options.bundledValidationDir));
277
+ if (!validator.valid) {
278
+ return resultWith([
279
+ versionDiagnostic('FG-VERSION-002', validator.received, validator.file),
280
+ ], 1);
281
+ }
282
+ const staticFailure = await inspectStaticDocs({ ...options, projectDir });
283
+ if (staticFailure)
284
+ return staticFailure;
285
+ let childResult;
286
+ try {
287
+ childResult = await (options.runProjectValidator ?? runCheckedInProjectValidator)({
288
+ projectDir,
289
+ });
290
+ }
291
+ catch {
292
+ return resultWith([clientDiagnostic('project validator failed to start')], 2);
293
+ }
294
+ return isCompleteChildResult(childResult)
295
+ ? childResult
296
+ : resultWith([clientDiagnostic('invalid project validator result')], 2);
297
+ };
@@ -26,17 +26,96 @@ export type SourceFilePatchBatch = {
26
26
  deletedPaths: string[];
27
27
  files: SourceFile[];
28
28
  };
29
+ export type SourceCandidateOperation = {
30
+ type: 'create' | 'modify';
31
+ path: string;
32
+ content: string;
33
+ contentType?: string;
34
+ sha256: string;
35
+ } | {
36
+ type: 'delete';
37
+ path: string;
38
+ } | {
39
+ type: 'rename';
40
+ path: string;
41
+ oldPath: string;
42
+ };
43
+ export type SourceCandidateApi<TResult> = {
44
+ begin: (input: {
45
+ workspaceId: string;
46
+ funnelId: string;
47
+ baseDraftVersionId: string | null;
48
+ idempotencyKey: string;
49
+ }) => Promise<{
50
+ candidateId: string;
51
+ status: 'staging' | 'validating' | 'committed' | 'aborted' | 'expired' | 'failed';
52
+ baseDraftVersionId: string | null;
53
+ }>;
54
+ stage: (input: {
55
+ workspaceId: string;
56
+ candidateId: string;
57
+ idempotencyKey: string;
58
+ operations: SourceCandidateOperation[];
59
+ }) => Promise<unknown>;
60
+ finalize: (input: {
61
+ workspaceId: string;
62
+ candidateId: string;
63
+ message?: string;
64
+ }) => Promise<TResult>;
65
+ abort: (input: {
66
+ workspaceId: string;
67
+ candidateId: string;
68
+ }) => Promise<unknown>;
69
+ };
29
70
  export declare const DEFAULT_PATCH_BATCH_CONTENT_CHARS = 12000000;
71
+ export declare const DEFAULT_SOURCE_CANDIDATE_BATCH_CONTENT_BYTES = 12000000;
72
+ export declare const MAX_SOURCE_CANDIDATE_OPERATIONS: number;
73
+ export declare const MAX_SOURCE_CANDIDATE_FILE_BYTES: number;
74
+ export declare const MAX_SOURCE_CANDIDATE_PATH_CHARACTERS = 1024;
75
+ export declare const MAX_SOURCE_CANDIDATE_TOTAL_BYTES: number;
76
+ export type SyncDownLocalLifecycle = {
77
+ recoverMigration: () => Promise<unknown>;
78
+ recoverDocs: () => Promise<unknown>;
79
+ refreshDocs: () => Promise<unknown>;
80
+ writeSyncState: () => Promise<unknown>;
81
+ };
82
+ export declare function runSyncDownLocalLifecycle(lifecycle: SyncDownLocalLifecycle): Promise<void>;
30
83
  export declare function normalizeSyncPath(filePath: string): string;
31
84
  export declare function shouldSyncFile(filePath: string): boolean;
32
85
  export declare function buildSyncManifest(rootDir: string, input: SyncManifestInput): Promise<SyncManifest>;
86
+ export declare function buildCommittedSyncManifest(input: {
87
+ workspaceId: string;
88
+ funnelId: string;
89
+ draftVersionId: string;
90
+ files: readonly SyncManifestFile[];
91
+ }): SyncManifest;
33
92
  export declare function writeSyncManifest(rootDir: string, manifest: SyncManifest): Promise<void>;
34
93
  export declare function ensureGitignore(rootDir: string): Promise<void>;
35
94
  export declare function writeLocalEnvFile(rootDir: string, envFile: string | null | undefined): Promise<boolean>;
36
95
  export declare function readSyncManifest(rootDir: string): Promise<SyncManifest | null>;
96
+ export declare function collectSourceSnapshot(rootDir: string): Promise<{
97
+ manifestFiles: SyncManifestFile[];
98
+ sourceFiles: SourceFile[];
99
+ }>;
37
100
  export declare function collectSourceFiles(rootDir: string): Promise<SourceFile[]>;
38
101
  export declare function collectChangedSourceFiles(rootDir: string, previousManifest: SyncManifest): Promise<ChangedSourceFiles>;
39
102
  export declare function hasLocalSourceChanges(rootDir: string, previousManifest: SyncManifest): Promise<boolean>;
40
103
  export declare function chunkChangedSourceFiles(changes: Pick<ChangedSourceFiles, 'deletedPaths' | 'files'>, maxContentChars?: number): SourceFilePatchBatch[];
104
+ export declare function buildSourceCandidateOperations(input: {
105
+ previousFiles: readonly SyncManifestFile[];
106
+ files: readonly SourceFile[];
107
+ deletedPaths: readonly string[];
108
+ }): SourceCandidateOperation[];
109
+ export declare function runSourceCandidateSync<TResult>(input: {
110
+ workspaceId: string;
111
+ funnelId: string;
112
+ baseDraftVersionId: string | null;
113
+ message?: string;
114
+ operations: readonly SourceCandidateOperation[];
115
+ api: SourceCandidateApi<TResult>;
116
+ maxOperationsPerBatch?: number;
117
+ maxContentBytesPerBatch?: number;
118
+ signal?: AbortSignal;
119
+ }): Promise<TResult>;
41
120
  export declare function formatSyncUploadSummary(changes: Pick<ChangedSourceFiles, 'deletedPaths' | 'files'>, largestFileLimit?: number): string[];
42
121
  export declare function writeSourceFiles(rootDir: string, files: SourceFile[]): Promise<void>;