@mks2508/coolify-mks-cli-mcp 0.8.0 → 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.
Files changed (45) hide show
  1. package/dist/cli/coolify-state.d.ts +12 -4
  2. package/dist/cli/coolify-state.d.ts.map +1 -1
  3. package/dist/cli/index.js +8886 -7957
  4. package/dist/coolify/config.d.ts +25 -0
  5. package/dist/coolify/config.d.ts.map +1 -1
  6. package/dist/coolify/index.d.ts +118 -10
  7. package/dist/coolify/index.d.ts.map +1 -1
  8. package/dist/coolify/types.d.ts +61 -1
  9. package/dist/coolify/types.d.ts.map +1 -1
  10. package/dist/index.cjs +2267 -227
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.js +2289 -227
  13. package/dist/index.js.map +1 -1
  14. package/dist/sdk.d.ts +56 -8
  15. package/dist/sdk.d.ts.map +1 -1
  16. package/dist/server/stdio.js +253 -100
  17. package/dist/tools/definitions.d.ts.map +1 -1
  18. package/dist/tools/handlers.d.ts.map +1 -1
  19. package/dist/utils/env-parser.d.ts +24 -0
  20. package/dist/utils/env-parser.d.ts.map +1 -0
  21. package/dist/utils/format.d.ts +32 -0
  22. package/dist/utils/format.d.ts.map +1 -1
  23. package/package.json +2 -1
  24. package/src/cli/commands/create.ts +279 -37
  25. package/src/cli/commands/env.ts +348 -54
  26. package/src/cli/commands/init.ts +69 -15
  27. package/src/cli/commands/main-menu.ts +1 -1
  28. package/src/cli/commands/projects.ts +3 -3
  29. package/src/cli/commands/show.ts +39 -10
  30. package/src/cli/commands/status.ts +23 -7
  31. package/src/cli/commands/svc.ts +7 -1
  32. package/src/cli/commands/update.ts +52 -0
  33. package/src/cli/commands/volumes.ts +293 -0
  34. package/src/cli/coolify-state.ts +42 -4
  35. package/src/cli/index.ts +50 -4
  36. package/src/cli/ui/banner.ts +3 -3
  37. package/src/cli/ui/screen.ts +26 -2
  38. package/src/coolify/config.ts +75 -0
  39. package/src/coolify/index.ts +325 -106
  40. package/src/coolify/types.ts +62 -1
  41. package/src/sdk.ts +87 -39
  42. package/src/tools/definitions.ts +22 -0
  43. package/src/tools/handlers.ts +19 -0
  44. package/src/utils/env-parser.ts +45 -0
  45. package/src/utils/format.ts +178 -0
@@ -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,
@@ -184,7 +185,12 @@ export class CoolifyService {
184
185
 
185
186
  try {
186
187
  data = text ? JSON.parse(text) : undefined;
187
- } 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
+ );
188
194
  if (!response.ok) {
189
195
  return {
190
196
  error: text || `HTTP ${response.status}`,
@@ -192,6 +198,13 @@ export class CoolifyService {
192
198
  durationMs,
193
199
  };
194
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
+ };
195
208
  }
196
209
 
