Reference
The collage CLI
Every command of the collage CLI — new, dev, build, export, serve, version and help — with its flags and exactly what it runs.
The collage command scaffolds projects and drives the ones you have: running one
in development, compiling the binary you deploy, and exporting it as static files.
go install github.com/Elagoht/collage/cmd/collage@latest
It never links your application into itself — it cannot, because your application
is your code. dev, build and export run the go tool in the current
directory, exactly as you would by hand, and the rest of this page says precisely
what each one runs.
Usage
collage <command> [flags]
| Command | What it does |
|---|---|
new |
Scaffold a new collage project |
dev |
Run the current directory's project in development mode |
build |
Compile the current directory's project into the binary you deploy |
export |
Render the current directory's project to static files |
serve |
Serve a static export the way a static host would |
version |
Print the collage CLI version |
help |
Show help for a command, or list every command |
collage help <command> prints that command's own usage, and so does
collage <command> -h. Flags use Go's flag syntax: -out dist and -out=dist
are the same, and --out works too.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success, including help, collage -h (since v0.11.0) and collage <command> -h |
1 |
The command parsed correctly and failed to do its work |
2 |
A usage problem: no command, an unknown command, a bad flag, or an unexpected argument |
collage new
collage new <name> [--template demo|minimal] [--dir path] [--module path] [--force]
Scaffolds a new, runnable project named <name>.
| Flag | Default | Meaning |
|---|---|---|
--template name |
demo |
The project to scaffold: demo or minimal (since v0.14.2; it replaces -minimal) |
-dir path |
./<name> |
Directory to scaffold into |
-module path |
<name> |
The module path written into go.mod |
-force |
off | Scaffold into a non-empty directory anyway |
collage new myblog # into ./myblog, module "myblog"
collage new myblog --template minimal # one page, nothing to delete
collage new myblog -module github.com/me/myblog
collage new myblog -dir . -force # into the current, non-empty directory
Flags take one dash or two, and may come before or after the name. Exactly one name is required; none, or
more than one, is a usage error. A target directory that exists and is not empty
is refused unless you pass -force — and with -force, files the scaffold writes
replace files of the same name.
Every project gets go.mod, a main.go holding the configuration, the static
mount, the CLI contract described below and the
dispatch of plugin commands, a routes.go registering every
route, a layout that declares the site's title with rc.HoistTitle (so a page's
own title replaces it), a home page, static/, a .gitignore and a README.
The demo project, the default, adds a page of live demos — an action answering
JSON, a form posting to its own page, a fragment with its own URL, a JSON
document — split into pages/, fragments/, actions/, documents/ and
store/, a not-found page, tests for each, plugins-config.json, a favicon and a
.env.example.
--template minimal is the least a project can be: the layout around one
page, <h1>Hello from collage</h1>, and a stylesheet that sets the background and
text colour, dark mode included. Nothing else — no tests, and no not-found page:
collage answers an unknown address with its own plain 404 until you register one.
When it is done it prints the next steps — the cp line only for the demo
project, the one with a .env.example:
cd myblog
go mod tidy
cp .env.example .env.development
collage dev
collage dev
collage dev
Builds the project in the current directory, runs it with COLLAGE_DEV=1 set,
and rebuilds and restarts it whenever its Go code changes. It takes no flags and
no arguments. Press Ctrl-C to stop; the program receives the interrupt too and
shuts down the way it would in production.
The scaffolded main.go turns on development mode when COLLAGE_DEV=1 is set.
Development mode reads templates and static files from disk on every request, so
editing those needs no rebuild and none happens — the page in your browser reloads
itself instead. Content your program reads from disk itself, such as Markdown,
reloads the page too once its directory is named in
Config.DevWatch (since v0.10.0). See
Templates for what else development
mode changes.
What triggers a rebuild
| Watched | Not watched |
|---|---|
.go files anywhere in the project |
_test.go files |
go.mod and go.sum at the project root |
templates, static files and DevWatch directories (reloaded without a rebuild) |
| the environment file below | hidden directories (.git, .cache, …) |
bin, dist, node_modules, testdata, vendor |
The skipped directories are what keeps the loop from feeding itself: nothing the running program writes — its cache, an export — can trigger a rebuild.
How a rebuild goes:
- It polls every 300 ms, comparing modification times and sizes. No file-system notification library, and the same behaviour on every platform.
- A burst of writes is one rebuild. After a change it waits until the files have been quiet for one interval, so a formatter rewriting several files costs one build.
- The new build is made first.
go buildwrites a binary to a temporary directory outside the project; only once it compiles is the old process interrupted — it drains its requests as on Ctrl-C, and is killed only if it has not exited within 10 seconds — and the new one started. - A change that does not compile leaves the last good build serving, with the compiler's error on screen.
- A program that exits by itself — a panic at startup, a port already in use — is not restarted in a loop. The next change starts it again.
Environment files
collage dev adds the variables of .env.development in the current directory to
the program's environment, or of .env when there is no .env.development. One
file, never both: .env.development replaces .env rather than being merged over
it.
# .env.development
PORT=3000
HOST=localhost
export COLLAGE_CSRF_KEY="0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0"
- The format is
KEY=valuelines, blank lines and lines starting with#. Anexportprefix is allowed, and so is a pair of matching single or double quotes around a value, which are removed with nothing inside interpreted. - An unquoted value ends at a
#that follows whitespace, soPORT=3000 # devis3000. - Keys are letters, digits and underscores, not starting with a digit.
- A malformed line stops the program from starting:
collage devreports it with the file and the line (.env.development:3) and starts or restarts nothing until it is fixed — a program already running keeps running on the values it was started with. It keeps watching, so saving the corrected file carries on. A skipped line would be a setting you wrote and the program never saw. - A variable already set in the shell wins over the file, so
PORT=4000 collage devstill works. COLLAGE_DEV=1is always set, whatever the file says.- No file is not an error. When a file is read, its name is printed on stderr.
- The file is read again on every restart, and it is watched, so editing it restarts the program with the new values.
Only collage dev reads these files. collage build, collage export and the
built binary take their environment from wherever they run.
collage build
collage build [-o path] [-os name] [-arch name] [-i]
Compiles the current directory's project into the binary you deploy. It runs:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o bin/<name> .
| Flag | Default | Meaning |
|---|---|---|
-o path |
bin/<name> |
Where to write the binary |
-os name |
linux |
Target operating system (GOOS) |
-arch name |
amd64 |
Target architecture (GOARCH) |
-i |
off | Ask which extra files to write beside the binary |
<name> is the last element of the module path in go.mod; a directory with no
go.mod, or one that declares no module, is an error. For -os windows, .exe is
appended to the path when it is not already there. Positional arguments are a
usage error.
Why those settings:
- CGO off, because collage and the standard library need no C, and a static binary can go into an image with nothing else in it.
-trimpath, so the binary does not carry the paths of the machine that built it.-s -wdrops the debug tables, which is most of the size.- linux/amd64 by default rather than the machine you are on: a binary built on a Mac does not run in a Linux container, and "exec format error" on a server is the wrong place to find that out.
bin/, notdist/:dist/is wherecollage exportwrites, andexport -cleanempties it.
When it finishes it prints the binary's path, platform, size and build time.
Extra files with -i
With -i it asks two questions before building, reading answers from standard
input — y or yes means yes, anything else no:
- Write a Dockerfile? A two-stage image: a
golangbuild stage pinned to the major and minor version of the Go that built the CLI, and agcr.io/distroless/static-debian12stage holding only the binary, withHOST=0.0.0.0,PORT=8080, port 8080 exposed and a commented-outCOLLAGE_CSRF_KEY. - Write a systemd unit? A
<name>.servicerunning/usr/local/bin/<name>from/srv/<name>withHOST=127.0.0.1,PORT=8080,Restart=on-failureandTimeoutStopSec=30, to adjust before installing.
Both are written beside the binary — bin/Dockerfile, bin/<name>.service —
because they are generated, and the project root is for what a person wrote. The
report prints the one command that placement costs:
docker build -f bin/Dockerfile .
An existing file is never overwritten. If one is already there the question is not asked. These are files a project edits, and a build command that replaced one with a default would quietly undo somebody's work.
Without -i, only the binary is written. See Deployment.
collage export
collage export [-out dir] [-clean]
Renders the current directory's project to static files — HTML for every page that can be one, plus every mounted asset — for a static host. It runs:
go run . -collage-build -out <dir> # plus -clean when you passed it
| Flag | Default | Meaning |
|---|---|---|
-out dir |
dist |
Directory the project renders into |
-clean |
off | Remove the directory's existing contents before building |
The program's output — the build report — is streamed straight through, not reformatted. Positional arguments are a usage error.
What gets exported, what is skipped and why, and the safety checks on the output
directory are in Static export. For a site with forms or
per-request pages, collage build is the one you want.
collage serve
collage serve [-dir dir] [-host name] [-port n]
Serves a static export the way a static host would, so what you see is what you will get after deploying it. It serves files; it does not run your project.
| Flag | Default | Meaning |
|---|---|---|
-dir dir |
dist |
Directory to serve |
-host name |
localhost |
Interface to listen on |
-port n |
4000 |
Port to listen on |
The port is 4000 rather than 3000 so it can run beside collage dev — which is
exactly when you compare the two.
It behaves like a static host rather than a file server:
- A path with no extension is answered with
<path>/index.html, which is the shape an export writes. - Directories are never listed.
- A path that resolves to nothing is answered with the export's own
404.htmland a 404 status, or a plain 404 when there is none. - Only
GETandHEADare answered; anything else is a 405. - Every response is sent with
Cache-Control: no-store, so re-exporting and reloading shows the new output rather than the old.
A directory that does not exist, or holds no files, is an error that tells you to
run collage export first. A path that exists but is not a directory is an error
too, saying just that.
collage version
collage version
Prints collage version <version>. The version is read from the binary's build
information, so go install ...@v0.9.0 reports 0.9.0, and a binary built in a git
checkout reports a pseudo-version — 0.11.1-0.<timestamp>-<commit> for a commit
after the v0.11.0 tag.
Only a binary with no version information at all — built outside a repository, or
with -buildvcs=false — reports devel.
collage help
collage help # every command
collage help export # one command's usage
With no argument it lists every command and exits 0. With a command name it
prints that command's usage; an unknown name prints
collage: unknown command: "nope" and exits 2.
The contract with main.go
dev and export depend on two things your main.go does, and the scaffolded one
does both — along with a third, running plugin commands, that no collage command
needs but your plugins do:
| Command | Runs | Your main.go must |
|---|---|---|
collage dev |
go build, then the binary, with COLLAGE_DEV=1 |
turn on development mode when COLLAGE_DEV is 1 |
collage export |
go run . -collage-build -out <dir> [-clean] |
parse -collage-build, -out and -clean, and on -collage-build render to <dir> instead of serving |
collage build needs nothing from main.go: compiling is something go build
does without being told anything.
The scaffolded version, trimmed to the part that matters:
func main() {
buildFlag := flag.Bool("collage-build", false, "render the app to static files instead of serving it")
outFlag := flag.String("out", "dist", "output directory for -collage-build")
cleanFlag := flag.Bool("clean", false, "remove -out's existing contents before building")
portFlag := flag.Int("port", envInt("PORT", 3000), "port to listen on (env PORT)")
flag.Parse()
devMode := os.Getenv("COLLAGE_DEV") == "1"
app, err := newApp(devMode, *portFlag)
if err != nil {
log.Fatal(err)
}
// A word after the flags is a plugin's command: go run . <command>
if args := flag.Args(); len(args) > 0 {
code, err := collage.DispatchCommands(context.Background(), app, args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
os.Exit(code)
}
if *buildFlag {
if err := staticBuild(app, *outFlag, *cleanFlag); err != nil {
log.Fatalf("static build: %v", err)
}
return
}
if err := app.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
If you rewrite main.go, keep all of it working, or collage dev,
collage export and your plugins' commands stop doing anything useful in your
project.
Plugin commands
A plugin can contribute a command through Host.RegisterCommand. The collage
CLI does not run it. The CLI never loads your application, so a command that
only exists once your plugins have started is out of its reach, and
collage <plugin-command> is an unknown command.
Your own program dispatches them, with collage.DispatchCommands, and the
scaffolded main.go does (since v0.10.0 — a project scaffolded earlier can copy
the block above). After the flags are parsed and the
application is built, a word left over is a command:
if args := flag.Args(); len(args) > 0 {
code, err := collage.DispatchCommands(context.Background(), app, args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
os.Exit(code)
}
DispatchCommands starts the application — running every plugin's Init, which
is what registers their commands — and runs the command named by the first
argument. So a plugin's command runs as your program's subcommand, after any
flags:
go run . pages
./bin/myblog -port 4000 pages
The exit code follows the CLI's: 0 for success; 1 for a startup failure, a
command that ran and failed, or a command with no Run; 2 for a nil app, no
arguments, or a word no plugin registered (ErrUnknownCommand). An
unclaimed word is a usage error rather than a server started by accident. A
program that would rather serve when no command matches can check
errors.Is(err, collage.ErrUnknownCommand) and carry on instead of exiting.
app.Commands() lists what the plugins registered, if you want to print your own
usage. Dispatching starts the application, which closes registration, and a later
ListenAndServe reuses that start. Writing such a command is covered in
Writing a plugin.