@better-openclaw/core 1.0.19 → 1.0.21

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 (61) hide show
  1. package/.github/dependabot.yml +32 -0
  2. package/.github/workflows/ci.yml +8 -8
  3. package/.github/workflows/publish-core.yml +4 -4
  4. package/SECURITY.md +62 -0
  5. package/dist/deployers/coolify.cjs +61 -50
  6. package/dist/deployers/coolify.cjs.map +1 -1
  7. package/dist/deployers/coolify.d.cts +4 -11
  8. package/dist/deployers/coolify.d.cts.map +1 -1
  9. package/dist/deployers/coolify.d.mts +4 -11
  10. package/dist/deployers/coolify.d.mts.map +1 -1
  11. package/dist/deployers/coolify.mjs +62 -50
  12. package/dist/deployers/coolify.mjs.map +1 -1
  13. package/dist/deployers/dokploy.cjs +106 -29
  14. package/dist/deployers/dokploy.cjs.map +1 -1
  15. package/dist/deployers/dokploy.d.cts +2 -1
  16. package/dist/deployers/dokploy.d.cts.map +1 -1
  17. package/dist/deployers/dokploy.d.mts +2 -1
  18. package/dist/deployers/dokploy.d.mts.map +1 -1
  19. package/dist/deployers/dokploy.mjs +107 -29
  20. package/dist/deployers/dokploy.mjs.map +1 -1
  21. package/dist/deployers/index.cjs.map +1 -1
  22. package/dist/deployers/index.d.cts +2 -2
  23. package/dist/deployers/index.d.cts.map +1 -1
  24. package/dist/deployers/index.d.mts +2 -2
  25. package/dist/deployers/index.d.mts.map +1 -1
  26. package/dist/deployers/index.mjs.map +1 -1
  27. package/dist/deployers/strip-host-ports.cjs +138 -0
  28. package/dist/deployers/strip-host-ports.cjs.map +1 -0
  29. package/dist/deployers/strip-host-ports.d.cts +62 -0
  30. package/dist/deployers/strip-host-ports.d.cts.map +1 -0
  31. package/dist/deployers/strip-host-ports.d.mts +62 -0
  32. package/dist/deployers/strip-host-ports.d.mts.map +1 -0
  33. package/dist/deployers/strip-host-ports.mjs +133 -0
  34. package/dist/deployers/strip-host-ports.mjs.map +1 -0
  35. package/dist/deployers/strip-host-ports.test.cjs +89 -0
  36. package/dist/deployers/strip-host-ports.test.cjs.map +1 -0
  37. package/dist/deployers/strip-host-ports.test.d.cts +1 -0
  38. package/dist/deployers/strip-host-ports.test.d.mts +1 -0
  39. package/dist/deployers/strip-host-ports.test.mjs +90 -0
  40. package/dist/deployers/strip-host-ports.test.mjs.map +1 -0
  41. package/dist/deployers/types.d.cts +173 -2
  42. package/dist/deployers/types.d.cts.map +1 -1
  43. package/dist/deployers/types.d.mts +173 -2
  44. package/dist/deployers/types.d.mts.map +1 -1
  45. package/dist/index.d.cts +2 -2
  46. package/dist/index.d.mts +2 -2
  47. package/dist/services/definitions/usesend.cjs +4 -4
  48. package/dist/services/definitions/usesend.cjs.map +1 -1
  49. package/dist/services/definitions/usesend.mjs +4 -4
  50. package/dist/services/definitions/usesend.mjs.map +1 -1
  51. package/package.json +4 -4
  52. package/src/__snapshots__/composer.snapshot.test.ts.snap +248 -38
  53. package/src/deployers/coolify.ts +198 -103
  54. package/src/deployers/dokploy.ts +209 -55
  55. package/src/deployers/index.ts +1 -0
  56. package/src/deployers/strip-host-ports.test.ts +100 -0
  57. package/src/deployers/strip-host-ports.ts +187 -0
  58. package/src/deployers/types.ts +185 -1
  59. package/src/index.ts +19 -4
  60. package/src/services/definitions/usesend.ts +4 -4
  61. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,32 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: npm
9
+ directory: /
10
+ schedule:
11
+ interval: weekly
12
+ day: monday
13
+ open-pull-requests-limit: 10
14
+ groups:
15
+ dev-dependencies:
16
+ dependency-type: development
17
+ update-types:
18
+ - minor
19
+ - patch
20
+ production-dependencies:
21
+ dependency-type: production
22
+ update-types:
23
+ - patch
24
+ labels:
25
+ - dependencies
26
+
27
+ - package-ecosystem: github-actions
28
+ directory: /
29
+ schedule:
30
+ interval: weekly
31
+ labels:
32
+ - ci
@@ -10,9 +10,9 @@ jobs:
10
10
  lint:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v4
13
+ - uses: actions/checkout@v6
14
14
  - uses: pnpm/action-setup@v4
15
- - uses: actions/setup-node@v4
15
+ - uses: actions/setup-node@v6
16
16
  with:
17
17
  node-version: 22
18
18
  cache: pnpm
@@ -22,9 +22,9 @@ jobs:
22
22
  typecheck:
23
23
  runs-on: ubuntu-latest
24
24
  steps:
25
- - uses: actions/checkout@v4
25
+ - uses: actions/checkout@v6
26
26
  - uses: pnpm/action-setup@v4
27
- - uses: actions/setup-node@v4
27
+ - uses: actions/setup-node@v6
28
28
  with:
29
29
  node-version: 22
30
30
  cache: pnpm
@@ -34,9 +34,9 @@ jobs:
34
34
  test:
35
35
  runs-on: ubuntu-latest
36
36
  steps:
37
- - uses: actions/checkout@v4
37
+ - uses: actions/checkout@v6
38
38
  - uses: pnpm/action-setup@v4
39
- - uses: actions/setup-node@v4
39
+ - uses: actions/setup-node@v6
40
40
  with:
41
41
  node-version: 22
42
42
  cache: pnpm
@@ -47,9 +47,9 @@ jobs:
47
47
  runs-on: ubuntu-latest