197
210
  if (!response.ok) {
@@ -320,8 +333,8 @@ export class CoolifyService {
320
333
  "private-github-app": "/applications/private-github-app",
321
334
  "private-deploy-key": "/applications/private-deploy-key",
322
335
  dockerfile: "/applications/dockerfile",
323
- "docker-image": "/applications/docker-image",
324
- "docker-compose": "/applications/docker-compose",
336
+ "docker-image": "/applications/dockerimage",
337
+ "docker-compose": "/applications/dockercompose",
325
338
  dockerimage: "/applications/dockerimage",
326
339
  dockercompose: "/applications/dockercompose",
327
340
  };
@@ -334,15 +347,14 @@ export class CoolifyService {
334
347
  description: options.description,
335
348
  project_uuid: options.projectUuid,
336
349
  environment_uuid: options.environmentUuid,
350
+ environment_name: options.environmentName,
337
351
  server_uuid: options.serverUuid,
338
352
  };
339
353
 
340
- // Git repository — applies to all types that have a repo URL
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@).
341
356
  if (options.githubRepoUrl) {
342
- // Coolify expects 'user/repo' format, not full URL
343
- body.git_repository = options.githubRepoUrl
344
- .replace(/^https?:\/\/github\.com\//, "")
345
- .replace(/\.git$/, "");
357
+ body.git_repository = options.githubRepoUrl;
346
358
  }
347
359
 
348
360
  // Type-specific fields
@@ -354,25 +366,34 @@ export class CoolifyService {
354
366
  if (options.githubAppUuid) {
355
367
  body.github_app_uuid = options.githubAppUuid;
356
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
+ }
357
373
  body.git_branch = options.branch || "main";
358
374
  body.build_pack = options.buildPack || "dockerfile";
359
375
  if (options.portsExposes) {
360
376
  body.ports_exposes = options.portsExposes;
361
377
  }
362
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 /)
363
381
  if (options.dockerfileLocation) {
364
- body.dockerfile_location = options.dockerfileLocation;
382
+ const p = options.dockerfileLocation;
383
+ body.dockerfile_location = p.startsWith("/") ? p : `/${p}`;
365
384
  }
366
385
  if (options.dockerComposeLocation) {
367
- body.docker_compose_location = options.dockerComposeLocation;
386
+ const p = options.dockerComposeLocation;
387
+ body.docker_compose_location = p.startsWith("/") ? p : `/${p}`;
368
388
  }
369
389
  if (options.baseDirectory) {
370
- body.base_directory = options.baseDirectory;
390
+ const p = options.baseDirectory;
391
+ body.base_directory = p.startsWith("/") ? p : `/${p}`;
371
392
  }
372
393
  } else if (appType === "docker-image" && options.dockerImage) {
373
- body.docker_image = options.dockerImage;
394
+ body.docker_registry_image_name = options.dockerImage;
374
395
  } else if (appType === "docker-compose" && options.dockerCompose) {
375
- body.docker_compose = options.dockerCompose;
396
+ body.docker_compose_raw = options.dockerCompose;
376
397
  }
377
398
 
378
399
  log.debug(`Create application body: ${JSON.stringify(body, null, 2)}`);
