@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/README.md +92 -70
- package/dist/index.js +303 -111
- package/dist/index.mjs +312 -115
- package/package.json +1 -1
- package/src/components/DeployItem.tsx +334 -155
- package/src/components/DeployTool.tsx +34 -27
- package/src/components/TokenSetup.tsx +1 -1
- package/src/lib/api.ts +33 -2
- package/src/lib/helpers.ts +25 -8
- package/src/types.ts +11 -3
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// Per-deploy-target card — shows status, build timer, history, cancel, and
|
|
1
|
+
// Per-deploy-target card — shows status, build timer, history, cancel, deploy, copy URL, and error logs
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
3
3
|
import {
|
|
4
|
-
Card, Box, Stack, Flex, Text, Button, Tooltip, Badge, Spinner,
|
|
4
|
+
Card, Box, Stack, Flex, Text, Button, Tooltip, Badge, Spinner,
|
|
5
|
+
MenuButton, Menu, MenuItem, Code,
|
|
5
6
|
} from '@sanity/ui'
|
|
6
7
|
import {
|
|
7
8
|
RocketIcon, ClockIcon, TrashIcon, EllipsisVerticalIcon, LaunchIcon,
|
|
9
|
+
CopyIcon, CheckmarkIcon, WarningOutlineIcon,
|
|
8
10
|
} from '@sanity/icons'
|
|
9
|
-
import { listDeployments, cancelDeployment, triggerDeploy } from '../lib/api'
|
|
10
|
-
import { parseHookUrl, isActiveState, formatDuration, timeAgo, shortSha, safeHref } from '../lib/helpers'
|
|
11
|
+
import { listDeployments, cancelDeployment, triggerDeploy, getDeploymentEvents } from '../lib/api'
|
|
12
|
+
import { parseHookUrl, isActiveState, formatDuration, timeAgo, shortSha, safeHref, projectHref } from '../lib/helpers'
|
|
11
13
|
import { StatusBadge } from './StatusBadge'
|
|
12
14
|
import { DeployHistory } from './DeployHistory'
|
|
13
15
|
import type { DeployTarget, VercelDeployment } from '../types'
|
|
@@ -23,15 +25,24 @@ interface DeployItemProps {
|
|
|
23
25
|
export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
24
26
|
const { projectId, hookId } = parseHookUrl(target.url)
|
|
25
27
|
|
|
26
|
-
const [deployments, setDeployments]
|
|
28
|
+
const [deployments, setDeployments] = useState<VercelDeployment[]>([])
|
|
27
29
|
const [loadingInitial, setLoadingInitial] = useState(true)
|
|
28
|
-
const [triggering, setTriggering]
|
|
29
|
-
const [canceling, setCanceling]
|
|
30
|
-
const [deployError, setDeployError]
|
|
31
|
-
const [showHistory, setShowHistory]
|
|
32
|
-
const [elapsed, setElapsed]
|
|
30
|
+
const [triggering, setTriggering] = useState(false)
|
|
31
|
+
const [canceling, setCanceling] = useState(false)
|
|
32
|
+
const [deployError, setDeployError] = useState<string | null>(null)
|
|
33
|
+
const [showHistory, setShowHistory] = useState(false)
|
|
34
|
+
const [elapsed, setElapsed] = useState(0)
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
// Copy URL state
|
|
37
|
+
const [copied, setCopied] = useState(false)
|
|
38
|
+
|
|
39
|
+
// Error log expansion state
|
|
40
|
+
const [showErrorLogs, setShowErrorLogs] = useState(false)
|
|
41
|
+
const [errorLines, setErrorLines] = useState<string[]>([])
|
|
42
|
+
const [loadingLogs, setLoadingLogs] = useState(false)
|
|
43
|
+
const [logError, setLogError] = useState<string | null>(null)
|
|
44
|
+
|
|
45
|
+
const latest = deployments[0]
|
|
35
46
|
const isActive = triggering || isActiveState(latest?.state)
|
|
36
47
|
|
|
37
48
|
// ── Fetch deployments ──────────────────────────────────────────────────────
|
|
@@ -45,25 +56,27 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
45
56
|
}
|
|
46
57
|
}, [projectId, hookId, token, target.teamId])
|
|
47
58
|
|
|
48
|
-
// Initial fetch
|
|
49
59
|
useEffect(() => {
|
|
50
60
|
fetchDeployments().finally(() => setLoadingInitial(false))
|
|
51
61
|
}, [fetchDeployments])
|
|
52
62
|
|
|
53
|
-
// Polling while active
|
|
54
63
|
useEffect(() => {
|
|
55
64
|
if (!isActive) return
|
|
56
65
|
const id = setInterval(fetchDeployments, POLL_INTERVAL_MS)
|
|
57
66
|
return () => clearInterval(id)
|
|
58
67
|
}, [isActive, fetchDeployments])
|
|
59
68
|
|
|
60
|
-
// Stop triggering once a deployment appears in an active state
|
|
61
69
|
useEffect(() => {
|
|
62
|
-
if (triggering && latest && latest.state !== undefined)
|
|
63
|
-
setTriggering(false)
|
|
64
|
-
}
|
|
70
|
+
if (triggering && latest && latest.state !== undefined) setTriggering(false)
|
|
65
71
|
}, [triggering, latest])
|
|
66
72
|
|
|
73
|
+
// Reset error logs when the deployment changes
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
setShowErrorLogs(false)
|
|
76
|
+
setErrorLines([])
|
|
77
|
+
setLogError(null)
|
|
78
|
+
}, [latest?.uid])
|
|
79
|
+
|
|
67
80
|
// ── Build timer ───────────────────────────────────────────────────────────
|
|
68
81
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
69
82
|
useEffect(() => {
|
|
@@ -77,9 +90,7 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
77
90
|
setElapsed(0)
|
|
78
91
|
if (timerRef.current) clearInterval(timerRef.current)
|
|
79
92
|
}
|
|
80
|
-
return () => {
|
|
81
|
-
if (timerRef.current) clearInterval(timerRef.current)
|
|
82
|
-
}
|
|
93
|
+
return () => { if (timerRef.current) clearInterval(timerRef.current) }
|
|
83
94
|
}, [isActive, latest?.created])
|
|
84
95
|
|
|
85
96
|
// ── Actions ───────────────────────────────────────────────────────────────
|
|
@@ -88,7 +99,6 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
88
99
|
setTriggering(true)
|
|
89
100
|
try {
|
|
90
101
|
await triggerDeploy(target.url)
|
|
91
|
-
// Poll immediately to pick up the new deployment faster
|
|
92
102
|
setTimeout(fetchDeployments, 2000)
|
|
93
103
|
} catch (err) {
|
|
94
104
|
setTriggering(false)
|
|
@@ -109,161 +119,329 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
109
119
|
}
|
|
110
120
|
}, [latest?.uid, token, target.teamId, fetchDeployments])
|
|
111
121
|
|
|
122
|
+
const copyUrl = useCallback(() => {
|
|
123
|
+
if (!latest?.url) return
|
|
124
|
+
navigator.clipboard.writeText(`https://${latest.url}`).then(() => {
|
|
125
|
+
setCopied(true)
|
|
126
|
+
setTimeout(() => setCopied(false), 2000)
|
|
127
|
+
}).catch(err => {
|
|
128
|
+
console.error('deploy-vercel-from-sanity: clipboard error', err)
|
|
129
|
+
})
|
|
130
|
+
}, [latest?.url])
|
|
131
|
+
|
|
132
|
+
const fetchErrorLogs = useCallback(async () => {
|
|
133
|
+
if (!latest?.uid) return
|
|
134
|
+
setLoadingLogs(true)
|
|
135
|
+
setLogError(null)
|
|
136
|
+
try {
|
|
137
|
+
const events = await getDeploymentEvents({
|
|
138
|
+
deploymentId: latest.uid,
|
|
139
|
+
token,
|
|
140
|
+
teamId: target.teamId,
|
|
141
|
+
})
|
|
142
|
+
// Keep stderr lines and stdout lines that look like errors, in forward order
|
|
143
|
+
const lines = events
|
|
144
|
+
.filter(e => e.type === 'stderr' || e.type === 'stdout')
|
|
145
|
+
.map(e => e.text ?? '')
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.reverse() // API returns backward — flip to chronological
|
|
148
|
+
.slice(-30) // last 30 lines
|
|
149
|
+
setErrorLines(lines.length > 0 ? lines : ['No log output captured.'])
|
|
150
|
+
} catch (err) {
|
|
151
|
+
setLogError(err instanceof Error ? err.message : 'Failed to load build logs')
|
|
152
|
+
} finally {
|
|
153
|
+
setLoadingLogs(false)
|
|
154
|
+
}
|
|
155
|
+
}, [latest?.uid, token, target.teamId])
|
|
156
|
+
|
|
157
|
+
const toggleErrorLogs = useCallback(() => {
|
|
158
|
+
if (!showErrorLogs && errorLines.length === 0 && !logError) {
|
|
159
|
+
fetchErrorLogs()
|
|
160
|
+
}
|
|
161
|
+
setShowErrorLogs(v => !v)
|
|
162
|
+
}, [showErrorLogs, errorLines.length, logError, fetchErrorLogs])
|
|
163
|
+
|
|
112
164
|
// ── Derived display values ────────────────────────────────────────────────
|
|
113
|
-
const branch
|
|
165
|
+
const branch = latest?.meta?.githubCommitRef
|
|
114
166
|
const commitMsg = latest?.meta?.githubCommitMessage?.split('\n')[0]
|
|
115
|
-
const sha
|
|
116
|
-
const creator
|
|
167
|
+
const sha = shortSha(latest?.meta?.githubCommitSha)
|
|
168
|
+
const creator = latest?.creator?.username
|
|
117
169
|
const deployedAt = latest?.created ? timeAgo(latest.created) : null
|
|
170
|
+
const vercelProjectUrl = projectHref(latest?.inspectorUrl)
|
|
171
|
+
const isError = latest?.state === 'ERROR'
|
|
118
172
|
|
|
119
173
|
return (
|
|
120
174
|
<>
|
|
121
175
|
<Card padding={4} radius={2} shadow={1} tone="default">
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
176
|
+
<Flex gap={4} align="center">
|
|
177
|
+
|
|
178
|
+
{/* ── Left: info column ──────────────────────────────────── */}
|
|
179
|
+
<Stack space={2} flex={1} style={{ minWidth: 0 }}>
|
|
180
|
+
|
|
181
|
+
{/* Name + menu */}
|
|
182
|
+
<Flex align="center" justify="space-between" gap={2}>
|
|
126
183
|
<Text size={2} weight="semibold">{target.name}</Text>
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<Text size={0} muted>Hook: {hookId || '—'}</Text>
|
|
133
|
-
{target.teamId && (
|
|
134
|
-
<>
|
|
135
|
-
<Text size={0} muted>·</Text>
|
|
136
|
-
<Text size={0} muted>Team: {target.teamId}</Text>
|
|
137
|
-
</>
|
|
138
|
-
)}
|
|
139
|
-
</Flex>
|
|
140
|
-
</Stack>
|
|
141
|
-
|
|
142
|
-
{/* Action menu */}
|
|
143
|
-
<MenuButton
|
|
144
|
-
button={
|
|
145
|
-
<Button mode="ghost" icon={EllipsisVerticalIcon} />
|
|
146
|
-
}
|
|
147
|
-
id={`menu-${target._id}`}
|
|
148
|
-
menu={
|
|
149
|
-
<Menu>
|
|
150
|
-
<MenuItem
|
|
151
|
-
text="History"
|
|
152
|
-
icon={ClockIcon}
|
|
153
|
-
onClick={() => setShowHistory(true)}
|
|
154
|
-
/>
|
|
155
|
-
{safeHref(latest?.inspectorUrl) && (
|
|
184
|
+
<MenuButton
|
|
185
|
+
button={<Button mode="ghost" icon={EllipsisVerticalIcon} padding={2} />}
|
|
186
|
+
id={`menu-${target._id}`}
|
|
187
|
+
menu={
|
|
188
|
+
<Menu>
|
|
156
189
|
<MenuItem
|
|
157
|
-
text="
|
|
158
|
-
icon={
|
|
159
|
-
|
|
160
|
-
href={safeHref(latest?.inspectorUrl)}
|
|
161
|
-
target="_blank"
|
|
162
|
-
rel="noreferrer"
|
|
190
|
+
text="History"
|
|
191
|
+
icon={ClockIcon}
|
|
192
|
+
onClick={() => setShowHistory(true)}
|
|
163
193
|
/>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
195
|
+
<MenuItem
|
|
196
|
+
text="Build logs"
|
|
197
|
+
icon={LaunchIcon}
|
|
198
|
+
as="a"
|
|
199
|
+
href={safeHref(latest?.inspectorUrl)}
|
|
200
|
+
target="_blank"
|
|
201
|
+
rel="noreferrer"
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
{vercelProjectUrl && (
|
|
205
|
+
<MenuItem
|
|
206
|
+
text="Open in Vercel"
|
|
207
|
+
icon={LaunchIcon}
|
|
208
|
+
as="a"
|
|
209
|
+
href={vercelProjectUrl}
|
|
210
|
+
target="_blank"
|
|
211
|
+
rel="noreferrer"
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
{!target.disableDeleteAction && (
|
|
215
|
+
<MenuItem
|
|
216
|
+
text="Delete"
|
|
217
|
+
icon={TrashIcon}
|
|
218
|
+
tone="critical"
|
|
219
|
+
onClick={() => onDelete(target)}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
</Menu>
|
|
223
|
+
}
|
|
224
|
+
popover={{ placement: 'bottom-end' }}
|
|
225
|
+
/>
|
|
226
|
+
</Flex>
|
|
178
227
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
228
|
+
{/* Subtitle: project + hook IDs */}
|
|
229
|
+
<Flex gap={2} wrap="wrap">
|
|
230
|
+
<Text size={0} muted>
|
|
231
|
+
{projectId ? `${projectId.slice(0, 18)}…` : '—'}
|
|
232
|
+
</Text>
|
|
233
|
+
<Text size={0} muted>·</Text>
|
|
234
|
+
<Text size={0} muted>Hook: {hookId || '—'}</Text>
|
|
235
|
+
{target.teamId && (
|
|
236
|
+
<>
|
|
237
|
+
<Text size={0} muted>·</Text>
|
|
238
|
+
<Text size={0} muted>Team: {target.teamId}</Text>
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
184
241
|
</Flex>
|
|
185
|
-
) : (
|
|
186
|
-
<Stack space={3}>
|
|
187
|
-
<Flex align="center" gap={3} wrap="wrap">
|
|
188
|
-
{/* Status badge */}
|
|
189
|
-
{triggering ? (
|
|
190
|
-
<Flex align="center" gap={2}>
|
|
191
|
-
<Spinner muted />
|
|
192
|
-
<Badge tone="caution" mode="outline">Triggering…</Badge>
|
|
193
|
-
</Flex>
|
|
194
|
-
) : (
|
|
195
|
-
<StatusBadge state={latest?.state} showSpinner />
|
|
196
|
-
)}
|
|
197
242
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
243
|
+
{/* Status + metadata */}
|
|
244
|
+
{loadingInitial ? (
|
|
245
|
+
<Flex align="center" gap={2}>
|
|
246
|
+
<Spinner muted />
|
|
247
|
+
<Text size={1} muted>Loading…</Text>
|
|
248
|
+
</Flex>
|
|
249
|
+
) : (
|
|
250
|
+
<Stack space={2}>
|
|
251
|
+
<Flex align="center" gap={2} wrap="wrap">
|
|
252
|
+
{/* Status */}
|
|
253
|
+
{triggering ? (
|
|
254
|
+
<Flex align="center" gap={2}>
|
|
255
|
+
<Spinner muted />
|
|
256
|
+
<Badge tone="caution" mode="outline">Triggering…</Badge>
|
|
257
|
+
</Flex>
|
|
258
|
+
) : (
|
|
259
|
+
<StatusBadge state={latest?.state} showSpinner />
|
|
260
|
+
)}
|
|
205
261
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
262
|
+
{/* Build timer (active) or time since deploy (idle) */}
|
|
263
|
+
{isActive && elapsed > 0 ? (
|
|
264
|
+
<Flex align="center" gap={1}>
|
|
265
|
+
<ClockIcon />
|
|
266
|
+
<Text size={1} muted>{formatDuration(elapsed)}</Text>
|
|
267
|
+
</Flex>
|
|
268
|
+
) : (!isActive && deployedAt) ? (
|
|
269
|
+
<Text size={1} muted>{deployedAt}</Text>
|
|
270
|
+
) : null}
|
|
210
271
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
272
|
+
{/* Branch */}
|
|
273
|
+
{branch && (
|
|
274
|
+
<>
|
|
275
|
+
<Text size={1} muted>·</Text>
|
|
276
|
+
<Badge tone="default" mode="outline">{branch}</Badge>
|
|
277
|
+
</>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* Commit SHA — tooltip shows full message */}
|
|
281
|
+
{sha && (
|
|
282
|
+
<Tooltip
|
|
283
|
+
content={
|
|
284
|
+
<Box padding={2}>
|
|
285
|
+
<Text size={1}>{commitMsg ?? sha}</Text>
|
|
286
|
+
</Box>
|
|
287
|
+
}
|
|
288
|
+
portal
|
|
289
|
+
>
|
|
290
|
+
<Text
|
|
291
|
+
size={1}
|
|
292
|
+
muted
|
|
293
|
+
style={{ cursor: 'default', fontFamily: 'monospace' }}
|
|
294
|
+
>
|
|
295
|
+
{sha}
|
|
296
|
+
</Text>
|
|
297
|
+
</Tooltip>
|
|
298
|
+
)}
|
|
218
299
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
300
|
+
{/* Creator */}
|
|
301
|
+
{creator && <Text size={1} muted>by {creator}</Text>}
|
|
302
|
+
|
|
303
|
+
{/* Preview link + copy URL */}
|
|
304
|
+
{latest?.url && latest.state === 'READY' && (
|
|
305
|
+
<>
|
|
306
|
+
<a
|
|
307
|
+
href={`https://${latest.url}`}
|
|
308
|
+
target="_blank"
|
|
309
|
+
rel="noreferrer"
|
|
310
|
+
style={{ color: 'inherit' }}
|
|
311
|
+
>
|
|
312
|
+
<Flex align="center" gap={1}>
|
|
313
|
+
<LaunchIcon />
|
|
314
|
+
<Text size={1}>Preview</Text>
|
|
315
|
+
</Flex>
|
|
316
|
+
</a>
|
|
317
|
+
<Tooltip
|
|
318
|
+
content={
|
|
319
|
+
<Box padding={2}>
|
|
320
|
+
<Text size={1}>{copied ? 'Copied!' : 'Copy URL'}</Text>
|
|
321
|
+
</Box>
|
|
322
|
+
}
|
|
323
|
+
portal
|
|
324
|
+
>
|
|
325
|
+
<Button
|
|
326
|
+
mode="ghost"
|
|
327
|
+
icon={copied ? CheckmarkIcon : CopyIcon}
|
|
328
|
+
padding={1}
|
|
329
|
+
tone={copied ? 'positive' : 'default'}
|
|
330
|
+
onClick={copyUrl}
|
|
331
|
+
/>
|
|
332
|
+
</Tooltip>
|
|
333
|
+
</>
|
|
334
|
+
)}
|
|
335
|
+
</Flex>
|
|
336
|
+
|
|
337
|
+
{/* Commit message */}
|
|
338
|
+
{commitMsg && (
|
|
339
|
+
<Text
|
|
340
|
+
size={0}
|
|
341
|
+
muted
|
|
342
|
+
style={{
|
|
343
|
+
overflow: 'hidden',
|
|
344
|
+
textOverflow: 'ellipsis',
|
|
345
|
+
whiteSpace: 'nowrap',
|
|
346
|
+
}}
|
|
228
347
|
>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
</Text>
|
|
232
|
-
</Tooltip>
|
|
348
|
+
{commitMsg}
|
|
349
|
+
</Text>
|
|
233
350
|
)}
|
|
234
351
|
|
|
235
|
-
{/*
|
|
236
|
-
{
|
|
237
|
-
<
|
|
238
|
-
|
|
352
|
+
{/* Error expansion */}
|
|
353
|
+
{isError && (
|
|
354
|
+
<Stack space={2}>
|
|
355
|
+
<Button
|
|
356
|
+
text={showErrorLogs ? 'Hide error details' : 'Show error details'}
|
|
357
|
+
mode="ghost"
|
|
358
|
+
tone="critical"
|
|
359
|
+
icon={WarningOutlineIcon}
|
|
360
|
+
fontSize={1}
|
|
361
|
+
padding={2}
|
|
362
|
+
onClick={toggleErrorLogs}
|
|
363
|
+
style={{ alignSelf: 'flex-start' }}
|
|
364
|
+
/>
|
|
239
365
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
366
|
+
{showErrorLogs && (
|
|
367
|
+
<Card tone="critical" radius={2} padding={3}>
|
|
368
|
+
{loadingLogs && (
|
|
369
|
+
<Flex align="center" gap={2}>
|
|
370
|
+
<Spinner muted />
|
|
371
|
+
<Text size={1} muted>Loading logs…</Text>
|
|
372
|
+
</Flex>
|
|
373
|
+
)}
|
|
374
|
+
{logError && (
|
|
375
|
+
<Stack space={2}>
|
|
376
|
+
<Text size={1}>{logError}</Text>
|
|
377
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
378
|
+
<a
|
|
379
|
+
href={safeHref(latest?.inspectorUrl)}
|
|
380
|
+
target="_blank"
|
|
381
|
+
rel="noreferrer"
|
|
382
|
+
style={{ color: 'inherit' }}
|
|
383
|
+
>
|
|
384
|
+
<Flex align="center" gap={1}>
|
|
385
|
+
<LaunchIcon />
|
|
386
|
+
<Text size={1}>View full logs in Vercel</Text>
|
|
387
|
+
</Flex>
|
|
388
|
+
</a>
|
|
389
|
+
)}
|
|
390
|
+
</Stack>
|
|
391
|
+
)}
|
|
392
|
+
{!loadingLogs && !logError && errorLines.length > 0 && (
|
|
393
|
+
<Stack space={2}>
|
|
394
|
+
<Box
|
|
395
|
+
style={{
|
|
396
|
+
maxHeight: 240,
|
|
397
|
+
overflowY: 'auto',
|
|
398
|
+
fontFamily: 'monospace',
|
|
399
|
+
fontSize: 11,
|
|
400
|
+
lineHeight: 1.6,
|
|
401
|
+
}}
|
|
402
|
+
>
|
|
403
|
+
{errorLines.map((line, i) => (
|
|
404
|
+
<Code
|
|
405
|
+
key={i}
|
|
406
|
+
size={1}
|
|
407
|
+
style={{ display: 'block', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}
|
|
408
|
+
>
|
|
409
|
+
{line}
|
|
410
|
+
</Code>
|
|
411
|
+
))}
|
|
412
|
+
</Box>
|
|
413
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
414
|
+
<a
|
|
415
|
+
href={safeHref(latest?.inspectorUrl)}
|
|
416
|
+
target="_blank"
|
|
417
|
+
rel="noreferrer"
|
|
418
|
+
style={{ color: 'inherit' }}
|
|
419
|
+
>
|
|
420
|
+
<Flex align="center" gap={1}>
|
|
421
|
+
<LaunchIcon />
|
|
422
|
+
<Text size={1}>View full logs in Vercel</Text>
|
|
423
|
+
</Flex>
|
|
424
|
+
</a>
|
|
425
|
+
)}
|
|
426
|
+
</Stack>
|
|
427
|
+
)}
|
|
428
|
+
</Card>
|
|
429
|
+
)}
|
|
430
|
+
</Stack>
|
|
253
431
|
)}
|
|
254
|
-
</Flex>
|
|
255
432
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
433
|
+
{/* Trigger error */}
|
|
434
|
+
{deployError && (
|
|
435
|
+
<Card tone="critical" padding={2} radius={2}>
|
|
436
|
+
<Text size={1}>{deployError}</Text>
|
|
437
|
+
</Card>
|
|
438
|
+
)}
|
|
439
|
+
</Stack>
|
|
440
|
+
)}
|
|
441
|
+
</Stack>
|
|
264
442
|
|
|
265
|
-
{/* ──
|
|
266
|
-
<
|
|
443
|
+
{/* ── Right: action buttons ──────────────────────────────── */}
|
|
444
|
+
<Stack space={2} style={{ flexShrink: 0 }}>
|
|
267
445
|
{isActiveState(latest?.state) && (
|
|
268
446
|
<Button
|
|
269
447
|
text="Cancel"
|
|
@@ -282,8 +460,9 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
282
460
|
disabled={isActive}
|
|
283
461
|
onClick={deploy}
|
|
284
462
|
/>
|
|
285
|
-
</
|
|
286
|
-
|
|
463
|
+
</Stack>
|
|
464
|
+
|
|
465
|
+
</Flex>
|
|
287
466
|
</Card>
|
|
288
467
|
|
|
289
468
|
{showHistory && (
|