@liiift-studio/deploy-vercel-from-sanity 0.1.4 → 0.1.7

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/dist/index.js CHANGED
@@ -40,6 +40,7 @@ var import_icons2 = require("@sanity/icons");
40
40
 
41
41
  // src/lib/api.ts
42
42
  var BASE = "https://api.vercel.com";
43
+ var VERCEL_HOOK_RE = /^https:\/\/api\.vercel\.com\/v1\/integrations\/deploy\//;
43
44
  async function vercelFetch(path, token, init) {
44
45
  const res = await fetch(`${BASE}${path}`, {
45
46
  ...init,
@@ -75,9 +76,22 @@ async function cancelDeployment(opts) {
75
76
  });
76
77
  }
77
78
  async function triggerDeploy(hookUrl) {
79
+ if (!VERCEL_HOOK_RE.test(hookUrl)) {
80
+ throw new Error("Invalid deploy hook URL \u2014 must be a Vercel hook (api.vercel.com/v1/integrations/deploy/\u2026)");
81
+ }
78
82
  const res = await fetch(hookUrl, { method: "POST" });
79
83
  if (!res.ok) throw new Error(`Deploy hook returned ${res.status}`);
80
84
  }
85
+ async function getDeploymentEvents(opts) {
86
+ const params = new URLSearchParams({ limit: "100", direction: "backward" });
87
+ if (opts.teamId) params.set("teamId", opts.teamId);
88
+ const raw = await vercelFetch(
89
+ `/v2/deployments/${opts.deploymentId}/events?${params}`,
90
+ opts.token
91
+ );
92
+ const events = Array.isArray(raw) ? raw : raw.events ?? [];
93
+ return events.filter((e) => e.text?.trim());
94
+ }
81
95
 
82
96
  // src/lib/helpers.ts
83
97
  function parseHookUrl(url) {
@@ -146,6 +160,17 @@ function stateLabel(state) {
146
160
  return { label: "Unknown", tone: "default" };
147
161
  }
148
162
  }
163
+ function projectHref(inspectorUrl) {
164
+ if (!inspectorUrl) return null;
165
+ try {
166
+ const { origin, pathname } = new URL(inspectorUrl);
167
+ const parts = pathname.split("/").filter(Boolean);
168
+ if (parts.length < 2) return null;
169
+ return `${origin}/${parts[0]}/${parts[1]}`;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
149
174
 
150
175
  // src/components/StatusBadge.tsx
151
176
  var import_ui = require("@sanity/ui");
@@ -259,6 +284,11 @@ function DeployItem({ target, token, onDelete }) {
259
284
  const [deployError, setDeployError] = (0, import_react2.useState)(null);
260
285
  const [showHistory, setShowHistory] = (0, import_react2.useState)(false);
261
286
  const [elapsed, setElapsed] = (0, import_react2.useState)(0);
287
+ const [copied, setCopied] = (0, import_react2.useState)(false);
288
+ const [showErrorLogs, setShowErrorLogs] = (0, import_react2.useState)(false);
289
+ const [errorLines, setErrorLines] = (0, import_react2.useState)([]);
290
+ const [loadingLogs, setLoadingLogs] = (0, import_react2.useState)(false);
291
+ const [logError, setLogError] = (0, import_react2.useState)(null);
262
292
  const latest = deployments[0];
263
293
  const isActive = triggering || isActiveState(latest?.state);
264
294
  const fetchDeployments = (0, import_react2.useCallback)(async () => {
@@ -279,10 +309,13 @@ function DeployItem({ target, token, onDelete }) {
279
309
  return () => clearInterval(id);
280
310
  }, [isActive, fetchDeployments]);
281
311
  (0, import_react2.useEffect)(() => {
282
- if (triggering && latest && latest.state !== void 0) {
283
- setTriggering(false);
284
- }
312
+ if (triggering && latest && latest.state !== void 0) setTriggering(false);
285
313
  }, [triggering, latest]);
314
+ (0, import_react2.useEffect)(() => {
315
+ setShowErrorLogs(false);
316
+ setErrorLines([]);
317
+ setLogError(null);
318
+ }, [latest?.uid]);
286
319
  const timerRef = (0, import_react2.useRef)(null);
287
320
  (0, import_react2.useEffect)(() => {
288
321
  if (isActive) {
@@ -322,141 +355,308 @@ function DeployItem({ target, token, onDelete }) {
322
355
  setCanceling(false);
323
356
  }
324
357
  }, [latest?.uid, token, target.teamId, fetchDeployments]);
358
+ const copyUrl = (0, import_react2.useCallback)(() => {
359
+ if (!latest?.url) return;
360
+ navigator.clipboard.writeText(`https://${latest.url}`).then(() => {
361
+ setCopied(true);
362
+ setTimeout(() => setCopied(false), 2e3);
363
+ }).catch((err) => {
364
+ console.error("deploy-vercel-from-sanity: clipboard error", err);
365
+ });
366
+ }, [latest?.url]);
367
+ const fetchErrorLogs = (0, import_react2.useCallback)(async () => {
368
+ if (!latest?.uid) return;
369
+ setLoadingLogs(true);
370
+ setLogError(null);
371
+ try {
372
+ const events = await getDeploymentEvents({
373
+ deploymentId: latest.uid,
374
+ token,
375
+ teamId: target.teamId
376
+ });
377
+ const lines = events.filter((e) => e.type === "stderr" || e.type === "stdout").map((e) => e.text ?? "").filter(Boolean).reverse().slice(-30);
378
+ setErrorLines(lines.length > 0 ? lines : ["No log output captured."]);
379
+ } catch (err) {
380
+ setLogError(err instanceof Error ? err.message : "Failed to load build logs");
381
+ } finally {
382
+ setLoadingLogs(false);
383
+ }
384
+ }, [latest?.uid, token, target.teamId]);
385
+ const toggleErrorLogs = (0, import_react2.useCallback)(() => {
386
+ if (!showErrorLogs && errorLines.length === 0 && !logError) {
387
+ fetchErrorLogs();
388
+ }
389
+ setShowErrorLogs((v) => !v);
390
+ }, [showErrorLogs, errorLines.length, logError, fetchErrorLogs]);
325
391
  const branch = latest?.meta?.githubCommitRef;
326
392
  const commitMsg = latest?.meta?.githubCommitMessage?.split("\n")[0];
327
393
  const sha = shortSha(latest?.meta?.githubCommitSha);
328
394
  const creator = latest?.creator?.username;
329
395
  const deployedAt = latest?.created ? timeAgo(latest.created) : null;
396
+ const vercelProjectUrl = projectHref(latest?.inspectorUrl);
397
+ const isError = latest?.state === "ERROR";
330
398
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
331
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Card, { padding: 4, radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 4, children: [
332
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "flex-start", justify: "space-between", gap: 3, children: [
333
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, flex: 1, children: [
399
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Card, { padding: 5, radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { gap: 4, align: "stretch", children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 3, flex: 1, style: { minWidth: 0 }, children: [
401
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", justify: "space-between", gap: 2, children: [
334
402
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 2, weight: "semibold", children: target.name }),
335
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { gap: 2, wrap: "wrap", children: [
336
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 0, muted: true, children: projectId ? `${projectId.slice(0, 18)}\u2026` : "\u2014" }),
403
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
404
+ import_ui3.MenuButton,
405
+ {
406
+ button: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Button, { mode: "ghost", icon: import_icons2.EllipsisVerticalIcon, padding: 2 }),
407
+ id: `menu-${target._id}`,
408
+ menu: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Menu, { children: [
409
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
410
+ import_ui3.MenuItem,
411
+ {
412
+ text: "History",
413
+ icon: import_icons2.ClockIcon,
414
+ onClick: () => setShowHistory(true)
415
+ }
416
+ ),
417
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
418
+ import_ui3.MenuItem,
419
+ {
420
+ text: "Build logs",
421
+ icon: import_icons2.LaunchIcon,
422
+ as: "a",
423
+ href: safeHref(latest?.inspectorUrl),
424
+ target: "_blank",
425
+ rel: "noreferrer"
426
+ }
427
+ ),
428
+ vercelProjectUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
429
+ import_ui3.MenuItem,
430
+ {
431
+ text: "Open in Vercel",
432
+ icon: import_icons2.LaunchIcon,
433
+ as: "a",
434
+ href: vercelProjectUrl,
435
+ target: "_blank",
436
+ rel: "noreferrer"
437
+ }
438
+ ),
439
+ !target.disableDeleteAction && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
+ import_ui3.MenuItem,
441
+ {
442
+ text: "Delete",
443
+ icon: import_icons2.TrashIcon,
444
+ tone: "critical",
445
+ onClick: () => onDelete(target)
446
+ }
447
+ )
448
+ ] }),
449
+ popover: { placement: "bottom-end" }
450
+ }
451
+ )
452
+ ] }),
453
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { gap: 2, wrap: "wrap", children: [
454
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 0, muted: true, children: projectId ? `${projectId.slice(0, 18)}\u2026` : "\u2014" }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 0, muted: true, children: "\xB7" }),
456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Text, { size: 0, muted: true, children: [
457
+ "Hook: ",
458
+ hookId || "\u2014"
459
+ ] }),
460
+ target.teamId && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
337
461
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 0, muted: true, children: "\xB7" }),
338
462
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Text, { size: 0, muted: true, children: [
339
- "Hook: ",
340
- hookId || "\u2014"
341
- ] }),
342
- target.teamId && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
343
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 0, muted: true, children: "\xB7" }),
344
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Text, { size: 0, muted: true, children: [
345
- "Team: ",
346
- target.teamId
347
- ] })
463
+ "Team: ",
464
+ target.teamId
348
465
  ] })
