diff --git a/.gitignore b/.gitignore index 40b1c43..9218aca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules/ dist/ coverage/ -backend/bin/ +server/bin/ .env .env.* !.env.example diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7e13fab --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' + when: never + - if: '$CI_PIPELINE_SOURCE == "push"' + +stages: + - test + +server: + stage: test + # The Debian image includes the C compiler needed by Go's race detector. + image: golang:1.27.2 + script: + - cd server + - go vet ./... + - go test -race ./... + +webapp: + stage: test + image: node:24 + before_script: + - npm install --global pnpm@10.28.2 + - cd webapp + - pnpm install --frozen-lockfile + script: + - pnpm check + - pnpm test diff --git a/README.md b/README.md index 9e71b3d..6364226 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Subalineado -Go API and SolidJS + TypeScript frontend in one repository. No database is required. +Go API and SolidJS + TypeScript webapp in one repository. No database is required. ## Start in development @@ -19,7 +19,7 @@ Ctrl+C stops both services; remove their containers with: task dev:down ``` -Compose Watch syncs frontend source into Vite for hot reload and rebuilds the Go service on changes. Dependency and Vite configuration changes rebuild the frontend. Go module and compilation caches persist through BuildKit cache mounts. After a broken Go build, fix and save the source to trigger another build. Dockerfile changes require restarting with `--build`. +Compose Watch syncs webapp source into Vite for hot reload and rebuilds the Go service on changes. Dependency and Vite configuration changes rebuild the webapp. Go module and compilation caches persist through BuildKit cache mounts. After a broken Go build, fix and save the source to trigger another build. Dockerfile changes require restarting with `--build`. ## Discover and extend commands @@ -29,21 +29,21 @@ task --list # Same list, explicitly task --summary dev # Read usage details for development task dev:status # See running services task dev:logs # Follow their logs -task install # Install native Go and frontend dependencies +task install # Install native Go and webapp dependencies task check # Go vet, Biome and TypeScript checks task test # Go race tests and Vitest tests task format # Go formatting and safe Biome fixes task build # Build both production Docker images ``` -`install`, `check`, `test`, and `format` use your native Go/Node/pnpm tools. `dev`, `dev:*`, and `build` use Docker. Individual commands such as `task backend:test` and `task frontend:check` are also listed. Tasks stop when a command fails. +`install`, `check`, `test`, and `format` use your native Go/Node/pnpm tools. `dev`, `dev:*`, and `build` use Docker. Individual commands such as `task server:test` and `task webapp:check` are also listed. Tasks stop when a command fails. All commands live in the root `Taskfile.yml`. Add a named task with `desc` and `cmds`; use `dir` for commands in a service directory and `summary` for detailed usage. Task finds the file from repository subdirectories too. ```yaml - frontend:build: - desc: Build the frontend locally - dir: frontend + webapp:build: + desc: Build the webapp locally + dir: webapp cmds: - pnpm build ``` @@ -60,7 +60,7 @@ docker compose -f compose.yaml -f compose.dev.yaml up --build --watch docker compose up --build -d --wait ``` -Open http://localhost:3000. Nginx serves the compiled frontend and proxies `/api/*` and `/health` to Go. Both runtime containers run as non-root users and expose health checks. Ports bind to loopback for local use. +Open http://localhost:3000. Nginx serves the compiled webapp and proxies `/api/*` and `/health` to Go. Both runtime containers run as non-root users and expose health checks. Ports bind to loopback for local use. ```sh docker compose down @@ -73,7 +73,7 @@ Use Node **24**, pnpm **10.28.2**, and Go **1.27.2**. `.tool-versions` pins the In one terminal: ```sh -cd backend +cd server go mod download go run ./cmd/api ``` @@ -81,26 +81,26 @@ go run ./cmd/api In another: ```sh -cd frontend +cd webapp pnpm install --frozen-lockfile pnpm dev ``` Native Go development requires restarting `go run` after edits; Compose Watch automates this in the container workflow. -Backend checks: +Server checks: ```sh -cd backend +cd server go test -race ./... go vet ./... go build -o bin/api ./cmd/api ``` -Frontend checks: +Webapp checks: ```sh -cd frontend +cd webapp pnpm check pnpm test pnpm build @@ -108,11 +108,20 @@ pnpm build `pnpm format` applies Biome formatting and safe fixes; `pnpm test:watch` runs Vitest interactively. +## Continuous integration + +`.gitlab-ci.yml` runs two jobs in parallel on every push and merge request: + +- Server: `go vet ./...` and `go test -race ./...` with Go 1.27.2. +- Webapp: `pnpm check` (Biome and TypeScript) and `pnpm test` (Vitest) with Node 24 and pnpm 10.28.2. Dependencies use the frozen lockfile. + +When a branch has an open merge request, its pushes run the merge request pipeline instead of a duplicate branch pipeline. All checks run even for documentation-only changes. A GitLab runner supporting container images (Docker or Kubernetes executor) must be available to the project; no deployment credentials are needed. + ## Structure and API -- `backend/cmd/api`: process startup, HTTP timeouts, signal handling and graceful shutdown. -- `backend/internal/httpapi`: router, HTTP handlers and endpoint tests. -- `frontend/src`: page, typed API client and component tests. +- `server/cmd/api`: process startup, HTTP timeouts, signal handling and graceful shutdown. +- `server/internal/httpapi`: router, HTTP handlers and endpoint tests. +- `webapp/src`: page, typed API client and component tests. - `Taskfile.yml`: discoverable development, dependency, formatting and verification commands. - `compose.yaml`: production images and service dependencies. - `compose.dev.yaml`: development overrides and file-watch rules. @@ -122,7 +131,9 @@ pnpm build | `GET /health` | `200 {"status":"ok"}` | | `GET /api/test` | `200 {"message":"API connection successful"}` | -Responses use `application/json`. Other methods return 405; unknown routes return 404. `HTTP_ADDR` configures the API listener (default `:8080`). Vite reads `API_PROXY_TARGET` (default `http://localhost:8080`); Compose sets it to the backend service. This is a server-side proxy setting, not a browser URL. +Responses use `application/json`. Other methods return 405; unknown routes return 404. `HTTP_ADDR` configures the API listener (default `:8080`). Vite reads `API_PROXY_TARGET` (default `http://localhost:8080`); Compose sets it to the server service. This is a server-side proxy setting, not a browser URL. + + ## Tooling choices diff --git a/Taskfile.yml b/Taskfile.yml index dc59e0a..5f8e532 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -14,8 +14,8 @@ tasks: dev: desc: Start both services with hot reload summary: | - Build and start the API and frontend with Compose Watch. - Frontend: http://localhost:5173; API: http://localhost:8080. + Build and start the API and webapp with Compose Watch. + Webapp: http://localhost:5173; API: http://localhost:8080. Ctrl+C stops the services. Run task dev:down to remove containers. Override occupied ports with API_PORT=18080 DEV_WEB_PORT=15173 task dev. Requires Docker Compose 2.32+; native Go, Node and pnpm are unnecessary. @@ -38,22 +38,22 @@ tasks: - '{{.DEV_COMPOSE}} ps' install: - desc: Download native backend and frontend dependencies + desc: Download native server and webapp dependencies cmds: - - task: backend:install - - task: frontend:install + - task: server:install + - task: webapp:install check: - desc: Check Go code, frontend linting and TypeScript types + desc: Check Go code, webapp linting and TypeScript types cmds: - - task: backend:check - - task: frontend:check + - task: server:check + - task: webapp:check test: - desc: Run backend race tests and frontend component tests + desc: Run server race tests and webapp component tests cmds: - - task: backend:test - - task: frontend:test + - task: server:test + - task: webapp:test build: desc: Build both production Docker images @@ -63,53 +63,53 @@ tasks: format: desc: Format Go code and apply safe Biome fixes cmds: - - task: backend:format - - task: frontend:format + - task: server:format + - task: webapp:format - backend:install: + server:install: desc: Download Go modules - dir: backend + dir: server cmds: - go mod download - backend:check: + server:check: desc: Run Go vet - dir: backend + dir: server cmds: - go vet ./... - backend:test: + server:test: desc: Run Go tests with race detection - dir: backend + dir: server cmds: - go test -race ./... - backend:format: - desc: Format backend Go source - dir: backend + server:format: + desc: Format server Go source + dir: server cmds: - go fmt ./... - frontend:install: - desc: Install frontend dependencies from the lockfile - dir: frontend + webapp:install: + desc: Install webapp dependencies from the lockfile + dir: webapp cmds: - pnpm install --frozen-lockfile - frontend:check: + webapp:check: desc: Run Biome and TypeScript checks - dir: frontend + dir: webapp cmds: - pnpm check - frontend:test: + webapp:test: desc: Run Vitest component tests - dir: frontend + dir: webapp cmds: - pnpm test - frontend:format: + webapp:format: desc: Apply Biome formatting and safe fixes - dir: frontend + dir: webapp cmds: - pnpm format diff --git a/compose.dev.yaml b/compose.dev.yaml index f3d66cf..257b72e 100644 --- a/compose.dev.yaml +++ b/compose.dev.yaml @@ -1,36 +1,36 @@ services: - backend: + server: develop: watch: - action: rebuild - path: ./backend + path: ./server ignore: - bin/ - "**/*_test.go" - frontend: + webapp: build: target: dev ports: !override - "127.0.0.1:${DEV_WEB_PORT:-5173}:5173" environment: - API_PROXY_TARGET: http://backend:8080 + API_PROXY_TARGET: http://server:8080 healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:5173').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] develop: watch: - action: sync - path: ./frontend/src + path: ./webapp/src target: /app/src initial_sync: true - action: sync - path: ./frontend/index.html + path: ./webapp/index.html target: /app/index.html initial_sync: true - action: rebuild - path: ./frontend/package.json + path: ./webapp/package.json - action: rebuild - path: ./frontend/pnpm-lock.yaml + path: ./webapp/pnpm-lock.yaml - action: rebuild - path: ./frontend/vite.config.ts + path: ./webapp/vite.config.ts - action: rebuild - path: ./frontend/tsconfig.json + path: ./webapp/tsconfig.json diff --git a/compose.yaml b/compose.yaml index 72e38a6..1cb5286 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,8 +1,8 @@ name: subalineado services: - backend: + server: build: - context: ./backend + context: ./server target: runtime ports: - "127.0.0.1:${API_PORT:-8080}:8080" @@ -10,13 +10,13 @@ services: HTTP_ADDR: ":8080" init: true stop_grace_period: 15s - frontend: + webapp: build: - context: ./frontend + context: ./webapp target: runtime ports: - "127.0.0.1:${WEB_PORT:-3000}:8080" depends_on: - backend: + server: condition: service_healthy init: true diff --git a/backend/.dockerignore b/server/.dockerignore similarity index 100% rename from backend/.dockerignore rename to server/.dockerignore diff --git a/backend/Dockerfile b/server/Dockerfile similarity index 100% rename from backend/Dockerfile rename to server/Dockerfile diff --git a/backend/cmd/api/main.go b/server/cmd/api/main.go similarity index 96% rename from backend/cmd/api/main.go rename to server/cmd/api/main.go index 2991fdb..31bd564 100644 --- a/backend/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -7,7 +7,7 @@ import ( "net/http" "os" "os/signal" - "subalineado/backend/internal/httpapi" + "subalineado/server/internal/httpapi" "syscall" "time" ) diff --git a/backend/go.mod b/server/go.mod similarity index 65% rename from backend/go.mod rename to server/go.mod index 8c68828..3bfd908 100644 --- a/backend/go.mod +++ b/server/go.mod @@ -1,4 +1,4 @@ -module subalineado/backend +module subalineado/server go 1.27.2 diff --git a/backend/go.sum b/server/go.sum similarity index 100% rename from backend/go.sum rename to server/go.sum diff --git a/backend/internal/httpapi/router.go b/server/internal/httpapi/router.go similarity index 100% rename from backend/internal/httpapi/router.go rename to server/internal/httpapi/router.go diff --git a/backend/internal/httpapi/router_test.go b/server/internal/httpapi/router_test.go similarity index 100% rename from backend/internal/httpapi/router_test.go rename to server/internal/httpapi/router_test.go diff --git a/frontend/.dockerignore b/webapp/.dockerignore similarity index 100% rename from frontend/.dockerignore rename to webapp/.dockerignore diff --git a/frontend/Dockerfile b/webapp/Dockerfile similarity index 100% rename from frontend/Dockerfile rename to webapp/Dockerfile diff --git a/frontend/biome.json b/webapp/biome.json similarity index 100% rename from frontend/biome.json rename to webapp/biome.json diff --git a/frontend/index.html b/webapp/index.html similarity index 100% rename from frontend/index.html rename to webapp/index.html diff --git a/frontend/nginx.conf b/webapp/nginx.conf similarity index 79% rename from frontend/nginx.conf rename to webapp/nginx.conf index 5c170a8..e6a3dc8 100644 --- a/frontend/nginx.conf +++ b/webapp/nginx.conf @@ -5,12 +5,12 @@ server { index index.html; location /api/ { - proxy_pass http://backend:8080; + proxy_pass http://server:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location = /health { - proxy_pass http://backend:8080; + proxy_pass http://server:8080; } location / { try_files $uri $uri/ /index.html; diff --git a/frontend/package.json b/webapp/package.json similarity index 95% rename from frontend/package.json rename to webapp/package.json index 4ec0664..d17f733 100644 --- a/frontend/package.json +++ b/webapp/package.json @@ -1,5 +1,5 @@ { - "name": "subalineado-frontend", + "name": "subalineado-webapp", "private": true, "version": "0.1.0", "type": "module", diff --git a/frontend/pnpm-lock.yaml b/webapp/pnpm-lock.yaml similarity index 100% rename from frontend/pnpm-lock.yaml rename to webapp/pnpm-lock.yaml diff --git a/frontend/src/App.test.tsx b/webapp/src/App.test.tsx similarity index 100% rename from frontend/src/App.test.tsx rename to webapp/src/App.test.tsx diff --git a/frontend/src/App.tsx b/webapp/src/App.tsx similarity index 100% rename from frontend/src/App.tsx rename to webapp/src/App.tsx diff --git a/frontend/src/api.ts b/webapp/src/api.ts similarity index 100% rename from frontend/src/api.ts rename to webapp/src/api.ts diff --git a/frontend/src/index.tsx b/webapp/src/index.tsx similarity index 100% rename from frontend/src/index.tsx rename to webapp/src/index.tsx diff --git a/frontend/src/style.css b/webapp/src/style.css similarity index 100% rename from frontend/src/style.css rename to webapp/src/style.css diff --git a/frontend/src/test-setup.ts b/webapp/src/test-setup.ts similarity index 100% rename from frontend/src/test-setup.ts rename to webapp/src/test-setup.ts diff --git a/frontend/tsconfig.json b/webapp/tsconfig.json similarity index 100% rename from frontend/tsconfig.json rename to webapp/tsconfig.json diff --git a/frontend/vite.config.ts b/webapp/vite.config.ts similarity index 100% rename from frontend/vite.config.ts rename to webapp/vite.config.ts