@sponson/adapters 0.0.0-stage → 0.5.0

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 (132) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +4 -2
  3. package/dist/algolia.d.ts +6 -0
  4. package/dist/algolia.js +151 -0
  5. package/dist/algolia.js.map +1 -0
  6. package/dist/clerk.d.ts +8 -0
  7. package/dist/clerk.js +107 -0
  8. package/dist/clerk.js.map +1 -0
  9. package/dist/cloudflare.d.ts +25 -0
  10. package/dist/cloudflare.js +310 -0
  11. package/dist/cloudflare.js.map +1 -0
  12. package/dist/common.d.ts +73 -0
  13. package/dist/common.js +140 -0
  14. package/dist/common.js.map +1 -0
  15. package/dist/digest.d.ts +46 -0
  16. package/dist/digest.js +129 -0
  17. package/dist/digest.js.map +1 -0
  18. package/dist/digitalocean.d.ts +5 -0
  19. package/dist/digitalocean.js +243 -0
  20. package/dist/digitalocean.js.map +1 -0
  21. package/dist/http-adapter-action.d.ts +63 -0
  22. package/dist/http-adapter-action.js +369 -0
  23. package/dist/http-adapter-action.js.map +1 -0
  24. package/dist/http-adapter-auth.d.ts +88 -0
  25. package/dist/http-adapter-auth.js +437 -0
  26. package/dist/http-adapter-auth.js.map +1 -0
  27. package/dist/http-adapter-client.d.ts +51 -0
  28. package/dist/http-adapter-client.js +156 -0
  29. package/dist/http-adapter-client.js.map +1 -0
  30. package/dist/http-adapter-graphql.d.ts +39 -0
  31. package/dist/http-adapter-graphql.js +157 -0
  32. package/dist/http-adapter-graphql.js.map +1 -0
  33. package/dist/http-adapter-list.d.ts +33 -0
  34. package/dist/http-adapter-list.js +421 -0
  35. package/dist/http-adapter-list.js.map +1 -0
  36. package/dist/http-adapter-resource.d.ts +29 -0
  37. package/dist/http-adapter-resource.js +462 -0
  38. package/dist/http-adapter-resource.js.map +1 -0
  39. package/dist/http-adapter-spec.d.ts +216 -0
  40. package/dist/http-adapter-spec.js +660 -0
  41. package/dist/http-adapter-spec.js.map +1 -0
  42. package/dist/http-adapter.d.ts +16 -0
  43. package/dist/http-adapter.js +28 -0
  44. package/dist/http-adapter.js.map +1 -0
  45. package/dist/http.d.ts +179 -0
  46. package/dist/http.js +352 -0
  47. package/dist/http.js.map +1 -0
  48. package/dist/index.d.ts +28 -0
  49. package/dist/index.js +45 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/launchdarkly.d.ts +15 -0
  52. package/dist/launchdarkly.js +337 -0
  53. package/dist/launchdarkly.js.map +1 -0
  54. package/dist/manual.d.ts +13 -0
  55. package/dist/manual.js +201 -0
  56. package/dist/manual.js.map +1 -0
  57. package/dist/neon.d.ts +8 -0
  58. package/dist/neon.js +167 -0
  59. package/dist/neon.js.map +1 -0
  60. package/dist/netlify.d.ts +18 -0
  61. package/dist/netlify.js +436 -0
  62. package/dist/netlify.js.map +1 -0
  63. package/dist/planetscale.d.ts +10 -0
  64. package/dist/planetscale.js +311 -0
  65. package/dist/planetscale.js.map +1 -0
  66. package/dist/providers.d.ts +26 -0
  67. package/dist/providers.js +36 -0
  68. package/dist/providers.js.map +1 -0
  69. package/dist/recipes.d.ts +167 -0
  70. package/dist/recipes.js +246 -0
  71. package/dist/recipes.js.map +1 -0
  72. package/dist/secrets.d.ts +72 -0
  73. package/dist/secrets.js +476 -0
  74. package/dist/secrets.js.map +1 -0
  75. package/dist/sigv4.d.ts +56 -0
  76. package/dist/sigv4.js +118 -0
  77. package/dist/sigv4.js.map +1 -0
  78. package/dist/supabase.d.ts +9 -0
  79. package/dist/supabase.js +385 -0
  80. package/dist/supabase.js.map +1 -0
  81. package/dist/vercel.d.ts +8 -0
  82. package/dist/vercel.js +475 -0
  83. package/dist/vercel.js.map +1 -0
  84. package/dist/xata.d.ts +8 -0
  85. package/dist/xata.js +142 -0
  86. package/dist/xata.js.map +1 -0
  87. package/dist/xml.d.ts +25 -0
  88. package/dist/xml.js +211 -0
  89. package/dist/xml.js.map +1 -0
  90. package/package.json +58 -3
  91. package/recipes/amplify.yaml +147 -0
  92. package/recipes/auth0.yaml +178 -0
  93. package/recipes/azure_swa.yaml +116 -0
  94. package/recipes/cloud_run.yaml +97 -0
  95. package/recipes/cloudflare.yaml +108 -0
  96. package/recipes/cockroachdb.yaml +132 -0
  97. package/recipes/configcat.yaml +123 -0
  98. package/recipes/datadog.yaml +136 -0
  99. package/recipes/firebase.yaml +84 -0
  100. package/recipes/flagsmith.yaml +98 -0
  101. package/recipes/fly.yaml +97 -0
  102. package/recipes/fly_machines.yaml +67 -0
  103. package/recipes/gcs.yaml +102 -0
  104. package/recipes/github.yaml +123 -0
  105. package/recipes/growthbook.yaml +124 -0
  106. package/recipes/heroku.yaml +98 -0
  107. package/recipes/honeycomb.yaml +89 -0
  108. package/recipes/inngest.yaml +97 -0
  109. package/recipes/kinde.yaml +120 -0
  110. package/recipes/mongodb_atlas.yaml +119 -0
  111. package/recipes/okta.yaml +177 -0
  112. package/recipes/posthog.yaml +127 -0
  113. package/recipes/postmark.yaml +105 -0
  114. package/recipes/postmark_server.yaml +93 -0
  115. package/recipes/prisma.yaml +150 -0
  116. package/recipes/qstash.yaml +134 -0
  117. package/recipes/railway.yaml +130 -0
  118. package/recipes/render.yaml +103 -0
  119. package/recipes/resend.yaml +130 -0
  120. package/recipes/s3.yaml +108 -0
  121. package/recipes/sendgrid.yaml +165 -0
  122. package/recipes/sentry.yaml +158 -0
  123. package/recipes/split.yaml +121 -0
  124. package/recipes/statsig.yaml +124 -0
  125. package/recipes/stripe.yaml +310 -0
  126. package/recipes/stytch.yaml +113 -0
  127. package/recipes/svix.yaml +109 -0
  128. package/recipes/turso.yaml +98 -0
  129. package/recipes/unleash.yaml +149 -0
  130. package/recipes/upstash.yaml +130 -0
  131. package/recipes/vercel.yaml +116 -0
  132. package/recipes/workos.yaml +70 -0
