feat(environments): env CRUD, workspace switcher, CLI + MCP parity (#68)

Environment lanes existed as seeded rows (production/preview/development)
with a per-service environment label, but there was no way to list,
create, or delete them. Adds the management surface and the switcher:

- GET /projects/:id/environments with live service counts (sqlc)
- POST /projects/:id/environments — lowercase dns-label names, 1-50
  chars, unique per project (409 on dupe)
- DELETE /environments/:id — refuses while it holds services (409)
- Workspace service list gains env filter chips with counts plus an
  inline "+ env" creator
- CLI: containr environments list|create|delete (--yes on delete);
  MCP: containr_environments_list/create/delete (delete needs confirm)
- OpenAPI: new Environments tag + ProjectEnvironment schema

Live-verified: list counts, name validation, dupe 409, empty delete,
non-empty 409, CLI list.

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-10 01:17:32 +02:00
1 parent dd7b735b38
commit 60145f7b21
13 files changed
+727 -6

No files matched your search

+96
View File
@@ -5939,6 +5939,82 @@ paths:
schema:
$ref: '#/components/schemas/Error'
# Environments
/projects/{id}/environments:
get:
tags:
- Environments
summary: List project environments
description: Returns each environment lane with a live service count — the data behind the environment switcher.
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/ProjectIdParam'
responses:
'200':
description: Project environments
content:
application/json:
schema:
type: object
properties:
environments:
type: array
items:
$ref: '#/components/schemas/ProjectEnvironment'
post:
tags:
- Environments
summary: Create an environment
description: Adds a named environment lane. Names are lowercase alphanumeric with dashes, 1-50 chars, unique per project.
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/ProjectIdParam'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
responses:
'201':
description: Created environment
content:
application/json:
schema:
type: object
properties:
environment:
$ref: '#/components/schemas/ProjectEnvironment'
'409':
description: Environment already exists
/environments/{id}:
delete:
tags:
- Environments
summary: Delete an empty environment
description: Removes an environment lane; fails with 409 while it still holds services.
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Environment deleted
'409':
description: Environment still contains services
# Preview environments
/projects/{id}/preview-environments:
get:
@@ -9552,6 +9628,26 @@ components:
health_check:
type: object
ProjectEnvironment:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
project_id:
type: string
format: uuid
service_count:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
PreviewEnvironment:
type: object
properties: