diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b4870f10 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +node_modules +web-ui/node_modules +web-ui/dist +dist +coverage +docs +datacache +snapshots +.env +cache.sqlite +cache.sqlite-wal +cache.sqlite-shm +*.har +http-dump.jsonl +output.log +testout_* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9e4875e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Dev image for ParksAPI — Node 24 / npm 11 (see package.json "engines") +FROM node:24-slim + +ENV NPM_CONFIG_UPDATE_NOTIFIER=false \ + NPM_CONFIG_FUND=false \ + TZ=UTC + +WORKDIR /app + +# Install deps first so the layer is cached independently of the source. +# --ignore-scripts skips the root "prepare": "tsc" hook, which cannot run yet +# (no sources at this point) and is not needed: tsx executes TypeScript directly. +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts + +# Baked-in sources, so the image is usable without a bind mount too. +COPY . . + +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["npm", "run", "dev"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ab00974e --- /dev/null +++ b/Makefile @@ -0,0 +1,103 @@ +# ParksAPI — everything runs in a container (Node 24 / npm 11 inside). +# Nothing here needs Node installed on the host. +# +# make → this help +# make build → build the image once +# make park PARK=efteling +# +# Fedora ships Podman, not Docker. Override if you are on a Docker host: +# make COMPOSE="docker compose" dev + +COMPOSE ?= podman-compose +SERVICE = parksapi +RUN = $(COMPOSE) run --rm $(SERVICE) + +# Optional: extra flags forwarded to the test harness, e.g. +# make dev ARGS="--skip-schedules --verbose" +ARGS ?= +PARK ?= + +.DEFAULT_GOAL := help + +##@ Setup + +.PHONY: build +build: ## Build the dev image (npm install happens here) + $(COMPOSE) build + +.PHONY: rebuild +rebuild: ## Rebuild the image from scratch, ignoring layer cache + $(COMPOSE) build --no-cache + +.PHONY: deps +deps: ## Reinstall node_modules inside the container volume + $(RUN) npm ci --ignore-scripts + +##@ Running the harness + +.PHONY: dev +dev: ## Test every park (long: 76 destinations, hits live APIs) + $(RUN) npm run dev -- $(ARGS) + +.PHONY: park +park: ## Test one park — make park PARK=efteling + @test -n "$(PARK)" || { echo "usage: make park PARK= (see: make list)"; exit 1; } + $(RUN) npm run dev -- $(PARK) $(ARGS) + +.PHONY: category +category: ## Test a whole category — make category CATEGORY=Universal + @test -n "$(CATEGORY)" || { echo "usage: make category CATEGORY="; exit 1; } + $(RUN) npm run dev -- --category $(CATEGORY) $(ARGS) + +.PHONY: list +list: ## List available park IDs + $(RUN) npm run dev -- --list + +.PHONY: health +health: ## Health-check all endpoints + $(RUN) npm run health + +##@ Build & tests + +.PHONY: test +test: ## Run the Vitest suite + $(RUN) npm test + +.PHONY: coverage +coverage: ## Run tests with coverage report + $(RUN) npm run test:coverage + +.PHONY: compile +compile: ## Type-check / compile TypeScript to dist/ + $(RUN) npm run build + +.PHONY: web +web: ## Serve the web admin on http://localhost:8888 + $(COMPOSE) run --rm --service-ports $(SERVICE) npm run web + +.PHONY: shell +shell: ## Open a shell inside the container + $(RUN) bash + +##@ Housekeeping + +.PHONY: cache-clear +cache-clear: ## Drop the SQLite cache and retest everything fresh + $(RUN) npm run dev -- --clear-cache $(ARGS) + +.PHONY: clean +clean: ## Remove containers and the node_modules volume + -$(COMPOSE) down -v + +.PHONY: clean-all +clean-all: clean ## Also delete the built image + -podman rmi localhost/parksapi-dev + +##@ Help + +.PHONY: help +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"; printf "\nParksAPI — containerised commands\n"} \ + /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5); next } \ + /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-14s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + @printf "\nFirst run: make build && make park PARK=efteling\n\n" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..ba47a87c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + parksapi: + build: + context: . + image: localhost/parksapi-dev + working_dir: /app + # :z = SELinux shared relabel (required on Fedora for bind mounts) + volumes: + - .:/app:z + # keep container-installed node_modules out of the host tree + - parksapi_node_modules:/app/node_modules + environment: + TZ: ${TZ:-UTC} + # only published by `make web` (podman-compose run --service-ports) + ports: + - "8888:8888" + tty: true + command: ["npm", "run", "dev"] + +volumes: + parksapi_node_modules: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..15ed0409 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# Every npm script runs `tsx --env-file=.env`, which aborts if the file is +# missing. An empty .env is a valid configuration (all @config props default +# to empty strings), so create it when absent — this is the `touch .env` step. +[ -f /app/.env ] || : > /app/.env + +# When the source tree is bind-mounted, node_modules comes from a named volume +# that may still be empty on the very first run. +if [ ! -d /app/node_modules/tsx ]; then + echo "→ node_modules empty, running npm ci…" + npm ci --ignore-scripts +fi + +exec "$@"