CLI Reference

new module

Generate a module (entry point, first surface, contract) into the current project.
go tool gpsystem new module <name> [--surface <name>]

Run inside a project (the CLI walks up to gpsystem.yaml). The module name must be lowercase letters/digits: it becomes a Go package name, directory name and URL segment. The same rule applies to the first surface's name (--surface, default api; the old --domain flag still works as a deprecated alias).

What it generates

  • cmd/<name>/main.go: the ~20-line entry point calling fiberx.Run (or chix.Run for --engine chi projects)
  • internal/modules/<name>/register.go: Dependencies struct + one exported Register<Surface>(router, deps) function per surface (e.g. RegisterApi, RegisterWeb)
  • One first surface (default api) under surfaces/<surface>/: a handler implementing the generated strict interface (its sample List calls a minimal service/ seam), a policy/ registry stub, plus a kept-empty http/mapper/ directory (.gitkeep) for your own code
  • The module-level shared skeletons: core/ (home of the rules, entities and sentinel errors shared by the surfaces, with an ErrNotFound example) and repository/ (the module's single persistence layer); the first surface stamps these, later add surface calls skip them
  • spec/typespec/modules/<name>/: <surface>.tsp with a sample operation + models/<surface>.tsp
  • api/oapi-codegen/<name>-<surface>.yaml codegen config wired to the project's template set
  • The module's app service in compose.yml: built from the project's Dockerfile (target: development), wired with depends_on on postgres/redis/migrate, dual Traefik routers with path-based routing (see new project)

Anchor insertions

  • main.tsp ← module imports (gpsystem:modules)
  • cmd/<name>/main.go ← the <name>.Register<Surface>(api, deps) call (gpsystem:registrations)
  • register.go ← a per-surface Register<Surface> function + imports (rendered, then goimports-formatted)
  • compose.yml ← the module service at the gpsystem:services anchor

Compose backfill on an older project

In a project freshly generated by new project, compose.yml and Dockerfile already exist, so new module only inserts the module service. On an older project (generated before the compose stack was introduced), new module backfills what it can:

  • Creates Dockerfile and .dockerignore if they're missing.
  • Inserts the mailpit/rustfs/rustfs-init backing services at the gpsystem:services anchor (idempotent, re-running does not duplicate them).
  • If compose.yml has a postgres pg_isready healthcheck, the module service is inserted at the anchor too; if not (a service_healthy dependency on a healthcheck-less postgres would break compose), the module service's YAML is only printed to the terminal for manual insertion.
  • The top-level networks:/volumes: section (proxynet, rustfsdata, gomodcache, gocache) has no anchor either, so it's also only printed; insert it by hand.
  • If .env.example still has DB_HOST=localhost, a warning line flags that the app now runs inside compose and DB_HOST should become postgres.

If the backfill's printed-manual-insertion fallbacks pile up (no pg_isready healthcheck, missing networks:/volumes:), it's often simpler to run add compose --force and get a clean compose.yml rebuilt from the manifest instead of patching the old one by hand.

After generation

mise run generate    # tsp → OpenAPI → strict server code → build
go run ./cmd/<name>

The skeleton has no persistence yet: build the shared rules in the module's core/ package and the persistence in the module-level repository/, and call them from the handler through the surface's service/ seam.

The skeleton compiles and serves immediately (including correct problem+json error responses), so you can wire CI and deployment before writing business logic.
Copyright © 2026