@sponson/adapters 0.4.0 → 0.6.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.
- package/README.md +1 -1
- package/dist/algolia.d.ts +6 -0
- package/dist/algolia.js +151 -0
- package/dist/algolia.js.map +1 -0
- package/dist/clerk.js +1 -1
- package/dist/clerk.js.map +1 -1
- package/dist/cloud_run.d.ts +12 -0
- package/dist/cloud_run.js +469 -0
- package/dist/cloud_run.js.map +1 -0
- package/dist/cloudflare.d.ts +25 -0
- package/dist/cloudflare.js +310 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/common.d.ts +2 -6
- package/dist/common.js.map +1 -1
- package/dist/digest.d.ts +46 -0
- package/dist/digest.js +129 -0
- package/dist/digest.js.map +1 -0
- package/dist/digitalocean.d.ts +5 -0
- package/dist/digitalocean.js +243 -0
- package/dist/digitalocean.js.map +1 -0
- package/dist/http-adapter-action.d.ts +63 -0
- package/dist/http-adapter-action.js +369 -0
- package/dist/http-adapter-action.js.map +1 -0
- package/dist/http-adapter-auth.d.ts +94 -0
- package/dist/http-adapter-auth.js +440 -0
- package/dist/http-adapter-auth.js.map +1 -0
- package/dist/http-adapter-client.d.ts +57 -0
- package/dist/http-adapter-client.js +163 -0
- package/dist/http-adapter-client.js.map +1 -0
- package/dist/http-adapter-graphql.d.ts +39 -0
- package/dist/http-adapter-graphql.js +157 -0
- package/dist/http-adapter-graphql.js.map +1 -0
- package/dist/http-adapter-list.d.ts +33 -0
- package/dist/http-adapter-list.js +421 -0
- package/dist/http-adapter-list.js.map +1 -0
- package/dist/http-adapter-resource.d.ts +29 -0
- package/dist/http-adapter-resource.js +462 -0
- package/dist/http-adapter-resource.js.map +1 -0
- package/dist/http-adapter-spec.d.ts +216 -0
- package/dist/http-adapter-spec.js +660 -0
- package/dist/http-adapter-spec.js.map +1 -0
- package/dist/http-adapter.d.ts +16 -0
- package/dist/http-adapter.js +28 -0
- package/dist/http-adapter.js.map +1 -0
- package/dist/http.d.ts +61 -2
- package/dist/http.js +79 -18
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/launchdarkly.d.ts +15 -0
- package/dist/launchdarkly.js +337 -0
- package/dist/launchdarkly.js.map +1 -0
- package/dist/manual.d.ts +13 -0
- package/dist/manual.js +201 -0
- package/dist/manual.js.map +1 -0
- package/dist/neon.js +1 -1
- package/dist/neon.js.map +1 -1
- package/dist/netlify.d.ts +18 -0
- package/dist/netlify.js +436 -0
- package/dist/netlify.js.map +1 -0
- package/dist/planetscale.d.ts +10 -0
- package/dist/planetscale.js +311 -0
- package/dist/planetscale.js.map +1 -0
- package/dist/providers.d.ts +28 -0
- package/dist/providers.js +41 -0
- package/dist/providers.js.map +1 -0
- package/dist/recipes.d.ts +167 -0
- package/dist/recipes.js +246 -0
- package/dist/recipes.js.map +1 -0
- package/dist/secrets.d.ts +44 -0
- package/dist/secrets.js +279 -0
- package/dist/secrets.js.map +1 -1
- package/dist/sigv4.d.ts +56 -0
- package/dist/sigv4.js +118 -0
- package/dist/sigv4.js.map +1 -0
- package/dist/supabase.d.ts +9 -0
- package/dist/supabase.js +385 -0
- package/dist/supabase.js.map +1 -0
- package/dist/vercel.js +1 -1
- package/dist/vercel.js.map +1 -1
- package/dist/xata.d.ts +8 -0
- package/dist/xata.js +142 -0
- package/dist/xata.js.map +1 -0
- package/dist/xml.d.ts +25 -0
- package/dist/xml.js +211 -0
- package/dist/xml.js.map +1 -0
- package/package.json +11 -4
- package/recipes/amplify.yaml +147 -0
- package/recipes/auth0.yaml +178 -0
- package/recipes/azure_swa.yaml +116 -0
- package/recipes/cloudflare.yaml +108 -0
- package/recipes/cockroachdb.yaml +132 -0
- package/recipes/configcat.yaml +123 -0
- package/recipes/datadog.yaml +136 -0
- package/recipes/firebase.yaml +84 -0
- package/recipes/flagsmith.yaml +98 -0
- package/recipes/fly.yaml +97 -0
- package/recipes/fly_machines.yaml +67 -0
- package/recipes/gcs.yaml +102 -0
- package/recipes/github.yaml +124 -0
- package/recipes/growthbook.yaml +124 -0
- package/recipes/heroku.yaml +98 -0
- package/recipes/honeycomb.yaml +89 -0
- package/recipes/inngest.yaml +97 -0
- package/recipes/kinde.yaml +120 -0
- package/recipes/mongodb_atlas.yaml +119 -0
- package/recipes/okta.yaml +177 -0
- package/recipes/posthog.yaml +127 -0
- package/recipes/postmark.yaml +105 -0
- package/recipes/postmark_server.yaml +93 -0
- package/recipes/prisma.yaml +150 -0
- package/recipes/qstash.yaml +134 -0
- package/recipes/railway.yaml +130 -0
- package/recipes/render.yaml +103 -0
- package/recipes/resend.yaml +130 -0
- package/recipes/s3.yaml +108 -0
- package/recipes/sendgrid.yaml +165 -0
- package/recipes/sentry.yaml +158 -0
- package/recipes/split.yaml +121 -0
- package/recipes/statsig.yaml +124 -0
- package/recipes/stripe.yaml +310 -0
- package/recipes/stytch.yaml +113 -0
- package/recipes/svix.yaml +109 -0
- package/recipes/turso.yaml +98 -0
- package/recipes/unleash.yaml +149 -0
- package/recipes/upstash.yaml +130 -0
- package/recipes/vercel.yaml +116 -0
- package/recipes/workos.yaml +70 -0
package/recipes/fly.yaml
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Fly.io GraphQL API recipes (app secrets). Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
3
|
+
# The per-PR app itself is in fly_machines.yaml (the Machines API has another base URL).
|
|
4
|
+
provider: fly
|
|
5
|
+
title: Fly.io
|
|
6
|
+
category: deploy
|
|
7
|
+
verified_on: "2026-10-11"
|
|
8
|
+
docs:
|
|
9
|
+
- title: Secrets and Fly Apps
|
|
10
|
+
url: https://docs.fly.io/apps/secrets
|
|
11
|
+
- title: superfly/fly-go resource_secrets.go (the GraphQL operations flyctl sends)
|
|
12
|
+
url: https://github.com/superfly/fly-go/blob/main/resource_secrets.go
|
|
13
|
+
- title: superfly/fly-go schema.graphql
|
|
14
|
+
url: https://github.com/superfly/fly-go/blob/main/schema.graphql
|
|
15
|
+
- title: Working with the Machines API (tokens)
|
|
16
|
+
url: https://docs.fly.io/machines/api/working-with-machines-api/
|
|
17
|
+
api:
|
|
18
|
+
base_url: https://api.fly.io/graphql
|
|
19
|
+
base_url_env: FLY_API_URL
|
|
20
|
+
auth: { bearer_env: FLY_API_TOKEN }
|
|
21
|
+
detect:
|
|
22
|
+
env: ["FLY_*"]
|
|
23
|
+
files: [fly.toml]
|
|
24
|
+
starter:
|
|
25
|
+
- op: secret
|
|
26
|
+
where:
|
|
27
|
+
app: "the preview's Fly app: `app` in fly.toml, or the app per pull request a `fly_machines.app` line creates"
|
|
28
|
+
assumptions:
|
|
29
|
+
- id: FS1
|
|
30
|
+
verified: true
|
|
31
|
+
text: "`query ($appName: String!) { app(name: $appName) { secrets { name digest createdAt } } }` lists an app's secrets; values are never returned (Fly does not allow reading plain-text secret values), so the recipe locates a secret by name and compares nothing else."
|
|
32
|
+
source: https://github.com/superfly/fly-go/blob/main/resource_secrets.go
|
|
33
|
+
- id: FS2
|
|
34
|
+
verified: true
|
|
35
|
+
text: "`mutation($input: SetSecretsInput!) { setSecrets(input: $input) { release { id version } } }` with `{ appId, secrets: [{ key, value }] }` sets secrets; `unsetSecrets(input: { appId, keys })` removes them."
|
|
36
|
+
source: https://github.com/superfly/fly-go/blob/main/resource_secrets.go
|
|
37
|
+
- id: FS3
|
|
38
|
+
verified: true
|
|
39
|
+
text: "Plain-text values cannot be read back (`fly secrets list` shows a name, a digest and a date), so a value changed in the console, or in the plan, is not detected: drift is the secret's presence only. To change a value, run `sponson apply --destroy` for the scope (or remove the line, apply, and add it back)."
|
|
40
|
+
source: https://docs.fly.io/apps/secrets
|
|
41
|
+
- id: FS4
|
|
42
|
+
verified: true
|
|
43
|
+
text: "Machines read secrets as environment variables at boot: a secret set without updating the Machines (as `fly secrets set --stage` does) reaches only Machines started or updated afterwards. Order the line before the PR's `fly deploy`; after a later change run `fly secrets deploy`."
|
|
44
|
+
source: https://docs.fly.io/apps/secrets
|
|
45
|
+
- id: FS5
|
|
46
|
+
verified: false
|
|
47
|
+
text: "A token from `fly tokens create` (`FlyV1 fm2_…`) or `fly auth token` is accepted as `Authorization: Bearer <token>` by the GraphQL API, as the Machines API documents for the same `FLY_API_TOKEN`. (fly-go itself sends a macaroon as `FlyV1 <token>`; a block that needs that sends it with `auth: { header: Authorization, value_env: FLY_API_TOKEN }`.)"
|
|
48
|
+
- id: FS6
|
|
49
|
+
verified: false
|
|
50
|
+
text: "Unsetting a secret of an app that no longer exists answers an error saying the app could not be found, which the adapter reads as already gone; unsetting an absent key of an existing app is not an error. setSecrets through GraphQL updates no Machines (FS4). Current flyctl sets secrets through the Machines API instead; the GraphQL mutations are not marked deprecated."
|
|
51
|
+
ops:
|
|
52
|
+
secret:
|
|
53
|
+
kind: resource
|
|
54
|
+
title: App secret
|
|
55
|
+
summary: "One secret of a Fly app (typically the app made for the pull request, `fly_machines.app`), set with `setSecrets` and removed with `unsetSecrets`. Fly never returns secret values, so only its presence is compared: a secret unset in the console is recreated, a changed value is not noticed (FS3). Machines see it once started or updated after the write: order it before the PR's deploy (FS4)."
|
|
56
|
+
covers: [fly-secrets]
|
|
57
|
+
params:
|
|
58
|
+
app:
|
|
59
|
+
type: string
|
|
60
|
+
required: true
|
|
61
|
+
description: The app's name.
|
|
62
|
+
example: "demo-${ctx.scope}"
|
|
63
|
+
name:
|
|
64
|
+
type: string
|
|
65
|
+
required: true
|
|
66
|
+
pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
67
|
+
description: The secret's name (the environment variable the Machines see).
|
|
68
|
+
example: DATABASE_URL
|
|
69
|
+
value:
|
|
70
|
+
type: string
|
|
71
|
+
required: true
|
|
72
|
+
description: The secret's value, normally a `{ secret }` reference. It is sent, never stored or compared.
|
|
73
|
+
example: "postgres://app@db.internal:5432/${ctx.scope}"
|
|
74
|
+
http:
|
|
75
|
+
find:
|
|
76
|
+
graphql:
|
|
77
|
+
query: "query ($appName: String!) { app(name: $appName) { secrets { name digest createdAt } } }"
|
|
78
|
+
variables: { appName: { param: app } }
|
|
79
|
+
data_path: /app/secrets
|
|
80
|
+
match: { name: { param: name } }
|
|
81
|
+
create:
|
|
82
|
+
graphql:
|
|
83
|
+
query: "mutation($input: SetSecretsInput!) { setSecrets(input: $input) { release { id version } } }"
|
|
84
|
+
variables: { input: { appId: { param: app }, secrets: [{ key: { param: name }, value: { param: value } }] } }
|
|
85
|
+
idempotent: true
|
|
86
|
+
delete:
|
|
87
|
+
graphql:
|
|
88
|
+
query: "mutation($input: UnsetSecretsInput!) { unsetSecrets(input: $input) { release { id version } } }"
|
|
89
|
+
variables: { input: { appId: { param: app }, keys: [{ param: name }] } }
|
|
90
|
+
sim:
|
|
91
|
+
provider: fly
|
|
92
|
+
seed: { apps: { demo-pr-42: {} } }
|
|
93
|
+
present: /apps/demo-pr-42/secrets/DATABASE_URL
|
|
94
|
+
drift:
|
|
95
|
+
missing: { secrets.demo-pr-42.DATABASE_URL: delete }
|
|
96
|
+
live:
|
|
97
|
+
params: { app: SPONSON_LIVE_FLY_APP }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Fly.io Machines API recipes (the per-PR app). Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
3
|
+
# The app's secrets are in fly.yaml (the GraphQL API has another base URL).
|
|
4
|
+
provider: fly_machines
|
|
5
|
+
title: Fly.io Machines API
|
|
6
|
+
category: deploy
|
|
7
|
+
verified_on: "2026-10-11"
|
|
8
|
+
docs:
|
|
9
|
+
- title: Working with the Machines API
|
|
10
|
+
url: https://docs.fly.io/machines/api/working-with-machines-api/
|
|
11
|
+
- title: Apps resource
|
|
12
|
+
url: https://docs.fly.io/machines/api/apps-resource/
|
|
13
|
+
api:
|
|
14
|
+
base_url: https://api.machines.dev/v1
|
|
15
|
+
base_url_env: FLY_MACHINES_API_URL
|
|
16
|
+
auth: { bearer_env: FLY_API_TOKEN }
|
|
17
|
+
assumptions:
|
|
18
|
+
- id: FM1
|
|
19
|
+
verified: true
|
|
20
|
+
text: "The Machines API's public base URL is `https://api.machines.dev`, its paths start with `/v1`, and requests carry `Authorization: Bearer <fly_api_token>`."
|
|
21
|
+
source: https://docs.fly.io/machines/api/working-with-machines-api/
|
|
22
|
+
- id: FM2
|
|
23
|
+
verified: true
|
|
24
|
+
text: "`POST /v1/apps` with `app_name` (unique) and `org_slug` (both required) and optional `network` creates an app and answers 201 with `id` and `created_at`."
|
|
25
|
+
source: https://docs.fly.io/machines/api/apps-resource/
|
|
26
|
+
- id: FM3
|
|
27
|
+
verified: true
|
|
28
|
+
text: "`GET /v1/apps/{app_name}` answers the app (`id`, `name`, `status`, `organization { name, slug }`), so the name the plan chooses locates it."
|
|
29
|
+
source: https://docs.fly.io/machines/api/apps-resource/
|
|
30
|
+
- id: FM4
|
|
31
|
+
verified: true
|
|
32
|
+
text: "`DELETE /v1/apps/{app_name}` answers 202; Machines should be stopped first, and `?force=true` stops them and deletes the app at once, which is what the recipe's destroy sends (a PR app has the PR's Machines running)."
|
|
33
|
+
source: https://docs.fly.io/machines/api/apps-resource/
|
|
34
|
+
- id: FM5
|
|
35
|
+
verified: false
|
|
36
|
+
text: "Deleting the app deletes its secrets and its Machines with it; an app name another organization holds is refused with a conflict, which the adapter then fails to read (403/404) and reports."
|
|
37
|
+
ops:
|
|
38
|
+
app:
|
|
39
|
+
kind: resource
|
|
40
|
+
title: App
|
|
41
|
+
summary: "A Fly app per pull request, such as `demo-${ctx.scope}`, in an organization: created with `POST /v1/apps`, located by its name, deleted with its Machines on destroy (`?force=true`). Its secrets are `fly.secret` lines; the Machines are what the PR's `fly deploy` creates. Nothing about the app is compared besides its existence."
|
|
42
|
+
covers: [fly-secrets]
|
|
43
|
+
params:
|
|
44
|
+
app_name:
|
|
45
|
+
type: string
|
|
46
|
+
required: true
|
|
47
|
+
pattern: "^[a-z0-9][a-z0-9-]*$"
|
|
48
|
+
description: The app's name, unique across Fly.io.
|
|
49
|
+
example: "demo-${ctx.scope}"
|
|
50
|
+
org_slug:
|
|
51
|
+
type: string
|
|
52
|
+
required: true
|
|
53
|
+
description: The organization the app is created in.
|
|
54
|
+
example: acme
|
|
55
|
+
network:
|
|
56
|
+
type: string
|
|
57
|
+
description: A private network (6PN) to put the app on.
|
|
58
|
+
http:
|
|
59
|
+
read: { path: "/apps/{app_name}" }
|
|
60
|
+
create:
|
|
61
|
+
path: /apps
|
|
62
|
+
body: { app_name: { param: app_name }, org_slug: { param: org_slug }, network: { param: network } }
|
|
63
|
+
delete: { path: "/apps/{app_name}?force=true" }
|
|
64
|
+
sim:
|
|
65
|
+
id_from: app_name
|
|
66
|
+
live:
|
|
67
|
+
params: { org_slug: SPONSON_LIVE_FLY_ORG }
|
package/recipes/gcs.yaml
ADDED
|
@@ -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,124 @@
|
|
|
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: true
|
|
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`. Verified live 2026-10-11: create, then a re-apply that wrote nothing, then destroy, against api.github.com."
|
|
56
|
+
source: https://docs.github.com/en/rest/repos/webhooks
|
|
57
|
+
- id: GH9
|
|
58
|
+
verified: false
|
|
59
|
+
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."
|
|
60
|
+
ops:
|
|
61
|
+
repo_webhook:
|
|
62
|
+
kind: resource
|
|
63
|
+
title: Repository webhook
|
|
64
|
+
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).
|
|
65
|
+
covers: [github-webhook]
|
|
66
|
+
params:
|
|
67
|
+
owner:
|
|
68
|
+
type: string
|
|
69
|
+
required: true
|
|
70
|
+
description: The repository's owner (user or organization).
|
|
71
|
+
example: acme
|
|
72
|
+
repo:
|
|
73
|
+
type: string
|
|
74
|
+
required: true
|
|
75
|
+
description: The repository's name.
|
|
76
|
+
example: app
|
|
77
|
+
url:
|
|
78
|
+
type: string
|
|
79
|
+
required: true
|
|
80
|
+
pattern: "^https?://\\S+$"
|
|
81
|
+
description: The URL GitHub delivers to; it identifies the hook in the repository.
|
|
82
|
+
example: "https://${ctx.scope}.preview.example.com/api/github"
|
|
83
|
+
events:
|
|
84
|
+
type: list
|
|
85
|
+
default: [push]
|
|
86
|
+
description: The events that trigger a delivery (`["*"]` for all).
|
|
87
|
+
active:
|
|
88
|
+
type: boolean
|
|
89
|
+
default: true
|
|
90
|
+
description: Whether deliveries are sent.
|
|
91
|
+
content_type:
|
|
92
|
+
type: string
|
|
93
|
+
default: json
|
|
94
|
+
enum: [json, form]
|
|
95
|
+
description: The payload's media type.
|
|
96
|
+
insecure_ssl:
|
|
97
|
+
type: string
|
|
98
|
+
default: "0"
|
|
99
|
+
enum: ["0", "1"]
|
|
100
|
+
description: '"1" skips verifying the TLS certificate of the URL.'
|
|
101
|
+
secret:
|
|
102
|
+
type: string
|
|
103
|
+
description: "The secret deliveries are signed with (write-only; GH6). Use `{ secret: … }`."
|
|
104
|
+
http:
|
|
105
|
+
find:
|
|
106
|
+
path: "/repos/{owner}/{repo}/hooks?per_page=100"
|
|
107
|
+
match: { /config/url: { param: url } }
|
|
108
|
+
create:
|
|
109
|
+
path: "/repos/{owner}/{repo}/hooks"
|
|
110
|
+
body: { name: web, config: { secret: { param: secret } } }
|
|
111
|
+
update:
|
|
112
|
+
method: PATCH
|
|
113
|
+
path: "/repos/{owner}/{repo}/hooks/{id}"
|
|
114
|
+
body: { config: { url: { param: url }, secret: { param: secret } } }
|
|
115
|
+
delete: { path: "/repos/{owner}/{repo}/hooks/{id}" }
|
|
116
|
+
fields:
|
|
117
|
+
active: { param: active }
|
|
118
|
+
events: { param: events }
|
|
119
|
+
/config/content_type: { param: content_type }
|
|
120
|
+
/config/insecure_ssl: { param: insecure_ssl }
|
|
121
|
+
sim:
|
|
122
|
+
numeric_ids: true
|
|
123
|
+
live:
|
|
124
|
+
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 }
|