onflyt-cli 0.1.0-beta
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 +13093 -0
- package/package.json +61 -0
- package/src/App.tsx +13 -0
- package/src/commands/credits.tsx +151 -0
- package/src/commands/delete.tsx +315 -0
- package/src/commands/deploy.tsx +1039 -0
- package/src/commands/deployments.tsx +331 -0
- package/src/commands/help.tsx +74 -0
- package/src/commands/init.tsx +587 -0
- package/src/commands/login.tsx +207 -0
- package/src/commands/logout.tsx +31 -0
- package/src/commands/logs.tsx +447 -0
- package/src/commands/projects.tsx +287 -0
- package/src/commands/rollback.tsx +455 -0
- package/src/commands/teams.tsx +113 -0
- package/src/commands/whoami.tsx +48 -0
- package/src/components/Loading.tsx +68 -0
- package/src/index.tsx +130 -0
- package/src/lib/api.ts +152 -0
- package/src/lib/config.ts +90 -0
- package/src/lib/deploy-api.ts +515 -0
- package/src/lib/deploy.ts +260 -0
- package/src/lib/framework.ts +227 -0
- package/src/lib/git.ts +179 -0
- package/src/lib/scaffold.ts +225 -0
- package/src/shared.ts +353 -0
- package/src/types.d.ts +5 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
import React, { useEffect, useState, useCallback } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { readFileSync, existsSync } from "fs";
|
|
4
|
+
import { isLoggedIn } from "../lib/config.js";
|
|
5
|
+
import { GitDetector } from "../lib/git.js";
|
|
6
|
+
import {
|
|
7
|
+
Team,
|
|
8
|
+
Balance,
|
|
9
|
+
ProjectConfig,
|
|
10
|
+
INSTANCE_OPTIONS,
|
|
11
|
+
isPodProject,
|
|
12
|
+
getProjectConfig,
|
|
13
|
+
loadTeamsWithBalances,
|
|
14
|
+
getTeamDetails,
|
|
15
|
+
getTeamLimits,
|
|
16
|
+
getTeamPlanLabel,
|
|
17
|
+
findOrCreateProject,
|
|
18
|
+
updateProjectSettings,
|
|
19
|
+
getProjectDetails,
|
|
20
|
+
startDeployment,
|
|
21
|
+
getDeploymentStatus,
|
|
22
|
+
streamLogs,
|
|
23
|
+
DeploymentStatus,
|
|
24
|
+
deployManual,
|
|
25
|
+
} from "../lib/deploy-api.js";
|
|
26
|
+
import { TIER_HOURLY_PRICE } from "../shared";
|
|
27
|
+
import { api } from "../lib/api.js";
|
|
28
|
+
|
|
29
|
+
type Step =
|
|
30
|
+
| "loading"
|
|
31
|
+
| "team"
|
|
32
|
+
| "config"
|
|
33
|
+
| "instance"
|
|
34
|
+
| "env"
|
|
35
|
+
| "deploying"
|
|
36
|
+
| "done"
|
|
37
|
+
| "error";
|
|
38
|
+
|
|
39
|
+
type DeployStage = "starting" | "zipping" | "uploading" | "deployed";
|
|
40
|
+
|
|
41
|
+
interface Props {
|
|
42
|
+
teamFlag?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Deploy: React.FC<Props> = ({ teamFlag }) => {
|
|
46
|
+
const [step, setStep] = useState<Step>("loading");
|
|
47
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const handleSigInt = () => process.exit(0);
|
|
51
|
+
process.on("SIGINT", handleSigInt);
|
|
52
|
+
return () => {
|
|
53
|
+
process.off("SIGINT", handleSigInt);
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (step === "done" || step === "error") {
|
|
59
|
+
const timer = setTimeout(() => process.exit(0), 500);
|
|
60
|
+
return () => clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
}, [step]);
|
|
63
|
+
const [teams, setTeams] = useState<Team[]>([]);
|
|
64
|
+
const [selectedTeamIndex, setSelectedTeamIndex] = useState(0);
|
|
65
|
+
const [balances, setBalances] = useState<Record<string, Balance>>({});
|
|
66
|
+
const [projectConfig, setProjectConfig] = useState<ProjectConfig | null>(
|
|
67
|
+
null,
|
|
68
|
+
);
|
|
69
|
+
const [selectedInstanceIndex, setSelectedInstanceIndex] = useState(2);
|
|
70
|
+
const [replicas, setReplicas] = useState(1);
|
|
71
|
+
const [envVars, setEnvVars] = useState<Array<{ key: string; value: string }>>(
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
74
|
+
const [envMessage, setEnvMessage] = useState<string>("");
|
|
75
|
+
const [deploymentId, setDeploymentId] = useState<string>("");
|
|
76
|
+
const [deploymentStatus, setDeploymentStatus] =
|
|
77
|
+
useState<DeploymentStatus>("queued");
|
|
78
|
+
const [liveLogs, setLiveLogs] = useState<string[]>([]);
|
|
79
|
+
const [previewUrl, setPreviewUrl] = useState<string>("");
|
|
80
|
+
const [liveUrl, setLiveUrl] = useState<string>("");
|
|
81
|
+
const [projectId, setProjectId] = useState<string>("");
|
|
82
|
+
const [dotCount, setDotCount] = useState(0);
|
|
83
|
+
const [existingProject, setExistingProject] = useState<any>(null);
|
|
84
|
+
const [isNewProject, setIsNewProject] = useState(false);
|
|
85
|
+
const [projectInstanceSize, setProjectInstanceSize] = useState<string>("");
|
|
86
|
+
const [projectReplicas, setProjectReplicas] = useState<number>(1);
|
|
87
|
+
const [totalLifetimeSpend, setTotalLifetimeSpend] = useState<number>(0);
|
|
88
|
+
const [currentBalance, setCurrentBalance] = useState<{
|
|
89
|
+
balanceUSD: number;
|
|
90
|
+
balanceFormatted: string;
|
|
91
|
+
}>({ balanceUSD: 0, balanceFormatted: "$0.00" });
|
|
92
|
+
const [hasGitRemote, setHasGitRemote] = useState(false);
|
|
93
|
+
const [deployStage, setDeployStage] = useState<DeployStage>("starting");
|
|
94
|
+
const [uploadProgress, setUploadProgress] = useState({
|
|
95
|
+
uploaded: 0,
|
|
96
|
+
total: 0,
|
|
97
|
+
});
|
|
98
|
+
const [zipSize, setZipSize] = useState<number | null>(null);
|
|
99
|
+
|
|
100
|
+
const selectedTeam = teams[selectedTeamIndex];
|
|
101
|
+
const limits = getTeamLimits(totalLifetimeSpend);
|
|
102
|
+
const planLabel = getTeamPlanLabel(totalLifetimeSpend);
|
|
103
|
+
const needsPod = isPodProject(projectConfig?.framework);
|
|
104
|
+
|
|
105
|
+
const selectedInstance = INSTANCE_OPTIONS[selectedInstanceIndex];
|
|
106
|
+
const estimatedHourlyCost =
|
|
107
|
+
needsPod && selectedInstance
|
|
108
|
+
? TIER_HOURLY_PRICE[selectedInstance.id] * replicas
|
|
109
|
+
: null;
|
|
110
|
+
|
|
111
|
+
const formatEstimatedCost = (cost: number): string => {
|
|
112
|
+
if (cost === 0) return "Free";
|
|
113
|
+
return `$${cost.toFixed(3)}/hr`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (step !== "loading") return;
|
|
118
|
+
|
|
119
|
+
if (!isLoggedIn()) {
|
|
120
|
+
setErrorMsg("Not logged in. Run 'onflyt login' first.");
|
|
121
|
+
setStep("error");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const config = getProjectConfig();
|
|
126
|
+
if (!config) {
|
|
127
|
+
setErrorMsg("No onflyt.json found. Run 'onflyt init' first.");
|
|
128
|
+
setStep("error");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setProjectConfig(config);
|
|
132
|
+
|
|
133
|
+
const loadData = async () => {
|
|
134
|
+
try {
|
|
135
|
+
const gitDetector = new GitDetector(process.cwd());
|
|
136
|
+
const gitInfo = await gitDetector.detect();
|
|
137
|
+
setHasGitRemote(gitInfo.isGitRepo && gitInfo.remotes.length > 0);
|
|
138
|
+
|
|
139
|
+
const { teams: loadedTeams, balances: loadedBalances } =
|
|
140
|
+
await loadTeamsWithBalances();
|
|
141
|
+
setTeams(loadedTeams);
|
|
142
|
+
setBalances(loadedBalances);
|
|
143
|
+
|
|
144
|
+
const selectedIdx = teamFlag
|
|
145
|
+
? loadedTeams.findIndex(
|
|
146
|
+
(t) => t.team.id === teamFlag || t.team.slug === teamFlag,
|
|
147
|
+
)
|
|
148
|
+
: 0;
|
|
149
|
+
if (selectedIdx >= 0) setSelectedTeamIndex(selectedIdx);
|
|
150
|
+
|
|
151
|
+
const selectedTeamForProject =
|
|
152
|
+
loadedTeams[selectedIdx >= 0 ? selectedIdx : 0];
|
|
153
|
+
|
|
154
|
+
if (selectedTeamForProject) {
|
|
155
|
+
try {
|
|
156
|
+
const teamDetails = await getTeamDetails(
|
|
157
|
+
selectedTeamForProject.team.id,
|
|
158
|
+
);
|
|
159
|
+
setTotalLifetimeSpend(teamDetails.totalLifetimeSpend);
|
|
160
|
+
setCurrentBalance({
|
|
161
|
+
balanceUSD: teamDetails.balanceUSD,
|
|
162
|
+
balanceFormatted:
|
|
163
|
+
loadedBalances[selectedTeamForProject.team.id]
|
|
164
|
+
?.balanceFormatted || "$0.00",
|
|
165
|
+
});
|
|
166
|
+
} catch {
|
|
167
|
+
setTotalLifetimeSpend(0);
|
|
168
|
+
setCurrentBalance(
|
|
169
|
+
loadedBalances[selectedTeamForProject.team.id] || {
|
|
170
|
+
balanceUSD: 0,
|
|
171
|
+
balanceFormatted: "$0.00",
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const projectsRes = await api.get<any>(
|
|
178
|
+
`/projects/team/${selectedTeamForProject.team.id}`,
|
|
179
|
+
);
|
|
180
|
+
const existingProject = (projectsRes.projects || []).find(
|
|
181
|
+
(p: any) => p.name === config.name,
|
|
182
|
+
);
|
|
183
|
+
if (existingProject) {
|
|
184
|
+
const fullProject = await getProjectDetails(existingProject.id);
|
|
185
|
+
setExistingProject(fullProject);
|
|
186
|
+
setProjectId(fullProject.id);
|
|
187
|
+
setIsNewProject(false);
|
|
188
|
+
setProjectInstanceSize(fullProject.instanceSize || "micro");
|
|
189
|
+
setProjectReplicas(fullProject.maxInstances || 1);
|
|
190
|
+
const instanceIdx = INSTANCE_OPTIONS.findIndex(
|
|
191
|
+
(i) => i.id === (fullProject.instanceSize || "micro"),
|
|
192
|
+
);
|
|
193
|
+
setSelectedInstanceIndex(instanceIdx >= 0 ? instanceIdx : 2);
|
|
194
|
+
} else {
|
|
195
|
+
setIsNewProject(true);
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
setIsNewProject(true);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
setStep("team");
|
|
203
|
+
} catch (err: any) {
|
|
204
|
+
setErrorMsg(err.message || "Failed to load data");
|
|
205
|
+
setStep("error");
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
loadData();
|
|
210
|
+
}, [step, teamFlag]);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (step !== "deploying") return;
|
|
214
|
+
if (deploymentStatus === "deployed" || deploymentStatus === "failed")
|
|
215
|
+
return;
|
|
216
|
+
|
|
217
|
+
const interval = setInterval(() => {
|
|
218
|
+
setDotCount((c) => (c + 1) % 4);
|
|
219
|
+
}, 500);
|
|
220
|
+
|
|
221
|
+
return () => clearInterval(interval);
|
|
222
|
+
}, [step, deploymentStatus]);
|
|
223
|
+
|
|
224
|
+
const handleDeploy = useCallback(async () => {
|
|
225
|
+
if (!selectedTeam || !projectConfig) return;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const project = await findOrCreateProject(
|
|
229
|
+
selectedTeam.team.id,
|
|
230
|
+
projectConfig.name,
|
|
231
|
+
projectConfig.framework,
|
|
232
|
+
projectConfig.gitRepoUrl,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (!project.isNew) {
|
|
236
|
+
const fullProject = await getProjectDetails(project.id);
|
|
237
|
+
setExistingProject(fullProject);
|
|
238
|
+
setProjectId(project.id);
|
|
239
|
+
setIsNewProject(false);
|
|
240
|
+
const existingSize = fullProject.instanceSize || "micro";
|
|
241
|
+
const existingReplicas = fullProject.maxInstances || 1;
|
|
242
|
+
setProjectInstanceSize(existingSize);
|
|
243
|
+
setProjectReplicas(existingReplicas);
|
|
244
|
+
if (needsPod) {
|
|
245
|
+
const instanceIdx = INSTANCE_OPTIONS.findIndex(
|
|
246
|
+
(i) => i.id === existingSize,
|
|
247
|
+
);
|
|
248
|
+
setSelectedInstanceIndex(instanceIdx >= 0 ? instanceIdx : 2);
|
|
249
|
+
setReplicas(existingReplicas);
|
|
250
|
+
setStep("instance");
|
|
251
|
+
} else {
|
|
252
|
+
setStep("env");
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
setProjectId(project.id);
|
|
256
|
+
setIsNewProject(true);
|
|
257
|
+
if (needsPod) {
|
|
258
|
+
setStep("instance");
|
|
259
|
+
} else {
|
|
260
|
+
setStep("env");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (err: any) {
|
|
264
|
+
setErrorMsg(err.message);
|
|
265
|
+
setStep("error");
|
|
266
|
+
}
|
|
267
|
+
}, [selectedTeam, projectConfig, needsPod]);
|
|
268
|
+
|
|
269
|
+
const triggerDeployment = useCallback(
|
|
270
|
+
async (projId: string) => {
|
|
271
|
+
setStep("deploying");
|
|
272
|
+
setDeployStage("starting");
|
|
273
|
+
setUploadProgress({ uploaded: 0, total: 0 });
|
|
274
|
+
setZipSize(null);
|
|
275
|
+
|
|
276
|
+
const hasConfiguredGit = !!projectConfig?.gitRepoUrl;
|
|
277
|
+
|
|
278
|
+
if (hasConfiguredGit) {
|
|
279
|
+
try {
|
|
280
|
+
const depId = await startDeployment(
|
|
281
|
+
projId,
|
|
282
|
+
projectConfig?.gitBranch || "main",
|
|
283
|
+
needsPod ? selectedInstance?.id : undefined,
|
|
284
|
+
needsPod ? replicas : undefined,
|
|
285
|
+
envVars.length > 0 ? envVars : undefined,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
setDeploymentId(depId);
|
|
289
|
+
pollStatus(depId);
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
setStep("error");
|
|
292
|
+
setErrorMsg(err.message);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { join } = await import("path");
|
|
298
|
+
const { tmpdir } = await import("os");
|
|
299
|
+
const { statSync } = await import("fs");
|
|
300
|
+
const zipPath = join(tmpdir(), `onflyt-deploy-${Date.now()}.zip`);
|
|
301
|
+
|
|
302
|
+
setDeployStage("zipping");
|
|
303
|
+
|
|
304
|
+
setTimeout(async () => {
|
|
305
|
+
try {
|
|
306
|
+
const { createProjectZip } = await import("../lib/deploy-api.js");
|
|
307
|
+
await createProjectZip(process.cwd(), zipPath);
|
|
308
|
+
|
|
309
|
+
const size = statSync(zipPath).size;
|
|
310
|
+
setZipSize(size);
|
|
311
|
+
console.log(
|
|
312
|
+
`\x1b[90m Archive size: ${(size / 1024 / 1024).toFixed(1)} MB\x1b[0m`,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (size > 100 * 1024 * 1024) {
|
|
316
|
+
console.log(`\x1b[33m Warning: File exceeds 100MB limit\x1b[0m`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
setDeployStage("uploading");
|
|
320
|
+
const { deployManual } = await import("../lib/deploy-api.js");
|
|
321
|
+
const depId = await deployManual(
|
|
322
|
+
projId,
|
|
323
|
+
zipPath,
|
|
324
|
+
projectConfig?.framework,
|
|
325
|
+
needsPod ? selectedInstance?.id : undefined,
|
|
326
|
+
needsPod ? replicas : undefined,
|
|
327
|
+
envVars.length > 0 ? envVars : undefined,
|
|
328
|
+
(uploaded, total) => {
|
|
329
|
+
setUploadProgress({ uploaded, total });
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
buildCommand: projectConfig?.buildCommand,
|
|
333
|
+
outputDirectory: projectConfig?.outputDirectory,
|
|
334
|
+
installCommand: projectConfig?.installCommand,
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
setDeploymentId(depId);
|
|
339
|
+
pollStatus(depId);
|
|
340
|
+
} catch (err: any) {
|
|
341
|
+
setStep("error");
|
|
342
|
+
setErrorMsg(err.message);
|
|
343
|
+
}
|
|
344
|
+
}, 100);
|
|
345
|
+
},
|
|
346
|
+
[projectConfig, selectedInstance, replicas, envVars, needsPod],
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const handleUpdateAndDeploy = useCallback(async () => {
|
|
350
|
+
try {
|
|
351
|
+
if (needsPod) {
|
|
352
|
+
const instanceToUse = selectedInstance?.id || projectInstanceSize;
|
|
353
|
+
const replicasToUse = replicas || projectReplicas;
|
|
354
|
+
if (instanceToUse || replicasToUse) {
|
|
355
|
+
await updateProjectSettings(
|
|
356
|
+
projectId,
|
|
357
|
+
instanceToUse as any,
|
|
358
|
+
replicasToUse,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
setStep("deploying");
|
|
363
|
+
triggerDeployment(projectId);
|
|
364
|
+
} catch (err: any) {
|
|
365
|
+
setErrorMsg(err.message);
|
|
366
|
+
setStep("error");
|
|
367
|
+
}
|
|
368
|
+
}, [
|
|
369
|
+
projectId,
|
|
370
|
+
projectInstanceSize,
|
|
371
|
+
projectReplicas,
|
|
372
|
+
selectedInstance,
|
|
373
|
+
replicas,
|
|
374
|
+
needsPod,
|
|
375
|
+
triggerDeployment,
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
const pollStatus = useCallback(
|
|
379
|
+
(depId: string) => {
|
|
380
|
+
const poll = async () => {
|
|
381
|
+
try {
|
|
382
|
+
const details = await getDeploymentStatus(depId);
|
|
383
|
+
setDeploymentStatus(details.status);
|
|
384
|
+
|
|
385
|
+
if (details.status === "building" && liveLogs.length === 0) {
|
|
386
|
+
if (limits.enableLogStreaming) {
|
|
387
|
+
streamLogs(
|
|
388
|
+
depId,
|
|
389
|
+
(log) => {
|
|
390
|
+
setLiveLogs((prev) => [...prev.slice(-200), log]);
|
|
391
|
+
},
|
|
392
|
+
() => {
|
|
393
|
+
console.log("\x1b[33m⚠ Live logs unavailable\x1b[0m");
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (details.status === "live") {
|
|
400
|
+
setLiveUrl(
|
|
401
|
+
details.url || `https://${projectConfig?.name}.onflyt.dev`,
|
|
402
|
+
);
|
|
403
|
+
setPreviewUrl(details.previewUrl || "");
|
|
404
|
+
setStep("done");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (details.status === "failed") {
|
|
409
|
+
setErrorMsg(
|
|
410
|
+
"Deployment failed. Run 'onflyt logs " + depId + "' for details.",
|
|
411
|
+
);
|
|
412
|
+
setStep("error");
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
setTimeout(() => poll(), 3000);
|
|
417
|
+
} catch (err: any) {
|
|
418
|
+
setTimeout(() => poll(), 5000);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
poll();
|
|
423
|
+
},
|
|
424
|
+
[liveLogs.length, limits.enableLogStreaming, projectConfig?.name],
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
useInput((input, key) => {
|
|
428
|
+
if (input === "q" || input === "Q" || (key.ctrl && input === "c")) {
|
|
429
|
+
process.exit(0);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (key.escape) {
|
|
433
|
+
if (step === "instance") setStep("config");
|
|
434
|
+
if (step === "env") setStep(needsPod ? "instance" : "config");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (key.return) {
|
|
439
|
+
if (step === "team") {
|
|
440
|
+
setStep("config");
|
|
441
|
+
} else if (step === "config") {
|
|
442
|
+
handleDeploy();
|
|
443
|
+
} else if (step === "instance") {
|
|
444
|
+
setStep("env");
|
|
445
|
+
} else if (step === "env") {
|
|
446
|
+
triggerDeployment(projectId);
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (step === "config" && input === "1" && needsPod) {
|
|
452
|
+
setStep("instance");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (step === "team") {
|
|
457
|
+
if (key.upArrow) {
|
|
458
|
+
setSelectedTeamIndex((i) => Math.max(0, i - 1));
|
|
459
|
+
} else if (key.downArrow) {
|
|
460
|
+
setSelectedTeamIndex((i) => Math.min(teams.length - 1, i + 1));
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (step === "instance") {
|
|
466
|
+
if (key.upArrow) {
|
|
467
|
+
setSelectedInstanceIndex((i) => Math.max(0, i - 1));
|
|
468
|
+
} else if (key.downArrow) {
|
|
469
|
+
setSelectedInstanceIndex((i) =>
|
|
470
|
+
Math.min(INSTANCE_OPTIONS.length - 1, i + 1),
|
|
471
|
+
);
|
|
472
|
+
} else if (key.leftArrow) {
|
|
473
|
+
setReplicas((r) => Math.max(1, r - 1));
|
|
474
|
+
} else if (key.rightArrow) {
|
|
475
|
+
setReplicas((r) =>
|
|
476
|
+
Math.min(INSTANCE_OPTIONS[selectedInstanceIndex].maxReplicas, r + 1),
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (step === "env") {
|
|
483
|
+
if (input === "1") {
|
|
484
|
+
handleImportEnv();
|
|
485
|
+
} else if (input === "2") {
|
|
486
|
+
handleAddEnv();
|
|
487
|
+
} else if (input === "3") {
|
|
488
|
+
handleAddEnv();
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (input === "q" || input === "Q") {
|
|
494
|
+
process.exit(0);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const handleImportEnv = () => {
|
|
499
|
+
try {
|
|
500
|
+
const projectPath = process.cwd();
|
|
501
|
+
const envPath = `${projectPath}/.env`;
|
|
502
|
+
if (existsSync(envPath)) {
|
|
503
|
+
const content = readFileSync(envPath, "utf-8");
|
|
504
|
+
const vars = content
|
|
505
|
+
.split("\n")
|
|
506
|
+
.filter((line: string) => line.includes("=") && !line.startsWith("#"))
|
|
507
|
+
.map((line: string) => {
|
|
508
|
+
const [key, ...rest] = line.split("=");
|
|
509
|
+
return { key: key.trim(), value: rest.join("=").trim() };
|
|
510
|
+
});
|
|
511
|
+
setEnvVars(vars);
|
|
512
|
+
}
|
|
513
|
+
} catch {}
|
|
514
|
+
handleUpdateAndDeploy();
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const handleAddEnv = () => {
|
|
518
|
+
handleUpdateAndDeploy();
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
if (step === "loading") {
|
|
522
|
+
return (
|
|
523
|
+
<Box flexDirection="column">
|
|
524
|
+
<Text bold color="rgb(255,191,0)">
|
|
525
|
+
<Text>DEPLOY</Text>
|
|
526
|
+
</Text>
|
|
527
|
+
<Box marginTop={1}>
|
|
528
|
+
<Text>Loading...</Text>
|
|
529
|
+
</Box>
|
|
530
|
+
</Box>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (step === "team") {
|
|
535
|
+
return (
|
|
536
|
+
<Box flexDirection="column" padding={1}>
|
|
537
|
+
<Text bold color="rgb(255,191,0)">
|
|
538
|
+
<Text>DEPLOY</Text>
|
|
539
|
+
</Text>
|
|
540
|
+
|
|
541
|
+
<Box marginTop={1} flexDirection="column">
|
|
542
|
+
<Text bold>Select Team</Text>
|
|
543
|
+
<Text dimColor>Use ↑↓ to navigate, Enter to confirm</Text>
|
|
544
|
+
|
|
545
|
+
{teams.map((team, idx) => (
|
|
546
|
+
<Box key={team.team.id} marginTop={1}>
|
|
547
|
+
<Text color={idx === selectedTeamIndex ? "cyan" : "gray"}>
|
|
548
|
+
{idx === selectedTeamIndex ? "▶ " : " "}
|
|
549
|
+
</Text>
|
|
550
|
+
<Text bold={idx === selectedTeamIndex}>{team.team.name}</Text>
|
|
551
|
+
<Text dimColor> ({team.role})</Text>
|
|
552
|
+
</Box>
|
|
553
|
+
))}
|
|
554
|
+
</Box>
|
|
555
|
+
|
|
556
|
+
<Box marginTop={2}>
|
|
557
|
+
<Text dimColor>[Enter] Deploy to this team [Esc] Cancel</Text>
|
|
558
|
+
</Box>
|
|
559
|
+
</Box>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (step === "error") {
|
|
564
|
+
return (
|
|
565
|
+
<Box flexDirection="column">
|
|
566
|
+
<Text bold color="rgb(255,191,0)">
|
|
567
|
+
<Text>DEPLOY</Text>
|
|
568
|
+
</Text>
|
|
569
|
+
<Box marginTop={1}>
|
|
570
|
+
<Text bold color="red">
|
|
571
|
+
✖ Error
|
|
572
|
+
</Text>
|
|
573
|
+
</Box>
|
|
574
|
+
<Box marginTop={1}>
|
|
575
|
+
<Text color="red">{errorMsg}</Text>
|
|
576
|
+
</Box>
|
|
577
|
+
</Box>
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (step === "config") {
|
|
582
|
+
return (
|
|
583
|
+
<Box flexDirection="column" padding={1}>
|
|
584
|
+
<Text bold color="rgb(255,191,0)">
|
|
585
|
+
<Text>DEPLOY</Text>
|
|
586
|
+
</Text>
|
|
587
|
+
|
|
588
|
+
<Box marginTop={1} flexDirection="column">
|
|
589
|
+
<Text bold>┌─ Project Configuration</Text>
|
|
590
|
+
<Box marginTop={1}>
|
|
591
|
+
<Text color="cyan"> │ Name: </Text>
|
|
592
|
+
<Text bold>{projectConfig?.name || "Unknown"}</Text>
|
|
593
|
+
</Box>
|
|
594
|
+
<Box>
|
|
595
|
+
<Text color="cyan"> │ Type: </Text>
|
|
596
|
+
<Text bold color="yellow">
|
|
597
|
+
{projectConfig?.framework?.toUpperCase() || "STATIC"}
|
|
598
|
+
</Text>
|
|
599
|
+
{needsPod && <Text color="magenta"> (Onflyt Pod)</Text>}
|
|
600
|
+
</Box>
|
|
601
|
+
<Box>
|
|
602
|
+
<Text color="cyan"> │ Deploy: </Text>
|
|
603
|
+
<Text color={hasGitRemote ? "green" : "yellow"}>
|
|
604
|
+
{hasGitRemote ? "Git-based" : "Manual (ZIP)"}
|
|
605
|
+
</Text>
|
|
606
|
+
</Box>
|
|
607
|
+
<Box>
|
|
608
|
+
<Text color="cyan"> └─ Team: </Text>
|
|
609
|
+
<Text>{selectedTeam?.team.name}</Text>
|
|
610
|
+
</Box>
|
|
611
|
+
</Box>
|
|
612
|
+
|
|
613
|
+
<Box marginTop={2} flexDirection="column">
|
|
614
|
+
<Text bold>┌─ Usage Plan</Text>
|
|
615
|
+
<Box marginTop={1}>
|
|
616
|
+
<Text color="cyan"> │ Plan: </Text>
|
|
617
|
+
<Text bold color="yellow">
|
|
618
|
+
{planLabel}
|
|
619
|
+
</Text>
|
|
620
|
+
</Box>
|
|
621
|
+
<Box>
|
|
622
|
+
<Text color="cyan"> │ Credits: </Text>
|
|
623
|
+
<Text>{currentBalance.balanceFormatted}</Text>
|
|
624
|
+
</Box>
|
|
625
|
+
<Box>
|
|
626
|
+
<Text color="cyan"> │ Max Replicas: </Text>
|
|
627
|
+
<Text>{limits.maxInstancesPerProject}</Text>
|
|
628
|
+
</Box>
|
|
629
|
+
<Box>
|
|
630
|
+
<Text color="cyan"> └─ Build Time: </Text>
|
|
631
|
+
<Text>{limits.maxBuildMinutes} min</Text>
|
|
632
|
+
</Box>
|
|
633
|
+
</Box>
|
|
634
|
+
|
|
635
|
+
{needsPod && (
|
|
636
|
+
<Box marginTop={2} flexDirection="column">
|
|
637
|
+
<Text bold>┌─ Onflyt Pod</Text>
|
|
638
|
+
<Box marginTop={1}>
|
|
639
|
+
<Text color="cyan"> │ Instance: </Text>
|
|
640
|
+
<Text color="yellow">{selectedInstance?.label || "not set"}</Text>
|
|
641
|
+
</Box>
|
|
642
|
+
<Box>
|
|
643
|
+
<Text color="cyan"> │ Replicas: </Text>
|
|
644
|
+
<Text color="yellow">{replicas}</Text>
|
|
645
|
+
</Box>
|
|
646
|
+
<Box>
|
|
647
|
+
<Text color="cyan"> └─ Est. Cost: </Text>
|
|
648
|
+
<Text color="yellow">
|
|
649
|
+
{estimatedHourlyCost !== null
|
|
650
|
+
? formatEstimatedCost(estimatedHourlyCost)
|
|
651
|
+
: "Free"}
|
|
652
|
+
</Text>
|
|
653
|
+
</Box>
|
|
654
|
+
</Box>
|
|
655
|
+
)}
|
|
656
|
+
|
|
657
|
+
<Box marginTop={2}>
|
|
658
|
+
<Text bold color="gray">
|
|
659
|
+
{" "}
|
|
660
|
+
─────────────────────────────────────────────
|
|
661
|
+
</Text>
|
|
662
|
+
</Box>
|
|
663
|
+
|
|
664
|
+
<Box marginTop={2} flexDirection="column">
|
|
665
|
+
{needsPod && (
|
|
666
|
+
<Box marginBottom={1}>
|
|
667
|
+
<Text color="gray"> </Text>
|
|
668
|
+
<Text bold color="cyan">
|
|
669
|
+
[1]
|
|
670
|
+
</Text>
|
|
671
|
+
<Text color="gray"> Configure Onflyt Pod</Text>
|
|
672
|
+
</Box>
|
|
673
|
+
)}
|
|
674
|
+
<Box>
|
|
675
|
+
<Text color="gray"> </Text>
|
|
676
|
+
<Text bold color="green">
|
|
677
|
+
▸
|
|
678
|
+
</Text>
|
|
679
|
+
<Text color="gray"> </Text>
|
|
680
|
+
<Text bold color="green">
|
|
681
|
+
[Enter] Deploy now
|
|
682
|
+
</Text>
|
|
683
|
+
</Box>
|
|
684
|
+
<Box marginTop={1}>
|
|
685
|
+
<Text color="gray"> </Text>
|
|
686
|
+
<Text color="gray">[q] Quit</Text>
|
|
687
|
+
</Box>
|
|
688
|
+
</Box>
|
|
689
|
+
</Box>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (step === "instance") {
|
|
694
|
+
return (
|
|
695
|
+
<Box flexDirection="column" padding={1}>
|
|
696
|
+
<Text bold color="rgb(255,191,0)">
|
|
697
|
+
<Text>DEPLOY</Text>
|
|
698
|
+
</Text>
|
|
699
|
+
|
|
700
|
+
<Box marginTop={1} flexDirection="column">
|
|
701
|
+
<Text bold>┌─ Onflyt Pod Configuration</Text>
|
|
702
|
+
<Text color="gray"> Choose instance size and replica count</Text>
|
|
703
|
+
|
|
704
|
+
<Box marginTop={1} flexDirection="column">
|
|
705
|
+
{INSTANCE_OPTIONS.map((option, idx) => (
|
|
706
|
+
<Box key={option.id}>
|
|
707
|
+
<Text color="gray"> </Text>
|
|
708
|
+
<Text
|
|
709
|
+
bold={selectedInstanceIndex === idx}
|
|
710
|
+
color={selectedInstanceIndex === idx ? "cyan" : "gray"}
|
|
711
|
+
>
|
|
712
|
+
{selectedInstanceIndex === idx ? "▸" : " "}[{idx + 1}]{" "}
|
|
713
|
+
{option.label.padEnd(10)}
|
|
714
|
+
</Text>
|
|
715
|
+
<Text color="gray">
|
|
716
|
+
{option.cpu} CPU, {option.ram} RAM, {option.disk} Disk
|
|
717
|
+
</Text>
|
|
718
|
+
<Text color="yellow"> - {option.hourly}</Text>
|
|
719
|
+
</Box>
|
|
720
|
+
))}
|
|
721
|
+
</Box>
|
|
722
|
+
|
|
723
|
+
<Box marginTop={1}>
|
|
724
|
+
<Text color="cyan"> │ Selected: </Text>
|
|
725
|
+
<Text bold color="cyan">
|
|
726
|
+
{INSTANCE_OPTIONS[selectedInstanceIndex].label}
|
|
727
|
+
</Text>
|
|
728
|
+
<Text color="gray">
|
|
729
|
+
{" "}
|
|
730
|
+
({INSTANCE_OPTIONS[selectedInstanceIndex].cpu} CPU,{" "}
|
|
731
|
+
{INSTANCE_OPTIONS[selectedInstanceIndex].ram} RAM)
|
|
732
|
+
</Text>
|
|
733
|
+
</Box>
|
|
734
|
+
|
|
735
|
+
<Box marginTop={1}>
|
|
736
|
+
<Text color="cyan"> └─ Replicas: </Text>
|
|
737
|
+
<Text bold color="cyan">
|
|
738
|
+
{replicas}
|
|
739
|
+
</Text>
|
|
740
|
+
<Text color="gray">
|
|
741
|
+
{" "}
|
|
742
|
+
(max{" "}
|
|
743
|
+
{Math.min(
|
|
744
|
+
limits.maxInstancesPerProject,
|
|
745
|
+
INSTANCE_OPTIONS[selectedInstanceIndex].maxReplicas,
|
|
746
|
+
)}
|
|
747
|
+
)
|
|
748
|
+
</Text>
|
|
749
|
+
</Box>
|
|
750
|
+
</Box>
|
|
751
|
+
|
|
752
|
+
<Box marginTop={2}>
|
|
753
|
+
<Text color="gray">
|
|
754
|
+
{" "}
|
|
755
|
+
↑↓ Change instance ←→ Change replicas [Enter] Continue [Esc] Back
|
|
756
|
+
</Text>
|
|
757
|
+
</Box>
|
|
758
|
+
</Box>
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (step === "env") {
|
|
763
|
+
return (
|
|
764
|
+
<Box flexDirection="column" padding={1}>
|
|
765
|
+
<Text bold color="rgb(255,191,0)">
|
|
766
|
+
<Text>DEPLOY</Text>
|
|
767
|
+
</Text>
|
|
768
|
+
|
|
769
|
+
<Box marginTop={1} flexDirection="column">
|
|
770
|
+
<Text bold>┌─ Environment Variables</Text>
|
|
771
|
+
<Text color="gray"> Import from .env or add manually</Text>
|
|
772
|
+
|
|
773
|
+
<Box marginTop={1}>
|
|
774
|
+
<Text color="gray"> </Text>
|
|
775
|
+
<Text bold color="cyan">
|
|
776
|
+
[1]
|
|
777
|
+
</Text>
|
|
778
|
+
<Text color="gray"> Import from .env</Text>
|
|
779
|
+
</Box>
|
|
780
|
+
<Box>
|
|
781
|
+
<Text color="gray"> </Text>
|
|
782
|
+
<Text bold color="cyan">
|
|
783
|
+
[2]
|
|
784
|
+
</Text>
|
|
785
|
+
<Text color="gray"> Add manually</Text>
|
|
786
|
+
</Box>
|
|
787
|
+
<Box>
|
|
788
|
+
<Text color="gray"> </Text>
|
|
789
|
+
<Text bold color="cyan">
|
|
790
|
+
[3]
|
|
791
|
+
</Text>
|
|
792
|
+
<Text color="gray"> Skip (use existing)</Text>
|
|
793
|
+
</Box>
|
|
794
|
+
</Box>
|
|
795
|
+
|
|
796
|
+
<Box marginTop={2}>
|
|
797
|
+
<Text color="gray"> [1/2/3] Select option [Esc] Back</Text>
|
|
798
|
+
</Box>
|
|
799
|
+
|
|
800
|
+
{envMessage && (
|
|
801
|
+
<Box marginTop={1}>
|
|
802
|
+
<Text color={envMessage.includes("✓") ? "green" : "yellow"}>
|
|
803
|
+
{envMessage}
|
|
804
|
+
</Text>
|
|
805
|
+
</Box>
|
|
806
|
+
)}
|
|
807
|
+
</Box>
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (step === "deploying") {
|
|
812
|
+
const dots = ".".repeat(dotCount);
|
|
813
|
+
const statusColors: Record<DeploymentStatus, string> = {
|
|
814
|
+
queued: "yellow",
|
|
815
|
+
building: "cyan",
|
|
816
|
+
provisioning: "magenta",
|
|
817
|
+
deployed: "green",
|
|
818
|
+
failed: "red",
|
|
819
|
+
};
|
|
820
|
+
const statusLabels: Record<DeploymentStatus, string> = {
|
|
821
|
+
queued: "QUEUED",
|
|
822
|
+
building: "BUILDING",
|
|
823
|
+
provisioning: "PROVISIONING",
|
|
824
|
+
deployed: "DEPLOYED",
|
|
825
|
+
failed: "FAILED",
|
|
826
|
+
};
|
|
827
|
+
const statusIcons: Record<DeploymentStatus, string> = {
|
|
828
|
+
queued: "⏳",
|
|
829
|
+
building: "🔨",
|
|
830
|
+
provisioning: "⚙️",
|
|
831
|
+
deployed: "✅",
|
|
832
|
+
failed: "❌",
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const progressPercent =
|
|
836
|
+
uploadProgress.total > 0
|
|
837
|
+
? Math.round((uploadProgress.uploaded / uploadProgress.total) * 100)
|
|
838
|
+
: 0;
|
|
839
|
+
|
|
840
|
+
const getStageInfo = (): { icon: string; label: string; color: string } => {
|
|
841
|
+
switch (deployStage) {
|
|
842
|
+
case "starting":
|
|
843
|
+
return { icon: "⏳", label: "Starting deployment", color: "cyan" };
|
|
844
|
+
case "zipping":
|
|
845
|
+
return { icon: "📦", label: "Creating ZIP archive", color: "cyan" };
|
|
846
|
+
case "uploading":
|
|
847
|
+
if (progressPercent >= 100) {
|
|
848
|
+
return { icon: "✅", label: "Upload completed", color: "green" };
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
icon: "⬆️",
|
|
852
|
+
label: `Uploading ${progressPercent}%`,
|
|
853
|
+
color: "cyan",
|
|
854
|
+
};
|
|
855
|
+
case "deployed":
|
|
856
|
+
return { icon: "✅", label: "Deployed", color: "green" };
|
|
857
|
+
default:
|
|
858
|
+
return { icon: "⏳", label: "Processing", color: "cyan" };
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const stageInfo = getStageInfo();
|
|
863
|
+
|
|
864
|
+
const formatBytes = (bytes: number): string => {
|
|
865
|
+
if (bytes === 0) return "0 B";
|
|
866
|
+
const k = 1024;
|
|
867
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
868
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
869
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
const progressBar = (percent: number, width: number = 30): string => {
|
|
873
|
+
const filled = Math.round((percent / 100) * width);
|
|
874
|
+
const empty = width - filled;
|
|
875
|
+
return "█".repeat(filled) + "░".repeat(empty);
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
const showProgressBar =
|
|
879
|
+
deployStage === "uploading" && zipSize && progressPercent < 100;
|
|
880
|
+
|
|
881
|
+
return (
|
|
882
|
+
<Box flexDirection="column" padding={1}>
|
|
883
|
+
<Text bold color="rgb(255,191,0)">
|
|
884
|
+
<Text>DEPLOY</Text>
|
|
885
|
+
</Text>
|
|
886
|
+
|
|
887
|
+
<Box marginTop={2}>
|
|
888
|
+
<Text bold color={stageInfo.color}>
|
|
889
|
+
{stageInfo.icon} {stageInfo.label}
|
|
890
|
+
</Text>
|
|
891
|
+
<Text dimColor>{dots}</Text>
|
|
892
|
+
</Box>
|
|
893
|
+
|
|
894
|
+
{deployStage === "zipping" && (
|
|
895
|
+
<Box marginTop={1}>
|
|
896
|
+
<Text dimColor>Compressing files...</Text>
|
|
897
|
+
</Box>
|
|
898
|
+
)}
|
|
899
|
+
|
|
900
|
+
{showProgressBar && (
|
|
901
|
+
<Box marginTop={1} flexDirection="column">
|
|
902
|
+
<Box>
|
|
903
|
+
<Text dimColor>[{progressBar(progressPercent)}]</Text>
|
|
904
|
+
</Box>
|
|
905
|
+
<Box>
|
|
906
|
+
<Text dimColor>
|
|
907
|
+
{formatBytes(uploadProgress.uploaded)} /{" "}
|
|
908
|
+
{formatBytes(uploadProgress.total)}
|
|
909
|
+
</Text>
|
|
910
|
+
</Box>
|
|
911
|
+
</Box>
|
|
912
|
+
)}
|
|
913
|
+
|
|
914
|
+
<Box marginTop={2}>
|
|
915
|
+
<Text bold color="cyan">
|
|
916
|
+
Status:{" "}
|
|
917
|
+
</Text>
|
|
918
|
+
<Text bold color={statusColors[deploymentStatus]}>
|
|
919
|
+
{statusIcons[deploymentStatus]} {statusLabels[deploymentStatus]}
|
|
920
|
+
</Text>
|
|
921
|
+
<Text dimColor>{dots}</Text>
|
|
922
|
+
</Box>
|
|
923
|
+
{deploymentId && (
|
|
924
|
+
<Box>
|
|
925
|
+
<Text bold color="cyan">
|
|
926
|
+
ID:
|
|
927
|
+
</Text>
|
|
928
|
+
<Text> {deploymentId}</Text>
|
|
929
|
+
</Box>
|
|
930
|
+
)}
|
|
931
|
+
<Box>
|
|
932
|
+
<Text bold color="cyan">
|
|
933
|
+
Project:
|
|
934
|
+
</Text>
|
|
935
|
+
<Text> {projectConfig?.name}</Text>
|
|
936
|
+
</Box>
|
|
937
|
+
|
|
938
|
+
{liveLogs.length > 0 && (
|
|
939
|
+
<Box marginTop={2} flexDirection="column">
|
|
940
|
+
<Text bold color="cyan">
|
|
941
|
+
Build Logs:
|
|
942
|
+
</Text>
|
|
943
|
+
<Box
|
|
944
|
+
flexDirection="column"
|
|
945
|
+
marginTop={1}
|
|
946
|
+
padding={1}
|
|
947
|
+
borderStyle="round"
|
|
948
|
+
borderDimColor
|
|
949
|
+
>
|
|
950
|
+
{liveLogs.slice(-15).map((log, idx) => (
|
|
951
|
+
<Text key={idx} color="white">
|
|
952
|
+
{log}
|
|
953
|
+
</Text>
|
|
954
|
+
))}
|
|
955
|
+
</Box>
|
|
956
|
+
</Box>
|
|
957
|
+
)}
|
|
958
|
+
|
|
959
|
+
{deploymentId && (
|
|
960
|
+
<Box marginTop={2}>
|
|
961
|
+
<Text color="gray">
|
|
962
|
+
Run 'onflyt tail {deploymentId}' to watch logs
|
|
963
|
+
</Text>
|
|
964
|
+
</Box>
|
|
965
|
+
)}
|
|
966
|
+
</Box>
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (step === "done") {
|
|
971
|
+
return (
|
|
972
|
+
<Box flexDirection="column" padding={1}>
|
|
973
|
+
<Text bold color="rgb(255,191,0)">
|
|
974
|
+
DEPLOYED!
|
|
975
|
+
</Text>
|
|
976
|
+
|
|
977
|
+
<Box
|
|
978
|
+
marginTop={2}
|
|
979
|
+
flexDirection="column"
|
|
980
|
+
borderStyle="round"
|
|
981
|
+
borderColor="green"
|
|
982
|
+
padding={1}
|
|
983
|
+
>
|
|
984
|
+
<Text bold color="green">
|
|
985
|
+
✓ Deployment Successful
|
|
986
|
+
</Text>
|
|
987
|
+
|
|
988
|
+
<Box marginTop={1}>
|
|
989
|
+
<Text bold color="cyan">
|
|
990
|
+
ID:
|
|
991
|
+
</Text>
|
|
992
|
+
<Text> {deploymentId}</Text>
|
|
993
|
+
</Box>
|
|
994
|
+
|
|
995
|
+
<Box marginTop={1}>
|
|
996
|
+
<Text bold color="cyan">
|
|
997
|
+
Live URL:
|
|
998
|
+
</Text>
|
|
999
|
+
<Text bold color="white">
|
|
1000
|
+
{" "}
|
|
1001
|
+
{liveUrl}
|
|
1002
|
+
</Text>
|
|
1003
|
+
</Box>
|
|
1004
|
+
|
|
1005
|
+
{previewUrl && (
|
|
1006
|
+
<Box marginTop={1}>
|
|
1007
|
+
<Text bold color="cyan">
|
|
1008
|
+
Preview:
|
|
1009
|
+
</Text>
|
|
1010
|
+
<Text color="white"> {previewUrl}</Text>
|
|
1011
|
+
</Box>
|
|
1012
|
+
)}
|
|
1013
|
+
|
|
1014
|
+
{needsPod && (
|
|
1015
|
+
<Box marginTop={1}>
|
|
1016
|
+
<Text bold color="magenta">
|
|
1017
|
+
Onflyt Pod:
|
|
1018
|
+
</Text>
|
|
1019
|
+
<Text>
|
|
1020
|
+
{" "}
|
|
1021
|
+
{replicas}x {INSTANCE_OPTIONS[selectedInstanceIndex].label}
|
|
1022
|
+
</Text>
|
|
1023
|
+
</Box>
|
|
1024
|
+
)}
|
|
1025
|
+
</Box>
|
|
1026
|
+
|
|
1027
|
+
<Box marginTop={2}>
|
|
1028
|
+
<Text color="gray">
|
|
1029
|
+
Run 'onflyt logs {deploymentId}' to view logs
|
|
1030
|
+
</Text>
|
|
1031
|
+
</Box>
|
|
1032
|
+
</Box>
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return null;
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
export default Deploy;
|