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.
@@ -0,0 +1,331 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Text, Box, useInput } from "ink";
3
+ import { isLoggedIn, getConfig } from "../lib/config.js";
4
+ import { api } from "../lib/api.js";
5
+ import { Logo } from "../components/Loading.js";
6
+ import Spinner from "ink-spinner";
7
+
8
+ interface DeploymentsProps {
9
+ projectName?: string;
10
+ }
11
+
12
+ type Step =
13
+ | "loading"
14
+ | "loading-projects"
15
+ | "loading-deployments"
16
+ | "team"
17
+ | "project"
18
+ | "display"
19
+ | "error";
20
+
21
+ const Deployments: React.FC<DeploymentsProps> = ({ projectName }) => {
22
+ const [step, setStep] = useState<Step>("loading");
23
+
24
+ const [teams, setTeams] = useState<any[]>([]);
25
+ const [projects, setProjects] = useState<any[]>([]);
26
+ const [deployments, setDeployments] = useState<any[]>([]);
27
+ const [targetProject, setTargetProject] = useState<any>(null);
28
+
29
+ const [selectedTeamIndex, setSelectedTeamIndex] = useState(0);
30
+ const [selectedProjectIndex, setSelectedProjectIndex] = useState(0);
31
+ const [errorMsg, setErrorMsg] = useState("");
32
+
33
+ useEffect(() => {
34
+ const handleSigInt = () => process.exit(0);
35
+ process.on("SIGINT", handleSigInt);
36
+ return () => {
37
+ process.off("SIGINT", handleSigInt);
38
+ };
39
+ }, []);
40
+
41
+ useInput((input, key) => {
42
+ if (input === "q" || input === "Q" || (key.ctrl && input === "c")) {
43
+ process.exit(0);
44
+ }
45
+
46
+ if (step === "team") {
47
+ if (key.upArrow) {
48
+ setSelectedTeamIndex((i) => Math.max(0, i - 1));
49
+ } else if (key.downArrow) {
50
+ setSelectedTeamIndex((i) => Math.min(teams.length - 1, i + 1));
51
+ } else if (key.return) {
52
+ setStep("loading-projects");
53
+ loadProjects(teams[selectedTeamIndex].team.id);
54
+ }
55
+ }
56
+
57
+ if (step === "project") {
58
+ if (key.upArrow) {
59
+ setSelectedProjectIndex((i) => Math.max(0, i - 1));
60
+ } else if (key.downArrow) {
61
+ setSelectedProjectIndex((i) => Math.min(projects.length - 1, i + 1));
62
+ } else if (key.return) {
63
+ setStep("loading-deployments");
64
+ loadDeployments(projects[selectedProjectIndex]);
65
+ } else if (key.escape) {
66
+ setStep("team");
67
+ }
68
+ }
69
+ });
70
+
71
+ useEffect(() => {
72
+ if (!isLoggedIn()) {
73
+ setErrorMsg("Not logged in. Run 'onflyt login' first.");
74
+ setStep("error");
75
+ return;
76
+ }
77
+
78
+ loadTeams();
79
+ }, []);
80
+
81
+ const loadTeams = async () => {
82
+ try {
83
+ const config = getConfig();
84
+ api.setToken(config.token!);
85
+ const meData = await api.get<any>("/auth/me");
86
+ const userTeams = meData.teams || [];
87
+
88
+ if (userTeams.length === 0) {
89
+ setErrorMsg("No teams found");
90
+ setStep("error");
91
+ return;
92
+ }
93
+
94
+ setTeams(userTeams);
95
+
96
+ if (userTeams.length === 1) {
97
+ setSelectedTeamIndex(0);
98
+ setStep("loading-projects");
99
+ loadProjects(userTeams[0].team.id);
100
+ } else {
101
+ setStep("team");
102
+ }
103
+ } catch (err: any) {
104
+ setErrorMsg(err.message);
105
+ setStep("error");
106
+ }
107
+ };
108
+
109
+ const loadProjects = async (teamId: string) => {
110
+ try {
111
+ const projectsRes = await api.get<any>(`/projects/team/${teamId}`);
112
+ const teamProjects = projectsRes.projects || [];
113
+
114
+ if (teamProjects.length === 0) {
115
+ setErrorMsg("No projects found in this team");
116
+ setStep("error");
117
+ return;
118
+ }
119
+
120
+ setProjects(teamProjects);
121
+ setSelectedProjectIndex(0);
122
+ setStep("project");
123
+ } catch (err: any) {
124
+ setErrorMsg(err.message);
125
+ setStep("error");
126
+ }
127
+ };
128
+
129
+ const loadDeployments = async (project: any) => {
130
+ try {
131
+ setTargetProject(project);
132
+ const depsRes = await api.get<any>(`/deployments/${project.id}?limit=50`);
133
+ const allDeployments = depsRes.deployments || [];
134
+
135
+ setDeployments(allDeployments);
136
+ setStep("display");
137
+ } catch (err: any) {
138
+ setErrorMsg(err.message);
139
+ setStep("error");
140
+ }
141
+ };
142
+
143
+ if (
144
+ step === "loading" ||
145
+ step === "loading-projects" ||
146
+ step === "loading-deployments"
147
+ ) {
148
+ return (
149
+ <Box flexDirection="column" padding={1}>
150
+ <Logo />
151
+ <Box marginTop={1}>
152
+ <Text bold>Deployments</Text>
153
+ </Box>
154
+ <Box marginTop={1}>
155
+ <Text dimColor>
156
+ Loading
157
+ {step === "loading-projects"
158
+ ? " projects..."
159
+ : step === "loading-deployments"
160
+ ? " deployments..."
161
+ : "..."}
162
+ </Text>
163
+ </Box>
164
+ <Box marginTop={1}>
165
+ <Text>
166
+ <Spinner />
167
+ </Text>
168
+ </Box>
169
+ </Box>
170
+ );
171
+ }
172
+
173
+ if (step === "error") {
174
+ return (
175
+ <Box flexDirection="column">
176
+ <Logo />
177
+ <Box marginTop={1}>
178
+ <Text bold color="red">
179
+ ✖ Error
180
+ </Text>
181
+ </Box>
182
+ <Box marginTop={1}>
183
+ <Text color="red">{errorMsg}</Text>
184
+ </Box>
185
+ </Box>
186
+ );
187
+ }
188
+
189
+ if (step === "team") {
190
+ return (
191
+ <Box flexDirection="column" padding={1}>
192
+ <Logo />
193
+ <Box marginTop={1}>
194
+ <Text bold>Deployments</Text>
195
+ </Box>
196
+ <Box marginTop={1}>
197
+ <Text dimColor>
198
+ Step 1/2: Select Team (↑↓ navigate, Enter select)
199
+ </Text>
200
+ </Box>
201
+
202
+ <Box marginTop={1} flexDirection="column">
203
+ {teams.map((t, idx) => (
204
+ <Box key={t.team.id} marginTop={1}>
205
+ <Text color={idx === selectedTeamIndex ? "cyan" : "gray"}>
206
+ {idx === selectedTeamIndex ? "▶ " : " "}
207
+ </Text>
208
+ <Text bold={idx === selectedTeamIndex}>{t.team.name}</Text>
209
+ </Box>
210
+ ))}
211
+ </Box>
212
+ </Box>
213
+ );
214
+ }
215
+
216
+ if (step === "project") {
217
+ return (
218
+ <Box flexDirection="column" padding={1}>
219
+ <Logo />
220
+ <Box marginTop={1}>
221
+ <Text bold>Deployments</Text>
222
+ </Box>
223
+ <Box marginTop={1}>
224
+ <Text dimColor>
225
+ Step 2/2: Select Project - {teams[selectedTeamIndex]?.team.name}
226
+ </Text>
227
+ </Box>
228
+ <Box>
229
+ <Text dimColor>(↑↓ navigate, Enter select, Esc go back)</Text>
230
+ </Box>
231
+
232
+ <Box marginTop={1} flexDirection="column">
233
+ {projects.map((p, idx) => (
234
+ <Box key={p.id} marginTop={1}>
235
+ <Text color={idx === selectedProjectIndex ? "cyan" : "gray"}>
236
+ {idx === selectedProjectIndex ? "▶ " : " "}
237
+ </Text>
238
+ <Text bold={idx === selectedProjectIndex}>{p.name}</Text>
239
+ </Box>
240
+ ))}
241
+ </Box>
242
+ </Box>
243
+ );
244
+ }
245
+
246
+ const formatDate = (dateStr: string) => {
247
+ const date = new Date(dateStr);
248
+ return date.toLocaleDateString() + " " + date.toLocaleTimeString();
249
+ };
250
+
251
+ const getStatusColor = (status: string) => {
252
+ switch (status.toLowerCase()) {
253
+ case "live":
254
+ case "deployed":
255
+ case "success":
256
+ return "green";
257
+ case "building":
258
+ case "queued":
259
+ case "provisioning":
260
+ return "cyan";
261
+ case "failed":
262
+ return "red";
263
+ default:
264
+ return "gray";
265
+ }
266
+ };
267
+
268
+ return (
269
+ <Box flexDirection="column" padding={1}>
270
+ <Logo />
271
+
272
+ <Box marginTop={1}>
273
+ <Text bold>Deployments</Text>
274
+ </Box>
275
+ <Box>
276
+ <Text dimColor>
277
+ {targetProject?.name} - {teams[selectedTeamIndex]?.team.name}
278
+ </Text>
279
+ </Box>
280
+
281
+ <Box marginTop={1}>
282
+ <Text dimColor>
283
+ {deployments.length === 0
284
+ ? "No deployments found"
285
+ : `Found ${deployments.length} deployment(s)`}
286
+ </Text>
287
+ </Box>
288
+
289
+ <Box marginTop={1} flexDirection="column">
290
+ {deployments.map((dep, idx) => (
291
+ <Box
292
+ key={dep.id}
293
+ marginTop={1}
294
+ flexDirection="column"
295
+ borderStyle="round"
296
+ borderDimColor
297
+ paddingX={1}
298
+ >
299
+ <Box>
300
+ <Text bold color={getStatusColor(dep.status)}>
301
+ {dep.status.toUpperCase()}
302
+ </Text>
303
+ <Text dimColor> - {formatDate(dep.createdAt)}</Text>
304
+ </Box>
305
+ <Box>
306
+ <Text dimColor>ID: {dep.id}</Text>
307
+ </Box>
308
+ {dep.commitMessage && (
309
+ <Box>
310
+ <Text dimColor wrap="wrap">
311
+ {dep.commitMessage}
312
+ </Text>
313
+ </Box>
314
+ )}
315
+ {dep.runtimeStatus && (
316
+ <Box>
317
+ <Text dimColor>Runtime: {dep.runtimeStatus}</Text>
318
+ </Box>
319
+ )}
320
+ </Box>
321
+ ))}
322
+ </Box>
323
+
324
+ <Box marginTop={2}>
325
+ <Text dimColor>Press Q or Ctrl+C to exit</Text>
326
+ </Box>
327
+ </Box>
328
+ );
329
+ };
330
+
331
+ export default Deployments;
@@ -0,0 +1,74 @@
1
+ import React, { useEffect } from "react";
2
+
3
+ const Help: React.FC = () => {
4
+ useEffect(() => {
5
+ console.log(`
6
+ \x1b[38;2;255;191;0m ⬡ ONFLYT \x1b[0m\x1b[1m Deploy CLI v0.1.0-beta\x1b[0m
7
+
8
+ \x1b[1mUSAGE\x1b[0m
9
+ $ onflyt <command> [options]
10
+
11
+ \x1b[1mCOMMANDS\x1b[0m
12
+ \x1b[36mlogin\x1b[0m Authenticate with GitHub OAuth
13
+ \x1b[36mlogout\x1b[0m Sign out
14
+ \x1b[36mwhoami\x1b[0m Show current user info
15
+
16
+ \x1b[33mPROJECT\x1b[0m
17
+ \x1b[36minit\x1b[0m Initialize new project (creates onflyt.json)
18
+ \x1b[36mdeploy\x1b[0m Deploy project (ZIP upload)
19
+ \x1b[36mprojects\x1b[0m List projects by team
20
+ \x1b[36mdelete\x1b[0m Delete a project
21
+
22
+ \x1b[33mDEPLOYMENTS\x1b[0m
23
+ \x1b[36mlogs\x1b[0m View deployment logs
24
+ \x1b[36mdeployments\x1b[0m List project deployments
25
+ \x1b[36mrollback\x1b[0m Rollback to previous deployment
26
+
27
+ \x1b[33mTEAM & BILLING\x1b[0m
28
+ \x1b[36mteams\x1b[0m List your teams
29
+ \x1b[36mcredits\x1b[0m Check credits balance
30
+
31
+ \x1b[1mGLOBAL OPTIONS\x1b[0m
32
+ \x1b[36m-h, --help\x1b[0m Show this help
33
+ \x1b[36m-v, --version\x1b[0m Show CLI version
34
+ \x1b[36m-t, --team <id>\x1b[0m Target specific team
35
+ \x1b[36m--no-open\x1b[0m Don't auto-open browser on login
36
+
37
+ \x1b[1mINIT OPTIONS\x1b[0m
38
+ \x1b[36m--name <name>\x1b[0m Project name
39
+ \x1b[36m--template <id>\x1b[0m Template (blank, nextjs, react-vite, etc.)
40
+ \x1b[36m--framework <fw>\x1b[0m Framework (nextjs, react, node, etc.)
41
+ \x1b[36m--package-manager <pm>\x1b[0m Package manager (npm, bun, yarn, pnpm)
42
+ \x1b[36m--git\x1b[0m Connect Git repository
43
+ \x1b[36m--no-git\x1b[0m Skip Git connection
44
+ \x1b[36m-y, --yes\x1b[0m Skip all prompts (use defaults)
45
+
46
+ \x1b[1mLOGS OPTIONS\x1b[0m
47
+ \x1b[36m-l, --live\x1b[0m Stream live logs (SSE)
48
+
49
+ \x1b[1mQUICK START\x1b[0m
50
+ $ onflyt login Authenticate
51
+ $ onflyt init Create onflyt.json
52
+ $ onflyt deploy Deploy project
53
+
54
+ \x1b[1mKEYBOARD SHORTCUTS\x1b[0m
55
+ \x1b[36m↑↓\x1b[0m Navigate selection
56
+ \x1b[36mEnter\x1b[0m Select / Confirm
57
+ \x1b[36mEsc\x1b[0m Go back / Cancel
58
+ \x1b[36mQ\x1b[0m Quit
59
+ \x1b[36mCtrl+C\x1b[0m Quit
60
+
61
+ \x1b[1mEXAMPLES\x1b[0m
62
+ $ onflyt deploy --team tm_xxx Deploy to specific team
63
+ $ onflyt logs dep_xxx --live Stream live logs
64
+ $ onflyt init --name myapp --yes Quick init with defaults
65
+
66
+ \x1b[1mDOCS\x1b[0m
67
+ https://docs.onflyt.com/cli
68
+ `);
69
+ }, []);
70
+
71
+ return null;
72
+ };
73
+
74
+ export default Help;