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; the relay: 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:docs

The 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:app

That 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 and FORM_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 .env runs 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:code

Running the API locally

Build the Lambda image and start a local API on port 3000:

task relay:build:container
task start:api

By 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.json

test/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:site

Deploying 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

  1. Fork the repository and create a branch.
  2. Make changes. Run task lint:all and task test:all before pushing, and task lint:security if you touched anything security-relevant (crypto, TLS, file or network I/O, dependency versions).
  3. If you added a stack parameter, add it to both .env.project (with its default) and template.yaml, and to .env.example instead if it is a secret.
  4. Open a pull request. The PR description should explain why, not just what.
  5. Use Semantic Git commit messages.

Commit convention

Follow semantic git prefixes:

PrefixWhen to use
featNew functionality
fixBug fix
docsDocumentation only
choreBuild, tooling, deps
refactorNo behavior change
testTests only