Contribution Guide
How to set up a development environment for working on webform-relay, and contribute changes.
This is the direct-clone path. If you only want to use webform-relay — give your own project its own deployed instance — don’t clone it, vendor it: see Getting Started.
Prerequisites
All development tooling runs inside Docker containers. You do not need Go, the AWS CLI, or SAM CLI installed on your machine.
Required on the host:
- Docker 24+
- Task 3.39+ (
flatten:on the dev include needs 3.39.0; therelay:include works on any Task 3) - Git
Initial setup
git clone https://gitlab.com/frob/webform-relay
cd webform-relay
# Build the SAM/AWS CLI tools image (once, or after Dockerfile.tools changes)
task relay:tools:build
# Download Hugo module dependencies for the docs site (once)
task deps:docsThe root Taskfile.yml is already wired — no includes: to add. It consumes
taskfiles/deploy.yml exactly the way a downstream project does, just pointing
at a local path:
dotenv: ['.env', '.env.project']
includes:
dev:
taskfile: ./taskfiles/dev.yml
dir: .
flatten: true # dev tasks keep unprefixed names: task test:all
relay:
taskfile: ./taskfiles/deploy.yml
dir: . # deploy tasks are namespaced: task relay:deploy:appThat means the vendored-consumer path is exercised on every contributor’s
machine, not only in downstream projects — if you change taskfiles/deploy.yml,
you are changing the file other projects vendor, and
{{.TASKFILE_DIR}}/{{.ROOT_DIR}} happen to be identical here in a way they
are not there. See
Two directories, not one
before touching a path in that file.
Configuration files
Deployment config is split in two, and the split matters when you contribute:
cp .env.example .env # secrets — gitignored, never commit.env— AWS access keys andFORM_TOKEN_SECRET. Gitignored. You only need it for tasks that talk to AWS (relay:deploy:*,relay:aws:*,relay:logs:*,relay:info). Building, testing, linting, and the local API need none of it, so a fresh clone with no.envruns the whole dev loop..env.project— region, bucket names,STACK_NAME, and the other non-secret stack parameters. Committed. Edit it in a PR when a parameter’s default should change for everyone; don’t put a machine-specific value in it.
To override a committed value locally, put the key in .env instead — it is
listed first in dotenv:, and Task takes the first file that sets a key, so
.env wins. A shell export wins over both.
Development workflow
# Compile check
task build:app
# Run tests
task test:all
# Lint
task lint:all
# Security scanners (gosec + govulncheck), the local equivalent of CI's
# security stage. Not part of lint:all or check — both CI jobs are
# allow_failure until the repo has a clean baseline, so these report rather
# than gate. Run them individually with task lint:gosec / task lint:govulncheck.
task lint:security
# Format
task fmt:codeRunning the API locally
Build the Lambda image and start a local API on port 3000:
task relay:build:container
task start:apiBy default the Lambda tries to load config from S3, which requires real AWS
credentials. For fully local development, pass a SAM env-vars JSON file that
sets CONFIG_INLINE to your YAML config:
task start:api ENV_VARS=test/e2e/testdata/env-vars.jsontest/e2e/testdata/env-vars.json ships with the repository and contains a
minimal contact form for testing. To use your own config, create a similar
file:
{
"SubmitFunction": {
"CONFIG_INLINE": "cache_ttl: 30s\nforms:\n contact:\n fields:\n - name: email\n required: true\n type: email\n outputs:\n - type: http_post\n url: http://localhost:9999/sink\n"
}
}Then submit to it:
curl -X POST http://localhost:3000/api/v1/submit/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=Jane&email=jane@example.com&message=Hello"Working on the docs and site
# Generate all content and build both sites
task build:web
# Serve at http://localhost:8080
task start:siteDeploying from a clone
The relay: tasks work from a clone exactly as they do from a vendored setup —
same names, same .env/.env.project split, same bootstrap. Follow
Getting Started from step 4 onward, skipping the
v add and the includes: block (both already done here) and reading
“your project root” as “the repo root”.
Submitting changes
- Fork the repository and create a branch.
- Make changes. Run
task lint:allandtask test:allbefore pushing, andtask lint:securityif you touched anything security-relevant (crypto, TLS, file or network I/O, dependency versions). - If you added a stack parameter, add it to both
.env.project(with its default) andtemplate.yaml, and to.env.exampleinstead if it is a secret. - Open a pull request. The PR description should explain why, not just what.
- Use Semantic Git commit messages.
Commit convention
Follow semantic git prefixes:
| Prefix | When to use |
|---|---|
feat | New functionality |
fix | Bug fix |
docs | Documentation only |
chore | Build, tooling, deps |
refactor | No behavior change |
test | Tests only |