@sponson/adapters 0.4.0 → 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.
- 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/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 +88 -0
- package/dist/http-adapter-auth.js +437 -0
- package/dist/http-adapter-auth.js.map +1 -0
- package/dist/http-adapter-client.d.ts +51 -0
- package/dist/http-adapter-client.js +156 -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 +12 -0
- package/dist/index.js +24 -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 +26 -0
- package/dist/providers.js +36 -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/cloud_run.yaml +97 -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 +123 -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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Upstash QStash recipes (its own API and token; Upstash Redis is recipes/upstash.yaml).
|
|
3
|
+
# Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
4
|
+
provider: qstash
|
|
5
|
+
title: Upstash QStash
|
|
6
|
+
category: queues
|
|
7
|
+
verified_on: "2026-10-11"
|
|
8
|
+
docs:
|
|
9
|
+
- title: Authentication
|
|
10
|
+
url: https://upstash.com/docs/qstash/api/authentication
|
|
11
|
+
- title: Create a schedule
|
|
12
|
+
url: https://upstash.com/docs/qstash/api-reference/schedules/create-a-schedule
|
|
13
|
+
- title: Get a schedule
|
|
14
|
+
url: https://upstash.com/docs/qstash/api-reference/schedules/get-a-schedule
|
|
15
|
+
- title: Delete a schedule
|
|
16
|
+
url: https://upstash.com/docs/qstash/api-reference/schedules/delete-a-schedule
|
|
17
|
+
- title: API examples (raw destination URL in the path)
|
|
18
|
+
url: https://upstash.com/docs/qstash/overall/apiexamples
|
|
19
|
+
- title: Multi-region (base URLs, tokens)
|
|
20
|
+
url: https://upstash.com/docs/qstash/howto/multi-region
|
|
21
|
+
api:
|
|
22
|
+
base_url: https://qstash.upstash.io
|
|
23
|
+
base_url_env: QSTASH_API_URL
|
|
24
|
+
auth: { bearer_env: QSTASH_TOKEN }
|
|
25
|
+
detect:
|
|
26
|
+
deps: ["@upstash/qstash"]
|
|
27
|
+
env: ["QSTASH_*"]
|
|
28
|
+
starter:
|
|
29
|
+
- op: schedule
|
|
30
|
+
values: { schedule_id: "cron-${ctx.scope}" }
|
|
31
|
+
where:
|
|
32
|
+
destination: "the preview's endpoint the schedule calls: its URL plus the path (a `{ from }` reference cannot carry a path)"
|
|
33
|
+
assumptions:
|
|
34
|
+
- id: QS1
|
|
35
|
+
verified: true
|
|
36
|
+
text: "Requests authenticate with the QStash token as `Authorization: Bearer`; the SDKs read it from `QSTASH_TOKEN`."
|
|
37
|
+
source: https://upstash.com/docs/qstash/api/authentication
|
|
38
|
+
- id: QS2
|
|
39
|
+
verified: true
|
|
40
|
+
text: "Each region (`qstash.upstash.io` = EU, `qstash-us-east-1.upstash.io`) has its own token and its own schedules; the default here is the EU URL, and a US account overrides `base_url` in its block."
|
|
41
|
+
source: https://upstash.com/docs/qstash/howto/multi-region
|
|
42
|
+
- id: QS3
|
|
43
|
+
verified: true
|
|
44
|
+
text: "`POST /v2/schedules/{destination}` creates a schedule: the cron in `Upstash-Cron`, the method used to call the destination in `Upstash-Method` (default POST), the request body as the message delivered on every run. It answers `{ scheduleId }`."
|
|
45
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/create-a-schedule
|
|
46
|
+
- id: QS4
|
|
47
|
+
verified: true
|
|
48
|
+
text: "With `Upstash-Schedule-Id`, a schedule with that id is updated with the new settings if it exists (an upsert): the id is the client's, the read path names it, and the same request is both create and update."
|
|
49
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/create-a-schedule
|
|
50
|
+
- id: QS5
|
|
51
|
+
verified: true
|
|
52
|
+
text: "The official example writes the destination URL in the path as it is (`/v2/schedules/https://example.com`), hence the `{+destination}` placeholder."
|
|
53
|
+
source: https://upstash.com/docs/qstash/overall/apiexamples
|
|
54
|
+
- id: QS6
|
|
55
|
+
verified: false
|
|
56
|
+
text: "A destination URL with a query string arrives intact with its `?` escaped as `%3F` (a raw `?` would end the path); not documented."
|
|
57
|
+
- id: QS7
|
|
58
|
+
verified: true
|
|
59
|
+
text: "`GET /v2/schedules/{scheduleId}` answers the schedule bare with `scheduleId`, `cron`, `destination`, `method` and `body`, and 404 when there is none; `DELETE` on the same path removes it."
|
|
60
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/get-a-schedule
|
|
61
|
+
- id: QS8
|
|
62
|
+
verified: false
|
|
63
|
+
text: "Deleting a schedule that is already gone answers 404 (only 200 is documented), which destroy treats as done."
|
|
64
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/delete-a-schedule
|
|
65
|
+
- id: QS9
|
|
66
|
+
verified: true
|
|
67
|
+
text: "Schedule ids are alphanumerics, hyphens, periods and underscores (400 otherwise); a cron may carry a `CRON_TZ=<zone>` prefix and runs at most once a minute."
|
|
68
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/create-a-schedule
|
|
69
|
+
- id: QS10
|
|
70
|
+
verified: false
|
|
71
|
+
text: "`GET` returns `cron`, `destination` and `method` exactly as they were sent (the cron string unnormalised), so comparing them is not drift on every plan."
|
|
72
|
+
source: https://upstash.com/docs/qstash/api-reference/schedules/get-a-schedule
|
|
73
|
+
ops:
|
|
74
|
+
schedule:
|
|
75
|
+
kind: resource
|
|
76
|
+
title: Schedule
|
|
77
|
+
summary: "A cron schedule that calls a preview's URL, such as `/api/cron` of `pr-42`, under a schedule id per scope. Created and updated by the same upsert (QS4); a console edit of its cron, destination or method is drift; destroy deletes it. The message body is sent on create and update but not compared."
|
|
78
|
+
covers: [qstash-schedule]
|
|
79
|
+
params:
|
|
80
|
+
schedule_id:
|
|
81
|
+
type: string
|
|
82
|
+
required: true
|
|
83
|
+
pattern: "^[A-Za-z0-9._-]+$"
|
|
84
|
+
description: The schedule's id, chosen by the plan (one per scope).
|
|
85
|
+
example: "cron-${ctx.scope}"
|
|
86
|
+
destination:
|
|
87
|
+
type: string
|
|
88
|
+
required: true
|
|
89
|
+
pattern: "^https?://"
|
|
90
|
+
description: The URL the schedule calls.
|
|
91
|
+
example: "https://${ctx.scope}.preview.example.com/api/cron"
|
|
92
|
+
cron:
|
|
93
|
+
type: string
|
|
94
|
+
required: true
|
|
95
|
+
description: "The cron expression, optionally prefixed `CRON_TZ=<zone> `."
|
|
96
|
+
example: "*/15 * * * *"
|
|
97
|
+
method:
|
|
98
|
+
type: string
|
|
99
|
+
default: POST
|
|
100
|
+
enum: [GET, POST, PUT, PATCH, DELETE]
|
|
101
|
+
description: The HTTP method used to call the destination.
|
|
102
|
+
body:
|
|
103
|
+
type: map
|
|
104
|
+
description: The JSON message delivered on every run (sent, not compared).
|
|
105
|
+
http:
|
|
106
|
+
read: { path: "/v2/schedules/{schedule_id}" }
|
|
107
|
+
create:
|
|
108
|
+
path: "/v2/schedules/{+destination}"
|
|
109
|
+
headers:
|
|
110
|
+
Upstash-Schedule-Id: { param: schedule_id }
|
|
111
|
+
Upstash-Cron: { param: cron }
|
|
112
|
+
Upstash-Method: { param: method }
|
|
113
|
+
send_fields: false
|
|
114
|
+
body: { param: body }
|
|
115
|
+
update:
|
|
116
|
+
method: POST
|
|
117
|
+
path: "/v2/schedules/{+destination}"
|
|
118
|
+
headers:
|
|
119
|
+
Upstash-Schedule-Id: { param: schedule_id }
|
|
120
|
+
Upstash-Cron: { param: cron }
|
|
121
|
+
Upstash-Method: { param: method }
|
|
122
|
+
send_fields: false
|
|
123
|
+
body: { param: body }
|
|
124
|
+
id_path: /scheduleId
|
|
125
|
+
fields:
|
|
126
|
+
cron: { param: cron }
|
|
127
|
+
destination: { param: destination }
|
|
128
|
+
method: { param: method }
|
|
129
|
+
sim:
|
|
130
|
+
collection: /v2/schedules
|
|
131
|
+
header_fields: { upstash-schedule-id: scheduleId, upstash-cron: cron, upstash-method: method }
|
|
132
|
+
suffix_field: destination
|
|
133
|
+
live:
|
|
134
|
+
params: { destination: SPONSON_LIVE_QSTASH_DESTINATION }
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Railway public GraphQL API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
3
|
+
provider: railway
|
|
4
|
+
title: Railway
|
|
5
|
+
category: deploy
|
|
6
|
+
verified_on: "2026-10-11"
|
|
7
|
+
docs:
|
|
8
|
+
- title: Public API (endpoint, tokens, errors)
|
|
9
|
+
url: https://docs.railway.com/reference/public-api
|
|
10
|
+
- title: Manage Variables with the Public API
|
|
11
|
+
url: https://docs.railway.com/guides/manage-variables
|
|
12
|
+
- title: Environments (PR environments)
|
|
13
|
+
url: https://docs.railway.com/reference/environments
|
|
14
|
+
- title: CLI (RAILWAY_TOKEN and RAILWAY_API_TOKEN)
|
|
15
|
+
url: https://docs.railway.com/guides/cli
|
|
16
|
+
- title: railwayapp/cli GraphQL schema
|
|
17
|
+
url: https://github.com/railwayapp/cli/blob/master/src/gql/schema.json
|
|
18
|
+
api:
|
|
19
|
+
base_url: https://backboard.railway.com/graphql/v2
|
|
20
|
+
base_url_env: RAILWAY_API_URL
|
|
21
|
+
auth: { bearer_env: RAILWAY_API_TOKEN }
|
|
22
|
+
detect:
|
|
23
|
+
env: ["RAILWAY_*"]
|
|
24
|
+
files: [railway.json, railway.toml]
|
|
25
|
+
starter:
|
|
26
|
+
- op: variable
|
|
27
|
+
where:
|
|
28
|
+
project_id: "Railway → your project → Settings → Project ID, or `railway status --json`"
|
|
29
|
+
environment_id: "the pull request environment Railway created: its Settings, or `railway environment`"
|
|
30
|
+
assumptions:
|
|
31
|
+
- id: RY1
|
|
32
|
+
verified: true
|
|
33
|
+
text: "The public API is one GraphQL endpoint, `https://backboard.railway.com/graphql/v2`; account, workspace and OAuth tokens are sent as `Authorization: Bearer <token>`, project tokens in `Project-Access-Token` instead (a project token therefore needs a hand-written block with `auth: { header: Project-Access-Token, value_env: RAILWAY_TOKEN }`)."
|
|
34
|
+
source: https://docs.railway.com/reference/public-api
|
|
35
|
+
- id: RY2
|
|
36
|
+
verified: true
|
|
37
|
+
text: "The Railway CLI reads an account or workspace token from `RAILWAY_API_TOKEN` and a project token from `RAILWAY_TOKEN`, so the bearer credential of this recipe is `RAILWAY_API_TOKEN`."
|
|
38
|
+
source: https://docs.railway.com/guides/cli
|
|
39
|
+
- id: RY3
|
|
40
|
+
verified: true
|
|
41
|
+
text: "Failures may be HTTP 200 with an `errors` array (a denied query among them); 400 is a parse or validation failure, 429 a rate limit with `Retry-After`. Queries are safe to retry; a mutation only when no answer arrived."
|
|
42
|
+
source: https://docs.railway.com/reference/public-api
|
|
43
|
+
- id: RY4
|
|
44
|
+
verified: true
|
|
45
|
+
text: "`variables(projectId, environmentId, serviceId?, unrendered?)` answers the variables of a service in an environment (without `serviceId`, the environment's shared variables) as a map of name to value; `unrendered: true` answers references such as `${{Postgres.DATABASE_URL}}` as written instead of resolved, so a value is compared as it was set."
|
|
46
|
+
source: https://docs.railway.com/guides/manage-variables
|
|
47
|
+
- id: RY5
|
|
48
|
+
verified: true
|
|
49
|
+
text: "`variableUpsert(input: { projectId, environmentId, serviceId?, name, value, skipDeploys? })` creates or replaces one variable (without `serviceId`, a shared variable); by default it triggers a redeploy of the service (`skipDeploys` defaults to false)."
|
|
50
|
+
source: https://docs.railway.com/guides/manage-variables
|
|
51
|
+
- id: RY6
|
|
52
|
+
verified: true
|
|
53
|
+
text: "`variableDelete(input: { projectId, environmentId, serviceId?, name })` deletes one variable."
|
|
54
|
+
source: https://docs.railway.com/guides/manage-variables
|
|
55
|
+
- id: RY7
|
|
56
|
+
verified: true
|
|
57
|
+
text: "Railway creates a PR environment when a pull request is opened (with PR environments enabled) and deletes it, with its variables, when the pull request is merged or closed. The recipe takes the environment's id as a param: the plan copies it from the dashboard, `railway status`, or a line that outputs it; the recipe does not look environments up by name (their naming is not documented)."
|
|
58
|
+
source: https://docs.railway.com/reference/environments
|
|
59
|
+
- id: RY8
|
|
60
|
+
verified: false
|
|
61
|
+
text: "Deleting a variable whose environment Railway already removed (the pull request closed) is an error saying the environment was not found, which the adapter reads as already gone; deleting a variable that does not exist answers `true`."
|
|
62
|
+
- id: RY9
|
|
63
|
+
verified: false
|
|
64
|
+
text: "Sponson does not wait for the redeploy a write triggers (RY5): a step that needs the running service to see the value waits on Railway's deployment status itself. A reference value written as `${{…}}` is not supported in a plan, where `${…}` is Sponson's own interpolation."
|
|
65
|
+
ops:
|
|
66
|
+
variable:
|
|
67
|
+
kind: resource
|
|
68
|
+
title: Variable of a service in an environment
|
|
69
|
+
summary: "One variable of a service (or, without `service_id`, a shared variable) in a Railway environment, typically the PR environment Railway created. Upserted with `variableUpsert`, read unrendered from `variables`, removed with `variableDelete`. A console edit of the value is drift. Railway redeploys the service after each write unless `skip_deploys` is set; Sponson does not wait for that deploy (RY9)."
|
|
70
|
+
covers: [railway-variables]
|
|
71
|
+
params:
|
|
72
|
+
project_id:
|
|
73
|
+
type: string
|
|
74
|
+
required: true
|
|
75
|
+
description: The project's id.
|
|
76
|
+
example: prj-railway-demo
|
|
77
|
+
environment_id:
|
|
78
|
+
type: string
|
|
79
|
+
required: true
|
|
80
|
+
description: The environment's id (for a PR environment, the one Railway created for the pull request; RY7).
|
|
81
|
+
example: env-pr-42
|
|
82
|
+
service_id:
|
|
83
|
+
type: string
|
|
84
|
+
description: The service's id; without it, the variable is shared by the environment's services.
|
|
85
|
+
example: svc-web
|
|
86
|
+
name:
|
|
87
|
+
type: string
|
|
88
|
+
required: true
|
|
89
|
+
pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
90
|
+
description: The variable's name.
|
|
91
|
+
example: API_URL
|
|
92
|
+
value:
|
|
93
|
+
type: string
|
|
94
|
+
required: true
|
|
95
|
+
description: The variable's value (may be a `{ secret }` reference).
|
|
96
|
+
example: "https://api-${ctx.scope}.example.app"
|
|
97
|
+
skip_deploys:
|
|
98
|
+
type: boolean
|
|
99
|
+
description: Do not redeploy the service after a write (Railway's default is to redeploy).
|
|
100
|
+
http:
|
|
101
|
+
read:
|
|
102
|
+
graphql:
|
|
103
|
+
query: "query variables($projectId: String!, $environmentId: String!, $serviceId: String, $unrendered: Boolean) { variables(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId, unrendered: $unrendered) }"
|
|
104
|
+
variables: { projectId: { param: project_id }, environmentId: { param: environment_id }, serviceId: { param: service_id }, unrendered: true }
|
|
105
|
+
data_path: { join: ["/variables/", { param: name }] }
|
|
106
|
+
create:
|
|
107
|
+
graphql:
|
|
108
|
+
query: "mutation variableUpsert($input: VariableUpsertInput!) { variableUpsert(input: $input) }"
|
|
109
|
+
variables: { input: { projectId: { param: project_id }, environmentId: { param: environment_id }, serviceId: { param: service_id }, name: { param: name }, value: { param: value }, skipDeploys: { param: skip_deploys } } }
|
|
110
|
+
idempotent: true
|
|
111
|
+
update:
|
|
112
|
+
graphql:
|
|
113
|
+
query: "mutation variableUpsert($input: VariableUpsertInput!) { variableUpsert(input: $input) }"
|
|
114
|
+
variables: { input: { projectId: { param: project_id }, environmentId: { param: environment_id }, serviceId: { param: service_id }, name: { param: name }, value: { param: value }, skipDeploys: { param: skip_deploys } } }
|
|
115
|
+
idempotent: true
|
|
116
|
+
delete:
|
|
117
|
+
graphql:
|
|
118
|
+
query: "mutation variableDelete($input: VariableDeleteInput!) { variableDelete(input: $input) }"
|
|
119
|
+
variables: { input: { projectId: { param: project_id }, environmentId: { param: environment_id }, serviceId: { param: service_id }, name: { param: name } } }
|
|
120
|
+
fields:
|
|
121
|
+
value: { param: value }
|
|
122
|
+
sim:
|
|
123
|
+
provider: railway
|
|
124
|
+
seed: { environments: { env-pr-42: { projectId: prj-railway-demo, name: pr-42 } } }
|
|
125
|
+
present: /variables/env-pr-42~1svc-web/API_URL
|
|
126
|
+
drift:
|
|
127
|
+
changed: { vars.env-pr-42.svc-web.API_URL: "https://somewhere-else.example" }
|
|
128
|
+
missing: { vars.env-pr-42.svc-web.API_URL: delete }
|
|
129
|
+
live:
|
|
130
|
+
params: { project_id: SPONSON_LIVE_RAILWAY_PROJECT_ID, environment_id: SPONSON_LIVE_RAILWAY_ENVIRONMENT_ID, service_id: SPONSON_LIVE_RAILWAY_SERVICE_ID }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Render API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
3
|
+
provider: render
|
|
4
|
+
title: Render
|
|
5
|
+
category: deploy
|
|
6
|
+
verified_on: "2026-10-11"
|
|
7
|
+
docs:
|
|
8
|
+
- title: Authentication
|
|
9
|
+
url: https://api-docs.render.com/reference/authentication
|
|
10
|
+
- title: Retrieve environment variable
|
|
11
|
+
url: https://api-docs.render.com/reference/retrieve-env-var
|
|
12
|
+
- title: Add or update environment variable
|
|
13
|
+
url: https://api-docs.render.com/reference/update-env-var
|
|
14
|
+
- title: Delete environment variable
|
|
15
|
+
url: https://api-docs.render.com/reference/delete-env-var
|
|
16
|
+
- title: Update environment variables (replace all; deploy behaviour)
|
|
17
|
+
url: https://api-docs.render.com/reference/update-env-vars-for-service
|
|
18
|
+
- title: Service previews
|
|
19
|
+
url: https://render.com/docs/service-previews
|
|
20
|
+
- title: Render CLI (RENDER_API_KEY)
|
|
21
|
+
url: https://render.com/docs/cli
|
|
22
|
+
api:
|
|
23
|
+
base_url: https://api.render.com/v1
|
|
24
|
+
base_url_env: RENDER_API_URL
|
|
25
|
+
auth: { bearer_env: RENDER_API_KEY }
|
|
26
|
+
detect:
|
|
27
|
+
env: ["RENDER_*"]
|
|
28
|
+
files: [render.yaml]
|
|
29
|
+
starter:
|
|
30
|
+
- op: env_var
|
|
31
|
+
where:
|
|
32
|
+
service_id: "Render Dashboard → the preview service: the `srv-…` id in its URL"
|
|
33
|
+
assumptions:
|
|
34
|
+
- id: RE1
|
|
35
|
+
verified: true
|
|
36
|
+
text: "Requests authenticate with an API key as `Authorization: Bearer`; the Render CLI reads it from `RENDER_API_KEY`."
|
|
37
|
+
source: https://api-docs.render.com/reference/authentication
|
|
38
|
+
- id: RE2
|
|
39
|
+
verified: true
|
|
40
|
+
text: "`GET /services/{serviceId}/env-vars/{envVarKey}` answers the variable bare, `{ key, value }`, and 404 when it does not exist."
|
|
41
|
+
source: https://api-docs.render.com/reference/retrieve-env-var
|
|
42
|
+
- id: RE3
|
|
43
|
+
verified: true
|
|
44
|
+
text: "`PUT /services/{serviceId}/env-vars/{envVarKey}` with `{ value }` adds the variable or replaces its value (200, `{ key, value }`): a create whose answer was lost is the same request again, and the variable's name is its identity."
|
|
45
|
+
source: https://api-docs.render.com/reference/update-env-var
|
|
46
|
+
- id: RE4
|
|
47
|
+
verified: true
|
|
48
|
+
text: "`DELETE /services/{serviceId}/env-vars/{envVarKey}` removes the variable (204)."
|
|
49
|
+
source: https://api-docs.render.com/reference/delete-env-var
|
|
50
|
+
- id: RE5
|
|
51
|
+
verified: false
|
|
52
|
+
text: "Deleting a variable that is already gone answers 404 (documented only as \"Unable to find the requested resource\"), which destroy treats as done."
|
|
53
|
+
source: https://api-docs.render.com/reference/delete-env-var
|
|
54
|
+
- id: RE6
|
|
55
|
+
verified: true
|
|
56
|
+
text: "Changing environment variables through the API does not deploy the service, whatever its autoDeploy setting (stated for the replace-all endpoint); a running preview sees a new value only after its next deploy."
|
|
57
|
+
source: https://api-docs.render.com/reference/update-env-vars-for-service
|
|
58
|
+
- id: RE7
|
|
59
|
+
verified: false
|
|
60
|
+
text: "The single-variable PUT does not deploy either (its page is silent; assumed like the replace-all endpoint)."
|
|
61
|
+
- id: RE8
|
|
62
|
+
verified: true
|
|
63
|
+
text: "Render creates service previews and preview environments itself (per pull request, from the service's Previews setting or `render.yaml`); a preview copies the base service's variables once, when it is created. No API looks up the preview of pull request N, so the line takes the service's id."
|
|
64
|
+
source: https://render.com/docs/service-previews
|
|
65
|
+
- id: RE9
|
|
66
|
+
verified: false
|
|
67
|
+
text: "`GET` returns the value in plain text for every variable, including ones generated with `generateValue`, so the value can be compared for drift."
|
|
68
|
+
source: https://api-docs.render.com/reference/retrieve-env-var
|
|
69
|
+
ops:
|
|
70
|
+
env_var:
|
|
71
|
+
kind: resource
|
|
72
|
+
title: Service environment variable
|
|
73
|
+
summary: "One environment variable on a Render service, such as a preview service's `API_URL`, written by name (`PUT` adds or replaces it). A console edit of its value is drift; destroy deletes it. The service's id is a param: Render creates preview services itself, and writing a variable does not deploy (RE6)."
|
|
74
|
+
covers: [render-env]
|
|
75
|
+
params:
|
|
76
|
+
service_id:
|
|
77
|
+
type: string
|
|
78
|
+
required: true
|
|
79
|
+
pattern: "^[a-z]{3}-[a-z0-9]+$"
|
|
80
|
+
description: The service's id (`srv-…`, in the service's dashboard URL and settings).
|
|
81
|
+
example: srv-d0a1b2c3d4e5f6g7h8i9
|
|
82
|
+
key:
|
|
83
|
+
type: string
|
|
84
|
+
required: true
|
|
85
|
+
pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
86
|
+
description: The variable's name.
|
|
87
|
+
example: API_URL
|
|
88
|
+
value:
|
|
89
|
+
type: string
|
|
90
|
+
required: true
|
|
91
|
+
description: "The variable's value: a literal, `{ from }` or `{ secret }`."
|
|
92
|
+
example: "https://api-${ctx.scope}.example.com"
|
|
93
|
+
http:
|
|
94
|
+
read: { path: "/services/{service_id}/env-vars/{key}" }
|
|
95
|
+
create: { method: PUT, path: "/services/{service_id}/env-vars/{key}" }
|
|
96
|
+
update: { method: PUT }
|
|
97
|
+
id_path: /key
|
|
98
|
+
fields:
|
|
99
|
+
value: { param: value }
|
|
100
|
+
sim:
|
|
101
|
+
collection: "/services/{service_id}/env-vars"
|
|
102
|
+
live:
|
|
103
|
+
params: { service_id: SPONSON_LIVE_RENDER_SERVICE_ID }
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# Resend API recipes. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
3
|
+
provider: resend
|
|
4
|
+
title: Resend
|
|
5
|
+
category: email
|
|
6
|
+
verified_on: "2026-10-11"
|
|
7
|
+
docs:
|
|
8
|
+
- title: Create API key
|
|
9
|
+
url: https://resend.com/docs/api-reference/api-keys/create-api-key
|
|
10
|
+
- title: List API keys
|
|
11
|
+
url: https://resend.com/docs/api-reference/api-keys/list-api-keys
|
|
12
|
+
- title: Delete API key
|
|
13
|
+
url: https://resend.com/docs/api-reference/api-keys/delete-api-key
|
|
14
|
+
- title: Create webhook
|
|
15
|
+
url: https://resend.com/docs/api-reference/webhooks/create-webhook
|
|
16
|
+
- title: Retrieve webhook
|
|
17
|
+
url: https://resend.com/docs/api-reference/webhooks/get-webhook
|
|
18
|
+
- title: List webhooks
|
|
19
|
+
url: https://resend.com/docs/api-reference/webhooks/list-webhooks
|
|
20
|
+
- title: Update webhook
|
|
21
|
+
url: https://resend.com/docs/api-reference/webhooks/update-webhook
|
|
22
|
+
- title: Delete webhook
|
|
23
|
+
url: https://resend.com/docs/api-reference/webhooks/delete-webhook
|
|
24
|
+
api:
|
|
25
|
+
base_url: https://api.resend.com
|
|
26
|
+
base_url_env: RESEND_API_URL
|
|
27
|
+
auth: { bearer_env: RESEND_API_KEY }
|
|
28
|
+
detect:
|
|
29
|
+
deps: [resend]
|
|
30
|
+
env: ["RESEND_*"]
|
|
31
|
+
starter:
|
|
32
|
+
- op: api_key
|
|
33
|
+
values: { name: "sponson-${ctx.env}-${ctx.scope}" }
|
|
34
|
+
assumptions:
|
|
35
|
+
- id: RS1
|
|
36
|
+
verified: true
|
|
37
|
+
text: "`POST /api-keys` creates an API key from `name` (at most 50 characters), `permission` (`full_access` or `sending_access`) and `domain_id` (with `sending_access` only), answering `{ id, token }`; requests carry `Authorization: Bearer <key>`."
|
|
38
|
+
source: https://resend.com/docs/api-reference/api-keys/create-api-key
|
|
39
|
+
- id: RS2
|
|
40
|
+
verified: true
|
|
41
|
+
text: "`GET /api-keys` answers `{ object: list, has_more, data: [{ id, name, created_at, last_used_at }] }`, every key when no `limit` is sent, and never the token: the token is shown only in the create answer, so it is a once-only output."
|
|
42
|
+
source: https://resend.com/docs/api-reference/api-keys/list-api-keys
|
|
43
|
+
- id: RS3
|
|
44
|
+
verified: true
|
|
45
|
+
text: "`DELETE /api-keys/{api_key_id}` removes a key; there is no endpoint to read or change one key, so its permission and domain are sent on create and never compared."
|
|
46
|
+
source: https://resend.com/docs/api-reference/api-keys/delete-api-key
|
|
47
|
+
- id: RS4
|
|
48
|
+
verified: true
|
|
49
|
+
text: "`POST /webhooks` creates a webhook from `endpoint` and `events` (both required), answering `{ object, id, signing_secret }`."
|
|
50
|
+
source: https://resend.com/docs/api-reference/webhooks/create-webhook
|
|
51
|
+
- id: RS5
|
|
52
|
+
verified: true
|
|
53
|
+
text: "`GET /webhooks/{webhook_id}` answers the webhook with `status` (`enabled`/`disabled`), `endpoint`, `events` and `signing_secret`: the secret stays readable, so it is an ordinary sensitive output."
|
|
54
|
+
source: https://resend.com/docs/api-reference/webhooks/get-webhook
|
|
55
|
+
- id: RS6
|
|
56
|
+
verified: true
|
|
57
|
+
text: "`GET /webhooks` answers `{ object: list, has_more, data: [{ id, created_at, status, endpoint, events }] }`, every webhook when no `limit` is sent."
|
|
58
|
+
source: https://resend.com/docs/api-reference/webhooks/list-webhooks
|
|
59
|
+
- id: RS7
|
|
60
|
+
verified: true
|
|
61
|
+
text: "`PATCH /webhooks/{webhook_id}` changes `endpoint`, `events` and `status`; `DELETE` on the same path removes the webhook."
|
|
62
|
+
source: https://resend.com/docs/api-reference/webhooks/update-webhook
|
|
63
|
+
- id: RS8
|
|
64
|
+
verified: false
|
|
65
|
+
text: "Key names and webhook endpoints are not unique in Resend: the recipe takes the name (keys) and the endpoint URL (webhooks) as the identity, and refuses (PARAM_INVALID) when two match, so give each environment its own name and URL."
|
|
66
|
+
- id: RS9
|
|
67
|
+
verified: false
|
|
68
|
+
text: "Creating a key or a webhook needs a `full_access` key; a deleted object answers 404."
|
|
69
|
+
ops:
|
|
70
|
+
api_key:
|
|
71
|
+
kind: resource
|
|
72
|
+
title: API key
|
|
73
|
+
summary: "A restricted API key per environment, such as `sponson-preview-pr-42` allowed only to send from one domain. Its token is a once-only output: pass it to the app's environment line in the same run (`{ from: <line>.token }`); later runs keep what that line holds, and `apply --recreate <line>` issues a new one. Only its existence is compared for drift: Resend shows no permission or domain for a key."
|
|
74
|
+
covers: [resend-key-webhook]
|
|
75
|
+
params:
|
|
76
|
+
name:
|
|
77
|
+
type: string
|
|
78
|
+
required: true
|
|
79
|
+
pattern: "^.{1,50}$"
|
|
80
|
+
description: The key's name, unique among the account's keys (RS8); its identity.
|
|
81
|
+
example: "sponson-${ctx.env}-${ctx.scope}"
|
|
82
|
+
permission:
|
|
83
|
+
type: string
|
|
84
|
+
default: sending_access
|
|
85
|
+
enum: [full_access, sending_access]
|
|
86
|
+
description: What the key may do.
|
|
87
|
+
domain_id:
|
|
88
|
+
type: string
|
|
89
|
+
description: With `sending_access`, the only domain the key may send from.
|
|
90
|
+
http:
|
|
91
|
+
find:
|
|
92
|
+
path: /api-keys
|
|
93
|
+
list_path: /data
|
|
94
|
+
match: { name: { param: name } }
|
|
95
|
+
create:
|
|
96
|
+
path: /api-keys
|
|
97
|
+
body: { permission: { param: permission }, domain_id: { param: domain_id } }
|
|
98
|
+
delete: { path: "/api-keys/{id}" }
|
|
99
|
+
outputs:
|
|
100
|
+
token: { path: /token, sensitive: true, once: true }
|
|
101
|
+
webhook:
|
|
102
|
+
kind: resource
|
|
103
|
+
title: Webhook
|
|
104
|
+
summary: "A webhook delivering an environment's email events to its URL, such as the preview's `/api/resend` handler. Located by its endpoint URL; a console edit of its events is drift (its enabled/disabled status is left to the console: the create request takes none). Its signing secret is a sensitive output, readable on every run."
|
|
105
|
+
covers: [resend-key-webhook]
|
|
106
|
+
params:
|
|
107
|
+
endpoint:
|
|
108
|
+
type: string
|
|
109
|
+
required: true
|
|
110
|
+
pattern: "^https://\\S+$"
|
|
111
|
+
description: The URL events are delivered to; its identity (RS8).
|
|
112
|
+
example: "https://${ctx.scope}.preview.example.com/api/resend"
|
|
113
|
+
events:
|
|
114
|
+
type: list
|
|
115
|
+
required: true
|
|
116
|
+
description: The event types to deliver, such as `email.delivered` and `email.bounced`.
|
|
117
|
+
example: [email.delivered, email.bounced]
|
|
118
|
+
http:
|
|
119
|
+
find:
|
|
120
|
+
path: /webhooks
|
|
121
|
+
list_path: /data
|
|
122
|
+
match: { endpoint: { param: endpoint } }
|
|
123
|
+
read: { path: "/webhooks/{id}" }
|
|
124
|
+
create: { path: /webhooks }
|
|
125
|
+
update: { method: PATCH, path: "/webhooks/{id}" }
|
|
126
|
+
delete: { path: "/webhooks/{id}" }
|
|
127
|
+
fields:
|
|
128
|
+
events: { param: events }
|
|
129
|
+
outputs:
|
|
130
|
+
signing_secret: { path: /signing_secret, sensitive: true }
|
package/recipes/s3.yaml
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../../../schema/recipe.schema.json
|
|
2
|
+
# S3 API recipes (Amazon S3, and Cloudflare R2 through its S3-compatible endpoint): XML documents, every request
|
|
3
|
+
# signed with AWS Signature Version 4. Format and checklist: docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
|
|
4
|
+
provider: s3
|
|
5
|
+
title: Amazon S3 / Cloudflare R2 (S3 API)
|
|
6
|
+
category: cors
|
|
7
|
+
verified_on: "2026-10-11"
|
|
8
|
+
docs:
|
|
9
|
+
- title: PutBucketCors
|
|
10
|
+
url: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html
|
|
11
|
+
- title: GetBucketCors
|
|
12
|
+
url: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html
|
|
13
|
+
- title: DeleteBucketCors
|
|
14
|
+
url: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html
|
|
15
|
+
- title: R2 S3 API compatibility
|
|
16
|
+
url: https://developers.cloudflare.com/r2/api/s3/api/
|
|
17
|
+
base_url_example: https://assets.s3.us-east-1.amazonaws.com
|
|
18
|
+
api:
|
|
19
|
+
base_url_env: S3_API_URL
|
|
20
|
+
encoding: xml
|
|
21
|
+
auth:
|
|
22
|
+
aws_sigv4: { region: us-east-1, service: s3, access_key_id_env: AWS_ACCESS_KEY_ID, secret_access_key_env: AWS_SECRET_ACCESS_KEY }
|
|
23
|
+
detect:
|
|
24
|
+
deps: ["@aws-sdk/client-s3"]
|
|
25
|
+
starter:
|
|
26
|
+
- op: bucket_cors
|
|
27
|
+
preview_url: origin
|
|
28
|
+
values: { rule_id: "sponson-${ctx.scope}" }
|
|
29
|
+
where:
|
|
30
|
+
base_url: "the bucket's endpoint: `https://<bucket>.s3.<region>.amazonaws.com`, or R2's `https://<account id>.r2.cloudflarestorage.com/<bucket>`"
|
|
31
|
+
assumptions:
|
|
32
|
+
- id: SS1
|
|
33
|
+
verified: true
|
|
34
|
+
text: "`PUT /?cors` on the bucket's endpoint replaces its whole CORS configuration with a `CORSConfiguration` document of up to 100 `CORSRule`s (each `ID`, `AllowedOrigin`s, `AllowedMethod`s, `AllowedHeader`s, `ExposeHeader`s, `MaxAgeSeconds`) and requires `Content-MD5`, which the SigV4 signer adds for `s3`."
|
|
35
|
+
source: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html
|
|
36
|
+
- id: SS2
|
|
37
|
+
verified: true
|
|
38
|
+
text: "`GET /?cors` answers the configuration as XML."
|
|
39
|
+
source: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html
|
|
40
|
+
- id: SS3
|
|
41
|
+
verified: true
|
|
42
|
+
text: "`DELETE /?cors` removes the configuration (the way to leave a bucket with no rule)."
|
|
43
|
+
source: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html
|
|
44
|
+
- id: SS4
|
|
45
|
+
verified: true
|
|
46
|
+
text: "R2 implements PutBucketCors, GetBucketCors and DeleteBucketCors at `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`; its SigV4 region is `auto`, and `us-east-1` is accepted as an alias, so the recipe's region works for R2 unchanged."
|
|
47
|
+
source: https://developers.cloudflare.com/r2/api/s3/api/
|
|
48
|
+
- id: SS5
|
|
49
|
+
verified: false
|
|
50
|
+
text: "A bucket without a CORS configuration answers 404 `NoSuchCORSConfiguration` (read as an empty list, `parent.if_missing: empty`), and an empty configuration is refused as `MalformedXML` (so the last rule is removed with DELETE, `parent.when_empty: delete`)."
|
|
51
|
+
- id: SS6
|
|
52
|
+
verified: false
|
|
53
|
+
text: "R2 accepts path-style requests (`https://<ACCOUNT_ID>.r2.cloudflarestorage.com/<bucket>/?cors`), so its base URL names the bucket in the path."
|
|
54
|
+
- id: SS7
|
|
55
|
+
verified: false
|
|
56
|
+
text: "S3 answers a rule's elements as they were written; a rule's `ID` identifies it among the bucket's rules (IDs are not required to be unique by S3, so other writers must not reuse the recipe's)."
|
|
57
|
+
ops:
|
|
58
|
+
bucket_cors:
|
|
59
|
+
kind: list_item
|
|
60
|
+
title: CORS rule of a bucket
|
|
61
|
+
summary: "A CORS rule of an S3 or R2 bucket allowing the preview's origin, identified by its `ID` (such as `sponson-pr-42`); every other rule is left as it is. The base URL names the bucket: `https://<bucket>.s3.<region>.amazonaws.com` (set `auth.aws_sigv4.region` to the bucket's region), or `https://<account>.r2.cloudflarestorage.com/<bucket>` for R2. A console edit of the rule is drift; destroy removes only this rule (the whole configuration when it was the last one)."
|
|
62
|
+
covers: [s3-r2-cors]
|
|
63
|
+
params:
|
|
64
|
+
rule_id:
|
|
65
|
+
type: string
|
|
66
|
+
required: true
|
|
67
|
+
pattern: "^.{1,255}$"
|
|
68
|
+
description: The rule's `ID`, at most 255 characters; its identity (SS7).
|
|
69
|
+
example: "sponson-${ctx.scope}"
|
|
70
|
+
origin:
|
|
71
|
+
type: string
|
|
72
|
+
required: true
|
|
73
|
+
pattern: "^(https?://\\S+|\\*)$"
|
|
74
|
+
description: The origin allowed (the preview's URL, without a path).
|
|
75
|
+
example: "https://${ctx.scope}.preview.example.app"
|
|
76
|
+
methods:
|
|
77
|
+
type: list
|
|
78
|
+
default: [GET]
|
|
79
|
+
description: "The methods allowed: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`."
|
|
80
|
+
allowed_headers:
|
|
81
|
+
type: list
|
|
82
|
+
description: "Request headers a preflight may ask for (`*` for any); absent: none."
|
|
83
|
+
expose_headers:
|
|
84
|
+
type: list
|
|
85
|
+
description: Response headers the browser may read; absent, none.
|
|
86
|
+
max_age_seconds:
|
|
87
|
+
type: string
|
|
88
|
+
default: "3000"
|
|
89
|
+
pattern: "^[0-9]+$"
|
|
90
|
+
description: How long a preflight answer may be cached, in seconds (a string, as XML carries it).
|
|
91
|
+
http:
|
|
92
|
+
parent: { path: "/?cors", method: PUT, if_missing: empty, when_empty: delete }
|
|
93
|
+
list_path: /CORSConfiguration/CORSRule
|
|
94
|
+
key_field: ID
|
|
95
|
+
item:
|
|
96
|
+
AllowedHeader: { param: allowed_headers }
|
|
97
|
+
AllowedMethod: { param: methods }
|
|
98
|
+
AllowedOrigin: { param: origin }
|
|
99
|
+
ExposeHeader: { param: expose_headers }
|
|
100
|
+
ID: { param: rule_id }
|
|
101
|
+
MaxAgeSeconds: { param: max_age_seconds }
|
|
102
|
+
sim:
|
|
103
|
+
provider: aws
|
|
104
|
+
base_path: /aws/s3/assets
|
|
105
|
+
seed: { buckets: { assets: { cors: null } } }
|
|
106
|
+
present: /buckets/assets/cors/0
|
|
107
|
+
drift:
|
|
108
|
+
missing: { s3.assets.cors: "remove:sponson-pr-42" }
|