@masonator/coolify-mcp 0.2.18 → 0.3.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/LICENSE +21 -0
- package/README.md +148 -171
- package/dist/__tests__/coolify-client.test.d.ts +1 -0
- package/dist/__tests__/coolify-client.test.js +286 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -139
- package/dist/lib/coolify-client.d.ts +97 -0
- package/dist/lib/coolify-client.js +502 -0
- package/dist/lib/mcp-server.d.ts +29 -0
- package/dist/lib/mcp-server.js +211 -0
- package/dist/types/coolify.d.ts +640 -0
- package/dist/types/coolify.js +5 -0
- package/package.json +36 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Stu Mason
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,92 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
A Model Context Protocol (MCP) server implementation for [Coolify](https://coolify.io/), enabling AI assistants to interact with your Coolify instances through natural language.
|
|
4
|
-
|
|
5
|
-
## Example Prompts
|
|
6
|
-
|
|
7
|
-
Here are example prompts you can use with MCP-compatible AI assistants to interact with your Coolify instance:
|
|
8
|
-
|
|
9
|
-
### Server Management
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
# List and Inspect Servers
|
|
13
|
-
- Show me all Coolify servers in my instance
|
|
14
|
-
- What's the status of server {uuid}?
|
|
15
|
-
- Show me the resources running on server {uuid}
|
|
16
|
-
- What domains are configured for server {uuid}?
|
|
17
|
-
- Can you validate the connection to server {uuid}?
|
|
18
|
-
|
|
19
|
-
# Resource Monitoring
|
|
20
|
-
- How much CPU and memory is server {uuid} using?
|
|
21
|
-
- List all resources running on server {uuid}
|
|
22
|
-
- Show me the current status of all servers
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Project Management
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
# Project Operations
|
|
29
|
-
- List all my Coolify projects
|
|
30
|
-
- Create a new project called "my-webapp" with description "My web application"
|
|
31
|
-
- Show me the details of project {uuid}
|
|
32
|
-
- Update project {uuid} to change its name to "new-name"
|
|
33
|
-
- Delete project {uuid}
|
|
34
|
-
|
|
35
|
-
# Environment Management
|
|
36
|
-
- Show me the environments in project {uuid}
|
|
37
|
-
- Get details of the production environment in project {uuid}
|
|
38
|
-
- What variables are set in the staging environment of project {uuid}?
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Application and Service Management
|
|
1
|
+
[](https://mseep.ai/app/stumason-coolify-mcp)
|
|
42
2
|
|
|
43
|
-
|
|
44
|
-
# Application Management
|
|
45
|
-
- List all applications
|
|
46
|
-
- Show me details of application {uuid}
|
|
47
|
-
- Create a new application called "my-nodejs-app"
|
|
48
|
-
- Delete application {uuid}
|
|
49
|
-
|
|
50
|
-
# Service Operations
|
|
51
|
-
- Show me all running services
|
|
52
|
-
- Create a new WordPress service:
|
|
53
|
-
- Name: my-blog
|
|
54
|
-
- Project UUID: {project_uuid}
|
|
55
|
-
- Server UUID: {server_uuid}
|
|
56
|
-
- Type: wordpress-with-mysql
|
|
57
|
-
- What's the status of service {uuid}?
|
|
58
|
-
- Delete service {uuid} and clean up its resources
|
|
59
|
-
```
|
|
3
|
+
# Coolify MCP Server
|
|
60
4
|
|
|
61
|
-
|
|
5
|
+
A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabling AI assistants to manage and debug your Coolify instances through natural language.
|
|
62
6
|
|
|
63
|
-
|
|
64
|
-
# Database Operations
|
|
65
|
-
- List all databases
|
|
66
|
-
- Show me the configuration of database {uuid}
|
|
67
|
-
- Update database {uuid}:
|
|
68
|
-
- Increase memory limit to 1GB
|
|
69
|
-
- Change public port to 5432
|
|
70
|
-
- Update password
|
|
71
|
-
- Delete database {uuid} and clean up volumes
|
|
72
|
-
|
|
73
|
-
# Database Types
|
|
74
|
-
- Create a PostgreSQL database
|
|
75
|
-
- Set up a Redis instance
|
|
76
|
-
- Configure a MongoDB database
|
|
77
|
-
- Initialize a MySQL database
|
|
78
|
-
```
|
|
7
|
+
## Features
|
|
79
8
|
|
|
80
|
-
|
|
9
|
+
This MCP server provides 46 tools focused on **debugging, management, and deployment**:
|
|
81
10
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
11
|
+
| Category | Tools |
|
|
12
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| **Servers** | list, get, validate, resources, domains |
|
|
14
|
+
| **Projects** | list, get, create, update, delete |
|
|
15
|
+
| **Environments** | list, get, create, delete |
|
|
16
|
+
| **Applications** | list, get, update, delete, start, stop, restart, logs, env vars (CRUD), create (private-gh, private-key) |
|
|
17
|
+
| **Databases** | list, get, start, stop, restart |
|
|
18
|
+
| **Services** | list, get, start, stop, restart, env vars (list, create, delete) |
|
|
19
|
+
| **Deployments** | list, get, deploy, list by application |
|
|
90
20
|
|
|
91
21
|
## Installation
|
|
92
22
|
|
|
@@ -94,121 +24,171 @@ Here are example prompts you can use with MCP-compatible AI assistants to intera
|
|
|
94
24
|
|
|
95
25
|
- Node.js >= 18
|
|
96
26
|
- A running Coolify instance
|
|
97
|
-
- Coolify API access token
|
|
27
|
+
- Coolify API access token (generate in Coolify Settings > API)
|
|
98
28
|
|
|
99
|
-
###
|
|
29
|
+
### Claude Desktop
|
|
100
30
|
|
|
101
|
-
|
|
31
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
102
32
|
|
|
103
33
|
```json
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"COOLIFY_ACCESS_TOKEN": "
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"coolify": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "@masonator/coolify-mcp"],
|
|
39
|
+
"env": {
|
|
40
|
+
"COOLIFY_ACCESS_TOKEN": "your-api-token",
|
|
111
41
|
"COOLIFY_BASE_URL": "https://your-coolify-instance.com"
|
|
42
|
+
}
|
|
112
43
|
}
|
|
44
|
+
}
|
|
113
45
|
}
|
|
114
46
|
```
|
|
115
47
|
|
|
116
|
-
|
|
48
|
+
### Claude Code
|
|
117
49
|
|
|
118
50
|
```bash
|
|
119
|
-
|
|
51
|
+
claude mcp add coolify \
|
|
52
|
+
--env COOLIFY_BASE_URL="https://your-coolify-instance.com" \
|
|
53
|
+
--env COOLIFY_ACCESS_TOKEN="your-api-token" \
|
|
54
|
+
-- npx -y @masonator/coolify-mcp
|
|
120
55
|
```
|
|
121
56
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
### Local Setup
|
|
57
|
+
### Cursor
|
|
125
58
|
|
|
126
59
|
```bash
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
cd coolify-mcp
|
|
60
|
+
env COOLIFY_ACCESS_TOKEN=your-api-token COOLIFY_BASE_URL=https://your-coolify-instance.com npx -y @masonator/coolify-mcp
|
|
61
|
+
```
|
|
130
62
|
|
|
131
|
-
|
|
132
|
-
npm install
|
|
63
|
+
## Example Prompts
|
|
133
64
|
|
|
134
|
-
|
|
135
|
-
npm run build
|
|
65
|
+
### Debugging & Monitoring
|
|
136
66
|
|
|
137
|
-
|
|
138
|
-
|
|
67
|
+
```
|
|
68
|
+
Show me all servers and their status
|
|
69
|
+
What resources are running on server {uuid}?
|
|
70
|
+
Get the logs for application {uuid}
|
|
71
|
+
What environment variables are set for application {uuid}?
|
|
72
|
+
Show me recent deployments for application {uuid}
|
|
139
73
|
```
|
|
140
74
|
|
|
141
|
-
###
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
# Required
|
|
145
|
-
COOLIFY_ACCESS_TOKEN=your_access_token_here
|
|
75
|
+
### Application Management
|
|
146
76
|
|
|
147
|
-
|
|
148
|
-
|
|
77
|
+
```
|
|
78
|
+
Restart application {uuid}
|
|
79
|
+
Stop the database {uuid}
|
|
80
|
+
Start service {uuid}
|
|
81
|
+
Deploy application {uuid} with force rebuild
|
|
82
|
+
Update the DATABASE_URL env var for application {uuid}
|
|
149
83
|
```
|
|
150
84
|
|
|
151
|
-
|
|
85
|
+
### Project Setup
|
|
152
86
|
|
|
153
|
-
|
|
87
|
+
```
|
|
88
|
+
Create a new project called "my-app"
|
|
89
|
+
Create a staging environment in project {uuid}
|
|
90
|
+
Deploy my app from private GitHub repo org/repo on branch main
|
|
91
|
+
```
|
|
154
92
|
|
|
155
|
-
|
|
93
|
+
## Environment Variables
|
|
156
94
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// Additional properties based on your Coolify instance
|
|
162
|
-
}
|
|
163
|
-
```
|
|
95
|
+
| Variable | Required | Default | Description |
|
|
96
|
+
| ---------------------- | -------- | ----------------------- | ------------------------- |
|
|
97
|
+
| `COOLIFY_ACCESS_TOKEN` | Yes | - | Your Coolify API token |
|
|
98
|
+
| `COOLIFY_BASE_URL` | No | `http://localhost:3000` | Your Coolify instance URL |
|
|
164
99
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
interface Service {
|
|
169
|
-
id: number;
|
|
170
|
-
uuid: string;
|
|
171
|
-
name: string;
|
|
172
|
-
type: ServiceType; // Various types like 'wordpress', 'mysql', etc.
|
|
173
|
-
status: 'running' | 'stopped' | 'error';
|
|
174
|
-
project_uuid: string;
|
|
175
|
-
environment_uuid: string;
|
|
176
|
-
server_uuid: string;
|
|
177
|
-
domains?: string[];
|
|
178
|
-
}
|
|
179
|
-
```
|
|
100
|
+
## Development
|
|
180
101
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
uuid: string;
|
|
187
|
-
name: string;
|
|
188
|
-
type: 'postgresql' | 'mysql' | 'mongodb' | 'redis' | /* other types */;
|
|
189
|
-
status: 'running' | 'stopped' | 'error';
|
|
190
|
-
is_public: boolean;
|
|
191
|
-
public_port?: number;
|
|
192
|
-
// Additional configuration based on database type
|
|
193
|
-
}
|
|
194
|
-
```
|
|
102
|
+
```bash
|
|
103
|
+
# Clone and install
|
|
104
|
+
git clone https://github.com/stumason/coolify-mcp.git
|
|
105
|
+
cd coolify-mcp
|
|
106
|
+
npm install
|
|
195
107
|
|
|
196
|
-
|
|
108
|
+
# Build
|
|
109
|
+
npm run build
|
|
197
110
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
111
|
+
# Test
|
|
112
|
+
npm test
|
|
113
|
+
|
|
114
|
+
# Run locally
|
|
115
|
+
COOLIFY_BASE_URL="https://your-coolify.com" \
|
|
116
|
+
COOLIFY_ACCESS_TOKEN="your-token" \
|
|
117
|
+
node dist/index.js
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Available Tools
|
|
121
|
+
|
|
122
|
+
### Servers
|
|
123
|
+
|
|
124
|
+
- `get_version` - Get Coolify API version
|
|
125
|
+
- `list_servers` - List all servers
|
|
126
|
+
- `get_server` - Get server details
|
|
127
|
+
- `get_server_resources` - Get resources running on a server
|
|
128
|
+
- `get_server_domains` - Get domains configured on a server
|
|
129
|
+
- `validate_server` - Validate server connection
|
|
130
|
+
|
|
131
|
+
### Projects
|
|
132
|
+
|
|
133
|
+
- `list_projects` - List all projects
|
|
134
|
+
- `get_project` - Get project details
|
|
135
|
+
- `create_project` - Create a new project
|
|
136
|
+
- `update_project` - Update a project
|
|
137
|
+
- `delete_project` - Delete a project
|
|
138
|
+
|
|
139
|
+
### Environments
|
|
140
|
+
|
|
141
|
+
- `list_environments` - List environments in a project
|
|
142
|
+
- `get_environment` - Get environment details
|
|
143
|
+
- `create_environment` - Create environment in a project
|
|
144
|
+
- `delete_environment` - Delete an environment
|
|
145
|
+
|
|
146
|
+
### Applications
|
|
147
|
+
|
|
148
|
+
- `list_applications` - List all applications
|
|
149
|
+
- `get_application` - Get application details
|
|
150
|
+
- `create_application_private_gh` - Create app from private GitHub repo (GitHub App)
|
|
151
|
+
- `create_application_private_key` - Create app from private repo using deploy key
|
|
152
|
+
- `update_application` - Update an application
|
|
153
|
+
- `delete_application` - Delete an application
|
|
154
|
+
- `start_application` - Start an application
|
|
155
|
+
- `stop_application` - Stop an application
|
|
156
|
+
- `restart_application` - Restart an application
|
|
157
|
+
- `get_application_logs` - Get application logs
|
|
158
|
+
- `list_application_envs` - List application environment variables
|
|
159
|
+
- `create_application_env` - Create application environment variable
|
|
160
|
+
- `update_application_env` - Update application environment variable
|
|
161
|
+
- `delete_application_env` - Delete application environment variable
|
|
162
|
+
|
|
163
|
+
### Databases
|
|
164
|
+
|
|
165
|
+
- `list_databases` - List all databases
|
|
166
|
+
- `get_database` - Get database details
|
|
167
|
+
- `start_database` - Start a database
|
|
168
|
+
- `stop_database` - Stop a database
|
|
169
|
+
- `restart_database` - Restart a database
|
|
170
|
+
|
|
171
|
+
### Services
|
|
172
|
+
|
|
173
|
+
- `list_services` - List all services
|
|
174
|
+
- `get_service` - Get service details
|
|
175
|
+
- `start_service` - Start a service
|
|
176
|
+
- `stop_service` - Stop a service
|
|
177
|
+
- `restart_service` - Restart a service
|
|
178
|
+
- `list_service_envs` - List service environment variables
|
|
179
|
+
- `create_service_env` - Create service environment variable
|
|
180
|
+
- `delete_service_env` - Delete service environment variable
|
|
181
|
+
|
|
182
|
+
### Deployments
|
|
183
|
+
|
|
184
|
+
- `list_deployments` - List running deployments
|
|
185
|
+
- `get_deployment` - Get deployment details
|
|
186
|
+
- `deploy` - Deploy by tag or UUID
|
|
187
|
+
- `list_application_deployments` - List deployments for an application
|
|
208
188
|
|
|
209
189
|
## Contributing
|
|
210
190
|
|
|
211
|
-
Contributions
|
|
191
|
+
Contributions welcome! Please open an issue first to discuss major changes.
|
|
212
192
|
|
|
213
193
|
## License
|
|
214
194
|
|
|
@@ -216,8 +196,5 @@ MIT
|
|
|
216
196
|
|
|
217
197
|
## Support
|
|
218
198
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
1. Check the [issues](https://github.com/stumason/coolify-mcp/issues) page
|
|
222
|
-
2. Create a new issue if needed
|
|
223
|
-
3. Join the Coolify community
|
|
199
|
+
- [GitHub Issues](https://github.com/stumason/coolify-mcp/issues)
|
|
200
|
+
- [Coolify Community](https://coolify.io/docs/contact)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { CoolifyClient } from '../lib/coolify-client.js';
|
|
3
|
+
// Helper to create mock response
|
|
4
|
+
function mockResponse(data, ok = true, status = 200) {
|
|
5
|
+
return {
|
|
6
|
+
ok,
|
|
7
|
+
status,
|
|
8
|
+
statusText: ok ? 'OK' : 'Error',
|
|
9
|
+
text: async () => JSON.stringify(data),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const mockFetch = jest.fn();
|
|
13
|
+
describe('CoolifyClient', () => {
|
|
14
|
+
let client;
|
|
15
|
+
const mockServers = [
|
|
16
|
+
{
|
|
17
|
+
id: 1,
|
|
18
|
+
uuid: 'test-uuid',
|
|
19
|
+
name: 'test-server',
|
|
20
|
+
ip: '192.168.1.1',
|
|
21
|
+
user: 'root',
|
|
22
|
+
port: 22,
|
|
23
|
+
created_at: '2024-01-01',
|
|
24
|
+
updated_at: '2024-01-01',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
const mockServerInfo = {
|
|
28
|
+
id: 1,
|
|
29
|
+
uuid: 'test-uuid',
|
|
30
|
+
name: 'test-server',
|
|
31
|
+
ip: '192.168.1.1',
|
|
32
|
+
user: 'root',
|
|
33
|
+
port: 22,
|
|
34
|
+
created_at: '2024-01-01',
|
|
35
|
+
updated_at: '2024-01-01',
|
|
36
|
+
};
|
|
37
|
+
const mockServerResources = [
|
|
38
|
+
{
|
|
39
|
+
id: 1,
|
|
40
|
+
uuid: 'resource-uuid',
|
|
41
|
+
name: 'test-app',
|
|
42
|
+
type: 'application',
|
|
43
|
+
status: 'running',
|
|
44
|
+
created_at: '2024-01-01',
|
|
45
|
+
updated_at: '2024-01-01',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const mockService = {
|
|
49
|
+
id: 1,
|
|
50
|
+
uuid: 'test-uuid',
|
|
51
|
+
name: 'test-service',
|
|
52
|
+
type: 'code-server',
|
|
53
|
+
status: 'running',
|
|
54
|
+
created_at: '2024-01-01',
|
|
55
|
+
updated_at: '2024-01-01',
|
|
56
|
+
};
|
|
57
|
+
const errorResponse = {
|
|
58
|
+
message: 'Resource not found',
|
|
59
|
+
};
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
mockFetch.mockClear();
|
|
62
|
+
global.fetch = mockFetch;
|
|
63
|
+
client = new CoolifyClient({
|
|
64
|
+
baseUrl: 'http://localhost:3000',
|
|
65
|
+
accessToken: 'test-api-key',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('constructor', () => {
|
|
69
|
+
it('should throw error if baseUrl is missing', () => {
|
|
70
|
+
expect(() => new CoolifyClient({ baseUrl: '', accessToken: 'test' })).toThrow('Coolify base URL is required');
|
|
71
|
+
});
|
|
72
|
+
it('should throw error if accessToken is missing', () => {
|
|
73
|
+
expect(() => new CoolifyClient({ baseUrl: 'http://localhost', accessToken: '' })).toThrow('Coolify access token is required');
|
|
74
|
+
});
|
|
75
|
+
it('should strip trailing slash from baseUrl', () => {
|
|
76
|
+
const c = new CoolifyClient({
|
|
77
|
+
baseUrl: 'http://localhost:3000/',
|
|
78
|
+
accessToken: 'test',
|
|
79
|
+
});
|
|
80
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ version: '1.0.0' }));
|
|
81
|
+
c.getVersion();
|
|
82
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/version', expect.any(Object));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('listServers', () => {
|
|
86
|
+
it('should return a list of servers', async () => {
|
|
87
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockServers));
|
|
88
|
+
const servers = await client.listServers();
|
|
89
|
+
expect(servers).toEqual(mockServers);
|
|
90
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers', expect.objectContaining({
|
|
91
|
+
headers: expect.objectContaining({
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
Authorization: 'Bearer test-api-key',
|
|
94
|
+
}),
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
it('should handle errors', async () => {
|
|
98
|
+
mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
|
|
99
|
+
await expect(client.listServers()).rejects.toThrow('Resource not found');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('getServer', () => {
|
|
103
|
+
it('should get server info', async () => {
|
|
104
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockServerInfo));
|
|
105
|
+
const result = await client.getServer('test-uuid');
|
|
106
|
+
expect(result).toEqual(mockServerInfo);
|
|
107
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/test-uuid', expect.any(Object));
|
|
108
|
+
});
|
|
109
|
+
it('should handle errors', async () => {
|
|
110
|
+
mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
|
|
111
|
+
await expect(client.getServer('test-uuid')).rejects.toThrow('Resource not found');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('getServerResources', () => {
|
|
115
|
+
it('should get server resources', async () => {
|
|
116
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockServerResources));
|
|
117
|
+
const result = await client.getServerResources('test-uuid');
|
|
118
|
+
expect(result).toEqual(mockServerResources);
|
|
119
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/test-uuid/resources', expect.any(Object));
|
|
120
|
+
});
|
|
121
|
+
it('should handle errors', async () => {
|
|
122
|
+
mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
|
|
123
|
+
await expect(client.getServerResources('test-uuid')).rejects.toThrow('Resource not found');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('listServices', () => {
|
|
127
|
+
it('should list services', async () => {
|
|
128
|
+
mockFetch.mockResolvedValueOnce(mockResponse([mockService]));
|
|
129
|
+
const result = await client.listServices();
|
|
130
|
+
expect(result).toEqual([mockService]);
|
|
131
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', expect.any(Object));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('getService', () => {
|
|
135
|
+
it('should get service info', async () => {
|
|
136
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockService));
|
|
137
|
+
const result = await client.getService('test-uuid');
|
|
138
|
+
expect(result).toEqual(mockService);
|
|
139
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', expect.any(Object));
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('createService', () => {
|
|
143
|
+
it('should create a service', async () => {
|
|
144
|
+
const responseData = {
|
|
145
|
+
uuid: 'test-uuid',
|
|
146
|
+
domains: ['test.com'],
|
|
147
|
+
};
|
|
148
|
+
mockFetch.mockResolvedValueOnce(mockResponse(responseData));
|
|
149
|
+
const createData = {
|
|
150
|
+
name: 'test-service',
|
|
151
|
+
type: 'code-server',
|
|
152
|
+
project_uuid: 'project-uuid',
|
|
153
|
+
environment_uuid: 'env-uuid',
|
|
154
|
+
server_uuid: 'server-uuid',
|
|
155
|
+
};
|
|
156
|
+
const result = await client.createService(createData);
|
|
157
|
+
expect(result).toEqual(responseData);
|
|
158
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', expect.objectContaining({
|
|
159
|
+
method: 'POST',
|
|
160
|
+
body: JSON.stringify(createData),
|
|
161
|
+
}));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('deleteService', () => {
|
|
165
|
+
it('should delete a service', async () => {
|
|
166
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Service deleted' }));
|
|
167
|
+
const result = await client.deleteService('test-uuid');
|
|
168
|
+
expect(result).toEqual({ message: 'Service deleted' });
|
|
169
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', expect.objectContaining({
|
|
170
|
+
method: 'DELETE',
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
it('should delete a service with options', async () => {
|
|
174
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Service deleted' }));
|
|
175
|
+
await client.deleteService('test-uuid', {
|
|
176
|
+
deleteVolumes: true,
|
|
177
|
+
dockerCleanup: true,
|
|
178
|
+
});
|
|
179
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid?delete_volumes=true&docker_cleanup=true', expect.objectContaining({
|
|
180
|
+
method: 'DELETE',
|
|
181
|
+
}));
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('applications', () => {
|
|
185
|
+
it('should list applications', async () => {
|
|
186
|
+
const mockApps = [{ id: 1, uuid: 'app-uuid', name: 'test-app' }];
|
|
187
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockApps));
|
|
188
|
+
const result = await client.listApplications();
|
|
189
|
+
expect(result).toEqual(mockApps);
|
|
190
|
+
});
|
|
191
|
+
it('should start an application', async () => {
|
|
192
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Started', deployment_uuid: 'dep-uuid' }));
|
|
193
|
+
const result = await client.startApplication('app-uuid', {
|
|
194
|
+
force: true,
|
|
195
|
+
});
|
|
196
|
+
expect(result).toEqual({
|
|
197
|
+
message: 'Started',
|
|
198
|
+
deployment_uuid: 'dep-uuid',
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
it('should stop an application', async () => {
|
|
202
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Stopped' }));
|
|
203
|
+
const result = await client.stopApplication('app-uuid');
|
|
204
|
+
expect(result).toEqual({ message: 'Stopped' });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('databases', () => {
|
|
208
|
+
it('should list databases', async () => {
|
|
209
|
+
const mockDbs = [{ id: 1, uuid: 'db-uuid', name: 'test-db' }];
|
|
210
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockDbs));
|
|
211
|
+
const result = await client.listDatabases();
|
|
212
|
+
expect(result).toEqual(mockDbs);
|
|
213
|
+
});
|
|
214
|
+
it('should start a database', async () => {
|
|
215
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Started' }));
|
|
216
|
+
const result = await client.startDatabase('db-uuid');
|
|
217
|
+
expect(result).toEqual({ message: 'Started' });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('teams', () => {
|
|
221
|
+
it('should list teams', async () => {
|
|
222
|
+
const mockTeams = [{ id: 1, name: 'test-team', personal_team: false }];
|
|
223
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTeams));
|
|
224
|
+
const result = await client.listTeams();
|
|
225
|
+
expect(result).toEqual(mockTeams);
|
|
226
|
+
});
|
|
227
|
+
it('should get current team', async () => {
|
|
228
|
+
const mockTeam = { id: 1, name: 'my-team', personal_team: true };
|
|
229
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockTeam));
|
|
230
|
+
const result = await client.getCurrentTeam();
|
|
231
|
+
expect(result).toEqual(mockTeam);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('deployments', () => {
|
|
235
|
+
it('should list deployments', async () => {
|
|
236
|
+
const mockDeps = [
|
|
237
|
+
{
|
|
238
|
+
id: 1,
|
|
239
|
+
uuid: 'dep-uuid',
|
|
240
|
+
deployment_uuid: 'dep-123',
|
|
241
|
+
status: 'finished',
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockDeps));
|
|
245
|
+
const result = await client.listDeployments();
|
|
246
|
+
expect(result).toEqual(mockDeps);
|
|
247
|
+
});
|
|
248
|
+
it('should deploy by tag', async () => {
|
|
249
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deployed' }));
|
|
250
|
+
const result = await client.deployByTagOrUuid('my-tag', true);
|
|
251
|
+
expect(result).toEqual({ message: 'Deployed' });
|
|
252
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deploy?tag=my-tag&force=true', expect.any(Object));
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('private keys', () => {
|
|
256
|
+
it('should list private keys', async () => {
|
|
257
|
+
const mockKeys = [{ id: 1, uuid: 'key-uuid', name: 'my-key' }];
|
|
258
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockKeys));
|
|
259
|
+
const result = await client.listPrivateKeys();
|
|
260
|
+
expect(result).toEqual(mockKeys);
|
|
261
|
+
});
|
|
262
|
+
it('should create a private key', async () => {
|
|
263
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-key-uuid' }));
|
|
264
|
+
const result = await client.createPrivateKey({
|
|
265
|
+
name: 'new-key',
|
|
266
|
+
private_key: 'ssh-rsa AAAA...',
|
|
267
|
+
});
|
|
268
|
+
expect(result).toEqual({ uuid: 'new-key-uuid' });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
describe('error handling', () => {
|
|
272
|
+
it('should handle network errors', async () => {
|
|
273
|
+
mockFetch.mockRejectedValueOnce(new TypeError('fetch failed'));
|
|
274
|
+
await expect(client.listServers()).rejects.toThrow('Failed to connect to Coolify server');
|
|
275
|
+
});
|
|
276
|
+
it('should handle empty responses', async () => {
|
|
277
|
+
mockFetch.mockResolvedValueOnce({
|
|
278
|
+
ok: true,
|
|
279
|
+
status: 204,
|
|
280
|
+
text: async () => '',
|
|
281
|
+
});
|
|
282
|
+
const result = await client.deleteServer('test-uuid');
|
|
283
|
+
expect(result).toEqual({});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
package/dist/index.d.ts
ADDED