@liiift-studio/deploy-vercel-from-sanity 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +586 -329
- package/dist/index.mjs +597 -336
- package/package.json +1 -1
- package/src/components/DeployHistory.tsx +1 -1
- package/src/components/DeployItem.tsx +346 -275
- package/src/components/DeployTargetForm.tsx +143 -0
- package/src/components/DeployTool.tsx +127 -33
- package/src/components/StatusBadge.tsx +1 -1
- package/src/components/TokenSetup.tsx +57 -54
- package/src/version.ts +2 -0
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
MenuButton, Menu, MenuItem, Code,
|
|
6
6
|
} from '@sanity/ui'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
CopyIcon, CheckmarkIcon, WarningOutlineIcon,
|
|
8
|
+
ClockIcon, TrashIcon, EllipsisVerticalIcon, LaunchIcon,
|
|
9
|
+
CopyIcon, CheckmarkIcon, WarningOutlineIcon, ChevronDownIcon, ChevronUpIcon, EditIcon, SchemaIcon
|
|
10
10
|
} from '@sanity/icons'
|
|
11
11
|
import { listDeployments, cancelDeployment, triggerDeploy, getDeploymentEvents } from '../lib/api'
|
|
12
12
|
import { parseHookUrl, isActiveState, formatDuration, timeAgo, shortSha, safeHref, projectHref } from '../lib/helpers'
|
|
@@ -15,34 +15,33 @@ import { DeployHistory } from './DeployHistory'
|
|
|
15
15
|
import type { DeployTarget, VercelDeployment } from '../types'
|
|
16
16
|
|
|
17
17
|
const POLL_INTERVAL_MS = 5_000
|
|
18
|
+
const LABEL_WIDTH = 64
|
|
18
19
|
|
|
19
20
|
interface DeployItemProps {
|
|
20
21
|
target: DeployTarget
|
|
21
22
|
token: string
|
|
22
23
|
onDelete: (target: DeployTarget) => void
|
|
24
|
+
onEdit: (target: DeployTarget) => void
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
27
|
+
export function DeployItem({ target, token, onDelete, onEdit }: DeployItemProps) {
|
|
26
28
|
const { projectId, hookId } = parseHookUrl(target.url)
|
|
27
29
|
|
|
28
|
-
const [deployments, setDeployments]
|
|
30
|
+
const [deployments, setDeployments] = useState<VercelDeployment[]>([])
|
|
29
31
|
const [loadingInitial, setLoadingInitial] = useState(true)
|
|
30
|
-
const [triggering, setTriggering]
|
|
31
|
-
const [canceling, setCanceling]
|
|
32
|
-
const [deployError, setDeployError]
|
|
33
|
-
const [showHistory, setShowHistory]
|
|
34
|
-
const [elapsed, setElapsed]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const [
|
|
41
|
-
|
|
42
|
-
const [
|
|
43
|
-
const [logError, setLogError] = useState<string | null>(null)
|
|
44
|
-
|
|
45
|
-
const latest = deployments[0]
|
|
32
|
+
const [triggering, setTriggering] = useState(false)
|
|
33
|
+
const [canceling, setCanceling] = useState(false)
|
|
34
|
+
const [deployError, setDeployError] = useState<string | null>(null)
|
|
35
|
+
const [showHistory, setShowHistory] = useState(false)
|
|
36
|
+
const [elapsed, setElapsed] = useState(0)
|
|
37
|
+
const [copied, setCopied] = useState(false)
|
|
38
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
39
|
+
const [showErrorLogs, setShowErrorLogs] = useState(false)
|
|
40
|
+
const [errorLines, setErrorLines] = useState<string[]>([])
|
|
41
|
+
const [loadingLogs, setLoadingLogs] = useState(false)
|
|
42
|
+
const [logError, setLogError] = useState<string | null>(null)
|
|
43
|
+
|
|
44
|
+
const latest = deployments[0]
|
|
46
45
|
const isActive = triggering || isActiveState(latest?.state)
|
|
47
46
|
|
|
48
47
|
// ── Fetch deployments ──────────────────────────────────────────────────────
|
|
@@ -70,7 +69,6 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
70
69
|
if (triggering && latest && latest.state !== undefined) setTriggering(false)
|
|
71
70
|
}, [triggering, latest])
|
|
72
71
|
|
|
73
|
-
// Reset error logs when the deployment changes
|
|
74
72
|
useEffect(() => {
|
|
75
73
|
setShowErrorLogs(false)
|
|
76
74
|
setErrorLines([])
|
|
@@ -139,13 +137,12 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
139
137
|
token,
|
|
140
138
|
teamId: target.teamId,
|
|
141
139
|
})
|
|
142
|
-
// Keep stderr lines and stdout lines that look like errors, in forward order
|
|
143
140
|
const lines = events
|
|
144
141
|
.filter(e => e.type === 'stderr' || e.type === 'stdout')
|
|
145
142
|
.map(e => e.text ?? '')
|
|
146
143
|
.filter(Boolean)
|
|
147
|
-
.reverse()
|
|
148
|
-
.slice(-30)
|
|
144
|
+
.reverse()
|
|
145
|
+
.slice(-30)
|
|
149
146
|
setErrorLines(lines.length > 0 ? lines : ['No log output captured.'])
|
|
150
147
|
} catch (err) {
|
|
151
148
|
setLogError(err instanceof Error ? err.message : 'Failed to load build logs')
|
|
@@ -155,295 +152,363 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
155
152
|
}, [latest?.uid, token, target.teamId])
|
|
156
153
|
|
|
157
154
|
const toggleErrorLogs = useCallback(() => {
|
|
158
|
-
if (!showErrorLogs && errorLines.length === 0 && !logError)
|
|
159
|
-
fetchErrorLogs()
|
|
160
|
-
}
|
|
155
|
+
if (!showErrorLogs && errorLines.length === 0 && !logError) fetchErrorLogs()
|
|
161
156
|
setShowErrorLogs(v => !v)
|
|
162
157
|
}, [showErrorLogs, errorLines.length, logError, fetchErrorLogs])
|
|
163
158
|
|
|
164
159
|
// ── Derived display values ────────────────────────────────────────────────
|
|
165
|
-
const branch
|
|
166
|
-
const commitMsg
|
|
167
|
-
const sha
|
|
168
|
-
const
|
|
169
|
-
const
|
|
160
|
+
const branch = latest?.meta?.githubCommitRef
|
|
161
|
+
const commitMsg = latest?.meta?.githubCommitMessage?.split('\n')[0]
|
|
162
|
+
const sha = shortSha(latest?.meta?.githubCommitSha)
|
|
163
|
+
const fullSha = latest?.meta?.githubCommitSha
|
|
164
|
+
const creator = latest?.creator?.username
|
|
165
|
+
const deployedAt = latest?.created ? timeAgo(latest.created) : null
|
|
170
166
|
const vercelProjectUrl = projectHref(latest?.inspectorUrl)
|
|
171
|
-
const isError
|
|
167
|
+
const isError = latest?.state === 'ERROR'
|
|
172
168
|
|
|
173
169
|
return (
|
|
174
170
|
<>
|
|
175
|
-
<Card
|
|
176
|
-
<Flex
|
|
171
|
+
<Card radius={2} shadow={1} tone="default">
|
|
172
|
+
<Flex align="stretch" className="dvfs-card-flex">
|
|
177
173
|
|
|
178
174
|
{/* ── Left: info column ──────────────────────────────────── */}
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
{/* Name + menu */}
|
|
182
|
-
<Flex align="center" justify="space-between" gap={2}>
|
|
183
|
-
<Text size={2} weight="semibold">{target.name}</Text>
|
|
184
|
-
<MenuButton
|
|
185
|
-
button={<Button mode="ghost" icon={EllipsisVerticalIcon} padding={2} />}
|
|
186
|
-
id={`menu-${target._id}`}
|
|
187
|
-
menu={
|
|
188
|
-
<Menu>
|
|
189
|
-
<MenuItem
|
|
190
|
-
text="History"
|
|
191
|
-
icon={ClockIcon}
|
|
192
|
-
onClick={() => setShowHistory(true)}
|
|
193
|
-
/>
|
|
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>
|
|
227
|
-
|
|
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
|
-
)}
|
|
241
|
-
</Flex>
|
|
242
|
-
|
|
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
|
-
)}
|
|
175
|
+
<Flex direction="column" flex={1} style={{ minWidth: 0 }}>
|
|
261
176
|
|
|
262
|
-
|
|
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}
|
|
177
|
+
<Stack space={3} padding={3} style={{ flex: 1 }}>
|
|
271
178
|
|
|
272
|
-
|
|
179
|
+
{/* ── Title row: name + branch + status + menu ──────── */}
|
|
180
|
+
<Flex align="center" justify="space-between" gap={2}>
|
|
181
|
+
<Flex align="center" gap={2} style={{ minWidth: 0, flexWrap: 'wrap' }}>
|
|
182
|
+
<Text size={2} weight="semibold" style={{ flexShrink: 0 }}>{target.name}</Text>
|
|
273
183
|
{branch && (
|
|
184
|
+
<Badge tone="default" padding={2}>
|
|
185
|
+
<Flex align="center" gap={1}>
|
|
186
|
+
{branch}
|
|
187
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" style={{ marginLeft: "-0.1em", opacity: 0.5 }}>
|
|
188
|
+
<path d="M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm0 2.122a2.25 2.25 0 1 0-1.5 0v.878A2.25 2.25 0 0 0 5.75 8.5h1.5v2.128a2.251 2.251 0 1 0 1.5 0V8.5h1.5a2.25 2.25 0 0 0 2.25-2.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75h-4.5A.75.75 0 0 1 5 6.25v-.878zm3.75 7.378a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm3-8.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" />
|
|
189
|
+
</svg>
|
|
190
|
+
</Flex>
|
|
191
|
+
</Badge>
|
|
192
|
+
)}
|
|
193
|
+
{token && !loadingInitial && (
|
|
274
194
|
<>
|
|
275
|
-
|
|
276
|
-
|
|
195
|
+
{triggering ? (
|
|
196
|
+
<Flex align="center" gap={1}>
|
|
197
|
+
<Spinner muted />
|
|
198
|
+
<Badge tone="caution" padding={2}>Triggering…</Badge>
|
|
199
|
+
</Flex>
|
|
200
|
+
) : (
|
|
201
|
+
<StatusBadge state={latest?.state} showSpinner />
|
|
202
|
+
)}
|
|
203
|
+
{isActive && elapsed > 0 ? (
|
|
204
|
+
<Flex align="center" gap={1}>
|
|
205
|
+
<ClockIcon />
|
|
206
|
+
<Text size={1} muted>{formatDuration(elapsed)}</Text>
|
|
207
|
+
</Flex>
|
|
208
|
+
) : (!isActive && deployedAt) ? (
|
|
209
|
+
<Text size={1} muted>{deployedAt}</Text>
|
|
210
|
+
) : null}
|
|
277
211
|
</>
|
|
278
212
|
)}
|
|
213
|
+
</Flex>
|
|
214
|
+
<MenuButton
|
|
215
|
+
button={<Button mode="ghost" icon={EllipsisVerticalIcon} padding={2} />}
|
|
216
|
+
id={`menu-${target._id}`}
|
|
217
|
+
menu={
|
|
218
|
+
<Menu>
|
|
219
|
+
<MenuItem
|
|
220
|
+
text="Edit target"
|
|
221
|
+
icon={EditIcon}
|
|
222
|
+
onClick={() => onEdit(target)}
|
|
223
|
+
/>
|
|
224
|
+
<MenuItem
|
|
225
|
+
text="History"
|
|
226
|
+
icon={ClockIcon}
|
|
227
|
+
onClick={() => setShowHistory(true)}
|
|
228
|
+
/>
|
|
229
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
230
|
+
<MenuItem
|
|
231
|
+
text="Build logs"
|
|
232
|
+
icon={LaunchIcon}
|
|
233
|
+
as="a"
|
|
234
|
+
href={safeHref(latest?.inspectorUrl)}
|
|
235
|
+
target="_blank"
|
|
236
|
+
rel="noreferrer"
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
{vercelProjectUrl && (
|
|
240
|
+
<MenuItem
|
|
241
|
+
text="Open in Vercel"
|
|
242
|
+
icon={LaunchIcon}
|
|
243
|
+
as="a"
|
|
244
|
+
href={vercelProjectUrl}
|
|
245
|
+
target="_blank"
|
|
246
|
+
rel="noreferrer"
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
{!target.disableDeleteAction && (
|
|
250
|
+
<MenuItem
|
|
251
|
+
text="Delete"
|
|
252
|
+
icon={TrashIcon}
|
|
253
|
+
tone="critical"
|
|
254
|
+
onClick={() => onDelete(target)}
|
|
255
|
+
/>
|
|
256
|
+
)}
|
|
257
|
+
</Menu>
|
|
258
|
+
}
|
|
259
|
+
popover={{ placement: 'bottom-end' }}
|
|
260
|
+
/>
|
|
261
|
+
</Flex>
|
|
279
262
|
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
)}
|
|
263
|
+
{/* ── Divider below title ────────────────────────────── */}
|
|
264
|
+
<hr style={{ border: 'none', borderTop: '1px solid currentColor', opacity: 0.1, margin: 0 }} />
|
|
299
265
|
|
|
300
|
-
|
|
301
|
-
|
|
266
|
+
{/* ── Status + metadata ──────────────────────────────── */}
|
|
267
|
+
{!token ? (
|
|
268
|
+
<Text size={1} muted>Connect a Vercel API token to see deployment status.</Text>
|
|
269
|
+
) : loadingInitial ? (
|
|
270
|
+
<Flex align="center" gap={2}>
|
|
271
|
+
<Spinner muted />
|
|
272
|
+
<Text size={1} muted>Loading…</Text>
|
|
273
|
+
</Flex>
|
|
274
|
+
) : (
|
|
275
|
+
<Stack space={2}>
|
|
276
|
+
|
|
277
|
+
{/* Metadata row */}
|
|
278
|
+
<Flex align="center" gap={2} wrap="wrap">
|
|
279
|
+
|
|
280
|
+
{/* Visit link + copy URL */}
|
|
281
|
+
{latest?.url && latest.state === 'READY' && (
|
|
282
|
+
<>
|
|
283
|
+
<a
|
|
284
|
+
href={`https://${latest.url}`}
|
|
285
|
+
target="_blank"
|
|
286
|
+
rel="noreferrer"
|
|
287
|
+
style={{ color: 'inherit' }}
|
|
288
|
+
>
|
|
289
|
+
<Flex align="center" gap={1}>
|
|
290
|
+
<Text size={1}>{latest.url}</Text>
|
|
291
|
+
</Flex>
|
|
292
|
+
</a>
|
|
293
|
+
</>
|
|
294
|
+
)}
|
|
302
295
|
|
|
303
|
-
|
|
304
|
-
|
|
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>
|
|
296
|
+
{/* Commit SHA — tooltip shows full message */}
|
|
297
|
+
{sha && (
|
|
317
298
|
<Tooltip
|
|
318
299
|
content={
|
|
319
300
|
<Box padding={2}>
|
|
320
|
-
<Text size={1}>{
|
|
301
|
+
<Text size={1}>{commitMsg ?? sha}</Text>
|
|
321
302
|
</Box>
|
|
322
303
|
}
|
|
323
304
|
portal
|
|
324
305
|
>
|
|
306
|
+
<Text
|
|
307
|
+
size={1}
|
|
308
|
+
muted
|
|
309
|
+
style={{ cursor: 'default', fontFamily: 'monospace' }}
|
|
310
|
+
>
|
|
311
|
+
{sha}
|
|
312
|
+
</Text>
|
|
313
|
+
</Tooltip>
|
|
314
|
+
)}
|
|
315
|
+
|
|
316
|
+
{/* Creator */}
|
|
317
|
+
{creator && <Text size={1} muted>by {creator}</Text>}
|
|
318
|
+
|
|
319
|
+
{/* Visit link + copy URL */}
|
|
320
|
+
{latest?.url && latest.state === 'READY' && (
|
|
321
|
+
<>
|
|
322
|
+
<Tooltip
|
|
323
|
+
content={
|
|
324
|
+
<Box padding={2}>
|
|
325
|
+
<Text size={1}>{copied ? 'Copied!' : 'Copy URL'}</Text>
|
|
326
|
+
</Box>
|
|
327
|
+
}
|
|
328
|
+
portal
|
|
329
|
+
>
|
|
330
|
+
<Button
|
|
331
|
+
mode="ghost"
|
|
332
|
+
icon={copied ? CheckmarkIcon : CopyIcon}
|
|
333
|
+
padding={1}
|
|
334
|
+
tone={copied ? 'positive' : 'default'}
|
|
335
|
+
onClick={copyUrl}
|
|
336
|
+
style={{ cursor: 'pointer' }}
|
|
337
|
+
/>
|
|
338
|
+
</Tooltip>
|
|
339
|
+
</>
|
|
340
|
+
)}
|
|
341
|
+
</Flex>
|
|
342
|
+
|
|
343
|
+
<Flex align="center" gap={2} wrap="wrap">
|
|
344
|
+
{/* Commit message */}
|
|
345
|
+
{commitMsg && (
|
|
346
|
+
<Text
|
|
347
|
+
size={0}
|
|
348
|
+
muted
|
|
349
|
+
style={{ fontStyle: 'italic'}}
|
|
350
|
+
>
|
|
351
|
+
{commitMsg}
|
|
352
|
+
</Text>
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
{/* Error expansion */}
|
|
356
|
+
{isError && (
|
|
357
|
+
<Stack space={2}>
|
|
325
358
|
<Button
|
|
359
|
+
text={showErrorLogs ? 'Hide error details' : 'Show error details'}
|
|
326
360
|
mode="ghost"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
361
|
+
tone="critical"
|
|
362
|
+
icon={WarningOutlineIcon}
|
|
363
|
+
fontSize={1}
|
|
364
|
+
padding={2}
|
|
365
|
+
onClick={toggleErrorLogs}
|
|
366
|
+
style={{ alignSelf: 'flex-start', cursor: 'pointer' }}
|
|
331
367
|
/>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
<Text
|
|
340
|
-
size={0}
|
|
341
|
-
muted
|
|
342
|
-
style={{
|
|
343
|
-
overflow: 'hidden',
|
|
344
|
-
textOverflow: 'ellipsis',
|
|
345
|
-
whiteSpace: 'nowrap',
|
|
346
|
-
}}
|
|
347
|
-
>
|
|
348
|
-
{commitMsg}
|
|
349
|
-
</Text>
|
|
350
|
-
)}
|
|
351
|
-
|
|
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
|
-
/>
|
|
365
|
-
|
|
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>
|
|
368
|
+
{showErrorLogs && (
|
|
369
|
+
<Card tone="critical" radius={2} padding={3}>
|
|
370
|
+
{loadingLogs && (
|
|
371
|
+
<Flex align="center" gap={2}>
|
|
372
|
+
<Spinner muted />
|
|
373
|
+
<Text size={1} muted>Loading logs…</Text>
|
|
374
|
+
</Flex>
|
|
389
375
|
)}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
<LaunchIcon />
|
|
422
|
-
<Text size={1}>View full logs in Vercel</Text>
|
|
423
|
-
</Flex>
|
|
424
|
-
</a>
|
|
376
|
+
{logError && (
|
|
377
|
+
<Stack space={2}>
|
|
378
|
+
<Text size={1}>{logError}</Text>
|
|
379
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
380
|
+
<a href={safeHref(latest?.inspectorUrl)} target="_blank" rel="noreferrer" style={{ color: 'inherit' }}>
|
|
381
|
+
<Flex align="center" gap={1}>
|
|
382
|
+
<LaunchIcon />
|
|
383
|
+
<Text size={1}>View full logs in Vercel</Text>
|
|
384
|
+
</Flex>
|
|
385
|
+
</a>
|
|
386
|
+
)}
|
|
387
|
+
</Stack>
|
|
388
|
+
)}
|
|
389
|
+
{!loadingLogs && !logError && errorLines.length > 0 && (
|
|
390
|
+
<Stack space={2}>
|
|
391
|
+
<Box style={{ maxHeight: 240, overflowY: 'auto', fontFamily: 'monospace', fontSize: 11, lineHeight: 1.6 }}>
|
|
392
|
+
{errorLines.map((line, i) => (
|
|
393
|
+
<Code key={i} size={1} style={{ display: 'block', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
|
394
|
+
{line}
|
|
395
|
+
</Code>
|
|
396
|
+
))}
|
|
397
|
+
</Box>
|
|
398
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
399
|
+
<a href={safeHref(latest?.inspectorUrl)} target="_blank" rel="noreferrer" style={{ color: 'inherit' }}>
|
|
400
|
+
<Flex align="center" gap={1}>
|
|
401
|
+
<LaunchIcon />
|
|
402
|
+
<Text size={1}>View full logs in Vercel</Text>
|
|
403
|
+
</Flex>
|
|
404
|
+
</a>
|
|
405
|
+
)}
|
|
406
|
+
</Stack>
|
|
425
407
|
)}
|
|
426
|
-
</
|
|
408
|
+
</Card>
|
|
427
409
|
)}
|
|
410
|
+
</Stack>
|
|
411
|
+
)}
|
|
412
|
+
|
|
413
|
+
{/* Trigger error */}
|
|
414
|
+
{deployError && (
|
|
415
|
+
<Card tone="critical" padding={2} radius={2}>
|
|
416
|
+
<Text size={1}>{deployError}</Text>
|
|
428
417
|
</Card>
|
|
429
418
|
)}
|
|
419
|
+
</Flex>
|
|
420
|
+
</Stack>
|
|
421
|
+
)}
|
|
422
|
+
|
|
423
|
+
</Stack>
|
|
424
|
+
|
|
425
|
+
{/* ── Details accordion — flush to left/bottom/right ─────── */}
|
|
426
|
+
<Box>
|
|
427
|
+
<Button
|
|
428
|
+
mode="ghost"
|
|
429
|
+
iconRight={showDetails ? ChevronUpIcon : ChevronDownIcon}
|
|
430
|
+
text="Details"
|
|
431
|
+
fontSize={0}
|
|
432
|
+
padding={3}
|
|
433
|
+
onClick={() => setShowDetails(v => !v)}
|
|
434
|
+
style={{ width: '100%', justifyContent: 'flex-start', borderRadius: 0, cursor: 'pointer' }}
|
|
435
|
+
/>
|
|
436
|
+
{showDetails && (
|
|
437
|
+
<Card tone="primary" padding={3} style={{ borderRadius: 0 }}>
|
|
438
|
+
<Stack space={2}>
|
|
439
|
+
<Flex gap={2} align="center">
|
|
440
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Project</Text>
|
|
441
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>{projectId || '—'}</Text>
|
|
442
|
+
</Flex>
|
|
443
|
+
<Flex gap={2} align="center">
|
|
444
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Hook</Text>
|
|
445
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>{hookId || '—'}</Text>
|
|
446
|
+
</Flex>
|
|
447
|
+
{target.teamId && (
|
|
448
|
+
<Flex gap={2} align="center">
|
|
449
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Team</Text>
|
|
450
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>{target.teamId}</Text>
|
|
451
|
+
</Flex>
|
|
452
|
+
)}
|
|
453
|
+
{fullSha && (
|
|
454
|
+
<Flex gap={2} align="flex-start">
|
|
455
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Commit</Text>
|
|
456
|
+
<Text size={0} muted style={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>{fullSha}</Text>
|
|
457
|
+
</Flex>
|
|
458
|
+
)}
|
|
459
|
+
{latest?.meta?.githubCommitAuthorName && (
|
|
460
|
+
<Flex gap={2} align="center">
|
|
461
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Author</Text>
|
|
462
|
+
<Text size={0} muted>{latest.meta.githubCommitAuthorName}</Text>
|
|
463
|
+
</Flex>
|
|
464
|
+
)}
|
|
465
|
+
{branch && (
|
|
466
|
+
<Flex gap={2} align="center">
|
|
467
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Branch</Text>
|
|
468
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>{branch}</Text>
|
|
469
|
+
</Flex>
|
|
470
|
+
)}
|
|
471
|
+
{latest?.uid && (
|
|
472
|
+
<Flex gap={2} align="center">
|
|
473
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Deploy ID</Text>
|
|
474
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>{latest.uid}</Text>
|
|
475
|
+
</Flex>
|
|
476
|
+
)}
|
|
477
|
+
{latest?.url && (
|
|
478
|
+
<Flex gap={2} align="flex-start">
|
|
479
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>URL</Text>
|
|
480
|
+
<Text size={0} muted style={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>{latest.url}</Text>
|
|
481
|
+
</Flex>
|
|
482
|
+
)}
|
|
483
|
+
{safeHref(latest?.inspectorUrl) && (
|
|
484
|
+
<Flex gap={2} align="center">
|
|
485
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Inspector</Text>
|
|
486
|
+
<a href={safeHref(latest?.inspectorUrl)!} target="_blank" rel="noreferrer" style={{ color: 'inherit' }}>
|
|
487
|
+
<Flex align="center" gap={1}>
|
|
488
|
+
<Text size={0} muted style={{ fontFamily: 'monospace' }}>Open in Vercel</Text>
|
|
489
|
+
<LaunchIcon style={{ width: 10, height: 10 }} />
|
|
490
|
+
</Flex>
|
|
491
|
+
</a>
|
|
492
|
+
</Flex>
|
|
493
|
+
)}
|
|
494
|
+
{latest?.created && (
|
|
495
|
+
<Flex gap={2} align="center">
|
|
496
|
+
<Text size={0} muted weight="semibold" style={{ minWidth: LABEL_WIDTH }}>Created</Text>
|
|
497
|
+
<Text size={0} muted>{new Date(latest.created).toLocaleString()}</Text>
|
|
498
|
+
</Flex>
|
|
499
|
+
)}
|
|
430
500
|
</Stack>
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
<Text size={1}>{deployError}</Text>
|
|
437
|
-
</Card>
|
|
438
|
-
)}
|
|
439
|
-
</Stack>
|
|
440
|
-
)}
|
|
441
|
-
</Stack>
|
|
501
|
+
</Card>
|
|
502
|
+
)}
|
|
503
|
+
</Box>
|
|
504
|
+
|
|
505
|
+
</Flex>
|
|
442
506
|
|
|
443
507
|
{/* ── Right: action buttons — stretch full card height ── */}
|
|
444
508
|
<Flex
|
|
445
509
|
direction="column"
|
|
446
510
|
gap={2}
|
|
511
|
+
className="dvfs-deploy-col"
|
|
447
512
|
style={{ flexShrink: 0, alignSelf: 'stretch' }}
|
|
448
513
|
>
|
|
449
514
|
{isActiveState(latest?.state) && (
|
|
@@ -454,16 +519,22 @@ export function DeployItem({ target, token, onDelete }: DeployItemProps) {
|
|
|
454
519
|
loading={canceling}
|
|
455
520
|
disabled={canceling}
|
|
456
521
|
onClick={cancel}
|
|
522
|
+
style={{ cursor: 'pointer' }}
|
|
457
523
|
/>
|
|
458
524
|
)}
|
|
459
525
|
<Button
|
|
460
526
|
text="Deploy"
|
|
461
527
|
tone="primary"
|
|
462
|
-
icon={RocketIcon}
|
|
463
528
|
loading={triggering}
|
|
464
529
|
disabled={isActive}
|
|
465
530
|
onClick={deploy}
|
|
466
|
-
style={{
|
|
531
|
+
style={{
|
|
532
|
+
flex: 1,
|
|
533
|
+
borderRadius: 0,
|
|
534
|
+
borderTopRightRadius: 3,
|
|
535
|
+
borderBottomRightRadius: 3,
|
|
536
|
+
cursor: 'pointer',
|
|
537
|
+
}}
|
|
467
538
|
/>
|
|
468
539
|
</Flex>
|
|
469
540
|
|