onveloz 0.0.0-beta.21 → 0.0.0-beta.23

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.
@@ -0,0 +1,1028 @@
1
+ import { a as statusLabels, i as TERMINAL_STATUSES, l as info, n as isHiddenMessage, o as getClient, r as parseBuildLine, t as cleanDisplayLine, u as success } from "./index.mjs";
2
+ import chalk from "chalk";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { Box, Static, Text, render, useApp } from "ink";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region ../../packages/api/src/lib/build-steps.ts
8
+ const TIMESTAMP_RE = /^\[(\d{4}-\d{2}-\d{2}T[\d:.]+Z)\]\s*/;
9
+ const STEP_PREFIX_RE = /^#(\d+)\s+(.*)/;
10
+ const TIMING_PREFIX_RE = /^(\d+\.\d+)\s*(.*)/;
11
+ const DONE_RE = /^DONE\s+([\d.]+s?)$/;
12
+ const STAGE_RE = /^\[([\w-]+)\s+(\d+)\/\d+\]/;
13
+ function parseStageInfo(title) {
14
+ const match = title.match(STAGE_RE);
15
+ if (!match) return null;
16
+ return {
17
+ stage: match[1].toLowerCase(),
18
+ substep: parseInt(match[2], 10)
19
+ };
20
+ }
21
+ /** Derive stage order from first occurrence in the step list (preserves Dockerfile order). */
22
+ function buildStageOrder(steps) {
23
+ const order = /* @__PURE__ */ new Map();
24
+ for (const step of steps) {
25
+ const info$1 = parseStageInfo(step.title);
26
+ if (info$1 && !order.has(info$1.stage)) order.set(info$1.stage, order.size);
27
+ }
28
+ return order;
29
+ }
30
+ const RUNTIME_LINE_PATTERNS = [
31
+ /^[\u26A0\u{1F680}]/u,
32
+ /^\{.*"(?:level|msg|pid)"/,
33
+ /^Error:\s/,
34
+ /^\s+at\s+/,
35
+ /^errorno:/i,
36
+ /^code:\s+'/,
37
+ /^syscall:\s+'/,
38
+ /Server (?:running|started)/i
39
+ ];
40
+ function isRuntimeLine(content) {
41
+ return RUNTIME_LINE_PATTERNS.some((p) => p.test(content.trim()));
42
+ }
43
+ function parseBuildSteps(rawText) {
44
+ const rawLines = rawText.split("\n");
45
+ let currentPhase = 0;
46
+ let maxStepInPhase = 0;
47
+ const phaseStepMap = /* @__PURE__ */ new Map();
48
+ const allSteps = [];
49
+ let currentGeneralStep = null;
50
+ for (const raw of rawLines) {
51
+ const trimmed = raw.trim();
52
+ if (!trimmed) continue;
53
+ const tsMatch = trimmed.match(TIMESTAMP_RE);
54
+ const timestamp = tsMatch?.[1] ?? null;
55
+ const line = tsMatch ? trimmed.slice(tsMatch[0].length) : trimmed;
56
+ if (!line.trim()) continue;
57
+ const stepMatch = line.match(STEP_PREFIX_RE);
58
+ if (!stepMatch) {
59
+ if (!currentGeneralStep) {
60
+ currentGeneralStep = {
61
+ stepNumber: null,
62
+ title: "Configuração",
63
+ duration: null,
64
+ status: "done",
65
+ lines: [],
66
+ phase: currentPhase,
67
+ startedAt: timestamp
68
+ };
69
+ allSteps.push(currentGeneralStep);
70
+ }
71
+ currentGeneralStep.lines.push({
72
+ content: line,
73
+ elapsed: null,
74
+ timestamp
75
+ });
76
+ continue;
77
+ }
78
+ currentGeneralStep = null;
79
+ const stepNum = parseInt(stepMatch[1], 10);
80
+ let content = stepMatch[2];
81
+ if (content.trim() === "...") continue;
82
+ const existingKey = `${currentPhase}-${stepNum}`;
83
+ const existing = phaseStepMap.get(existingKey);
84
+ if (existing && (existing.status === "done" || existing.status === "cached") && stepNum < maxStepInPhase) {
85
+ currentPhase++;
86
+ maxStepInPhase = 0;
87
+ }
88
+ if (stepNum > maxStepInPhase) maxStepInPhase = stepNum;
89
+ const key = `${currentPhase}-${stepNum}`;
90
+ let step = phaseStepMap.get(key);
91
+ if (!step) {
92
+ step = {
93
+ stepNumber: stepNum,
94
+ title: "",
95
+ duration: null,
96
+ status: "running",
97
+ lines: [],
98
+ phase: currentPhase,
99
+ startedAt: timestamp
100
+ };
101
+ phaseStepMap.set(key, step);
102
+ allSteps.push(step);
103
+ }
104
+ const doneMatch = content.match(DONE_RE);
105
+ if (doneMatch) {
106
+ step.duration = doneMatch[1];
107
+ step.status = "done";
108
+ continue;
109
+ }
110
+ if (content.trim() === "CACHED") {
111
+ step.status = "cached";
112
+ step.duration = "cached";
113
+ continue;
114
+ }
115
+ let elapsed = null;
116
+ const timingMatch = content.match(TIMING_PREFIX_RE);
117
+ if (timingMatch) {
118
+ elapsed = parseFloat(timingMatch[1]);
119
+ content = timingMatch[2];
120
+ }
121
+ if (!content.trim()) continue;
122
+ if (!step.title) step.title = content.trim();
123
+ if (step.lines.length > 0 || content.trim() !== step.title) step.lines.push({
124
+ content: content.trim(),
125
+ elapsed,
126
+ timestamp
127
+ });
128
+ }
129
+ const numberedWithIndex = [];
130
+ const structure = [];
131
+ for (let i = 0; i < allSteps.length; i++) {
132
+ const step = allSteps[i];
133
+ if (step.stepNumber === null) structure.push({
134
+ type: "general",
135
+ origIdx: i
136
+ });
137
+ else if (step.title.trim() !== "") {
138
+ structure.push({
139
+ type: "numbered",
140
+ origIdx: i
141
+ });
142
+ numberedWithIndex.push({
143
+ step,
144
+ origIdx: i
145
+ });
146
+ }
147
+ }
148
+ const stageOrder = buildStageOrder(numberedWithIndex.map((n) => n.step));
149
+ numberedWithIndex.sort((a, b) => {
150
+ if (a.step.phase !== b.step.phase) return a.step.phase - b.step.phase;
151
+ if (a.step.startedAt && b.step.startedAt) {
152
+ const tsDiff = new Date(a.step.startedAt).getTime() - new Date(b.step.startedAt).getTime();
153
+ if (tsDiff !== 0) return tsDiff;
154
+ }
155
+ const aInfo = parseStageInfo(a.step.title);
156
+ const bInfo = parseStageInfo(b.step.title);
157
+ const aOrder = aInfo ? stageOrder.get(aInfo.stage) ?? 99 : 99;
158
+ const bOrder = bInfo ? stageOrder.get(bInfo.stage) ?? 99 : 99;
159
+ if (aOrder !== bOrder) return aOrder - bOrder;
160
+ return (aInfo?.substep ?? a.step.stepNumber ?? 0) - (bInfo?.substep ?? b.step.stepNumber ?? 0);
161
+ });
162
+ const result = [];
163
+ let nIdx = 0;
164
+ let generalCount = 0;
165
+ const totalGenerals = structure.filter((s) => s.type === "general").length;
166
+ for (const entry of structure) {
167
+ if (entry.type === "numbered") {
168
+ result.push(numberedWithIndex[nIdx].step);
169
+ nIdx++;
170
+ continue;
171
+ }
172
+ generalCount++;
173
+ const generalStep = allSteps[entry.origIdx];
174
+ if (generalCount === totalGenerals && totalGenerals > 1) {
175
+ const deployLines = [];
176
+ const healthLines = [];
177
+ let hitRuntime = false;
178
+ for (const line of generalStep.lines) {
179
+ if (!hitRuntime && isRuntimeLine(line.content)) hitRuntime = true;
180
+ if (hitRuntime) healthLines.push(line);
181
+ else deployLines.push(line);
182
+ }
183
+ if (deployLines.length > 0) result.push({
184
+ stepNumber: null,
185
+ title: "Finalização",
186
+ duration: null,
187
+ status: "done",
188
+ lines: deployLines,
189
+ phase: generalStep.phase,
190
+ startedAt: deployLines[0]?.timestamp ?? generalStep.startedAt
191
+ });
192
+ if (healthLines.length > 0) result.push({
193
+ stepNumber: null,
194
+ title: "Verificação de saúde",
195
+ duration: null,
196
+ status: "done",
197
+ lines: healthLines,
198
+ phase: generalStep.phase,
199
+ startedAt: healthLines[0]?.timestamp ?? null
200
+ });
201
+ } else result.push(generalStep);
202
+ }
203
+ return result;
204
+ }
205
+
206
+ //#endregion
207
+ //#region src/components/deploy-tui.tsx
208
+ const BRAND_COLOR = "#FF4D00";
209
+ const BAR_WIDTH = 20;
210
+ const SPINNER_FRAMES = [
211
+ "⠋",
212
+ "⠙",
213
+ "⠹",
214
+ "⠸",
215
+ "⠼",
216
+ "⠴",
217
+ "⠦",
218
+ "⠧",
219
+ "⠇",
220
+ "⠏"
221
+ ];
222
+ const MAX_OUTPUT_LINES = 5;
223
+ /** Regex to extract stage info from BuildStep titles like `[stage-0 3/11] COPY ...` */
224
+ const STAGE_TITLE_RE = /^\[([\w-]+)\s+(\d+)\/(\d+)\]\s+(.+)$/;
225
+ function buildStepsToStages(steps) {
226
+ const stages = /* @__PURE__ */ new Map();
227
+ const platformMessages = [];
228
+ const stepDetails = /* @__PURE__ */ new Map();
229
+ const displayOrder = [];
230
+ const seenStages = /* @__PURE__ */ new Set();
231
+ for (const step of steps) {
232
+ if (step.stepNumber === null) {
233
+ for (const line of step.lines) {
234
+ const cleaned = cleanDisplayLine(line.content);
235
+ if (cleaned) platformMessages.push(cleaned);
236
+ }
237
+ continue;
238
+ }
239
+ const match = step.title.match(STAGE_TITLE_RE);
240
+ if (!match) {
241
+ if (step.title && !/^\[(?:depot|internal)\]/i.test(step.title)) displayOrder.push({
242
+ kind: "extra",
243
+ step
244
+ });
245
+ continue;
246
+ }
247
+ const stageName = match[1];
248
+ const substep = parseInt(match[2], 10);
249
+ const total = parseInt(match[3], 10);
250
+ const command = match[4];
251
+ let stage = stages.get(stageName);
252
+ if (!stage) {
253
+ stage = {
254
+ name: stageName,
255
+ total,
256
+ steps: /* @__PURE__ */ new Map(),
257
+ stepNumMap: /* @__PURE__ */ new Map(),
258
+ cachedStepNums: /* @__PURE__ */ new Set(),
259
+ doneStepNums: /* @__PURE__ */ new Set()
260
+ };
261
+ stages.set(stageName, stage);
262
+ }
263
+ if (!seenStages.has(stageName)) {
264
+ seenStages.add(stageName);
265
+ displayOrder.push({
266
+ kind: "stage",
267
+ name: stageName
268
+ });
269
+ }
270
+ stage.steps.set(substep, command);
271
+ stage.stepNumMap.set(step.stepNumber, substep);
272
+ stage.total = Math.max(stage.total, total);
273
+ if (step.status === "cached") stage.cachedStepNums.add(step.stepNumber);
274
+ else if (step.status === "done") stage.doneStepNums.add(step.stepNumber);
275
+ stepDetails.set(`${stageName}-${substep}`, {
276
+ duration: step.duration !== "cached" ? step.duration : null,
277
+ status: step.status,
278
+ lastLines: step.lines.slice(-MAX_OUTPUT_LINES).map((l) => l.content),
279
+ lineCount: step.lines.length
280
+ });
281
+ }
282
+ return {
283
+ stages,
284
+ platformMessages,
285
+ displayOrder,
286
+ stepDetails
287
+ };
288
+ }
289
+ function AnimatedSpinner({ color = "cyan" }) {
290
+ const [frame, setFrame] = useState(0);
291
+ useEffect(() => {
292
+ const timer = setInterval(() => {
293
+ setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
294
+ }, 80);
295
+ return () => clearInterval(timer);
296
+ }, []);
297
+ return /* @__PURE__ */ jsx(Text, {
298
+ color,
299
+ children: SPINNER_FRAMES[frame]
300
+ });
301
+ }
302
+ function ProgressBar({ filled, total, allCached, allDone }) {
303
+ const ratio = total > 0 ? filled / total : 0;
304
+ const filledChars = Math.round(ratio * BAR_WIDTH);
305
+ const emptyChars = BAR_WIDTH - filledChars;
306
+ const counter = `${filled}/${total}`;
307
+ if (allCached) return /* @__PURE__ */ jsxs(Text, {
308
+ color: BRAND_COLOR,
309
+ children: [
310
+ "━".repeat(BAR_WIDTH),
311
+ " ",
312
+ counter,
313
+ " ✓ veloz cache"
314
+ ]
315
+ });
316
+ if (allDone) return /* @__PURE__ */ jsxs(Text, {
317
+ color: "green",
318
+ children: [
319
+ "━".repeat(BAR_WIDTH),
320
+ " ",
321
+ counter,
322
+ " ✓"
323
+ ]
324
+ });
325
+ return /* @__PURE__ */ jsxs(Text, { children: [
326
+ /* @__PURE__ */ jsx(Text, {
327
+ color: "cyan",
328
+ children: "━".repeat(filledChars)
329
+ }),
330
+ /* @__PURE__ */ jsx(Text, {
331
+ dimColor: true,
332
+ children: "─".repeat(emptyChars)
333
+ }),
334
+ /* @__PURE__ */ jsxs(Text, {
335
+ dimColor: true,
336
+ children: [" ", counter]
337
+ })
338
+ ] });
339
+ }
340
+ function getStageStats(stage) {
341
+ let finishedCount = 0;
342
+ let cachedCount = 0;
343
+ for (const bkNum of stage.stepNumMap.keys()) if (stage.cachedStepNums.has(bkNum)) {
344
+ finishedCount++;
345
+ cachedCount++;
346
+ } else if (stage.doneStepNums.has(bkNum)) finishedCount++;
347
+ const allFinished = stage.total > 0 && finishedCount >= stage.total;
348
+ return {
349
+ finishedCount,
350
+ cachedCount,
351
+ allFinished,
352
+ allCached: allFinished && cachedCount >= stage.total,
353
+ allDone: allFinished && cachedCount < stage.total
354
+ };
355
+ }
356
+ function getStepStatus(stepNum, stage) {
357
+ for (const [bkNum, dockerStep] of stage.stepNumMap.entries()) if (dockerStep === stepNum) {
358
+ if (stage.cachedStepNums.has(bkNum)) return "cached";
359
+ if (stage.doneStepNums.has(bkNum)) return "done";
360
+ return "running";
361
+ }
362
+ return "pending";
363
+ }
364
+ function StepStatusIcon({ status, spinnerFrame }) {
365
+ switch (status) {
366
+ case "cached": return /* @__PURE__ */ jsx(Text, {
367
+ color: BRAND_COLOR,
368
+ children: "✓"
369
+ });
370
+ case "done": return /* @__PURE__ */ jsx(Text, {
371
+ color: "green",
372
+ children: "✓"
373
+ });
374
+ case "running": return /* @__PURE__ */ jsx(Text, {
375
+ color: "cyan",
376
+ children: SPINNER_FRAMES[spinnerFrame]
377
+ });
378
+ case "pending": return /* @__PURE__ */ jsx(Text, {
379
+ dimColor: true,
380
+ children: "·"
381
+ });
382
+ }
383
+ }
384
+ function friendlyExtraTitle(title) {
385
+ if (/^exporting to image/i.test(title)) return "Código compilado, distribuindo no sistema da Veloz";
386
+ if (/^exporting to client/i.test(title)) return "Exportando resultado";
387
+ return title;
388
+ }
389
+ function BuildDashboard({ serviceName, stages, displayOrder, committedCount, platformMessages, stepDetails, spinnerText }) {
390
+ const [spinnerFrame, setSpinnerFrame] = useState(0);
391
+ useEffect(() => {
392
+ const timer = setInterval(() => {
393
+ setSpinnerFrame((f) => (f + 1) % SPINNER_FRAMES.length);
394
+ }, 80);
395
+ return () => clearInterval(timer);
396
+ }, []);
397
+ const visibleItems = displayOrder.slice(committedCount);
398
+ const showHeader = committedCount === 0;
399
+ const stageNames = displayOrder.filter((d) => d.kind === "stage").map((d) => d.name);
400
+ const maxNameLen = Math.max(...stageNames.map((n) => n.length), 4);
401
+ const allStagesComplete = stageNames.length > 0 && stageNames.every((name) => {
402
+ const stage = stages.get(name);
403
+ return stage ? getStageStats(stage).allFinished : false;
404
+ });
405
+ const hasRunningExtra = displayOrder.some((d) => d.kind === "extra" && d.step.status === "running");
406
+ const allComplete = displayOrder.length > 0 && displayOrder.every((item) => {
407
+ if (item.kind === "stage") {
408
+ const stage = stages.get(item.name);
409
+ return stage ? getStageStats(stage).allFinished : false;
410
+ }
411
+ return item.step.status === "done" || item.step.status === "cached";
412
+ });
413
+ let bottomSpinner = spinnerText;
414
+ if (allComplete) bottomSpinner = "Finalizando build...";
415
+ else if (allStagesComplete && hasRunningExtra) bottomSpinner = "Distribuindo...";
416
+ return /* @__PURE__ */ jsxs(Box, {
417
+ flexDirection: "column",
418
+ paddingLeft: 2,
419
+ children: [
420
+ showHeader && /* @__PURE__ */ jsxs(Fragment, { children: [
421
+ /* @__PURE__ */ jsxs(Text, {
422
+ bold: true,
423
+ color: "cyan",
424
+ children: ["BUILD ", serviceName ? /* @__PURE__ */ jsxs(Text, {
425
+ dimColor: true,
426
+ children: [
427
+ "(",
428
+ serviceName,
429
+ ")"
430
+ ]
431
+ }) : null]
432
+ }),
433
+ platformMessages.map((msg, i) => /* @__PURE__ */ jsxs(Text, { children: [" ", msg] }, `pm-${i}`)),
434
+ displayOrder.length > 0 && /* @__PURE__ */ jsx(Text, { children: "" })
435
+ ] }),
436
+ visibleItems.map((item, idx) => {
437
+ if (item.kind === "stage") {
438
+ const stage = stages.get(item.name);
439
+ const stats = getStageStats(stage);
440
+ const sortedSteps = [...stage.steps.entries()].sort((a, b) => a[0] - b[0]);
441
+ return /* @__PURE__ */ jsxs(Box, {
442
+ flexDirection: "column",
443
+ children: [
444
+ /* @__PURE__ */ jsxs(Box, { children: [
445
+ /* @__PURE__ */ jsx(Text, {
446
+ bold: true,
447
+ children: item.name.padEnd(maxNameLen)
448
+ }),
449
+ /* @__PURE__ */ jsx(Text, { children: " " }),
450
+ /* @__PURE__ */ jsx(ProgressBar, {
451
+ filled: stats.finishedCount,
452
+ total: stage.total,
453
+ allCached: stats.allCached,
454
+ allDone: stats.allDone
455
+ })
456
+ ] }),
457
+ sortedSteps.map(([stepNum, command]) => {
458
+ const status$1 = getStepStatus(stepNum, stage);
459
+ const detail = stepDetails.get(`${item.name}-${stepNum}`);
460
+ const isDone$1 = status$1 === "done" || status$1 === "cached";
461
+ const isRunning$1 = status$1 === "running";
462
+ return /* @__PURE__ */ jsxs(Box, {
463
+ flexDirection: "column",
464
+ children: [/* @__PURE__ */ jsxs(Box, { children: [
465
+ /* @__PURE__ */ jsx(Text, { children: " " }),
466
+ /* @__PURE__ */ jsx(StepStatusIcon, {
467
+ status: status$1,
468
+ spinnerFrame
469
+ }),
470
+ /* @__PURE__ */ jsxs(Text, {
471
+ dimColor: isDone$1,
472
+ children: [
473
+ " ",
474
+ command,
475
+ status$1 === "cached" ? /* @__PURE__ */ jsx(Text, {
476
+ color: BRAND_COLOR,
477
+ children: " (veloz cache)"
478
+ }) : isDone$1 && detail?.duration ? /* @__PURE__ */ jsxs(Text, {
479
+ dimColor: true,
480
+ children: [" ", detail.duration]
481
+ }) : null
482
+ ]
483
+ })
484
+ ] }), isRunning$1 && detail && detail.lastLines.length > 0 && /* @__PURE__ */ jsx(Box, {
485
+ flexDirection: "column",
486
+ children: detail.lastLines.map((line, li) => /* @__PURE__ */ jsxs(Text, {
487
+ dimColor: true,
488
+ children: [" ", line]
489
+ }, li))
490
+ })]
491
+ }, stepNum);
492
+ }),
493
+ idx < visibleItems.length - 1 && /* @__PURE__ */ jsx(Text, { children: "" })
494
+ ]
495
+ }, item.name);
496
+ }
497
+ const step = item.step;
498
+ const isDone = step.status === "done" || step.status === "cached";
499
+ const isRunning = step.status === "running";
500
+ const status = step.status === "error" ? "running" : step.status;
501
+ const title = friendlyExtraTitle(step.title);
502
+ const lastLines = step.lines.slice(-MAX_OUTPUT_LINES).map((l) => l.content);
503
+ return /* @__PURE__ */ jsxs(Box, {
504
+ flexDirection: "column",
505
+ children: [/* @__PURE__ */ jsxs(Box, { children: [
506
+ /* @__PURE__ */ jsx(Text, { children: " " }),
507
+ /* @__PURE__ */ jsx(StepStatusIcon, {
508
+ status,
509
+ spinnerFrame
510
+ }),
511
+ /* @__PURE__ */ jsxs(Text, {
512
+ dimColor: isDone,
513
+ children: [
514
+ " ",
515
+ title,
516
+ isDone && step.duration && step.duration !== "cached" ? /* @__PURE__ */ jsxs(Text, {
517
+ dimColor: true,
518
+ children: [" ", step.duration]
519
+ }) : null
520
+ ]
521
+ })
522
+ ] }), isRunning && lastLines.length > 0 && /* @__PURE__ */ jsx(Box, {
523
+ flexDirection: "column",
524
+ children: lastLines.map((line, li) => /* @__PURE__ */ jsxs(Text, {
525
+ dimColor: true,
526
+ children: [" ", line]
527
+ }, li))
528
+ })]
529
+ }, `extra-${idx}`);
530
+ }),
531
+ /* @__PURE__ */ jsxs(Box, {
532
+ marginTop: 1,
533
+ children: [/* @__PURE__ */ jsx(AnimatedSpinner, {}), /* @__PURE__ */ jsxs(Text, { children: [" ", bottomSpinner] })]
534
+ })
535
+ ]
536
+ });
537
+ }
538
+ function DeployTUIApp({ stream, serviceName, resultRef }) {
539
+ const { exit } = useApp();
540
+ const [, forceUpdate] = useState(0);
541
+ const [staticLines, setStaticLines] = useState([]);
542
+ const [shouldExit, setShouldExit] = useState(false);
543
+ const stateRef = useRef({
544
+ stages: /* @__PURE__ */ new Map(),
545
+ displayOrder: [],
546
+ platformMessages: [],
547
+ stepDetails: /* @__PURE__ */ new Map(),
548
+ phase: "waiting",
549
+ finalStatus: "",
550
+ committedCount: 0,
551
+ headerCommitted: false
552
+ });
553
+ useEffect(() => {
554
+ if (shouldExit) exit();
555
+ }, [shouldExit, exit]);
556
+ useEffect(() => {
557
+ const state$1 = stateRef.current;
558
+ const allLogs = [];
559
+ let accumulatedBuildLogs = "";
560
+ let runtimeLogId = 0;
561
+ function applyBuildParse() {
562
+ const result = buildStepsToStages(parseBuildSteps(accumulatedBuildLogs));
563
+ state$1.stages = result.stages;
564
+ state$1.displayOrder = result.displayOrder;
565
+ state$1.platformMessages = result.platformMessages;
566
+ state$1.stepDetails = result.stepDetails;
567
+ }
568
+ /** Check if a display item is fully complete */
569
+ function isItemComplete(item) {
570
+ if (item.kind === "stage") {
571
+ const stage = state$1.stages.get(item.name);
572
+ return stage ? getStageStats(stage).allFinished : false;
573
+ }
574
+ return item.step.status === "done" || item.step.status === "cached";
575
+ }
576
+ /** Progressively commit completed leading items to Static for scrolling */
577
+ function commitCompleted() {
578
+ const newItems = [];
579
+ if (!state$1.headerCommitted && state$1.displayOrder.length > 0) {
580
+ state$1.headerCommitted = true;
581
+ newItems.push({
582
+ id: "build-header",
583
+ node: /* @__PURE__ */ jsxs(Text, {
584
+ bold: true,
585
+ color: "cyan",
586
+ children: [" BUILD ", serviceName ? /* @__PURE__ */ jsxs(Text, {
587
+ dimColor: true,
588
+ children: [
589
+ "(",
590
+ serviceName,
591
+ ")"
592
+ ]
593
+ }) : null]
594
+ })
595
+ });
596
+ for (const [i, msg] of state$1.platformMessages.entries()) newItems.push({
597
+ id: `platform-${i}`,
598
+ node: /* @__PURE__ */ jsxs(Text, { children: [" ", msg] })
599
+ });
600
+ newItems.push({
601
+ id: "header-gap",
602
+ node: /* @__PURE__ */ jsx(Text, { children: "" })
603
+ });
604
+ }
605
+ const stageNames = state$1.displayOrder.filter((d) => d.kind === "stage").map((d) => d.name);
606
+ const maxNameLen = Math.max(...stageNames.map((n) => n.length), 4);
607
+ for (let i = state$1.committedCount; i < state$1.displayOrder.length; i++) {
608
+ const item = state$1.displayOrder[i];
609
+ if (!isItemComplete(item)) break;
610
+ if (item.kind === "stage") {
611
+ const stage = state$1.stages.get(item.name);
612
+ const stats = getStageStats(stage);
613
+ const counter = `${stats.finishedCount}/${stage.total}`;
614
+ const barNode = stats.allCached ? /* @__PURE__ */ jsxs(Text, {
615
+ color: BRAND_COLOR,
616
+ children: [
617
+ "━".repeat(BAR_WIDTH),
618
+ " ",
619
+ counter,
620
+ " ✓ veloz cache"
621
+ ]
622
+ }) : /* @__PURE__ */ jsxs(Text, {
623
+ color: "green",
624
+ children: [
625
+ "━".repeat(BAR_WIDTH),
626
+ " ",
627
+ counter,
628
+ " ✓"
629
+ ]
630
+ });
631
+ newItems.push({
632
+ id: `stage-${item.name}`,
633
+ node: /* @__PURE__ */ jsxs(Box, { children: [
634
+ /* @__PURE__ */ jsxs(Text, {
635
+ bold: true,
636
+ children: [" ", item.name.padEnd(maxNameLen)]
637
+ }),
638
+ /* @__PURE__ */ jsx(Text, { children: " " }),
639
+ barNode
640
+ ] })
641
+ });
642
+ const sortedSteps = [...stage.steps.entries()].sort((a, b) => a[0] - b[0]);
643
+ for (const [stepNum, command] of sortedSteps) {
644
+ const stepStatus = getStepStatus(stepNum, stage);
645
+ const detail = state$1.stepDetails.get(`${item.name}-${stepNum}`);
646
+ const icon = stepStatus === "cached" ? /* @__PURE__ */ jsx(Text, {
647
+ color: BRAND_COLOR,
648
+ children: "✓"
649
+ }) : /* @__PURE__ */ jsx(Text, {
650
+ color: "green",
651
+ children: "✓"
652
+ });
653
+ const suffix = stepStatus === "cached" ? /* @__PURE__ */ jsx(Text, {
654
+ color: BRAND_COLOR,
655
+ children: " (veloz cache)"
656
+ }) : detail?.duration ? /* @__PURE__ */ jsxs(Text, {
657
+ dimColor: true,
658
+ children: [" ", detail.duration]
659
+ }) : null;
660
+ newItems.push({
661
+ id: `step-${item.name}-${stepNum}`,
662
+ node: /* @__PURE__ */ jsxs(Text, { children: [
663
+ " ",
664
+ icon,
665
+ " ",
666
+ /* @__PURE__ */ jsx(Text, {
667
+ dimColor: true,
668
+ children: command
669
+ }),
670
+ suffix
671
+ ] })
672
+ });
673
+ }
674
+ } else {
675
+ const step = item.step;
676
+ const title = friendlyExtraTitle(step.title);
677
+ const duration = step.duration && step.duration !== "cached" ? ` ${step.duration}` : "";
678
+ newItems.push({
679
+ id: `extra-${i}`,
680
+ node: /* @__PURE__ */ jsxs(Text, { children: [
681
+ " ",
682
+ /* @__PURE__ */ jsx(Text, {
683
+ color: "green",
684
+ children: "✓"
685
+ }),
686
+ " ",
687
+ /* @__PURE__ */ jsxs(Text, {
688
+ dimColor: true,
689
+ children: [title, duration]
690
+ })
691
+ ] })
692
+ });
693
+ }
694
+ newItems.push({
695
+ id: `gap-${i}`,
696
+ node: /* @__PURE__ */ jsx(Text, { children: "" })
697
+ });
698
+ state$1.committedCount = i + 1;
699
+ }
700
+ if (newItems.length > 0) setStaticLines((prev) => [...prev, ...newItems]);
701
+ }
702
+ /** Force-commit all remaining items (even if not complete) — used at phase transitions */
703
+ function commitAllRemaining() {
704
+ const newItems = [];
705
+ const stageNames = state$1.displayOrder.filter((d) => d.kind === "stage").map((d) => d.name);
706
+ const maxNameLen = Math.max(...stageNames.map((n) => n.length), 4);
707
+ for (let i = state$1.committedCount; i < state$1.displayOrder.length; i++) {
708
+ const item = state$1.displayOrder[i];
709
+ if (item.kind === "stage") {
710
+ const stage = state$1.stages.get(item.name);
711
+ const stats = getStageStats(stage);
712
+ const counter = `${stats.finishedCount}/${stage.total}`;
713
+ const barNode = stats.allCached ? /* @__PURE__ */ jsxs(Text, {
714
+ color: BRAND_COLOR,
715
+ children: [
716
+ "━".repeat(BAR_WIDTH),
717
+ " ",
718
+ counter,
719
+ " ✓ veloz cache"
720
+ ]
721
+ }) : stats.allDone ? /* @__PURE__ */ jsxs(Text, {
722
+ color: "green",
723
+ children: [
724
+ "━".repeat(BAR_WIDTH),
725
+ " ",
726
+ counter,
727
+ " ✓"
728
+ ]
729
+ }) : /* @__PURE__ */ jsxs(Text, { children: [
730
+ /* @__PURE__ */ jsx(Text, {
731
+ color: "cyan",
732
+ children: "━".repeat(Math.round(stats.finishedCount / Math.max(stage.total, 1) * BAR_WIDTH))
733
+ }),
734
+ /* @__PURE__ */ jsx(Text, {
735
+ dimColor: true,
736
+ children: "─".repeat(BAR_WIDTH - Math.round(stats.finishedCount / Math.max(stage.total, 1) * BAR_WIDTH))
737
+ }),
738
+ /* @__PURE__ */ jsxs(Text, {
739
+ dimColor: true,
740
+ children: [" ", counter]
741
+ })
742
+ ] });
743
+ newItems.push({
744
+ id: `stage-${item.name}`,
745
+ node: /* @__PURE__ */ jsxs(Box, { children: [
746
+ /* @__PURE__ */ jsxs(Text, {
747
+ bold: true,
748
+ children: [" ", item.name.padEnd(maxNameLen)]
749
+ }),
750
+ /* @__PURE__ */ jsx(Text, { children: " " }),
751
+ barNode
752
+ ] })
753
+ });
754
+ const sortedSteps = [...stage.steps.entries()].sort((a, b) => a[0] - b[0]);
755
+ for (const [stepNum, command] of sortedSteps) {
756
+ const stepStatus = getStepStatus(stepNum, stage);
757
+ const detail = state$1.stepDetails.get(`${item.name}-${stepNum}`);
758
+ const icon = stepStatus === "cached" ? /* @__PURE__ */ jsx(Text, {
759
+ color: BRAND_COLOR,
760
+ children: "✓"
761
+ }) : stepStatus === "done" || stepStatus === "cached" ? /* @__PURE__ */ jsx(Text, {
762
+ color: "green",
763
+ children: "✓"
764
+ }) : /* @__PURE__ */ jsx(Text, {
765
+ dimColor: true,
766
+ children: "·"
767
+ });
768
+ const suffix = stepStatus === "cached" ? /* @__PURE__ */ jsx(Text, {
769
+ color: BRAND_COLOR,
770
+ children: " (veloz cache)"
771
+ }) : detail?.duration ? /* @__PURE__ */ jsxs(Text, {
772
+ dimColor: true,
773
+ children: [" ", detail.duration]
774
+ }) : null;
775
+ newItems.push({
776
+ id: `step-${item.name}-${stepNum}`,
777
+ node: /* @__PURE__ */ jsxs(Text, { children: [
778
+ " ",
779
+ icon,
780
+ " ",
781
+ /* @__PURE__ */ jsx(Text, {
782
+ dimColor: true,
783
+ children: command
784
+ }),
785
+ suffix
786
+ ] })
787
+ });
788
+ }
789
+ } else {
790
+ const step = item.step;
791
+ const isDone = step.status === "done" || step.status === "cached";
792
+ const title = friendlyExtraTitle(step.title);
793
+ const duration = isDone && step.duration && step.duration !== "cached" ? ` ${step.duration}` : "";
794
+ const icon = isDone ? /* @__PURE__ */ jsx(Text, {
795
+ color: "green",
796
+ children: "✓"
797
+ }) : /* @__PURE__ */ jsx(Text, {
798
+ dimColor: true,
799
+ children: "·"
800
+ });
801
+ newItems.push({
802
+ id: `extra-${i}`,
803
+ node: /* @__PURE__ */ jsxs(Text, { children: [
804
+ " ",
805
+ icon,
806
+ " ",
807
+ /* @__PURE__ */ jsxs(Text, {
808
+ dimColor: true,
809
+ children: [title, duration]
810
+ })
811
+ ] })
812
+ });
813
+ }
814
+ newItems.push({
815
+ id: `gap-${i}`,
816
+ node: /* @__PURE__ */ jsx(Text, { children: "" })
817
+ });
818
+ }
819
+ state$1.committedCount = state$1.displayOrder.length;
820
+ return newItems;
821
+ }
822
+ const consume = async () => {
823
+ try {
824
+ for await (const event of stream) if (event.type === "status") {
825
+ state$1.finalStatus = event.content;
826
+ switch (event.content) {
827
+ case "BUILDING":
828
+ state$1.phase = "building";
829
+ forceUpdate((n) => n + 1);
830
+ break;
831
+ case "DEPLOYING":
832
+ if (accumulatedBuildLogs) applyBuildParse();
833
+ for (const stage of state$1.stages.values()) if (stage.steps.size > 0 && stage.steps.size < stage.total) stage.total = stage.steps.size;
834
+ commitCompleted();
835
+ if (state$1.committedCount < state$1.displayOrder.length) {
836
+ const remaining = commitAllRemaining();
837
+ if (remaining.length > 0) setStaticLines((prev) => [...prev, ...remaining]);
838
+ }
839
+ state$1.phase = "deploying";
840
+ forceUpdate((n) => n + 1);
841
+ break;
842
+ case "LIVE":
843
+ setStaticLines((prev) => [...prev, {
844
+ id: "live-status",
845
+ node: /* @__PURE__ */ jsxs(Text, {
846
+ color: "green",
847
+ children: [" ", "✓ Publicado"]
848
+ })
849
+ }]);
850
+ state$1.phase = "live";
851
+ forceUpdate((n) => n + 1);
852
+ break;
853
+ default: if (TERMINAL_STATUSES.has(event.content)) {
854
+ if (accumulatedBuildLogs) applyBuildParse();
855
+ if (state$1.phase === "building" || state$1.phase === "waiting") {
856
+ commitCompleted();
857
+ if (state$1.committedCount < state$1.displayOrder.length) {
858
+ const remaining = commitAllRemaining();
859
+ if (remaining.length > 0) setStaticLines((prev) => [...prev, ...remaining]);
860
+ }
861
+ }
862
+ state$1.phase = "failed";
863
+ forceUpdate((n) => n + 1);
864
+ }
865
+ }
866
+ } else if (event.type === "log") {
867
+ const lines = event.content.split("\n");
868
+ allLogs.push(...lines);
869
+ if (state$1.phase === "deploying" || state$1.phase === "live") {
870
+ for (const line of lines) {
871
+ const trimmed = line.trim();
872
+ if (!trimmed) continue;
873
+ const parsed = parseBuildLine(trimmed);
874
+ let text = null;
875
+ if (parsed.kind === "platform") text = parsed.message;
876
+ else if (parsed.kind === "other" && parsed.text) text = parsed.text;
877
+ else if (parsed.kind === "output") text = parsed.text;
878
+ if (text && !isHiddenMessage(text)) {
879
+ const logId = runtimeLogId++;
880
+ const newItems = [];
881
+ if (logId === 0) newItems.push({
882
+ id: "runtime-header",
883
+ node: /* @__PURE__ */ jsx(Text, {
884
+ bold: true,
885
+ color: "cyan",
886
+ children: "\n RUNTIME"
887
+ })
888
+ });
889
+ newItems.push({
890
+ id: `log-${logId}`,
891
+ node: /* @__PURE__ */ jsxs(Text, { children: [" ", text] })
892
+ });
893
+ setStaticLines((prev) => [...prev, ...newItems]);
894
+ }
895
+ }
896
+ continue;
897
+ }
898
+ if (state$1.phase === "waiting") state$1.phase = "building";
899
+ if (accumulatedBuildLogs && !accumulatedBuildLogs.endsWith("\n")) accumulatedBuildLogs += "\n";
900
+ accumulatedBuildLogs += event.content;
901
+ applyBuildParse();
902
+ commitCompleted();
903
+ forceUpdate((n) => n + 1);
904
+ }
905
+ } catch {}
906
+ resultRef.current = {
907
+ status: state$1.finalStatus,
908
+ logs: allLogs.filter((l) => l.trim())
909
+ };
910
+ setShouldExit(true);
911
+ };
912
+ consume();
913
+ }, [
914
+ stream,
915
+ serviceName,
916
+ resultRef,
917
+ exit,
918
+ setShouldExit
919
+ ]);
920
+ const state = stateRef.current;
921
+ const showBuild = state.phase === "waiting" || state.phase === "building";
922
+ const showDeploySpinner = state.phase === "deploying";
923
+ return /* @__PURE__ */ jsxs(Box, {
924
+ flexDirection: "column",
925
+ children: [
926
+ /* @__PURE__ */ jsx(Static, {
927
+ items: staticLines,
928
+ children: (line) => /* @__PURE__ */ jsx(Box, { children: line.node }, line.id)
929
+ }),
930
+ showBuild && /* @__PURE__ */ jsx(BuildDashboard, {
931
+ serviceName,
932
+ stages: state.stages,
933
+ displayOrder: state.displayOrder,
934
+ committedCount: state.committedCount,
935
+ platformMessages: state.platformMessages,
936
+ stepDetails: state.stepDetails,
937
+ spinnerText: state.phase === "waiting" ? "Aguardando início do build..." : "Compilando..."
938
+ }),
939
+ showDeploySpinner && /* @__PURE__ */ jsxs(Box, {
940
+ paddingLeft: 2,
941
+ children: [/* @__PURE__ */ jsx(AnimatedSpinner, {}), /* @__PURE__ */ jsx(Text, { children: " Publicando..." })]
942
+ })
943
+ ]
944
+ });
945
+ }
946
+ async function fetchDeployUrls(client, serviceId) {
947
+ try {
948
+ return (await client.domains.list({ serviceId })).map((d) => `https://${d.domain}`);
949
+ } catch {
950
+ return [];
951
+ }
952
+ }
953
+ function getFailureHints(status) {
954
+ switch (status) {
955
+ case "BUILD_FAILED": return [
956
+ "Verifique os logs de build acima para erros de compilação",
957
+ "Teste o build localmente: rode o comando de build do seu projeto",
958
+ "Use 'veloz config show' para verificar as configurações"
959
+ ];
960
+ case "DEPLOY_FAILED": return [
961
+ "O build passou mas o serviço falhou ao iniciar",
962
+ "Verifique se a porta configurada está correta: 'veloz config show'",
963
+ "Veja os logs de runtime: 'veloz logs -f'"
964
+ ];
965
+ case "CANCELLED": return ["Deploy cancelado. Execute 'veloz deploy' para tentar novamente."];
966
+ default: return ["Execute 'veloz logs -f' para mais detalhes."];
967
+ }
968
+ }
969
+ async function renderDeployTUI(deploymentId, serviceId, serviceName) {
970
+ const client = await getClient();
971
+ const stream = await client.logs.streamBuildLogs({ deploymentId });
972
+ const resultRef = { current: {
973
+ status: "",
974
+ logs: []
975
+ } };
976
+ const instance = render(/* @__PURE__ */ jsx(DeployTUIApp, {
977
+ stream,
978
+ serviceName,
979
+ resultRef
980
+ }), {
981
+ exitOnCtrlC: false,
982
+ patchConsole: false
983
+ });
984
+ try {
985
+ await instance.waitUntilExit();
986
+ } catch {
987
+ instance.unmount();
988
+ }
989
+ if (!resultRef.current.status) try {
990
+ const d = await client.deployments.get({ deploymentId });
991
+ resultRef.current.status = d.status;
992
+ try {
993
+ const logs = await client.logs.getBuildLogs({ deploymentId });
994
+ if (logs.buildLogs) resultRef.current.logs.push(...logs.buildLogs.split("\n"));
995
+ } catch {}
996
+ } catch {}
997
+ const urls = resultRef.current.status === "LIVE" ? await fetchDeployUrls(client, serviceId) : [];
998
+ const finalStatus = resultRef.current.status;
999
+ if (finalStatus === "LIVE") {
1000
+ success(serviceName ? `Deploy de ${chalk.bold(serviceName)} concluído! Serviço ativo.` : "Deploy concluído! Serviço ativo.");
1001
+ for (const url of urls) info(chalk.bold(url));
1002
+ } else if (TERMINAL_STATUSES.has(finalStatus)) {
1003
+ const label = statusLabels[finalStatus] ?? finalStatus;
1004
+ const hints = getFailureHints(finalStatus);
1005
+ const allLogs = resultRef.current.logs;
1006
+ if (allLogs.length > 0) {
1007
+ process.stderr.write("\n");
1008
+ process.stderr.write(chalk.red(` ${"─".repeat(50)}`) + "\n");
1009
+ process.stderr.write(chalk.red.bold(" Logs de build:") + "\n");
1010
+ process.stderr.write(chalk.red(` ${"─".repeat(50)}`) + "\n");
1011
+ for (const line of allLogs) if (line.trim()) process.stderr.write(chalk.dim(` ${line}`) + "\n");
1012
+ process.stderr.write(chalk.red(` ${"─".repeat(50)}`) + "\n");
1013
+ }
1014
+ const errorMsg = serviceName ? `Deploy de ${chalk.bold(serviceName)} finalizou: ${label}` : `Deploy finalizou: ${label}`;
1015
+ process.stderr.write(chalk.red(`\n✗ ${errorMsg}`) + "\n");
1016
+ for (const hint of hints) process.stderr.write(chalk.yellow(` → ${hint}`) + "\n");
1017
+ process.exit(1);
1018
+ }
1019
+ return {
1020
+ status: finalStatus,
1021
+ logs: resultRef.current.logs.filter((l) => l.trim()),
1022
+ urls,
1023
+ serviceName
1024
+ };
1025
+ }
1026
+
1027
+ //#endregion
1028
+ export { renderDeployTUI };