@mks2508/coolify-mks-cli-mcp 0.6.3 → 0.9.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/dist/cli/coolify-state.d.ts +101 -5
- package/dist/cli/coolify-state.d.ts.map +1 -1
- package/dist/cli/index.js +23165 -11543
- package/dist/cli/ui/highlighter.d.ts +28 -0
- package/dist/cli/ui/highlighter.d.ts.map +1 -0
- package/dist/cli/ui/index.d.ts +9 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/spinners.d.ts +100 -0
- package/dist/cli/ui/spinners.d.ts.map +1 -0
- package/dist/cli/ui/tables.d.ts +103 -0
- package/dist/cli/ui/tables.d.ts.map +1 -0
- package/dist/coolify/config.d.ts +25 -0
- package/dist/coolify/config.d.ts.map +1 -1
- package/dist/coolify/index.d.ts +139 -12
- package/dist/coolify/index.d.ts.map +1 -1
- package/dist/coolify/types.d.ts +160 -2
- package/dist/coolify/types.d.ts.map +1 -1
- package/dist/examples/demo-ui.d.ts +8 -0
- package/dist/examples/demo-ui.d.ts.map +1 -0
- package/dist/index.cjs +2580 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2598 -226
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +96 -7
- package/dist/sdk.d.ts.map +1 -1
- package/dist/server/stdio.js +475 -73
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/utils/env-parser.d.ts +24 -0
- package/dist/utils/env-parser.d.ts.map +1 -0
- package/dist/utils/format.d.ts +32 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/package.json +17 -4
- package/src/cli/actions.ts +9 -2
- package/src/cli/commands/create.ts +332 -24
- package/src/cli/commands/db.ts +37 -0
- package/src/cli/commands/delete.ts +6 -2
- package/src/cli/commands/deploy.ts +347 -49
- package/src/cli/commands/deployments.ts +6 -2
- package/src/cli/commands/diagnose.ts +3 -3
- package/src/cli/commands/env.ts +424 -31
- package/src/cli/commands/exec.ts +6 -2
- package/src/cli/commands/init.ts +991 -0
- package/src/cli/commands/logs.ts +224 -24
- package/src/cli/commands/main-menu.ts +21 -0
- package/src/cli/commands/projects.ts +312 -29
- package/src/cli/commands/restart.ts +6 -2
- package/src/cli/commands/service-logs.ts +14 -0
- package/src/cli/commands/show.ts +45 -12
- package/src/cli/commands/start.ts +6 -2
- package/src/cli/commands/status.ts +554 -0
- package/src/cli/commands/stop.ts +6 -2
- package/src/cli/commands/svc.ts +7 -1
- package/src/cli/commands/update.ts +79 -2
- package/src/cli/commands/volumes.ts +293 -0
- package/src/cli/coolify-state.ts +203 -12
- package/src/cli/index.ts +138 -11
- package/src/cli/name-resolver.ts +228 -0
- package/src/cli/ui/banner.ts +276 -0
- package/src/cli/ui/highlighter.ts +176 -0
- package/src/cli/ui/index.ts +9 -0
- package/src/cli/ui/prompts.ts +155 -0
- package/src/cli/ui/screen.ts +630 -0
- package/src/cli/ui/select.ts +280 -0
- package/src/cli/ui/spinners.ts +256 -0
- package/src/cli/ui/tables.ts +407 -0
- package/src/coolify/config.ts +75 -0
- package/src/coolify/index.ts +565 -101
- package/src/coolify/types.ts +165 -2
- package/src/examples/demo-ui.ts +78 -0
- package/src/sdk.ts +211 -1
- package/src/tools/definitions.ts +22 -0
- package/src/tools/handlers.ts +19 -0
- package/src/utils/env-parser.ts +45 -0
- package/src/utils/format.ts +178 -0
package/src/coolify/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ICoolifyDeployResult,
|
|
22
22
|
type ICoolifyDestination,
|
|
23
23
|
type ICoolifyEnvironment,
|
|
24
|
+
type ICoolifyGithubApp,
|
|
24
25
|
type ICoolifyLogs,
|
|
25
26
|
type ICoolifyLogsOptions,
|
|
26
27
|
type ICoolifyPrivateKey,
|
|
@@ -32,6 +33,10 @@ import {
|
|
|
32
33
|
type ICoolifyTeam,
|
|
33
34
|
type ICoolifyUpdateOptions,
|
|
34
35
|
type ICoolifyVersion,
|
|
36
|
+
type ICoolifyInfrastructureTree,
|
|
37
|
+
type ICoolifyProjectNode,
|
|
38
|
+
type ICoolifyEnvironmentNode,
|
|
39
|
+
type ICoolifyResource,
|
|
35
40
|
type IProgressCallback,
|
|
36
41
|
} from "./types.js";
|
|
37
42
|
|
|
@@ -180,7 +185,12 @@ export class CoolifyService {
|
|
|
180
185
|
|
|
181
186
|
try {
|
|
182
187
|
data = text ? JSON.parse(text) : undefined;
|
|
183
|
-
} catch {
|
|
188
|
+
} catch (parseErr) {
|
|
189
|
+
const preview = text ? text.slice(0, 200) : "(empty body)";
|
|
190
|
+
const method = options.method ?? "GET";
|
|
191
|
+
log.warn(
|
|
192
|
+
`Failed to parse JSON response from ${method} ${endpoint} (status ${response.status}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}. Body preview: ${preview}`,
|
|
193
|
+
);
|
|
184
194
|
if (!response.ok) {
|
|
185
195
|
return {
|
|
186
196
|
error: text || `HTTP ${response.status}`,
|
|
@@ -188,6 +198,13 @@ export class CoolifyService {
|
|
|
188
198
|
durationMs,
|
|
189
199
|
};
|
|
190
200
|
}
|
|
201
|
+
// OK status but body is not JSON — synthesize an error so the caller's error path triggers.
|
|
202
|
+
// Avoids silently returning undefined data which causes cryptic downstream crashes.
|
|
203
|
+
return {
|
|
204
|
+
error: `Response was not valid JSON (status ${response.status}): ${preview}`,
|
|
205
|
+
status: response.status,
|
|
206
|
+
durationMs,
|
|
207
|
+
};
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
if (!response.ok) {
|
|
@@ -316,8 +333,8 @@ export class CoolifyService {
|
|
|
316
333
|
"private-github-app": "/applications/private-github-app",
|
|
317
334
|
"private-deploy-key": "/applications/private-deploy-key",
|
|
318
335
|
dockerfile: "/applications/dockerfile",
|
|
319
|
-
"docker-image": "/applications/
|
|
320
|
-
"docker-compose": "/applications/
|
|
336
|
+
"docker-image": "/applications/dockerimage",
|
|
337
|
+
"docker-compose": "/applications/dockercompose",
|
|
321
338
|
dockerimage: "/applications/dockerimage",
|
|
322
339
|
dockercompose: "/applications/dockercompose",
|
|
323
340
|
};
|
|
@@ -330,43 +347,53 @@ export class CoolifyService {
|
|
|
330
347
|
description: options.description,
|
|
331
348
|
project_uuid: options.projectUuid,
|
|
332
349
|
environment_uuid: options.environmentUuid,
|
|
350
|
+
environment_name: options.environmentName,
|
|
333
351
|
server_uuid: options.serverUuid,
|
|
334
352
|
};
|
|
335
353
|
|
|
354
|
+
// Git repository — applies to all types that have a repo URL.
|
|
355
|
+
// Coolify's API expects the full URL (https://, http://, git://, or git@).
|
|
356
|
+
if (options.githubRepoUrl) {
|
|
357
|
+
body.git_repository = options.githubRepoUrl;
|
|
358
|
+
}
|
|
359
|
+
|
|
336
360
|
// Type-specific fields
|
|
337
361
|
if (
|
|
338
362
|
appType === "public" ||
|
|
339
363
|
appType === "private-github-app" ||
|
|
340
364
|
appType === "private-deploy-key"
|
|
341
365
|
) {
|
|
342
|
-
if (options.githubRepoUrl) {
|
|
343
|
-
// Coolify expects 'user/repo' format, not full URL
|
|
344
|
-
body.git_repository = options.githubRepoUrl
|
|
345
|
-
.replace(/^https?:\/\/github\.com\//, "")
|
|
346
|
-
.replace(/\.git$/, "");
|
|
347
|
-
}
|
|
348
366
|
if (options.githubAppUuid) {
|
|
349
367
|
body.github_app_uuid = options.githubAppUuid;
|
|
350
368
|
}
|
|
369
|
+
// private_key_uuid is required for private-deploy-key type
|
|
370
|
+
if (appType === "private-deploy-key" && options.privateKeyUuid) {
|
|
371
|
+
body.private_key_uuid = options.privateKeyUuid;
|
|
372
|
+
}
|
|
351
373
|
body.git_branch = options.branch || "main";
|
|
352
374
|
body.build_pack = options.buildPack || "dockerfile";
|
|
353
375
|
if (options.portsExposes) {
|
|
354
376
|
body.ports_exposes = options.portsExposes;
|
|
355
377
|
}
|
|
356
378
|
// Dockerfile / Docker Compose configuration
|
|
379
|
+
// Coolify validates these paths against /^\/.*$/ regex — prepend leading slash
|
|
380
|
+
// if caller passed a relative path (CLI --help suggests "apps/x/Dockerfile" without /)
|
|
357
381
|
if (options.dockerfileLocation) {
|
|
358
|
-
|
|
382
|
+
const p = options.dockerfileLocation;
|
|
383
|
+
body.dockerfile_location = p.startsWith("/") ? p : `/${p}`;
|
|
359
384
|
}
|
|
360
385
|
if (options.dockerComposeLocation) {
|
|
361
|
-
|
|
386
|
+
const p = options.dockerComposeLocation;
|
|
387
|
+
body.docker_compose_location = p.startsWith("/") ? p : `/${p}`;
|
|
362
388
|
}
|
|
363
389
|
if (options.baseDirectory) {
|
|
364
|
-
|
|
390
|
+
const p = options.baseDirectory;
|
|
391
|
+
body.base_directory = p.startsWith("/") ? p : `/${p}`;
|
|
365
392
|
}
|
|
366
393
|
} else if (appType === "docker-image" && options.dockerImage) {
|
|
367
|
-
body.
|
|
394
|
+
body.docker_registry_image_name = options.dockerImage;
|
|
368
395
|
} else if (appType === "docker-compose" && options.dockerCompose) {
|
|
369
|
-
body.
|
|
396
|
+
body.docker_compose_raw = options.dockerCompose;
|
|
370
397
|
}
|
|
371
398
|
|
|
372
399
|
log.debug(`Create application body: ${JSON.stringify(body, null, 2)}`);
|
|
@@ -394,6 +421,15 @@ export class CoolifyService {
|
|
|
394
421
|
/**
|
|
395
422
|
* Sets environment variables for an application.
|
|
396
423
|
*
|
|
424
|
+
* Delegates to {@link bulkUpdateEnvironmentVariables} so the same
|
|
425
|
+
* create-or-update logic applies. This fixes the post-delete scenario
|
|
426
|
+
* (where the variable was deleted via the API and only its base value
|
|
427
|
+
* from docker-compose/git remains visible) and prevents duplicates
|
|
428
|
+
* that the singular POST endpoint would create when called for a key
|
|
429
|
+
* that already exists. Also eliminates the N+1 API calls and the
|
|
430
|
+
* TOCTOU race between the DELETE and the re-POST that the previous
|
|
431
|
+
* per-key POST -> DELETE -> re-POST dance had.
|
|
432
|
+
*
|
|
397
433
|
* @param appUuid - Application UUID
|
|
398
434
|
* @param envVars - Environment variables to set
|
|
399
435
|
* @returns Result indicating success or error
|
|
@@ -406,43 +442,19 @@ export class CoolifyService {
|
|
|
406
442
|
`Setting ${Object.keys(envVars).length} environment variables for ${appUuid}`,
|
|
407
443
|
);
|
|
408
444
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
445
|
+
const vars = Object.entries(envVars).map(([key, value]) => ({
|
|
446
|
+
key,
|
|
447
|
+
value,
|
|
448
|
+
is_buildtime: false,
|
|
449
|
+
is_runtime: true,
|
|
450
|
+
}));
|
|
415
451
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
Array<{ uuid: string; key: string }>
|
|
420
|
-
>(`/applications/${appUuid}/envs`);
|
|
421
|
-
const existing = listResult.data?.find((v) => v.key === key);
|
|
422
|
-
if (existing) {
|
|
423
|
-
await this.request(`/applications/${appUuid}/envs/${existing.uuid}`, {
|
|
424
|
-
method: "DELETE",
|
|
425
|
-
});
|
|
426
|
-
const repost = await this.request(`/applications/${appUuid}/envs`, {
|
|
427
|
-
method: "POST",
|
|
428
|
-
body: JSON.stringify({ key, value, is_preview: false }),
|
|
429
|
-
});
|
|
430
|
-
if (repost.error) {
|
|
431
|
-
log.error(`Failed to update env var ${key}: ${repost.error}`);
|
|
432
|
-
return err(new Error(`Failed to update ${key}: ${repost.error}`));
|
|
433
|
-
}
|
|
434
|
-
log.debug(`Updated existing env var: ${key}`);
|
|
435
|
-
} else {
|
|
436
|
-
log.error(`Failed to set env var ${key}: ${result.error}`);
|
|
437
|
-
return err(new Error(`Failed to set ${key}: ${result.error}`));
|
|
438
|
-
}
|
|
439
|
-
} else if (result.error) {
|
|
440
|
-
log.error(`Failed to set env var ${key}: ${result.error}`);
|
|
441
|
-
return err(new Error(`Failed to set ${key}: ${result.error}`));
|
|
442
|
-
}
|
|
452
|
+
const result = await this.bulkUpdateEnvironmentVariables(appUuid, vars);
|
|
453
|
+
if (isErr(result)) {
|
|
454
|
+
return err(result.error);
|
|
443
455
|
}
|
|
444
456
|
|
|
445
|
-
log.success(`${
|
|
457
|
+
log.success(`${vars.length} environment variables set`);
|
|
446
458
|
return ok(undefined);
|
|
447
459
|
}
|
|
448
460
|
|
|
@@ -472,60 +484,41 @@ export class CoolifyService {
|
|
|
472
484
|
|
|
473
485
|
/**
|
|
474
486
|
* Sets a single environment variable for an application.
|
|
475
|
-
*
|
|
487
|
+
*
|
|
488
|
+
* Delegates to {@link bulkUpdateEnvironmentVariables} so the same
|
|
489
|
+
* create-or-update logic applies. This fixes the post-delete scenario
|
|
490
|
+
* (where the variable was deleted via the API and only its base value
|
|
491
|
+
* from docker-compose/git remains visible) and prevents duplicates
|
|
492
|
+
* that the singular PATCH endpoint would create when called without
|
|
493
|
+
* the variable's UUID.
|
|
476
494
|
*
|
|
477
495
|
* @param appUuid - Application UUID
|
|
478
496
|
* @param key - Variable name
|
|
479
497
|
* @param value - Variable value
|
|
480
|
-
* @param isBuildTime - Whether the variable is available at build time
|
|
498
|
+
* @param isBuildTime - Whether the variable is available at build time
|
|
499
|
+
* (only for new vars; existing runtime-only vars will be flipped to
|
|
500
|
+
* build-time and vice versa).
|
|
481
501
|
* @returns Result indicating success or error
|
|
482
502
|
*/
|
|
483
503
|
async setEnvironmentVariable(
|
|
484
504
|
appUuid: string,
|
|
485
505
|
key: string,
|
|
486
506
|
value: string,
|
|
487
|
-
|
|
507
|
+
isBuildTime: boolean = false,
|
|
488
508
|
): Promise<Result<void, Error>> {
|
|
489
|
-
log.info(`Setting environment variable ${key} for ${appUuid}`);
|
|
490
|
-
|
|
491
|
-
// Check if variable already exists
|
|
492
|
-
const existingVars = await this.getEnvironmentVariables(appUuid);
|
|
493
|
-
if (isErr(existingVars)) {
|
|
494
|
-
return err(existingVars.error);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const exists = existingVars.value.some((ev) => ev.key === key);
|
|
509
|
+
log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
|
|
498
510
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
},
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
if (result.error) {
|
|
511
|
-
log.error(`Failed to update env var: ${result.error}`);
|
|
512
|
-
return err(new Error(result.error));
|
|
513
|
-
}
|
|
514
|
-
} else {
|
|
515
|
-
// Use POST to create new variable
|
|
516
|
-
log.debug(`Variable ${key} does not exist, using POST to create`);
|
|
517
|
-
const result = await this.request<{ uuid: string }>(
|
|
518
|
-
`/applications/${appUuid}/envs`,
|
|
519
|
-
{
|
|
520
|
-
method: "POST",
|
|
521
|
-
body: JSON.stringify({ key, value }),
|
|
522
|
-
},
|
|
523
|
-
);
|
|
511
|
+
const result = await this.bulkUpdateEnvironmentVariables(appUuid, [
|
|
512
|
+
{
|
|
513
|
+
key,
|
|
514
|
+
value,
|
|
515
|
+
is_buildtime: isBuildTime,
|
|
516
|
+
is_runtime: !isBuildTime,
|
|
517
|
+
},
|
|
518
|
+
]);
|
|
524
519
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return err(new Error(result.error));
|
|
528
|
-
}
|
|
520
|
+
if (isErr(result)) {
|
|
521
|
+
return err(result.error);
|
|
529
522
|
}
|
|
530
523
|
|
|
531
524
|
log.success(`Environment variable ${key} set for ${appUuid}`);
|
|
@@ -649,12 +642,7 @@ export class CoolifyService {
|
|
|
649
642
|
async listGithubApps(
|
|
650
643
|
page?: number,
|
|
651
644
|
perPage?: number,
|
|
652
|
-
): Promise<
|
|
653
|
-
Result<
|
|
654
|
-
Array<{ id: number; uuid: string; name: string; is_public: boolean }>,
|
|
655
|
-
Error
|
|
656
|
-
>
|
|
657
|
-
> {
|
|
645
|
+
): Promise<Result<ICoolifyGithubApp[], Error>> {
|
|
658
646
|
let endpoint = "/github-apps";
|
|
659
647
|
const params = new URLSearchParams();
|
|
660
648
|
if (page) params.set("page", page.toString());
|
|
@@ -663,14 +651,40 @@ export class CoolifyService {
|
|
|
663
651
|
endpoint += `?${params.toString()}`;
|
|
664
652
|
}
|
|
665
653
|
|
|
666
|
-
const result =
|
|
667
|
-
await this.request<
|
|
668
|
-
Array<{ id: number; uuid: string; name: string; is_public: boolean }>
|
|
669
|
-
>(endpoint);
|
|
654
|
+
const result = await this.request<ICoolifyGithubApp[]>(endpoint);
|
|
670
655
|
if (result.error) return err(new Error(result.error));
|
|
671
656
|
return ok(result.data || []);
|
|
672
657
|
}
|
|
673
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Lists all GitHub Apps configured in Coolify.
|
|
661
|
+
* Fetches all pages internally and returns a flat list.
|
|
662
|
+
*
|
|
663
|
+
* @param perPage - Items per page for each request (default: 50)
|
|
664
|
+
* @returns Result with all GitHub Apps or error
|
|
665
|
+
*/
|
|
666
|
+
async listGithubAppsAll(
|
|
667
|
+
perPage = 50,
|
|
668
|
+
): Promise<Result<ICoolifyGithubApp[], Error>> {
|
|
669
|
+
const allApps: ICoolifyGithubApp[] = [];
|
|
670
|
+
let page = 1;
|
|
671
|
+
|
|
672
|
+
while (true) {
|
|
673
|
+
const result = await this.listGithubApps(page, perPage);
|
|
674
|
+
if (isErr(result)) return err(result.error);
|
|
675
|
+
const apps = result.value;
|
|
676
|
+
|
|
677
|
+
if (apps.length === 0) break;
|
|
678
|
+
allApps.push(...apps);
|
|
679
|
+
|
|
680
|
+
// If we got fewer than perPage, we've reached the last page
|
|
681
|
+
if (apps.length < perPage) break;
|
|
682
|
+
page++;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return ok(allApps);
|
|
686
|
+
}
|
|
687
|
+
|
|
674
688
|
/**
|
|
675
689
|
* Lists all projects.
|
|
676
690
|
*
|
|
@@ -906,10 +920,32 @@ export class CoolifyService {
|
|
|
906
920
|
if (options.domains) body.domains = options.domains;
|
|
907
921
|
if (options.dockerComposeDomains)
|
|
908
922
|
body.docker_compose_domains = options.dockerComposeDomains;
|
|
923
|
+
if (options.dockerComposeRaw !== undefined)
|
|
924
|
+
body.docker_compose_raw = options.dockerComposeRaw;
|
|
909
925
|
if (options.isForceHttpsEnabled !== undefined)
|
|
910
926
|
body.is_force_https_enabled = options.isForceHttpsEnabled;
|
|
911
927
|
if (options.isAutoDeployEnabled !== undefined)
|
|
912
928
|
body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
|
|
929
|
+
if (options.watchPaths !== undefined)
|
|
930
|
+
body.watch_paths = options.watchPaths;
|
|
931
|
+
if (options.healthCheckEnabled !== undefined)
|
|
932
|
+
body.health_check_enabled = options.healthCheckEnabled;
|
|
933
|
+
if (options.healthCheckPath)
|
|
934
|
+
body.health_check_path = options.healthCheckPath;
|
|
935
|
+
if (options.healthCheckPort)
|
|
936
|
+
body.health_check_port = String(options.healthCheckPort);
|
|
937
|
+
if (options.healthCheckMethod)
|
|
938
|
+
body.health_check_method = options.healthCheckMethod;
|
|
939
|
+
if (options.healthCheckInterval)
|
|
940
|
+
body.health_check_interval = options.healthCheckInterval;
|
|
941
|
+
if (options.healthCheckTimeout)
|
|
942
|
+
body.health_check_timeout = options.healthCheckTimeout;
|
|
943
|
+
if (options.healthCheckRetries)
|
|
944
|
+
body.health_check_retries = options.healthCheckRetries;
|
|
945
|
+
if (options.healthCheckStartPeriod)
|
|
946
|
+
body.health_check_start_period = options.healthCheckStartPeriod;
|
|
947
|
+
if (options.healthCheckReturnCode)
|
|
948
|
+
body.health_check_return_code = options.healthCheckReturnCode;
|
|
913
949
|
|
|
914
950
|
const result = await this.request<ICoolifyApplication>(
|
|
915
951
|
`/applications/${appUuid}`,
|
|
@@ -1004,8 +1040,15 @@ export class CoolifyService {
|
|
|
1004
1040
|
/**
|
|
1005
1041
|
* Bulk updates environment variables for an application.
|
|
1006
1042
|
*
|
|
1043
|
+
* Uses the bulk endpoint `PATCH /applications/{appUuid}/envs/bulk` which
|
|
1044
|
+
* has create-or-update semantics: if the variable exists in the override
|
|
1045
|
+
* table, its value is updated; otherwise a new override is created. This
|
|
1046
|
+
* is the only reliable way to update a variable that has been deleted
|
|
1047
|
+
* (i.e. its base value from docker-compose/git is still visible in
|
|
1048
|
+
* `getEnvironmentVariables` but no override row exists).
|
|
1049
|
+
*
|
|
1007
1050
|
* @param appUuid - Application UUID
|
|
1008
|
-
* @param envVars - Array of
|
|
1051
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1009
1052
|
* @returns Result indicating success or error
|
|
1010
1053
|
*/
|
|
1011
1054
|
async bulkUpdateEnvironmentVariables(
|
|
@@ -1014,15 +1057,19 @@ export class CoolifyService {
|
|
|
1014
1057
|
key: string;
|
|
1015
1058
|
value: string;
|
|
1016
1059
|
is_preview?: boolean;
|
|
1060
|
+
is_buildtime?: boolean;
|
|
1061
|
+
is_runtime?: boolean;
|
|
1017
1062
|
}>,
|
|
1018
1063
|
): Promise<Result<{ message: string }, Error>> {
|
|
1019
1064
|
log.info(`Bulk updating ${envVars.length} env vars for ${appUuid}`);
|
|
1020
1065
|
|
|
1066
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
1067
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
1021
1068
|
const result = await this.request<{ message: string }>(
|
|
1022
1069
|
`/applications/${appUuid}/envs/bulk`,
|
|
1023
1070
|
{
|
|
1024
1071
|
method: "PATCH",
|
|
1025
|
-
body: JSON.stringify(envVars),
|
|
1072
|
+
body: JSON.stringify({ data: envVars }),
|
|
1026
1073
|
},
|
|
1027
1074
|
);
|
|
1028
1075
|
|
|
@@ -1035,6 +1082,50 @@ export class CoolifyService {
|
|
|
1035
1082
|
return ok(result.data || { message: "Environment variables updated" });
|
|
1036
1083
|
}
|
|
1037
1084
|
|
|
1085
|
+
/**
|
|
1086
|
+
* Bulk updates environment variables for a database.
|
|
1087
|
+
*
|
|
1088
|
+
* Uses the bulk endpoint `PATCH /databases/{databaseUuid}/envs/bulk` which
|
|
1089
|
+
* has create-or-update semantics. Note: the database schema is narrower
|
|
1090
|
+
* than applications — it accepts `key`, `value`, `is_literal`,
|
|
1091
|
+
* `is_multiline`, `is_shown_once` but does NOT accept `is_preview`,
|
|
1092
|
+
* `is_buildtime` or `is_runtime` (those flags are application-only).
|
|
1093
|
+
*
|
|
1094
|
+
* @param databaseUuid - Database UUID
|
|
1095
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1096
|
+
* @returns Result indicating success or error
|
|
1097
|
+
*/
|
|
1098
|
+
async bulkUpdateDatabaseEnvVars(
|
|
1099
|
+
databaseUuid: string,
|
|
1100
|
+
envVars: Array<{
|
|
1101
|
+
key: string;
|
|
1102
|
+
value: string;
|
|
1103
|
+
is_literal?: boolean;
|
|
1104
|
+
is_multiline?: boolean;
|
|
1105
|
+
is_shown_once?: boolean;
|
|
1106
|
+
}>,
|
|
1107
|
+
): Promise<Result<{ message: string }, Error>> {
|
|
1108
|
+
log.info(`Bulk updating ${envVars.length} env vars for database ${databaseUuid}`);
|
|
1109
|
+
|
|
1110
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
1111
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
1112
|
+
const result = await this.request<{ message: string }>(
|
|
1113
|
+
`/databases/${databaseUuid}/envs/bulk`,
|
|
1114
|
+
{
|
|
1115
|
+
method: "PATCH",
|
|
1116
|
+
body: JSON.stringify({ data: envVars }),
|
|
1117
|
+
},
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
if (result.error) {
|
|
1121
|
+
log.error(`Failed to bulk update database env vars: ${result.error}`);
|
|
1122
|
+
return err(new Error(result.error));
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
log.success(`Bulk updated ${envVars.length} env vars for database ${databaseUuid}`);
|
|
1126
|
+
return ok(result.data || { message: "Environment variables updated" });
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1038
1129
|
/**
|
|
1039
1130
|
* Gets deployment history for an application.
|
|
1040
1131
|
*
|
|
@@ -1620,6 +1711,186 @@ export class CoolifyService {
|
|
|
1620
1711
|
return ok({ success: true, message: "Service deleted" });
|
|
1621
1712
|
}
|
|
1622
1713
|
|
|
1714
|
+
/**
|
|
1715
|
+
* Gets the full infrastructure tree: Projects → Environments → Resources.
|
|
1716
|
+
*
|
|
1717
|
+
* Fetches all projects, apps, databases, and services in parallel,
|
|
1718
|
+
* then groups them by environment_id into a hierarchical tree.
|
|
1719
|
+
*
|
|
1720
|
+
* @returns Result with the full infrastructure tree or error
|
|
1721
|
+
*/
|
|
1722
|
+
async getInfrastructureTree(): Promise<
|
|
1723
|
+
Result<ICoolifyInfrastructureTree, Error>
|
|
1724
|
+
> {
|
|
1725
|
+
log.info("Building infrastructure tree");
|
|
1726
|
+
|
|
1727
|
+
// Fetch all data in parallel
|
|
1728
|
+
const [projectsResult, appsResult, dbsResult, svcsResult, serversResult] =
|
|
1729
|
+
await Promise.all([
|
|
1730
|
+
this.listProjects(),
|
|
1731
|
+
this.listApplications(),
|
|
1732
|
+
this.listDatabases(),
|
|
1733
|
+
this.listServices(),
|
|
1734
|
+
this.listServers(),
|
|
1735
|
+
]);
|
|
1736
|
+
|
|
1737
|
+
if (isErr(projectsResult)) return err(projectsResult.error);
|
|
1738
|
+
if (isErr(appsResult)) return err(appsResult.error);
|
|
1739
|
+
if (isErr(serversResult)) return err(serversResult.error);
|
|
1740
|
+
|
|
1741
|
+
const projects = projectsResult.value;
|
|
1742
|
+
const apps = appsResult.value;
|
|
1743
|
+
const dbs = isErr(dbsResult) ? [] : dbsResult.value;
|
|
1744
|
+
const svcs = isErr(svcsResult) ? [] : svcsResult.value;
|
|
1745
|
+
const servers = serversResult.value;
|
|
1746
|
+
|
|
1747
|
+
// Fetch environments for each project in parallel
|
|
1748
|
+
const envResults = await Promise.allSettled(
|
|
1749
|
+
projects.map((p) => this.getProjectEnvironments(p.uuid)),
|
|
1750
|
+
);
|
|
1751
|
+
|
|
1752
|
+
// Build environment_id → { projectUuid, envName, envUuid } mapping
|
|
1753
|
+
const envIdMap = new Map<
|
|
1754
|
+
number,
|
|
1755
|
+
{ projectUuid: string; envName: string; envUuid: string }
|
|
1756
|
+
>();
|
|
1757
|
+
|
|
1758
|
+
for (let i = 0; i < projects.length; i++) {
|
|
1759
|
+
const envResult = envResults[i];
|
|
1760
|
+
if (envResult.status === "fulfilled" && !isErr(envResult.value)) {
|
|
1761
|
+
for (const env of envResult.value.value) {
|
|
1762
|
+
envIdMap.set(env.id, {
|
|
1763
|
+
projectUuid: projects[i].uuid,
|
|
1764
|
+
envName: env.name,
|
|
1765
|
+
envUuid: env.uuid,
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// Build project nodes
|
|
1772
|
+
const projectNodes: ICoolifyProjectNode[] = projects.map((p) => ({
|
|
1773
|
+
uuid: p.uuid,
|
|
1774
|
+
name: p.name,
|
|
1775
|
+
description: p.description,
|
|
1776
|
+
environments: [],
|
|
1777
|
+
}));
|
|
1778
|
+
|
|
1779
|
+
const projectMap = new Map<string, ICoolifyProjectNode>();
|
|
1780
|
+
for (const node of projectNodes) {
|
|
1781
|
+
projectMap.set(node.uuid, node);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Populate environments from envIdMap
|
|
1785
|
+
const envNodeMap = new Map<number, ICoolifyEnvironmentNode>();
|
|
1786
|
+
for (const [envId, info] of envIdMap) {
|
|
1787
|
+
const project = projectMap.get(info.projectUuid);
|
|
1788
|
+
if (!project) continue;
|
|
1789
|
+
|
|
1790
|
+
let envNode = project.environments.find((e) => e.id === envId);
|
|
1791
|
+
if (!envNode) {
|
|
1792
|
+
envNode = {
|
|
1793
|
+
id: envId,
|
|
1794
|
+
uuid: info.envUuid,
|
|
1795
|
+
name: info.envName,
|
|
1796
|
+
resources: [],
|
|
1797
|
+
};
|
|
1798
|
+
project.environments.push(envNode);
|
|
1799
|
+
}
|
|
1800
|
+
envNodeMap.set(envId, envNode);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// Assign apps to environments
|
|
1804
|
+
for (const app of apps) {
|
|
1805
|
+
const envNode = app.environment_id
|
|
1806
|
+
? envNodeMap.get(app.environment_id)
|
|
1807
|
+
: undefined;
|
|
1808
|
+
const resource: ICoolifyResource = {
|
|
1809
|
+
uuid: app.uuid,
|
|
1810
|
+
name: app.name,
|
|
1811
|
+
kind: "app",
|
|
1812
|
+
status: app.status,
|
|
1813
|
+
fqdn: app.fqdn,
|
|
1814
|
+
};
|
|
1815
|
+
if (envNode) {
|
|
1816
|
+
envNode.resources.push(resource);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Assign databases to environments
|
|
1821
|
+
for (const db of dbs) {
|
|
1822
|
+
const envNode = (db as any).environment_id
|
|
1823
|
+
? envNodeMap.get((db as any).environment_id)
|
|
1824
|
+
: undefined;
|
|
1825
|
+
const resource: ICoolifyResource = {
|
|
1826
|
+
uuid: db.uuid,
|
|
1827
|
+
name: db.name,
|
|
1828
|
+
kind: "database",
|
|
1829
|
+
status: db.status,
|
|
1830
|
+
dbType: db.type,
|
|
1831
|
+
};
|
|
1832
|
+
if (envNode) {
|
|
1833
|
+
envNode.resources.push(resource);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// Assign services to environments
|
|
1838
|
+
for (const svc of svcs) {
|
|
1839
|
+
const envNode = (svc as any).environment_id
|
|
1840
|
+
? envNodeMap.get((svc as any).environment_id)
|
|
1841
|
+
: undefined;
|
|
1842
|
+
const resource: ICoolifyResource = {
|
|
1843
|
+
uuid: svc.uuid,
|
|
1844
|
+
name: svc.name,
|
|
1845
|
+
kind: "service",
|
|
1846
|
+
status: svc.status,
|
|
1847
|
+
};
|
|
1848
|
+
if (envNode) {
|
|
1849
|
+
envNode.resources.push(resource);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// Filter out empty projects
|
|
1854
|
+
const populatedProjects = projectNodes.filter(
|
|
1855
|
+
(p) => p.environments.some((e) => e.resources.length > 0),
|
|
1856
|
+
);
|
|
1857
|
+
|
|
1858
|
+
// Aggregate counts
|
|
1859
|
+
const allStatuses = [
|
|
1860
|
+
...apps.map((a) => a.status),
|
|
1861
|
+
...dbs.map((d) => d.status),
|
|
1862
|
+
...svcs.map((s) => s.status),
|
|
1863
|
+
];
|
|
1864
|
+
const counts = {
|
|
1865
|
+
projects: populatedProjects.length,
|
|
1866
|
+
apps: apps.length,
|
|
1867
|
+
databases: dbs.length,
|
|
1868
|
+
services: svcs.length,
|
|
1869
|
+
healthy: allStatuses.filter((s) => s.includes("healthy")).length,
|
|
1870
|
+
running: allStatuses.filter(
|
|
1871
|
+
(s) => s.startsWith("running") && !s.includes("healthy"),
|
|
1872
|
+
).length,
|
|
1873
|
+
stopped: allStatuses.filter((s) => s.includes("exited")).length,
|
|
1874
|
+
unhealthy: allStatuses.filter((s) => s.includes("unhealthy")).length,
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
const server = servers[0] || { name: "Unknown" };
|
|
1878
|
+
|
|
1879
|
+
log.success(
|
|
1880
|
+
`Infrastructure tree built: ${counts.projects} projects, ${counts.apps} apps, ${counts.databases} dbs, ${counts.services} svcs`,
|
|
1881
|
+
);
|
|
1882
|
+
|
|
1883
|
+
return ok({
|
|
1884
|
+
server: {
|
|
1885
|
+
name: server.name,
|
|
1886
|
+
ip: server.ip,
|
|
1887
|
+
uuid: server.uuid,
|
|
1888
|
+
},
|
|
1889
|
+
projects: populatedProjects,
|
|
1890
|
+
counts,
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1623
1894
|
/**
|
|
1624
1895
|
* Starts a service.
|
|
1625
1896
|
* Note: Coolify API uses GET for service start/stop/restart.
|
|
@@ -1701,9 +1972,65 @@ export class CoolifyService {
|
|
|
1701
1972
|
return ok(result.data || []);
|
|
1702
1973
|
}
|
|
1703
1974
|
|
|
1975
|
+
/**
|
|
1976
|
+
* Bulk updates environment variables for a service.
|
|
1977
|
+
*
|
|
1978
|
+
* Uses the bulk endpoint `PATCH /services/{uuid}/envs/bulk` which has
|
|
1979
|
+
* create-or-update semantics: if the variable exists in the override
|
|
1980
|
+
* table, its value is updated; otherwise a new override is created.
|
|
1981
|
+
*
|
|
1982
|
+
* This is the only reliable way to update a variable that has been
|
|
1983
|
+
* deleted (i.e. its base value from docker-compose/git is still visible
|
|
1984
|
+
* in `listServiceEnvVars` but no override row exists), and the only way
|
|
1985
|
+
* to set the same key twice without the `POST /services/{uuid}/envs`
|
|
1986
|
+
* returning 409 "already exists".
|
|
1987
|
+
*
|
|
1988
|
+
* @param serviceUuid - Service UUID
|
|
1989
|
+
* @param envVars - Array of variable definitions to upsert
|
|
1990
|
+
* @returns Result indicating success or error
|
|
1991
|
+
*/
|
|
1992
|
+
async bulkUpdateServiceEnvVars(
|
|
1993
|
+
serviceUuid: string,
|
|
1994
|
+
envVars: Array<{
|
|
1995
|
+
key: string;
|
|
1996
|
+
value: string;
|
|
1997
|
+
is_preview?: boolean;
|
|
1998
|
+
is_buildtime?: boolean;
|
|
1999
|
+
is_runtime?: boolean;
|
|
2000
|
+
}>,
|
|
2001
|
+
): Promise<Result<{ message: string }, Error>> {
|
|
2002
|
+
log.info(`Bulk updating ${envVars.length} env vars for service ${serviceUuid}`);
|
|
2003
|
+
|
|
2004
|
+
// Coolify bulk endpoint requires { data: [...] } envelope, not a raw array.
|
|
2005
|
+
// Sending a raw array yields 400 "Bulk data is required." from the API.
|
|
2006
|
+
const result = await this.request<{ message: string }>(
|
|
2007
|
+
`/services/${serviceUuid}/envs/bulk`,
|
|
2008
|
+
{
|
|
2009
|
+
method: "PATCH",
|
|
2010
|
+
body: JSON.stringify({ data: envVars }),
|
|
2011
|
+
},
|
|
2012
|
+
);
|
|
2013
|
+
|
|
2014
|
+
if (result.error) {
|
|
2015
|
+
log.error(`Failed to bulk update service env vars: ${result.error}`);
|
|
2016
|
+
return err(new Error(result.error));
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
log.success(`Bulk updated ${envVars.length} env vars for service ${serviceUuid}`);
|
|
2020
|
+
return ok(result.data || { message: "Environment variables updated" });
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1704
2023
|
/**
|
|
1705
2024
|
* Creates an environment variable for a service.
|
|
1706
2025
|
*
|
|
2026
|
+
* NOTE: Prefer `bulkUpdateServiceEnvVars` for any non-trivial use case.
|
|
2027
|
+
* This endpoint uses `POST /services/{uuid}/envs` and returns 409
|
|
2028
|
+
* "already exists" when the key is already present as an override,
|
|
2029
|
+
* with no recovery path on the server side. Bulk has create-or-update
|
|
2030
|
+
* semantics and handles both new and existing keys reliably.
|
|
2031
|
+
*
|
|
2032
|
+
* Kept for callers that explicitly want a strict create-only operation.
|
|
2033
|
+
*
|
|
1707
2034
|
* @param uuid - Service UUID
|
|
1708
2035
|
* @param data - Env var data (key, value, is_preview)
|
|
1709
2036
|
* @returns Result with created env var UUID or error
|
|
@@ -1721,6 +2048,110 @@ export class CoolifyService {
|
|
|
1721
2048
|
return ok(result.data!);
|
|
1722
2049
|
}
|
|
1723
2050
|
|
|
2051
|
+
/**
|
|
2052
|
+
* Deletes an environment variable from a service.
|
|
2053
|
+
*
|
|
2054
|
+
* @param serviceUuid - Service UUID
|
|
2055
|
+
* @param key - Variable name to delete
|
|
2056
|
+
* @returns Result indicating success or error
|
|
2057
|
+
*/
|
|
2058
|
+
async deleteServiceEnvVar(
|
|
2059
|
+
serviceUuid: string,
|
|
2060
|
+
key: string,
|
|
2061
|
+
): Promise<Result<void, Error>> {
|
|
2062
|
+
log.info(`Deleting environment variable ${key} from service ${serviceUuid}`);
|
|
2063
|
+
|
|
2064
|
+
// First get all env vars to find the UUID of the one to delete
|
|
2065
|
+
const envVarsResult = await this.listServiceEnvVars(serviceUuid);
|
|
2066
|
+
if (isErr(envVarsResult)) {
|
|
2067
|
+
return err(envVarsResult.error);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const envVar = envVarsResult.value.find((ev) => ev.key === key);
|
|
2071
|
+
if (!envVar) {
|
|
2072
|
+
log.error(`Environment variable ${key} not found on service ${serviceUuid}`);
|
|
2073
|
+
return err(new Error(`Environment variable ${key} not found`));
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
const result = await this.request(
|
|
2077
|
+
`/services/${serviceUuid}/envs/${envVar.uuid}`,
|
|
2078
|
+
{
|
|
2079
|
+
method: "DELETE",
|
|
2080
|
+
},
|
|
2081
|
+
);
|
|
2082
|
+
|
|
2083
|
+
if (result.error) {
|
|
2084
|
+
log.error(`Failed to delete service env var: ${result.error}`);
|
|
2085
|
+
return err(new Error(result.error));
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
log.success(`Environment variable ${key} deleted from service ${serviceUuid}`);
|
|
2089
|
+
return ok(undefined);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Lists environment variables for a database.
|
|
2094
|
+
*
|
|
2095
|
+
* @param databaseUuid - Database UUID
|
|
2096
|
+
* @returns Result with env vars list or error
|
|
2097
|
+
*/
|
|
2098
|
+
async listDatabaseEnvVars(
|
|
2099
|
+
databaseUuid: string,
|
|
2100
|
+
): Promise<Result<ICoolifyEnvVar[], Error>> {
|
|
2101
|
+
log.info(`Listing env vars for database ${databaseUuid}`);
|
|
2102
|
+
|
|
2103
|
+
const result = await this.request<ICoolifyEnvVar[]>(
|
|
2104
|
+
`/databases/${databaseUuid}/envs`,
|
|
2105
|
+
);
|
|
2106
|
+
if (result.error) {
|
|
2107
|
+
log.error(`Failed to list database env vars: ${result.error}`);
|
|
2108
|
+
return err(new Error(result.error));
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
return ok(result.data || []);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Deletes an environment variable from a database.
|
|
2116
|
+
*
|
|
2117
|
+
* @param databaseUuid - Database UUID
|
|
2118
|
+
* @param key - Variable name to delete
|
|
2119
|
+
* @returns Result indicating success or error
|
|
2120
|
+
*/
|
|
2121
|
+
async deleteDatabaseEnvVar(
|
|
2122
|
+
databaseUuid: string,
|
|
2123
|
+
key: string,
|
|
2124
|
+
): Promise<Result<void, Error>> {
|
|
2125
|
+
log.info(`Deleting environment variable ${key} from database ${databaseUuid}`);
|
|
2126
|
+
|
|
2127
|
+
// First get all env vars to find the UUID of the one to delete
|
|
2128
|
+
const envVarsResult = await this.listDatabaseEnvVars(databaseUuid);
|
|
2129
|
+
if (isErr(envVarsResult)) {
|
|
2130
|
+
return err(envVarsResult.error);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
const envVar = envVarsResult.value.find((ev) => ev.key === key);
|
|
2134
|
+
if (!envVar) {
|
|
2135
|
+
log.error(`Environment variable ${key} not found on database ${databaseUuid}`);
|
|
2136
|
+
return err(new Error(`Environment variable ${key} not found`));
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
const result = await this.request(
|
|
2140
|
+
`/databases/${databaseUuid}/envs/${envVar.uuid}`,
|
|
2141
|
+
{
|
|
2142
|
+
method: "DELETE",
|
|
2143
|
+
},
|
|
2144
|
+
);
|
|
2145
|
+
|
|
2146
|
+
if (result.error) {
|
|
2147
|
+
log.error(`Failed to delete database env var: ${result.error}`);
|
|
2148
|
+
return err(new Error(result.error));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
log.success(`Environment variable ${key} deleted from database ${databaseUuid}`);
|
|
2152
|
+
return ok(undefined);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1724
2155
|
// ===========================================================================
|
|
1725
2156
|
// Additional Server endpoints
|
|
1726
2157
|
// ===========================================================================
|
|
@@ -2295,6 +2726,35 @@ export class CoolifyService {
|
|
|
2295
2726
|
// Smart Resolution Helpers
|
|
2296
2727
|
// ===========================================================================
|
|
2297
2728
|
|
|
2729
|
+
/**
|
|
2730
|
+
* Gets full application details by UUID.
|
|
2731
|
+
*
|
|
2732
|
+
* Makes a direct GET request to /api/v1/applications/{uuid} which returns
|
|
2733
|
+
* complete application data including project_uuid and environment_uuid.
|
|
2734
|
+
*
|
|
2735
|
+
* @param appUuid - Application UUID
|
|
2736
|
+
* @returns Result with full application details or error
|
|
2737
|
+
*/
|
|
2738
|
+
async getApplication(
|
|
2739
|
+
appUuid: string,
|
|
2740
|
+
): Promise<Result<ICoolifyApplication, Error>> {
|
|
2741
|
+
log.info(`Getting application details: ${appUuid}`);
|
|
2742
|
+
|
|
2743
|
+
const result = await this.request<ICoolifyApplication>(
|
|
2744
|
+
`/applications/${appUuid}`,
|
|
2745
|
+
);
|
|
2746
|
+
|
|
2747
|
+
if (result.error) {
|
|
2748
|
+
return err(new Error(result.error));
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
if (!result.data) {
|
|
2752
|
+
return err(new Error("Application not found"));
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
return ok(result.data);
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2298
2758
|
/**
|
|
2299
2759
|
* Resolves an application by UUID, name, or domain (FQDN).
|
|
2300
2760
|
*
|
|
@@ -2878,4 +3338,8 @@ export type {
|
|
|
2878
3338
|
ICoolifyLogsOptions,
|
|
2879
3339
|
ICoolifyLogs,
|
|
2880
3340
|
IProgressCallback,
|
|
3341
|
+
ICoolifyInfrastructureTree,
|
|
3342
|
+
ICoolifyProjectNode,
|
|
3343
|
+
ICoolifyEnvironmentNode,
|
|
3344
|
+
ICoolifyResource,
|
|
2881
3345
|
} from "./types.js";
|