Requirements
- Go (the
go.modversion) for the CLI and the panel server. - Node 22 and npm for the screens (build only: the final binary does not depend on Node).
- An authenticated
gh, for the GitHub integrations anddeck update.
From source to binary
git clone git@github.com:thecodecodex/deck.git && cd decknpm installnpm run build # static export of the screens, copied to internal/hubserver/web/make build # compiles ./deck embedding the latest screens build./deck uiOrder matters: go build embeds whatever is in internal/hubserver/web/ at that moment. Without npm run build, the
binary still compiles, but deck ui warns that it has no panel and only the API responds.
Everyday commands
| Command | Purpose |
|---|---|
npm run dev | Screens with hot reload: next dev on 4005 behind deck ui --dev on 4004 (open 4004) |
go run ./cmd/deck <command> | Run the CLI without compiling |
make test | go vet + go test |
npx tsc --noEmit | Typecheck the screens |
npm run build:demo | Build the public demo (fixtures instead of the API) |
Repository layout
| Folder | Contents |
|---|---|
cmd/deck | CLI entry point |
cmd/assinar-release | Signs checksums.txt in CI and generates the key pair |
internal/commands | Command registry (registry.go) and the deck ui command |
internal/hubserver | Panel server: middleware, actions, /api/* routes, embedded pages |
internal/config | deck.yaml |
internal/selfupdate | deck update, channels, rollback and signature |
internal/<module> | gitworkflow, azure, database, mocks, setup, testscenarios, devlauncher… |
src/app, src/components | Screens (static export) |
src/demo | Public demo fixtures |
public/_headers | Demo headers, with the same CSP as deck ui |
Project rules
- Nothing project-specific in the code. Repository, organization, URL, e-mail, tenant or board column come from
deck.yaml. New field:internal/config,deck.example.yamland, if a screen needs it, the public configuration (internal/hubserver/config.goandsrc/lib/configTipos.ts). - Language: code, comments and messages in Brazilian Portuguese; README in English.
- Interface: only components and tokens from
@thecodecodex/shell; a new generic component is born there. - Screens only talk to same-origin
/api/*, and the server never receives flags from the client.
Adding an action to the panel
A new action (for example, a button that runs a CLI command) always follows the same path:
- The command exists in the CLI (
internal/commands/registry.go), with--json/--yeswhen it makes sense. - The action is declared in the route handler, with typed parameters and the function that builds the argv:
"comment-card": { Timeout: 2 * time.Minute, Params: map[string]Param{ "id": obrigatorio(Numerico("ID de card")), "text": obrigatorio(Texto("Texto do comentário", 4000)), }, Monta: func(p map[string]string) []string { return []string{"boards-comment-card", "--id", p["id"], "--text", p["text"]} },},- Two tests in
internal/hubserver: one that checks the exact argv built and one that checks that invalid inputs (a flag instead of a value, a value outside the list, a line break) are rejected without running anything. - The screen calls the route with
POSTandContent-Type: application/json. - If the action reads or writes a file, the path goes through a containment check.
Tests
| Suite | What it covers |
|---|---|
internal/hubserver | Middleware (host, origin, cookie, method), argv of each action, malicious inputs rejected, file containment, E2E lock and abort, CSP identical to the demo’s |
internal/hubserver (Unix) | Real execution: no shell, empty stdin, timeout killing the process tree |
internal/commons | Input validators |
internal/selfupdate | Channels, checksum, valid, tampered, wrong-key and missing signatures |
| Other packages | Rules of each module (git, boards, database, mocks, setup…) |
CI runs go vet, go test -race, a cross-build for Windows, typecheck, the screens build and the demo build.
Releases
Every push to main publishes a v<version>-dev release; a manual dispatch with the “stable” channel promotes the
commit to v<version>. Versioning follows CalSemVer.
Signing key
- The public key lives in
internal/selfupdate/assinatura.goand is embedded in every binary. - The private key lives only in the repository secret
DECK_RELEASE_CHAVE_PRIVADA. Without it, the signing step fails and the release is not published (an unsigned release would be rejected by every binary). - Generating a new pair (for example, if the key leaks):
go run ./cmd/assinar-release -gerar <file>writes the private key with0600permissions and prints the public one. Rotating the key requires publishing a version with the new public key; anyone on an older version needs to update manually once.
Public demo
npm run build:demo generates the same export with NEXT_PUBLIC_DECK_DEMO=1: calls to /api/* are answered by
fixtures in the browser (src/demo/fixtures.ts). It is deployed to Cloudflare (wrangler.jsonc) at
deck.thecodecodex.com, with the headers from public/_headers.