@liiift-studio/deploy-vercel-from-sanity 1.2.0 → 1.2.1
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/index.d.mts +1 -33
- package/dist/index.d.ts +1 -33
- package/dist/index.js +185 -99
- package/dist/index.mjs +195 -108
- package/package.json +12 -8
- package/src/compat/code.tsx +0 -29
- package/src/compat/index.ts +0 -14
- package/src/compat/menu.tsx +0 -225
- package/src/compat/primitives.tsx +0 -93
- package/src/compat/resolve.ts +0 -55
- package/src/compat/toast.tsx +0 -173
- package/src/compat/tooltip.tsx +0 -78
- package/src/components/DeployHistory.tsx +0 -160
- package/src/components/DeployItem.tsx +0 -596
- package/src/components/DeployTargetForm.tsx +0 -172
- package/src/components/DeployTool.tsx +0 -318
- package/src/components/StatusBadge.tsx +0 -23
- package/src/components/TokenSetup.tsx +0 -98
- package/src/icons.tsx +0 -78
- package/src/index.ts +0 -47
- package/src/lib/api.ts +0 -102
- package/src/lib/helpers.ts +0 -131
- package/src/schema/schemaIcon.tsx +0 -38
- package/src/schema/vercelConfig.ts +0 -34
- package/src/schema/vercelDeploy.ts +0 -49
- package/src/types.ts +0 -77
- package/src/version.ts +0 -2
package/README.md
CHANGED
|
@@ -188,7 +188,7 @@ If "No stderr or stdout was captured" appears, the build may have failed before
|
|
|
188
188
|
|
|
189
189
|
## Security
|
|
190
190
|
|
|
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
|
|
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 deliberately **not** registered in the schema — registering it would list a "Vercel Deploy Configuration" entry in the Structure sidebar for every editor. Revoke the stored token from the plugin instead: **Deploy → Token connected → Remove 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.
|
|
192
192
|
|
|
193
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.
|
|
194
194
|
|
package/dist/index.d.mts
CHANGED
|
@@ -89,38 +89,6 @@ declare const vercelDeploySchema: {
|
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
/**
|
|
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
92
|
/**
|
|
125
93
|
* Sanity Studio plugin — trigger and monitor Vercel deployments.
|
|
126
94
|
*
|
|
@@ -141,4 +109,4 @@ declare const vercelConfigSchema: {
|
|
|
141
109
|
*/
|
|
142
110
|
declare const vercelDeploy: sanity.Plugin<void | VercelDeployPluginConfig>;
|
|
143
111
|
|
|
144
|
-
export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment,
|
|
112
|
+
export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelDeploy, vercelDeploySchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -89,38 +89,6 @@ declare const vercelDeploySchema: {
|
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
/**
|
|
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
92
|
/**
|
|
125
93
|
* Sanity Studio plugin — trigger and monitor Vercel deployments.
|
|
126
94
|
*
|
|
@@ -141,4 +109,4 @@ declare const vercelConfigSchema: {
|
|
|
141
109
|
*/
|
|
142
110
|
declare const vercelDeploy: sanity.Plugin<void | VercelDeployPluginConfig>;
|
|
143
111
|
|
|
144
|
-
export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment,
|
|
112
|
+
export { type DeployTarget, type VercelDeployPluginConfig, type VercelDeployState, type VercelDeployment, vercelDeploy, vercelDeploySchema };
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
vercelConfigSchema: () => vercelConfigSchema,
|
|
34
33
|
vercelDeploy: () => vercelDeploy,
|
|
35
34
|
vercelDeploySchema: () => vercelDeploySchema
|
|
36
35
|
});
|
|
37
36
|
module.exports = __toCommonJS(index_exports);
|
|
38
|
-
var
|
|
37
|
+
var import_sanity5 = require("sanity");
|
|
39
38
|
|
|
40
39
|
// src/icons.tsx
|
|
41
40
|
var import_react = require("react");
|
|
@@ -45,10 +44,18 @@ var sanityUi = __toESM(require("@sanity/ui"));
|
|
|
45
44
|
var sanityIcons = __toESM(require("@sanity/icons"));
|
|
46
45
|
var UI = sanityUi;
|
|
47
46
|
var ICONS = sanityIcons;
|
|
48
|
-
function
|
|
47
|
+
function resolveComponent(ns, name) {
|
|
49
48
|
const value = ns[name];
|
|
50
49
|
return typeof value === "function" || typeof value === "object" && value !== null ? value : void 0;
|
|
51
50
|
}
|
|
51
|
+
function resolveFunction(ns, name) {
|
|
52
|
+
const value = ns[name];
|
|
53
|
+
return typeof value === "function" ? value : void 0;
|
|
54
|
+
}
|
|
55
|
+
function resolveRecord(ns, name) {
|
|
56
|
+
const value = ns[name];
|
|
57
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
58
|
+
}
|
|
52
59
|
var STACK_USES_GAP = typeof UI.Stack === "function";
|
|
53
60
|
|
|
54
61
|
// src/icons.tsx
|
|
@@ -57,11 +64,11 @@ var GLYPH = { width: "1em", height: "1em", viewBox: "0 0 25 25", fill: "none" };
|
|
|
57
64
|
var MissingIcon = (0, import_react.forwardRef)(function MissingIcon2(props, ref) {
|
|
58
65
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...GLYPH, xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, ref });
|
|
59
66
|
});
|
|
60
|
-
var SYMBOL_MAP =
|
|
67
|
+
var SYMBOL_MAP = resolveRecord(ICONS, "icons");
|
|
61
68
|
function resolveIcon(name, symbol) {
|
|
62
|
-
const named =
|
|
69
|
+
const named = resolveComponent(ICONS, name);
|
|
63
70
|
if (named) return named;
|
|
64
|
-
const Icon =
|
|
71
|
+
const Icon = resolveComponent(ICONS, "Icon");
|
|
65
72
|
if (!Icon) return MissingIcon;
|
|
66
73
|
if (SYMBOL_MAP && !(symbol in SYMBOL_MAP)) return MissingIcon;
|
|
67
74
|
const Resolved = (0, import_react.forwardRef)(function SanityIcon(props, ref) {
|
|
@@ -99,21 +106,43 @@ var import_react2 = require("react");
|
|
|
99
106
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
100
107
|
function domFallback(tag) {
|
|
101
108
|
const Fallback = (0, import_react2.forwardRef)(function SanityUiFallback(props, ref) {
|
|
102
|
-
const { children, style, id, className, onClick, href, title, ...rest } = props;
|
|
109
|
+
const { children, style, id, className, onClick, href, title, as, ...rest } = props;
|
|
110
|
+
const element = typeof as === "string" ? as : tag;
|
|
103
111
|
const passthrough = { style, id, className, onClick, href, title, ref };
|
|
104
|
-
for (const key of [
|
|
112
|
+
for (const key of [
|
|
113
|
+
"role",
|
|
114
|
+
"type",
|
|
115
|
+
"value",
|
|
116
|
+
"checked",
|
|
117
|
+
"placeholder",
|
|
118
|
+
"disabled",
|
|
119
|
+
"tabIndex",
|
|
120
|
+
"onChange",
|
|
121
|
+
"onKeyDown",
|
|
122
|
+
"onFocus",
|
|
123
|
+
"onBlur",
|
|
124
|
+
"onMouseEnter",
|
|
125
|
+
"onMouseLeave",
|
|
126
|
+
"htmlFor",
|
|
127
|
+
"target",
|
|
128
|
+
"rel",
|
|
129
|
+
"name",
|
|
130
|
+
"autoComplete",
|
|
131
|
+
"required",
|
|
132
|
+
"readOnly"
|
|
133
|
+
]) {
|
|
105
134
|
if (key in rest) passthrough[key] = rest[key];
|
|
106
135
|
}
|
|
107
136
|
for (const key of Object.keys(rest)) {
|
|
108
137
|
if (key.startsWith("aria-") || key.startsWith("data-")) passthrough[key] = rest[key];
|
|
109
138
|
}
|
|
110
|
-
return (0, import_react2.createElement)(
|
|
139
|
+
return (0, import_react2.createElement)(element, passthrough, children);
|
|
111
140
|
});
|
|
112
141
|
Fallback.displayName = `SanityUiFallback(${tag})`;
|
|
113
142
|
return Fallback;
|
|
114
143
|
}
|
|
115
144
|
function primitive(name, tag) {
|
|
116
|
-
return
|
|
145
|
+
return resolveComponent(UI, name) ?? domFallback(tag);
|
|
117
146
|
}
|
|
118
147
|
var Box = primitive("Box", "div");
|
|
119
148
|
var Card = primitive("Card", "div");
|
|
@@ -172,11 +201,19 @@ function scheduleDismiss(id, status) {
|
|
|
172
201
|
}
|
|
173
202
|
function pushLocalToast(params) {
|
|
174
203
|
const toast = { ...params, id: nextToastId++ };
|
|
175
|
-
|
|
204
|
+
const next = [...localToasts, toast];
|
|
205
|
+
for (const dropped of next.slice(0, Math.max(0, next.length - MAX_VISIBLE_TOASTS))) {
|
|
206
|
+
const timer = dismissTimers.get(dropped.id);
|
|
207
|
+
if (timer) {
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
dismissTimers.delete(dropped.id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
localToasts = next.slice(-MAX_VISIBLE_TOASTS);
|
|
176
213
|
emitToasts();
|
|
177
214
|
scheduleDismiss(toast.id, toast.status);
|
|
178
215
|
}
|
|
179
|
-
var installedUseToast =
|
|
216
|
+
var installedUseToast = resolveFunction(UI, "useToast");
|
|
180
217
|
var localToaster = { push: pushLocalToast };
|
|
181
218
|
function useToast() {
|
|
182
219
|
const real = installedUseToast;
|
|
@@ -193,6 +230,10 @@ function ToastViewport() {
|
|
|
193
230
|
toastListeners.delete(setToasts);
|
|
194
231
|
};
|
|
195
232
|
}, []);
|
|
233
|
+
const resume = (0, import_react3.useCallback)((toast, el) => {
|
|
234
|
+
if (el && el.contains(document.activeElement)) return;
|
|
235
|
+
scheduleDismiss(toast.id, toast.status);
|
|
236
|
+
}, []);
|
|
196
237
|
const hold = (0, import_react3.useCallback)((id) => {
|
|
197
238
|
const timer = dismissTimers.get(id);
|
|
198
239
|
if (timer) {
|
|
@@ -204,9 +245,8 @@ function ToastViewport() {
|
|
|
204
245
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
205
246
|
Box,
|
|
206
247
|
{
|
|
207
|
-
role: "
|
|
208
|
-
"aria-
|
|
209
|
-
"aria-atomic": false,
|
|
248
|
+
role: "log",
|
|
249
|
+
"aria-label": "Deployment notifications",
|
|
210
250
|
style: {
|
|
211
251
|
position: "fixed",
|
|
212
252
|
bottom: 16,
|
|
@@ -221,11 +261,12 @@ function ToastViewport() {
|
|
|
221
261
|
padding: 3,
|
|
222
262
|
radius: 2,
|
|
223
263
|
shadow: 3,
|
|
224
|
-
tone: LOCAL_TOAST_TONE[toast.status ?? "info"]
|
|
264
|
+
tone: LOCAL_TOAST_TONE[toast.status ?? "info"],
|
|
225
265
|
style: { pointerEvents: "auto" },
|
|
226
266
|
onMouseEnter: () => hold(toast.id),
|
|
227
267
|
onFocusCapture: () => hold(toast.id),
|
|
228
|
-
onMouseLeave: () =>
|
|
268
|
+
onMouseLeave: (e) => resume(toast, e.currentTarget),
|
|
269
|
+
onBlurCapture: (e) => resume(toast, e.currentTarget),
|
|
229
270
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Flex, { align: "flex-start", gap: 3, children: [
|
|
230
271
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Stack, { space: 2, flex: 1, children: [
|
|
231
272
|
toast.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { size: 1, weight: "semibold", children: toast.title }),
|
|
@@ -253,48 +294,47 @@ function ToastViewport() {
|
|
|
253
294
|
// src/compat/tooltip.tsx
|
|
254
295
|
var import_react4 = require("react");
|
|
255
296
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
256
|
-
var InstalledTooltip =
|
|
297
|
+
var InstalledTooltip = resolveComponent(UI, "Tooltip");
|
|
257
298
|
function Tooltip({ text, children }) {
|
|
258
299
|
const id = (0, import_react4.useId)();
|
|
259
300
|
const [visible, setVisible] = (0, import_react4.useState)(false);
|
|
260
301
|
if (InstalledTooltip) {
|
|
261
302
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InstalledTooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { size: 1, children: text }) }), portal: true, children });
|
|
262
303
|
}
|
|
304
|
+
const described = visible && (0, import_react4.isValidElement)(children) ? (0, import_react4.cloneElement)(children, { "aria-describedby": id }) : children;
|
|
263
305
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
264
306
|
"span",
|
|
265
307
|
{
|
|
266
308
|
style: { position: "relative", display: "inline-flex" },
|
|
267
|
-
"aria-describedby": id,
|
|
268
309
|
onMouseEnter: () => setVisible(true),
|
|
269
310
|
onMouseLeave: () => setVisible(false),
|
|
270
311
|
onFocusCapture: () => setVisible(true),
|
|
271
312
|
onBlurCapture: () => setVisible(false),
|
|
272
313
|
onKeyDown: (e) => {
|
|
273
|
-
if (e.key === "Escape")
|
|
314
|
+
if (e.key === "Escape" && visible) {
|
|
315
|
+
e.stopPropagation();
|
|
316
|
+
setVisible(false);
|
|
317
|
+
}
|
|
274
318
|
},
|
|
275
319
|
children: [
|
|
276
|
-
|
|
277
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
278
|
-
|
|
320
|
+
described,
|
|
321
|
+
visible && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
322
|
+
"span",
|
|
279
323
|
{
|
|
280
324
|
id,
|
|
281
325
|
role: "tooltip",
|
|
282
|
-
radius: 2,
|
|
283
|
-
shadow: 2,
|
|
284
|
-
padding: 2,
|
|
285
326
|
style: {
|
|
286
327
|
position: "absolute",
|
|
287
328
|
bottom: "100%",
|
|
288
329
|
left: "50%",
|
|
289
330
|
transform: "translateX(-50%)",
|
|
290
|
-
|
|
331
|
+
// No gap between trigger and tooltip, so the pointer can travel onto it
|
|
332
|
+
// without leaving the hover target (WCAG 1.4.13 Hoverable).
|
|
333
|
+
paddingBottom: 4,
|
|
291
334
|
whiteSpace: "nowrap",
|
|
292
|
-
|
|
293
|
-
zIndex: 1e3,
|
|
294
|
-
// Kept in the accessibility tree when hidden so `aria-describedby` still resolves.
|
|
295
|
-
visibility: visible ? "visible" : "hidden"
|
|
335
|
+
zIndex: 1e3
|
|
296
336
|
},
|
|
297
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { size: 1, children: text })
|
|
337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Card, { radius: 2, shadow: 2, padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { size: 1, children: text }) })
|
|
298
338
|
}
|
|
299
339
|
)
|
|
300
340
|
]
|
|
@@ -305,9 +345,9 @@ function Tooltip({ text, children }) {
|
|
|
305
345
|
// src/compat/menu.tsx
|
|
306
346
|
var import_react5 = require("react");
|
|
307
347
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
308
|
-
var InstalledMenuButton =
|
|
309
|
-
var InstalledMenu =
|
|
310
|
-
var InstalledMenuItem =
|
|
348
|
+
var InstalledMenuButton = resolveComponent(UI, "MenuButton");
|
|
349
|
+
var InstalledMenu = resolveComponent(UI, "Menu");
|
|
350
|
+
var InstalledMenuItem = resolveComponent(UI, "MenuItem");
|
|
311
351
|
var INSTALLED_MENU = InstalledMenuButton && InstalledMenu && InstalledMenuItem ? { MenuButton: InstalledMenuButton, Menu: InstalledMenu, MenuItem: InstalledMenuItem } : null;
|
|
312
352
|
var itemKey = (item, index) => item.key ?? `${index}-${item.text}`;
|
|
313
353
|
function ActionMenu({ id, label, items, buttonIcon }) {
|
|
@@ -335,7 +375,10 @@ function ActionMenu({ id, label, items, buttonIcon }) {
|
|
|
335
375
|
(0, import_react5.useEffect)(() => {
|
|
336
376
|
if (INSTALLED_MENU || !open) return;
|
|
337
377
|
const onPointerDown = (e) => {
|
|
338
|
-
if (
|
|
378
|
+
if (wrapRef.current?.contains(e.target)) return;
|
|
379
|
+
const hadFocus = wrapRef.current?.contains(document.activeElement);
|
|
380
|
+
setOpen(false);
|
|
381
|
+
if (hadFocus) triggerRef.current?.focus();
|
|
339
382
|
};
|
|
340
383
|
document.addEventListener("mousedown", onPointerDown);
|
|
341
384
|
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
@@ -373,7 +416,20 @@ function ActionMenu({ id, label, items, buttonIcon }) {
|
|
|
373
416
|
return;
|
|
374
417
|
}
|
|
375
418
|
if (e.key === "Tab") {
|
|
376
|
-
|
|
419
|
+
triggerRef.current?.focus();
|
|
420
|
+
setOpen(false);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
const item = shownItems[activeIndex];
|
|
426
|
+
if (item) {
|
|
427
|
+
if (item.href) {
|
|
428
|
+
itemRefs.current[activeIndex]?.click();
|
|
429
|
+
} else {
|
|
430
|
+
runAction(item);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
377
433
|
return;
|
|
378
434
|
}
|
|
379
435
|
if (e.key === "ArrowDown") {
|
|
@@ -448,7 +504,6 @@ function ActionMenu({ id, label, items, buttonIcon }) {
|
|
|
448
504
|
ref: (el) => {
|
|
449
505
|
itemRefs.current[i] = el;
|
|
450
506
|
},
|
|
451
|
-
onMouseEnter: () => setActiveIndex(i),
|
|
452
507
|
style: {
|
|
453
508
|
display: "block",
|
|
454
509
|
width: "100%",
|
|
@@ -473,15 +528,17 @@ function ActionMenu({ id, label, items, buttonIcon }) {
|
|
|
473
528
|
href: item.href,
|
|
474
529
|
target: "_blank",
|
|
475
530
|
rel: "noreferrer",
|
|
476
|
-
onClick: () =>
|
|
531
|
+
onClick: () => {
|
|
532
|
+
setOpen(false);
|
|
533
|
+
triggerRef.current?.focus();
|
|
534
|
+
},
|
|
477
535
|
children: content
|
|
478
536
|
},
|
|
479
537
|
itemKey(item, i)
|
|
480
538
|
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
481
|
-
"
|
|
539
|
+
"div",
|
|
482
540
|
{
|
|
483
541
|
...shared,
|
|
484
|
-
type: "button",
|
|
485
542
|
onClick: () => runAction(item),
|
|
486
543
|
children: content
|
|
487
544
|
},
|
|
@@ -495,7 +552,7 @@ function ActionMenu({ id, label, items, buttonIcon }) {
|
|
|
495
552
|
|
|
496
553
|
// src/compat/code.tsx
|
|
497
554
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
498
|
-
var InstalledCode =
|
|
555
|
+
var InstalledCode = resolveComponent(UI, "Code");
|
|
499
556
|
var FALLBACK_CODE_STYLE = {
|
|
500
557
|
display: "block",
|
|
501
558
|
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
@@ -672,6 +729,7 @@ function StatusBadge({ state, showSpinner }) {
|
|
|
672
729
|
var import_react6 = require("react");
|
|
673
730
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
674
731
|
function DeployHistory({ target, token, onClose }) {
|
|
732
|
+
const dialogId = (0, import_react6.useId)();
|
|
675
733
|
const [deployments, setDeployments] = (0, import_react6.useState)([]);
|
|
676
734
|
const [loading, setLoading] = (0, import_react6.useState)(true);
|
|
677
735
|
const [error, setError] = (0, import_react6.useState)(null);
|
|
@@ -695,7 +753,7 @@ function DeployHistory({ target, token, onClose }) {
|
|
|
695
753
|
Dialog,
|
|
696
754
|
{
|
|
697
755
|
header: `${target.name} \u2014 Deployment History`,
|
|
698
|
-
id:
|
|
756
|
+
id: dialogId,
|
|
699
757
|
onClose,
|
|
700
758
|
width: 2,
|
|
701
759
|
footer: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { padding: 3, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { text: "Close", icon: CloseIcon, mode: "ghost", onClick: onClose }) }),
|
|
@@ -783,8 +841,11 @@ function DeployItem({ target, token, onDelete, onEdit }) {
|
|
|
783
841
|
const triggeredFromUidRef = (0, import_react7.useRef)(void 0);
|
|
784
842
|
const requestSeqRef = (0, import_react7.useRef)(0);
|
|
785
843
|
const mountedRef = (0, import_react7.useRef)(true);
|
|
786
|
-
(0, import_react7.useEffect)(() =>
|
|
787
|
-
mountedRef.current =
|
|
844
|
+
(0, import_react7.useEffect)(() => {
|
|
845
|
+
mountedRef.current = true;
|
|
846
|
+
return () => {
|
|
847
|
+
mountedRef.current = false;
|
|
848
|
+
};
|
|
788
849
|
}, []);
|
|
789
850
|
const latest = deployments[0];
|
|
790
851
|
const isPending = pendingSince !== null;
|
|
@@ -1010,17 +1071,36 @@ function DeployItem({ target, token, onDelete, onEdit }) {
|
|
|
1010
1071
|
formatDuration(Math.floor((latest.ready - latest.created) / 1e3)),
|
|
1011
1072
|
" to build"
|
|
1012
1073
|
] }),
|
|
1013
|
-
latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime9.
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1074
|
+
latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1075
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Tooltip, { text: copied ? "Copied" : "Copy deployment URL", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1076
|
+
Button,
|
|
1077
|
+
{
|
|
1078
|
+
mode: "ghost",
|
|
1079
|
+
icon: copied ? CheckmarkIcon : CopyIcon,
|
|
1080
|
+
padding: 1,
|
|
1081
|
+
tone: copied ? "positive" : "default",
|
|
1082
|
+
onClick: copyUrl,
|
|
1083
|
+
"aria-label": copied ? "Deployment URL copied" : "Copy deployment URL",
|
|
1084
|
+
style: { cursor: "pointer" }
|
|
1085
|
+
}
|
|
1086
|
+
) }),
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1088
|
+
"span",
|
|
1089
|
+
{
|
|
1090
|
+
role: "status",
|
|
1091
|
+
"aria-live": "polite",
|
|
1092
|
+
style: {
|
|
1093
|
+
position: "absolute",
|
|
1094
|
+
width: 1,
|
|
1095
|
+
height: 1,
|
|
1096
|
+
overflow: "hidden",
|
|
1097
|
+
clip: "rect(0 0 0 0)",
|
|
1098
|
+
whiteSpace: "nowrap"
|
|
1099
|
+
},
|
|
1100
|
+
children: copied ? "Deployment URL copied to clipboard" : ""
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] })
|
|
1024
1104
|
] }),
|
|
1025
1105
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, wrap: "wrap", children: [
|
|
1026
1106
|
commitMsg && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
@@ -1071,7 +1151,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
|
|
|
1071
1151
|
deployError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: deployError }) })
|
|
1072
1152
|
] })
|
|
1073
1153
|
] }),
|
|
1074
|
-
pollError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "caution", padding: 3, radius: 2,
|
|
1154
|
+
pollError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "caution", padding: 3, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, children: [
|
|
1075
1155
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(WarningOutlineIcon, { "aria-hidden": "true" }),
|
|
1076
1156
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { size: 1, children: [
|
|
1077
1157
|
"Status updates paused \u2014 ",
|
|
@@ -1183,12 +1263,14 @@ var import_react8 = require("react");
|
|
|
1183
1263
|
var import_sanity = require("sanity");
|
|
1184
1264
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1185
1265
|
var TOKEN_DOC_ID = "config.vercelDeploy";
|
|
1186
|
-
function TokenSetup({ onSaved, onCancel }) {
|
|
1266
|
+
function TokenSetup({ onSaved, onCancel, hasToken = false }) {
|
|
1267
|
+
const dialogId = (0, import_react8.useId)();
|
|
1187
1268
|
const tokenId = (0, import_react8.useId)();
|
|
1188
1269
|
const client = (0, import_sanity.useClient)({ apiVersion: "2025-01-01" });
|
|
1189
1270
|
const [token, setToken] = (0, import_react8.useState)("");
|
|
1190
1271
|
const [saving, setSaving] = (0, import_react8.useState)(false);
|
|
1191
1272
|
const [error, setError] = (0, import_react8.useState)(null);
|
|
1273
|
+
const [revoking, setRevoking] = (0, import_react8.useState)(false);
|
|
1192
1274
|
const save = (0, import_react8.useCallback)(async () => {
|
|
1193
1275
|
if (!token.trim()) return;
|
|
1194
1276
|
setSaving(true);
|
|
@@ -1206,27 +1288,54 @@ function TokenSetup({ onSaved, onCancel }) {
|
|
|
1206
1288
|
setSaving(false);
|
|
1207
1289
|
}
|
|
1208
1290
|
}, [client, token, onSaved]);
|
|
1291
|
+
const revoke = (0, import_react8.useCallback)(async () => {
|
|
1292
|
+
setRevoking(true);
|
|
1293
|
+
setError(null);
|
|
1294
|
+
try {
|
|
1295
|
+
await client.delete(TOKEN_DOC_ID);
|
|
1296
|
+
onSaved();
|
|
1297
|
+
} catch (err) {
|
|
1298
|
+
setError(err instanceof Error ? err.message : "Failed to remove token");
|
|
1299
|
+
} finally {
|
|
1300
|
+
setRevoking(false);
|
|
1301
|
+
}
|
|
1302
|
+
}, [client, onSaved]);
|
|
1209
1303
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1210
1304
|
Dialog,
|
|
1211
1305
|
{
|
|
1212
1306
|
header: "Connect Vercel API token",
|
|
1213
|
-
id:
|
|
1307
|
+
id: dialogId,
|
|
1214
1308
|
onClose: onCancel,
|
|
1215
1309
|
width: 1,
|
|
1216
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
|
|
1217
|
-
|
|
1218
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1310
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Flex, { padding: 3, gap: 2, justify: hasToken ? "space-between" : "flex-end", children: [
|
|
1311
|
+
hasToken && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1219
1312
|
Button,
|
|
1220
1313
|
{
|
|
1221
|
-
text: "
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1314
|
+
text: "Remove token",
|
|
1315
|
+
mode: "ghost",
|
|
1316
|
+
tone: "critical",
|
|
1317
|
+
icon: TrashIcon,
|
|
1318
|
+
loading: revoking,
|
|
1319
|
+
disabled: revoking || saving,
|
|
1320
|
+
onClick: revoke,
|
|
1227
1321
|
style: { cursor: "pointer" }
|
|
1228
1322
|
}
|
|
1229
|
-
)
|
|
1323
|
+
),
|
|
1324
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Flex, { gap: 2, children: [
|
|
1325
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { text: "Cancel", mode: "ghost", onClick: onCancel, style: { cursor: "pointer" } }),
|
|
1326
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1327
|
+
Button,
|
|
1328
|
+
{
|
|
1329
|
+
text: "Save and connect",
|
|
1330
|
+
tone: "primary",
|
|
1331
|
+
icon: CheckmarkCircleIcon,
|
|
1332
|
+
loading: saving,
|
|
1333
|
+
disabled: !token.trim() || saving,
|
|
1334
|
+
onClick: save,
|
|
1335
|
+
style: { cursor: "pointer" }
|
|
1336
|
+
}
|
|
1337
|
+
)
|
|
1338
|
+
] })
|
|
1230
1339
|
] }),
|
|
1231
1340
|
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Stack, { space: 4, padding: 4, children: [
|
|
1232
1341
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Stack, { space: 3, children: [
|
|
@@ -1267,6 +1376,7 @@ var VERCEL_HOOK_RE2 = /^https:\/\/api\.vercel\.com\/v1\/integrations\/deploy\//;
|
|
|
1267
1376
|
function DeployTargetForm({ initial, onSaved, onClose }) {
|
|
1268
1377
|
const client = (0, import_sanity2.useClient)({ apiVersion: "2025-01-01" });
|
|
1269
1378
|
const isEdit = Boolean(initial);
|
|
1379
|
+
const dialogId = (0, import_react9.useId)();
|
|
1270
1380
|
const nameId = (0, import_react9.useId)();
|
|
1271
1381
|
const urlId = (0, import_react9.useId)();
|
|
1272
1382
|
const urlErrorId = (0, import_react9.useId)();
|
|
@@ -1309,7 +1419,7 @@ function DeployTargetForm({ initial, onSaved, onClose }) {
|
|
|
1309
1419
|
Dialog,
|
|
1310
1420
|
{
|
|
1311
1421
|
header: isEdit ? `Edit "${initial?.name}"` : "Add deploy target",
|
|
1312
|
-
id:
|
|
1422
|
+
id: dialogId,
|
|
1313
1423
|
onClose,
|
|
1314
1424
|
width: 1,
|
|
1315
1425
|
footer: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
|
|
@@ -1408,7 +1518,7 @@ function DeployTargetForm({ initial, onSaved, onClose }) {
|
|
|
1408
1518
|
}
|
|
1409
1519
|
|
|
1410
1520
|
// src/version.ts
|
|
1411
|
-
var VERSION = "1.2.
|
|
1521
|
+
var VERSION = "1.2.1";
|
|
1412
1522
|
|
|
1413
1523
|
// src/components/DeployTool.tsx
|
|
1414
1524
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
@@ -1613,7 +1723,8 @@ function DeployTool() {
|
|
|
1613
1723
|
setShowTokenSetup(false);
|
|
1614
1724
|
load();
|
|
1615
1725
|
},
|
|
1616
|
-
onCancel: () => setShowTokenSetup(false)
|
|
1726
|
+
onCancel: () => setShowTokenSetup(false),
|
|
1727
|
+
hasToken: Boolean(token)
|
|
1617
1728
|
}
|
|
1618
1729
|
),
|
|
1619
1730
|
showCreateForm && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
@@ -1760,37 +1871,13 @@ var vercelDeploySchema = (0, import_sanity4.defineType)({
|
|
|
1760
1871
|
}
|
|
1761
1872
|
});
|
|
1762
1873
|
|
|
1763
|
-
// src/schema/vercelConfig.ts
|
|
1764
|
-
var import_sanity5 = require("sanity");
|
|
1765
|
-
var vercelConfigSchema = (0, import_sanity5.defineType)({
|
|
1766
|
-
name: "vercelDeploy.config",
|
|
1767
|
-
title: "Vercel Deploy Configuration",
|
|
1768
|
-
type: "document",
|
|
1769
|
-
fields: [
|
|
1770
|
-
(0, import_sanity5.defineField)({
|
|
1771
|
-
name: "accessToken",
|
|
1772
|
-
title: "Vercel API Token",
|
|
1773
|
-
type: "string",
|
|
1774
|
-
description: "Readable by anyone who can read this dataset. Delete this document to revoke the stored token."
|
|
1775
|
-
})
|
|
1776
|
-
],
|
|
1777
|
-
preview: {
|
|
1778
|
-
select: { token: "accessToken" },
|
|
1779
|
-
prepare: ({ token }) => ({
|
|
1780
|
-
title: "Vercel API Token",
|
|
1781
|
-
// Never render the secret itself in a preview.
|
|
1782
|
-
subtitle: token ? "Connected" : "Not set"
|
|
1783
|
-
})
|
|
1784
|
-
}
|
|
1785
|
-
});
|
|
1786
|
-
|
|
1787
1874
|
// src/index.ts
|
|
1788
|
-
var vercelDeploy = (0,
|
|
1875
|
+
var vercelDeploy = (0, import_sanity5.definePlugin)((options) => {
|
|
1789
1876
|
const config = options ?? {};
|
|
1790
1877
|
return {
|
|
1791
1878
|
name: "deploy-vercel-from-sanity",
|
|
1792
1879
|
schema: {
|
|
1793
|
-
types: [vercelDeploySchema
|
|
1880
|
+
types: [vercelDeploySchema]
|
|
1794
1881
|
},
|
|
1795
1882
|
tools: [
|
|
1796
1883
|
{
|
|
@@ -1804,7 +1891,6 @@ var vercelDeploy = (0, import_sanity6.definePlugin)((options) => {
|
|
|
1804
1891
|
});
|
|
1805
1892
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1806
1893
|
0 && (module.exports = {
|
|
1807
|
-
vercelConfigSchema,
|
|
1808
1894
|
vercelDeploy,
|
|
1809
1895
|
vercelDeploySchema
|
|
1810
1896
|
});
|