@labring/devbox-sdk 1.1.0
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/README.md +478 -0
- package/dist/index.cjs +2691 -0
- package/dist/index.d.cts +875 -0
- package/dist/index.d.ts +875 -0
- package/dist/index.mjs +2665 -0
- package/package.json +76 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2691 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var yaml = require('js-yaml');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
10
|
+
|
|
11
|
+
// src/core/constants.ts
|
|
12
|
+
var DEFAULT_CONFIG = {
|
|
13
|
+
/** Default base URL for Devbox API */
|
|
14
|
+
BASE_URL: "https://devbox.usw.sealos.io/v1",
|
|
15
|
+
/** Default HTTP server port for containers */
|
|
16
|
+
CONTAINER_HTTP_PORT: 3e3,
|
|
17
|
+
/** Default mock server configuration */
|
|
18
|
+
MOCK_SERVER: {
|
|
19
|
+
DEFAULT_URL: "http://localhost:9757",
|
|
20
|
+
ENV_VAR: "MOCK_SERVER_URL"
|
|
21
|
+
},
|
|
22
|
+
/** Default HTTP client settings */
|
|
23
|
+
HTTP_CLIENT: {
|
|
24
|
+
TIMEOUT: 3e4,
|
|
25
|
+
// 30 seconds
|
|
26
|
+
RETRIES: 3
|
|
27
|
+
},
|
|
28
|
+
/** File operation limits */
|
|
29
|
+
FILE_LIMITS: {
|
|
30
|
+
MAX_FILE_SIZE: 100 * 1024 * 1024,
|
|
31
|
+
// 100MB
|
|
32
|
+
MAX_BATCH_SIZE: 50,
|
|
33
|
+
// maximum files per batch
|
|
34
|
+
CHUNK_SIZE: 1024 * 1024
|
|
35
|
+
// 1MB chunks for streaming
|
|
36
|
+
},
|
|
37
|
+
/** Performance targets */
|
|
38
|
+
PERFORMANCE: {
|
|
39
|
+
SMALL_FILE_LATENCY_MS: 50,
|
|
40
|
+
// <50ms for files <1MB
|
|
41
|
+
LARGE_FILE_THROUGHPUT_MBPS: 15,
|
|
42
|
+
// >15MB/s for large files
|
|
43
|
+
CONNECTION_REUSE_RATE: 0.98,
|
|
44
|
+
// >98% connection reuse
|
|
45
|
+
STARTUP_TIME_MS: 100
|
|
46
|
+
// <100ms Bun server startup
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var API_ENDPOINTS = {
|
|
50
|
+
/** Devbox management endpoints */
|
|
51
|
+
DEVBOX: {
|
|
52
|
+
LIST: "/api/v1/devbox",
|
|
53
|
+
CREATE: "/api/v1/devbox",
|
|
54
|
+
GET: "/api/v1/devbox/{name}",
|
|
55
|
+
UPDATE: "/api/v1/devbox/{name}",
|
|
56
|
+
DELETE: "/api/v1/devbox/{name}/delete",
|
|
57
|
+
START: "/api/v1/devbox/{name}/start",
|
|
58
|
+
PAUSE: "/api/v1/devbox/{name}/pause",
|
|
59
|
+
RESTART: "/api/v1/devbox/{name}/restart",
|
|
60
|
+
SHUTDOWN: "/api/v1/devbox/{name}/shutdown",
|
|
61
|
+
MONITOR: "/api/v1/devbox/{name}/monitor",
|
|
62
|
+
TEMPLATES: "/api/v1/devbox/templates",
|
|
63
|
+
PORTS: "/api/v1/devbox/{name}/ports",
|
|
64
|
+
AUTOSTART: "/api/v1/devbox/{name}/autostart",
|
|
65
|
+
RELEASE: {
|
|
66
|
+
LIST: "/api/v1/devbox/{name}/release",
|
|
67
|
+
CREATE: "/api/v1/devbox/{name}/release",
|
|
68
|
+
DELETE: "/api/v1/devbox/{name}/release/{tag}",
|
|
69
|
+
DEPLOY: "/api/v1/devbox/{name}/release/{tag}/deploy"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
/** Container server endpoints */
|
|
73
|
+
CONTAINER: {
|
|
74
|
+
HEALTH: "/health",
|
|
75
|
+
FILES: {
|
|
76
|
+
WRITE: "/api/v1/files/write",
|
|
77
|
+
READ: "/api/v1/files/read",
|
|
78
|
+
LIST: "/api/v1/files/list",
|
|
79
|
+
DELETE: "/api/v1/files/delete",
|
|
80
|
+
MOVE: "/api/v1/files/move",
|
|
81
|
+
RENAME: "/api/v1/files/rename",
|
|
82
|
+
DOWNLOAD: "/api/v1/files/download",
|
|
83
|
+
BATCH_UPLOAD: "/api/v1/files/batch-upload",
|
|
84
|
+
BATCH_DOWNLOAD: "/api/v1/files/batch-download",
|
|
85
|
+
SEARCH: "/api/v1/files/search",
|
|
86
|
+
FIND: "/api/v1/files/find",
|
|
87
|
+
REPLACE: "/api/v1/files/replace"
|
|
88
|
+
},
|
|
89
|
+
PROCESS: {
|
|
90
|
+
EXEC: "/api/v1/process/exec",
|
|
91
|
+
EXEC_SYNC: "/api/v1/process/exec-sync",
|
|
92
|
+
EXEC_SYNC_STREAM: "/api/v1/process/sync-stream",
|
|
93
|
+
LIST: "/api/v1/process/list",
|
|
94
|
+
STATUS: "/api/v1/process/{process_id}/status",
|
|
95
|
+
KILL: "/api/v1/process/{process_id}/kill",
|
|
96
|
+
LOGS: "/api/v1/process/{process_id}/logs"
|
|
97
|
+
},
|
|
98
|
+
PORTS: "/api/v1/ports"
|
|
99
|
+
// Temporarily disabled - ws module removed
|
|
100
|
+
// WEBSOCKET: '/ws',
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var ERROR_CODES = {
|
|
104
|
+
/** Authentication errors */
|
|
105
|
+
AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED",
|
|
106
|
+
INVALID_KUBECONFIG: "INVALID_KUBECONFIG",
|
|
107
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
108
|
+
INVALID_TOKEN: "INVALID_TOKEN",
|
|
109
|
+
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
110
|
+
INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS",
|
|
111
|
+
/** Connection errors */
|
|
112
|
+
CONNECTION_FAILED: "CONNECTION_FAILED",
|
|
113
|
+
CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
|
|
114
|
+
/** Devbox errors */
|
|
115
|
+
DEVBOX_NOT_FOUND: "DEVBOX_NOT_FOUND",
|
|
116
|
+
DEVBOX_NOT_READY: "DEVBOX_NOT_READY",
|
|
117
|
+
DEVBOX_CREATION_FAILED: "DEVBOX_CREATION_FAILED",
|
|
118
|
+
DEVBOX_OPERATION_FAILED: "DEVBOX_OPERATION_FAILED",
|
|
119
|
+
/** Validation errors */
|
|
120
|
+
INVALID_REQUEST: "INVALID_REQUEST",
|
|
121
|
+
MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
|
|
122
|
+
INVALID_FIELD_VALUE: "INVALID_FIELD_VALUE",
|
|
123
|
+
INVALID_JSON_FORMAT: "INVALID_JSON_FORMAT",
|
|
124
|
+
INVALID_PATH: "INVALID_PATH",
|
|
125
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
126
|
+
/** Resource errors */
|
|
127
|
+
NOT_FOUND: "NOT_FOUND",
|
|
128
|
+
PROCESS_NOT_FOUND: "PROCESS_NOT_FOUND",
|
|
129
|
+
SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
|
|
130
|
+
FILE_NOT_FOUND: "FILE_NOT_FOUND",
|
|
131
|
+
DIRECTORY_NOT_FOUND: "DIRECTORY_NOT_FOUND",
|
|
132
|
+
/** State errors */
|
|
133
|
+
CONFLICT: "CONFLICT",
|
|
134
|
+
PROCESS_ALREADY_RUNNING: "PROCESS_ALREADY_RUNNING",
|
|
135
|
+
PROCESS_NOT_RUNNING: "PROCESS_NOT_RUNNING",
|
|
136
|
+
SESSION_INACTIVE: "SESSION_INACTIVE",
|
|
137
|
+
RESOURCE_LOCKED: "RESOURCE_LOCKED",
|
|
138
|
+
PROCESS_ALREADY_TERMINATED: "PROCESS_ALREADY_TERMINATED",
|
|
139
|
+
/** Operation errors */
|
|
140
|
+
OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
|
|
141
|
+
OPERATION_FAILED: "OPERATION_FAILED",
|
|
142
|
+
EXECUTION_FAILED: "EXECUTION_FAILED",
|
|
143
|
+
SIGNAL_FAILED: "SIGNAL_FAILED",
|
|
144
|
+
/** File operation errors */
|
|
145
|
+
FILE_OPERATION_ERROR: "FILE_OPERATION_ERROR",
|
|
146
|
+
FILE_TOO_LARGE: "FILE_TOO_LARGE",
|
|
147
|
+
FILE_TRANSFER_FAILED: "FILE_TRANSFER_FAILED",
|
|
148
|
+
PATH_TRAVERSAL_DETECTED: "PATH_TRAVERSAL_DETECTED",
|
|
149
|
+
DIRECTORY_NOT_EMPTY: "DIRECTORY_NOT_EMPTY",
|
|
150
|
+
DISK_FULL: "DISK_FULL",
|
|
151
|
+
FILE_LOCKED: "FILE_LOCKED",
|
|
152
|
+
/** Process errors */
|
|
153
|
+
PROCESS_START_FAILED: "PROCESS_START_FAILED",
|
|
154
|
+
INVALID_SIGNAL: "INVALID_SIGNAL",
|
|
155
|
+
PROCESS_LIMIT_EXCEEDED: "PROCESS_LIMIT_EXCEEDED",
|
|
156
|
+
/** Session errors */
|
|
157
|
+
SESSION_CREATION_FAILED: "SESSION_CREATION_FAILED",
|
|
158
|
+
SESSION_LIMIT_EXCEEDED: "SESSION_LIMIT_EXCEEDED",
|
|
159
|
+
SESSION_TIMEOUT: "SESSION_TIMEOUT",
|
|
160
|
+
SHELL_NOT_FOUND: "SHELL_NOT_FOUND",
|
|
161
|
+
/** WebSocket errors */
|
|
162
|
+
// Temporarily disabled - ws module removed
|
|
163
|
+
// WEBSOCKET_CONNECTION_FAILED: 'WEBSOCKET_CONNECTION_FAILED',
|
|
164
|
+
// INVALID_SUBSCRIPTION: 'INVALID_SUBSCRIPTION',
|
|
165
|
+
// TARGET_NOT_SUBSCRIBABLE: 'TARGET_NOT_SUBSCRIBABLE',
|
|
166
|
+
/** Server errors */
|
|
167
|
+
SERVER_UNAVAILABLE: "SERVER_UNAVAILABLE",
|
|
168
|
+
HEALTH_CHECK_FAILED: "HEALTH_CHECK_FAILED",
|
|
169
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
170
|
+
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
|
|
171
|
+
MAINTENANCE_MODE: "MAINTENANCE_MODE"
|
|
172
|
+
};
|
|
173
|
+
var HTTP_STATUS = {
|
|
174
|
+
OK: 200,
|
|
175
|
+
CREATED: 201,
|
|
176
|
+
ACCEPTED: 202,
|
|
177
|
+
NO_CONTENT: 204,
|
|
178
|
+
BAD_REQUEST: 400,
|
|
179
|
+
UNAUTHORIZED: 401,
|
|
180
|
+
FORBIDDEN: 403,
|
|
181
|
+
NOT_FOUND: 404,
|
|
182
|
+
METHOD_NOT_ALLOWED: 405,
|
|
183
|
+
TIMEOUT: 408,
|
|
184
|
+
CONFLICT: 409,
|
|
185
|
+
GONE: 410,
|
|
186
|
+
TOO_MANY_REQUESTS: 429,
|
|
187
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
188
|
+
BAD_GATEWAY: 502,
|
|
189
|
+
SERVICE_UNAVAILABLE: 503,
|
|
190
|
+
GATEWAY_TIMEOUT: 504
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/utils/error.ts
|
|
194
|
+
var DevboxSDKError = class extends Error {
|
|
195
|
+
constructor(message, code, context) {
|
|
196
|
+
super(message);
|
|
197
|
+
this.code = code;
|
|
198
|
+
this.context = context;
|
|
199
|
+
this.name = "DevboxSDKError";
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var AuthenticationError = class extends DevboxSDKError {
|
|
203
|
+
constructor(message, context) {
|
|
204
|
+
super(message, "AUTHENTICATION_FAILED", context);
|
|
205
|
+
this.name = "AuthenticationError";
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var ConnectionError = class extends DevboxSDKError {
|
|
209
|
+
constructor(message, context) {
|
|
210
|
+
super(message, "CONNECTION_FAILED", context);
|
|
211
|
+
this.name = "ConnectionError";
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var FileOperationError = class extends DevboxSDKError {
|
|
215
|
+
constructor(message, context, code = ERROR_CODES.FILE_TRANSFER_FAILED) {
|
|
216
|
+
super(message, code, context);
|
|
217
|
+
this.name = "FileOperationError";
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var DevboxNotFoundError = class extends DevboxSDKError {
|
|
221
|
+
constructor(devboxName, context) {
|
|
222
|
+
super(`Devbox '${devboxName}' not found`, "DEVBOX_NOT_FOUND", context);
|
|
223
|
+
this.name = "DevboxNotFoundError";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var DevboxNotReadyError = class extends DevboxSDKError {
|
|
227
|
+
constructor(devboxName, currentStatus, context) {
|
|
228
|
+
const statusInfo = currentStatus ? ` (current status: ${currentStatus})` : "";
|
|
229
|
+
super(
|
|
230
|
+
`Devbox '${devboxName}' is not ready yet${statusInfo}. The devbox may still be starting. Please wait a moment and try again, or use 'await devbox.waitForReady()' to wait until it's fully initialized.`,
|
|
231
|
+
"DEVBOX_NOT_READY",
|
|
232
|
+
context
|
|
233
|
+
);
|
|
234
|
+
this.name = "DevboxNotReadyError";
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var ValidationError = class extends DevboxSDKError {
|
|
238
|
+
constructor(message, context) {
|
|
239
|
+
super(message, "VALIDATION_ERROR", context);
|
|
240
|
+
this.name = "ValidationError";
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
function mapServerStatusToErrorCode(status) {
|
|
244
|
+
switch (status) {
|
|
245
|
+
case 1404:
|
|
246
|
+
return ERROR_CODES.FILE_NOT_FOUND;
|
|
247
|
+
case 1400:
|
|
248
|
+
return ERROR_CODES.VALIDATION_ERROR;
|
|
249
|
+
case 1401:
|
|
250
|
+
return ERROR_CODES.UNAUTHORIZED;
|
|
251
|
+
case 1403:
|
|
252
|
+
return ERROR_CODES.INSUFFICIENT_PERMISSIONS;
|
|
253
|
+
case 1422:
|
|
254
|
+
return ERROR_CODES.INVALID_REQUEST;
|
|
255
|
+
case 1500:
|
|
256
|
+
return ERROR_CODES.INTERNAL_ERROR;
|
|
257
|
+
case 1409:
|
|
258
|
+
return ERROR_CODES.CONFLICT;
|
|
259
|
+
case 1600:
|
|
260
|
+
return ERROR_CODES.OPERATION_FAILED;
|
|
261
|
+
case 500:
|
|
262
|
+
return ERROR_CODES.INTERNAL_ERROR;
|
|
263
|
+
default:
|
|
264
|
+
return ERROR_CODES.OPERATION_FAILED;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function parseServerResponse(jsonData) {
|
|
268
|
+
if (jsonData.status !== void 0 && jsonData.status !== 0) {
|
|
269
|
+
const errorCode = mapServerStatusToErrorCode(jsonData.status);
|
|
270
|
+
const errorMessage = jsonData.message || "Unknown server error";
|
|
271
|
+
throw createErrorFromServerResponse(errorMessage, errorCode, void 0);
|
|
272
|
+
}
|
|
273
|
+
return jsonData.Data !== void 0 ? jsonData.Data : jsonData;
|
|
274
|
+
}
|
|
275
|
+
function createErrorFromServerResponse(error, code, timestamp) {
|
|
276
|
+
const errorContext = { timestamp, serverErrorCode: code };
|
|
277
|
+
switch (code) {
|
|
278
|
+
case ERROR_CODES.UNAUTHORIZED:
|
|
279
|
+
case ERROR_CODES.INVALID_TOKEN:
|
|
280
|
+
case ERROR_CODES.TOKEN_EXPIRED:
|
|
281
|
+
case ERROR_CODES.INSUFFICIENT_PERMISSIONS:
|
|
282
|
+
return new AuthenticationError(error, errorContext);
|
|
283
|
+
case ERROR_CODES.FILE_NOT_FOUND:
|
|
284
|
+
case ERROR_CODES.DIRECTORY_NOT_FOUND:
|
|
285
|
+
case ERROR_CODES.FILE_OPERATION_ERROR:
|
|
286
|
+
case ERROR_CODES.FILE_TOO_LARGE:
|
|
287
|
+
case ERROR_CODES.FILE_LOCKED:
|
|
288
|
+
case ERROR_CODES.DIRECTORY_NOT_EMPTY:
|
|
289
|
+
case ERROR_CODES.DISK_FULL:
|
|
290
|
+
return new FileOperationError(error, errorContext, code);
|
|
291
|
+
case ERROR_CODES.INVALID_REQUEST:
|
|
292
|
+
case ERROR_CODES.MISSING_REQUIRED_FIELD:
|
|
293
|
+
case ERROR_CODES.INVALID_FIELD_VALUE:
|
|
294
|
+
case ERROR_CODES.INVALID_JSON_FORMAT:
|
|
295
|
+
case ERROR_CODES.INVALID_PATH:
|
|
296
|
+
case ERROR_CODES.INVALID_SIGNAL:
|
|
297
|
+
return new ValidationError(error, errorContext);
|
|
298
|
+
case ERROR_CODES.DEVBOX_NOT_FOUND:
|
|
299
|
+
case ERROR_CODES.PROCESS_NOT_FOUND:
|
|
300
|
+
case ERROR_CODES.SESSION_NOT_FOUND:
|
|
301
|
+
case ERROR_CODES.NOT_FOUND:
|
|
302
|
+
if (code === ERROR_CODES.DEVBOX_NOT_FOUND) {
|
|
303
|
+
const devboxNameMatch = error.match(/Devbox '([^']+)'/i) || error.match(/devbox[:\s]+([^\s]+)/i);
|
|
304
|
+
const devboxName = devboxNameMatch?.[1] ?? "unknown";
|
|
305
|
+
return new DevboxNotFoundError(devboxName, errorContext);
|
|
306
|
+
}
|
|
307
|
+
return new DevboxSDKError(error, code || ERROR_CODES.INTERNAL_ERROR, errorContext);
|
|
308
|
+
default:
|
|
309
|
+
return new DevboxSDKError(error, code, errorContext);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/utils/logger.ts
|
|
314
|
+
var LOG_LEVEL_PRIORITY = {
|
|
315
|
+
["INFO" /* INFO */]: 1,
|
|
316
|
+
["WARN" /* WARN */]: 2,
|
|
317
|
+
["ERROR" /* ERROR */]: 3,
|
|
318
|
+
["SILENT" /* SILENT */]: Number.POSITIVE_INFINITY
|
|
319
|
+
// Highest priority, suppresses all logs
|
|
320
|
+
};
|
|
321
|
+
function getLogLevelFromEnv() {
|
|
322
|
+
const envLevel = process.env.LOG_LEVEL?.toUpperCase();
|
|
323
|
+
if (envLevel === "INFO") {
|
|
324
|
+
return "INFO" /* INFO */;
|
|
325
|
+
}
|
|
326
|
+
if (envLevel === "WARN" || envLevel === "WARNING") {
|
|
327
|
+
return "WARN" /* WARN */;
|
|
328
|
+
}
|
|
329
|
+
if (envLevel === "ERROR") {
|
|
330
|
+
return "ERROR" /* ERROR */;
|
|
331
|
+
}
|
|
332
|
+
return "SILENT" /* SILENT */;
|
|
333
|
+
}
|
|
334
|
+
var Logger = class {
|
|
335
|
+
currentLevel;
|
|
336
|
+
constructor() {
|
|
337
|
+
this.currentLevel = getLogLevelFromEnv();
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Check if a log level should be output
|
|
341
|
+
*/
|
|
342
|
+
shouldLog(level) {
|
|
343
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.currentLevel];
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Log info message
|
|
347
|
+
*/
|
|
348
|
+
info(message, ...args) {
|
|
349
|
+
if (this.shouldLog("INFO" /* INFO */)) {
|
|
350
|
+
console.log(`[INFO] ${message}`, ...args);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Log warning message
|
|
355
|
+
*/
|
|
356
|
+
warn(message, ...args) {
|
|
357
|
+
if (this.shouldLog("WARN" /* WARN */)) {
|
|
358
|
+
console.warn(`[WARN] ${message}`, ...args);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Log error message
|
|
363
|
+
*/
|
|
364
|
+
error(message, ...args) {
|
|
365
|
+
if (this.shouldLog("ERROR" /* ERROR */)) {
|
|
366
|
+
console.error(`[ERROR] ${message}`, ...args);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
var logger = new Logger();
|
|
371
|
+
function parseKubeconfigServerUrl(kubeconfig) {
|
|
372
|
+
if (!kubeconfig || typeof kubeconfig !== "string") {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const config = yaml__default.default.load(kubeconfig);
|
|
377
|
+
if (!config) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
const currentContextName = config["current-context"] || config.currentContext;
|
|
381
|
+
if (!currentContextName) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
const contexts = config.contexts || [];
|
|
385
|
+
const currentContext = contexts.find(
|
|
386
|
+
(ctx) => ctx.name === currentContextName
|
|
387
|
+
);
|
|
388
|
+
if (!currentContext || !currentContext.context) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
const clusterName = currentContext.context.cluster;
|
|
392
|
+
if (!clusterName) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const clusters = config.clusters || [];
|
|
396
|
+
const cluster = clusters.find((cl) => cl.name === clusterName);
|
|
397
|
+
if (!cluster || !cluster.cluster) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const serverUrl = cluster.cluster.server;
|
|
401
|
+
if (!serverUrl || typeof serverUrl !== "string") {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
const url = new URL(serverUrl);
|
|
406
|
+
url.hostname = `devbox.${url.hostname}`;
|
|
407
|
+
url.port = "";
|
|
408
|
+
url.pathname = "";
|
|
409
|
+
let result = url.toString();
|
|
410
|
+
if (result.endsWith("/")) {
|
|
411
|
+
result = result.slice(0, -1);
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
} catch (_urlError) {
|
|
415
|
+
return serverUrl;
|
|
416
|
+
}
|
|
417
|
+
} catch (_error) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/api/auth.ts
|
|
423
|
+
var KubeconfigAuthenticator = class {
|
|
424
|
+
token;
|
|
425
|
+
constructor(kubeconfig) {
|
|
426
|
+
if (!kubeconfig || typeof kubeconfig !== "string") {
|
|
427
|
+
throw new DevboxSDKError(
|
|
428
|
+
"kubeconfig is required and must be a string",
|
|
429
|
+
ERROR_CODES.INVALID_KUBECONFIG
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
this.token = encodeURIComponent(kubeconfig);
|
|
433
|
+
}
|
|
434
|
+
getAuthHeaders() {
|
|
435
|
+
return {
|
|
436
|
+
Authorization: this.token
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// src/api/endpoints.ts
|
|
442
|
+
var APIEndpoints = class {
|
|
443
|
+
baseUrl;
|
|
444
|
+
constructor(baseUrl = "https://devbox.usw.sealos.io/v1") {
|
|
445
|
+
this.baseUrl = baseUrl;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get the base URL
|
|
449
|
+
*/
|
|
450
|
+
getBaseUrl() {
|
|
451
|
+
return this.baseUrl;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Construct URL with parameters
|
|
455
|
+
*/
|
|
456
|
+
constructUrl(template, params = {}) {
|
|
457
|
+
let url = template;
|
|
458
|
+
for (const [key, value] of Object.entries(params)) {
|
|
459
|
+
url = url.replace(`{${key}}`, encodeURIComponent(value));
|
|
460
|
+
}
|
|
461
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
462
|
+
return `${baseUrl}${url}`;
|
|
463
|
+
}
|
|
464
|
+
// Devbox management endpoints
|
|
465
|
+
devboxList() {
|
|
466
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.LIST);
|
|
467
|
+
}
|
|
468
|
+
devboxCreate() {
|
|
469
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.CREATE);
|
|
470
|
+
}
|
|
471
|
+
devboxGet(name) {
|
|
472
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.GET, { name });
|
|
473
|
+
}
|
|
474
|
+
devboxUpdate(name) {
|
|
475
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.UPDATE, { name });
|
|
476
|
+
}
|
|
477
|
+
devboxDelete(name) {
|
|
478
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.DELETE, { name });
|
|
479
|
+
}
|
|
480
|
+
devboxStart(name) {
|
|
481
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.START, { name });
|
|
482
|
+
}
|
|
483
|
+
devboxPause(name) {
|
|
484
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.PAUSE, { name });
|
|
485
|
+
}
|
|
486
|
+
devboxRestart(name) {
|
|
487
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.RESTART, { name });
|
|
488
|
+
}
|
|
489
|
+
devboxShutdown(name) {
|
|
490
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.SHUTDOWN, { name });
|
|
491
|
+
}
|
|
492
|
+
devboxMonitor(name) {
|
|
493
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.MONITOR, { name });
|
|
494
|
+
}
|
|
495
|
+
devboxTemplates() {
|
|
496
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.TEMPLATES);
|
|
497
|
+
}
|
|
498
|
+
devboxPorts(name) {
|
|
499
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.PORTS, { name });
|
|
500
|
+
}
|
|
501
|
+
devboxAutostart(name) {
|
|
502
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.AUTOSTART, { name });
|
|
503
|
+
}
|
|
504
|
+
// Release endpoints
|
|
505
|
+
releaseList(name) {
|
|
506
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.LIST, { name });
|
|
507
|
+
}
|
|
508
|
+
releaseCreate(name) {
|
|
509
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.CREATE, { name });
|
|
510
|
+
}
|
|
511
|
+
releaseDelete(name, tag) {
|
|
512
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DELETE, { name, tag });
|
|
513
|
+
}
|
|
514
|
+
releaseDeploy(name, tag) {
|
|
515
|
+
return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DEPLOY, { name, tag });
|
|
516
|
+
}
|
|
517
|
+
containerHealth(baseUrl) {
|
|
518
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.HEALTH}`;
|
|
519
|
+
}
|
|
520
|
+
filesWrite(baseUrl) {
|
|
521
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.WRITE}`;
|
|
522
|
+
}
|
|
523
|
+
filesRead(baseUrl) {
|
|
524
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.READ}`;
|
|
525
|
+
}
|
|
526
|
+
filesList(baseUrl) {
|
|
527
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.LIST}`;
|
|
528
|
+
}
|
|
529
|
+
filesDelete(baseUrl) {
|
|
530
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.DELETE}`;
|
|
531
|
+
}
|
|
532
|
+
filesBatchUpload(baseUrl) {
|
|
533
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_UPLOAD}`;
|
|
534
|
+
}
|
|
535
|
+
filesBatchDownload(baseUrl) {
|
|
536
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD}`;
|
|
537
|
+
}
|
|
538
|
+
filesSearch(baseUrl) {
|
|
539
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.SEARCH}`;
|
|
540
|
+
}
|
|
541
|
+
filesFind(baseUrl) {
|
|
542
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.FIND}`;
|
|
543
|
+
}
|
|
544
|
+
filesReplace(baseUrl) {
|
|
545
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.REPLACE}`;
|
|
546
|
+
}
|
|
547
|
+
processExec(baseUrl) {
|
|
548
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.EXEC}`;
|
|
549
|
+
}
|
|
550
|
+
processStatus(baseUrl, pid) {
|
|
551
|
+
return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace("{pid}", pid.toString())}`;
|
|
552
|
+
}
|
|
553
|
+
// Temporarily disabled - ws module removed
|
|
554
|
+
// websocket(baseUrl: string): string {
|
|
555
|
+
// return `${baseUrl}${API_ENDPOINTS.CONTAINER.WEBSOCKET}`
|
|
556
|
+
// }
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/api/types.ts
|
|
560
|
+
var DevboxRuntime = /* @__PURE__ */ ((DevboxRuntime2) => {
|
|
561
|
+
DevboxRuntime2["NUXT3"] = "nuxt3";
|
|
562
|
+
DevboxRuntime2["ANGULAR"] = "angular";
|
|
563
|
+
DevboxRuntime2["QUARKUS"] = "quarkus";
|
|
564
|
+
DevboxRuntime2["UBUNTU"] = "ubuntu";
|
|
565
|
+
DevboxRuntime2["FLASK"] = "flask";
|
|
566
|
+
DevboxRuntime2["JAVA"] = "java";
|
|
567
|
+
DevboxRuntime2["CHI"] = "chi";
|
|
568
|
+
DevboxRuntime2["NET"] = "net";
|
|
569
|
+
DevboxRuntime2["IRIS"] = "iris";
|
|
570
|
+
DevboxRuntime2["HEXO"] = "hexo";
|
|
571
|
+
DevboxRuntime2["PYTHON"] = "python";
|
|
572
|
+
DevboxRuntime2["DOCUSAURUS"] = "docusaurus";
|
|
573
|
+
DevboxRuntime2["VITEPRESS"] = "vitepress";
|
|
574
|
+
DevboxRuntime2["CPP"] = "cpp";
|
|
575
|
+
DevboxRuntime2["VUE"] = "vue";
|
|
576
|
+
DevboxRuntime2["NGINX"] = "nginx";
|
|
577
|
+
DevboxRuntime2["ROCKET"] = "rocket";
|
|
578
|
+
DevboxRuntime2["DEBIAN_SSH"] = "debian-ssh";
|
|
579
|
+
DevboxRuntime2["VERT_X"] = "vert.x";
|
|
580
|
+
DevboxRuntime2["EXPRESS_JS"] = "express.js";
|
|
581
|
+
DevboxRuntime2["DJANGO"] = "django";
|
|
582
|
+
DevboxRuntime2["NEXT_JS"] = "next.js";
|
|
583
|
+
DevboxRuntime2["SEALAF"] = "sealaf";
|
|
584
|
+
DevboxRuntime2["GO"] = "go";
|
|
585
|
+
DevboxRuntime2["REACT"] = "react";
|
|
586
|
+
DevboxRuntime2["PHP"] = "php";
|
|
587
|
+
DevboxRuntime2["SVELTE"] = "svelte";
|
|
588
|
+
DevboxRuntime2["C"] = "c";
|
|
589
|
+
DevboxRuntime2["ASTRO"] = "astro";
|
|
590
|
+
DevboxRuntime2["UMI"] = "umi";
|
|
591
|
+
DevboxRuntime2["GIN"] = "gin";
|
|
592
|
+
DevboxRuntime2["NODE_JS"] = "node.js";
|
|
593
|
+
DevboxRuntime2["ECHO"] = "echo";
|
|
594
|
+
DevboxRuntime2["RUST"] = "rust";
|
|
595
|
+
DevboxRuntime2["TEST_AGENT"] = "node-expt-agent";
|
|
596
|
+
return DevboxRuntime2;
|
|
597
|
+
})(DevboxRuntime || {});
|
|
598
|
+
|
|
599
|
+
// src/api/client.ts
|
|
600
|
+
var SealosAPIClient = class {
|
|
601
|
+
baseUrl;
|
|
602
|
+
timeout;
|
|
603
|
+
retries;
|
|
604
|
+
rejectUnauthorized;
|
|
605
|
+
getAuthHeaders;
|
|
606
|
+
constructor(config) {
|
|
607
|
+
this.baseUrl = config.baseUrl || "https://devbox.usw.sealos.io";
|
|
608
|
+
this.timeout = config.timeout || 3e4;
|
|
609
|
+
this.retries = config.retries || 3;
|
|
610
|
+
this.rejectUnauthorized = config.rejectUnauthorized ?? process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0";
|
|
611
|
+
this.getAuthHeaders = config.getAuthHeaders;
|
|
612
|
+
if (!this.rejectUnauthorized) {
|
|
613
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async request(method, path, options = {}) {
|
|
617
|
+
const url = new URL(path, this.baseUrl);
|
|
618
|
+
if (options.params) {
|
|
619
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
620
|
+
if (value !== void 0 && value !== null) {
|
|
621
|
+
url.searchParams.append(key, String(value));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const fetchOptions = {
|
|
626
|
+
method,
|
|
627
|
+
headers: {
|
|
628
|
+
"Content-Type": "application/json",
|
|
629
|
+
...this.getAuthHeaders ? this.getAuthHeaders() : {},
|
|
630
|
+
...options.headers
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
if (options.data) {
|
|
634
|
+
fetchOptions.body = JSON.stringify(options.data);
|
|
635
|
+
}
|
|
636
|
+
let lastError = new Error("Unknown error");
|
|
637
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
638
|
+
try {
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
641
|
+
const response = await fetch(url.toString(), {
|
|
642
|
+
...fetchOptions,
|
|
643
|
+
signal: controller.signal
|
|
644
|
+
});
|
|
645
|
+
clearTimeout(timeoutId);
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
let errorData = {};
|
|
648
|
+
try {
|
|
649
|
+
const contentType2 = response.headers.get("content-type") || "";
|
|
650
|
+
if (contentType2.includes("application/json")) {
|
|
651
|
+
errorData = await response.json();
|
|
652
|
+
} else {
|
|
653
|
+
const errorText = await response.text().catch(() => "Unable to read error response");
|
|
654
|
+
try {
|
|
655
|
+
errorData = JSON.parse(errorText);
|
|
656
|
+
} catch {
|
|
657
|
+
errorData = { error: errorText };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} catch (e) {
|
|
661
|
+
}
|
|
662
|
+
const errorMessage = errorData.error || response.statusText;
|
|
663
|
+
const errorCode = errorData.code || this.getErrorCodeFromStatus(response.status);
|
|
664
|
+
throw new DevboxSDKError(errorMessage, errorCode, {
|
|
665
|
+
status: response.status,
|
|
666
|
+
statusText: response.statusText,
|
|
667
|
+
timestamp: errorData.timestamp,
|
|
668
|
+
serverErrorCode: errorData.code
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
const contentType = response.headers.get("content-type");
|
|
672
|
+
const data = contentType?.includes("application/json") ? await response.json() : await response.text();
|
|
673
|
+
logger.info("Response data:", url.toString(), data);
|
|
674
|
+
return {
|
|
675
|
+
data,
|
|
676
|
+
status: response.status,
|
|
677
|
+
statusText: response.statusText,
|
|
678
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
679
|
+
};
|
|
680
|
+
} catch (error) {
|
|
681
|
+
lastError = error;
|
|
682
|
+
if (attempt === this.retries || !this.shouldRetry(error)) {
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1e3));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
throw lastError;
|
|
689
|
+
}
|
|
690
|
+
shouldRetry(error) {
|
|
691
|
+
if (error instanceof DevboxSDKError) {
|
|
692
|
+
const nonRetryable4xxCodes = [
|
|
693
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
694
|
+
ERROR_CODES.INVALID_TOKEN,
|
|
695
|
+
ERROR_CODES.TOKEN_EXPIRED,
|
|
696
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
697
|
+
ERROR_CODES.MISSING_REQUIRED_FIELD,
|
|
698
|
+
ERROR_CODES.INVALID_FIELD_VALUE,
|
|
699
|
+
ERROR_CODES.NOT_FOUND,
|
|
700
|
+
ERROR_CODES.FILE_NOT_FOUND,
|
|
701
|
+
ERROR_CODES.PROCESS_NOT_FOUND,
|
|
702
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
703
|
+
ERROR_CODES.CONFLICT,
|
|
704
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
705
|
+
ERROR_CODES.AUTHENTICATION_FAILED
|
|
706
|
+
];
|
|
707
|
+
if (nonRetryable4xxCodes.includes(error.code)) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
return [
|
|
711
|
+
ERROR_CODES.CONNECTION_TIMEOUT,
|
|
712
|
+
ERROR_CODES.CONNECTION_FAILED,
|
|
713
|
+
ERROR_CODES.SERVER_UNAVAILABLE,
|
|
714
|
+
ERROR_CODES.SERVICE_UNAVAILABLE,
|
|
715
|
+
ERROR_CODES.OPERATION_TIMEOUT,
|
|
716
|
+
ERROR_CODES.SESSION_TIMEOUT,
|
|
717
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
718
|
+
].includes(error.code);
|
|
719
|
+
}
|
|
720
|
+
return error.name === "AbortError" || error.message.includes("fetch");
|
|
721
|
+
}
|
|
722
|
+
getErrorCodeFromStatus(status) {
|
|
723
|
+
switch (status) {
|
|
724
|
+
case 401:
|
|
725
|
+
return ERROR_CODES.AUTHENTICATION_FAILED;
|
|
726
|
+
case 403:
|
|
727
|
+
return ERROR_CODES.AUTHENTICATION_FAILED;
|
|
728
|
+
case 404:
|
|
729
|
+
return ERROR_CODES.DEVBOX_NOT_FOUND;
|
|
730
|
+
case 408:
|
|
731
|
+
return ERROR_CODES.CONNECTION_TIMEOUT;
|
|
732
|
+
case 429:
|
|
733
|
+
return "TOO_MANY_REQUESTS";
|
|
734
|
+
case 500:
|
|
735
|
+
return ERROR_CODES.INTERNAL_ERROR;
|
|
736
|
+
case 502:
|
|
737
|
+
return ERROR_CODES.SERVER_UNAVAILABLE;
|
|
738
|
+
case 503:
|
|
739
|
+
return ERROR_CODES.SERVICE_UNAVAILABLE;
|
|
740
|
+
case 504:
|
|
741
|
+
return ERROR_CODES.CONNECTION_TIMEOUT;
|
|
742
|
+
default:
|
|
743
|
+
return ERROR_CODES.INTERNAL_ERROR;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
get(url, options) {
|
|
747
|
+
return this.request("GET", url, options);
|
|
748
|
+
}
|
|
749
|
+
post(url, options) {
|
|
750
|
+
return this.request("POST", url, options);
|
|
751
|
+
}
|
|
752
|
+
put(url, options) {
|
|
753
|
+
return this.request("PUT", url, options);
|
|
754
|
+
}
|
|
755
|
+
delete(url, options) {
|
|
756
|
+
return this.request("DELETE", url, options);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
var DevboxAPI = class {
|
|
760
|
+
httpClient;
|
|
761
|
+
authenticator;
|
|
762
|
+
endpoints;
|
|
763
|
+
constructor(config) {
|
|
764
|
+
this.authenticator = new KubeconfigAuthenticator(config.kubeconfig);
|
|
765
|
+
const kubeconfigUrl = parseKubeconfigServerUrl(config.kubeconfig);
|
|
766
|
+
const baseUrl = config.baseUrl || kubeconfigUrl || "https://devbox.usw.sealos.io";
|
|
767
|
+
this.httpClient = new SealosAPIClient({
|
|
768
|
+
baseUrl,
|
|
769
|
+
timeout: config.timeout,
|
|
770
|
+
retries: config.retries,
|
|
771
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
772
|
+
getAuthHeaders: () => this.authenticator.getAuthHeaders()
|
|
773
|
+
});
|
|
774
|
+
this.endpoints = new APIEndpoints(baseUrl);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Create a new Devbox instance
|
|
778
|
+
*/
|
|
779
|
+
async createDevbox(config) {
|
|
780
|
+
const request = {
|
|
781
|
+
name: config.name,
|
|
782
|
+
runtime: config.runtime,
|
|
783
|
+
resource: config.resource,
|
|
784
|
+
ports: config.ports?.map((p) => ({ number: p.number, protocol: p.protocol })),
|
|
785
|
+
env: config.env
|
|
786
|
+
};
|
|
787
|
+
try {
|
|
788
|
+
const response = await this.httpClient.post(this.endpoints.devboxCreate(), {
|
|
789
|
+
data: request
|
|
790
|
+
});
|
|
791
|
+
if (!response || !response.data) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
`Invalid API response: response or response.data is undefined. Response: ${JSON.stringify(response)}`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
let createResponse;
|
|
797
|
+
if (typeof response.data === "object" && "data" in response.data) {
|
|
798
|
+
const responseData = response.data;
|
|
799
|
+
if (!responseData.data) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
`Invalid API response structure: expected { data: DevboxCreateResponse }, but data field is undefined. Full response: ${JSON.stringify(response.data)}`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
createResponse = responseData.data;
|
|
805
|
+
} else {
|
|
806
|
+
createResponse = response.data;
|
|
807
|
+
}
|
|
808
|
+
if (!createResponse || !createResponse.name) {
|
|
809
|
+
throw new Error(
|
|
810
|
+
`Invalid DevboxCreateResponse: missing 'name' field. Response data: ${JSON.stringify(createResponse)}`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
return this.transformCreateResponseToDevboxInfo(
|
|
814
|
+
createResponse,
|
|
815
|
+
config.runtime,
|
|
816
|
+
config.resource
|
|
817
|
+
);
|
|
818
|
+
} catch (error) {
|
|
819
|
+
throw this.handleAPIError(error, "Failed to create Devbox");
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Get an existing Devbox instance
|
|
824
|
+
*/
|
|
825
|
+
async getDevbox(name) {
|
|
826
|
+
try {
|
|
827
|
+
const response = await this.httpClient.get(this.endpoints.devboxGet(name));
|
|
828
|
+
const responseData = response.data;
|
|
829
|
+
return this.transformDetailToDevboxInfo(responseData.data);
|
|
830
|
+
} catch (error) {
|
|
831
|
+
throw this.handleAPIError(error, `Failed to get Devbox '${name}'`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* List all Devbox instances
|
|
836
|
+
*/
|
|
837
|
+
async listDevboxes() {
|
|
838
|
+
try {
|
|
839
|
+
const response = await this.httpClient.get(this.endpoints.devboxList());
|
|
840
|
+
const listResponse = response.data;
|
|
841
|
+
return listResponse.data.map(this.transformListItemToDevboxInfo);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw this.handleAPIError(error, "Failed to list Devboxes");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Start a Devbox instance
|
|
848
|
+
*/
|
|
849
|
+
async startDevbox(name) {
|
|
850
|
+
try {
|
|
851
|
+
await this.httpClient.post(this.endpoints.devboxStart(name), {
|
|
852
|
+
data: {}
|
|
853
|
+
});
|
|
854
|
+
} catch (error) {
|
|
855
|
+
throw this.handleAPIError(error, `Failed to start Devbox '${name}'`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Pause a Devbox instance
|
|
860
|
+
*/
|
|
861
|
+
async pauseDevbox(name) {
|
|
862
|
+
try {
|
|
863
|
+
await this.httpClient.post(this.endpoints.devboxPause(name), {
|
|
864
|
+
data: {}
|
|
865
|
+
});
|
|
866
|
+
} catch (error) {
|
|
867
|
+
throw this.handleAPIError(error, `Failed to pause Devbox '${name}'`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Restart a Devbox instance
|
|
872
|
+
*/
|
|
873
|
+
async restartDevbox(name) {
|
|
874
|
+
try {
|
|
875
|
+
await this.httpClient.post(this.endpoints.devboxRestart(name), {
|
|
876
|
+
data: {}
|
|
877
|
+
});
|
|
878
|
+
} catch (error) {
|
|
879
|
+
throw this.handleAPIError(error, `Failed to restart Devbox '${name}'`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Delete a Devbox instance
|
|
884
|
+
*/
|
|
885
|
+
async deleteDevbox(name) {
|
|
886
|
+
try {
|
|
887
|
+
await this.httpClient.delete(this.endpoints.devboxDelete(name));
|
|
888
|
+
} catch (error) {
|
|
889
|
+
throw this.handleAPIError(error, `Failed to delete Devbox '${name}'`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Update a Devbox instance configuration
|
|
894
|
+
*/
|
|
895
|
+
async updateDevbox(name, config) {
|
|
896
|
+
try {
|
|
897
|
+
await this.httpClient.request("PATCH", this.endpoints.devboxUpdate(name), {
|
|
898
|
+
data: config
|
|
899
|
+
});
|
|
900
|
+
} catch (error) {
|
|
901
|
+
throw this.handleAPIError(error, `Failed to update Devbox '${name}'`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Shutdown a Devbox instance
|
|
906
|
+
*/
|
|
907
|
+
async shutdownDevbox(name) {
|
|
908
|
+
try {
|
|
909
|
+
await this.httpClient.post(this.endpoints.devboxShutdown(name), {
|
|
910
|
+
data: {}
|
|
911
|
+
});
|
|
912
|
+
} catch (error) {
|
|
913
|
+
throw this.handleAPIError(error, `Failed to shutdown Devbox '${name}'`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get available runtime templates
|
|
918
|
+
*/
|
|
919
|
+
async getTemplates() {
|
|
920
|
+
try {
|
|
921
|
+
const response = await this.httpClient.get(
|
|
922
|
+
this.endpoints.devboxTemplates()
|
|
923
|
+
);
|
|
924
|
+
return response.data.data;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
throw this.handleAPIError(error, "Failed to get templates");
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Update port configuration for a Devbox
|
|
931
|
+
*/
|
|
932
|
+
async updatePorts(name, ports) {
|
|
933
|
+
try {
|
|
934
|
+
await this.httpClient.put(this.endpoints.devboxPorts(name), {
|
|
935
|
+
data: { ports }
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
throw this.handleAPIError(error, `Failed to update ports for '${name}'`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Configure autostart for a Devbox
|
|
943
|
+
*/
|
|
944
|
+
async configureAutostart(name, config) {
|
|
945
|
+
try {
|
|
946
|
+
await this.httpClient.post(this.endpoints.devboxAutostart(name), {
|
|
947
|
+
data: config || {}
|
|
948
|
+
});
|
|
949
|
+
} catch (error) {
|
|
950
|
+
throw this.handleAPIError(error, `Failed to configure autostart for '${name}'`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* List releases for a Devbox
|
|
955
|
+
*/
|
|
956
|
+
async listReleases(name) {
|
|
957
|
+
try {
|
|
958
|
+
const response = await this.httpClient.get(this.endpoints.releaseList(name));
|
|
959
|
+
const responseData = response.data;
|
|
960
|
+
return responseData.data || [];
|
|
961
|
+
} catch (error) {
|
|
962
|
+
throw this.handleAPIError(error, `Failed to list releases for '${name}'`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Create a release for a Devbox
|
|
967
|
+
*/
|
|
968
|
+
async createRelease(name, config) {
|
|
969
|
+
try {
|
|
970
|
+
await this.httpClient.post(this.endpoints.releaseCreate(name), {
|
|
971
|
+
data: config
|
|
972
|
+
});
|
|
973
|
+
} catch (error) {
|
|
974
|
+
throw this.handleAPIError(error, `Failed to create release for '${name}'`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Delete a release
|
|
979
|
+
*/
|
|
980
|
+
async deleteRelease(name, tag) {
|
|
981
|
+
try {
|
|
982
|
+
await this.httpClient.delete(this.endpoints.releaseDelete(name, tag));
|
|
983
|
+
} catch (error) {
|
|
984
|
+
throw this.handleAPIError(error, `Failed to delete release '${tag}' for '${name}'`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Deploy a release
|
|
989
|
+
*/
|
|
990
|
+
async deployRelease(name, tag) {
|
|
991
|
+
try {
|
|
992
|
+
await this.httpClient.post(this.endpoints.releaseDeploy(name, tag), {
|
|
993
|
+
data: {}
|
|
994
|
+
});
|
|
995
|
+
} catch (error) {
|
|
996
|
+
throw this.handleAPIError(error, `Failed to deploy release '${tag}' for '${name}'`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Get monitoring data for a Devbox instance
|
|
1001
|
+
*/
|
|
1002
|
+
async getMonitorData(name, timeRange) {
|
|
1003
|
+
try {
|
|
1004
|
+
const params = {
|
|
1005
|
+
start: timeRange?.start || Date.now() - 36e5,
|
|
1006
|
+
// Default 1 hour ago
|
|
1007
|
+
end: timeRange?.end || Date.now(),
|
|
1008
|
+
step: timeRange?.step
|
|
1009
|
+
};
|
|
1010
|
+
const response = await this.httpClient.get(this.endpoints.devboxMonitor(name), {
|
|
1011
|
+
params
|
|
1012
|
+
});
|
|
1013
|
+
const dataPoints = response.data;
|
|
1014
|
+
return dataPoints.map(this.transformMonitorData);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
throw this.handleAPIError(error, `Failed to get monitor data for '${name}'`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Test authentication
|
|
1021
|
+
*/
|
|
1022
|
+
async testAuth() {
|
|
1023
|
+
try {
|
|
1024
|
+
await this.httpClient.get(this.endpoints.devboxList());
|
|
1025
|
+
return true;
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
transformSSHInfoToDevboxInfo(sshInfo) {
|
|
1031
|
+
return {
|
|
1032
|
+
name: sshInfo.name,
|
|
1033
|
+
status: sshInfo.status,
|
|
1034
|
+
runtime: sshInfo.runtime,
|
|
1035
|
+
resources: sshInfo.resources,
|
|
1036
|
+
podIP: sshInfo.podIP,
|
|
1037
|
+
ssh: sshInfo.ssh ? {
|
|
1038
|
+
host: sshInfo.ssh.host,
|
|
1039
|
+
port: sshInfo.ssh.port,
|
|
1040
|
+
user: sshInfo.ssh.user,
|
|
1041
|
+
privateKey: sshInfo.ssh.privateKey
|
|
1042
|
+
} : void 0
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
transformListItemToDevboxInfo(listItem) {
|
|
1046
|
+
return {
|
|
1047
|
+
name: listItem.name,
|
|
1048
|
+
status: listItem.status,
|
|
1049
|
+
runtime: listItem.runtime,
|
|
1050
|
+
resources: listItem.resources
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Safely convert a string to DevboxRuntime enum
|
|
1055
|
+
* Returns the enum value if valid, otherwise returns a default value
|
|
1056
|
+
*/
|
|
1057
|
+
stringToRuntime(value) {
|
|
1058
|
+
if (!value) {
|
|
1059
|
+
return "node-expt-agent" /* TEST_AGENT */;
|
|
1060
|
+
}
|
|
1061
|
+
const runtimeValues = Object.values(DevboxRuntime);
|
|
1062
|
+
if (runtimeValues.includes(value)) {
|
|
1063
|
+
return value;
|
|
1064
|
+
}
|
|
1065
|
+
return "node-expt-agent" /* TEST_AGENT */;
|
|
1066
|
+
}
|
|
1067
|
+
transformCreateResponseToDevboxInfo(createResponse, runtime, resource) {
|
|
1068
|
+
return {
|
|
1069
|
+
name: createResponse.name,
|
|
1070
|
+
status: "Pending",
|
|
1071
|
+
// New devboxes start in Pending state
|
|
1072
|
+
runtime,
|
|
1073
|
+
// Use the runtime from the create request
|
|
1074
|
+
resources: {
|
|
1075
|
+
cpu: resource.cpu,
|
|
1076
|
+
// Use the resource from the create request
|
|
1077
|
+
memory: resource.memory
|
|
1078
|
+
// Use the resource from the create request
|
|
1079
|
+
},
|
|
1080
|
+
ssh: {
|
|
1081
|
+
host: createResponse.domain,
|
|
1082
|
+
port: createResponse.sshPort,
|
|
1083
|
+
user: createResponse.userName,
|
|
1084
|
+
privateKey: createResponse.base64PrivateKey
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Transform DevboxDetail (actual API response) to DevboxInfo
|
|
1090
|
+
*/
|
|
1091
|
+
transformDetailToDevboxInfo(detail) {
|
|
1092
|
+
const runtime = typeof detail.runtime === "string" ? this.stringToRuntime(detail.runtime) : detail.runtime;
|
|
1093
|
+
const ssh = detail.ssh?.privateKey ? {
|
|
1094
|
+
host: detail.ssh.host,
|
|
1095
|
+
port: detail.ssh.port,
|
|
1096
|
+
user: detail.ssh.user,
|
|
1097
|
+
privateKey: detail.ssh.privateKey
|
|
1098
|
+
} : void 0;
|
|
1099
|
+
let podIP;
|
|
1100
|
+
if (detail.pods && detail.pods.length > 0) ;
|
|
1101
|
+
return {
|
|
1102
|
+
name: detail.name,
|
|
1103
|
+
status: detail.status,
|
|
1104
|
+
runtime,
|
|
1105
|
+
resources: detail.resources,
|
|
1106
|
+
podIP,
|
|
1107
|
+
ssh,
|
|
1108
|
+
ports: detail.ports,
|
|
1109
|
+
agentServer: detail.agentServer
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Transform DevboxGetResponse to DevboxInfo (legacy method, kept for backward compatibility)
|
|
1114
|
+
*/
|
|
1115
|
+
transformGetResponseToDevboxInfo(getResponse) {
|
|
1116
|
+
const status = typeof getResponse.status === "string" ? getResponse.status : getResponse.status.value;
|
|
1117
|
+
const resources = getResponse.resources || {
|
|
1118
|
+
cpu: getResponse.cpu || 0,
|
|
1119
|
+
memory: getResponse.memory || 0
|
|
1120
|
+
};
|
|
1121
|
+
const runtime = getResponse.runtime ? this.stringToRuntime(getResponse.runtime) : getResponse.iconId ? this.stringToRuntime(getResponse.iconId) : "node-expt-agent" /* TEST_AGENT */;
|
|
1122
|
+
return {
|
|
1123
|
+
name: getResponse.name,
|
|
1124
|
+
status,
|
|
1125
|
+
runtime,
|
|
1126
|
+
resources
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
transformMonitorData(dataPoint) {
|
|
1130
|
+
return {
|
|
1131
|
+
cpu: dataPoint.cpu,
|
|
1132
|
+
memory: dataPoint.memory,
|
|
1133
|
+
network: dataPoint.network,
|
|
1134
|
+
disk: dataPoint.disk,
|
|
1135
|
+
timestamp: dataPoint.timestamp
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
handleAPIError(error, context) {
|
|
1139
|
+
if (error instanceof DevboxSDKError) {
|
|
1140
|
+
return error;
|
|
1141
|
+
}
|
|
1142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1143
|
+
return new DevboxSDKError(`${context}: ${message}`, ERROR_CODES.INTERNAL_ERROR, {
|
|
1144
|
+
originalError: error
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// src/http/client.ts
|
|
1150
|
+
var DevboxContainerClient = class {
|
|
1151
|
+
baseUrl;
|
|
1152
|
+
timeout;
|
|
1153
|
+
token;
|
|
1154
|
+
constructor(baseUrl, timeout, token) {
|
|
1155
|
+
this.baseUrl = baseUrl;
|
|
1156
|
+
this.timeout = timeout;
|
|
1157
|
+
this.token = token;
|
|
1158
|
+
}
|
|
1159
|
+
async get(path, options) {
|
|
1160
|
+
return this.request("GET", path, options);
|
|
1161
|
+
}
|
|
1162
|
+
async post(path, options) {
|
|
1163
|
+
return this.request("POST", path, options);
|
|
1164
|
+
}
|
|
1165
|
+
async put(path, options) {
|
|
1166
|
+
return this.request("PUT", path, options);
|
|
1167
|
+
}
|
|
1168
|
+
async delete(path, options) {
|
|
1169
|
+
return this.request("DELETE", path, options);
|
|
1170
|
+
}
|
|
1171
|
+
async request(method, path, options) {
|
|
1172
|
+
const url = new URL(path, this.baseUrl);
|
|
1173
|
+
if (options?.params) {
|
|
1174
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
1175
|
+
if (value !== void 0 && value !== null) {
|
|
1176
|
+
url.searchParams.append(key, String(value));
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
const isFormData = options?.body !== void 0 && options.body instanceof FormData;
|
|
1181
|
+
const fetchOptions = {
|
|
1182
|
+
method,
|
|
1183
|
+
headers: {
|
|
1184
|
+
...isFormData ? {} : { "Content-Type": "application/json" },
|
|
1185
|
+
...options?.headers,
|
|
1186
|
+
// Decode base64 token and use as Bearer token
|
|
1187
|
+
Authorization: `Bearer ${Buffer.from(this.token, "base64").toString("utf-8")}`
|
|
1188
|
+
},
|
|
1189
|
+
signal: options?.signal
|
|
1190
|
+
};
|
|
1191
|
+
if (options?.body !== void 0) {
|
|
1192
|
+
if (isFormData) {
|
|
1193
|
+
fetchOptions.body = options.body;
|
|
1194
|
+
} else if (typeof options.body === "string") {
|
|
1195
|
+
fetchOptions.body = options.body;
|
|
1196
|
+
} else if (Buffer.isBuffer(options.body) || options.body instanceof ArrayBuffer || options.body instanceof Uint8Array) {
|
|
1197
|
+
fetchOptions.body = options.body;
|
|
1198
|
+
} else {
|
|
1199
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const controller = new AbortController();
|
|
1203
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1204
|
+
try {
|
|
1205
|
+
const response = await fetch(url.toString(), {
|
|
1206
|
+
...fetchOptions,
|
|
1207
|
+
signal: options?.signal || controller.signal
|
|
1208
|
+
});
|
|
1209
|
+
logger.info("Request URL:", url.toString());
|
|
1210
|
+
clearTimeout(timeoutId);
|
|
1211
|
+
if (!response.ok) {
|
|
1212
|
+
let errorData = {};
|
|
1213
|
+
try {
|
|
1214
|
+
const contentType2 = response.headers.get("content-type") || "";
|
|
1215
|
+
if (contentType2.includes("application/json")) {
|
|
1216
|
+
errorData = await response.json();
|
|
1217
|
+
}
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
const errorMessage = errorData.error || response.statusText;
|
|
1221
|
+
const errorCode = errorData.code || ERROR_CODES.CONNECTION_FAILED;
|
|
1222
|
+
throw new DevboxSDKError(errorMessage, errorCode, {
|
|
1223
|
+
status: response.status,
|
|
1224
|
+
statusText: response.statusText,
|
|
1225
|
+
timestamp: errorData.timestamp,
|
|
1226
|
+
serverErrorCode: errorData.code
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1230
|
+
let data;
|
|
1231
|
+
if (contentType.includes("application/json")) {
|
|
1232
|
+
const jsonData = await response.json();
|
|
1233
|
+
data = parseServerResponse(jsonData);
|
|
1234
|
+
} else if (contentType.includes("application/octet-stream") || contentType.includes("application/gzip") || contentType.includes("application/x-tar") || contentType.includes("multipart/") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/")) {
|
|
1235
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1236
|
+
data = Buffer.from(arrayBuffer);
|
|
1237
|
+
} else {
|
|
1238
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1239
|
+
data = Buffer.from(arrayBuffer);
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
data,
|
|
1243
|
+
status: response.status,
|
|
1244
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
1245
|
+
url: response.url
|
|
1246
|
+
};
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
logger.error("Request failed:", error);
|
|
1249
|
+
clearTimeout(timeoutId);
|
|
1250
|
+
if (error instanceof DevboxSDKError) {
|
|
1251
|
+
throw error;
|
|
1252
|
+
}
|
|
1253
|
+
throw new DevboxSDKError(
|
|
1254
|
+
`Request failed: ${error.message}`,
|
|
1255
|
+
ERROR_CODES.CONNECTION_FAILED,
|
|
1256
|
+
{ originalError: error.message }
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// src/http/manager.ts
|
|
1263
|
+
var ContainerUrlResolver = class {
|
|
1264
|
+
apiClient;
|
|
1265
|
+
cache = /* @__PURE__ */ new Map();
|
|
1266
|
+
CACHE_TTL = 6e4;
|
|
1267
|
+
mockServerUrl;
|
|
1268
|
+
baseUrl;
|
|
1269
|
+
timeout;
|
|
1270
|
+
constructor(config) {
|
|
1271
|
+
this.mockServerUrl = config.mockServerUrl || process.env.MOCK_SERVER_URL;
|
|
1272
|
+
const kubeconfigUrl = config.kubeconfig ? parseKubeconfigServerUrl(config.kubeconfig) : null;
|
|
1273
|
+
this.baseUrl = config.baseUrl || kubeconfigUrl || "https://devbox.usw.sealos.io";
|
|
1274
|
+
this.timeout = config.http?.timeout || 3e4;
|
|
1275
|
+
}
|
|
1276
|
+
setAPIClient(apiClient) {
|
|
1277
|
+
this.apiClient = apiClient;
|
|
1278
|
+
}
|
|
1279
|
+
async executeWithConnection(devboxName, operation) {
|
|
1280
|
+
const devboxInfo = await this.getDevboxInfo(devboxName);
|
|
1281
|
+
const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo);
|
|
1282
|
+
const token = devboxInfo?.agentServer?.token;
|
|
1283
|
+
if (!serverUrl || !token) {
|
|
1284
|
+
throw new DevboxNotReadyError(devboxName, devboxInfo?.status, {
|
|
1285
|
+
hasServerUrl: !!serverUrl,
|
|
1286
|
+
hasToken: !!token,
|
|
1287
|
+
currentStatus: devboxInfo?.status
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
const client = new DevboxContainerClient(serverUrl, this.timeout, token);
|
|
1291
|
+
return await operation(client);
|
|
1292
|
+
}
|
|
1293
|
+
async getServerUrl(devboxName) {
|
|
1294
|
+
const configuredUrl = this.getConfiguredServerUrl();
|
|
1295
|
+
if (configuredUrl) {
|
|
1296
|
+
return configuredUrl;
|
|
1297
|
+
}
|
|
1298
|
+
if (!this.apiClient) {
|
|
1299
|
+
throw new DevboxSDKError(
|
|
1300
|
+
"API client not set. Call setAPIClient() first.",
|
|
1301
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
const cached = this.getFromCache(`url:${devboxName}`);
|
|
1305
|
+
if (cached && typeof cached === "string") {
|
|
1306
|
+
return cached;
|
|
1307
|
+
}
|
|
1308
|
+
try {
|
|
1309
|
+
const url = await this.resolveServerUrlFromAPI(devboxName);
|
|
1310
|
+
this.setCache(`url:${devboxName}`, url);
|
|
1311
|
+
return url;
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
if (error instanceof DevboxSDKError) {
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1316
|
+
throw new DevboxSDKError(
|
|
1317
|
+
`Failed to get server URL for '${devboxName}': ${error.message}`,
|
|
1318
|
+
ERROR_CODES.CONNECTION_FAILED,
|
|
1319
|
+
{ originalError: error.message }
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
getConfiguredServerUrl() {
|
|
1324
|
+
if (this.mockServerUrl) {
|
|
1325
|
+
return this.mockServerUrl;
|
|
1326
|
+
}
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
async resolveServerUrlFromAPI(devboxName) {
|
|
1330
|
+
const devboxInfo = await this.getDevboxInfo(devboxName);
|
|
1331
|
+
if (!devboxInfo) {
|
|
1332
|
+
throw new DevboxSDKError(`Devbox '${devboxName}' not found`, ERROR_CODES.DEVBOX_NOT_FOUND);
|
|
1333
|
+
}
|
|
1334
|
+
const url = this.extractUrlFromDevboxInfo(devboxInfo);
|
|
1335
|
+
if (!url) {
|
|
1336
|
+
throw new DevboxSDKError(
|
|
1337
|
+
`Devbox '${devboxName}' does not have an accessible URL`,
|
|
1338
|
+
ERROR_CODES.CONNECTION_FAILED
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
return url;
|
|
1342
|
+
}
|
|
1343
|
+
extractUrlFromDevboxInfo(devboxInfo) {
|
|
1344
|
+
if (devboxInfo.agentServer?.url) {
|
|
1345
|
+
const serviceName = devboxInfo.agentServer.url;
|
|
1346
|
+
const urlObj = new URL(this.baseUrl);
|
|
1347
|
+
const domain = urlObj.hostname.replace(/^devbox\./, "");
|
|
1348
|
+
return `${urlObj.protocol}//devbox-${serviceName}-agent.${domain}`;
|
|
1349
|
+
}
|
|
1350
|
+
if (devboxInfo.ports && devboxInfo.ports.length > 0) {
|
|
1351
|
+
const port = devboxInfo.ports[0];
|
|
1352
|
+
if (port?.publicAddress) {
|
|
1353
|
+
return port.publicAddress;
|
|
1354
|
+
}
|
|
1355
|
+
if (port?.privateAddress) {
|
|
1356
|
+
return port.privateAddress;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (devboxInfo.podIP) {
|
|
1360
|
+
return `http://${devboxInfo.podIP}:3000`;
|
|
1361
|
+
}
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
async getDevboxInfo(devboxName) {
|
|
1365
|
+
const cached = this.getFromCache(`devbox:${devboxName}`);
|
|
1366
|
+
if (cached) {
|
|
1367
|
+
return cached;
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
if (!this.apiClient) {
|
|
1371
|
+
throw new Error("API client not set");
|
|
1372
|
+
}
|
|
1373
|
+
const devboxInfo = await this.apiClient.getDevbox(devboxName);
|
|
1374
|
+
this.setCache(`devbox:${devboxName}`, devboxInfo);
|
|
1375
|
+
return devboxInfo;
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
getFromCache(key) {
|
|
1381
|
+
const entry = this.cache.get(key);
|
|
1382
|
+
if (!entry) return null;
|
|
1383
|
+
if (Date.now() - entry.timestamp > this.CACHE_TTL) {
|
|
1384
|
+
this.cache.delete(key);
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
return entry.data;
|
|
1388
|
+
}
|
|
1389
|
+
setCache(key, data) {
|
|
1390
|
+
this.cache.set(key, {
|
|
1391
|
+
data,
|
|
1392
|
+
timestamp: Date.now()
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
clearCache() {
|
|
1396
|
+
this.cache.clear();
|
|
1397
|
+
}
|
|
1398
|
+
async closeAllConnections() {
|
|
1399
|
+
this.clearCache();
|
|
1400
|
+
}
|
|
1401
|
+
async checkDevboxHealth(devboxName) {
|
|
1402
|
+
try {
|
|
1403
|
+
const devboxInfo = await this.getDevboxInfo(devboxName);
|
|
1404
|
+
const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo);
|
|
1405
|
+
if (!serverUrl) return false;
|
|
1406
|
+
const token = devboxInfo?.agentServer?.token;
|
|
1407
|
+
if (!token) return false;
|
|
1408
|
+
const client = new DevboxContainerClient(serverUrl, this.timeout, token);
|
|
1409
|
+
const response = await client.get("/health");
|
|
1410
|
+
return response.data?.healthStatus === "ok";
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
// src/core/git/git.ts
|
|
1418
|
+
var Git = class {
|
|
1419
|
+
constructor(deps) {
|
|
1420
|
+
this.deps = deps;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Build Git URL with authentication
|
|
1424
|
+
*/
|
|
1425
|
+
buildAuthUrl(url, auth) {
|
|
1426
|
+
if (!auth) return url;
|
|
1427
|
+
if (auth.token) {
|
|
1428
|
+
const urlMatch = url.match(/^(https?:\/\/)([^@]+@)?([^\/]+)(\/.+)?$/);
|
|
1429
|
+
if (urlMatch) {
|
|
1430
|
+
const [, protocol, , host, path] = urlMatch;
|
|
1431
|
+
return `${protocol}${auth.token}@${host}${path || ""}`;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (auth.username && (auth.password || auth.token)) {
|
|
1435
|
+
const urlMatch = url.match(/^(https?:\/\/)([^\/]+)(\/.+)?$/);
|
|
1436
|
+
if (urlMatch) {
|
|
1437
|
+
const [, protocol, host, path] = urlMatch;
|
|
1438
|
+
const password = auth.password || auth.token || "";
|
|
1439
|
+
return `${protocol}${auth.username}:${password}@${host}${path || ""}`;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return url;
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Setup Git authentication environment variables
|
|
1446
|
+
*/
|
|
1447
|
+
setupGitAuth(env = {}, auth) {
|
|
1448
|
+
const gitEnv = { ...env };
|
|
1449
|
+
if (auth?.username) {
|
|
1450
|
+
gitEnv.GIT_USERNAME = auth.username;
|
|
1451
|
+
}
|
|
1452
|
+
if (auth?.password) {
|
|
1453
|
+
gitEnv.GIT_PASSWORD = auth.password;
|
|
1454
|
+
} else if (auth?.token) {
|
|
1455
|
+
gitEnv.GIT_PASSWORD = auth.token;
|
|
1456
|
+
}
|
|
1457
|
+
return gitEnv;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Parse Git branch list output
|
|
1461
|
+
*/
|
|
1462
|
+
parseGitBranches(stdout, currentBranch) {
|
|
1463
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
1464
|
+
const branches = [];
|
|
1465
|
+
for (const line of lines) {
|
|
1466
|
+
const trimmed = line.trim();
|
|
1467
|
+
if (!trimmed) continue;
|
|
1468
|
+
const isCurrent = trimmed.startsWith("*");
|
|
1469
|
+
const isRemote = trimmed.includes("remotes/");
|
|
1470
|
+
let name = trimmed.replace(/^\*\s*/, "").trim();
|
|
1471
|
+
if (isRemote) {
|
|
1472
|
+
const match = name.match(/^remotes\/[^/]+\/(.+)$/);
|
|
1473
|
+
if (match?.[1]) {
|
|
1474
|
+
name = match[1];
|
|
1475
|
+
} else {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
branches.push({
|
|
1480
|
+
name,
|
|
1481
|
+
isCurrent: name === currentBranch || isCurrent,
|
|
1482
|
+
isRemote,
|
|
1483
|
+
commit: ""
|
|
1484
|
+
// Will be filled by additional git command if needed
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
return branches;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Parse Git status output
|
|
1491
|
+
*/
|
|
1492
|
+
parseGitStatus(stdout, branchLine) {
|
|
1493
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
1494
|
+
const staged = [];
|
|
1495
|
+
const modified = [];
|
|
1496
|
+
const untracked = [];
|
|
1497
|
+
const deleted = [];
|
|
1498
|
+
for (const line of lines) {
|
|
1499
|
+
if (line.length < 3) continue;
|
|
1500
|
+
const status = line.substring(0, 2);
|
|
1501
|
+
const file = line.substring(3).trim();
|
|
1502
|
+
if (status[0] === "A" || status[0] === "M" || status[0] === "R" || status[0] === "C") {
|
|
1503
|
+
staged.push(file);
|
|
1504
|
+
}
|
|
1505
|
+
if (status[1] === "M" || status[1] === "D") {
|
|
1506
|
+
modified.push(file);
|
|
1507
|
+
}
|
|
1508
|
+
if (status === "??") {
|
|
1509
|
+
untracked.push(file);
|
|
1510
|
+
}
|
|
1511
|
+
if (status[0] === "D" || status[1] === "D") {
|
|
1512
|
+
deleted.push(file);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
let currentBranch = "main";
|
|
1516
|
+
let ahead = 0;
|
|
1517
|
+
let behind = 0;
|
|
1518
|
+
if (branchLine) {
|
|
1519
|
+
const branchMatch = branchLine.match(/^##\s+([^.]+)/);
|
|
1520
|
+
if (branchMatch?.[1]) {
|
|
1521
|
+
currentBranch = branchMatch[1];
|
|
1522
|
+
}
|
|
1523
|
+
const aheadMatch = branchLine.match(/ahead\s+(\d+)/);
|
|
1524
|
+
if (aheadMatch?.[1]) {
|
|
1525
|
+
ahead = Number.parseInt(aheadMatch[1], 10);
|
|
1526
|
+
}
|
|
1527
|
+
const behindMatch = branchLine.match(/behind\s+(\d+)/);
|
|
1528
|
+
if (behindMatch?.[1]) {
|
|
1529
|
+
behind = Number.parseInt(behindMatch[1], 10);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const isClean = staged.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0;
|
|
1533
|
+
return {
|
|
1534
|
+
currentBranch,
|
|
1535
|
+
isClean,
|
|
1536
|
+
ahead,
|
|
1537
|
+
behind,
|
|
1538
|
+
staged,
|
|
1539
|
+
modified,
|
|
1540
|
+
untracked,
|
|
1541
|
+
deleted
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Clone a Git repository
|
|
1546
|
+
*/
|
|
1547
|
+
async clone(options) {
|
|
1548
|
+
const args = ["clone"];
|
|
1549
|
+
if (options.branch) {
|
|
1550
|
+
args.push("-b", options.branch);
|
|
1551
|
+
}
|
|
1552
|
+
if (options.depth) {
|
|
1553
|
+
args.push("--depth", String(options.depth));
|
|
1554
|
+
}
|
|
1555
|
+
if (options.commit) {
|
|
1556
|
+
args.push("--single-branch");
|
|
1557
|
+
}
|
|
1558
|
+
const authUrl = this.buildAuthUrl(options.url, options.auth);
|
|
1559
|
+
args.push(authUrl);
|
|
1560
|
+
if (options.targetDir) {
|
|
1561
|
+
args.push(options.targetDir);
|
|
1562
|
+
}
|
|
1563
|
+
const env = this.setupGitAuth({}, options.auth);
|
|
1564
|
+
const result = await this.deps.execSync({
|
|
1565
|
+
command: "git",
|
|
1566
|
+
args,
|
|
1567
|
+
env,
|
|
1568
|
+
timeout: 300
|
|
1569
|
+
// 5 minutes timeout for clone
|
|
1570
|
+
});
|
|
1571
|
+
if (result.exitCode !== 0) {
|
|
1572
|
+
throw new Error(`Git clone failed: ${result.stderr || result.stdout}`);
|
|
1573
|
+
}
|
|
1574
|
+
if (options.commit && options.targetDir) {
|
|
1575
|
+
await this.deps.execSync({
|
|
1576
|
+
command: "git",
|
|
1577
|
+
args: ["checkout", options.commit],
|
|
1578
|
+
cwd: options.targetDir
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Pull changes from remote repository
|
|
1584
|
+
*/
|
|
1585
|
+
async pull(repoPath, options) {
|
|
1586
|
+
const remote = options?.remote || "origin";
|
|
1587
|
+
if (options?.auth) {
|
|
1588
|
+
const urlResult = await this.deps.execSync({
|
|
1589
|
+
command: "git",
|
|
1590
|
+
args: ["remote", "get-url", remote],
|
|
1591
|
+
cwd: repoPath
|
|
1592
|
+
});
|
|
1593
|
+
if (urlResult.exitCode === 0) {
|
|
1594
|
+
const currentUrl = urlResult.stdout.trim();
|
|
1595
|
+
const authUrl = this.buildAuthUrl(currentUrl, options.auth);
|
|
1596
|
+
await this.deps.execSync({
|
|
1597
|
+
command: "git",
|
|
1598
|
+
args: ["remote", "set-url", remote, authUrl],
|
|
1599
|
+
cwd: repoPath
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const args = ["pull"];
|
|
1604
|
+
if (options?.branch) {
|
|
1605
|
+
args.push(remote, options.branch);
|
|
1606
|
+
}
|
|
1607
|
+
const result = await this.deps.execSync({
|
|
1608
|
+
command: "git",
|
|
1609
|
+
args,
|
|
1610
|
+
cwd: repoPath,
|
|
1611
|
+
timeout: 120
|
|
1612
|
+
// 2 minutes timeout
|
|
1613
|
+
});
|
|
1614
|
+
if (result.exitCode !== 0) {
|
|
1615
|
+
throw new Error(`Git pull failed: ${result.stderr || result.stdout}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Push changes to remote repository
|
|
1620
|
+
*/
|
|
1621
|
+
async push(repoPath, options) {
|
|
1622
|
+
const remote = options?.remote || "origin";
|
|
1623
|
+
if (options?.auth) {
|
|
1624
|
+
const urlResult = await this.deps.execSync({
|
|
1625
|
+
command: "git",
|
|
1626
|
+
args: ["remote", "get-url", remote],
|
|
1627
|
+
cwd: repoPath
|
|
1628
|
+
});
|
|
1629
|
+
if (urlResult.exitCode === 0) {
|
|
1630
|
+
const currentUrl = urlResult.stdout.trim();
|
|
1631
|
+
const authUrl = this.buildAuthUrl(currentUrl, options.auth);
|
|
1632
|
+
await this.deps.execSync({
|
|
1633
|
+
command: "git",
|
|
1634
|
+
args: ["remote", "set-url", remote, authUrl],
|
|
1635
|
+
cwd: repoPath
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const args = ["push"];
|
|
1640
|
+
if (options?.force) {
|
|
1641
|
+
args.push("--force");
|
|
1642
|
+
}
|
|
1643
|
+
if (options?.branch) {
|
|
1644
|
+
args.push(remote, options.branch);
|
|
1645
|
+
} else {
|
|
1646
|
+
args.push(remote);
|
|
1647
|
+
}
|
|
1648
|
+
const result = await this.deps.execSync({
|
|
1649
|
+
command: "git",
|
|
1650
|
+
args,
|
|
1651
|
+
cwd: repoPath,
|
|
1652
|
+
timeout: 120
|
|
1653
|
+
// 2 minutes timeout
|
|
1654
|
+
});
|
|
1655
|
+
if (result.exitCode !== 0) {
|
|
1656
|
+
throw new Error(`Git push failed: ${result.stderr || result.stdout}`);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* List all branches
|
|
1661
|
+
*/
|
|
1662
|
+
async branches(repoPath) {
|
|
1663
|
+
const currentBranchResult = await this.deps.execSync({
|
|
1664
|
+
command: "git",
|
|
1665
|
+
args: ["rev-parse", "--abbrev-ref", "HEAD"],
|
|
1666
|
+
cwd: repoPath
|
|
1667
|
+
});
|
|
1668
|
+
const currentBranch = currentBranchResult.stdout.trim();
|
|
1669
|
+
const branchesResult = await this.deps.execSync({
|
|
1670
|
+
command: "git",
|
|
1671
|
+
args: ["branch", "-a"],
|
|
1672
|
+
cwd: repoPath
|
|
1673
|
+
});
|
|
1674
|
+
if (branchesResult.exitCode !== 0) {
|
|
1675
|
+
throw new Error(`Git branches failed: ${branchesResult.stderr || branchesResult.stdout}`);
|
|
1676
|
+
}
|
|
1677
|
+
const branches = this.parseGitBranches(branchesResult.stdout, currentBranch);
|
|
1678
|
+
for (const branch of branches) {
|
|
1679
|
+
try {
|
|
1680
|
+
const commitResult = await this.deps.execSync({
|
|
1681
|
+
command: "git",
|
|
1682
|
+
args: ["rev-parse", branch.isRemote ? `origin/${branch.name}` : branch.name],
|
|
1683
|
+
cwd: repoPath
|
|
1684
|
+
});
|
|
1685
|
+
if (commitResult.exitCode === 0) {
|
|
1686
|
+
branch.commit = commitResult.stdout.trim();
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return branches;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Create a new branch
|
|
1695
|
+
*/
|
|
1696
|
+
async createBranch(repoPath, branchName, checkout = false) {
|
|
1697
|
+
const args = checkout ? ["checkout", "-b", branchName] : ["branch", branchName];
|
|
1698
|
+
const result = await this.deps.execSync({
|
|
1699
|
+
command: "git",
|
|
1700
|
+
args,
|
|
1701
|
+
cwd: repoPath
|
|
1702
|
+
});
|
|
1703
|
+
if (result.exitCode !== 0) {
|
|
1704
|
+
throw new Error(`Git create branch failed: ${result.stderr || result.stdout}`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Delete a branch
|
|
1709
|
+
*/
|
|
1710
|
+
async deleteBranch(repoPath, branchName, force = false, remote = false) {
|
|
1711
|
+
if (remote) {
|
|
1712
|
+
const result = await this.deps.execSync({
|
|
1713
|
+
command: "git",
|
|
1714
|
+
args: ["push", "origin", "--delete", branchName],
|
|
1715
|
+
cwd: repoPath
|
|
1716
|
+
});
|
|
1717
|
+
if (result.exitCode !== 0) {
|
|
1718
|
+
throw new Error(`Git delete remote branch failed: ${result.stderr || result.stdout}`);
|
|
1719
|
+
}
|
|
1720
|
+
} else {
|
|
1721
|
+
const args = force ? ["branch", "-D", branchName] : ["branch", "-d", branchName];
|
|
1722
|
+
const result = await this.deps.execSync({
|
|
1723
|
+
command: "git",
|
|
1724
|
+
args,
|
|
1725
|
+
cwd: repoPath
|
|
1726
|
+
});
|
|
1727
|
+
if (result.exitCode !== 0) {
|
|
1728
|
+
throw new Error(`Git delete branch failed: ${result.stderr || result.stdout}`);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Checkout a branch
|
|
1734
|
+
*/
|
|
1735
|
+
async checkoutBranch(repoPath, branchName, create = false) {
|
|
1736
|
+
const args = create ? ["checkout", "-b", branchName] : ["checkout", branchName];
|
|
1737
|
+
const result = await this.deps.execSync({
|
|
1738
|
+
command: "git",
|
|
1739
|
+
args,
|
|
1740
|
+
cwd: repoPath
|
|
1741
|
+
});
|
|
1742
|
+
if (result.exitCode !== 0) {
|
|
1743
|
+
throw new Error(`Git checkout failed: ${result.stderr || result.stdout}`);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
normalizePath(repoPath, filePath) {
|
|
1747
|
+
const normalize = (p) => {
|
|
1748
|
+
let normalized = p.trim();
|
|
1749
|
+
if (normalized.startsWith("./")) {
|
|
1750
|
+
normalized = normalized.substring(2);
|
|
1751
|
+
}
|
|
1752
|
+
normalized = normalized.replace(/\/$/, "");
|
|
1753
|
+
return normalized;
|
|
1754
|
+
};
|
|
1755
|
+
const normRepo = normalize(repoPath);
|
|
1756
|
+
const normFile = normalize(filePath);
|
|
1757
|
+
if (normFile.startsWith(`${normRepo}/`)) {
|
|
1758
|
+
return normFile.substring(normRepo.length + 1);
|
|
1759
|
+
}
|
|
1760
|
+
if (normFile === normRepo) {
|
|
1761
|
+
return ".";
|
|
1762
|
+
}
|
|
1763
|
+
if (filePath.startsWith("/")) {
|
|
1764
|
+
const repoIndex = filePath.indexOf(normRepo);
|
|
1765
|
+
if (repoIndex !== -1) {
|
|
1766
|
+
const afterRepo = filePath.substring(repoIndex + normRepo.length);
|
|
1767
|
+
if (afterRepo.startsWith("/")) {
|
|
1768
|
+
return afterRepo.substring(1) || ".";
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return normFile;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Stage files for commit
|
|
1776
|
+
*/
|
|
1777
|
+
async add(repoPath, files) {
|
|
1778
|
+
const args = ["add"];
|
|
1779
|
+
if (!files || Array.isArray(files) && files.length === 0) {
|
|
1780
|
+
args.push(".");
|
|
1781
|
+
} else if (typeof files === "string") {
|
|
1782
|
+
args.push(this.normalizePath(repoPath, files));
|
|
1783
|
+
} else {
|
|
1784
|
+
args.push(...files.map((file) => this.normalizePath(repoPath, file)));
|
|
1785
|
+
}
|
|
1786
|
+
const result = await this.deps.execSync({
|
|
1787
|
+
command: "git",
|
|
1788
|
+
args,
|
|
1789
|
+
cwd: repoPath
|
|
1790
|
+
});
|
|
1791
|
+
if (result.exitCode !== 0) {
|
|
1792
|
+
throw new Error(`Git add failed: ${result.stderr || result.stdout}`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Commit changes
|
|
1797
|
+
*/
|
|
1798
|
+
async commit(repoPath, message, author, email, allowEmpty) {
|
|
1799
|
+
const args = ["commit"];
|
|
1800
|
+
if (allowEmpty) {
|
|
1801
|
+
args.push("--allow-empty");
|
|
1802
|
+
}
|
|
1803
|
+
args.push("--author", `${author} <${email}>`);
|
|
1804
|
+
args.push("-m", message);
|
|
1805
|
+
const result = await this.deps.execSync({
|
|
1806
|
+
command: "git",
|
|
1807
|
+
args,
|
|
1808
|
+
cwd: repoPath
|
|
1809
|
+
});
|
|
1810
|
+
if (result.exitCode !== 0) {
|
|
1811
|
+
throw new Error(`Git commit failed: ${result.stderr || result.stdout}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Get repository status
|
|
1816
|
+
*/
|
|
1817
|
+
async status(repoPath) {
|
|
1818
|
+
const porcelainResult = await this.deps.execSync({
|
|
1819
|
+
command: "git",
|
|
1820
|
+
args: ["status", "--porcelain"],
|
|
1821
|
+
cwd: repoPath
|
|
1822
|
+
});
|
|
1823
|
+
const branchResult = await this.deps.execSync({
|
|
1824
|
+
command: "git",
|
|
1825
|
+
args: ["status", "-sb"],
|
|
1826
|
+
cwd: repoPath
|
|
1827
|
+
});
|
|
1828
|
+
if (porcelainResult.exitCode !== 0 || branchResult.exitCode !== 0) {
|
|
1829
|
+
throw new Error(`Git status failed: ${branchResult.stderr || branchResult.stdout}`);
|
|
1830
|
+
}
|
|
1831
|
+
const branchLine = branchResult.stdout.split("\n")[0] || "";
|
|
1832
|
+
return this.parseGitStatus(porcelainResult.stdout, branchLine);
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
|
|
1836
|
+
// src/core/devbox-instance.ts
|
|
1837
|
+
var DevboxInstance = class {
|
|
1838
|
+
info;
|
|
1839
|
+
sdk;
|
|
1840
|
+
git;
|
|
1841
|
+
constructor(info, sdk) {
|
|
1842
|
+
this.info = info;
|
|
1843
|
+
this.sdk = sdk;
|
|
1844
|
+
this.git = new Git({
|
|
1845
|
+
execSync: (options) => this.execSync(options)
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
// Properties
|
|
1849
|
+
get name() {
|
|
1850
|
+
return this.info.name;
|
|
1851
|
+
}
|
|
1852
|
+
get status() {
|
|
1853
|
+
return this.info.status;
|
|
1854
|
+
}
|
|
1855
|
+
get runtime() {
|
|
1856
|
+
return this.info.runtime;
|
|
1857
|
+
}
|
|
1858
|
+
get resources() {
|
|
1859
|
+
return this.info.resources;
|
|
1860
|
+
}
|
|
1861
|
+
get serverUrl() {
|
|
1862
|
+
if (!this.info.podIP) {
|
|
1863
|
+
throw new Error(`Devbox '${this.name}' does not have a pod IP address`);
|
|
1864
|
+
}
|
|
1865
|
+
return `http://${this.info.podIP}:3000`;
|
|
1866
|
+
}
|
|
1867
|
+
// Lifecycle operations
|
|
1868
|
+
async start() {
|
|
1869
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1870
|
+
await apiClient.startDevbox(this.name);
|
|
1871
|
+
await this.refreshInfo();
|
|
1872
|
+
}
|
|
1873
|
+
async pause() {
|
|
1874
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1875
|
+
await apiClient.pauseDevbox(this.name);
|
|
1876
|
+
await this.refreshInfo();
|
|
1877
|
+
}
|
|
1878
|
+
async restart() {
|
|
1879
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1880
|
+
await apiClient.restartDevbox(this.name);
|
|
1881
|
+
await this.refreshInfo();
|
|
1882
|
+
}
|
|
1883
|
+
async shutdown() {
|
|
1884
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1885
|
+
await apiClient.shutdownDevbox(this.name);
|
|
1886
|
+
await this.refreshInfo();
|
|
1887
|
+
}
|
|
1888
|
+
async delete() {
|
|
1889
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1890
|
+
await apiClient.deleteDevbox(this.name);
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Refresh the instance information from the API
|
|
1894
|
+
*/
|
|
1895
|
+
async refreshInfo() {
|
|
1896
|
+
const apiClient = this.sdk.getAPIClient();
|
|
1897
|
+
this.info = await apiClient.getDevbox(this.name);
|
|
1898
|
+
}
|
|
1899
|
+
async writeFile(path, content, options) {
|
|
1900
|
+
this.validatePath(path);
|
|
1901
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
1902
|
+
await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
1903
|
+
const contentSize = Buffer.isBuffer(content) ? content.length : Buffer.byteLength(content, "utf-8");
|
|
1904
|
+
const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024;
|
|
1905
|
+
const useBinaryMode = contentSize > LARGE_FILE_THRESHOLD;
|
|
1906
|
+
if (Buffer.isBuffer(content)) {
|
|
1907
|
+
if (options?.encoding === "base64") {
|
|
1908
|
+
const base64Content = content.toString("base64");
|
|
1909
|
+
await client.post("/api/v1/files/write", {
|
|
1910
|
+
body: {
|
|
1911
|
+
path,
|
|
1912
|
+
content: base64Content,
|
|
1913
|
+
encoding: "base64"
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
} else {
|
|
1917
|
+
await client.post("/api/v1/files/write", {
|
|
1918
|
+
params: { path },
|
|
1919
|
+
headers: {
|
|
1920
|
+
"Content-Type": "application/octet-stream"
|
|
1921
|
+
},
|
|
1922
|
+
body: content
|
|
1923
|
+
// Direct binary data
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
} else {
|
|
1927
|
+
if (useBinaryMode && !options?.encoding) {
|
|
1928
|
+
const buffer = Buffer.from(content, "utf-8");
|
|
1929
|
+
await client.post("/api/v1/files/write", {
|
|
1930
|
+
params: { path },
|
|
1931
|
+
headers: {
|
|
1932
|
+
"Content-Type": "application/octet-stream"
|
|
1933
|
+
},
|
|
1934
|
+
body: buffer
|
|
1935
|
+
});
|
|
1936
|
+
} else if (options?.encoding === "base64") {
|
|
1937
|
+
const base64Content = Buffer.from(content, "utf-8").toString("base64");
|
|
1938
|
+
await client.post("/api/v1/files/write", {
|
|
1939
|
+
body: {
|
|
1940
|
+
path,
|
|
1941
|
+
content: base64Content,
|
|
1942
|
+
encoding: "base64"
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
} else {
|
|
1946
|
+
await client.post("/api/v1/files/write", {
|
|
1947
|
+
body: {
|
|
1948
|
+
path,
|
|
1949
|
+
content
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
async readFile(path, options) {
|
|
1957
|
+
this.validatePath(path);
|
|
1958
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
1959
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
1960
|
+
const response = await client.get("/api/v1/files/read", {
|
|
1961
|
+
params: { path, ...options }
|
|
1962
|
+
});
|
|
1963
|
+
if (Buffer.isBuffer(response.data)) {
|
|
1964
|
+
return response.data;
|
|
1965
|
+
}
|
|
1966
|
+
if (typeof response.data === "string") {
|
|
1967
|
+
return Buffer.from(response.data, "utf-8");
|
|
1968
|
+
}
|
|
1969
|
+
if (response.data instanceof ArrayBuffer) {
|
|
1970
|
+
return Buffer.from(new Uint8Array(response.data));
|
|
1971
|
+
}
|
|
1972
|
+
if (response.data instanceof Uint8Array) {
|
|
1973
|
+
return Buffer.from(response.data);
|
|
1974
|
+
}
|
|
1975
|
+
const dataType = typeof response.data;
|
|
1976
|
+
const dataConstructor = response.data?.constructor?.name || "unknown";
|
|
1977
|
+
throw new Error(
|
|
1978
|
+
`Failed to read file: unexpected response format (type: ${dataType}, constructor: ${dataConstructor})`
|
|
1979
|
+
);
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Validate file path to prevent directory traversal attacks
|
|
1984
|
+
*/
|
|
1985
|
+
validatePath(path) {
|
|
1986
|
+
if (!path || path.length === 0) {
|
|
1987
|
+
throw new Error("Path cannot be empty");
|
|
1988
|
+
}
|
|
1989
|
+
if (path.endsWith("/") || path.endsWith("\\")) {
|
|
1990
|
+
throw new Error("Path cannot end with a directory separator");
|
|
1991
|
+
}
|
|
1992
|
+
const normalized = path.replace(/\\/g, "/");
|
|
1993
|
+
if (normalized.includes("../") || normalized.includes("..\\")) {
|
|
1994
|
+
throw new Error(`Path traversal detected: ${path}`);
|
|
1995
|
+
}
|
|
1996
|
+
if (normalized.startsWith("/") && (normalized.startsWith("/../") || normalized === "/..")) {
|
|
1997
|
+
throw new Error(`Invalid absolute path: ${path}`);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
async deleteFile(path) {
|
|
2001
|
+
this.validatePath(path);
|
|
2002
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2003
|
+
await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2004
|
+
await client.post("/api/v1/files/delete", {
|
|
2005
|
+
body: { path }
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
async listFiles(path) {
|
|
2010
|
+
this.validatePath(path);
|
|
2011
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2012
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2013
|
+
const response = await client.get("/api/v1/files/list", {
|
|
2014
|
+
params: { path }
|
|
2015
|
+
});
|
|
2016
|
+
return response.data;
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
async uploadFiles(files, options) {
|
|
2020
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2021
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2022
|
+
const formData = new FormData();
|
|
2023
|
+
let targetDir;
|
|
2024
|
+
const relativePaths = [];
|
|
2025
|
+
const filePaths = Object.keys(files);
|
|
2026
|
+
if (options?.targetDir) {
|
|
2027
|
+
targetDir = options.targetDir.replace(/\/+$/, "") || ".";
|
|
2028
|
+
for (const filePath of filePaths) {
|
|
2029
|
+
if (filePath.startsWith(`${targetDir}/`)) {
|
|
2030
|
+
relativePaths.push(filePath.slice(targetDir.length + 1));
|
|
2031
|
+
} else if (filePath === targetDir) {
|
|
2032
|
+
relativePaths.push("");
|
|
2033
|
+
} else {
|
|
2034
|
+
relativePaths.push(filePath);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
} else {
|
|
2038
|
+
if (filePaths.length === 0) {
|
|
2039
|
+
targetDir = ".";
|
|
2040
|
+
} else {
|
|
2041
|
+
const dirParts = filePaths.map((path) => {
|
|
2042
|
+
const parts = path.split("/");
|
|
2043
|
+
return parts.slice(0, -1);
|
|
2044
|
+
});
|
|
2045
|
+
if (dirParts.length > 0 && dirParts[0] && dirParts[0].length > 0) {
|
|
2046
|
+
const commonPrefix = [];
|
|
2047
|
+
const minLength = Math.min(...dirParts.map((p) => p.length));
|
|
2048
|
+
const firstDirParts = dirParts[0];
|
|
2049
|
+
for (let i = 0; i < minLength; i++) {
|
|
2050
|
+
const segment = firstDirParts[i];
|
|
2051
|
+
if (segment && dirParts.every((p) => p[i] === segment)) {
|
|
2052
|
+
commonPrefix.push(segment);
|
|
2053
|
+
} else {
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
targetDir = commonPrefix.length > 0 ? commonPrefix.join("/") : ".";
|
|
2058
|
+
} else {
|
|
2059
|
+
targetDir = ".";
|
|
2060
|
+
}
|
|
2061
|
+
const normalizedTargetDir = targetDir === "." ? "" : targetDir;
|
|
2062
|
+
for (const filePath of filePaths) {
|
|
2063
|
+
if (normalizedTargetDir && filePath.startsWith(`${normalizedTargetDir}/`)) {
|
|
2064
|
+
relativePaths.push(filePath.slice(normalizedTargetDir.length + 1));
|
|
2065
|
+
} else {
|
|
2066
|
+
relativePaths.push(filePath);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
formData.append("targetDir", targetDir);
|
|
2072
|
+
let index = 0;
|
|
2073
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
2074
|
+
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
2075
|
+
const relativePath = relativePaths[index++] || filePath.split("/").pop() || "file";
|
|
2076
|
+
const fullPath = targetDir === "." ? relativePath : `${targetDir}/${relativePath}`;
|
|
2077
|
+
const uint8Array = new Uint8Array(buffer);
|
|
2078
|
+
const file = new File([uint8Array], fullPath);
|
|
2079
|
+
formData.append("files", file);
|
|
2080
|
+
}
|
|
2081
|
+
const response = await client.post("/api/v1/files/batch-upload", {
|
|
2082
|
+
body: formData
|
|
2083
|
+
});
|
|
2084
|
+
return response.data;
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
async moveFile(source, destination, overwrite = false) {
|
|
2088
|
+
this.validatePath(source);
|
|
2089
|
+
this.validatePath(destination);
|
|
2090
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2091
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2092
|
+
const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.MOVE, {
|
|
2093
|
+
body: {
|
|
2094
|
+
source,
|
|
2095
|
+
destination,
|
|
2096
|
+
overwrite
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
return response.data;
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Rename a file or directory
|
|
2104
|
+
* @param oldPath Current file or directory path
|
|
2105
|
+
* @param newPath New file or directory path
|
|
2106
|
+
* @returns Rename operation response
|
|
2107
|
+
*/
|
|
2108
|
+
async renameFile(oldPath, newPath) {
|
|
2109
|
+
this.validatePath(oldPath);
|
|
2110
|
+
this.validatePath(newPath);
|
|
2111
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2112
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2113
|
+
const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.RENAME, {
|
|
2114
|
+
body: {
|
|
2115
|
+
oldPath,
|
|
2116
|
+
newPath
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
return response.data;
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Search for files by filename pattern (case-insensitive substring match)
|
|
2124
|
+
* @param options Search options including directory and pattern
|
|
2125
|
+
* @returns List of matching file paths
|
|
2126
|
+
*/
|
|
2127
|
+
async searchFiles(options) {
|
|
2128
|
+
if (!options.pattern || options.pattern.trim().length === 0) {
|
|
2129
|
+
throw new Error("Pattern cannot be empty");
|
|
2130
|
+
}
|
|
2131
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2132
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2133
|
+
const response = await client.post(
|
|
2134
|
+
API_ENDPOINTS.CONTAINER.FILES.SEARCH,
|
|
2135
|
+
{
|
|
2136
|
+
body: {
|
|
2137
|
+
dir: options.dir || ".",
|
|
2138
|
+
pattern: options.pattern
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
);
|
|
2142
|
+
return response.data;
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Find files by content keyword (searches inside text files)
|
|
2147
|
+
* @param options Find options including directory and keyword
|
|
2148
|
+
* @returns List of file paths containing the keyword
|
|
2149
|
+
*/
|
|
2150
|
+
async findInFiles(options) {
|
|
2151
|
+
if (!options.keyword || options.keyword.trim().length === 0) {
|
|
2152
|
+
throw new Error("Keyword cannot be empty");
|
|
2153
|
+
}
|
|
2154
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2155
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2156
|
+
const response = await client.post(
|
|
2157
|
+
API_ENDPOINTS.CONTAINER.FILES.FIND,
|
|
2158
|
+
{
|
|
2159
|
+
body: {
|
|
2160
|
+
dir: options.dir || ".",
|
|
2161
|
+
keyword: options.keyword
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
);
|
|
2165
|
+
return response.data;
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Replace text in multiple files
|
|
2170
|
+
* @param options Replace options including file paths, from text, and to text
|
|
2171
|
+
* @returns Replacement results for each file
|
|
2172
|
+
*/
|
|
2173
|
+
async replaceInFiles(options) {
|
|
2174
|
+
if (!options.from || options.from.trim().length === 0) {
|
|
2175
|
+
throw new Error("'from' string cannot be empty");
|
|
2176
|
+
}
|
|
2177
|
+
if (!options.files || options.files.length === 0) {
|
|
2178
|
+
throw new Error("At least one file path is required");
|
|
2179
|
+
}
|
|
2180
|
+
for (const filePath of options.files) {
|
|
2181
|
+
this.validatePath(filePath);
|
|
2182
|
+
}
|
|
2183
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2184
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2185
|
+
const response = await client.post(
|
|
2186
|
+
API_ENDPOINTS.CONTAINER.FILES.REPLACE,
|
|
2187
|
+
{
|
|
2188
|
+
body: {
|
|
2189
|
+
files: options.files,
|
|
2190
|
+
from: options.from,
|
|
2191
|
+
to: options.to
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
);
|
|
2195
|
+
return response.data;
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Download a single file
|
|
2200
|
+
* @param path File path to download
|
|
2201
|
+
* @returns Buffer containing file content
|
|
2202
|
+
*/
|
|
2203
|
+
async downloadFile(path) {
|
|
2204
|
+
this.validatePath(path);
|
|
2205
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2206
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2207
|
+
const response = await client.get(
|
|
2208
|
+
`${API_ENDPOINTS.CONTAINER.FILES.DOWNLOAD}?path=${encodeURIComponent(path)}`
|
|
2209
|
+
);
|
|
2210
|
+
return response.data;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Download multiple files with format options
|
|
2215
|
+
* @param paths Array of file paths to download
|
|
2216
|
+
* @param options Download options including format
|
|
2217
|
+
* @returns Buffer containing downloaded files (tar.gz, tar, or multipart format)
|
|
2218
|
+
*/
|
|
2219
|
+
async downloadFiles(paths, options) {
|
|
2220
|
+
if (!paths || paths.length === 0) {
|
|
2221
|
+
throw new Error("At least one file path is required");
|
|
2222
|
+
}
|
|
2223
|
+
for (const path of paths) {
|
|
2224
|
+
this.validatePath(path);
|
|
2225
|
+
}
|
|
2226
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2227
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2228
|
+
const headers = {};
|
|
2229
|
+
if (options?.format) {
|
|
2230
|
+
switch (options.format) {
|
|
2231
|
+
case "tar.gz":
|
|
2232
|
+
headers.Accept = "application/gzip";
|
|
2233
|
+
break;
|
|
2234
|
+
case "tar":
|
|
2235
|
+
headers.Accept = "application/x-tar";
|
|
2236
|
+
break;
|
|
2237
|
+
case "multipart":
|
|
2238
|
+
headers.Accept = "multipart/mixed";
|
|
2239
|
+
break;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD, {
|
|
2243
|
+
body: { paths, format: options?.format },
|
|
2244
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
2245
|
+
});
|
|
2246
|
+
return response.data;
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Get listening ports on the system
|
|
2251
|
+
* @returns Ports response with list of listening ports (3000-9999 range)
|
|
2252
|
+
*/
|
|
2253
|
+
async getPorts() {
|
|
2254
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2255
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2256
|
+
const response = await client.get(API_ENDPOINTS.CONTAINER.PORTS);
|
|
2257
|
+
return response.data;
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Get preview link for a specific port
|
|
2262
|
+
* @param port Port number to get preview link for
|
|
2263
|
+
* @returns Preview URL information
|
|
2264
|
+
*/
|
|
2265
|
+
async getPreviewLink(port) {
|
|
2266
|
+
await this.refreshInfo();
|
|
2267
|
+
if (!this.info.agentServer?.url) {
|
|
2268
|
+
throw new Error(
|
|
2269
|
+
`No agentServer URL available for Devbox '${this.name}'. Cannot generate preview link.`
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
const serviceName = this.info.agentServer.url;
|
|
2273
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2274
|
+
const baseUrl = urlResolver.baseUrl;
|
|
2275
|
+
const urlObj = new URL(baseUrl);
|
|
2276
|
+
const domain = urlObj.hostname.replace(/^devbox\./, "");
|
|
2277
|
+
const url = `${urlObj.protocol}//devbox-${serviceName}-${port}.${domain}`;
|
|
2278
|
+
const protocol = urlObj.protocol.replace(":", "");
|
|
2279
|
+
return {
|
|
2280
|
+
url,
|
|
2281
|
+
port,
|
|
2282
|
+
protocol
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
// Temporarily disabled - ws module removed
|
|
2286
|
+
// File watching (instance method)
|
|
2287
|
+
// async watchFiles(
|
|
2288
|
+
// path: string,
|
|
2289
|
+
// callback: (event: FileChangeEvent) => void
|
|
2290
|
+
// ): Promise<FileWatchWebSocket> {
|
|
2291
|
+
// const urlResolver = this.sdk.getUrlResolver()
|
|
2292
|
+
// const serverUrl = await urlResolver.getServerUrl(this.name)
|
|
2293
|
+
// const { default: WebSocket } = await import('ws')
|
|
2294
|
+
// const ws = new WebSocket(`ws://${serverUrl.replace('http://', '')}/ws`) as unknown as FileWatchWebSocket
|
|
2295
|
+
// ws.onopen = () => {
|
|
2296
|
+
// const watchRequest: WatchRequest = { type: 'watch', path }
|
|
2297
|
+
// ws.send(JSON.stringify(watchRequest))
|
|
2298
|
+
// }
|
|
2299
|
+
// ws.onmessage = (event: any) => {
|
|
2300
|
+
// try {
|
|
2301
|
+
// const data = typeof event.data === 'string' ? event.data : event.data?.toString() || ''
|
|
2302
|
+
// const fileEvent = JSON.parse(data) as FileChangeEvent
|
|
2303
|
+
// callback(fileEvent)
|
|
2304
|
+
// } catch (error) {
|
|
2305
|
+
// console.error('Failed to parse file watch event:', error)
|
|
2306
|
+
// }
|
|
2307
|
+
// }
|
|
2308
|
+
// return ws
|
|
2309
|
+
// }
|
|
2310
|
+
// Process execution
|
|
2311
|
+
/**
|
|
2312
|
+
* Execute a process asynchronously
|
|
2313
|
+
* All commands are automatically executed through shell (sh -c) for consistent behavior
|
|
2314
|
+
* This ensures environment variables, pipes, redirections, etc. work as expected
|
|
2315
|
+
* @param options Process execution options
|
|
2316
|
+
* @returns Process execution response with process_id and pid
|
|
2317
|
+
*/
|
|
2318
|
+
async executeCommand(options) {
|
|
2319
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2320
|
+
let fullCommand = options.command;
|
|
2321
|
+
if (options.args && options.args.length > 0) {
|
|
2322
|
+
fullCommand = `${options.command} ${options.args.join(" ")}`;
|
|
2323
|
+
}
|
|
2324
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2325
|
+
const response = await client.post(
|
|
2326
|
+
API_ENDPOINTS.CONTAINER.PROCESS.EXEC,
|
|
2327
|
+
{
|
|
2328
|
+
body: {
|
|
2329
|
+
command: "sh",
|
|
2330
|
+
args: ["-c", fullCommand],
|
|
2331
|
+
cwd: options.cwd,
|
|
2332
|
+
env: options.env,
|
|
2333
|
+
timeout: options.timeout
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
);
|
|
2337
|
+
return response.data;
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Execute a process synchronously and wait for completion
|
|
2342
|
+
* All commands are automatically executed through shell (sh -c) for consistent behavior
|
|
2343
|
+
* This ensures environment variables, pipes, redirections, etc. work as expected
|
|
2344
|
+
* @param options Process execution options
|
|
2345
|
+
* @returns Synchronous execution response with stdout, stderr, and exit code
|
|
2346
|
+
*/
|
|
2347
|
+
async execSync(options) {
|
|
2348
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2349
|
+
let fullCommand = options.command;
|
|
2350
|
+
if (options.args && options.args.length > 0) {
|
|
2351
|
+
fullCommand = `${options.command} ${options.args.join(" ")}`;
|
|
2352
|
+
}
|
|
2353
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2354
|
+
const response = await client.post(
|
|
2355
|
+
API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC,
|
|
2356
|
+
{
|
|
2357
|
+
body: {
|
|
2358
|
+
command: "sh",
|
|
2359
|
+
args: ["-c", fullCommand],
|
|
2360
|
+
cwd: options.cwd,
|
|
2361
|
+
env: options.env,
|
|
2362
|
+
timeout: options.timeout
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
);
|
|
2366
|
+
return response.data;
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Execute code directly (Node.js or Python)
|
|
2371
|
+
* @param code Code string to execute
|
|
2372
|
+
* @param options Code execution options
|
|
2373
|
+
* @returns Synchronous execution response with stdout, stderr, and exit code
|
|
2374
|
+
*/
|
|
2375
|
+
async codeRun(code, options) {
|
|
2376
|
+
const language = options?.language || this.detectLanguage(code);
|
|
2377
|
+
const command = this.buildCodeCommand(code, language, options?.argv);
|
|
2378
|
+
return this.execSync({
|
|
2379
|
+
command,
|
|
2380
|
+
cwd: options?.cwd,
|
|
2381
|
+
env: options?.env,
|
|
2382
|
+
timeout: options?.timeout
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Detect programming language from code string
|
|
2387
|
+
* @param code Code string to analyze
|
|
2388
|
+
* @returns Detected language ('node' or 'python')
|
|
2389
|
+
*/
|
|
2390
|
+
detectLanguage(code) {
|
|
2391
|
+
if (/\bdef\s+\w+\(|^\s*import\s+\w+|print\s*\(|:\s*$/.test(code)) {
|
|
2392
|
+
return "python";
|
|
2393
|
+
}
|
|
2394
|
+
if (/\brequire\s*\(|module\.exports|console\.log/.test(code)) {
|
|
2395
|
+
return "node";
|
|
2396
|
+
}
|
|
2397
|
+
return "node";
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Build shell command to execute code
|
|
2401
|
+
* Note: sh -c wrapper is now handled by wrapCommandWithShell
|
|
2402
|
+
* @param code Code string to execute
|
|
2403
|
+
* @param language Programming language ('node' or 'python')
|
|
2404
|
+
* @param argv Command line arguments
|
|
2405
|
+
* @returns Shell command string (without sh -c wrapper)
|
|
2406
|
+
*/
|
|
2407
|
+
buildCodeCommand(code, language, argv) {
|
|
2408
|
+
const base64Code = Buffer.from(code).toString("base64");
|
|
2409
|
+
const argvStr = argv && argv.length > 0 ? ` ${argv.join(" ")}` : "";
|
|
2410
|
+
if (language === "python") {
|
|
2411
|
+
return `python3 -u -c "exec(__import__(\\"base64\\").b64decode(\\"${base64Code}\\").decode())"${argvStr}`;
|
|
2412
|
+
}
|
|
2413
|
+
return `echo ${base64Code} | base64 --decode | node -e "$(cat)"${argvStr}`;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Execute a process synchronously with streaming output (SSE)
|
|
2417
|
+
* @param options Process execution options
|
|
2418
|
+
* @returns ReadableStream for Server-Sent Events
|
|
2419
|
+
*/
|
|
2420
|
+
async execSyncStream(options) {
|
|
2421
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2422
|
+
const serverUrl = await urlResolver.getServerUrl(this.name);
|
|
2423
|
+
const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC_STREAM;
|
|
2424
|
+
const url = `${serverUrl}${endpoint}`;
|
|
2425
|
+
const response = await fetch(url, {
|
|
2426
|
+
method: "POST",
|
|
2427
|
+
headers: {
|
|
2428
|
+
"Content-Type": "application/json",
|
|
2429
|
+
Accept: "text/event-stream",
|
|
2430
|
+
Authorization: "Bearer 1234"
|
|
2431
|
+
// TODO: remove this
|
|
2432
|
+
},
|
|
2433
|
+
body: JSON.stringify({
|
|
2434
|
+
command: options.command,
|
|
2435
|
+
args: options.args,
|
|
2436
|
+
cwd: options.cwd,
|
|
2437
|
+
env: options.env,
|
|
2438
|
+
shell: options.shell,
|
|
2439
|
+
timeout: options.timeout
|
|
2440
|
+
})
|
|
2441
|
+
});
|
|
2442
|
+
if (!response.ok) {
|
|
2443
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!response.body) {
|
|
2446
|
+
throw new Error("Response body is null");
|
|
2447
|
+
}
|
|
2448
|
+
return response.body;
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* List all processes
|
|
2452
|
+
* @returns List of all processes with their metadata
|
|
2453
|
+
*/
|
|
2454
|
+
async listProcesses() {
|
|
2455
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2456
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2457
|
+
const response = await client.get(API_ENDPOINTS.CONTAINER.PROCESS.LIST);
|
|
2458
|
+
return response.data;
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Get process status by process_id
|
|
2463
|
+
* @param processId Process ID (string)
|
|
2464
|
+
* @returns Process status response
|
|
2465
|
+
*/
|
|
2466
|
+
async getProcessStatus(processId) {
|
|
2467
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2468
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2469
|
+
const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace("{process_id}", processId);
|
|
2470
|
+
const response = await client.get(endpoint);
|
|
2471
|
+
return response.data;
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Kill a process by process_id
|
|
2476
|
+
* @param processId Process ID (string)
|
|
2477
|
+
* @param options Optional kill options (signal)
|
|
2478
|
+
*/
|
|
2479
|
+
async killProcess(processId, options) {
|
|
2480
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2481
|
+
await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2482
|
+
const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.KILL.replace("{process_id}", processId);
|
|
2483
|
+
await client.post(endpoint, {
|
|
2484
|
+
params: options?.signal ? { signal: options.signal } : void 0
|
|
2485
|
+
});
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Get process logs by process_id
|
|
2490
|
+
* @param processId Process ID (string)
|
|
2491
|
+
* @param stream Enable log streaming (default: false)
|
|
2492
|
+
* @returns Process logs response
|
|
2493
|
+
*/
|
|
2494
|
+
async getProcessLogs(processId, stream = false) {
|
|
2495
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2496
|
+
return await urlResolver.executeWithConnection(this.name, async (client) => {
|
|
2497
|
+
const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.LOGS.replace("{process_id}", processId);
|
|
2498
|
+
const response = await client.get(endpoint, {
|
|
2499
|
+
params: { stream }
|
|
2500
|
+
});
|
|
2501
|
+
return response.data;
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
// Monitoring
|
|
2505
|
+
async getMonitorData(timeRange) {
|
|
2506
|
+
return await this.sdk.getMonitorData(this.name, timeRange);
|
|
2507
|
+
}
|
|
2508
|
+
// Health check
|
|
2509
|
+
async isHealthy() {
|
|
2510
|
+
try {
|
|
2511
|
+
const urlResolver = this.sdk.getUrlResolver();
|
|
2512
|
+
return await urlResolver.checkDevboxHealth(this.name);
|
|
2513
|
+
} catch {
|
|
2514
|
+
return false;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Wait for the Devbox to be ready and healthy
|
|
2519
|
+
* @param timeoutOrOptions Timeout in milliseconds (for backward compatibility) or options object
|
|
2520
|
+
* @param checkInterval Check interval in milliseconds (for backward compatibility, ignored if first param is options)
|
|
2521
|
+
*/
|
|
2522
|
+
async waitForReady(timeoutOrOptions, checkInterval = 2e3) {
|
|
2523
|
+
const options = typeof timeoutOrOptions === "number" ? {
|
|
2524
|
+
timeout: timeoutOrOptions,
|
|
2525
|
+
checkInterval,
|
|
2526
|
+
// For backward compatibility: if explicitly called with numbers, use fixed interval
|
|
2527
|
+
useExponentialBackoff: false
|
|
2528
|
+
} : timeoutOrOptions ?? {
|
|
2529
|
+
// Default: use exponential backoff when called without params
|
|
2530
|
+
useExponentialBackoff: true
|
|
2531
|
+
};
|
|
2532
|
+
const {
|
|
2533
|
+
timeout = 3e5,
|
|
2534
|
+
// 5 minutes
|
|
2535
|
+
checkInterval: fixedInterval,
|
|
2536
|
+
useExponentialBackoff = fixedInterval === void 0,
|
|
2537
|
+
initialCheckInterval = 200,
|
|
2538
|
+
// 0.2 seconds - faster initial checks
|
|
2539
|
+
maxCheckInterval = 5e3,
|
|
2540
|
+
// 5 seconds
|
|
2541
|
+
backoffMultiplier = 1.5
|
|
2542
|
+
} = options;
|
|
2543
|
+
const startTime = Date.now();
|
|
2544
|
+
let currentInterval = useExponentialBackoff ? initialCheckInterval : fixedInterval ?? 2e3;
|
|
2545
|
+
let checkCount = 0;
|
|
2546
|
+
let lastStatus = this.status;
|
|
2547
|
+
while (Date.now() - startTime < timeout) {
|
|
2548
|
+
try {
|
|
2549
|
+
await this.refreshInfo();
|
|
2550
|
+
if (this.status === "Running") {
|
|
2551
|
+
const healthy = await this.isHealthy();
|
|
2552
|
+
if (healthy) {
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
if (lastStatus !== "Running") {
|
|
2556
|
+
currentInterval = Math.min(initialCheckInterval * 2, 1e3);
|
|
2557
|
+
checkCount = 1;
|
|
2558
|
+
}
|
|
2559
|
+
} else if (lastStatus !== this.status) {
|
|
2560
|
+
currentInterval = initialCheckInterval;
|
|
2561
|
+
checkCount = 0;
|
|
2562
|
+
}
|
|
2563
|
+
lastStatus = this.status;
|
|
2564
|
+
} catch {
|
|
2565
|
+
}
|
|
2566
|
+
if (useExponentialBackoff) {
|
|
2567
|
+
currentInterval = Math.min(
|
|
2568
|
+
initialCheckInterval * backoffMultiplier ** checkCount,
|
|
2569
|
+
maxCheckInterval
|
|
2570
|
+
);
|
|
2571
|
+
checkCount++;
|
|
2572
|
+
}
|
|
2573
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
2574
|
+
}
|
|
2575
|
+
throw new Error(`Devbox '${this.name}' did not become ready within ${timeout}ms`);
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Get detailed information about the instance
|
|
2579
|
+
*/
|
|
2580
|
+
async getDetailedInfo() {
|
|
2581
|
+
await this.refreshInfo();
|
|
2582
|
+
return { ...this.info };
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
|
|
2586
|
+
// src/core/devbox-sdk.ts
|
|
2587
|
+
var DevboxSDK = class {
|
|
2588
|
+
apiClient;
|
|
2589
|
+
urlResolver;
|
|
2590
|
+
constructor(config) {
|
|
2591
|
+
this.apiClient = new DevboxAPI({
|
|
2592
|
+
kubeconfig: config.kubeconfig,
|
|
2593
|
+
baseUrl: config.baseUrl,
|
|
2594
|
+
timeout: config.http?.timeout,
|
|
2595
|
+
retries: config.http?.retries,
|
|
2596
|
+
rejectUnauthorized: config.http?.rejectUnauthorized
|
|
2597
|
+
});
|
|
2598
|
+
this.urlResolver = new ContainerUrlResolver(config);
|
|
2599
|
+
this.urlResolver.setAPIClient(this.apiClient);
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Create a new Devbox instance (async, returns immediately without waiting)
|
|
2603
|
+
* @param config Devbox creation configuration
|
|
2604
|
+
* @returns DevboxInstance (may not be ready immediately - use waitForReady() if needed)
|
|
2605
|
+
* @description This method returns immediately after creating the Devbox without waiting for it to be ready.
|
|
2606
|
+
* The returned instance may not be ready for file operations or commands.
|
|
2607
|
+
* Use `createDevbox()` (default behavior) or call `waitForReady()` on the instance if you need to wait.
|
|
2608
|
+
*/
|
|
2609
|
+
async createDevboxAsync(config) {
|
|
2610
|
+
const devboxInfo = await this.apiClient.createDevbox(config);
|
|
2611
|
+
return new DevboxInstance(devboxInfo, this);
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Create a new Devbox instance
|
|
2615
|
+
* @param config Devbox creation configuration
|
|
2616
|
+
* @param options Creation options (waitUntilReady defaults to true)
|
|
2617
|
+
* @returns DevboxInstance (ready for use if waitUntilReady is true)
|
|
2618
|
+
* @description By default, this method waits for the Devbox to be fully ready before returning.
|
|
2619
|
+
* Set `options.waitUntilReady = false` to return immediately without waiting.
|
|
2620
|
+
*/
|
|
2621
|
+
async createDevbox(config, options = {}) {
|
|
2622
|
+
const {
|
|
2623
|
+
waitUntilReady = true,
|
|
2624
|
+
timeout = 18e4,
|
|
2625
|
+
// 3 minutes
|
|
2626
|
+
checkInterval,
|
|
2627
|
+
useExponentialBackoff = true,
|
|
2628
|
+
initialCheckInterval = 200,
|
|
2629
|
+
// 0.2 seconds - faster initial checks
|
|
2630
|
+
maxCheckInterval = 5e3,
|
|
2631
|
+
// 5 seconds
|
|
2632
|
+
backoffMultiplier = 1.5
|
|
2633
|
+
} = options;
|
|
2634
|
+
const instance = await this.createDevboxAsync(config);
|
|
2635
|
+
if (waitUntilReady) {
|
|
2636
|
+
await instance.waitForReady({
|
|
2637
|
+
timeout,
|
|
2638
|
+
checkInterval,
|
|
2639
|
+
useExponentialBackoff,
|
|
2640
|
+
initialCheckInterval,
|
|
2641
|
+
maxCheckInterval,
|
|
2642
|
+
backoffMultiplier
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
return instance;
|
|
2646
|
+
}
|
|
2647
|
+
async getDevbox(name) {
|
|
2648
|
+
const devboxInfo = await this.apiClient.getDevbox(name);
|
|
2649
|
+
return new DevboxInstance(devboxInfo, this);
|
|
2650
|
+
}
|
|
2651
|
+
async listDevboxes() {
|
|
2652
|
+
const devboxes = await this.apiClient.listDevboxes();
|
|
2653
|
+
return devboxes.map((info) => new DevboxInstance(info, this));
|
|
2654
|
+
}
|
|
2655
|
+
async getMonitorData(devboxName, timeRange) {
|
|
2656
|
+
return await this.apiClient.getMonitorData(devboxName, timeRange);
|
|
2657
|
+
}
|
|
2658
|
+
async close() {
|
|
2659
|
+
await this.urlResolver.closeAllConnections();
|
|
2660
|
+
}
|
|
2661
|
+
getAPIClient() {
|
|
2662
|
+
return this.apiClient;
|
|
2663
|
+
}
|
|
2664
|
+
getUrlResolver() {
|
|
2665
|
+
return this.urlResolver;
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
|
|
2669
|
+
// src/index.ts
|
|
2670
|
+
var VERSION = "1.0.0";
|
|
2671
|
+
var index_default = DevboxSDK;
|
|
2672
|
+
|
|
2673
|
+
exports.API_ENDPOINTS = API_ENDPOINTS;
|
|
2674
|
+
exports.AuthenticationError = AuthenticationError;
|
|
2675
|
+
exports.ConnectionError = ConnectionError;
|
|
2676
|
+
exports.ContainerUrlResolver = ContainerUrlResolver;
|
|
2677
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
2678
|
+
exports.DevboxAPI = DevboxAPI;
|
|
2679
|
+
exports.DevboxContainerClient = DevboxContainerClient;
|
|
2680
|
+
exports.DevboxInstance = DevboxInstance;
|
|
2681
|
+
exports.DevboxNotFoundError = DevboxNotFoundError;
|
|
2682
|
+
exports.DevboxNotReadyError = DevboxNotReadyError;
|
|
2683
|
+
exports.DevboxRuntime = DevboxRuntime;
|
|
2684
|
+
exports.DevboxSDK = DevboxSDK;
|
|
2685
|
+
exports.DevboxSDKError = DevboxSDKError;
|
|
2686
|
+
exports.ERROR_CODES = ERROR_CODES;
|
|
2687
|
+
exports.FileOperationError = FileOperationError;
|
|
2688
|
+
exports.HTTP_STATUS = HTTP_STATUS;
|
|
2689
|
+
exports.VERSION = VERSION;
|
|
2690
|
+
exports.ValidationError = ValidationError;
|
|
2691
|
+
exports.default = index_default;
|