349
466
  ] })
350
467
  ] }),
351
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
352
- import_ui3.MenuButton,
353
- {
354
- button: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Button, { mode: "ghost", icon: import_icons2.EllipsisVerticalIcon }),
355
- id: `menu-${target._id}`,
356
- menu: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Menu, { children: [
468
+ loadingInitial ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Spinner, { muted: true }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: "Loading\u2026" })
471
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, children: [
472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, wrap: "wrap", children: [
473
+ triggering ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, children: [
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Spinner, { muted: true }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Badge, { tone: "caution", mode: "outline", children: "Triggering\u2026" })
476
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBadge, { state: latest?.state, showSpinner: true }),
477
+ isActive && elapsed > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
478
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.ClockIcon, {}),
479
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: formatDuration(elapsed) })
480
+ ] }) : !isActive && deployedAt ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: deployedAt }) : null,
481
+ branch && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: "\xB7" }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Badge, { tone: "default", mode: "outline", children: branch })
484
+ ] }),
485
+ sha && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
486
+ import_ui3.Tooltip,
487
+ {
488
+ content: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: commitMsg ?? sha }) }),
489
+ portal: true,
490
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
491
+ import_ui3.Text,
492
+ {
493
+ size: 1,
494
+ muted: true,
495
+ style: { cursor: "default", fontFamily: "monospace" },
496
+ children: sha
497
+ }
498
+ )
499
+ }
500
+ ),
501
+ creator && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Text, { size: 1, muted: true, children: [
502
+ "by ",
503
+ creator
504
+ ] }),
505
+ latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
357
506
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
358
- import_ui3.MenuItem,
359
- {
360
- text: "History",
361
- icon: import_icons2.ClockIcon,
362
- onClick: () => setShowHistory(true)
363
- }
364
- ),
365
- safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
366
- import_ui3.MenuItem,
507
+ "a",
367
508
  {
368
- text: "Build logs",
369
- icon: import_icons2.LaunchIcon,
370
- as: "a",
371
- href: safeHref(latest?.inspectorUrl),
509
+ href: `https://${latest.url}`,
372
510
  target: "_blank",
373
- rel: "noreferrer"
511
+ rel: "noreferrer",
512
+ style: { color: "inherit" },
513
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.LaunchIcon, {}),
515
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: "Preview" })
516
+ ] })
374
517
  }
