# Monorepo

Um repositório com `apps/web` e `apps/api` vira **dois serviços do mesmo projeto**, cada um com seu `root_dir`. O projeto é o grupo: os serviços se enxergam pela [rede privada](/conectados/rede-privada/).

## Pré-requisitos

- Token com `projects:write`, `deploy` e `env:write`.
- Integração GitHub instalada no time (o `installation_id` identifica a instalação). A rota para listar instalações não faz parte do contrato v13: pegue o `installation_id` no painel.

## Fonte git por serviço

A fonte do serviço é `{"type": "git", "installation_id": "...", "repo": "org/repo", "branch": "main", "root_dir": "/apps/api", "watch_paths": ["apps/api/**"]}`.

- `root_dir`: subdiretório do repositório que é a raiz do serviço (detecção da stack, Dockerfile e contexto de build partem dele). Não pode sair do repositório.
- `watch_paths`: padrões que decidem se um push afeta o serviço. Sem `watch_paths`, todo push na branch o afeta.
- Cada serviço escolhe seu `build.builder` (`auto` detecta pela raiz do serviço).

```bash
# INSTALL: installation_id da integração GitHub do time
NAME="mono-$(date +%s)-$RANDOM"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $NAME-projeto" -d "{\"name\": \"$NAME\"}" | jq -r .id)

# API privada (só o front alcança), em Go
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
  -d "{\"name\": \"api\", \"kind\": \"app\", \"visibility\": \"private\", \"port\": 8080, \"auto_deploy\": true,
       \"build\": {\"builder\": \"go\"},
       \"source\": {\"type\": \"git\", \"installation_id\": \"$INSTALL\", \"repo\": \"org/repo\", \"branch\": \"main\",
                    \"root_dir\": \"/apps/api\", \"watch_paths\": [\"apps/api/**\"]}}"

# front público, em Next.js
curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
  -d "{\"visibility\": \"public\", \"slug\": \"$NAME\", \"port\": 3000, \"auto_deploy\": true,
       \"build\": {\"builder\": \"node\"},
       \"source\": {\"type\": \"git\", \"installation_id\": \"$INSTALL\", \"repo\": \"org/repo\", \"branch\": \"main\",
                    \"root_dir\": \"/apps/web\", \"watch_paths\": [\"apps/web/**\", \"packages/ui/**\"]}}"
```

| Resposta | O que fazer |
|---|---|
| `400 invalid_request` | `root_dir` inválido ou `source.type` desconhecido. |
| `404` | Projeto inexistente ou de outro time (indistinguíveis). |
| `409 name_taken` / `slug_taken` | Nome ou `slug` já em uso. |

## Auto-deploy por git

Com `auto_deploy: true`, o push na branch aciona o deploy. O webhook do GitHub encontra **todos** os serviços ligados ao repositório e à branch; com `watch_paths`, só os afetados pelos arquivos do push. Se mais de um serviço do grupo for afetado, o resultado é **um deploy de grupo**.

## Deploy de grupo

`POST /v1/projects/{p}/deploy` publica o grupo em **ordem topológica das referências**: o serviço referenciado sobe antes de quem o referencia (por exemplo `api` antes de `web`, se `web` usa `${{api.PRIVATE_URL}}`). Se o build de um serviço falhar, os dependentes ficam `blocked`; os que já estavam ativos continuam no ar.

```bash
G=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: grupo-mono-1" -d '{}' | jq -r .id)

# polling do GRUPO: um GET mostra o status geral e o de cada serviço
curl -sS "$NEBULAR_API/v1/projects/$P/group-deployments/$G" -H "Authorization: Bearer $NEBULAR_TOKEN" \
  | jq -r '.status, (.items[] | "\(.service_id) \(.status)")'
```

O corpo é `{}` com `Content-Type: application/json`; o `POST` também é aceito **sem corpo** (nesse caso o `Content-Type` é dispensável). O polling é feito no **grupo** (`GET /v1/projects/{p}/group-deployments/{g}`), não serviço a serviço. Repita a consulta com backoff (2 s, dobrando até 30 s, até ~10 min) até o `status` do grupo deixar de ser `running`: `ready` é sucesso; `failed`, `canceled` ou `superseded` são finais, e o item `failed` traz o `deployment_id` cujo log de build você lê (`GET .../deployments/{d}/logs`). Os itens têm `status` próprio (`queued`, `building`, `deploying`, `ready`, `failed`, `blocked`, `skipped`).

Exemplo executável (projeto de imagem; primeiro com `{}`, depois sem corpo):

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"mono3-$SUF\"}" | jq -r .id)
curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \
  -d '{"source": {"type": "image", "ref": "ghcr.io/org/app:1.0"}}' >/dev/null

# 1) com Content-Type e {}: a forma que funciona em qualquer cliente
G=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: mono3-a-$SUF" -d '{}' | jq -r .id)
for i in $(seq 1 40); do
  st=$(curl -sS "$NEBULAR_API/v1/projects/$P/group-deployments/$G" -H "$H" | jq -r .status)
  [ "$st" = running ] || break
  sleep 2
done
[ "$st" = ready ]

# 2) sem corpo e sem Content-Type: também aceito (vale {})
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" -H "$H" -H "Idempotency-Key: mono3-b-$SUF"
```

| Estado ou erro | O que fazer |
|---|---|
| `blocked` | Um serviço do qual este depende falhou: conserte-o primeiro e redeploye o grupo. |
| `422 unresolved_reference` ao disparar o deploy | Uma `${{serviço.VAR}}` aponta para variável ou serviço inexistente (a lista vem no erro). |
| `422 reference_cycle` ao disparar o deploy | Os serviços se referenciam em ciclo: quebre o ciclo. |
| `429` | Espere `Retry-After`. |

Rollback é **por serviço**: `POST .../services/{s}/deployments/{d}/rollback` ([Rollback](/operacao/rollback/)). Para variáveis e referências entre serviços, veja [Variáveis e referências](/conectados/variaveis-e-referencias/).
