How to help
Report something, make it faster, or pick up an issue. The rules are short and binding; what to run, and when, is What to do. In this repo charter is this checkout’s: go tool charter <command>.
Set up
git clone https://github.com/joeblew999/charter && cd charter
mise install # the tools: go, node, gh, fnox, TinyGo, workerd…
gh auth login # the repo job reads GitHub
npm ci # the packages; builds charter-lib (ts/)
go tool charter check # the packages, repo and projects jobs (Docker for the conformance projects)
go tool charter ci # every job GitHub runs, the clients in every language among them (Docker)
Docker: charter starts it where it is installed but not running and may be started (a Claude cloud session), and otherwise says to start it. Where the internet is a proxy on the machine itself with its own CA (a Claude cloud session), Fern’s generators run their containers through internal/fern/cloud/docker, which gives them the host’s network, the proxy and its CA (internal/fern/docker.go); on a Mac or a GitHub runner the containers are as Docker makes them.
What is where
| Folder | What it is |
|---|---|
The top (go.mod) | The Go library, one package per folder (auth/, serve/…), with its Worker glue in worker/; and the command, charter, in cmd/charter/, its steps in internal/cli/: a project’s (internal/cli/projectsteps) and a repo’s (internal/cli/repo, with the files charter writes into every repo in internal/cli/repo/files/) |
ts/ | charter-lib, what a TypeScript Worker imports |
examples/start-ts/, examples/notes-ts/ | The TypeScript examples, which charter new copies: one route, and the notes API (sign-in, streams, rate limits) |
examples/start-go/, examples/notes-go/ | The same two APIs in Go, held to the TypeScript ones by twins |
examples/start-htmx/, examples/start-datastar/ | Go with server-rendered pages (Server-rendered pages) |
examples/conformance-ts/, examples/conformance-go/ | Every Fern feature, its SDKs tested against the API, inside a Worker too; the TypeScript one is the reference |
The top’s charter.toml says how the repo is kept: its GitHub settings and its jobs. The root package.json is the npm workspace of ts/ and the projects in examples/; the root go.work is the Go workspace of the library and the Go projects; mise installs the tools (mise.toml).
What is generated
Never edit these: change the source, then run the command.
| Path | Written by |
|---|---|
fern/openapi.json, fern/asyncapi.json, fern/generators.yml | charter spec, from the contract; Fern’s settings from the spec and charter.toml ([sdks]) |
api/queries.go, api/tables.ts | charter spec, from migrations/*.sql (and in Go api/queries.sql), as db in charter.toml says |
pages/*.x.go (committed) | gsx, from the components; charter check writes them and fails while they were stale |
sdk/go/ (committed) | charter sdk -publish |
sdk/out/, build/, ts/dist/ | charter sdk; the Worker’s build (charter build: TinyGo’s, or the TypeScript bundle) and the program that writes a project’s specs (build/_spec, build/_spec.mjs: charter spec); npm ci |
examples/conformance-ts/client/ | The SDK the conformance Worker runs, copied in by check, dev and deploy |
dist/ | charter release: charter-lib packed in dist/lib/, the binaries in dist/charter/ (goreleaser) |
docs/charter/, AGENTS.md, CLAUDE.md, .github/ (forms, labels, workflows) | charter init, from internal/cli/repo/files/, charter’s commands and the top’s charter.toml |
A project’s .mise/tasks/, .claude/skills/charter/, .config/mise/conf.d/charter.toml | charter tasks and charter skill |
package-lock.json | npm install |
Report a bug
Use the GitHub forms; for a bug in Fern, TinyGo, workers-go, Huma or oRPC, check Upstream issues first. An agent prints the same form:
go tool charter issues bug # or feature, upstream, plan: the body, and the gh command that files it
Make it faster
The target: a Go Worker costs no more CPU per request than its TypeScript twin. What was tried and measured is in Benchmarks. Issues: perf.
Pick up an issue
The plan is the open issues. Before a pull request: go tool charter check passes, and docs/ says what changed.
A change to charter’s repo files
What charter init writes into every repo is in internal/cli/repo/files/, the same in every repo; the tests beside it (internal/cli/repo/*_test.go) run init, docs and the rest in throwaway repos. After changing one, go tool charter init writes it into this repo too, and charter check fails until it does.
The CI
The top’s charter.toml has this repo’s own jobs, [ci.jobs.<name>]; charter init writes a workflow for each (.github/workflows/self-<job>.yml), and charter-repo.yml is charter’s repo job, the same in every repo. Every workflow calls the job runner (charter-ci.yml): mise installs the tools, npm the packages, then charter (this checkout’s, built) runs the job as go tool charter ci <job> runs it here. One job in one project: go tool charter ci clients examples/conformance-go.
Every command says what it made and how long it took: each Worker its size, gzipped, against its limit; each program of a second or more, each server’s start, each job. check ends with both.
| Job | When | What |
|---|---|---|
repo | Push, pull request | charter’s job: charter’s files and the workflows (init -check), the docs, the repo on GitHub, the upstream issues |
packages | Push, pull request, tag; Linux and macOS | charter-lib with its tests, the Go library and charter with theirs (the projects kept alike among them: examples/examples_test.go), the release’s config, the workflows linted |
projects | Push, pull request, tag; one runner per project | Every project’s check |
twins | Push, pull request, tag | Each API that is here in TypeScript and in Go, both under workerd, the same requests to both: no answer may differ, nor their saved specs and migrations (The standard API) |
new | Push, pull request, tag | A project made by charter new from each example, in both languages, with charter from mise and charter-lib packed, installed, and its check passing (examples/new-projects.sh) |
go-app | Push, pull request, tag | A Go app made by charter init gets charter’s TinyGo through mise, every way it builds (examples/new-app.sh) |
clients | Push, pull request, tag; one runner per conformance project | Every client its fern/generators.yml declares, generated and proven in its language |
deploy | By hand: go tool charter ci deploy <project> -remote | A project’s deploy, then its live tests |
Where the tests are
Each test checks one thing, at one level, and the same level is in the same place in Go and in TypeScript:
| # | Level | Who needs it | Where | When it runs |
|---|---|---|---|---|
| 1 | The standard: a running API serves its specs, lists its MCP tools, leads a client to sign in, refuses with problem details | Every API | Built into charter (internal/standard): it reads the API’s own spec, so nothing is written or copied | Every project’s check and live-test, first |
| 2 | The API’s own: it does what it is for | Every API | The project’s test/ (test/live.ts, test/sdk.ts…); a Go twin runs its TypeScript reference’s, as its charter.toml’s [test] says | The project’s check and live-test |
| 3 | Unit tests: a handler on its own | Where wanted | Beside the code: api/*_test.go, api/*.test.ts | The project’s check |
| 4 | Twins: the Go project answers as its TypeScript reference | An API in both languages | Built into charter (charter twins, internal/twins) | The twins job |
| 5 | The libraries and charter | charter’s developers | Beside the code: */*_test.go at the top, ts/src/*.test.ts | The packages job |
| 6 | This repo only: the projects are alike; a project made from the release works | charter’s developers | examples/examples_test.go, examples/new-projects.sh | The packages and new jobs |
Cut a release
go tool charter release X.Y.Z -dry-run # every check, charter-lib into dist/lib, the binaries into dist/charter
go tool charter release X.Y.Z # the version, the tag, the GitHub Release with charter-lib and the binaries
It runs charter check before it tags, and never waits for GitHub. It tags vX.Y.Z (the Go library’s, the root module, which go get pins; above v0.19.1, the last tag of the Go tool the root held before charter-ts), and the Release vX.Y.Z, marked latest, carries the charter binary for every machine and charter-lib-X.Y.Z.tgz (goreleaser, .goreleaser.yaml): a repo takes charter with mise use github:joeblew999/charter, and charter-lib by its URL in package.json. It puts that version into every install command the docs show.