375
518
  ),
376
- !target.disableDeleteAction && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
377
- import_ui3.MenuItem,
519
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
520
+ import_ui3.Tooltip,
378
521
  {
379
- text: "Delete",
380
- icon: import_icons2.TrashIcon,
381
- tone: "critical",
382
- onClick: () => onDelete(target)
522
+ content: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: copied ? "Copied!" : "Copy URL" }) }),
523
+ portal: true,
524
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
525
+ import_ui3.Button,
526
+ {
527
+ mode: "ghost",
528
+ icon: copied ? import_icons2.CheckmarkIcon : import_icons2.CopyIcon,
529
+ padding: 1,
530
+ tone: copied ? "positive" : "default",
531
+ onClick: copyUrl
532
+ }
533
+ )
383
534
  }
384
535
  )
385
- ] }),
386
- popover: { placement: "bottom-end" }
387
- }
388
- )
389
- ] }),
390
- loadingInitial ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, children: [
391
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Spinner, { muted: true }),
392
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: "Loading\u2026" })
393
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 3, children: [
394
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 3, wrap: "wrap", children: [
395
- triggering ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, children: [
396
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Spinner, { muted: true }),
397
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Badge, { tone: "caution", mode: "outline", children: "Triggering\u2026" })
398
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBadge, { state: latest?.state, showSpinner: true }),
399
- isActive && elapsed > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
400
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.ClockIcon, {}),
401
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: formatDuration(elapsed) })
402
- ] }),
403
- !isActive && deployedAt && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: deployedAt }),
404
- branch && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
405
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: "\xB7" }),
406
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Badge, { tone: "default", mode: "outline", children: branch })
536
+ ] })
407
537
  ] }),