@@ -0,0 +1,102 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Google Cloud Storage JSON API recipes (Firebase Storage buckets too), authenticated by the token gcloud prints.
3
+ # Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: gcs
5
+ title: Google Cloud Storage (Firebase Storage)
6
+ category: cors
7
+ verified_on: "2026-10-11"
8
+ docs:
9
+ - title: "Buckets: patch"
10
+ url: https://docs.cloud.google.com/storage/docs/json_api/v1/buckets/patch
11
+ - title: Buckets resource (cors)
12
+ url: https://docs.cloud.google.com/storage/docs/json_api/v1/buckets
13
+ - title: Set up and view CORS configurations
14
+ url: https://docs.cloud.google.com/storage/docs/using-cors
15
+ - title: Performance tips (patch semantics)
16
+ url: https://docs.cloud.google.com/storage/docs/json_api/v1/how-tos/performance
17
+ api:
18
+ base_url: https://storage.googleapis.com/storage/v1
19
+ base_url_env: GCS_API_URL
20
+ auth: { command: [gcloud, auth, print-access-token] }
21
+ detect:
22
+ deps: ["@google-cloud/storage"]
23
+ starter:
24
+ - op: bucket_cors
25
+ preview_url: origin
26
+ where:
27
+ bucket: "`gcloud storage buckets list`, or Firebase console → Storage (the bucket's name)"
28
+ assumptions:
29
+ - id: GC1
30
+ verified: true
31
+ text: "The JSON API takes an OAuth access token as `Authorization: Bearer`; Google's own example gets it from `gcloud auth print-access-token`."
32
+ source: https://docs.cloud.google.com/storage/docs/using-cors
33
+ - id: GC2
34
+ verified: true
35
+ text: "`GET /b/{bucket}?fields=cors` answers the bucket's CORS configuration; `PATCH /b/{bucket}?fields=cors` with `{ cors: [...] }` sets it, and `{ cors: [] }` removes it."
36
+ source: https://docs.cloud.google.com/storage/docs/using-cors
37
+ - id: GC3
38
+ verified: true
39
+ text: "A patch that contains a list replaces the whole list: individual entries cannot be added or removed, so the whole `cors` list is written back."
40
+ source: https://docs.cloud.google.com/storage/docs/json_api/v1/how-tos/performance
41
+ - id: GC4
42
+ verified: true
43
+ text: "Each `cors` entry has `origin` (list), `method` (list), `responseHeader` (list) and `maxAgeSeconds` (integer)."
44
+ source: https://docs.cloud.google.com/storage/docs/json_api/v1/buckets
45
+ - id: GC5
46
+ verified: true
47
+ text: "`PATCH` takes `ifMetagenerationMatch`, a precondition on the bucket's metageneration. The recipe does not send it (the http adapter has no preconditions); Sponson's own writers are serialised by the parent lock, a writer outside Sponson can still race."
48
+ source: https://docs.cloud.google.com/storage/docs/json_api/v1/buckets/patch
49
+ - id: GC6
50
+ verified: true
51
+ text: "Changing a bucket's CORS needs the `storage.buckets.update` permission."
52
+ source: https://docs.cloud.google.com/storage/docs/json_api/v1/buckets/patch
53
+ - id: GC7
54
+ verified: false
55
+ text: "A bucket without a CORS configuration answers `{}` (no `cors` field), read as an empty list, and entries are answered as written, in order."
56
+ - id: GC8
57
+ verified: false
58
+ text: "An entry is identified by its first origin: the recipe writes one origin per entry, and no other entry of the bucket starts with the same origin."
59
+ ops:
60
+ bucket_cors:
61
+ kind: list_item
62
+ title: CORS entry of a bucket
63
+ summary: "An entry of a bucket's `cors` list allowing one origin, such as the preview's URL on the Firebase Storage bucket `my-app.appspot.com`, with its methods, response headers and max age. The rest of the list is left as it is; a console edit of the entry is drift, and destroy removes only this entry."
64
+ covers: [gcs-cors]
65
+ params:
66
+ bucket:
67
+ type: string
68
+ required: true
69
+ description: The bucket's name (Firebase Storage's default bucket is `<project>.appspot.com` or `<project>.firebasestorage.app`).
70
+ example: my-app.appspot.com
71
+ origin:
72
+ type: string
73
+ required: true
74
+ pattern: "^(https?://\\S+|\\*)$"
75
+ description: The origin allowed, without a path (the preview's URL); the entry's identity (GC8).
76
+ example: "https://${ctx.scope}.preview.example.app"
77
+ methods:
78
+ type: list
79
+ default: [GET, HEAD]
80
+ description: The methods allowed.
81
+ response_headers:
82
+ type: list
83
+ default: [Content-Type]
84
+ description: The response headers the browser may read.
85
+ max_age_seconds:
86
+ type: integer
87
+ default: 3600
88
+ description: How long a preflight answer may be cached.
89
+ http:
90
+ parent: { path: "/b/{bucket}?fields=cors", method: PATCH }
91
+ list_path: /cors
92
+ key_field: /origin/0
93
+ item:
94
+ origin: [{ param: origin }]
95
+ method: { param: methods }
96
+ responseHeader: { param: response_headers }
97
+ maxAgeSeconds: { param: max_age_seconds }
98
+ sim:
99
+ objects:
100
+ "/b/{bucket}": { cors: [{ origin: [https://app.example.com], method: [GET], responseHeader: [Content-Type], maxAgeSeconds: 3600 }] }
101
+ live:
102
+ params: { bucket: SPONSON_LIVE_GCS_BUCKET }
@@ -0,0 +1,123 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # GitHub REST API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: github
4
+ title: GitHub
5
+ category: webhooks
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: REST API endpoints for repository webhooks
9
+ url: https://docs.github.com/en/rest/repos/webhooks
10
+ - title: Authenticating to the REST API
11
+ url: https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api
12
+ - title: Getting started with the REST API (headers, API versions)
13
+ url: https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api
14
+ api:
15
+ base_url: https://api.github.com
16
+ base_url_env: GITHUB_API_URL
17
+ auth: { bearer_env: GITHUB_TOKEN }
18
+ headers: { X-GitHub-Api-Version: "2022-11-28", User-Agent: sponson }
19
+ detect:
20
+ deps: ["@octokit/webhooks", probot]
21
+ env: [GITHUB_WEBHOOK_SECRET]
22
+ starter:
23
+ - op: repo_webhook
24
+ where:
25
+ url: "the preview's webhook handler: its URL plus the handler's path (a `{ from }` reference cannot carry a path)"
26
+ assumptions:
27
+ - id: GH1
28
+ verified: true
29
+ text: "`POST /repos/{owner}/{repo}/hooks` creates a repository webhook from `name: web`, `config` (`url`, `content_type`, `secret`, `insecure_ssl`), `events` and `active`, answering 201 with the hook (its integer `id` and `config`), authenticated with `Authorization: Bearer <token>`."
30
+ source: https://docs.github.com/en/rest/repos/webhooks
31
+ - id: GH2
32
+ verified: true
33
+ text: "`GET /repos/{owner}/{repo}/hooks` answers a bare array of hooks, each with `config.url`; it pages with `per_page` (at most 100) and `page`."
34
+ source: https://docs.github.com/en/rest/repos/webhooks
35
+ - id: GH3
36
+ verified: true
37
+ text: "`PATCH /repos/{owner}/{repo}/hooks/{hook_id}` updates `config`, `events` (replacing the list) and `active`; a secret set before is removed unless the same or a new one is sent, so every update sends the config's `url` and `secret` again."
38
+ source: https://docs.github.com/en/rest/repos/webhooks
39
+ - id: GH4
40
+ verified: true
41
+ text: "`DELETE /repos/{owner}/{repo}/hooks/{hook_id}` answers 204, and 404 when the hook is gone."
42
+ source: https://docs.github.com/en/rest/repos/webhooks
43
+ - id: GH5
44
+ verified: true
45
+ text: "Each hook should have a unique config, and hooks may share one only when their events do not overlap. The recipe takes the URL as the hook's identity; two hooks with the same URL make `find` refuse (PARAM_INVALID) instead of guessing."
46
+ source: https://docs.github.com/en/rest/repos/webhooks
47
+ - id: GH6
48
+ verified: false
49
+ text: "The secret is write-only: answers carry it masked or not at all, so it is sent on create and update but never compared, and a secret changed in the plan reaches GitHub only with the next change of a compared field. Keep it in a secret manager and reference it with `{ secret: … }`."
50
+ - id: GH7
51
+ verified: false
52
+ text: "A repository has at most 100 hooks for this purpose (GitHub limits hooks per event), so the first page of `?per_page=100` holds the one `find` looks for."
53
+ - id: GH8
54
+ verified: false
55
+ text: "`config.content_type` and `config.insecure_ssl` are answered as sent (`json`/`form`, the strings `\"0\"`/`\"1\"`), and `Accept: application/json` is served like `application/vnd.github+json`."
56
+ - id: GH9
57
+ verified: false
58
+ text: "The token needs the repository permission Webhooks (write) or the classic `admin:repo_hook` scope; the `GITHUB_TOKEN` of GitHub Actions does not have it, so use a GitHub App token or a fine-grained token. `GITHUB_API_URL` (set by Actions) points at GitHub Enterprise Server where that is the host."
59
+ ops:
60
+ repo_webhook:
61
+ kind: resource
62
+ title: Repository webhook
63
+ summary: A repository webhook delivering to an environment's URL, such as the preview deployment's `/api/github` handler. Located by its URL; a console edit of its events, its active flag, its content type or its TLS check is drift. The secret is sent but not compared (GH6).
64
+ covers: [github-webhook]
65
+ params:
66
+ owner:
67
+ type: string
68
+ required: true
69
+ description: The repository's owner (user or organization).
70
+ example: acme
71
+ repo:
72
+ type: string
73
+ required: true
74
+ description: The repository's name.
75
+ example: app
76
+ url:
77
+ type: string
78
+ required: true
79
+ pattern: "^https?://\\S+$"
80
+ description: The URL GitHub delivers to; it identifies the hook in the repository.
81
+ example: "https://${ctx.scope}.preview.example.com/api/github"
82
+ events:
83
+ type: list
84
+ default: [push]
85
+ description: The events that trigger a delivery (`["*"]` for all).
86
+ active:
87
+ type: boolean
88
+ default: true
89
+ description: Whether deliveries are sent.
90
+ content_type:
91
+ type: string
92
+ default: json
93
+ enum: [json, form]
94
+ description: The payload's media type.
95
+ insecure_ssl:
96
+ type: string
97
+ default: "0"
98
+ enum: ["0", "1"]
99
+ description: '"1" skips verifying the TLS certificate of the URL.'
100
+ secret:
101
+ type: string
102
+ description: "The secret deliveries are signed with (write-only; GH6). Use `{ secret: … }`."
103
+ http:
104
+ find:
105
+ path: "/repos/{owner}/{repo}/hooks?per_page=100"
106
+ match: { /config/url: { param: url } }
107
+ create:
108
+ path: "/repos/{owner}/{repo}/hooks"
109
+ body: { name: web, config: { secret: { param: secret } } }
110
+ update:
111
+ method: PATCH
112
+ path: "/repos/{owner}/{repo}/hooks/{id}"
113
+ body: { config: { url: { param: url }, secret: { param: secret } } }
114
+ delete: { path: "/repos/{owner}/{repo}/hooks/{id}" }
115
+ fields:
116
+ active: { param: active }
117
+ events: { param: events }
118
+ /config/content_type: { param: content_type }
119
+ /config/insecure_ssl: { param: insecure_ssl }
120
+ sim:
121
+ numeric_ids: true
122
+ live:
123
+ params: { owner: SPONSON_LIVE_GITHUB_OWNER, repo: SPONSON_LIVE_GITHUB_REPO }
@@ -0,0 +1,124 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # GrowthBook REST API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: growthbook
4
+ title: GrowthBook
5
+ category: flags
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: REST API reference (authentication, versioning)
9
+ url: https://docs.growthbook.io/api
10
+ - title: Get a feature (v2)
11
+ url: https://docs.growthbook.io/api#operation/getFeatureV2
12
+ - title: Update a feature (v2)
13
+ url: https://docs.growthbook.io/api#operation/updateFeatureV2
14
+ - title: OpenAPI spec
15
+ url: https://api.growthbook.io/api/v1/openapi.yaml
16
+ api:
17
+ base_url: https://api.growthbook.io/api
18
+ base_url_env: GROWTHBOOK_API_URL
19
+ auth: { bearer_env: GROWTHBOOK_API_KEY }
20
+ detect:
21
+ deps: ["@growthbook/"]
22
+ env: ["GROWTHBOOK_*"]
23
+ starter:
24
+ - op: force_rule
25
+ values: { description: "Sponson preview ${ctx.scope}", condition: "{\"preview\": \"${ctx.scope}\"}" }
26
+ where:
27
+ feature_id: "GrowthBook → Features: the feature's key"
28
+ environment: "GrowthBook → Settings → Environments: the one previews evaluate in"
29
+ assumptions:
30
+ - id: GB1
31
+ verified: true
32
+ text: "GrowthBook Cloud's API is at `https://api.growthbook.io/api` (self-hosted: its API host plus `/api`), authenticated with a secret key or personal access token as `Authorization: Bearer`."
33
+ source: https://docs.growthbook.io/api
34
+ - id: GB2
35
+ verified: true
36
+ text: "The v1 feature endpoints (rules per environment) are deprecated; in v2 a feature's rules are one top-level `rules` array, each rule naming its `environments` (or `allEnvironments`)."
37
+ source: https://docs.growthbook.io/api#operation/getFeatureV2
38
+ - id: GB3
39
+ verified: true
40
+ text: "`GET /v2/features/{id}` answers `{ feature: { …, rules: [...] } }`."
41
+ source: https://docs.growthbook.io/api#operation/getFeatureV2
42
+ - id: GB4
43
+ verified: true
44
+ text: "`POST /v2/features/{id}` patch-merges the top-level fields it is sent, and `rules`, when sent, replaces the whole array: so the array is read, the rule added or changed, and the whole array sent back. Rules read with GET may be posted back unchanged. The update publishes a new revision at once."
45
+ source: https://docs.growthbook.io/api#operation/updateFeatureV2
46
+ - id: GB5
47
+ verified: true
48
+ text: "A force rule is `{ type: force, description, condition, value, enabled, allEnvironments, environments }`; `condition` is a JSON query serialised as a string and `value` is a string (`\"true\"` for a boolean feature)."
49
+ source: https://api.growthbook.io/api/v1/openapi.yaml
50
+ - id: GB6
51
+ verified: false
52
+ text: "Rule ids may be chosen by the client but what the server does with them is not documented, so the rule is identified by its `description`, which it stores as written; two rules with that description are one rule to Sponson."
53
+ - id: GB7
54
+ verified: false
55
+ text: "A rule is read back with `condition` exactly as sent (not reformatted), so the compared fields do not show as changed on every plan."
56
+ - id: GB8
57
+ verified: true
58
+ text: "When the feature requires approval, the update answers 422 with the gates that blocked it, unless the organisation lets the REST API bypass approvals; the apply then fails."
59
+ source: https://docs.growthbook.io/api#operation/updateFeatureV2
60
+ ops:
61
+ force_rule:
62
+ kind: list_item
63
+ title: Feature force rule
64
+ summary: "A force rule on an existing feature that serves a value to the preview in one environment, such as `value \"true\"` when `{\"preview\": \"pr-42\"}`. Added after the feature's other rules (rules are evaluated in order: an earlier rule that matches the preview wins). A console edit of its condition, value, environments or enabled state is drift. Destroy removes the rule."
65
+ covers: [growthbook-force-rule]
66
+ params:
67
+ feature_id:
68
+ type: string
69
+ required: true
70
+ description: The feature's key.
71
+ example: new-checkout
72
+ environment:
73
+ type: string
74
+ required: true
75
+ description: The environment the rule applies in.
76
+ example: staging
77
+ description:
78
+ type: string
79
+ required: true
80
+ description: The rule's description, unique among the feature's rules; it identifies the rule.
81
+ example: "Sponson preview ${ctx.scope}"
82
+ condition:
83
+ type: string
84
+ required: true
85
+ description: The targeting condition, a JSON query as a string.
86
+ example: "{\"preview\": \"${ctx.scope}\"}"
87
+ value:
88
+ type: string
89
+ required: true
90
+ description: The value served, as a string (`"true"` for a boolean feature, JSON for a JSON feature).
91
+ example: "true"
92
+ enabled:
93
+ type: boolean
94
+ default: true
95
+ description: Whether the rule is on.
96
+ http:
97
+ parent: { path: "/v2/features/{feature_id}", method: POST, item_path: /feature }
98
+ list_path: /rules
99
+ key_field: description
100
+ item:
101
+ description: { param: description }
102
+ type: force
103
+ condition: { param: condition }
104
+ value: { param: value }
105
+ enabled: { param: enabled }
106
+ allEnvironments: false
107
+ environments: [{ param: environment }]
108
+ sim:
109
+ objects:
110
+ "/v2/features/{feature_id}":
111
+ id: new-checkout
112
+ valueType: boolean
113
+ defaultValue: "false"
114
+ rules:
115
+ - id: fr_internal
116
+ type: force
117
+ description: Internal users
118
+ condition: "{\"internal\": true}"
119
+ value: "true"
120
+ enabled: true
121
+ allEnvironments: true
122
+ environments: []
123
+ live:
124
+ params: { feature_id: SPONSON_LIVE_GROWTHBOOK_FEATURE_ID, environment: SPONSON_LIVE_GROWTHBOOK_ENVIRONMENT }
@@ -0,0 +1,98 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Heroku Platform API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: heroku
4
+ title: Heroku
5
+ category: deploy
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Platform API reference (clients, Config Vars, Review App)
9
+ url: https://devcenter.heroku.com/articles/platform-api-reference
10
+ - title: Platform API JSON schema (config-var)
11
+ url: https://api.heroku.com/schema
12
+ - title: Configuration and config vars
13
+ url: https://devcenter.heroku.com/articles/config-vars
14
+ - title: CLI authentication (HEROKU_API_KEY)
15
+ url: https://devcenter.heroku.com/articles/authentication
16
+ api:
17
+ base_url: https://api.heroku.com
18
+ base_url_env: HEROKU_API_URL
19
+ auth: { bearer_env: HEROKU_API_KEY }
20
+ media_type: "application/vnd.heroku+json; version=3"
21
+ detect:
22
+ env: ["HEROKU_*"]
23
+ files: [heroku.yml]
24
+ starter:
25
+ - op: config_var
26
+ where:
27
+ app: "the review app's name (`heroku apps`; by default `<pipeline>-pr-<n>`)"
28
+ assumptions:
29
+ - id: HK1
30
+ verified: true
31
+ text: "Requests authenticate as `Authorization: Bearer <token>`; the Heroku CLI reads the token from `HEROKU_API_KEY`."
32
+ source: https://devcenter.heroku.com/articles/authentication
33
+ - id: HK2
34
+ verified: true
35
+ text: "Every request must send `Accept: application/vnd.heroku+json; version=3`; without it the API refuses the request (documented as 406 `not_acceptable`; a request without it was answered 400 `missing_version` when checked)."
36
+ source: https://devcenter.heroku.com/articles/platform-api-reference
37
+ - id: HK3
38
+ verified: true
39
+ text: "`GET /apps/{app_id_or_name}/config-vars` answers the app's config vars as one flat map of name to string value."
40
+ source: https://devcenter.heroku.com/articles/platform-api-reference
41
+ - id: HK4
42
+ verified: true
43
+ text: "`PATCH /apps/{app_id_or_name}/config-vars` with a map sets the named vars and removes those set to `null`, leaving every other var as it is: one var is written without reading or rewriting the others, so concurrent writers of other vars cannot lose them (no lock needed)."
44
+ source: https://devcenter.heroku.com/articles/platform-api-reference
45
+ - id: HK5
46
+ verified: true
47
+ text: "Config var names match `^\\w+$` and values are strings (`null` only as a delete in a PATCH)."
48
+ source: https://api.heroku.com/schema
49
+ - id: HK6
50
+ verified: true
51
+ text: "Every change to config vars creates a new release and restarts the app, so a write takes effect without a separate deploy."
52
+ source: https://devcenter.heroku.com/articles/config-vars
53
+ - id: HK7
54
+ verified: true
55
+ text: "Review apps are created asynchronously (`POST /review-apps` answers `pending` with a nullable `app`), by Heroku itself when the pipeline has automatic review apps; this recipe does not create them, it writes vars on an app that exists (the review app's name or id)."
56
+ source: https://devcenter.heroku.com/articles/platform-api-reference
57
+ - id: HK9
58
+ verified: false
59
+ text: "A PATCH whose `Content-Type` is the same vendor media type (`media_type` sends it as both `Accept` and `Content-Type`) is accepted like `application/json`; the reference's examples send `application/json`."
60
+ source: https://devcenter.heroku.com/articles/platform-api-reference
61
+ - id: HK8
62
+ verified: false
63
+ text: "A missing app answers 404 `not_found` on GET and PATCH, which destroy treats as the var being gone."
64
+ source: https://devcenter.heroku.com/articles/platform-api-reference
65
+ ops:
66
+ config_var:
67
+ kind: list_item
68
+ title: App config var
69
+ summary: "One config var on a Heroku app, such as a review app's `API_URL`. Written as a merge patch of that one var (`null` deletes it), so other vars, including other scopes', are never rewritten. A console edit of its value is drift; destroy removes only this var. Heroku restarts the app on every change (HK6)."
70
+ covers: [heroku-config-vars]
71
+ params:
72
+ app:
73
+ type: string
74
+ required: true
75
+ description: The app's name or id (a review app's, such as `myapp-pr-42`).
76
+ example: "myapp-${ctx.scope}"
77
+ key:
78
+ type: string
79
+ required: true
80
+ pattern: "^\\w+$"
81
+ description: The config var's name.
82
+ example: API_URL
83
+ value:
84
+ type: string
85
+ required: true
86
+ description: "The value: a literal, `{ from }` or `{ secret }`."
87
+ example: "https://api-${ctx.scope}.example.com"
88
+ http:
89
+ parent: { path: "/apps/{app}/config-vars" }
90
+ list_path: ""
91
+ shape: map
92
+ item: { param: key }
93
+ value: { param: value }
94
+ sim:
95
+ objects:
96
+ "/apps/{app}/config-vars": { DATABASE_URL: "postgres://seeded", LOG_LEVEL: info }
97
+ live:
98
+ params: { app: SPONSON_LIVE_HEROKU_APP }
@@ -0,0 +1,89 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Honeycomb API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
3
+ provider: honeycomb
4
+ title: Honeycomb
5
+ category: observability
6
+ verified_on: "2026-10-11"
7
+ docs:
8
+ - title: Create a Marker
9
+ url: https://docs.honeycomb.io/api/markers/create-a-marker
10
+ - title: List All Markers
11
+ url: https://docs.honeycomb.io/api/markers
12
+ - title: Update a Marker
13
+ url: https://docs.honeycomb.io/api/markers/update-a-marker
14
+ - title: Delete a Marker
15
+ url: https://docs.honeycomb.io/api/markers/delete-a-marker
16
+ api:
17
+ base_url: https://api.honeycomb.io
18
+ base_url_env: HONEYCOMB_API_URL
19
+ auth: { header: X-Honeycomb-Team, value_env: HONEYCOMB_API_KEY }
20
+ detect:
21
+ deps: ["@honeycombio/"]
22
+ env: ["HONEYCOMB_*"]
23
+ starter:
24
+ - op: marker
25
+ values: { dataset: __all__, message: "deploy ${ctx.scope} ${ctx.git.sha}" }
26
+ assumptions:
27
+ - id: HC1
28
+ verified: true
29
+ text: "`POST /1/markers/{datasetSlug}` creates a marker from `start_time`, `end_time` (Unix seconds), `message`, `type` and `url` (all optional; `start_time` defaults to the request's arrival), answering 201 with the marker and its `id`; `__all__` as the dataset slug makes an environment-wide marker. Requests carry a Configuration Key with the Manage Markers permission in `X-Honeycomb-Team`."
30
+ source: https://docs.honeycomb.io/api/markers/create-a-marker
31
+ - id: HC2
32
+ verified: true
33
+ text: "`GET /1/markers/{datasetSlug}` answers a bare array of the dataset's markers (`id`, `message`, `type`, `url`, `start_time`, `end_time`, `created_at`, `updated_at`, `color`)."
34
+ source: https://docs.honeycomb.io/api/markers
35
+ - id: HC3
36
+ verified: true
37
+ text: "`PUT /1/markers/{datasetSlug}/{markerId}` replaces the marker: a field left out of the body is erased."
38
+ source: https://docs.honeycomb.io/api/markers/update-a-marker
39
+ - id: HC4
40
+ verified: true
41
+ text: "`DELETE /1/markers/{datasetSlug}/{markerId}` removes the marker and answers it (200)."
42
+ source: https://docs.honeycomb.io/api/markers/delete-a-marker
43
+ - id: HC5
44
+ verified: true
45
+ text: "The API is served from `api.honeycomb.io` (US) and `api.eu1.honeycomb.io` (EU); EU teams set `base_url` in the provider block."
46
+ source: https://docs.honeycomb.io/api/markers/create-a-marker
47
+ - id: HC6
48
+ verified: false
49
+ text: "Message and type identify the marker this recipe created among the dataset's markers (Honeycomb does not require them to be unique: two equal ones make `find` refuse with PARAM_INVALID), and the list holds every marker of the dataset in one answer."
50
+ - id: HC7
51
+ verified: false
52
+ text: "An update (a changed `url`) re-sends message, type and url but not `start_time`, which Honeycomb then sets to the time of the update (HC3), moving the marker; markers are rarely edited."
53
+ - id: HC8
54
+ verified: false
55
+ text: "A deleted marker answers 404 to DELETE, which destroy treats as already gone."
56
+ ops:
57
+ marker:
58
+ kind: resource
59
+ title: Deploy marker
60
+ summary: "A marker on an environment's dataset (or on every dataset of the environment with `__all__`) for a deploy, such as `deploy pr-42 0123abc`. Located by its message and type; a console edit of its URL is drift. Destroying the scope removes it; write `destroy: keep` to keep the deploy history."
61
+ covers: [honeycomb-marker]
62
+ params:
63
+ dataset:
64
+ type: string
65
+ required: true
66
+ description: The dataset's slug, or `__all__` for an environment-wide marker.
67
+ example: __all__
68
+ message:
69
+ type: string
70
+ required: true
71
+ description: The marker's text; with its type, its identity (HC6).
72
+ example: "deploy ${ctx.scope} ${ctx.git.sha}"
73
+ type:
74
+ type: string
75
+ default: deploy
76
+ description: The marker's type, which groups markers (and their color).
77
+ url:
78
+ type: string
79
+ description: The link opened from the marker (the preview or the build).
80
+ example: "https://${ctx.scope}.preview.example.com"
81
+ http:
82
+ find:
83
+ path: "/1/markers/{dataset}"
84
+ match: { message: { param: message }, type: { param: type } }
85
+ create: { path: "/1/markers/{dataset}" }
86
+ update: { method: PUT, path: "/1/markers/{dataset}/{id}" }
87
+ delete: { path: "/1/markers/{dataset}/{id}" }
88
+ fields:
89
+ url: { param: url }
@@ -0,0 +1,97 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Inngest recipes. Format and checklist: docs/adr/0020-recipes.md, docs/adr/0022-one-off-actions.md, CONTRIBUTING.md
3
+ # "Adding a recipe". No API block: the sync is a request to the app's own serve endpoint, which registers itself
4
+ # with Inngest using its own signing key, so Sponson sends no Inngest credential at all.
5
+ provider: inngest
6
+ title: Inngest
7
+ category: queues
8
+ verified_on: "2026-10-11"
9
+ docs:
10
+ - title: Syncing an Inngest app (curl -X PUT on the serve endpoint), Inngest's integration guide
11
+ url: https://www.inngest.com/llm-context.md
12
+ - title: Apps in Inngest Cloud (sync methods, when to sync)
13
+ url: https://www.inngest.com/docs/apps/cloud
14
+ - title: Environments and branch environments
15
+ url: https://www.inngest.com/docs/platform/environments
16
+ - title: The TypeScript SDK's serve handler (InngestCommHandler, PUT = register)
17
+ url: https://github.com/inngest/inngest-js/blob/main/packages/inngest/src/components/InngestCommHandler.ts
18
+ - title: REST API v2 OpenAPI document (V2_SyncApp)
19
+ url: https://api-docs.inngest.com/api-specs/v2.json
20
+ detect:
21
+ deps: [inngest]
22
+ env: ["INNGEST_*"]
23
+ starter:
24
+ - op: sync
25
+ preview_url: url
26
+ values: { revision: "${ctx.git.sha}" }
27
+ assumptions:
28
+ - id: IN1
29
+ verified: true
30
+ text: "A `PUT` to the app's serve endpoint (by convention `/api/inngest`, `curl -X PUT https://your-app.com/api/inngest --fail-with-body`) makes the app's SDK register its functions with Inngest; the SDK authenticates that registration with the app's own `INNGEST_SIGNING_KEY`, so the caller sends no credential."
31
+ source: https://www.inngest.com/llm-context.md
32
+ - id: IN2
33
+ verified: true
34
+ text: "The app registers into the environment its SDK is configured for: `INNGEST_ENV` (or the client's `env` option) names the branch environment and always wins; Vercel's `VERCEL_GIT_COMMIT_REF` is detected automatically, while Netlify, Cloudflare Pages, Railway and Render must pass their branch variable. Sponson does not choose the environment: the app must be configured so."
35
+ source: https://www.inngest.com/docs/platform/environments
36
+ - id: IN3
37
+ verified: true
38
+ text: "A branch environment is created on demand the first time an app syncs with it (or events are sent to it), and every branch environment shares one signing key and one event key, so a preview needs no per-branch key."
39
+ source: https://www.inngest.com/docs/platform/environments
40
+ - id: IN4
41
+ verified: true
42
+ text: "Without `x-inngest-sync-kind: in-band` (which this recipe never sends) the serve handler syncs out of band: it calls Inngest's register endpoint itself and answers with that call's status and a JSON body `{ message, modified }`; a failed registration answers 500 (`Failed to register`)."
43
+ source: https://github.com/inngest/inngest-js/blob/main/packages/inngest/src/components/InngestCommHandler.ts
44
+ - id: IN5
45
+ verified: true
46
+ text: "Sync only once the new version of the code is live: some hosts roll a deploy out over seconds or minutes and an early sync registers the old functions. The recipe's trigger is the deployment's URL (a deploy line's external output, so the line waits for the deploy) and a revision."
47
+ source: https://www.inngest.com/docs/apps/cloud
48
+ - id: IN6
49
+ verified: true
50
+ text: "Branch environments are archived automatically three days after their latest deployment (archiving stops functions from triggering and keeps the data), and archiving cannot be undone per app; there is no request to undo a sync, so destroy sends nothing and forgets the record."
51
+ source: https://www.inngest.com/docs/platform/environments
52
+ - id: IN7
53
+ verified: true
54
+ text: "The REST API's `POST /v2/apps/{appId}/syncs` (marked beta, `x-beta: true`) takes `{ url }` and answers a sync with `status` pending, success, error or duplicate, but the operation declares no header or parameter that selects a branch environment; this recipe does not use it."
55
+ source: https://api-docs.inngest.com/api-specs/v2.json
56
+ - id: IN8
57
+ verified: false
58
+ text: "Syncing the same deployment again is harmless (the same functions registered again, `modified: false`), so the at-least-once delivery of an action (sent again after a crash before it was recorded) needs no idempotency key."
59
+ - id: IN9
60
+ verified: false
61
+ text: "A preview behind Vercel Deployment Protection answers the `PUT` with 401 unless it carries `x-vercel-protection-bypass` with the project's automation bypass secret; pass it with `headers`, as a `{ secret }`."
62
+ ops:
63
+ sync:
64
+ kind: action
65
+ title: App sync into its branch environment
66
+ summary: Registers a preview deployment's Inngest functions by sending `PUT` to its serve endpoint, once per deployment URL and revision; the app's SDK picks the branch environment (`INNGEST_ENV`, IN2) and signs the registration with its own key (IN1), so no Inngest credential is needed.
67
+ covers: [inngest-sync]
68
+ params:
69
+ url:
70
+ type: string
71
+ description: "The deployment's base URL, without a trailing slash: usually `{ from: <deploy line>.preview_url }`, so the sync waits for the deploy (IN5)."
72
+ required: true
73
+ pattern: "^https?://\\S+[^/]$"
74
+ example: https://acme-git-feat-x-acme.vercel.app
75
+ path:
76
+ type: string
77
+ description: Where the app serves Inngest's `serve()` handler, appended to `url`.
78
+ default: /api/inngest
79
+ pattern: "^/\\S*$"
80
+ revision:
81
+ type: string
82
+ description: "What else makes it sync again when it changes, usually the commit: `\"${ctx.git.sha}\"`."
83
+ required: true
84
+ example: "${ctx.git.sha}"
85
+ headers:
86
+ type: map
87
+ description: "Extra request headers, such as `x-vercel-protection-bypass: { secret: env://VERCEL_AUTOMATION_BYPASS_SECRET }` for a protected preview (IN9)."
88
+ http:
89
+ trigger: { url: { param: url }, path: { param: path }, revision: { param: revision } }
90
+ request:
91
+ method: PUT
92
+ url: { join: [{ param: url }, { param: path }] }
93
+ headers: { param: headers }
94
+ outputs: { modified: /modified }
95
+ live:
96
+ # A deployed app with Inngest's serve handler and INNGEST_ENV set to a throwaway branch environment.
97
+ params: { url: SPONSON_LIVE_INNGEST_APP_URL }