Skip to content

Commit 60f77e4

Browse files
authored
Merge pull request #10 from StuMason/feature/environment-management
feat: implement environment management feature
2 parents 7f96411 + e417c52 commit 60f77e4

File tree

8 files changed

+139
-494
lines changed

8 files changed

+139
-494
lines changed
Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
description: FOLLOW feature development workflow WHEN implementing new features TO ensure consistent, tested, and documented changes
3-
globs: src/**/*.{ts,tsx}
4-
alwaysApply: true
3+
globs: src/**/*
54
---
65

76
# Feature Development Workflow
@@ -17,7 +16,8 @@ alwaysApply: true
1716
- Locate and review feature documentation in docs/features directory
1817
- Follow documentation requirements systematically
1918
- IMPORTANT Commit frequently to trigger pre-commit hooks (linting, formatting, tests)
20-
- Push to remote remotr
19+
- CRITICAL Always stage ALL modified files after making changes
20+
- CRITICAL Always push changes to remote after committing
2121
- Add comprehensive tests for new functionality
2222
- Push completed work to remote for PR review
2323

@@ -29,28 +29,36 @@ alwaysApply: true
2929
3. Review docs/features/002-server-info-resource.md
3030
4. Implement ServerInfo interface
3131
5. Add getServerInfo method with tests
32-
6. Commit to run hooks: "feat: add server info interface"
33-
7. Implement ServerStatus interface
34-
8. Add getServerStatus method with tests
35-
9. Commit to run hooks: "feat: add server status resource"
36-
10. Fix any linting issues
37-
11. Push to remote when complete
32+
6. git add . # Stage ALL changes
33+
7. Commit to run hooks: "feat: add server info interface"
34+
8. git push origin feature/server-info # Push changes immediately
35+
9. Implement ServerStatus interface
36+
10. Add getServerStatus method with tests
37+
11. git add . # Stage ALL changes again
38+
12. Commit to run hooks: "feat: add server status resource"
39+
13. git push origin feature/server-info # Push changes again
40+
14. Fix any linting issues
41+
15. git add . && git commit -m "fix: linting issues"
42+
16. git push origin feature/server-info
3843
</example>
3944

4045
<example type="invalid">
4146
# Poor workflow
4247
1. Start coding without checking docs
4348
2. Make all changes in one big commit
4449
3. Skip tests or add them later
45-
4. Push directly to main
46-
5. Fix linting issues after PR
50+
4. Leave changes unstaged
51+
5. Forget to push changes to remote
52+
6. Push directly to main
53+
7. Fix linting issues after PR
4754
</example>
4855

4956
## Critical Points
5057
<critical>
5158
- ALWAYS work from feature documentation
52-
-
5359
- NEVER skip tests for new functionality
5460
- Commit OFTEN to utilize pre-commit hooks
61+
- ALWAYS stage ALL modified files after making changes
62+
- ALWAYS push changes to remote after committing
5563
- Keep commits focused and well-described
5664
</critical>

.cursor/rules/802-coolify-mcp-workflow.mdc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: FOLLOW Coolify MCP workflow WHEN implementing new Coolify API endpoints TO ensure consistent and tested MCP tools
2+
description: FOLLOW Coolify MCP workflow WHEN implementing new Coolify API endpoints TO ensure consistent and tested MCP
33
globs: src/**/*.{ts,tsx}
44
alwaysApply: false
55
---
@@ -13,7 +13,7 @@ alwaysApply: false
1313

