Referência
CLI
Os comandos de trilha e suas opções.
trilha new <dir> [--module caminho] [--lang en|pt] [--agents] [--trilha-dir ../trilha] [--no-tidy]
trilha gen [--check] [--package nome]
trilha generate page|route|test <url> | component <Nome>
[--methods GET,POST] [--bind Tipo] [--form Tipo] [--layout arquivo] [--force] [--dir caminho] [--lang en|pt]
trilha dev [--addr :3000]
trilha build [-o bin/<nome>]
trilha export [-o out] [--base /prefixo]
trilha openapi [-o arquivo] [--title T] [--version V] [--server URL] [--check]
trilha routes
trilha check [--json] [--fix]
trilha ctx [--json] [--routes|--types|--all]
trilha audit [--no-vuln]
trilha ui [--force] [--css-only|--js-only]
trilha agents [--force] [--lang en|pt]
trilha version| Comando | O que faz |
|---|---|
new | cria um projeto com go.mod, layout, página inicial, 404, uma rota de API, public/style.css e .gitignore; roda go mod tidy e gen |
gen | varre app/ e escreve trilha_gen.go; falha com uma linha por convenção violada |
generate | grava um esqueleto — página, rota de API ou componente — na pasta que a convenção pede |
dev | gen + go build + executa o app em uma porta interna + proxy em --addr + recarga por SSE + inspetor de rotas em /_trilha/routes |
build | gen + go build -trimpath -ldflags="-s -w" com CGO_ENABLED=0 |
export | gen + go build + executa com TRILHA_EXPORT para gerar HTML estático |
openapi | escreve o documento OpenAPI 3.1 das rotas de API (-o - na saída padrão) |
routes | imprime MÉTODOS PADRÃO ORIGEM para cada rota |
check | o portão único: gen, gofmt, vet, test, audit e openapi, nesta ordem, parando na primeira falha |
ctx | o mapa do projeto — rotas, API, tipos, setup — numa leitura só, em Markdown ou JSON |
audit | checklist de segurança antes de publicar (veja Segurança) |
agents | grava AGENTS.md e CLAUDE.md para um agente de código achar as convenções |
Os comandos rodam na pasta que contém app/. O caminho de import do projeto vem do go.mod mais próximo, mais a subpasta, então um app pode viver dentro de um módulo maior.
Idioma#
As mensagens da CLI seguem TRILHA_LANG, depois LC_ALL, LC_MESSAGES e LANG: um valor começando com pt (em qualquer caixa) seleciona português; qualquer outro, inclusive variável indefinida, seleciona inglês. As mensagens do runtime, do scanner e do gerador (as que acabam no seu código e nos seus logs) são sempre em inglês.
trilha new --lang en|pt escolhe o idioma dos textos gerados (página inicial, 404, <html lang>); o padrão é o idioma da CLI.
trilha dev#
Além do proxy e da recarga, o supervisor serve o inspetor de rotas em /_trilha/routes: a tabela de rotas em ordem de precedência com layouts e middlewares de cada uma, e uma caixa que responde qual padrão atenderia um caminho. A página é do supervisor, não do app, então ela não existe no binário que o trilha build produz — veja Dev e produção.
trilha generate#
A convenção é o que custa lembrar: que /blog/{slug} mora em app/blog/slug_/, que uma pasta catch-all termina em __, que um grupo termina em -. O generate recebe a URL e faz a tradução:
trilha generate page /blog/{slug} # app/blog/slug_/page.go
trilha generate route /api/itens/{id} # app/api/itens/id_/route.go
trilha generate component Aviso # internal/components/aviso.goA página e a rota saem compilando, com c.Param já lendo cada parâmetro, e o trilha_gen.go é regerado no fim — a URL responde antes de você abrir o editor. Um componente é uma função que devolve h.Node, então compõe como qualquer outra; --dir põe em outro lugar (internal/icones, por exemplo).
O nome do pacote é o que já está declarado na pasta, quando existe; senão vem do nome da pasta (slug_ → slug, relatorio.csv → relatoriocsv, type → type_).
Um arquivo existente não é sobrescrito sem --force, e o --force não cobre a única recusa que é convenção: uma pasta responde ou uma página ou uma rota, nunca as duas.
O contrato, não só a pasta#
Sem flags o esqueleto é genérico, e o que sobra para escrever — a struct, o Bind, a validação, a resposta, o teste — é justamente onde se erra assinatura. As flags escrevem essa parte:
trilha generate route /api/posts/{id}/comments --methods GET,POST --bind Comment
trilha generate page /contato --form Contact --layout app/layout.go
trilha generate test /api/posts--methodsescreve um handler por método, na assinatura que o scanner lê, com oc.Param("id")já pronto para cada parâmetro do caminho.--bind Tipofaz os métodos com corpo chamaremc.BindJSON(&in): devolver esse erro já é o 422 com os campos, não sobra tratamento. Um tipo que o projeto já declara é importado de onde está; um que não existe nasce no pacote da rota com tagsjsonevalidatede exemplo. Um nome declarado em dois pacotes é recusa, e a mensagem manda escreverposts.Comment.--form Tiponuma página escreve a ida e volta inteira:trilha.CSRFInput, umui.Fieldpor campo com a mensagem ao lado, 422 comtrilha.FieldErrorsquando oBindrecusa ePOST → redirect → GETquando ele aceita.--layout <arquivo>grava olayout.goque falta acima da página. Um caminho que não envolve a página é recusado: o scanner nunca o aplicaria, e descobrir isso custa uma ida e volta.generate test <url>escreve o teste ao lado da rota, no pacote dela, com um caso por método que o scanner encontra — e um corpo montado das tags quando dá para ler o tipo que o handler lê. Logo depois de gerar, otrilha checkfica verde sem ninguém editar nada.
O --lang en|pt escolhe o idioma dos comentários do esqueleto; identificadores, nomes de campo e mensagens de erro seguem em inglês.
trilha ui#
Grava ou atualiza o kit de interface em public/: ui.theme.css (só criado; é o seu tema), ui.css e ui.js (atualizados; se editados localmente, só com --force). --css-only e --js-only limitam o que é tocado. trilha new roda o mesmo passo. Veja Interface com ui.
trilha agents#
Grava dois arquivos na raiz do projeto, e só quando é pedido: suporte a agentes de código é opt-in, então trilha new sozinho não deixa nenhum dos dois. trilha new --agents já os cria junto com o projeto.
| Arquivo | De quem é |
|---|---|
AGENTS.md | do framework: as convenções, os comandos e o que não fazer |
CLAUDE.md | seu: três linhas apontando para o AGENTS.md, mais o que este repositório pedir |
O AGENTS.md leva um carimbo com o hash do próprio corpo, a mesma regra do kit ui. Uma cópia intocada de uma versão anterior é atualizada em silêncio na próxima rodada; uma que você editou só é sobrescrita com --force, e sem ele o comando para e avisa. O CLAUDE.md nunca é sobrescrito.
--lang en|pt escolhe a língua dos dois arquivos e por padrão é a da CLI.
Rode de novo depois de atualizar a CLI: o AGENTS.md nomeia os comandos da versão que o gravou, então uma cópia de uma release anterior continua mandando o agente para comandos que foram substituídos. A sequência inteira para um projeto vindo de versão anterior está na receita de migração.
trilha openapi#
Lê app/, deduz o documento a partir dos handlers e escreve openapi.json. -o - escreve na saída padrão; --title, --version e --server preenchem o que o código não tem como saber (o padrão é o nome do módulo, 0.0.0 e nenhum servidor). --check compara com o arquivo no disco e sai com 1 quando divergem — a mesma linha que o gen --check é, pelo mesmo motivo:
- run: trilha openapi --checkO que é deduzido e as diretivas openapi: estão em APIs.
trilha check#
Seis portões num comando, na ordem que falha mais barato primeiro: gen, gofmt, vet, test, audit (sem a varredura de vulnerabilidades, que precisa de rede) e openapi (só se o projeto guarda o documento). Ele para na primeira falha — o que vem depois de uma compilação quebrada não diz nada sobre o projeto — e os passos que não rodaram dizem isso:
✓ gen
✗ gofmt (failed)
app/blog/page.go: not gofmt'd
→ run gofmt -w (or trilha check --fix)
- vet (not run)
- test (not run)
- audit (not run)
- openapi (not run)Todo problema vem com o arquivo, a linha e a frase que resolve. O --fix regrava o trilha_gen.go e a formatação antes de julgá-los, e aí o passo reporta fixed. O --json escreve o relatório que uma ferramenta lê, com os mesmos campos:
{
"ok": false,
"steps": [{ "tool": "gen", "status": "failed" }],
"problems": [
{
"tool": "gen",
"file": "app/page.go",
"line": 3,
"message": "page.go must export func Page(c *trilha.Ctx) (h.Node, error); found func Render",
"fix": "rename the function to Page, or delete page.go if this directory is not a page"
}
]
}Sai com 1 quando algo falhou, então no CI é a linha única:
- run: trilha checktrilha ctx#
O mapa do projeto numa leitura só: o módulo, se o trilha_gen.go está em dia, cada rota com seu arquivo, métodos, parâmetros, layouts e middlewares, cada operação de API com sua query, corpo e respostas, os tipos que essas operações trocam e o que o app/setup.go provê:
# example.com/loja
- trilha 0.37.0 · 8 routes (6 pages, 2 APIs)
- trilha_gen.go: up to date
- app/setup.go: Setup, Config
## Routes
- `GET /` — app/page.go · layouts: app/layout.go
...O padrão é Markdown compacto, para ler. --routes e --types imprimem uma seção sozinha, --all não elide nada (os middlewares por método, todas as respostas de erro, o tipo Problem) e --json escreve o mesmo modelo como documento, ordenado e sem relógio nem caminho absoluto, de modo que duas execuções na mesma árvore dão os mesmos bytes.
A seção de API e os tipos saem da mesma inferência que está por trás do trilha openapi, então o mapa e o documento não têm como divergir. Como o openapi.json, a saída é um documento de máquina e não é traduzida.
trilha gen --check#
Gera em memória, compara com o trilha_gen.go commitado e sai com 1 mostrando as linhas que divergem — uma linha no CI, e uma pasta nova em app/ sem trilha gen depois deixa de ser um 404 que ninguém explica:
- run: trilha gen --checkO trilha check faz essa mesma comparação no primeiro portão, e é por isso que um projeto que usa o check não precisa de uma linha gen --check à parte. O trilha audit faz a comparação como aviso, e ainda compara a versão da CLI com a da biblioteca no go.mod: uma CLI mais nova escreve código que a biblioteca pode ainda não ter, e o erro aparece dentro de código gerado — o pior lugar para procurar.
Arquivo gerado#
trilha_gen.go é determinístico (mesma árvore, mesmos bytes), tem o cabeçalho // Code generated by trilha. DO NOT EDIT., mais a diretiva //go:generate trilha gen (para go generate ./... funcionar sem ninguém precisar saber o nome da ferramenta), e deve ser commitado: go build ./... funciona sem a CLI instalada. Ele define newApp() *trilha.App e main(); se outro arquivo do pacote já tem func main(), o gerador omite o dele (veja App).
Um app dentro de um binário que já existe#
O arquivo gerado adota o pacote que a pasta declara, então um app Trilha pode ser um pacote comum, importável, dentro de um servidor net/http que você já roda:
// internal/crm/crm.go — package crm, escrito à mão
// internal/crm/app/… — as rotas
// internal/crm/trilha_gen.go — package crm, func NewApp() *trilha.App
mux.Handle("/", crm.NewApp().Handler())A precedência, do mais explícito ao menos: --package <nome>; o pacote declarado pelos .go escritos à mão da pasta; o pacote declarado por um trilha_gen.go que já esteja lá; main. O terceiro passo é o que faz a bandeira valer uma vez só — o arquivo gerado lembra a escolha, e o trilha gen --check do CI não precisa dela.
Fora do package main o construtor é exportado (NewApp, porque quem chama mora em outro pacote) e nenhum func main() é escrito. O trilha dev e o trilha build recusam um app assim e dizem quem o roda: não há binário aqui, o hospedeiro é que tem um.
Códigos de saída#
0 sucesso; 1 erro de geração, compilação ou execução; 2 uso incorreto.