@liiift-studio/deploy-vercel-from-sanity 0.1.3 → 0.1.6

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,118 +355,276 @@ 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: 4, radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Flex, { gap: 4, align: "center", children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, 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: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui3.Stack, { space: 2, style: { flexShrink: 0 }, children: [
437
628
  isActiveState(latest?.state) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
438
629
  import_ui3.Button,
439
630
  {
@@ -475,7 +666,7 @@ var import_sanity = require("sanity");
475
666
  var import_ui4 = require("@sanity/ui");
476
667
  var import_icons3 = require("@sanity/icons");
477
668
  var import_jsx_runtime4 = require("react/jsx-runtime");
478
- var TOKEN_DOC_ID = "secrets.vercelDeploy";
669
+ var TOKEN_DOC_ID = "config.vercelDeploy";
479
670
  function TokenSetup({ onSaved }) {
480
671
  const client = (0, import_sanity.useClient)({ apiVersion: "2025-01-01" });
481
672
  const [token, setToken] = (0, import_react3.useState)("");
@@ -548,7 +739,7 @@ function TokenSetup({ onSaved }) {
548
739
 
549
740
  // src/components/DeployTool.tsx
550
741
  var import_jsx_runtime5 = require("react/jsx-runtime");
551
- var TOKEN_QUERY = `*[_id == "secrets.vercelDeploy"][0].accessToken`;
742
+ var TOKEN_QUERY = `*[_id == "config.vercelDeploy"][0].accessToken`;
552
743
  var TARGETS_QUERY = `*[_type == "vercel_deploy"] | order(_createdAt asc)`;
553
744
  function DeployTool() {
554
745
  const client = (0, import_sanity2.useClient)({ apiVersion: "2025-01-01" });
@@ -602,13 +793,7 @@ function DeployTool() {
602
793
  if (loading) {
603
794
  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
795
  }
605
- if (!token && !showTokenSetup) {
606
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TokenSetup, { onSaved: () => {
607
- setShowTokenSetup(false);
608
- load();
609
- } });
610
- }
611
- if (showTokenSetup) {
796
+ if (!token || showTokenSetup) {
612
797
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
613
798
  TokenSetup,
614
799
  {
@@ -626,26 +811,33 @@ function DeployTool() {
626
811
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons4.RocketIcon, {}),
627
812
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Heading, { size: 2, children: "Deploy" })
628
813
  ] }),
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
- )
814
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 3, children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Badge, { tone: "positive", mode: "outline", children: "Connected" }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
817
+ import_ui5.Button,
818
+ {
819
+ text: "Change Token",
820
+ mode: "ghost",
821
+ icon: import_icons4.TokenIcon,
822
+ fontSize: 1,
823
+ onClick: () => setShowTokenSetup(true)
824
+ }
825
+ )
826
+ ] })
639
827
  ] }),
640
828
  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
829
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 2, weight: "semibold", children: "No deploy targets configured" }),
642
830
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Text, { size: 1, muted: true, children: [
643
831
  "Create a ",
644
832
  /* @__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."
833
+ " document in the dataset with a Vercel deploy hook URL."
646
834
  ] })
647
835
  ] }) }),
648
- token && targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
836
+ targets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
837
+ display: "grid",
838
+ gridTemplateColumns: "repeat(auto-fill, minmax(380px, 1fr))",
839
+ gap: "12px"
840
+ }, children: targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
649
841
  DeployItem,
650
842
  {
651
843
  target,
@@ -653,7 +845,7 @@ function DeployTool() {
653
845
  onDelete: setPendingDelete
654
846
  },
655
847
  target._id
656
- ))
848
+ )) })
657
849
  ] }) }),
658
850
  pendingDelete && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
659
851
  import_ui5.Dialog,