@@ -400,6 +421,15 @@ export class CoolifyService {
400
421
  /**
401
422
  * Sets environment variables for an application.
402
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
+ *
403
433
  * @param appUuid - Application UUID
404
434
  * @param envVars - Environment variables to set
405
435
  * @returns Result indicating success or error
@@ -412,43 +442,19 @@ export class CoolifyService {
412
442
  `Setting ${Object.keys(envVars).length} environment variables for ${appUuid}`,
413
443
  );
414
444
 
415
- // Coolify API: POST to create, PATCH to update existing
416
- for (const [key, value] of Object.entries(envVars)) {
417
- const result = await this.request(`/applications/${appUuid}/envs`, {
418
- method: "POST",
419
- body: JSON.stringify({ key, value, is_preview: false, is_buildtime: false }),
420
- });
445
+ const vars = Object.entries(envVars).map(([key, value]) => ({
446
+ key,
447
+ value,
448
+ is_buildtime: false,
449
+ is_runtime: true,
450
+ }));
421
451
 
422
- if (result.error && result.error.includes("already exists")) {
423
- // Var exists — DELETE + re-POST (Coolify PATCH on envs returns 404)
424
- const listResult = await this.request<
425
- Array<{ uuid: string; key: string }>
426
- >(`/applications/${appUuid}/envs`);
427
- const existing = listResult.data?.find((v) => v.key === key);
428
- if (existing) {
429
- await this.request(`/applications/${appUuid}/envs/${existing.uuid}`, {
430
- method: "DELETE",
431
- });
432
- const repost = await this.request(`/applications/${appUuid}/envs`, {
433
- method: "POST",
434
- body: JSON.stringify({ key, value, is_preview: false, is_buildtime: false }),
435
- });
436
- if (repost.error) {
437
- log.error(`Failed to update env var ${key}: ${repost.error}`);
438
- return err(new Error(`Failed to update ${key}: ${repost.error}`));
439
- }
440
- log.debug(`Updated existing env var: ${key}`);
441
- } else {
442
- log.error(`Failed to set env var ${key}: ${result.error}`);
443
- return err(new Error(`Failed to set ${key}: ${result.error}`));
444
- }
445
- } else if (result.error) {
446
- log.error(`Failed to set env var ${key}: ${result.error}`);
447
- return err(new Error(`Failed to set ${key}: ${result.error}`));
448
- }
452
+ const result = await this.bulkUpdateEnvironmentVariables(appUuid, vars);
453
+ if (isErr(result)) {
454
+ return err(result.error);
449
455
  }
450
456
 
451
- log.success(`${Object.keys(envVars).length} environment variables set`);
457
+ log.success(`${vars.length} environment variables set`);
452
458
  return ok(undefined);
453
459
  }
454
460
 
@@ -478,12 +484,20 @@ export class CoolifyService {
478
484
 
479
485
  /**
480
486
  * Sets a single environment variable for an application.
481
- * Uses PATCH if variable exists, POST if it doesn't.
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.
482
494
  *
483
495
  * @param appUuid - Application UUID
484
496
  * @param key - Variable name
485
497
  * @param value - Variable value
486
- * @param isBuildTime - Whether the variable is available at build time (only for new vars)
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).
487
501
  * @returns Result indicating success or error
488
502
  */
489
503
  async setEnvironmentVariable(
@@ -494,52 +508,17 @@ export class CoolifyService {
494
508
  ): Promise<Result<void, Error>> {
495
509
  log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
496
510
 
497
- // Check if variable already exists
498
- const existingVars = await this.getEnvironmentVariables(appUuid);
499
- if (isErr(existingVars)) {
500
- return err(existingVars.error);
501
- }
502
-
503
- const exists = existingVars.value.some((ev) => ev.key === key);
504
-
505
- if (exists) {
506
- // Use PATCH to update existing variable
507
- log.debug(`Variable ${key} exists, using PATCH to update`);
508
- const result = await this.request<{ uuid: string }>(
509
- `/applications/${appUuid}/envs`,
510
- {
511
- method: "PATCH",
512
- body: JSON.stringify({
513
- key,
514
- value,
515
- is_buildtime: isBuildTime,
516
- }),
517
- },
518
- );
519
-
520
- if (result.error) {
521
- log.error(`Failed to update env var: ${result.error}`);
522
- return err(new Error(result.error));
523
- }
524
- } else {
525
- // Use POST to create new variable
526
- log.debug(`Variable ${key} does not exist, using POST to create`);
527
- const result = await this.request<{ uuid: string }>(
528
- `/applications/${appUuid}/envs`,
529
- {
530
- method: "POST",
531
- body: JSON.stringify({
532
- key,
533
- value,
534
- is_buildtime: isBuildTime,
535
- }),
536
- },
537
- );
511
+ const result = await this.bulkUpdateEnvironmentVariables(appUuid, [
512
+ {
513
+ key,
514
+ value,
515
+ is_buildtime: isBuildTime,
516
+ is_runtime: !isBuildTime,
517
+ },
518
+ ]);
538
519
 
539
- if (result.error) {
540
- log.error(`Failed to create env var: ${result.error}`);
541
- return err(new Error(result.error));
542
- }
520
+ if (isErr(result)) {
521
+ return err(result.error);
543
522
  }
544
523
 
545
524
  log.success(`Environment variable ${key} set for ${appUuid}`);
@@ -663,12 +642,7 @@ export class CoolifyService {
663
642
  async listGithubApps(
664
643
  page?: number,
665
644
  perPage?: number,
666
- ): Promise<
667
- Result<
668
- Array<{ id: number; uuid: string; name: string; is_public: boolean }>,
669
- Error
670
- >
671
- > {
645
+ ): Promise<Result<ICoolifyGithubApp[], Error>> {
672
646
  let endpoint = "/github-apps";
673
647
  const params = new URLSearchParams();
674
648
  if (page) params.set("page", page.toString());
@@ -677,14 +651,40 @@ export class CoolifyService {
677
651
  endpoint += `?${params.toString()}`;
678
652
  }
679
653
 
680
- const result =
681
- await this.request<
682
- Array<{ id: number; uuid: string; name: string; is_public: boolean }>
683
- >(endpoint);
654
+ const result = await this.request<ICoolifyGithubApp[]>(endpoint);
684
655
  if (result.error) return err(new Error(result.error));
685
656
  return ok(result.data || []);
686
657
  }
687
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
+
688
688
  /**
689
689
  * Lists all projects.
690
690
  *
@@ -920,10 +920,14 @@ export class CoolifyService {
920
920
  if (options.domains) body.domains = options.domains;
921
921
  if (options.dockerComposeDomains)
922
922
  body.docker_compose_domains = options.dockerComposeDomains;
923
+ if (options.dockerComposeRaw !== undefined)
924
+ body.docker_compose_raw = options.dockerComposeRaw;
923
925
  if (options.isForceHttpsEnabled !== undefined)
924
926
  body.is_force_https_enabled = options.isForceHttpsEnabled;
925
927
  if (options.isAutoDeployEnabled !== undefined)
926
928
  body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
929
+ if (options.watchPaths !== undefined)
930
+ body.watch_paths = options.watchPaths;
927
931
  if (options.healthCheckEnabled !== undefined)
928
932
  body.health_check_enabled = options.healthCheckEnabled;
929
933
  if (options.healthCheckPath)
@@ -1036,8 +1040,15 @@ export class CoolifyService {
1036
1040
  /**
1037
1041
  * Bulk updates environment variables for an application.
1038
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
+ *
1039
1050
  * @param appUuid - Application UUID
1040
- * @param envVars - Array of { key, value, is_preview? } objects
1051
+ * @param envVars - Array of variable definitions to upsert
1041
1052
  * @returns Result indicating success or error
1042
1053
  */
1043
1054
  async bulkUpdateEnvironmentVariables(
@@ -1046,15 +1057,19 @@ export class CoolifyService {
1046
1057
  key: string;
1047
1058
  value: string;
1048
1059
  is_preview?: boolean;
1060
+ is_buildtime?: boolean;
1061
+ is_runtime?: boolean;
1049
1062
  }>,
1050
1063
  ): Promise<Result<{ message: string }, Error>> {
1051
1064
  log.info(`Bulk updating ${envVars.length} env vars for ${appUuid}`);
1052
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.
1053
1068
  const result = await this.request<{ message: string }>(
1054
1069
  `/applications/${appUuid}/envs/bulk`,
1055
1070
  {
1056
1071
  method: "PATCH",
1057
- body: JSON.stringify(envVars),
1072
+ body: JSON.stringify({ data: envVars }),
1058
1073
  },
1059
1074
  );
1060
1075
 
@@ -1067,6 +1082,50 @@ export class CoolifyService {
1067
1082
  return ok(result.data || { message: "Environment variables updated" });
1068
1083
  }
1069
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
+
1070
1129
  /**
1071
1130
  * Gets deployment history for an application.
1072
1131
  *
@@ -1913,9 +1972,65 @@ export class CoolifyService {
1913
1972
  return ok(result.data || []);
1914
1973
  }
1915
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
+
1916
2023
  /**
1917
2024
  * Creates an environment variable for a service.
1918
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
+ *
1919
2034
  * @param uuid - Service UUID
1920
2035
  * @param data - Env var data (key, value, is_preview)
1921
2036
  * @returns Result with created env var UUID or error
@@ -1933,6 +2048,110 @@ export class CoolifyService {
1933
2048
  return ok(result.data!);
1934
2049
  }
1935
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
+
1936
2155
  // ===========================================================================
1937
2156
  // Additional Server endpoints
1938
2157
  // ===========================================================================