datapages.yaml
datapages.yaml names the entry point that datapages gen writes for a new project. It also configures the datapages watch development server and its Toxiproxy proxy.
The scaffold file
datapages init and datapages watch write this file into a module that has none:
cmd: cmd/server
watch:
exclude:
- ".git/**" # git internals
- ".*" # hidden files/directories
- "*~" # editor backup files
# The app through Toxiproxy and its settings page, on the ports the OS
# picks, which watch prints. Set host and ui-host for fixed ports.
# The presets are the throttling presets of Chrome DevTools.
toxiproxy:
presets:
- name: "Fast 4G"
toxics:
latency:
downstream:
latency: 165ms
jitter: 0s
client-bandwidth:
downstream:
rate: 1012500
upstream:
rate: 168750
- name: "Slow 4G"
toxics:
latency:
downstream:
latency: 563ms
jitter: 0s
client-bandwidth:
downstream:
rate: 180000
upstream:
rate: 84375
- name: "3G"
toxics:
latency:
downstream:
latency: 2s
jitter: 0s
client-bandwidth:
downstream:
rate: 50000
upstream:
rate: 50000
cmd names the directory where datapages gen writes the entry point while the module has no datapages.NewServer call. The exclude patterns keep changes to these files from rebuilding the app. .* and *~ match only names in the module root, because * does not match /. toxiproxy holds the 3 presets that the settings page of the proxy lists between its built-in presets. With the section, a proxy that cannot start stops watch.
With --web-components typescript, init adds a watcher that bundles the TypeScript sources, and keeps web/node_modules/ away from the file watcher. It appends these keys to the watch section, after toxiproxy:
watch:
# exclude and toxiproxy as above
watcher-ignore:
# The tree is large and would use up watch descriptors.
- "web/node_modules/**"
custom-watchers:
- name: "Bundle the Web Components (esbuild)"
include:
- "web/src/**/*.ts"
cmd: "cd web && node build.mjs"
fail-on-error: true
requires: reload
Location and loading
- The file is in the module root, next to
go.mod, and is nameddatapages.yamlordatapages.yml. Both at once are an error. datapages gen,datapages lint,datapages watchanddatapages initread it.lintuses only the endpoint keys ofpresets, and fails on an invalid file like the others.datapages versiondoes not read it.- Without the file, every key has its default.
watchandinitthen write the scaffold file, andgendoes not. A file thatwatchwrites applies from its next start. watchreads the file once, when it starts. A change to the file neither regenerates the code nor restarts the app. Thewatchsettings apply from the next start. The Reload config button of the settings page readswatch.toxiproxy.presetsagain, and its Save to config button writes them into the file.
The parser accepts a strict subset of YAML:
- A key this page does not list is an error, such as
field app not found in type config.Config. - A boolean is
trueorfalse.yes,onandTrueare errors. - A duration is a Go duration string, such as
50ms,2sor1m30s. A bare number is an error. - A list item without a value is an error.
nullis accepted forwatch,tls,compiler,toxiproxy, the lists, the mapsenvandendpoints, the toxic types,downstream,upstreamandtoxicity. It is an error for every other key,logandtoxicsincluded. An empty value, ascmd:ordebounce:, gives the default.- An empty file is an error, and so is a file with only comments.
Validation lists the checks that run when watch starts.
Keys
| Key | Type | Default |
|---|---|---|
cmd |
string | cmd/server |
watch |
mapping | none |
watch.app-host |
URL | http://localhost:8080 |
watch.proxy-timeout |
duration | 2s, no effect |
watch.flags |
string | "" |
watch.dir-work |
path | the module root |
watch.debounce |
duration | 50ms |
watch.exclude |
list of globs | none |
watch.watcher-ignore |
list of globs | none |
watch.format |
boolean | false |
watch.lint |
boolean | false |
watch.log |
mapping | the defaults of its keys |
watch.log.level |
erronly, verbose or debug |
erronly |
watch.log.clear-on |
restart or file-change |
"", no effect |
watch.log.print-js-debug-logs |
boolean | false |
watch.tls |
mapping | none, plain HTTP |
watch.tls.cert |
path | none |
watch.tls.key |
path | none |
watch.compiler |
mapping | none |
watch.compiler.gcflags |
string | "" |
watch.compiler.ldflags |
string | "" |
watch.compiler.asmflags |
string | "" |
watch.compiler.tags |
list of strings | none |
watch.compiler.race |
boolean | false |
watch.compiler.trimpath |
boolean | false |
watch.compiler.msan |
boolean | false |
watch.compiler.p |
integer | 0 |
watch.compiler.env |
map of strings | none |
watch.custom-watchers |
list of mappings | none |
watch.custom-watchers[].name |
string | required |
watch.custom-watchers[].include |
list of globs | none |
watch.custom-watchers[].exclude |
list of globs | none |
watch.custom-watchers[].cmd |
shell command | "" |
watch.custom-watchers[].fail-on-error |
boolean | false |
watch.custom-watchers[].debounce |
duration | 50ms |
watch.custom-watchers[].requires |
reload, restart or rebuild |
"" |
watch.toxiproxy |
mapping | none, the proxy runs with the defaults of its keys |
watch.toxiproxy.disabled |
boolean | false |
watch.toxiproxy.host |
host:port |
the host of --host, a port the OS picks |
watch.toxiproxy.ui-host |
host:port |
the host of host, a port the OS picks |
watch.toxiproxy.presets |
list of mappings | none |
watch.toxiproxy.presets[].name |
string | required |
watch.toxiproxy.presets[].toxics |
mapping of toxics | none |
watch.toxiproxy.presets[].endpoints |
map of route patterns to toxics | none |
cmd
Type string. Default cmd/server.
The directory of the main package, relative to the module root. An absolute path, or a path that leaves the module through .., is an error, as invalid cmd "../server": must be a relative path inside the module.
cmd matters while no main package of the module calls datapages.NewServer:
datapages genwrites<cmd>/main.gowith a call for the app packageapp/in a module without a call. It writes nothing when the directory holds Go files of packagemain, and fails when they belong to another package.datapages watchbuilds<cmd>when no main package callsdatapages.NewServerfor the app package it runs, as when the only call is in a test.datapages initnames<cmd>/as the server command inAGENTS.mdof a module with 1 app package and no main package with a call.
The first datapages gen writes main.go with the call. From then on gen ignores cmd, and watch builds the main package that holds the call. To move the entry point later, move its directory.
Set cmd before the first datapages gen of a new module to generate the entry point into another directory:
cmd: cmd/web
watch
Type mapping. Default none, which gives every key below its default.
The settings of datapages watch. datapages watch describes startup and file changes.
Dev server and app
app-host
Type string, an http or https URL with a host. Default http://localhost:8080.
The address of the app. The dev server forwards every request to it. After it starts the app, watch sends OPTIONS requests to this URL every 100 ms until any response arrives, for about 10 seconds at most, then reloads the open tabs. Make the app listen here. The cmd/server/main.go of the scaffold listens on HOST and PORT from the environment or .env, localhost:8080 by default.
A value without a scheme, such as localhost:8080, stops watch at startup with watch.app-host "localhost:8080" must be an http or https URL with a host, such as http://localhost:8080.
For an app that serves HTTPS, use an https URL. The startup requests accept any certificate. The forwarded requests check the certificate of the app against the root certificates of the system.
The address of the dev server itself is the --host flag of datapages watch, localhost:7331 by default.
proxy-timeout
Type duration. Default 2s.
Accepted for compatibility but has no effect. A forwarded request waits for the app without a time limit. If it fails before a response arrives, as while the app restarts, watch tries it up to 20 times with pauses that grow from 100 ms.
flags
Type string. Default "".
The arguments of the app process. watch splits the string at runs of white space. It interprets no quotes, which turns -name "a b" into the 3 arguments -name, "a and b". The main.go of the scaffold reads no flags. It takes its settings from environment variables.
watch:
flags: -verbose -addr localhost:8080
dir-work
Type string. Default the module root.
The working directory, relative to the module root. watch joins the value to the module root, a leading / included, and .. can leave the module. The directory must exist. watch uses it as:
- the working directory of the app, which resolves relative paths such as the
.envthat the scaffold reads, and the directory of static files in dev mode, as./app/static - the working directory of the custom watcher commands,
datapages genincluded, oftempl fmtforformatand ofgolangci-lintforlint - the base of the
excludepatterns and of theincludeandexcludepatterns of custom watchers
go build runs in the directory that watch was started in, and templ generate --watch in the module root, whatever dir-work says. watcher-ignore patterns are relative to the module root.
Warning
Leave dir-work at the module root. The patterns of the built-in datapages gen watcher are relative to the module root and match no file relative to another directory, which stops a changed .go file of an app package from regenerating the code. In dev mode the app also serves no static files, because it reads them from ./<app package>/<assets directory> relative to its working directory.
Rebuilds
debounce
Type duration. Default 50ms, also for 0.
The time watch waits after the last change before rebuilding the app. Each change restarts the wait. A burst of changes, such as a git checkout, therefore causes 1 build. This value applies to rebuilds requested by file changes and templ. Custom watchers have their own debounce. A negative value makes watch panic at startup.
exclude
Type list of glob patterns. Default none. The scaffold sets 3.
A changed file whose path matches a pattern does not rebuild the app. Custom watchers that include the file still run. watch adds datapages.yaml and datapages.yml to the patterns, which keeps the Save to config button of the settings page from restarting the app. watch matches the path relative to dir-work, with / as the separator on every system.
The patterns use the syntax of doublestar. * matches within 1 path element, ** matches any number of elements, and ?, [...] and {a,b} work as in shell globs:
| Pattern | Matches | Does not match |
|---|---|---|
.* |
.env, .git |
app/.hidden, .vscode/settings.json |
**/.* |
.env, app/.hidden |
.vscode/settings.json |
**/.*/** |
.env, .vscode/settings.json |
app/app.go |
*~ |
notes.txt~ |
app/app.go~ |
**/*~ |
notes.txt~, app/app.go~ |
|
.git/** |
.git, .git/index |
.gitignore |
watcher-ignore
Type list of glob patterns. Default none.
Paths that the file watcher leaves out. Unlike exclude, a matching file produces no event at all, for custom watchers too, and a matching directory gets no watch. watch matches the path relative to the module root, with the syntax of exclude.
Use it for large trees that would use up the file watches of the system, such as node_modules. Write the pattern to match everything below the directory, as web/node_modules/** does. web/node_modules alone leaves the directories below it watched.
Formatting and linting
format
Type boolean. Default false.
true runs templ fmt -fail <file> in dir-work on each .templ file that is written or created, which rewrites the file in the format of templ. After a file it changed, templ exits with status 1, and watch logs the error templ formatting error. The rebuild continues.
lint
Type boolean. Default false.
true runs golangci-lint run <module root>/... in dir-work with every build, in parallel with go build. When golangci-lint exits with status 1, the error page shows its output and watch does not start the new build. Another exit status is only logged. watch stops at startup when golangci-lint is not on the PATH.
watch.log
Type mapping. log: null is an error.
level
Type string, erronly, verbose or debug. Default erronly, also for "".
What watch logs to stderr, in the text format of Go's log/slog:
| Value | Logs |
|---|---|
erronly |
Errors |
verbose |
Errors, warnings and progress, such as templier started with the dev server URL, the build times and the restarts of the app |
debug |
Also every command watch runs, every file event and the output of templ |
Warnings, such as unsupported templ version, show only at verbose and debug. The output of the app shows at every level.
clear-on
Type string, restart or file-change. Default "".
No effect. templier clears the terminal through a function that its caller passes, and watch passes none. The value is still checked. Another one fails the loading of the file with invalid clear-on "always", use: restart, file-change.
print-js-debug-logs
Type boolean. Default false.
true makes the script that the dev server adds to every page log the state of its WebSocket connection to the browser console with console.debug. The messages start with templier:.
watch.tls
Type mapping. Default none, which serves the dev server over plain HTTP.
With the section, the dev server on --host serves HTTPS with this certificate, and the address to open becomes https://localhost:7331. Both keys are required. tls: {} stops watch at startup with engine: TLS.Cert is required when TLS is set.
cert
Type string. Default none.
The path of the PEM file with the certificate. A relative path is relative to the directory that watch was started in, which is the module root.
key
Type string. Default none.
The path of the PEM file with the private key of the certificate, relative like cert.
A file that cannot be read stops watch after startup with the error of the TLS setup of Go. The section configures the dev server only. The main.go of the scaffold serves HTTPS when PATH_TLS_CERT and PATH_TLS_KEY name the files. Set app-host to an https URL then.
watch:
tls:
cert: .dev/localhost.pem
key: .dev/localhost-key.pem
watch.compiler
Type mapping. Default none, which builds without extra flags.
The flags of the go build that watch runs, as go build <flags> -o <temporary file> ./<main package>/. watch adds the flags in the order of this table:
| Key | Type | Default | Adds |
|---|---|---|---|
gcflags |
string | "" |
-gcflags <value> |
ldflags |
string | "" |
-ldflags <value> |
asmflags |
string | "" |
-asmflags <value> |
tags |
list of strings | none | -tags <tag>,<tag> |
race |
boolean | false |
-race |
trimpath |
boolean | false |
-trimpath |
msan |
boolean | false |
-msan |
p |
integer from 0 to 4294967295 | 0 |
-p <n>, nothing for 0 |
env |
map of strings | none | <NAME>=<value> in the environment of go build |
An empty string, an empty list and false add nothing. Each string goes to go build as 1 argument, without shell quoting, which passes all=-N -l whole to -gcflags. env applies to go build only, not to the app, and overrides a variable of the same name in the environment of watch. The flags apply only to the builds of watch.
watch:
compiler:
gcflags: all=-N -l
tags:
- dev
env:
CGO_ENABLED: "1"
watch.custom-watchers
Type list of mappings. Default none.
Each entry runs a command when a file it includes changes, then takes an action, such as reloading the tabs. watch runs the command of every watcher once at startup, in list order, before the first build. A built-in watcher comes before the configured ones:
| Key | Value of the built-in watcher |
|---|---|
name |
datapages gen |
include |
<app>/**/*.go for every app package, <dir>/*.go for every package with a datapages.NewServer call |
exclude |
<app>/datapagesgen/** for every app package |
cmd |
The running datapages binary with the argument gen |
fail-on-error |
true |
requires |
rebuild |
When a change triggers several watchers, each runs its command. watch then takes the strongest action they require, in the order rebuild, restart, reload, none. A change that triggers a watcher gets that action instead of the default rebuild. With rebuild, a file that exclude matches, or whose name ends in _templ.go, still does not rebuild.
name
Type string. Required.
The name of the watcher in the log and above its output on the error page. A watcher without a name stops watch at startup with engine: CustomWatchers[1] has no name for the first watcher of the list, because the watcher that runs datapages gen comes first.
include
Type list of glob patterns. Default none.
The files that trigger the watcher, in the syntax of exclude and relative to dir-work. A watcher without include runs only at startup.
exclude
Type list of glob patterns. Default none.
Files that include matches and that do not trigger the watcher.
cmd
Type string. Default "".
The shell command, run with sh -c, or cmd /C on Windows, in dir-work. Its environment is that of watch plus FORCE_COLOR=1 and CLICOLOR_FORCE=1. A watcher may leave out cmd when it sets requires. A watcher with neither stops watch at startup. When the first word of the command is no program on the PATH, watch logs a warning, which shows at the verbose and debug levels, and runs the command anyway, because it may be a shell builtin.
fail-on-error
Type boolean. Default false.
With true, a command that exits with status 1 puts its output on the error page, which replaces the app until a later run of the command succeeds. The action of requires does not run. With false, watch logs status 1 as an error and skips the action. With either value, watch logs another non-zero status, or a command that cannot start, and takes the action anyway.
debounce
Type duration. Default 50ms, also for 0.
The time the watcher waits after the last change of a file it includes before it runs its command. A negative value makes watch panic at startup.
requires
Type string, reload, restart or rebuild. Default "".
The action after the command succeeds:
| Value | Action |
|---|---|
"" |
None. The command runs and nothing else happens |
reload |
Reload the open tabs |
restart |
Stop the app, start the same binary again, then reload the tabs |
rebuild |
Build the app again, with lint when it is on, restart it and reload the tabs |
watch.toxiproxy
Type mapping. Default none, which runs the proxy with the defaults of the keys below.
The Toxiproxy proxy in front of the dev server and its settings page. The scaffold sets 3 presets.
With the section, a proxy that cannot start stops watch at startup, as on a port in use. Without it, watch prints a warning that starts with warning: toxiproxy is off: and runs without the proxy.
disabled
Type boolean. Default false.
true turns the proxy and the settings page off. datapages lint then skips the endpoint keys of presets.
host
Type string, host:port. Default the host of --host and a port the OS picks.
The address of the proxy, which watch prints at startup. The port must differ from those of --host and app-host. A change applies from the next start.
ui-host
Type string, host:port. Default the host of host and a port the OS picks.
The address of the settings page, which watch prints with the address of the proxy. The port must differ from those of host, --host and app-host. A change applies from the next start.
presets
Type list of mappings. Default none.
The named sets of toxics that the settings page applies. The page lists them between its built-in presets No throttling and Unreachable. Its Save to config button writes this list, and its Reload config button reads it again. Write a preset shows an example.
| Key | Type | Default | Value |
|---|---|---|---|
name |
string | required | The name the settings page lists. No throttling and Unreachable are reserved, and 2 presets cannot share a name |
toxics |
mapping of toxic types | none | The toxics of every endpoint. Each type takes a downstream and an upstream toxic. Toxics lists the types and their fields |
endpoints |
map of route patterns to toxics | none | The toxics of 1 endpoint, keyed by the net/http.ServeMux pattern that the generated server registers, as "POST /post/{slug}/like/{$}". Each replaces the global toxic of the same type and stream. See Target 1 endpoint |
Validation
gen, lint, watch and init check the file when they load it. They reject:
- a YAML syntax error, an unknown key or a value of the wrong type
- a
level,clear-on,requiresorpervalue they do not know - a
cmdoutside the module - a preset without a
name, with the nameNo throttlingorUnreachable, or with the name of another preset - a
hostorui-hostthat is nothost:port, and the same value for both - an endpoint key that does not start with a method, as
GET /, or thatnet/http.ServeMuxrejects - a toxic field outside the range that Toxics lists, unless the toxic is
disabled, and atoxicityoutside 0 to 1
watch checks the rest when it starts and stops on the first error:
app-hostis anhttporhttpsURL with a host.- Each pattern in
exclude,watcher-ignoreand theincludeandexcludeof the custom watchers is a valid glob.[is not. - Each custom watcher has a
name, and acmdor arequires. - A
tlssection hascertandkey. templis on the PATH, and so isgolangci-lintwhenlintistrue.
Except for the app-host check, these errors start with initializing watch engine:. Nothing checks that dir-work and the files of tls exist before watch uses them.
With a watch.toxiproxy section, watch then stops when the port of host or ui-host is that of --host or app-host, or when it cannot listen on host or ui-host. An endpoint key that names no route of the app package is a warning in watch and an error in datapages lint.
Complete example
This file sets every key. proxy-timeout and clear-on have no effect, and asmflags, trimpath, msan and disabled keep their defaults:
cmd: cmd/server
watch:
app-host: http://localhost:8080
proxy-timeout: 2s # no effect
flags: -verbose
dir-work: . # the module root
debounce: 100ms
exclude:
- ".git/**"
- "**/.*"
- "**/*~"
- "docs/**"
watcher-ignore:
- "web/node_modules/**"
format: true
lint: true
log:
level: verbose
clear-on: restart # no effect
print-js-debug-logs: true
tls:
cert: .dev/localhost.pem
key: .dev/localhost-key.pem
compiler:
gcflags: all=-N -l
ldflags: -X main.version=dev
asmflags: ""
tags:
- dev
race: true
trimpath: false
msan: false
p: 4
env:
CGO_ENABLED: "1"
custom-watchers:
- name: Bundle the Web Components (esbuild)
include:
- "web/src/**/*.ts"
exclude:
- "web/src/**/*.test.ts"
cmd: cd web && node build.mjs
fail-on-error: true
debounce: 200ms
requires: reload
- name: Restart on a change of .env
include:
- ".env"
requires: restart
toxiproxy:
disabled: false
host: localhost:7332
ui-host: localhost:7333
presets:
- name: Flaky
toxics:
latency:
downstream:
latency: 300ms
jitter: 100ms
endpoints:
"POST /upload/{$}":
client-bandwidth:
upstream:
rate: 16000
The second watcher restarts the app when .env changes. The main.go of the scaffold reads .env when it starts. **/.* in exclude, like .* in the scaffold file, keeps a change of .env from rebuilding the app. The watcher still runs, because exclude does not apply to custom watchers.
See also
datapages watch: what the dev server does on startup and on a file change, and its flags.- Slow and failing networks: the proxy that
watch.toxiproxyconfigures, its settings page and the toxics. - App packages: how the commands find the app packages and the main package to build.
- Static assets: the static files that dev mode reads from the source tree.
- Dev Mode: what
TEMPL_DEV_MODE, whichwatchsets for the app, changes in the server. - Troubleshooting: dev server problems.