onflyt-cli 1.0.1-beta.0 → 1.0.1-beta.2
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.
Potentially problematic release.
This version of onflyt-cli might be problematic. Click here for more details.
- package/dist/index.js +72 -368
- package/onflyt.json +10 -0
- package/package.json +4 -5
- 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 +79 -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 +74 -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 +511 -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/types.d.ts +5 -0
- package/tsconfig.json +17 -0
- package/README.md +0 -338
package/src/index.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, Box } from "ink";
|
|
3
|
+
import meow from "meow";
|
|
4
|
+
import Help from "./commands/help.js";
|
|
5
|
+
import Login from "./commands/login.js";
|
|
6
|
+
import Logout from "./commands/logout.js";
|
|
7
|
+
import WhoAmI from "./commands/whoami.js";
|
|
8
|
+
import ProjectsList from "./commands/projects.js";
|
|
9
|
+
import Init from "./commands/init.js";
|
|
10
|
+
import Credits from "./commands/credits.js";
|
|
11
|
+
import Teams from "./commands/teams.js";
|
|
12
|
+
import Deploy from "./commands/deploy.js";
|
|
13
|
+
import Logs from "./commands/logs.js";
|
|
14
|
+
import Deployments from "./commands/deployments.js";
|
|
15
|
+
import Delete from "./commands/delete.js";
|
|
16
|
+
import Rollback from "./commands/rollback.js";
|
|
17
|
+
|
|
18
|
+
const cli = meow(
|
|
19
|
+
`
|
|
20
|
+
Onflyt CLI v1.0.1-beta.1
|
|
21
|
+
|
|
22
|
+
Usage
|
|
23
|
+
$ onflyt <command>
|
|
24
|
+
|
|
25
|
+
Commands
|
|
26
|
+
login Authenticate with Onflyt
|
|
27
|
+
init Initialize a new project
|
|
28
|
+
deploy Deploy your project
|
|
29
|
+
projects Manage projects
|
|
30
|
+
teams List your teams
|
|
31
|
+
logs View deployment logs
|
|
32
|
+
deployments List project deployments
|
|
33
|
+
delete Delete a project
|
|
34
|
+
rollback Rollback to previous deployment
|
|
35
|
+
whoami Show current user
|
|
36
|
+
logout Sign out
|
|
37
|
+
credits Check credits balance
|
|
38
|
+
|
|
39
|
+
Init Options
|
|
40
|
+
--name <name> Project name
|
|
41
|
+
--template <id> Template ID (blank, nextjs, react-vite, etc.)
|
|
42
|
+
--framework <fw> Framework (nextjs, react, node, etc.)
|
|
43
|
+
--package-manager Package manager (npm, bun, yarn, pnpm)
|
|
44
|
+
--git / --no-git Connect or skip git
|
|
45
|
+
--yes Skip all prompts (use defaults)
|
|
46
|
+
|
|
47
|
+
Logs Options
|
|
48
|
+
--live, -l Stream live logs (SSE)
|
|
49
|
+
|
|
50
|
+
Options
|
|
51
|
+
--version, -v Show CLI version
|
|
52
|
+
--help, -h Show this help
|
|
53
|
+
--debug Enable debug mode
|
|
54
|
+
--no-open Don't open browser automatically
|
|
55
|
+
`,
|
|
56
|
+
{
|
|
57
|
+
importMeta: import.meta,
|
|
58
|
+
autoHelp: false,
|
|
59
|
+
flags: {
|
|
60
|
+
version: { type: "boolean", shortFlag: "v" },
|
|
61
|
+
help: { type: "boolean", shortFlag: "h" },
|
|
62
|
+
team: { type: "string", shortFlag: "t" },
|
|
63
|
+
noOpen: { type: "boolean" },
|
|
64
|
+
name: { type: "string" },
|
|
65
|
+
template: { type: "string" },
|
|
66
|
+
framework: { type: "string" },
|
|
67
|
+
packageManager: { type: "string" },
|
|
68
|
+
git: { type: "boolean" },
|
|
69
|
+
yes: { type: "boolean", shortFlag: "y" },
|
|
70
|
+
live: { type: "boolean", shortFlag: "l" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
cli.flags.help ||
|
|
77
|
+
cli.input[0] === "help" ||
|
|
78
|
+
cli.input[0] === "--help" ||
|
|
79
|
+
cli.input[0] === "-h"
|
|
80
|
+
) {
|
|
81
|
+
render(<Help />, { exitOnCtrlC: false });
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
import { render } from "ink";
|
|
86
|
+
import App from "./App.js";
|
|
87
|
+
|
|
88
|
+
const command = cli.input[0];
|
|
89
|
+
|
|
90
|
+
const CommandRouter = () => {
|
|
91
|
+
switch (command) {
|
|
92
|
+
case "login":
|
|
93
|
+
return <Login openBrowser={!cli.flags.noOpen} />;
|
|
94
|
+
case "logout":
|
|
95
|
+
return <Logout />;
|
|
96
|
+
case "whoami":
|
|
97
|
+
return <WhoAmI />;
|
|
98
|
+
case "projects":
|
|
99
|
+
return <ProjectsList />;
|
|
100
|
+
case "init":
|
|
101
|
+
return (
|
|
102
|
+
<Init
|
|
103
|
+
name={cli.flags.name}
|
|
104
|
+
template={cli.flags.template}
|
|
105
|
+
framework={cli.flags.framework}
|
|
106
|
+
packageManager={cli.flags.packageManager}
|
|
107
|
+
git={cli.flags.git}
|
|
108
|
+
yes={cli.flags.yes}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
case "credits":
|
|
112
|
+
return <Credits />;
|
|
113
|
+
case "teams":
|
|
114
|
+
return <Teams />;
|
|
115
|
+
case "deploy":
|
|
116
|
+
return <Deploy teamFlag={cli.flags.team} />;
|
|
117
|
+
case "logs":
|
|
118
|
+
return <Logs deploymentId={cli.input[1]} live={cli.flags.live} />;
|
|
119
|
+
case "deployments":
|
|
120
|
+
return <Deployments projectName={cli.input[1]} />;
|
|
121
|
+
case "delete":
|
|
122
|
+
return <Delete projectId={cli.input[1]} />;
|
|
123
|
+
case "rollback":
|
|
124
|
+
return <Rollback deploymentId={cli.input[1]} />;
|
|
125
|
+
default:
|
|
126
|
+
return <App />;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
render(<CommandRouter />, { exitOnCtrlC: false });
|
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { API_URL, getConfig } from "./config.js";
|
|
2
|
+
|
|
3
|
+
export interface ApiError {
|
|
4
|
+
success: false;
|
|
5
|
+
error: string;
|
|
6
|
+
code?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ApiSuccess<T> {
|
|
10
|
+
success: true;
|
|
11
|
+
data: T;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ApiResponse<T> = ApiError | ApiSuccess<T>;
|
|
15
|
+
|
|
16
|
+
export class ApiClient {
|
|
17
|
+
private token: string | null;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
this.token = config.token || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setToken(token: string) {
|
|
25
|
+
this.token = token;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async request<T>(
|
|
29
|
+
endpoint: string,
|
|
30
|
+
options: RequestInit = {},
|
|
31
|
+
): Promise<T> {
|
|
32
|
+
const headers: Record<string, string> = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
...((options.headers as Record<string, string>) || {}),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (this.token) {
|
|
38
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
42
|
+
...options,
|
|
43
|
+
headers,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
let errorMessage = `Request failed with status ${response.status}`;
|
|
50
|
+
try {
|
|
51
|
+
const data = JSON.parse(text);
|
|
52
|
+
errorMessage = data.error || errorMessage;
|
|
53
|
+
} catch {}
|
|
54
|
+
throw new ApiException(errorMessage, response.status);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!text) {
|
|
58
|
+
return {} as T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(text);
|
|
63
|
+
if (data.success === false) {
|
|
64
|
+
throw new ApiException(
|
|
65
|
+
data.error || "Request failed",
|
|
66
|
+
response.status,
|
|
67
|
+
data.code,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (data.success === true) {
|
|
71
|
+
return data.data || data;
|
|
72
|
+
}
|
|
73
|
+
return data;
|
|
74
|
+
} catch {
|
|
75
|
+
return text as any;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async get<T>(endpoint: string): Promise<T> {
|
|
80
|
+
return this.request<T>(endpoint, { method: "GET" });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async post<T>(endpoint: string, body?: unknown): Promise<T> {
|
|
84
|
+
return this.request<T>(endpoint, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async patch<T>(endpoint: string, body?: unknown): Promise<T> {
|
|
91
|
+
return this.request<T>(endpoint, {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async delete<T>(endpoint: string): Promise<T> {
|
|
98
|
+
return this.request<T>(endpoint, { method: "DELETE" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async uploadFile<T>(
|
|
102
|
+
endpoint: string,
|
|
103
|
+
filePath: string,
|
|
104
|
+
filename: string,
|
|
105
|
+
onProgress?: (uploaded: number, total: number) => void,
|
|
106
|
+
): Promise<T> {
|
|
107
|
+
const { readFileSync } = await import("fs");
|
|
108
|
+
|
|
109
|
+
const fileBuffer = readFileSync(filePath);
|
|
110
|
+
const totalSize = fileBuffer.length;
|
|
111
|
+
|
|
112
|
+
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: fileBuffer,
|
|
115
|
+
headers: {
|
|
116
|
+
"Content-Type": "application/zip",
|
|
117
|
+
"Content-Length": String(totalSize),
|
|
118
|
+
Authorization: this.token ? `Bearer ${this.token}` : "",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const text = await response.text();
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
let errorMessage = `Upload failed with status ${response.status}`;
|
|
126
|
+
try {
|
|
127
|
+
const data = JSON.parse(text);
|
|
128
|
+
errorMessage = data.error || errorMessage;
|
|
129
|
+
} catch {}
|
|
130
|
+
throw new ApiException(errorMessage, response.status);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(text);
|
|
135
|
+
} catch {
|
|
136
|
+
return text as any;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export class ApiException extends Error {
|
|
142
|
+
constructor(
|
|
143
|
+
message: string,
|
|
144
|
+
public status: number,
|
|
145
|
+
public code?: string,
|
|
146
|
+
) {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = "ApiException";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const api = new ApiClient();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".onflyt");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
const PROJECT_CONFIG_FILE = "onflyt.json";
|
|
8
|
+
|
|
9
|
+
export const API_URL = process.env.ONFLYT_API_URL || "";
|
|
10
|
+
|
|
11
|
+
export interface Config {
|
|
12
|
+
token?: string;
|
|
13
|
+
user?: {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name: string;
|
|
17
|
+
avatar?: string;
|
|
18
|
+
};
|
|
19
|
+
defaultTeam?: string;
|
|
20
|
+
lastLogin?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getConfig(): Config {
|
|
24
|
+
try {
|
|
25
|
+
if (existsSync(CONFIG_FILE)) {
|
|
26
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// Return empty config if file doesn't exist or is invalid
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function saveConfig(config: Config): void {
|
|
35
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
36
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function clearConfig(): void {
|
|
40
|
+
if (existsSync(CONFIG_FILE)) {
|
|
41
|
+
const config = getConfig();
|
|
42
|
+
saveConfig({});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isLoggedIn(): boolean {
|
|
47
|
+
const config = getConfig();
|
|
48
|
+
return !!config.token;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ProjectConfig {
|
|
52
|
+
id?: string;
|
|
53
|
+
name: string;
|
|
54
|
+
teamId?: string;
|
|
55
|
+
framework: string;
|
|
56
|
+
buildCommand?: string;
|
|
57
|
+
outputDirectory?: string;
|
|
58
|
+
installCommand?: string;
|
|
59
|
+
startCommand?: string;
|
|
60
|
+
gitRepoUrl?: string;
|
|
61
|
+
gitBranch?: string;
|
|
62
|
+
gitRepoId?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getProjectConfig(
|
|
66
|
+
cwd: string = process.cwd(),
|
|
67
|
+
): ProjectConfig | null {
|
|
68
|
+
try {
|
|
69
|
+
const configPath = join(cwd, PROJECT_CONFIG_FILE);
|
|
70
|
+
if (existsSync(configPath)) {
|
|
71
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Return null if file doesn't exist or is invalid
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function saveProjectConfig(
|
|
80
|
+
config: ProjectConfig,
|
|
81
|
+
cwd: string = process.cwd(),
|
|
82
|
+
): void {
|
|
83
|
+
const configPath = join(cwd, PROJECT_CONFIG_FILE);
|
|
84
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function hasProjectConfig(cwd: string = process.cwd()): boolean {
|
|
88
|
+
const configPath = join(cwd, PROJECT_CONFIG_FILE);
|
|
89
|
+
return existsSync(configPath);
|
|
90
|
+
}
|