@liiift-studio/deploy-vercel-from-sanity 1.1.0 → 1.2.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  **Trigger and monitor Vercel deployments directly from [Sanity Studio](https://www.sanity.io) — no context switching required.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@liiift-studio/deploy-vercel-from-sanity)](https://www.npmjs.com/package/@liiift-studio/deploy-vercel-from-sanity)
6
- [![Sanity v3–v6](https://img.shields.io/badge/sanity-v3%20%E2%80%93%20v6-f03e2f)](https://www.sanity.io)
6
+ [![Sanity v3.30–v6](https://img.shields.io/badge/sanity-v3.30%20%E2%80%93%20v6-f03e2f)](https://www.sanity.io)
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
8
8
 
9
9
  ![The Deploy tool inside Sanity Studio — Production and Preview targets each showing a live status badge, branch, commit SHA, deploy author, and a one-click Deploy button](https://raw.githubusercontent.com/Liiift-Studio/Deploy-Vercel-from-Sanity/main/docs/screenshot.png?v=1)
@@ -23,8 +23,8 @@
23
23
  - **Deployment history** per target
24
24
  - **"Open in Vercel"** link to your project dashboard
25
25
  - **Multiple targets** — configure Production, Preview, and any number of custom environments
26
- - **Shared API token** — set it once; all authenticated studio users with editor access or above can deploy
27
- - **Responsive grid layout** — 2 columns on desktop, 1 on mobile
26
+ - **Shared API token** — set it once; readable by anyone who can read the dataset (see [Security](#security))
27
+ - **Responsive grid layout** — cards reflow to fill the available width, one column on narrow viewports
28
28
 
29
29
  ---
30
30
 
@@ -97,7 +97,7 @@ EOF
97
97
  |---|---|---|---|
98
98
  | `name` | `string` | `'vercel-deploy'` | Tool slug in the Studio sidebar |
99
99
  | `title` | `string` | `'Deploy'` | Tool label in the Studio sidebar |
100
- | `icon` | `ComponentType` | `RocketIcon` | Custom sidebar icon |
100
+ | `icon` | `ComponentType` | `RocketIcon` | Accepted and stored on the tool descriptor. Note that no Studio version from v3 to v6 currently renders `tool.icon`, so this has no visible effect today. |
101
101
 
102
102
  ---
103
103
 
@@ -105,6 +105,8 @@ EOF
105
105
 
106
106
  By default the Deploy tab is visible to all authenticated users. To hide it from viewers:
107
107
 
108
+ > Filter on the tool name you actually configured. The snippet below uses the default `vercel-deploy`; if you passed a custom `name`, match that instead or the filter silently does nothing.
109
+
108
110
  ```ts
109
111
  // sanity.config.ts
110
112
  tools: (prev, { currentUser }) => {
@@ -154,19 +156,19 @@ flowchart LR
154
156
 
155
157
  ## Troubleshooting
156
158
 
157
- ### "Token is invalid or expired"
159
+ ### "Vercel API 401 — token is invalid or expired"
158
160
 
159
161
  Your Vercel API token has been revoked or expired. Go to **Vercel → Settings → Tokens**, create a new token with **Full Account** scope, and reconnect it in the Deploy tab (top-right → *Token connected* button).
160
162
 
161
- ### "Token lacks the required permissions"
163
+ ### "Vercel API 403 — token lacks the required permissions"
162
164
 
163
165
  The token exists but was created with insufficient scope. Vercel tokens need **Full Account** scope to read deployments. Delete the token and create a new one with the correct scope.
164
166
 
165
- ### "Resource not found check the deploy hook URL and team ID"
167
+ ### "Vercel API 404 — resource not found. Check the deploy hook URL and team ID."
166
168
 
167
169
  Either the deploy hook URL is incorrect, or the project belongs to a Vercel team and the **Team ID** field is missing from the deploy target. Find your Team ID at **Vercel → Settings → General → Team ID** (starts with `team_`) and add it to the deploy target via the edit menu.
168
170
 
169
- ### "Rate limit reached"
171
+ ### "Vercel API 429 — rate limit reached"
170
172
 
171
173
  The plugin is making too many API calls at once (common when many targets are all actively building). Wait a few seconds — polling will resume automatically.
172
174
 
@@ -186,11 +188,11 @@ If "No stderr or stdout was captured" appears, the build may have failed before
186
188
 
187
189
  ## Security
188
190
 
189
- **API token storage** — The Vercel API token is stored in a `config.vercelDeploy` Sanity document, readable by all authenticated studio users. Note that a **Full Account** scoped token can read and write your entire Vercel account, so anyone with studio access can read a credential that grants broad Vercel access — treat the token accordingly. Audit who has access to your Sanity project at sanity.io → Project → Members. If your studio includes untrusted editors, consider a server-side proxy that holds the token and exposes only a scoped deploy endpoint.
191
+ **API token storage** — The Vercel API token is stored in cleartext in a `config.vercelDeploy` document of type `vercelDeploy.config`. Sanity has no per-document access control at this tier, so **the token is readable by anyone who can read the dataset** — and if the dataset is public, which is the usual setup for a statically generated front-end, that includes unauthenticated requests to the public GROQ API. A `viewer`-role member who cannot write a single document can also read it. Note that a **Full Account** scoped token can read and write your entire Vercel account, so anyone with studio access can read a credential that grants broad Vercel access — treat the token accordingly. The document type is registered with the plugin, so it is visible in Structure and can be deleted from the Studio to revoke the stored token. Audit who has access to your Sanity project at sanity.io → Project → Members, and do not store a Full Account token in a public dataset. If your studio includes untrusted editors, consider a server-side proxy that holds the token and exposes only a scoped deploy endpoint.
190
192
 
191
193
  **Deploy hook URL validation** — `triggerDeploy` validates that the hook URL matches `api.vercel.com/v1/integrations/deploy/` before making the request, preventing SSRF from a tampered document.
192
194
 
193
- **External links** — All external links use `target="_blank" rel="noreferrer"` and are validated through `safeHref()` before rendering, blocking `javascript:` injection from a compromised API response.
195
+ **External links** — All external links use `target="_blank" rel="noreferrer"` and every `href` is validated before rendering: `safeHref()` rejects any non-http(s) scheme, and deployment hostnames from the API go through `deploymentHref()`, which rejects anything that is not a bare host. Both block `javascript:` injection and host substitution from a compromised API response.
194
196
 
195
197
  **GROQ queries** — All GROQ queries in this plugin are static strings — no user input is interpolated.
196
198
 
@@ -198,23 +200,35 @@ If "No stderr or stdout was captured" appears, the build may have failed before
198
200
 
199
201
  ## Requirements
200
202
 
201
- - Sanity Studio v3, v4, v5, or v6
202
- - React 18 or 19
203
+ - Sanity Studio v3.30 or newer, through v6
204
+ - React 18 or 19 (Studio v5 and v6 require React 19)
205
+ - Node 20.19+ or 22.12+
203
206
  - A Vercel account with at least one project and a deploy hook configured
204
207
 
205
- Zero runtime dependencies — `react`, `sanity`, `@sanity/ui` and `@sanity/icons` are peer dependencies provided by your Studio; the plugin ships nothing else.
208
+ Zero runtime dependencies — `react`, `react-dom`, `sanity`, `@sanity/ui` and `@sanity/icons` are peer dependencies provided by your Studio; the plugin ships nothing else.
209
+
210
+ > **Why v3.30 and not v3.0?** `sanity@3.0` pulls `@sanity/ui` v1, and this plugin needs v2 or newer. `@sanity/ui` reaches 2.x at `sanity@3.30.0`.
206
211
 
207
212
  ### Studio compatibility
208
213
 
209
214
  A single build supports the whole range. `@sanity/icons` v5 and `@sanity/ui` v4
210
- both moved components out of their barrel files, so the plugin resolves them at
211
- runtime rather than importing names that only exist on one major:
215
+ both moved components out of their barrel files, so the plugin resolves every
216
+ `@sanity/ui` and `@sanity/icons` value from the installed package at runtime
217
+ rather than importing names that exist on only one major.
218
+
219
+ Behaviour depends on the resolved packages, not on the Studio version — a
220
+ lockfile that hoists a different major changes which row applies:
212
221
 
213
- | Studio | `@sanity/icons` | `@sanity/ui` | Notes |
222
+ | `@sanity/ui` | `@sanity/icons` | Typical Studio | Behaviour |
214
223
  | --- | --- | --- | --- |
215
- | v3 v6.3 | 3.x | 2.x / 3.x | Fully Studio-native |
216
- | v6.4 – v6.9 | 5.x | 3.x | Icons resolved via `<Icon symbol>` |
217
- | v6.10+ | 5.x | 4.x | Tooltips, overflow menu, log blocks and toasts use built-in fallbacks, since `@sanity/ui` v4 serves those from subpaths that do not exist on earlier majors |
224
+ | 2.x | 2.x / 3.x | v3.30 v3.x | Fully Studio-native |
225
+ | 3.x | 3.x | v4 v6.3 | Fully Studio-native |
226
+ | 3.x | 5.x | v6.4 – v6.9 | Icons resolved via `<Icon symbol>` |
227
+ | 4.x | 5.x | v6.10+ | Tooltips, overflow menu, log blocks and toasts use built-in equivalents, since `@sanity/ui` v4 serves those from subpaths that do not exist on earlier majors |
228
+
229
+ The built-in equivalents are functional rather than pixel-identical to Studio's
230
+ own: the overflow menu implements the WAI-ARIA menu button pattern locally, and
231
+ toasts render in a live region anchored to the bottom-right of the tool.
218
232
 
219
233
  ---
220
234
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as sanity from 'sanity';
2
- import { ComponentType, SVGProps } from 'react';
2
+ import * as react from 'react';
3
3
 
4
4
  type VercelDeployState = 'QUEUED' | 'INITIALIZING' | 'BUILDING' | 'READY' | 'ERROR' | 'CANCELED' | 'LOADING';
5
5
  /** A vercel_deploy document stored in the Sanity dataset */
@@ -52,16 +52,11 @@ interface VercelDeployPluginConfig {
52
52
  icon?: React.ComponentType;
53
53
  }
54
54
 
55
- /** Props every Sanity icon accepts — it renders a plain sized SVG. */
56
- type IconProps = SVGProps<SVGSVGElement>;
57
- /** An icon component, whichever shape the installed @sanity/icons exposes it in. */
58
- type IconComponent = ComponentType<IconProps>;
59
-
60
55
  declare const vercelDeploySchema: {
61
56
  name: "vercel_deploy";
62
57
  title: string;
63
58
  type: "document";
64
- icon: IconComponent;
59
+ icon: react.ForwardRefExoticComponent<Omit<react.SVGProps<SVGSVGElement>, "ref"> & react.RefAttributes<SVGSVGElement>>;
65
60
  fields: (({
66
61
  name: "name";
67
62
  title: string;
@@ -95,7 +90,42 @@ declare const vercelDeploySchema: {
95
90
  };
96
91
 
97
92
  /**
98
- * Sanity Studio v5 plugin trigger and monitor Vercel deployments.
93
+ * Registers the document the plugin stores its Vercel token in.
94
+ *
95
+ * The document was previously written without a registered type, which made it
96
+ * invisible to Structure, absent from schema extraction, and impossible to
97
+ * inspect or revoke from inside the Studio. Registering it does not change where
98
+ * the token lives — see the security note in the README about dataset read
99
+ * access — but it does make the credential visible and removable.
100
+ */
101
+ declare const vercelConfigSchema: {
102
+ name: "vercelDeploy.config";
103
+ title: string;
104
+ type: "document";
105
+ fields: {
106
+ name: "accessToken";
107
+ title: string;
108
+ type: "string";
109
+ description: string;
110
+ }[];
111
+ preview: {
112
+ select: {
113
+ token: string;
114
+ };
115
+ prepare: ({ token }: {
116
+ token?: string;
117
+ }) => {
118
+ title: string;
119
+ subtitle: string;
120
+ };
121
+ };
122
+ };
123
+
124
+ /**
125
+ * Sanity Studio plugin — trigger and monitor Vercel deployments.
126
+ *
127
+ * Supports Studio v3.30 through v6 from a single build; see the compatibility
128
+ * table in the README for how @sanity/ui and @sanity/icons are resolved.
99
129
  *
100
130
  * @example
101
131
  * // sanity.config.ts
@@ -111,4 +141,4 @@ declare const vercelDeploySchema: {
111
141
  */
112
142
  declare const vercelDeploy: sanity.Plugin<void | VercelDeployPluginConfig>;
113
143
 
114
- export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelDeploy, vercelDeploySchema };
144
+ export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelConfigSchema, vercelDeploy, vercelDeploySchema };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as sanity from 'sanity';
2
- import { ComponentType, SVGProps } from 'react';
2
+ import * as react from 'react';
3
3
 
4
4
  type VercelDeployState = 'QUEUED' | 'INITIALIZING' | 'BUILDING' | 'READY' | 'ERROR' | 'CANCELED' | 'LOADING';
5
5
  /** A vercel_deploy document stored in the Sanity dataset */
@@ -52,16 +52,11 @@ interface VercelDeployPluginConfig {
52
52
  icon?: React.ComponentType;
53
53
  }
54
54
 
55
- /** Props every Sanity icon accepts — it renders a plain sized SVG. */
56
- type IconProps = SVGProps<SVGSVGElement>;
57
- /** An icon component, whichever shape the installed @sanity/icons exposes it in. */
58
- type IconComponent = ComponentType<IconProps>;
59
-
60
55
  declare const vercelDeploySchema: {
61
56
  name: "vercel_deploy";
62
57
  title: string;
63
58
  type: "document";
64
- icon: IconComponent;
59
+ icon: react.ForwardRefExoticComponent<Omit<react.SVGProps<SVGSVGElement>, "ref"> & react.RefAttributes<SVGSVGElement>>;
65
60
  fields: (({
66
61
  name: "name";
67
62
  title: string;
@@ -95,7 +90,42 @@ declare const vercelDeploySchema: {
95
90
  };
96
91
 
97
92
  /**
98
- * Sanity Studio v5 plugin trigger and monitor Vercel deployments.
93
+ * Registers the document the plugin stores its Vercel token in.
94
+ *
95
+ * The document was previously written without a registered type, which made it
96
+ * invisible to Structure, absent from schema extraction, and impossible to
97
+ * inspect or revoke from inside the Studio. Registering it does not change where
98
+ * the token lives — see the security note in the README about dataset read
99
+ * access — but it does make the credential visible and removable.
100
+ */
101
+ declare const vercelConfigSchema: {
102
+ name: "vercelDeploy.config";
103
+ title: string;
104
+ type: "document";
105
+ fields: {
106
+ name: "accessToken";
107
+ title: string;
108
+ type: "string";
109
+ description: string;
110
+ }[];
111
+ preview: {
112
+ select: {
113
+ token: string;
114
+ };
115
+ prepare: ({ token }: {
116
+ token?: string;
117
+ }) => {
118
+ title: string;
119
+ subtitle: string;
120
+ };
121
+ };
122
+ };
123
+
124
+ /**
125
+ * Sanity Studio plugin — trigger and monitor Vercel deployments.
126
+ *
127
+ * Supports Studio v3.30 through v6 from a single build; see the compatibility
128
+ * table in the README for how @sanity/ui and @sanity/icons are resolved.
99
129
  *
100
130
  * @example
101
131
  * // sanity.config.ts
@@ -111,4 +141,4 @@ declare const vercelDeploySchema: {
111
141
  */
112
142
  declare const vercelDeploy: sanity.Plugin<void | VercelDeployPluginConfig>;
113
143
 
114
- export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelDeploy, vercelDeploySchema };
144
+ export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelConfigSchema, vercelDeploy, vercelDeploySchema };