1414
## Requirements
1515
1. Start by reviewing feature documentation in docs/features
16-
2. Verify endpoint exists in openapi.yaml specification
16+
2. Verify endpoint exists in docs/openapi-chunks/*
1717
3. Follow existing implementation patterns:
1818
- Add types to src/types/coolify.ts
1919
- Add client method to src/lib/coolify-client.ts

docs/features/004-environment-management.md

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,59 @@
22

33
## Context
44

5-
Environment management within projects - allows setting up different environments (dev, staging, prod).
5+
Environment management within projects - allows retrieving environment information and deploying applications within environments.
66

77
## API Endpoints Used
88

9-
- GET `/environments` (Line ~1200)
9+
- GET `/projects/{uuid}/{environment_name_or_uuid}`
1010

11-
- Lists all environments
12-
- Query params: project_uuid (optional)
13-
- Response: Array of Environment objects
11+
- Get environment details by project UUID and environment name/UUID
12+
- Response: Environment object
1413
- Auth: Bearer token required
1514

16-
- POST `/environments` (Line ~1250)
17-
18-
- Create new environment
19-
- Request body: {
20-
name: string,
21-
project_uuid: string,
22-
variables?: Record<string, string>
23-
}
24-
- Response: Environment object
15+
- POST `/applications/{uuid}/deploy`
16+
- Deploy an application using its UUID
17+
- Response: Deployment object
2518
- Auth: Bearer token required
2619

27-
- GET `/environments/{uuid}` (Line ~1300)
20+
Note: Environment creation and management is handled through the Projects API. Environments are created and configured as part of project setup.
2821

29-
- Get environment details
30-
- Response: Environment object with variables
31-
- Auth: Bearer token required
22+
## Implementation Status
3223

33-
- DELETE `/environments/{uuid}` (Line ~1350)
24+
### Completed
3425

35-
- Delete environment
36-
- Response: 204 No Content
37-
- Auth: Bearer token required
26+
- [x] Environment Detail Resource
3827

39-
- PUT `/environments/{uuid}/variables` (Line ~1400)
40-
- Update environment variables
41-
- Request body: { variables: Record<string, string> }
42-
- Response: Updated Environment object
43-
- Auth: Bearer token required
28+
- [x] GET project environment endpoint implemented
29+
- [x] Client method: `getProjectEnvironment`
30+
- [x] MCP tool: `get_project_environment`
31+
32+
- [x] Application Deployment
33+
- [x] Deploy application endpoint implemented
34+
- [x] Client method: `deployApplication`
35+
- [x] MCP tool: `deploy_application`
4436

45-
## Implementation Checklist
37+
### Environment Schema
4638

47-
- [x] Environment List Resource
48-
- [x] Environment Management Tools
39+
```typescript
40+
interface Environment {
41+
id: number;
42+
name: string;
43+
project_id: number;
44+
created_at: string;
45+
updated_at: string;
46+
description: string;
47+
}
48+
```
4949

5050
## Dependencies
5151

5252
- ADR 001 (Core Server Setup)
5353
- ADR 003 (Project Management)
54+
55+
## Notes
56+
57+
- Environment management is tightly coupled with projects in the Coolify API
58+
- Environment variables are managed at the application level during application creation/updates
59+
- Direct environment CRUD operations are not available through dedicated endpoints
60+
- Environment information can be retrieved through the project endpoints

src/__tests__/coolify-client.test.ts

Lines changed: 37 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { CoolifyClient } from '../lib/coolify-client.js';
2-
import type {
3-
ServerInfo,
4-
ServerResources,
5-
Environment,
6-
Application,
7-
CreateApplicationRequest,
8-
} from '../types/coolify.js';
2+
import type { ServerInfo, ServerResources, Environment, Deployment } from '../types/coolify.js';
93

104
// Mock fetch globally
115
const mockFetch = jest.fn();
@@ -300,158 +294,51 @@ describe('CoolifyClient', () => {
300294
});
301295
});
302296

303-
describe('Application Management', () => {
304-
const mockApplication: Application = {
305-
id: 1,
306-
uuid: 'test-app-uuid',
307-
name: 'test-app',
308-
environment_uuid: 'test-env-uuid',
309-
project_uuid: 'test-project-uuid',
310-
git_repository: 'https://github.com/test/repo',
311-
git_branch: 'main',
312-
build_pack: 'nixpacks',
313-
ports_exposes: '3000',
314-
status: 'running',
315-
created_at: '2024-03-05T12:00:00Z',
316-
updated_at: '2024-03-05T12:00:00Z',
317-
};
318-
319-
describe('listApplications', () => {
320-
it('should fetch all applications when no environment UUID is provided', async () => {
321-
mockFetch.mockResolvedValueOnce({
322-
ok: true,
323-
json: async () => [mockApplication],
324-
});
325-
326-
const result = await client.listApplications();
327-
328-
expect(result).toEqual([mockApplication]);
329-
expect(mockFetch).toHaveBeenCalledWith(
330-
'http://test.coolify.io/api/v1/applications',
331-
expect.objectContaining({
332-
headers: expect.objectContaining({
333-
Authorization: 'Bearer test-token',
334-
}),
335-
}),
336-
);
337-
});
338-
339-
it('should fetch applications filtered by environment UUID', async () => {
340-
mockFetch.mockResolvedValueOnce({
341-
ok: true,
342-
json: async () => [mockApplication],
343-
});
344-
345-
const result = await client.listApplications('test-env-uuid');
346-
347-
expect(result).toEqual([mockApplication]);
348-
expect(mockFetch).toHaveBeenCalledWith(
349-
'http://test.coolify.io/api/v1/applications?environment_uuid=test-env-uuid',
350-
expect.objectContaining({
351-
headers: expect.objectContaining({
352-
Authorization: 'Bearer test-token',
353-
}),
354-
}),
355-
);
356-
});
357-
});
358-
359-
describe('getApplication', () => {
360-
it('should fetch application details successfully', async () => {
361-
mockFetch.mockResolvedValueOnce({
362-
ok: true,
363-
json: async () => mockApplication,
364-
});
365-
366-
const result = await client.getApplication('test-app-uuid');
297+
describe('deployApplication', () => {
298+
it('should deploy an application', async () => {
299+
const mockDeployment: Deployment = {
300+
id: 1,
301+
uuid: 'test-deployment-uuid',
302+
application_uuid: 'test-app-uuid',
303+
status: 'running',
304+
created_at: '2024-03-20T12:00:00Z',
305+
updated_at: '2024-03-20T12:00:00Z',
306+
};
367307

368-
expect(result).toEqual(mockApplication);
369-
expect(mockFetch).toHaveBeenCalledWith(
370-
'http://test.coolify.io/api/v1/applications/test-app-uuid',
371-
expect.objectContaining({
372-
headers: expect.objectContaining({
373-
Authorization: 'Bearer test-token',
374-
}),
375-
}),
376-
);
308+
mockFetch.mockResolvedValueOnce({
309+
ok: true,
310+
json: () => Promise.resolve(mockDeployment),
377311
});
378-
});
379-
380-
describe('createApplication', () => {
381-
it('should create a new application successfully', async () => {
382-
mockFetch.mockResolvedValueOnce({
383-
ok: true,
384-
json: async () => mockApplication,
385-
});
386312

387-
const createRequest: CreateApplicationRequest = {
388-
project_uuid: 'test-project-uuid',
389-
environment_uuid: 'test-env-uuid',
390-
git_repository: 'https://github.com/test/repo',
391-
git_branch: 'main',
392-
build_pack: 'nixpacks',
393-
ports_exposes: '3000',
394-
name: 'test-app',
395-
};
396-
397-
const result = await client.createApplication(createRequest);
398-
399-
expect(result).toEqual(mockApplication);
400-
expect(mockFetch).toHaveBeenCalledWith(
401-
'http://test.coolify.io/api/v1/applications/public',
402-
expect.objectContaining({
403-
method: 'POST',
404-
body: JSON.stringify(createRequest),
405-
headers: expect.objectContaining({
406-
Authorization: 'Bearer test-token',
407-
}),
313+
const result = await client.deployApplication('test-app-uuid');
314+
expect(result).toEqual(mockDeployment);
315+
expect(mockFetch).toHaveBeenCalledWith(
316+
'http://test.coolify.io/api/v1/applications/test-app-uuid/deploy',
317+
expect.objectContaining({
318+
method: 'POST',
319+
headers: expect.objectContaining({
320+
Authorization: 'Bearer test-token',
321+
'Content-Type': 'application/json',
408322
}),
409-
);
410-
});
323+
}),
324+
);
411325
});
412326

413-
describe('deleteApplication', () => {
414-
it('should delete an application successfully', async () => {
415-
mockFetch.mockResolvedValueOnce({
416-
ok: true,
417-
json: async () => ({}),
418-
});
419-
420-
await client.deleteApplication('test-app-uuid');
327+
it('should handle errors when deploying an application', async () => {
328+
const errorResponse = {
329+
error: 'Error',
330+
status: 500,
331+
message: 'Failed to deploy application',
332+
};
421333

422-
expect(mockFetch).toHaveBeenCalledWith(
423-
'http://test.coolify.io/api/v1/applications/test-app-uuid',
424-
expect.objectContaining({
425-
method: 'DELETE',
426-
headers: expect.objectContaining({
427-
Authorization: 'Bearer test-token',
428-
}),
429-
}),
430-
);
334+
mockFetch.mockResolvedValueOnce({
335+
ok: false,
336+
json: () => Promise.resolve(errorResponse),
431337
});
432-
});
433338

434-
describe('deployApplication', () => {
435-
it('should make a POST request to deploy an application', async () => {
436-
const mockDeployment = { id: 'test-deployment' };
437-
mockFetch.mockResolvedValueOnce({
438-
ok: true,
439-
json: async () => mockDeployment,
440-
});
441-
442-
const result = await client.deployApplication('test-app-uuid');
443-
444-
expect(mockFetch).toHaveBeenCalledWith(
445-
'http://test.coolify.io/api/v1/applications/test-app-uuid/deploy',
446-
expect.objectContaining({
447-
method: 'POST',
448-
headers: expect.objectContaining({
449-
Authorization: 'Bearer test-token',
450-
}),
451-
}),
452-
);
453-
expect(result).toEqual(mockDeployment);
454-
});
339+
await expect(client.deployApplication('test-app-uuid')).rejects.toThrow(
340+
'Failed to deploy application',
341+
);
455342
});
456343
});
457344
});

0 commit comments

Comments
 (0)