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,515 @@
|
|
|
1
|
+
import { api } from "./api.js";
|
|
2
|
+
import { getConfig, API_URL } from "./config.js";
|
|
3
|
+
import {
|
|
4
|
+
readFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
statSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
} from "fs";
|
|
12
|
+
import { join, relative, basename } from "path";
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import {
|
|
15
|
+
CONTAINER_TIER_SPECS,
|
|
16
|
+
TIER_HOURLY_PRICE,
|
|
17
|
+
ContainerTier,
|
|
18
|
+
} from "../shared";
|
|
19
|
+
import { getLimitsBySpend, TeamLimits, getTierLabel } from "../shared";
|
|
20
|
+
|
|
21
|
+
export interface Team {
|
|
22
|
+
teamId: string;
|
|
23
|
+
role: string;
|
|
24
|
+
team: {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
slug: string;
|
|
28
|
+
plan: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Balance {
|
|
33
|
+
balanceUSD: number;
|
|
34
|
+
balanceFormatted: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProjectConfig {
|
|
38
|
+
name: string;
|
|
39
|
+
framework?: string;
|
|
40
|
+
buildCommand?: string;
|
|
41
|
+
outputDirectory?: string;
|
|
42
|
+
installCommand?: string;
|
|
43
|
+
startCommand?: string;
|
|
44
|
+
gitRepoUrl?: string;
|
|
45
|
+
gitBranch?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface InstanceOption {
|
|
49
|
+
id: ContainerTier;
|
|
50
|
+
label: string;
|
|
51
|
+
cpu: string;
|
|
52
|
+
ram: string;
|
|
53
|
+
disk: string;
|
|
54
|
+
maxReplicas: number;
|
|
55
|
+
hourly: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const INSTANCE_OPTIONS: InstanceOption[] = [
|
|
59
|
+
{
|
|
60
|
+
id: "micro",
|
|
61
|
+
label: "Micro",
|
|
62
|
+
cpu: "0.25",
|
|
63
|
+
ram: "1GB",
|
|
64
|
+
disk: "4GB",
|
|
65
|
+
maxReplicas: 1,
|
|
66
|
+
hourly: "Free",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "lite",
|
|
70
|
+
label: "Lite",
|
|
71
|
+
cpu: "0.25",
|
|
72
|
+
ram: "1GB",
|
|
73
|
+
disk: "4GB",
|
|
74
|
+
maxReplicas: 2,
|
|
75
|
+
hourly: formatPrice(TIER_HOURLY_PRICE.lite),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "standard",
|
|
79
|
+
label: "Standard",
|
|
80
|
+
cpu: "0.5",
|
|
81
|
+
ram: "4GB",
|
|
82
|
+
disk: "8GB",
|
|
83
|
+
maxReplicas: 4,
|
|
84
|
+
hourly: formatPrice(TIER_HOURLY_PRICE.standard),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "pro",
|
|
88
|
+
label: "Pro",
|
|
89
|
+
cpu: "1",
|
|
90
|
+
ram: "6GB",
|
|
91
|
+
disk: "12GB",
|
|
92
|
+
maxReplicas: 4,
|
|
93
|
+
hourly: formatPrice(TIER_HOURLY_PRICE.pro),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "business",
|
|
97
|
+
label: "Business",
|
|
98
|
+
cpu: "2",
|
|
99
|
+
ram: "8GB",
|
|
100
|
+
disk: "16GB",
|
|
101
|
+
maxReplicas: 10,
|
|
102
|
+
hourly: formatPrice(TIER_HOURLY_PRICE.business),
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
function formatPrice(price: number): string {
|
|
107
|
+
if (price === 0) return "Free";
|
|
108
|
+
return `$${price.toFixed(3)}/hr`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
import { FRAMEWORKS, FrameworkConfig } from "../shared";
|
|
112
|
+
|
|
113
|
+
export function isPodProject(framework?: string): boolean {
|
|
114
|
+
if (!framework) return false;
|
|
115
|
+
const config = FRAMEWORKS[framework.toLowerCase()];
|
|
116
|
+
if (!config) return false;
|
|
117
|
+
return config.deploymentType === "container";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getDeploymentType(framework?: string): "static" | "container" {
|
|
121
|
+
if (!framework) return "static";
|
|
122
|
+
const config = FRAMEWORKS[framework.toLowerCase()];
|
|
123
|
+
if (!config) return "static";
|
|
124
|
+
return config.deploymentType;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getProjectConfig(): ProjectConfig | null {
|
|
128
|
+
try {
|
|
129
|
+
const configData = JSON.parse(
|
|
130
|
+
readFileSync(`${process.cwd()}/onflyt.json`, "utf-8"),
|
|
131
|
+
);
|
|
132
|
+
return configData;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function loadTeamsWithBalances(): Promise<{
|
|
139
|
+
teams: Team[];
|
|
140
|
+
balances: Record<string, Balance>;
|
|
141
|
+
}> {
|
|
142
|
+
const config = getConfig();
|
|
143
|
+
api.setToken(config.token!);
|
|
144
|
+
|
|
145
|
+
const meData = await api.get<any>("/auth/me");
|
|
146
|
+
const teams: Team[] = meData.teams || [];
|
|
147
|
+
|
|
148
|
+
const balances: Record<string, Balance> = {};
|
|
149
|
+
await Promise.all(
|
|
150
|
+
teams.map(async (team: Team) => {
|
|
151
|
+
try {
|
|
152
|
+
const balanceData = await api.get<any>(
|
|
153
|
+
`/billing/balance?teamId=${team.team.id}`,
|
|
154
|
+
);
|
|
155
|
+
balances[team.team.id] = balanceData.data || balanceData;
|
|
156
|
+
} catch {
|
|
157
|
+
balances[team.team.id] = {
|
|
158
|
+
balanceUSD: 0,
|
|
159
|
+
balanceFormatted: "$0.00",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return { teams, balances };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function getTeamDetails(teamId: string): Promise<{
|
|
169
|
+
totalLifetimeSpend: number;
|
|
170
|
+
balanceUSD: number;
|
|
171
|
+
}> {
|
|
172
|
+
try {
|
|
173
|
+
const teamData = await api.get<any>(`/teams/${teamId}`);
|
|
174
|
+
const team = teamData.team || teamData;
|
|
175
|
+
return {
|
|
176
|
+
totalLifetimeSpend: team.totalLifetimeSpend || 0,
|
|
177
|
+
balanceUSD: team.balanceUSD || 0,
|
|
178
|
+
};
|
|
179
|
+
} catch {
|
|
180
|
+
return { totalLifetimeSpend: 0, balanceUSD: 0 };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function getTeamLimits(totalSpend: number): TeamLimits {
|
|
185
|
+
return getLimitsBySpend(totalSpend);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function getTeamPlanLabel(totalSpend: number): string {
|
|
189
|
+
return getTierLabel(totalSpend);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function findOrCreateProject(
|
|
193
|
+
teamId: string,
|
|
194
|
+
projectName: string,
|
|
195
|
+
framework?: string,
|
|
196
|
+
gitUrl?: string,
|
|
197
|
+
instanceSize?: ContainerTier,
|
|
198
|
+
maxInstances?: number,
|
|
199
|
+
): Promise<{
|
|
200
|
+
id: string;
|
|
201
|
+
name: string;
|
|
202
|
+
isNew: boolean;
|
|
203
|
+
existingProject?: any;
|
|
204
|
+
}> {
|
|
205
|
+
const projectsRes = await api.get<any>(`/projects/team/${teamId}`);
|
|
206
|
+
const existingProjects = projectsRes.projects || [];
|
|
207
|
+
const existing = existingProjects.find((p: any) => p.name === projectName);
|
|
208
|
+
|
|
209
|
+
if (existing) {
|
|
210
|
+
return {
|
|
211
|
+
id: existing.id,
|
|
212
|
+
name: existing.name,
|
|
213
|
+
isNew: false,
|
|
214
|
+
existingProject: existing,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const createRes = await api.post<any>("/projects", {
|
|
219
|
+
name: projectName,
|
|
220
|
+
framework: framework || "static",
|
|
221
|
+
teamId,
|
|
222
|
+
gitRepoUrl: gitUrl || null,
|
|
223
|
+
instanceSize,
|
|
224
|
+
maxInstances,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const project = createRes.project || createRes;
|
|
228
|
+
return { id: project.id, name: project.name, isNew: true };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function updateProjectSettings(
|
|
232
|
+
projectId: string,
|
|
233
|
+
instanceSize?: ContainerTier,
|
|
234
|
+
maxInstances?: number,
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
const updates: Record<string, any> = {};
|
|
237
|
+
if (instanceSize) updates.instanceSize = instanceSize;
|
|
238
|
+
if (maxInstances) updates.maxInstances = maxInstances;
|
|
239
|
+
|
|
240
|
+
if (Object.keys(updates).length > 0) {
|
|
241
|
+
await api.patch(`/projects/${projectId}`, updates);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function getProjectDetails(projectId: string): Promise<any> {
|
|
246
|
+
const res = await api.get<any>(`/projects/${projectId}`);
|
|
247
|
+
return res.project || res;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function startDeployment(
|
|
251
|
+
projectId: string,
|
|
252
|
+
branch?: string,
|
|
253
|
+
instanceSize?: ContainerTier,
|
|
254
|
+
replicas?: number,
|
|
255
|
+
envVars?: Array<{ key: string; value: string }>,
|
|
256
|
+
): Promise<string> {
|
|
257
|
+
const deployRes = await api.post<any>(`/deployments/${projectId}/deploy`, {
|
|
258
|
+
branch: branch || "main",
|
|
259
|
+
instanceSize,
|
|
260
|
+
replicas,
|
|
261
|
+
envVars,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return deployRes.deploymentId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export type DeploymentStatus =
|
|
268
|
+
| "queued"
|
|
269
|
+
| "building"
|
|
270
|
+
| "provisioning"
|
|
271
|
+
| "deployed"
|
|
272
|
+
| "failed";
|
|
273
|
+
|
|
274
|
+
export interface DeploymentDetails {
|
|
275
|
+
status: DeploymentStatus;
|
|
276
|
+
url?: string;
|
|
277
|
+
previewUrl?: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function getDeploymentStatus(
|
|
281
|
+
deploymentId: string,
|
|
282
|
+
): Promise<DeploymentDetails> {
|
|
283
|
+
const res = await api.get<any>(`/deployments/detail/${deploymentId}`);
|
|
284
|
+
const rawStatus = (
|
|
285
|
+
res.status ||
|
|
286
|
+
res.deployment?.status ||
|
|
287
|
+
"queued"
|
|
288
|
+
).toLowerCase();
|
|
289
|
+
|
|
290
|
+
if (rawStatus === "queued" || rawStatus === "uploaded") {
|
|
291
|
+
return { status: "queued" };
|
|
292
|
+
}
|
|
293
|
+
if (rawStatus === "building") {
|
|
294
|
+
return { status: "building" };
|
|
295
|
+
}
|
|
296
|
+
if (rawStatus === "live" || rawStatus === "running") {
|
|
297
|
+
return {
|
|
298
|
+
status: "deployed",
|
|
299
|
+
url: res.deployment?.url || res.deployment?.previewUrl,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (rawStatus === "failed") {
|
|
303
|
+
return { status: "failed" };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { status: "queued" };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function streamLogs(
|
|
310
|
+
deploymentId: string,
|
|
311
|
+
onLog: (log: string) => void,
|
|
312
|
+
onError: () => void,
|
|
313
|
+
): () => void {
|
|
314
|
+
let cancelled = false;
|
|
315
|
+
|
|
316
|
+
const startStream = async () => {
|
|
317
|
+
try {
|
|
318
|
+
const response = await fetch(
|
|
319
|
+
`${API_URL}/deployments/${deploymentId}/logs/stream`,
|
|
320
|
+
{
|
|
321
|
+
headers: { Authorization: `Bearer ${getConfig().token}` },
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (!response.body) {
|
|
326
|
+
onError();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const reader = response.body.getReader();
|
|
331
|
+
const decoder = new TextDecoder();
|
|
332
|
+
|
|
333
|
+
const read = () => {
|
|
334
|
+
if (cancelled) return;
|
|
335
|
+
|
|
336
|
+
reader.read().then(({ done, value }) => {
|
|
337
|
+
if (done || cancelled) return;
|
|
338
|
+
|
|
339
|
+
const chunk = decoder.decode(value);
|
|
340
|
+
const lines = chunk.split("\n").filter((l) => l.startsWith("data:"));
|
|
341
|
+
|
|
342
|
+
lines.forEach((l) => {
|
|
343
|
+
const log = l.replace("data: ", "").trim();
|
|
344
|
+
if (log) onLog(log);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
read();
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
read();
|
|
352
|
+
} catch {
|
|
353
|
+
if (!cancelled) onError();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
startStream();
|
|
358
|
+
|
|
359
|
+
return () => {
|
|
360
|
+
cancelled = true;
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface StoredLog {
|
|
365
|
+
timestamp: number;
|
|
366
|
+
level: "error" | "warn" | "info" | "debug";
|
|
367
|
+
message: string;
|
|
368
|
+
source?: "build" | "runtime";
|
|
369
|
+
replicaIndex?: number;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface StoredLogsResult {
|
|
373
|
+
logs: StoredLog[];
|
|
374
|
+
total: number;
|
|
375
|
+
hasMore: boolean;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function getStoredLogs(
|
|
379
|
+
deploymentId: string,
|
|
380
|
+
options?: {
|
|
381
|
+
source?: "build" | "runtime";
|
|
382
|
+
replicaIndex?: number;
|
|
383
|
+
limit?: number;
|
|
384
|
+
offset?: number;
|
|
385
|
+
search?: string;
|
|
386
|
+
},
|
|
387
|
+
): Promise<StoredLogsResult> {
|
|
388
|
+
const params = new URLSearchParams();
|
|
389
|
+
if (options?.source) params.set("source", options.source);
|
|
390
|
+
if (options?.replicaIndex !== undefined)
|
|
391
|
+
params.set("replicaIndex", String(options.replicaIndex));
|
|
392
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
393
|
+
if (options?.offset) params.set("offset", String(options.offset));
|
|
394
|
+
if (options?.search) params.set("search", options.search);
|
|
395
|
+
|
|
396
|
+
const queryString = params.toString();
|
|
397
|
+
const endpoint = `/logs/deployments/${deploymentId}/logs${queryString ? `?${queryString}` : ""}`;
|
|
398
|
+
|
|
399
|
+
const res = await api.get<any>(endpoint);
|
|
400
|
+
return {
|
|
401
|
+
logs: res.logs || [],
|
|
402
|
+
total: res.total || 0,
|
|
403
|
+
hasMore: res.hasMore || false,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
408
|
+
"node_modules",
|
|
409
|
+
".git",
|
|
410
|
+
".gitignore",
|
|
411
|
+
".env",
|
|
412
|
+
".env.local",
|
|
413
|
+
".env.*.local",
|
|
414
|
+
"dist",
|
|
415
|
+
"build",
|
|
416
|
+
".next",
|
|
417
|
+
".output",
|
|
418
|
+
".svelte-kit",
|
|
419
|
+
".turbo",
|
|
420
|
+
".vercel",
|
|
421
|
+
".netlify",
|
|
422
|
+
"coverage",
|
|
423
|
+
".nyc_output",
|
|
424
|
+
"*.log",
|
|
425
|
+
"npm-debug.log*",
|
|
426
|
+
"yarn-debug.log*",
|
|
427
|
+
"yarn-error.log*",
|
|
428
|
+
".DS_Store",
|
|
429
|
+
"Thumbs.db",
|
|
430
|
+
".cache",
|
|
431
|
+
".parcel-cache",
|
|
432
|
+
".vite",
|
|
433
|
+
".nuxt",
|
|
434
|
+
".output",
|
|
435
|
+
"__pycache__",
|
|
436
|
+
"*.pyc",
|
|
437
|
+
".pytest_cache",
|
|
438
|
+
"venv",
|
|
439
|
+
".venv",
|
|
440
|
+
"*.egg-info",
|
|
441
|
+
"vendor",
|
|
442
|
+
".idea",
|
|
443
|
+
".vscode",
|
|
444
|
+
"*.swp",
|
|
445
|
+
"*.swo",
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
export function createProjectZip(
|
|
449
|
+
sourceDir: string,
|
|
450
|
+
outputPath: string,
|
|
451
|
+
excludePatterns: string[] = DEFAULT_EXCLUDE_PATTERNS,
|
|
452
|
+
): Promise<string> {
|
|
453
|
+
return new Promise((resolve, reject) => {
|
|
454
|
+
const excludeStr = excludePatterns.map((p) => `-x "${p}"`).join(" ");
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
execSync(
|
|
458
|
+
`cd "${sourceDir}" && zip -r "${outputPath}" . -q ${excludeStr}`,
|
|
459
|
+
{ maxBuffer: 50 * 1024 * 1024 }, // 50MB buffer limit
|
|
460
|
+
);
|
|
461
|
+
resolve(outputPath);
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
reject(new Error(`Failed to create zip: ${error.message}`));
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export async function deployManual(
|
|
469
|
+
projectId: string,
|
|
470
|
+
zipPath: string,
|
|
471
|
+
framework?: string,
|
|
472
|
+
instanceSize?: ContainerTier,
|
|
473
|
+
replicas?: number,
|
|
474
|
+
envVars?: Array<{ key: string; value: string }>,
|
|
475
|
+
onUploadProgress?: (uploaded: number, total: number) => void,
|
|
476
|
+
buildSettings?: {
|
|
477
|
+
buildCommand?: string;
|
|
478
|
+
outputDirectory?: string;
|
|
479
|
+
installCommand?: string;
|
|
480
|
+
},
|
|
481
|
+
): Promise<string> {
|
|
482
|
+
try {
|
|
483
|
+
const uploadRes = await api.uploadFile<any>(
|
|
484
|
+
`/deployments/${projectId}/upload`,
|
|
485
|
+
zipPath,
|
|
486
|
+
"project.zip",
|
|
487
|
+
onUploadProgress,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const updates: any = {};
|
|
491
|
+
if (instanceSize) updates.instanceSize = instanceSize;
|
|
492
|
+
if (replicas) updates.maxInstances = replicas;
|
|
493
|
+
if (buildSettings?.buildCommand)
|
|
494
|
+
updates.buildCommand = buildSettings.buildCommand;
|
|
495
|
+
if (buildSettings?.outputDirectory)
|
|
496
|
+
updates.outputDirectory = buildSettings.outputDirectory;
|
|
497
|
+
if (buildSettings?.installCommand)
|
|
498
|
+
updates.installCommand = buildSettings.installCommand;
|
|
499
|
+
|
|
500
|
+
if (Object.keys(updates).length > 0) {
|
|
501
|
+
await api.patch(`/projects/${projectId}`, updates);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
unlinkSync(zipPath);
|
|
505
|
+
|
|
506
|
+
return uploadRes.deploymentId;
|
|
507
|
+
} catch (error: any) {
|
|
508
|
+
if (existsSync(zipPath)) {
|
|
509
|
+
try {
|
|
510
|
+
unlinkSync(zipPath);
|
|
511
|
+
} catch {}
|
|
512
|
+
}
|
|
513
|
+
throw new Error(`Manual deploy failed: ${error.message}`);
|
|
514
|
+
}
|
|
515
|
+
}
|