408
- sha && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
409
- import_ui3.Tooltip,
538
+ commitMsg && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
539
+ import_ui3.Text,
410
540
  {
411
- content: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: commitMsg ?? sha }) }),
412
- portal: true,
413
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, style: { cursor: "default" }, children: sha })
541
+ size: 0,
542
+ muted: true,
543
+ style: {
544
+ overflow: "hidden",
545
+ textOverflow: "ellipsis",
546
+ whiteSpace: "nowrap"
547
+ },
548
+ children: commitMsg
414
549
  }
415
550
  ),
416
- creator && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Text, { size: 1, muted: true, children: [
417
- "by ",
418
- creator
419
- ] }),
420
- latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
421
- "a",
422
- {
423
- href: `https://${latest.url}`,
424
- target: "_blank",
425
- rel: "noreferrer",
426
- style: { color: "inherit" },
427
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
428
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.LaunchIcon, {}),
429
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: "Preview" })
551
+ isError && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
553
+ import_ui3.Button,
554
+ {
555
+ text: showErrorLogs ? "Hide error details" : "Show error details",
556
+ mode: "ghost",
557
+ tone: "critical",
558
+ icon: import_icons2.WarningOutlineIcon,
559
+ fontSize: 1,
560
+ padding: 2,
561
+ onClick: toggleErrorLogs,
562
+ style: { alignSelf: "flex-start" }
563
+ }
564
+ ),
565
+ showErrorLogs && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Card, { tone: "critical", radius: 2, padding: 3, children: [
566
+ loadingLogs && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 2, children: [
567
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Spinner, { muted: true }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, muted: true, children: "Loading logs\u2026" })
569
+ ] }),
570
+ logError && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, children: [
571
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: logError }),
572
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
573
+ "a",
574
+ {
575
+ href: safeHref(latest?.inspectorUrl),
576
+ target: "_blank",
577
+ rel: "noreferrer",
578
+ style: { color: "inherit" },
579
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.LaunchIcon, {}),
581
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: "View full logs in Vercel" })
582
+ ] })
583
+ }
584
+ )
585
+ ] }),
586
+ !loadingLogs && !logError && errorLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
588
+ import_ui3.Box,
589
+ {
590
+ style: {
591
+ maxHeight: 240,
592
+ overflowY: "auto",
593
+ fontFamily: "monospace",
594
+ fontSize: 11,
595
+ lineHeight: 1.6
596
+ },
597
+ children: errorLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
598
+ import_ui3.Code,
599
+ {
600
+ size: 1,
601
+ style: { display: "block", whiteSpace: "pre-wrap", wordBreak: "break-all" },
602
+ children: line
603
+ },
604
+ i
605
+ ))
606
+ }
607
+ ),
608
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
609
+ "a",
610
+ {
611
+ href: safeHref(latest?.inspectorUrl),
612
+ target: "_blank",
613
+ rel: "noreferrer",
614
+ style: { color: "inherit" },
615
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", gap: 1, children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.LaunchIcon, {}),
617
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: "View full logs in Vercel" })
618
+ ] })
619
+ }
620
+ )
430
621
  ] })
431
- }
432
- )
433
- ] }),
434
- deployError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: deployError }) })
622
+ ] })
623
+ ] }),
624
+ deployError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui3.Text, { size: 1, children: deployError }) })
625
+ ] })
435
626
  ] }),
