Dual-mode REST API for producing CoMapeo configuration archives (.comapeocat).
/v1(legacy): accepts a ZIP upload and shells out tomapeo-settings-builder(root/aliases to/v1)./v2(modern): accepts JSON and usescomapeocat@1.1.0Writer with strict validation and provenance metadata.
- Bun-native (Elysia) server with TypeScript support.
- Provenance in archives (
builderName: "comapeocat",builderVersion: 1.1.0). - Defense-in-depth validation: streaming body size enforcement, icon download limits, BCP‑47 locale checks, entry caps.
- Legacy-friendly type mapping (
boolean→selectOneYes/No, etc.). - Scripted smoke tests for both endpoints (
scripts/test-api.sh, Docker smoke in CI).
- Bun ≥ 1.3.2 (1.3.2 inside the Docker image).
- Node.js 24 LTS toolchain available for Docker builds.
- Global dependency for v1:
npm install -g mapeo-settings-builder(not needed if you only use v2).
bun install # installs comapeocat@1.1.0 and all deps
npm install -g mapeo-settings-builder # only if you need /v1docker pull communityfirst/comapeo-config-builder-api:latest
docker run -p 3000:3000 communityfirst/comapeo-config-builder-api:latestdocker build -t comapeo-config-builder-api:local .
docker run -p 3000:3000 comapeo-config-builder-api:local# Dev with hot reload
bun run dev
# Production
bun run startbun test # all tests
bun test src/tests/unit/utils/shell.test.ts # single file
./scripts/test-api.sh # hits both endpoints (requires server running)
bun run lint # Biome lint
bun tsc --noEmit # TypeScript type-checkGET /health
POST /v1
POST / # alias to /v1
Content-Type: multipart/form-data
field: file=@config.zip
Example:
curl -X POST -F "file=@config.zip" -o out.comapeocat http://localhost:3000/v1POST /v2
Content-Type: application/json
Example payload:
{
"metadata": { "name": "demo", "version": "1.0.0" },
"categories": [
{
"id": "cat-1",
"name": "Trees",
"appliesTo": ["observation", "track"],
"fields": ["field-1"],
"tags": { "categoryId": "cat-1" },
"track": true
}
],
"fields": [
{ "id": "field-1", "name": "Species", "tagKey": "species", "type": "select", "options": [{ "label": "Oak", "value": "oak" }] }
],
"icons": [
{ "id": "tree", "svgUrl": "https://example.com/tree.svg" },
{ "id": "flower", "svgData": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>" },
{ "id": "marker", "svgUrl": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3e%3cpath%20d='M12%202C8.13%202%205%205.13%205%209c0%205.25%207%2013%207%2013s7-7.75%207-13c0-3.87-3.13-7-7-7z'/%3e%3c/svg%3e" }
],
"translations": { "en": { "labels": { "cat-1": "Trees" } } }
}Set metadata.legacyCompat: true to add a legacy-friendly tag per category. When enabled, each category gets an extra tag keyed by its id with value "yes", in addition to the default { categoryId: "<id>" }.
select→selectOnemultiselect→selectMultipletextarea→textinteger→numberboolean→selectOnewith Yes/No optionsdate/datetime/photo/location→text- Unsupported types → 400
- All categories appear in the observation list (order preserved).
- Categories with
track: trueare added to the track list (must not be empty if any track is declared).
- JSON body ≤ 10 MB (streaming-validated).
- SVG icons ≤ 2 MB each; icons can be provided via:
svgData(inline SVG string)svgUrl(remote fetch with 5s timeout and content-type check)svgUrlwith data URI (e.g.,data:image/svg+xml,%3csvg...%3e)
- Total entries (categories + fields + icons + options + translations) ≤ 10,000.
- Categories must include
appliesTo;tagsdefault to{ categoryId: <id> }when missing/empty. - Locales must be valid BCP‑47 (normalized via
Intl.getCanonicalLocales). - Metadata
name/versioncannot contain path separators (path traversal guard).
src/index.ts— entrypoint (readspackage.jsonversion, starts server).src/app.ts— Elysia app factory, routes/health,/,/v1,/v2, streaming JSON parser/limits.src/controllers/— request dispatchers.src/services/—settingsBuilder(v1) andcomapeocatBuilder(v2) implementations.src/middleware/— logger, error handler.src/config/app.ts— size/time limits and temp prefixes.scripts/— API and Docker smoke tests.
docker-test.yml: lint + unit/integration on Bun 1.3.2, v2 API smoke, Docker smoke; PRs also publish a GHCR preview imageghcr.io/<repo>:pr-<number>.deploy.yml: onmain, run tests/lint/tsc then build & pushcommunityfirst/comapeo-config-builder-api:latestto Docker Hub and sync its description.
MIT