48
48
  needs: [lint, typecheck, test]
49
49
  steps:
50
- - uses: actions/checkout@v4
50
+ - uses: actions/checkout@v6
51
51
  - uses: pnpm/action-setup@v4
52
- - uses: actions/setup-node@v4
52
+ - uses: actions/setup-node@v6
53
53
  with:
54
54
  node-version: 22
55
55
  cache: pnpm
@@ -11,15 +11,15 @@ jobs:
11
11
  contents: read
12
12
  id-token: write
13
13
  steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v4
14
+ - uses: actions/checkout@v6
15
+ - uses: actions/setup-node@v6
16
16
  with:
17
17
  node-version: "20.x"
18
18
  registry-url: "https://registry.npmjs.org"
19
19
 
20
- - uses: pnpm/action-setup@v2
20
+ - uses: pnpm/action-setup@v4
21
21
  with:
22
- version: 9
22
+ version: 10.30.3
23
23
  run_install: false
24
24
 
25
25
  - name: Install dependencies
package/SECURITY.md ADDED
@@ -0,0 +1,62 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 5.1.x | :white_check_mark: |
8
+ | 5.0.x | :x: |
9
+ | 4.0.x | :white_check_mark: |
10
+ | < 4.0 | :x: |
11
+
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ If you discover a security vulnerability in better-openclaw, please report it responsibly.
16
+
17
+ **Do NOT open a public GitHub issue for security vulnerabilities.**
18
+
19
+ ### How to Report
20
+
21
+ 1. **Email**: Send details to [bachir@bidew.io](mailto:bachir@bidew.io)
22
+ 2. **GitHub Security Advisories**: Use the [private vulnerability reporting](https://github.com/bidewio/better-openclaw/security/advisories/new) feature
23
+
24
+ ### What to Include
25
+
26
+ - Description of the vulnerability
27
+ - Steps to reproduce
28
+ - Impact assessment
29
+ - Suggested fix (if any)
30
+
31
+ ### Response Timeline
32
+
33
+ - **Acknowledgement**: Within 48 hours
34
+ - **Initial Assessment**: Within 1 week
35
+ - **Fix & Disclosure**: Coordinated with reporter, typically within 30 days
36
+
37
+ ### Scope
38
+
39
+ The following are in scope:
40
+
41
+ - `@better-openclaw/core` — service definitions, resolver, composer, generators
42
+ - `@better-openclaw/mcp` — MCP server
43
+ - `create-better-openclaw` — CLI tool
44
+ - `@better-openclaw/api` — REST API
45
+ - Generated Docker Compose files and scripts
46
+ - Secret generation logic
47
+
48
+ ### Out of Scope
49
+
50
+ - Third-party Docker images referenced by service definitions
51
+ - Vulnerabilities in upstream dependencies (report to the upstream project)
52
+ - The hosted website (better-openclaw.dev) infrastructure
53
+
54
+ ## Security Best Practices
55
+
56
+ When using better-openclaw:
57
+
58
+ - Always review generated `.env` files before deploying
59
+ - Never commit `.env` files to version control
60
+ - Use `--generate-secrets` (default) to create unique passwords per stack
61
+ - Keep Docker images updated (`docker compose pull`)
62
+ - Use a reverse proxy (Caddy/Traefik) with TLS in production
@@ -1,14 +1,24 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_deployers_strip_host_ports = require('./strip-host-ports.cjs');
2
3
 
3
4
  //#region src/deployers/coolify.ts
4
- /** Build a full Coolify API URL (base + /api/v1 + path). */
5
+ /**
6
+ * Coolify PaaS deployer
7
+ *
8
+ * Deploys Docker Compose stacks via Coolify v4 API
9
+ *
10
+ * Docs:
11
+ * https://coolify.io/docs/api-reference/api
12
+ *
13
+ * Auth:
14
+ * Authorization: Bearer <token>
15
+ *
16
+ * Base path:
17
+ * /api/v1
18
+ */
5
19
  function apiUrl(target, path) {
6
20
  return `${target.instanceUrl.replace(/\/+$/, "")}/api/v1${path}`;
7
21
  }
8
- /**
9
- * Typed fetch wrapper for the Coolify v4 API.
10
- * Handles JSON serialisation, Bearer auth, and error extraction.
11
- */
12
22
  async function coolifyFetch(target, path, options = {}) {
13
23
  const res = await fetch(apiUrl(target, path), {
14
24
  method: options.method ?? "GET",
@@ -31,18 +41,24 @@ async function coolifyFetch(target, path, options = {}) {
31
41
  if (!text) return void 0;
32
42
  return JSON.parse(text);
33
43
  }
34
- /**
35
- * Parse .env content into key-value pairs for Coolify's bulk env API.
36
- */
44
+ function hashString(str) {
45
+ let hash = 0;
46
+ for (let i = 0; i < str.length; i++) {
47
+ hash = (hash << 5) - hash + str.charCodeAt(i);
48
+ hash |= 0;
49
+ }
50
+ return hash;
51
+ }
37
52
  function parseEnvContent(envContent) {
53
+ if (!envContent) return [];
38
54
  const result = [];
39
55
  for (const line of envContent.split("\n")) {
40
56
  const trimmed = line.trim();
41
57
  if (!trimmed || trimmed.startsWith("#")) continue;
42
- const eqIdx = trimmed.indexOf("=");
43
- if (eqIdx <= 0) continue;
44
- const key = trimmed.slice(0, eqIdx);
45
- const value = trimmed.slice(eqIdx + 1);
58
+ const idx = trimmed.indexOf("=");
59
+ if (idx <= 0) continue;
60
+ const key = trimmed.slice(0, idx);
61
+ const value = trimmed.slice(idx + 1);
46
62
  result.push({
47
63
  key,
48
64
  value,
@@ -53,16 +69,6 @@ function parseEnvContent(envContent) {
53
69
  }
54
70
  return result;
55
71
  }
56
- /**
57
- * Deploys Docker Compose stacks to a Coolify v4 instance.
58
- *
59
- * Deploy flow (5 steps):
60
- * 1. Discover the first available server
61
- * 2. Create a Coolify project
62
- * 3. Create a compose service with the raw docker-compose YAML
63
- * 4. Push .env variables via the bulk env API
64
- * 5. Trigger the deployment
65
- */
66
72
  var CoolifyDeployer = class {
67
73
  name = "Coolify";
68
74
  id = "coolify";
@@ -83,15 +89,15 @@ var CoolifyDeployer = class {
83
89
  status: "pending"
84
90
  };
85
91
  const step2 = {
86
- step: "Create project",
92
+ step: "Find or create project",
87
93
  status: "pending"
88
94
  };
89
95
  const step3 = {
90
- step: "Create compose service",
96
+ step: "Find or create service",
91
97
  status: "pending"
92
98
  };
93
99
  const step4 = {
94
- step: "Set environment variables",
100
+ step: "Update environment variables",
95
101
  status: "pending"
96
102
  };
97
103
  const step5 = {
@@ -109,68 +115,73 @@ var CoolifyDeployer = class {
109
115
  success: false,
110
116
  steps
111
117
  };
118
+ const composeYaml = require_deployers_strip_host_ports.sanitizeComposeForPaas(input.composeYaml);
112
119
  try {
113
120
  step1.status = "running";
114
121
  const servers = await coolifyFetch(input.target, "/servers");
115
- if (!servers || servers.length === 0) throw new Error("No servers found in Coolify instance");
122
+ if (!servers.length) throw new Error("No Coolify servers available");
116
123
  const server = servers[0];
117
124
  step1.status = "done";
118
- step1.detail = `Server: ${server.name} (${server.ip})`;
125
+ step1.detail = `${server.name} (${server.ip})`;
119
126
  step2.status = "running";
120
- const project = await coolifyFetch(input.target, "/projects", {
127
+ let project = (await coolifyFetch(input.target, "/projects")).find((p) => p.name === input.projectName);
128
+ if (!project) project = await coolifyFetch(input.target, "/projects", {
121
129
  method: "POST",
122
130
  body: {
123
131
  name: input.projectName,
124
- description: input.description ?? `OpenClaw stack: ${input.projectName}`
132
+ description: input.description ?? `Stack ${input.projectName}`
125
133
  }
126
134
  });
127
135
  result.projectId = project.uuid;
128
136
  step2.status = "done";
129
- step2.detail = `Project: ${project.uuid}`;
137
+ step2.detail = project.uuid;
130
138
  const projectDetail = await coolifyFetch(input.target, `/projects/${project.uuid}`);
131
- const envUuid = projectDetail.environments?.[0]?.uuid;
132
- const envName = projectDetail.environments?.[0]?.name ?? "production";
133
- if (!envUuid) throw new Error("No default environment found in project");
139
+ const env = projectDetail.environments?.find((e) => e.name === "production") ?? projectDetail.environments?.[0];
140
+ if (!env) throw new Error("No environment found");
134
141
  step3.status = "running";
135
- const service = await coolifyFetch(input.target, "/services", {
142
+ let service = (await coolifyFetch(input.target, `/projects/${project.uuid}/services`)).find((s) => s.name === input.projectName);
143
+ if (!service) service = await coolifyFetch(input.target, "/services", {
136
144
  method: "POST",
137
145
  body: {
138
146
  project_uuid: project.uuid,
139
147
  server_uuid: server.uuid,
140
- environment_name: envName,
141
- environment_uuid: envUuid,
142
- docker_compose_raw: input.composeYaml,
143
- name: input.projectName,
144
- description: input.description ?? "Deployed via OpenClaw web builder",
145
- instant_deploy: false
148
+ environment_uuid: env.uuid,
149
+ environment_name: env.name,
150
+ docker_compose_raw: composeYaml,
151
+ name: input.projectName
146
152
  }
147
153
  });
154
+ else if (hashString(composeYaml) !== hashString(service.docker_compose_raw ?? "")) await coolifyFetch(input.target, `/services/${service.uuid}`, {
155
+ method: "PATCH",
156
+ body: { docker_compose_raw: composeYaml }
157
+ });
148
158
  result.composeId = service.uuid;
149
159
  step3.status = "done";
150
- step3.detail = `Service: ${service.uuid}`;
160
+ step3.detail = service.uuid;
151
161
  step4.status = "running";
152
162
  const envVars = parseEnvContent(input.envContent);
153
- if (envVars.length > 0) await coolifyFetch(input.target, `/services/${service.uuid}/envs`, {
163
+ if (envVars.length) await coolifyFetch(input.target, `/services/${service.uuid}/envs`, {
154
164
  method: "PATCH",
155
165
  body: envVars
156
166
  });
157
167
  step4.status = "done";
158
- step4.detail = `${envVars.length} variables set`;
168
+ step4.detail = `${envVars.length} vars`;
159
169
  step5.status = "running";
160
- const deployments = await coolifyFetch(input.target, `/deploy?uuid=${service.uuid}&force=true`);
170
+ const deploy = await coolifyFetch(input.target, `/deploy?uuid=${service.uuid}&force=true`);
161
171
  step5.status = "done";
162
- step5.detail = deployments?.deployments?.[0]?.deployment_uuid ?? "Deployment triggered";
172
+ step5.detail = deploy.deployments?.[0]?.deployment_uuid ?? "Deployment started";
163
173
  result.success = true;
164
174
  result.dashboardUrl = `${input.target.instanceUrl.replace(/\/+$/, "")}/project/${project.uuid}`;
175
+ return result;
165
176
  } catch (err) {
166
- const failedStep = steps.find((s) => s.status === "running");
167
- if (failedStep) {
168
- failedStep.status = "error";
169
- failedStep.detail = err instanceof Error ? err.message : String(err);
177
+ const running = steps.find((s) => s.status === "running");
178
+ if (running) {
179
+ running.status = "error";
180
+ running.detail = err instanceof Error ? err.message : String(err);
170
181
  }
171
182
  result.error = err instanceof Error ? err.message : String(err);
183
+ return result;
172
184
  }
173
- return result;
174
185
  }
175
186
  };
176
187
 
@@ -1 +1 @@
1
- {"version":3,"file":"coolify.cjs","names":[],"sources":["../../src/deployers/coolify.ts"],"sourcesContent":["/**\n * Coolify PaaS deployer — deploys Docker Compose stacks via the Coolify v4 REST API.\n *\n * API docs: https://coolify.io/docs/api-reference/api/\n * Auth: Authorization: Bearer <token>\n * Base path: /api/v1\n */\n\nimport type { DeployInput, DeployResult, DeployStep, DeployTarget, PaasDeployer } from \"./types.js\";\n\n/** Shape returned by Coolify's project endpoints. */\ninterface CoolifyProject {\n\tuuid: string;\n\tname: string;\n\tenvironments?: { uuid: string; name: string }[];\n}\n\n/** Shape returned by Coolify's server listing. */\ninterface CoolifyServer {\n\tuuid: string;\n\tname: string;\n\tip: string;\n}\n\n/** Shape returned when creating a Coolify service (compose stack). */\ninterface CoolifyService {\n\tuuid: string;\n}\n\n/** Shape returned by Coolify's deploy trigger endpoint. */\ninterface CoolifyDeployment {\n\tmessage: string;\n\tresource_uuid: string;\n\tdeployment_uuid: string;\n}\n\n/** Build a full Coolify API URL (base + /api/v1 + path). */\nfunction apiUrl(target: DeployTarget, path: string): string {\n\tconst base = target.instanceUrl.replace(/\\/+$/, \"\");\n\treturn `${base}/api/v1${path}`;\n}\n\n/**\n * Typed fetch wrapper for the Coolify v4 API.\n * Handles JSON serialisation, Bearer auth, and error extraction.\n */\nasync function coolifyFetch<T>(\n\ttarget: DeployTarget,\n\tpath: string,\n\toptions: { method?: string; body?: unknown } = {},\n): Promise<T> {\n\tconst res = await fetch(apiUrl(target, path), {\n\t\tmethod: options.method ?? \"GET\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAuthorization: `Bearer ${target.apiKey}`,\n\t\t},\n\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t});\n\n\tif (!res.ok) {\n\t\tconst text = await res.text().catch(() => \"\");\n\t\tlet detail = text;\n\t\ttry {\n\t\t\tconst json = JSON.parse(text);\n\t\t\tdetail = json.message || json.error || text;\n\t\t} catch {\n\t\t\t// use raw text\n\t\t}\n\t\tthrow new Error(`Coolify API ${res.status}: ${detail}`);\n\t}\n\n\tconst text = await res.text();\n\tif (!text) return undefined as T;\n\treturn JSON.parse(text) as T;\n}\n\n/**\n * Parse .env content into key-value pairs for Coolify's bulk env API.\n */\nfunction parseEnvContent(envContent: string): {\n\tkey: string;\n\tvalue: string;\n\tis_preview: boolean;\n\tis_build_time: boolean;\n\tis_literal: boolean;\n}[] {\n\tconst result: {\n\t\tkey: string;\n\t\tvalue: string;\n\t\tis_preview: boolean;\n\t\tis_build_time: boolean;\n\t\tis_literal: boolean;\n\t}[] = [];\n\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n\t\tconst eqIdx = trimmed.indexOf(\"=\");\n\t\tif (eqIdx <= 0) continue;\n\n\t\tconst key = trimmed.slice(0, eqIdx);\n\t\tconst value = trimmed.slice(eqIdx + 1);\n\n\t\tresult.push({\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\tis_preview: false,\n\t\t\tis_build_time: false,\n\t\t\tis_literal: true,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/**\n * Deploys Docker Compose stacks to a Coolify v4 instance.\n *\n * Deploy flow (5 steps):\n * 1. Discover the first available server\n * 2. Create a Coolify project\n * 3. Create a compose service with the raw docker-compose YAML\n * 4. Push .env variables via the bulk env API\n * 5. Trigger the deployment\n */\nexport class CoolifyDeployer implements PaasDeployer {\n\treadonly name = \"Coolify\";\n\treadonly id = \"coolify\";\n\n\tasync testConnection(target: DeployTarget): Promise<{ ok: boolean; error?: string }> {\n\t\ttry {\n\t\t\tawait coolifyFetch<unknown>(target, \"/version\");\n\t\t\treturn { ok: true };\n\t\t} catch (err) {\n\t\t\treturn { ok: false, error: err instanceof Error ? err.message : String(err) };\n\t\t}\n\t}\n\n\tasync deploy(input: DeployInput): Promise<DeployResult> {\n\t\tconst step1: DeployStep = { step: \"Discover server\", status: \"pending\" };\n\t\tconst step2: DeployStep = { step: \"Create project\", status: \"pending\" };\n\t\tconst step3: DeployStep = { step: \"Create compose service\", status: \"pending\" };\n\t\tconst step4: DeployStep = { step: \"Set environment variables\", status: \"pending\" };\n\t\tconst step5: DeployStep = { step: \"Trigger deployment\", status: \"pending\" };\n\t\tconst steps: DeployStep[] = [step1, step2, step3, step4, step5];\n\n\t\tconst result: DeployResult = { success: false, steps };\n\n\t\ttry {\n\t\t\t// Step 1: Discover default server\n\t\t\tstep1.status = \"running\";\n\t\t\tconst servers = await coolifyFetch<CoolifyServer[]>(input.target, \"/servers\");\n\t\t\tif (!servers || servers.length === 0) {\n\t\t\t\tthrow new Error(\"No servers found in Coolify instance\");\n\t\t\t}\n\t\t\tconst server = servers[0] as CoolifyServer;\n\t\t\tstep1.status = \"done\";\n\t\t\tstep1.detail = `Server: ${server.name} (${server.ip})`;\n\n\t\t\t// Step 2: Create project\n\t\t\tstep2.status = \"running\";\n\t\t\tconst project = await coolifyFetch<CoolifyProject>(input.target, \"/projects\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tname: input.projectName,\n\t\t\t\t\tdescription: input.description ?? `OpenClaw stack: ${input.projectName}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\tresult.projectId = project.uuid;\n\t\t\tstep2.status = \"done\";\n\t\t\tstep2.detail = `Project: ${project.uuid}`;\n\n\t\t\t// Get the default environment\n\t\t\tconst projectDetail = await coolifyFetch<CoolifyProject>(\n\t\t\t\tinput.target,\n\t\t\t\t`/projects/${project.uuid}`,\n\t\t\t);\n\t\t\tconst envUuid = projectDetail.environments?.[0]?.uuid;\n\t\t\tconst envName = projectDetail.environments?.[0]?.name ?? \"production\";\n\t\t\tif (!envUuid) {\n\t\t\t\tthrow new Error(\"No default environment found in project\");\n\t\t\t}\n\n\t\t\t// Step 3: Create compose service with docker_compose_raw\n\t\t\tstep3.status = \"running\";\n\t\t\tconst service = await coolifyFetch<CoolifyService>(input.target, \"/services\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tproject_uuid: project.uuid,\n\t\t\t\t\tserver_uuid: server.uuid,\n\t\t\t\t\tenvironment_name: envName,\n\t\t\t\t\tenvironment_uuid: envUuid,\n\t\t\t\t\tdocker_compose_raw: input.composeYaml,\n\t\t\t\t\tname: input.projectName,\n\t\t\t\t\tdescription: input.description ?? \"Deployed via OpenClaw web builder\",\n\t\t\t\t\tinstant_deploy: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\tresult.composeId = service.uuid;\n\t\t\tstep3.status = \"done\";\n\t\t\tstep3.detail = `Service: ${service.uuid}`;\n\n\t\t\t// Step 4: Set environment variables\n\t\t\tstep4.status = \"running\";\n\t\t\tconst envVars = parseEnvContent(input.envContent);\n\t\t\tif (envVars.length > 0) {\n\t\t\t\tawait coolifyFetch(input.target, `/services/${service.uuid}/envs`, {\n\t\t\t\t\tmethod: \"PATCH\",\n\t\t\t\t\tbody: envVars,\n\t\t\t\t});\n\t\t\t}\n\t\t\tstep4.status = \"done\";\n\t\t\tstep4.detail = `${envVars.length} variables set`;\n\n\t\t\t// Step 5: Trigger deployment\n\t\t\tstep5.status = \"running\";\n\t\t\tconst deployments = await coolifyFetch<{ deployments: CoolifyDeployment[] }>(\n\t\t\t\tinput.target,\n\t\t\t\t`/deploy?uuid=${service.uuid}&force=true`,\n\t\t\t);\n\t\t\tstep5.status = \"done\";\n\t\t\tstep5.detail = deployments?.deployments?.[0]?.deployment_uuid ?? \"Deployment triggered\";\n\n\t\t\tresult.success = true;\n\t\t\tconst base = input.target.instanceUrl.replace(/\\/+$/, \"\");\n\t\t\tresult.dashboardUrl = `${base}/project/${project.uuid}`;\n\t\t} catch (err) {\n\t\t\tconst failedStep = steps.find((s) => s.status === \"running\");\n\t\t\tif (failedStep) {\n\t\t\t\tfailedStep.status = \"error\";\n\t\t\t\tfailedStep.detail = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\t\t\tresult.error = err instanceof Error ? err.message : String(err);\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"],"mappings":";;;;AAqCA,SAAS,OAAO,QAAsB,MAAsB;AAE3D,QAAO,GADM,OAAO,YAAY,QAAQ,QAAQ,GAAG,CACpC,SAAS;;;;;;AAOzB,eAAe,aACd,QACA,MACA,UAA+C,EAAE,EACpC;CACb,MAAM,MAAM,MAAM,MAAM,OAAO,QAAQ,KAAK,EAAE;EAC7C,QAAQ,QAAQ,UAAU;EAC1B,SAAS;GACR,gBAAgB;GAChB,eAAe,UAAU,OAAO;GAChC;EACD,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,KAAK,GAAG;EACpD,CAAC;AAEF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,YAAY,GAAG;EAC7C,IAAI,SAAS;AACb,MAAI;GACH,MAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,YAAS,KAAK,WAAW,KAAK,SAAS;UAChC;AAGR,QAAM,IAAI,MAAM,eAAe,IAAI,OAAO,IAAI,SAAS;;CAGxD,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,KAAK,MAAM,KAAK;;;;;AAMxB,SAAS,gBAAgB,YAMrB;CACH,MAAM,SAMA,EAAE;AAER,MAAK,MAAM,QAAQ,WAAW,MAAM,KAAK,EAAE;EAC1C,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,CAAC,WAAW,QAAQ,WAAW,IAAI,CAAE;EAEzC,MAAM,QAAQ,QAAQ,QAAQ,IAAI;AAClC,MAAI,SAAS,EAAG;EAEhB,MAAM,MAAM,QAAQ,MAAM,GAAG,MAAM;EACnC,MAAM,QAAQ,QAAQ,MAAM,QAAQ,EAAE;AAEtC,SAAO,KAAK;GACX;GACA;GACA,YAAY;GACZ,eAAe;GACf,YAAY;GACZ,CAAC;;AAGH,QAAO;;;;;;;;;;;;AAaR,IAAa,kBAAb,MAAqD;CACpD,AAAS,OAAO;CAChB,AAAS,KAAK;CAEd,MAAM,eAAe,QAAgE;AACpF,MAAI;AACH,SAAM,aAAsB,QAAQ,WAAW;AAC/C,UAAO,EAAE,IAAI,MAAM;WACX,KAAK;AACb,UAAO;IAAE,IAAI;IAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IAAE;;;CAI/E,MAAM,OAAO,OAA2C;EACvD,MAAM,QAAoB;GAAE,MAAM;GAAmB,QAAQ;GAAW;EACxE,MAAM,QAAoB;GAAE,MAAM;GAAkB,QAAQ;GAAW;EACvE,MAAM,QAAoB;GAAE,MAAM;GAA0B,QAAQ;GAAW;EAC/E,MAAM,QAAoB;GAAE,MAAM;GAA6B,QAAQ;GAAW;EAClF,MAAM,QAAoB;GAAE,MAAM;GAAsB,QAAQ;GAAW;EAC3E,MAAM,QAAsB;GAAC;GAAO;GAAO;GAAO;GAAO;GAAM;EAE/D,MAAM,SAAuB;GAAE,SAAS;GAAO;GAAO;AAEtD,MAAI;AAEH,SAAM,SAAS;GACf,MAAM,UAAU,MAAM,aAA8B,MAAM,QAAQ,WAAW;AAC7E,OAAI,CAAC,WAAW,QAAQ,WAAW,EAClC,OAAM,IAAI,MAAM,uCAAuC;GAExD,MAAM,SAAS,QAAQ;AACvB,SAAM,SAAS;AACf,SAAM,SAAS,WAAW,OAAO,KAAK,IAAI,OAAO,GAAG;AAGpD,SAAM,SAAS;GACf,MAAM,UAAU,MAAM,aAA6B,MAAM,QAAQ,aAAa;IAC7E,QAAQ;IACR,MAAM;KACL,MAAM,MAAM;KACZ,aAAa,MAAM,eAAe,mBAAmB,MAAM;KAC3D;IACD,CAAC;AACF,UAAO,YAAY,QAAQ;AAC3B,SAAM,SAAS;AACf,SAAM,SAAS,YAAY,QAAQ;GAGnC,MAAM,gBAAgB,MAAM,aAC3B,MAAM,QACN,aAAa,QAAQ,OACrB;GACD,MAAM,UAAU,cAAc,eAAe,IAAI;GACjD,MAAM,UAAU,cAAc,eAAe,IAAI,QAAQ;AACzD,OAAI,CAAC,QACJ,OAAM,IAAI,MAAM,0CAA0C;AAI3D,SAAM,SAAS;GACf,MAAM,UAAU,MAAM,aAA6B,MAAM,QAAQ,aAAa;IAC7E,QAAQ;IACR,MAAM;KACL,cAAc,QAAQ;KACtB,aAAa,OAAO;KACpB,kBAAkB;KAClB,kBAAkB;KAClB,oBAAoB,MAAM;KAC1B,MAAM,MAAM;KACZ,aAAa,MAAM,eAAe;KAClC,gBAAgB;KAChB;IACD,CAAC;AACF,UAAO,YAAY,QAAQ;AAC3B,SAAM,SAAS;AACf,SAAM,SAAS,YAAY,QAAQ;AAGnC,SAAM,SAAS;GACf,MAAM,UAAU,gBAAgB,MAAM,WAAW;AACjD,OAAI,QAAQ,SAAS,EACpB,OAAM,aAAa,MAAM,QAAQ,aAAa,QAAQ,KAAK,QAAQ;IAClE,QAAQ;IACR,MAAM;IACN,CAAC;AAEH,SAAM,SAAS;AACf,SAAM,SAAS,GAAG,QAAQ,OAAO;AAGjC,SAAM,SAAS;GACf,MAAM,cAAc,MAAM,aACzB,MAAM,QACN,gBAAgB,QAAQ,KAAK,aAC7B;AACD,SAAM,SAAS;AACf,SAAM,SAAS,aAAa,cAAc,IAAI,mBAAmB;AAEjE,UAAO,UAAU;AAEjB,UAAO,eAAe,GADT,MAAM,OAAO,YAAY,QAAQ,QAAQ,GAAG,CAC3B,WAAW,QAAQ;WACzC,KAAK;GACb,MAAM,aAAa,MAAM,MAAM,MAAM,EAAE,WAAW,UAAU;AAC5D,OAAI,YAAY;AACf,eAAW,SAAS;AACpB,eAAW,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAErE,UAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAGhE,SAAO"}
1
+ {"version":3,"file":"coolify.cjs","names":["sanitizeComposeForPaas"],"sources":["../../src/deployers/coolify.ts"],"sourcesContent":["/**\n * Coolify PaaS deployer\n *\n * Deploys Docker Compose stacks via Coolify v4 API\n *\n * Docs:\n * https://coolify.io/docs/api-reference/api\n *\n * Auth:\n * Authorization: Bearer <token>\n *\n * Base path:\n * /api/v1\n */\n\nimport { sanitizeComposeForPaas } from \"./strip-host-ports.js\";\nimport type { DeployInput, DeployResult, DeployStep, DeployTarget, PaasDeployer } from \"./types.js\";\n\n/* ----------------------------- */\n/* Coolify API Types */\n/* ----------------------------- */\n\ninterface CoolifyProject {\n\tuuid: string;\n\tname: string;\n\tenvironments?: {\n\t\tuuid: string;\n\t\tname: string;\n\t}[];\n}\n\ninterface CoolifyServer {\n\tuuid: string;\n\tname: string;\n\tip: string;\n}\n\ninterface CoolifyService {\n\tuuid: string;\n\tname: string;\n\tdocker_compose_raw?: string;\n}\n\ninterface CoolifyDeployment {\n\tmessage: string;\n\tresource_uuid: string;\n\tdeployment_uuid: string;\n}\n\n/* ----------------------------- */\n/* Utilities */\n/* ----------------------------- */\n\nfunction apiUrl(target: DeployTarget, path: string) {\n\tconst base = target.instanceUrl.replace(/\\/+$/, \"\");\n\treturn `${base}/api/v1${path}`;\n}\n\nasync function coolifyFetch<T>(\n\ttarget: DeployTarget,\n\tpath: string,\n\toptions: { method?: string; body?: unknown } = {},\n): Promise<T> {\n\tconst res = await fetch(apiUrl(target, path), {\n\t\tmethod: options.method ?? \"GET\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAuthorization: `Bearer ${target.apiKey}`,\n\t\t},\n\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t});\n\n\tif (!res.ok) {\n\t\tconst text = await res.text().catch(() => \"\");\n\t\tlet detail = text;\n\n\t\ttry {\n\t\t\tconst json = JSON.parse(text);\n\t\t\tdetail = json.message || json.error || text;\n\t\t} catch {}\n\n\t\tthrow new Error(`Coolify API ${res.status}: ${detail}`);\n\t}\n\n\tconst text = await res.text();\n\n\tif (!text) return undefined as T;\n\n\treturn JSON.parse(text);\n}\n\nfunction hashString(str: string) {\n\tlet hash = 0;\n\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\thash |= 0;\n\t}\n\n\treturn hash;\n}\n\nfunction parseEnvContent(envContent?: string) {\n\tif (!envContent) return [];\n\n\tconst result = [];\n\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n\t\tconst idx = trimmed.indexOf(\"=\");\n\n\t\tif (idx <= 0) continue;\n\n\t\tconst key = trimmed.slice(0, idx);\n\t\tconst value = trimmed.slice(idx + 1);\n\n\t\tresult.push({\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\tis_preview: false,\n\t\t\tis_build_time: false,\n\t\t\tis_literal: true,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/* ----------------------------- */\n/* Coolify Deployer */\n/* ----------------------------- */\n\nexport class CoolifyDeployer implements PaasDeployer {\n\treadonly name = \"Coolify\";\n\treadonly id = \"coolify\";\n\n\tasync testConnection(target: DeployTarget) {\n\t\ttry {\n\t\t\tawait coolifyFetch(target, \"/version\");\n\n\t\t\treturn { ok: true };\n\t\t} catch (err) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t};\n\t\t}\n\t}\n\n\tasync deploy(input: DeployInput): Promise<DeployResult> {\n\t\tconst step1: DeployStep = { step: \"Discover server\", status: \"pending\" };\n\t\tconst step2: DeployStep = {\n\t\t\tstep: \"Find or create project\",\n\t\t\tstatus: \"pending\",\n\t\t};\n\t\tconst step3: DeployStep = {\n\t\t\tstep: \"Find or create service\",\n\t\t\tstatus: \"pending\",\n\t\t};\n\t\tconst step4: DeployStep = {\n\t\t\tstep: \"Update environment variables\",\n\t\t\tstatus: \"pending\",\n\t\t};\n\t\tconst step5: DeployStep = { step: \"Trigger deployment\", status: \"pending\" };\n\t\tconst steps: DeployStep[] = [step1, step2, step3, step4, step5];\n\n\t\tconst result: DeployResult = { success: false, steps };\n\n\t\t// Strip host port bindings — Coolify routes via Traefik,\n\t\t// so host ports are unnecessary and cause \"port already allocated\" errors.\n\t\tconst composeYaml = sanitizeComposeForPaas(input.composeYaml);\n\n\t\ttry {\n\t\t\t/* ----------------------------- */\n\t\t\t/* STEP 1: Discover server */\n\t\t\t/* ----------------------------- */\n\n\t\t\tstep1.status = \"running\";\n\n\t\t\tconst servers = await coolifyFetch<CoolifyServer[]>(input.target, \"/servers\");\n\n\t\t\tif (!servers.length) {\n\t\t\t\tthrow new Error(\"No Coolify servers available\");\n\t\t\t}\n\n\t\t\tconst server = servers[0]!;\n\n\t\t\tstep1.status = \"done\";\n\t\t\tstep1.detail = `${server.name} (${server.ip})`;\n\n\t\t\t/* ----------------------------- */\n\t\t\t/* STEP 2: Find or create project */\n\t\t\t/* ----------------------------- */\n\n\t\t\tstep2.status = \"running\";\n\n\t\t\tconst projects = await coolifyFetch<CoolifyProject[]>(input.target, \"/projects\");\n\n\t\t\tlet project = projects.find((p) => p.name === input.projectName);\n\n\t\t\tif (!project) {\n\t\t\t\tproject = await coolifyFetch<CoolifyProject>(input.target, \"/projects\", {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tname: input.projectName,\n\t\t\t\t\t\tdescription: input.description ?? `Stack ${input.projectName}`,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tresult.projectId = project.uuid;\n\n\t\t\tstep2.status = \"done\";\n\t\t\tstep2.detail = project.uuid;\n\n\t\t\t/* ----------------------------- */\n\t\t\t/* Find environment */\n\t\t\t/* ----------------------------- */\n\n\t\t\tconst projectDetail = await coolifyFetch<CoolifyProject>(\n\t\t\t\tinput.target,\n\t\t\t\t`/projects/${project.uuid}`,\n\t\t\t);\n\n\t\t\tconst env =\n\t\t\t\tprojectDetail.environments?.find((e) => e.name === \"production\") ??\n\t\t\t\tprojectDetail.environments?.[0];\n\n\t\t\tif (!env) throw new Error(\"No environment found\");\n\n\t\t\t/* ----------------------------- */\n\t\t\t/* STEP 3: Find or create service */\n\t\t\t/* ----------------------------- */\n\n\t\t\tstep3.status = \"running\";\n\n\t\t\tconst services = await coolifyFetch<CoolifyService[]>(\n\t\t\t\tinput.target,\n\t\t\t\t`/projects/${project.uuid}/services`,\n\t\t\t);\n\n\t\t\tlet service = services.find((s) => s.name === input.projectName);\n\n\t\t\tif (!service) {\n\t\t\t\tservice = await coolifyFetch<CoolifyService>(input.target, \"/services\", {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tproject_uuid: project.uuid,\n\n\t\t\t\t\t\tserver_uuid: server.uuid,\n\n\t\t\t\t\t\tenvironment_uuid: env.uuid,\n\n\t\t\t\t\t\tenvironment_name: env.name,\n\n\t\t\t\t\t\tdocker_compose_raw: composeYaml,\n\n\t\t\t\t\t\tname: input.projectName,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst newHash = hashString(composeYaml);\n\t\t\t\tconst oldHash = hashString(service.docker_compose_raw ?? \"\");\n\n\t\t\t\tif (newHash !== oldHash) {\n\t\t\t\t\tawait coolifyFetch(input.target, `/services/${service.uuid}`, {\n\t\t\t\t\t\tmethod: \"PATCH\",\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tdocker_compose_raw: composeYaml,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresult.composeId = service.uuid;\n\n\t\t\tstep3.status = \"done\";\n\t\t\tstep3.detail = service.uuid;\n\n\t\t\t/* ----------------------------- */\n\t\t\t/* STEP 4: Environment variables */\n\t\t\t/* ----------------------------- */\n\n\t\t\tstep4.status = \"running\";\n\n\t\t\tconst envVars = parseEnvContent(input.envContent);\n\n\t\t\tif (envVars.length) {\n\t\t\t\tawait coolifyFetch(input.target, `/services/${service.uuid}/envs`, {\n\t\t\t\t\tmethod: \"PATCH\",\n\t\t\t\t\tbody: envVars,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tstep4.status = \"done\";\n\t\t\tstep4.detail = `${envVars.length} vars`;\n\n\t\t\t/* ----------------------------- */\n\t\t\t/* STEP 5: Deploy */\n\t\t\t/* ----------------------------- */\n\n\t\t\tstep5.status = \"running\";\n\n\t\t\tconst deploy = await coolifyFetch<{ deployments: CoolifyDeployment[] }>(\n\t\t\t\tinput.target,\n\t\t\t\t`/deploy?uuid=${service.uuid}&force=true`,\n\t\t\t);\n\n\t\t\tstep5.status = \"done\";\n\t\t\tstep5.detail = deploy.deployments?.[0]?.deployment_uuid ?? \"Deployment started\";\n\n\t\t\tresult.success = true;\n\n\t\t\tconst base = input.target.instanceUrl.replace(/\\/+$/, \"\");\n\n\t\t\tresult.dashboardUrl = `${base}/project/${project.uuid}`;\n\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tconst running = steps.find((s) => s.status === \"running\");\n\n\t\t\tif (running) {\n\t\t\t\trunning.status = \"error\";\n\t\t\t\trunning.detail = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\n\t\t\tresult.error = err instanceof Error ? err.message : String(err);\n\n\t\t\treturn result;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -1,22 +1,15 @@
1
1
  import { DeployInput, DeployResult, DeployTarget, PaasDeployer } from "./types.cjs";
2
2
 
3
3
  //#region src/deployers/coolify.d.ts
4
- /**
5
- * Deploys Docker Compose stacks to a Coolify v4 instance.
6
- *
7
- * Deploy flow (5 steps):
8
- * 1. Discover the first available server
9
- * 2. Create a Coolify project
10
- * 3. Create a compose service with the raw docker-compose YAML
11
- * 4. Push .env variables via the bulk env API
12
- * 5. Trigger the deployment
13
- */
14
4
  declare class CoolifyDeployer implements PaasDeployer {
15
5
  readonly name = "Coolify";
16
6
  readonly id = "coolify";
17
7
  testConnection(target: DeployTarget): Promise<{
18
8
  ok: boolean;
19
- error?: string;
9
+ error?: undefined;
10
+ } | {
11
+ ok: boolean;
12
+ error: string;
20
13
  }>;
21
14
  deploy(input: DeployInput): Promise<DeployResult>;
22
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"coolify.d.cts","names":[],"sources":["../../src/deployers/coolify.ts"],"mappings":";;;;;;;;;;;;;cA+Ha,eAAA,YAA2B,YAAA;EAAA,SAC9B,IAAA;EAAA,SACA,EAAA;EAEH,cAAA,CAAe,MAAA,EAAQ,YAAA,GAAe,OAAA;IAAU,EAAA;IAAa,KAAA;EAAA;EAS7D,MAAA,CAAO,KAAA,EAAO,WAAA,GAAc,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"coolify.d.cts","names":[],"sources":["../../src/deployers/coolify.ts"],"mappings":";;;cAuIa,eAAA,YAA2B,YAAA;EAAA,SAC9B,IAAA;EAAA,SACA,EAAA;EAEH,cAAA,CAAe,MAAA,EAAQ,YAAA,GAAY,OAAA;;;;;;;EAanC,MAAA,CAAO,KAAA,EAAO,WAAA,GAAc,OAAA,CAAQ,YAAA;AAAA"}
@@ -1,22 +1,15 @@
1
1
  import { DeployInput, DeployResult, DeployTarget, PaasDeployer } from "./types.mjs";
2
2
 
3
3
  //#region src/deployers/coolify.d.ts
4
- /**
5
- * Deploys Docker Compose stacks to a Coolify v4 instance.
6
- *
7
- * Deploy flow (5 steps):
8
- * 1. Discover the first available server
9
- * 2. Create a Coolify project
10
- * 3. Create a compose service with the raw docker-compose YAML
11
- * 4. Push .env variables via the bulk env API
12
- * 5. Trigger the deployment
13
- */
14
4
  declare class CoolifyDeployer implements PaasDeployer {
15
5
  readonly name = "Coolify";
16
6
  readonly id = "coolify";
17
7
  testConnection(target: DeployTarget): Promise<{
18
8
  ok: boolean;
19
- error?: string;
9
+ error?: undefined;
10
+ } | {
11
+ ok: boolean;
12
+ error: string;
20
13
  }>;
21
14
  deploy(input: DeployInput): Promise<DeployResult>;
22
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"coolify.d.mts","names":[],"sources":["../../src/deployers/coolify.ts"],"mappings":";;;;;;;;;;;;;cA+Ha,eAAA,YAA2B,YAAA;EAAA,SAC9B,IAAA;EAAA,SACA,EAAA;EAEH,cAAA,CAAe,MAAA,EAAQ,YAAA,GAAe,OAAA;IAAU,EAAA;IAAa,KAAA;EAAA;EAS7D,MAAA,CAAO,KAAA,EAAO,WAAA,GAAc,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"coolify.d.mts","names":[],"sources":["../../src/deployers/coolify.ts"],"mappings":";;;cAuIa,eAAA,YAA2B,YAAA;EAAA,SAC9B,IAAA;EAAA,SACA,EAAA;EAEH,cAAA,CAAe,MAAA,EAAQ,YAAA,GAAY,OAAA;;;;;;;EAanC,MAAA,CAAO,KAAA,EAAO,WAAA,GAAc,OAAA,CAAQ,YAAA;AAAA"}