436
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { align: "center", justify: "flex-end", gap: 2, children: [
437
- isActiveState(latest?.state) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
438
- import_ui3.Button,
439
- {
440
- text: "Cancel",
441
- mode: "ghost",
442
- tone: "critical",
443
- loading: canceling,
444
- disabled: canceling,
445
- onClick: cancel
446
- }
447
- ),
448
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
449
- import_ui3.Button,
450
- {
451
- text: "Deploy",
452
- tone: "primary",
453
- icon: import_icons2.RocketIcon,
454
- loading: triggering,
455
- disabled: isActive,
456
- onClick: deploy
457
- }
458
- )
459
- ] })
627
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
628
+ import_ui3.Flex,
629
+ {
630
+ direction: "column",
631
+ gap: 2,
632
+ style: { flexShrink: 0, alignSelf: "stretch" },
633
+ children: [
634
+ isActiveState(latest?.state) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
635
+ import_ui3.Button,
636
+ {
637
+ text: "Cancel",
638
+ mode: "ghost",
639
+ tone: "critical",
640
+ loading: canceling,
641
+ disabled: canceling,
642
+ onClick: cancel
643
+ }
644
+ ),
645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
646
+ import_ui3.Button,
647
+ {
648
+ text: "Deploy",
649
+ tone: "primary",
650
+ icon: import_icons2.RocketIcon,
651
+ loading: triggering,
652
+ disabled: isActive,
653
+ onClick: deploy,
654
+ style: { flex: 1 }
655
+ }
656
+ )
657
+ ]
658
+ }
659
+ )
460
660
  ] }) }),
461
661
  showHistory && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
462
662
  DeployHistory,
@@ -602,13 +802,7 @@ function DeployTool() {
602
802
  if (loading) {
603
803
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Card, { height: "fill", tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Flex, { align: "center", justify: "center", height: "fill", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Spinner, { muted: true }) }) });
604
804
  }
605
- if (!token && !showTokenSetup) {
606
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TokenSetup, { onSaved: () => {
607
- setShowTokenSetup(false);
608
- load();
609
- } });
610
- }
611
- if (showTokenSetup) {
805
+ if (!token || showTokenSetup) {
612
806
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
613
807
  TokenSetup,
614
808
  {
@@ -626,26 +820,33 @@ function DeployTool() {
626
820
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons4.RocketIcon, {}),
627
821
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Heading, { size: 2, children: "Deploy" })
628
822
  ] }),
629
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
630
- import_ui5.Button,
631
- {
632
- text: "API Token",
633
- mode: "ghost",
634
- icon: import_icons4.TokenIcon,
635
- fontSize: 1,
636
- onClick: () => setShowTokenSetup(true)
637
- }
638
- )
823
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 3, children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Badge, { tone: "positive", mode: "outline", children: "Connected" }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
826
+ import_ui5.Button,
827
+ {
828
+ text: "Change Token",
829
+ mode: "ghost",
830
+ icon: import_icons4.TokenIcon,
831
+ fontSize: 1,
832
+ onClick: () => setShowTokenSetup(true)
833
+ }
834
+ )
835
+ ] })
639
836
  ] }),
640
837
  targets.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Card, { padding: 5, radius: 2, tone: "transparent", shadow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Stack, { space: 3, style: { textAlign: "center" }, children: [
641
838
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 2, weight: "semibold", children: "No deploy targets configured" }),
642
839
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Text, { size: 1, muted: true, children: [
643
840
  "Create a ",
644
841
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "vercel_deploy" }),
645
- " document in the dataset with a Vercel deploy hook URL, or add one via the Sanity CLI."
842
+ " document in the dataset with a Vercel deploy hook URL."
646
843
  ] })
647
844
  ] }) }),
648
- token && targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
845
+ targets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
846
+ display: "grid",
847
+ gridTemplateColumns: "repeat(auto-fill, minmax(540px, 1fr))",
848
+ gap: "16px"
849
+ }, children: targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
649
850
  DeployItem,
650
851
  {
651
852
  target,
@@ -653,7 +854,7 @@ function DeployTool() {
653
854
  onDelete: setPendingDelete
654
855
  },
655
856
  target._id
656
- ))
857
+ )) })
657
858
  ] }) }),
658
859
  pendingDelete && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
659
860
  import_ui5.Dialog,