@sponson/adapters 0.9.2 → 0.10.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.
@@ -0,0 +1,186 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Mailgun API recipes: an account webhook and an inbound route per preview. Format and checklist:
3
+ # docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: mailgun
5
+ title: Mailgun
6
+ category: email
7
+ verified_on: "2026-10-12"
8
+ docs:
9
+ - title: Mailgun API OpenAPI document (every operation and schema below)
10
+ url: https://documentation.mailgun.com/_bundle/docs/mailgun/api-reference/send/mailgun.yaml
11
+ - title: Authentication (HTTP Basic, user `api`)
12
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/authentication/
13
+ - title: Account Webhooks
14
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks
15
+ - title: Create an account-level webhook
16
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks/post-v1-webhooks
17
+ - title: Get account-level webhook by ID
18
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks/get-v1-webhooks--webhook-id-
19
+ - title: Update an account-level webhook
20
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks/put-v1-webhooks--webhook-id-
21
+ - title: Domain Webhooks (the v3 and v4 per-domain alternatives)
22
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/domain-webhooks
23
+ - title: Routes
24
+ url: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/routes
25
+ - title: Route actions
26
+ url: https://documentation.mailgun.com/docs/mailgun/user-manual/receive-forward-store/route-actions
27
+ api:
28
+ base_url: https://api.mailgun.net
29
+ base_url_env: MAILGUN_API_URL
30
+ auth: { basic: { user: api, password_env: MAILGUN_API_KEY } }
31
+ encoding: form
32
+ # Lists go as the key repeated (`action=…&action=…`, `event_types=…&event_types=…`): MG4.
33
+ form_arrays: repeat
34
+ credential:
35
+ url: "https://documentation.mailgun.com/docs/mailgun/api-reference/authentication/"
36
+ scope: "An account API key (Account Settings > API Keys): Domain Sending Keys 'only allow sending messages' and cannot manage webhooks or routes."
37
+ source: "https://documentation.mailgun.com/docs/mailgun/api-reference/authentication/"
38
+ verify: "GET /v1/webhooks"
39
+ detect:
40
+ deps: [mailgun.js, mailgun-js]
41
+ env: ["MAILGUN_*"]
42
+ starter:
43
+ - op: webhook
44
+ preview_url: url
45
+ values: { name: "sponson-${ctx.env}-${ctx.scope}" }
46
+ check: [path]
47
+ assumptions:
48
+ - id: MG1
49
+ verified: true
50
+ text: "Requests authenticate with HTTP Basic, the user name `api` and an API key as the password; the US API is `https://api.mailgun.net`, the EU one `https://api.eu.mailgun.net` (an EU account sets `base_url` in the provider block)."
51
+ source: https://documentation.mailgun.com/_bundle/docs/mailgun/api-reference/send/mailgun.yaml
52
+ - id: MG2
53
+ verified: true
54
+ text: "`POST /v1/webhooks` creates an account-level webhook from `url` (required), `event_types` (required, one of accepted, clicked, opened, unsubscribed, delivered, permanent_fail, temporary_fail, complained; repeated for several) and `description`, and answers `{ webhook_id }` only; it receives the events of every domain of the account. At most three URLs per event type (a fourth answers 409 'unable to add more than 3 urls for event type …'), so at most three previews (fewer when the account has its own webhooks) can each have one at a time."
55
+ source: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks/post-v1-webhooks
56
+ - id: MG3
57
+ verified: true
58
+ text: "`GET /v1/webhooks` answers `{ webhooks: [...] }` and `GET /v1/webhooks/{webhook_id}` one webhook, each with `webhook_id`, `description`, `url`, `event_types` (a list) and `created_at`; `PUT /v1/webhooks/{webhook_id}` replaces url, description and event types whole (204), `DELETE` on the same path removes it (204), and an unknown id answers 404."
59
+ source: https://documentation.mailgun.com/_bundle/docs/mailgun/api-reference/send/mailgun.yaml
60
+ - id: MG4
61
+ verified: true
62
+ text: "Lists in request bodies are the key repeated: the OpenAPI document declares `action` with `style: form, explode: true` and says to use `event_types` 'multiple times to specify multiple event types', hence `form_arrays: repeat`."
63
+ source: https://documentation.mailgun.com/_bundle/docs/mailgun/api-reference/send/mailgun.yaml
64
+ - id: MG5
65
+ verified: false
66
+ text: "The account-webhook operations, documented as `multipart/form-data`, accept the same fields `application/x-www-form-urlencoded`, as Mailgun's v3 operations (documented so) do."
67
+ - id: MG6
68
+ verified: true
69
+ text: "Webhook changes 'can take up to 10 minutes to become effective due to caching': a new preview may miss its first events."
70
+ source: https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/account-webhooks
71
+ - id: MG7
72
+ verified: true
73
+ text: "Routes are account-wide ('defined globally, per account, not per domain'). `POST /v3/routes` takes `priority` (default 0, lower first), `description`, `expression` (required) and `action` (repeated), and answers `{ message, route }`; `GET /v3/routes/{id}` answers `{ route }` (404 when unknown); `GET /v3/routes` answers `{ total_count, items }`, `limit` up to 1000; `PUT /v3/routes/{id}` updates only the fields sent and answers `{ message, route }`; `DELETE /v3/routes/{id}` removes it. A route object carries `id`, `priority`, `description`, `expression`, `actions` (a list) and `created_at`."
74
+ source: https://documentation.mailgun.com/_bundle/docs/mailgun/api-reference/send/mailgun.yaml
75
+ - id: MG8
76
+ verified: true
77
+ text: "`forward(\"https://…\")` posts a matching message to the URL; every route is evaluated unless a higher-priority one runs `stop()`; `match_recipient('regex')` filters on the recipient. The receiving domain needs its MX records pointing at Mailgun."
78
+ source: https://documentation.mailgun.com/docs/mailgun/user-manual/receive-forward-store/route-actions
79
+ - id: MG9
80
+ verified: false
81
+ text: "The API answers `description`, `url`, `event_types`, `expression` and `actions` as they were written, so an unchanged webhook or route hashes the same on every plan, and a route's description (not required to be unique by Mailgun) or a webhook's identifies what this recipe made: two equal ones make `find` refuse with PARAM_INVALID."
82
+ - id: MG10
83
+ verified: false
84
+ text: "A per-domain webhook (v3 `…/domains/{domain}/webhooks/{event}`, a list of up to three URLs per event; v4 `…/webhooks?url=`, answered as a map of event to URLs) is not an object of its own that a later run can read back by an id, so this recipe uses account webhooks; the preview's handler must ignore events of mail it did not send (every webhook gets every domain's events)."
85
+ ops:
86
+ webhook:
87
+ kind: resource
88
+ title: Account webhook
89
+ summary: "An account-level webhook that posts the chosen email events to the preview's own URL, `url` + `path` (usually `{ from: web.preview_url }`). Identified by its description (`name`), so a new preview URL updates it in place. It receives every domain's events (MG10), and Mailgun takes at most three URLs per event type (MG2): at most three previews at a time. A console edit of its URL or events is drift."
90
+ covers: [mailgun-webhook-route]
91
+ params:
92
+ name:
93
+ type: string
94
+ required: true
95
+ pattern: "^[A-Za-z0-9_.:/ -]{1,200}$"
96
+ description: "The webhook's identity, kept as its description: unique per preview, such as `sponson-${ctx.env}-${ctx.scope}`."
97
+ example: "sponson-${ctx.env}-${ctx.scope}"
98
+ url:
99
+ type: string
100
+ required: true
101
+ pattern: "^https://\\S*[^/]$"
102
+ description: "The preview's base URL, without a trailing slash: usually `{ from: <deploy line>.preview_url }`."
103
+ example: https://pr-42.preview.example.com
104
+ path:
105
+ type: string
106
+ default: /api/webhooks/mailgun
107
+ pattern: "^/\\S*$"
108
+ description: "Where the app handles Mailgun's events, appended to `url`."
109
+ event_types:
110
+ type: list
111
+ default: [delivered, permanent_fail, complained]
112
+ description: "The events posted: accepted, clicked, opened, unsubscribed, delivered, permanent_fail, temporary_fail, complained (MG2)."
113
+ http:
114
+ find:
115
+ path: /v1/webhooks
116
+ list_path: /webhooks
117
+ match: { description: { param: name } }
118
+ read: { path: "/v1/webhooks/{id}" }
119
+ create: { path: /v1/webhooks, locate: true }
120
+ update: { method: PUT, path: "/v1/webhooks/{id}" }
121
+ id_path: /webhook_id
122
+ fields:
123
+ url: { join: [{ param: url }, { param: path }] }
124
+ event_types: { param: event_types }
125
+ route:
126
+ kind: resource
127
+ title: Inbound route
128
+ summary: "An inbound route per preview that forwards mail matching `expression` (such as `match_recipient('pr-42@inbound.example.com')`) to the preview's own URL, `url` + `path`. Identified by its description (`name`); a console edit of its priority, expression or action is drift. The receiving domain is long-lived and shared (MX records at Mailgun, MG8); each preview gets its own address on it."
129
+ covers: [mailgun-webhook-route]
130
+ params:
131
+ name:
132
+ type: string
133
+ required: true
134
+ pattern: "^[A-Za-z0-9_.:/ -]{1,200}$"
135
+ description: "The route's identity, kept as its description: unique per preview, such as `sponson-${ctx.env}-${ctx.scope}`."
136
+ example: "sponson-${ctx.env}-${ctx.scope}"
137
+ expression:
138
+ type: string
139
+ required: true
140
+ description: "The filter (MG8), such as `match_recipient('${ctx.scope}@inbound.example.com')`."
141
+ example: "match_recipient('${ctx.scope}@inbound.example.com')"
142
+ url:
143
+ type: string
144
+ required: true
145
+ pattern: "^https://\\S*[^/\"]$"
146
+ description: "The preview's base URL, without a trailing slash: usually `{ from: <deploy line>.preview_url }`."
147
+ example: https://pr-42.preview.example.com
148
+ path:
149
+ type: string
150
+ default: /api/inbound/mailgun
151
+ pattern: "^/[^\\s\"]*$"
152
+ description: "Where the app receives forwarded mail, appended to `url`."
153
+ priority:
154
+ type: integer
155
+ default: 0
156
+ description: "Lower runs first (MG7)."
157
+ http:
158
+ find:
159
+ path: "/v3/routes?limit=1000"
160
+ list_path: /items
161
+ match: { description: { param: name } }
162
+ read: { path: "/v3/routes/{id}" }
163
+ create:
164
+ path: /v3/routes
165
+ send_fields: false
166
+ body:
167
+ priority: { param: priority }
168
+ description: { param: name }
169
+ expression: { param: expression }
170
+ action: [{ join: ["forward(\"", { param: url }, { param: path }, "\")"] }]
171
+ update:
172
+ method: PUT
173
+ path: "/v3/routes/{id}"
174
+ send_fields: false
175
+ body:
176
+ priority: { param: priority }
177
+ description: { param: name }
178
+ expression: { param: expression }
179
+ action: [{ join: ["forward(\"", { param: url }, { param: path }, "\")"] }]
180
+ item_path: /route
181
+ fields:
182
+ priority: { param: priority }
183
+ expression: { param: expression }
184
+ actions: [{ join: ["forward(\"", { param: url }, { param: path }, "\")"] }]
185
+ sim:
186
+ write_fields: { action: actions }
@@ -0,0 +1,108 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # New Relic NerdGraph recipes: a change-tracking deployment marker per release. Format and checklist:
3
+ # docs/adr/0020-recipes.md, docs/adr/0022-one-off-actions.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: newrelic
5
+ title: New Relic
6
+ category: observability
7
+ verified_on: "2026-10-12"
8
+ docs:
9
+ - title: Track changes with NerdGraph (changeTrackingCreateDeployment, changeTrackingCreateEvent)
10
+ url: https://docs.newrelic.com/docs/change-tracking/change-tracking-graphql/
11
+ - title: Introduction to NerdGraph (endpoints, the API-Key header)
12
+ url: https://docs.newrelic.com/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/
13
+ - title: New Relic's Go client, the mutation as its CLI and Terraform provider send it (ChangeTrackingDeploymentInput)
14
+ url: https://github.com/newrelic/newrelic-client-go/blob/main/pkg/changetracking/changetracking_api.go
15
+ - title: User keys
16
+ url: https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/
17
+ api:
18
+ base_url: https://api.newrelic.com/graphql
19
+ base_url_env: NEW_RELIC_API_URL
20
+ auth: { header: API-Key, value_env: NEW_RELIC_API_KEY }
21
+ credential:
22
+ url: "https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/"
23
+ scope: "A user key (`NRAK-…`, API keys > Create a key > User) of a user who can create change tracking events for the entity's account."
24
+ source: "https://docs.newrelic.com/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/"
25
+ detect:
26
+ deps: [newrelic, "@newrelic/"]
27
+ env: ["NEW_RELIC_*"]
28
+ starter:
29
+ - op: deployment
30
+ values: { version: "${ctx.git.sha}", description: "${ctx.env} ${ctx.scope}" }
31
+ where:
32
+ entity_guid: "the GUID of the APM (or browser) entity the environment reports as: the entity's metadata in New Relic, or `newrelic entity search`"
33
+ assumptions:
34
+ - id: NR1
35
+ verified: true
36
+ text: "NerdGraph is one GraphQL endpoint, `https://api.newrelic.com/graphql` (EU accounts: `https://api.eu.newrelic.com/graphql`, set `base_url` in the provider block), taking a user key in the `API-Key` header and a JSON body `{ query, variables }`."
37
+ source: https://docs.newrelic.com/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/
38
+ - id: NR2
39
+ verified: true
40
+ text: "`changeTrackingCreateDeployment(deployment: ChangeTrackingDeploymentInput!)` records a deployment marker on an entity from `version` and `entityGuid` (both required) and optionally `user`, `description`, `deploymentType` (`BASIC`, `BLUE_GREEN`, `CANARY`, `ROLLING`, `SHADOW`, `OTHER`), `deepLink`, `commit`, `changelog`, `groupId` and `timestamp` (within 24 hours of now), and answers `deploymentId` and `entityGuid`. New Relic recommends `changeTrackingCreateEvent` for new work but says this mutation 'isn't being retired and continues to work'."
41
+ source: https://docs.newrelic.com/docs/change-tracking/change-tracking-graphql/
42
+ - id: NR3
43
+ verified: true
44
+ text: "The input type's name is `ChangeTrackingDeploymentInput`, as New Relic's own Go client declares the mutation's variable."
45
+ source: https://github.com/newrelic/newrelic-client-go/blob/main/pkg/changetracking/changetracking_api.go
46
+ - id: NR4
47
+ verified: false
48
+ text: "NerdGraph has no mutation that deletes or edits a deployment marker (the REST v2 deployment endpoints, which could delete one, reach end of life on 2027-07-31), so destroy sends nothing and forgets the record: a marker is history."
49
+ - id: NR5
50
+ verified: false
51
+ text: "NerdGraph takes no idempotency key: a marker sent again after a crash before Sponson recorded it is a second marker for the same version (harmless, at least once)."
52
+ - id: NR6
53
+ verified: false
54
+ text: "A preview that reports to its own entity (an app name per environment, such as `web (preview)`) gets its markers there; a preview sharing production's entity would mark production's charts, so give previews their own entity or leave this line to production."
55
+ ops:
56
+ deployment:
57
+ kind: action
58
+ title: Deployment marker
59
+ summary: "A change-tracking deployment marker on the environment's entity (`entity_guid`) for a release, sent once per entity and version (usually the commit). Nothing to undo: markers are history and NerdGraph deletes none (NR4)."
60
+ covers: [newrelic-deployment]
61
+ params:
62
+ entity_guid:
63
+ type: string
64
+ required: true
65
+ description: The GUID of the entity the environment reports as (NR6).
66
+ example: MXxBUE18QVBQTElDQVRJT058MTIzNDU2Nzg
67
+ version:
68
+ type: string
69
+ required: true
70
+ description: "The release's version, usually the commit: `\"${ctx.git.sha}\"`. A new one sends a new marker."
71
+ example: "${ctx.git.sha}"
72
+ description:
73
+ type: string
74
+ description: "Text shown with the marker, such as `preview pr-42`."
75
+ example: "${ctx.env} ${ctx.scope}"
76
+ user:
77
+ type: string
78
+ default: sponson
79
+ description: Who deployed.
80
+ commit:
81
+ type: string
82
+ description: The commit deployed.
83
+ deep_link:
84
+ type: string
85
+ pattern: "^https?://\\S+$"
86
+ description: A link to the deployment (the preview URL, or the CI run).
87
+ deployment_type:
88
+ type: string
89
+ default: BASIC
90
+ enum: [BASIC, BLUE_GREEN, CANARY, ROLLING, SHADOW, OTHER]
91
+ description: The kind of deployment (NR2).
92
+ http:
93
+ trigger: { entity_guid: { param: entity_guid }, version: { param: version } }
94
+ request:
95
+ graphql:
96
+ query: "mutation ($deployment: ChangeTrackingDeploymentInput!) { changeTrackingCreateDeployment(deployment: $deployment) { deploymentId entityGuid } }"
97
+ variables:
98
+ deployment:
99
+ entityGuid: { param: entity_guid }
100
+ version: { param: version }
101
+ description: { param: description }
102
+ user: { param: user }
103
+ commit: { param: commit }
104
+ deepLink: { param: deep_link }
105
+ deploymentType: { param: deployment_type }
106
+ outputs: { deployment_id: /changeTrackingCreateDeployment/deploymentId }
107
+ live:
108
+ params: { entity_guid: SPONSON_LIVE_NEW_RELIC_ENTITY_GUID }
@@ -0,0 +1,94 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Rollbar API recipes: a deploy per release in the environment. Format and checklist: docs/adr/0020-recipes.md,
3
+ # docs/adr/0022-one-off-actions.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: rollbar
5
+ title: Rollbar
6
+ category: observability
7
+ verified_on: "2026-10-12"
8
+ docs:
9
+ - title: Report a deploy
10
+ url: https://docs.rollbar.com/reference/post-deploy
11
+ - title: Deploy tracking
12
+ url: https://docs.rollbar.com/docs/deploy-tracking
13
+ - title: Project access tokens and their scopes
14
+ url: https://docs.rollbar.com/docs/access-tokens
15
+ - title: API reference index (deploy endpoints)
16
+ url: https://docs.rollbar.com/llms.txt
17
+ api:
18
+ base_url: https://api.rollbar.com/api/1
19
+ base_url_env: ROLLBAR_API_URL
20
+ auth: { header: X-Rollbar-Access-Token, value_env: ROLLBAR_ACCESS_TOKEN }
21
+ credential:
22
+ url: "https://docs.rollbar.com/docs/access-tokens"
23
+ scope: "A project access token with the `post_server_item` scope ('Can perform all POST requests to /deploy/ and /item/')."
24
+ source: "https://docs.rollbar.com/reference/post-deploy"
25
+ detect:
26
+ deps: [rollbar, "@rollbar/"]
27
+ env: ["ROLLBAR_*"]
28
+ starter:
29
+ - op: deploy
30
+ values: { environment: "${ctx.env}", revision: "${ctx.git.sha}", comment: "${ctx.scope}" }
31
+ assumptions:
32
+ - id: RB1
33
+ verified: true
34
+ text: "`POST https://api.rollbar.com/api/1/deploy` records a deploy from `environment` and `revision` (required), `rollbar_username`, `local_username`, `comment` and `status` (`started`, `succeeded` by default, `failed`, `timed_out`), authenticated by a project access token with the `post_server_item` scope in `X-Rollbar-Access-Token`, and answers 200 with `data.deploy_id`, an integer."
35
+ source: https://docs.rollbar.com/reference/post-deploy
36
+ - id: RB2
37
+ verified: false
38
+ text: "The token in the header is enough: the reference also lists `access_token` as a body field, which this recipe does not send (a credential in a body would be part of what the request logs show)."
39
+ - id: RB3
40
+ verified: true
41
+ text: "The API reference has report, get, update and list operations for deploys and none that deletes one, so destroy sends nothing and forgets the record: a deploy is history."
42
+ source: https://docs.rollbar.com/llms.txt
43
+ - id: RB4
44
+ verified: false
45
+ text: "Rollbar takes no idempotency key for deploys: one sent again after a crash before Sponson recorded it is a second deploy of the same revision (harmless, at least once)."
46
+ - id: RB5
47
+ verified: false
48
+ text: "A deploy is attributed to errors of the environment of the same name that the SDK reports (`environment` in its configuration), so a preview's deploys stay apart from production's when the preview's SDK reports its own environment, such as `preview`."
49
+ ops:
50
+ deploy:
51
+ kind: action
52
+ title: Deploy
53
+ summary: "A deploy of the release's revision in the environment's Rollbar `environment` (such as `preview`), sent once per environment and revision, so errors are attributed to the deploy. Nothing to undo: Rollbar deletes no deploys (RB3)."
54
+ covers: [rollbar-deploy]
55
+ params:
56
+ environment:
57
+ type: string
58
+ required: true
59
+ description: "The Rollbar environment the deployment's SDK reports, such as `${ctx.env}` (RB5)."
60
+ example: "${ctx.env}"
61
+ revision:
62
+ type: string
63
+ required: true
64
+ description: "The revision deployed, usually the commit: `\"${ctx.git.sha}\"`. A new one sends a new deploy."
65
+ example: "${ctx.git.sha}"
66
+ comment:
67
+ type: string
68
+ description: "Text shown with the deploy, such as the scope."
69
+ example: "${ctx.scope}"
70
+ local_username:
71
+ type: string
72
+ default: sponson
73
+ description: Who deployed.
74
+ rollbar_username:
75
+ type: string
76
+ description: A Rollbar user to attribute the deploy to.
77
+ status:
78
+ type: string
79
+ default: succeeded
80
+ enum: [started, succeeded, failed, timed_out]
81
+ description: The deploy's status.
82
+ http:
83
+ trigger: { environment: { param: environment }, revision: { param: revision } }
84
+ request:
85
+ method: POST
86
+ path: /deploy
87
+ body:
88
+ environment: { param: environment }
89
+ revision: { param: revision }
90
+ comment: { param: comment }
91
+ local_username: { param: local_username }
92
+ rollbar_username: { param: rollbar_username }
93
+ status: { param: status }
94
+ outputs: { deploy_id: /data/deploy_id }
@@ -0,0 +1,163 @@
1
+ # yaml-language-server: $schema=../../../schema/recipe.schema.json
2
+ # Sanity (Content Lake) Projects API recipes, authenticated by a robot token. Format and checklist:
3
+ # docs/adr/0020-recipes.md, CONTRIBUTING.md "Adding a recipe".
4
+ provider: sanity
5
+ title: Sanity
6
+ category: cms
7
+ verified_on: "2026-10-12"
8
+ docs:
9
+ - title: Projects API reference (datasets, CORS origins)
10
+ url: https://www.sanity.io/docs/http-reference/projects-api
11
+ - title: Datasets (naming rules, plan limits, environments)
12
+ url: https://www.sanity.io/docs/content-lake/datasets
13
+ - title: CORS origins (preview deployments)
14
+ url: https://www.sanity.io/docs/content-lake/cors
15
+ - title: HTTP API authentication (robot tokens)
16
+ url: https://www.sanity.io/docs/content-lake/http-auth
17
+ - title: Cloud Clone (dataset copy)
18
+ url: https://www.sanity.io/docs/content-lake/how-to-use-cloud-clone-for-datasets
19
+ - title: CLI configuration (SANITY_AUTH_TOKEN)
20
+ url: https://www.sanity.io/docs/cli-reference/cli-config
21
+ - title: Pricing (datasets per plan)
22
+ url: https://www.sanity.io/pricing
23
+ api:
24
+ base_url: https://api.sanity.io/v2021-06-07
25
+ base_url_env: SANITY_API_URL
26
+ auth: { bearer_env: SANITY_AUTH_TOKEN }
27
+ credential:
28
+ url: "https://www.sanity.io/manage"
29
+ scope: "A robot token of the project (API → Tokens) with the Administrator role, or Developer on Growth and Enterprise: editors cannot create datasets, and CORS origins are project settings."
30
+ source: "https://www.sanity.io/docs/http-reference/projects-api"
31
+ detect:
32
+ deps: [sanity, "@sanity/client", next-sanity]
33
+ files: [sanity.config.ts, sanity.config.js, sanity.cli.ts, sanity.cli.js]
34
+ env: ["SANITY_*"]
35
+ starter:
36
+ - op: cors_origin
37
+ preview_url: origin
38
+ - op: dataset
39
+ values: { name: "preview-${ctx.scope}" }
40
+ where:
41
+ project_id: "`projectId` in sanity.config.ts or sanity.cli.ts (`SANITY_STUDIO_PROJECT_ID`), or sanity.io/manage"
42
+ assumptions:
43
+ - id: SN1
44
+ verified: true
45
+ text: "The Projects API is served at `https://api.sanity.io/<version>` (default `v2021-06-07`) and authenticates with a robot token as `Authorization: Bearer`; the Sanity CLI reads a token for unattended use from `SANITY_AUTH_TOKEN`."
46
+ source: https://www.sanity.io/docs/http-reference/projects-api
47
+ - id: SN2
48
+ verified: true
49
+ text: "Managing a project's datasets and settings needs the Administrator or Developer role (Developer only on Growth and Enterprise); editors can modify existing datasets but cannot create new ones."
50
+ source: https://www.sanity.io/docs/user-guides/roles
51
+ - id: SN3
52
+ verified: true
53
+ text: "`PUT /projects/{projectId}/datasets/{name}` with an optional `{ aclMode }` (`public`, the default, or `private`, a paid feature) creates an empty dataset and answers 200 with `{ datasetName, aclMode }`, not the dataset itself, so the recipe finds it by name afterwards (`create.locate`)."
54
+ source: https://www.sanity.io/docs/http-reference/projects-api
55
+ - id: SN4
56
+ verified: true
57
+ text: "`GET /projects/{projectId}/datasets` answers a bare list of `{ name, aclMode, … }`; dataset names are unique in a project."
58
+ source: https://www.sanity.io/docs/http-reference/projects-api
59
+ - id: SN5
60
+ verified: true
61
+ text: "`DELETE /projects/{projectId}/datasets/{name}` deletes the dataset and its documents and answers `{ deleted: true }`."
62
+ source: https://www.sanity.io/docs/http-reference/projects-api
63
+ - id: SN6
64
+ verified: true
65
+ text: "A dataset name is 1 to 64 characters of lowercase letters, digits, `-` and `_`, beginning and ending with a letter or digit."
66
+ source: https://www.sanity.io/docs/content-lake/datasets
67
+ - id: SN7
68
+ verified: true
69
+ text: "Free and Growth plans include 2 datasets (more on Growth cost $999 per dataset per month), and copying a dataset (Cloud Clone, an asynchronous job) is an Enterprise feature; so a dataset per preview is practical on Enterprise, or with one preview dataset at a time below it. This recipe makes an empty dataset; its content is the app's import or seed (application data, not Sponson's), and Cloud Clone copies are not covered."
70
+ source: https://www.sanity.io/pricing
71
+ - id: SN8
72
+ verified: true
73
+ text: "A browser querying the Content Lake (or a Studio, or the Presentation tool) from an origin must find that origin in the project's CORS origins; preview deployments get a new URL per branch, so the origin cannot be added ahead of time, and unused preview origins should be removed. `https://*.vercel.app` with credentials is unsafe."
74
+ source: https://www.sanity.io/docs/content-lake/cors
75
+ - id: SN9
76
+ verified: true
77
+ text: "`GET /projects/{projectId}/cors` answers a bare list of `{ id, origin, allowCredentials, createdAt }` with integer ids; `POST /projects/{projectId}/cors` with `{ origin, allowCredentials? }` creates one (201); `DELETE /projects/{projectId}/cors/{id}` deletes it."
78
+ source: https://www.sanity.io/docs/http-reference/projects-api
79
+ - id: SN10
80
+ verified: false
81
+ text: "No update of a CORS origin or of a dataset's `aclMode` is documented, so neither is compared for drift: `allowCredentials` and `aclMode` are sent when the line creates the object, and only its existence is checked after."
82
+ - id: SN11
83
+ verified: false
84
+ text: "A dataset being deleted (or a `PUT` of a name that exists) answers within the request; nothing waits, and a dataset is usable as soon as the `PUT` returns."
85
+ ops:
86
+ dataset:
87
+ kind: resource
88
+ title: Dataset per preview
89
+ summary: "An empty Sanity dataset per scope in a project, such as `preview-pr-42`, for the preview to import or seed its content into (SN7: copying production into it is Enterprise's Cloud Clone and is not covered). Located by name; only its existence is compared for drift; destroy deletes the dataset and its documents."
90
+ covers: [sanity-dataset]
91
+ params:
92
+ project_id:
93
+ type: string
94
+ required: true
95
+ pattern: "^[a-z0-9]+$"
96
+ description: The project id (`projectId` in sanity.config.ts).
97
+ example: abc123de
98
+ name:
99
+ type: string
100
+ required: true
101
+ pattern: "^(?!.*[-_]$)[a-z0-9][-_a-z0-9]{0,63}$"
102
+ description: The dataset's name; its identity in the project (SN6).
103
+ example: "preview-${ctx.scope}"
104
+ acl_mode:
105
+ type: string
106
+ default: public
107
+ enum: [public, private]
108
+ description: "Who may read it without a token: `public` (Sanity's default) or `private` (Growth and up). Sent on create only (SN10)."
109
+ http:
110
+ find:
111
+ path: "/projects/{project_id}/datasets"
112
+ match: { name: { param: name } }
113
+ create:
114
+ method: PUT
115
+ path: "/projects/{project_id}/datasets/{name}"
116
+ body: { aclMode: { param: acl_mode } }
117
+ send_fields: false
118
+ locate: true
119
+ delete: { path: "/projects/{project_id}/datasets/{id}" }
120
+ id_path: /name
121
+ outputs:
122
+ name: /name
123
+ sim:
124
+ collection: "/projects/{project_id}/datasets"
125
+ live:
126
+ params: { project_id: SPONSON_LIVE_SANITY_PROJECT_ID }
127
+ cors_origin:
128
+ kind: resource
129
+ title: CORS origin
130
+ summary: "The preview deployment's origin in the project's CORS origins (API → CORS origins), so the preview's browser code, an embedded Studio or the Presentation tool can query the Content Lake (SN8). Located by origin; removed on destroy; every other origin is left alone."
131
+ covers: [sanity-cors]
132
+ params:
133
+ project_id:
134
+ type: string
135
+ required: true
136
+ pattern: "^[a-z0-9]+$"
137
+ description: The project id (`projectId` in sanity.config.ts).
138
+ example: abc123de
139
+ origin:
140
+ type: string
141
+ required: true
142
+ pattern: "^https?://[^/\\s]+$"
143
+ description: "The origin, scheme and host without a path or trailing slash: usually `{ from: <deploy line>.preview_url }`."
144
+ example: "https://${ctx.scope}.preview.example.app"
145
+ allow_credentials:
146
+ type: boolean
147
+ default: false
148
+ description: "Allow requests with credentials (a token or a login), which a Studio needs; never for a wildcard origin (SN8). Sent on create only (SN10)."
149
+ http:
150
+ find:
151
+ path: "/projects/{project_id}/cors"
152
+ match: { origin: { param: origin } }
153
+ create:
154
+ path: "/projects/{project_id}/cors"
155
+ body: { allowCredentials: { param: allow_credentials } }
156
+ delete: { path: "/projects/{project_id}/cors/{id}" }
157
+ sim:
158
+ numeric_ids: true
159
+ console_url:
160
+ template: "https://www.sanity.io/manage/project/{project_id}/api"
161
+ source: https://github.com/sanity-io/client/blob/main/src/http/errors.ts
162
+ live:
163
+ params: { project_id: SPONSON_LIVE_SANITY_PROJECT_ID }