From ecd38ee28b1f703cb9ca6af42e007b513d7c0e96 Mon Sep 17 00:00:00 2001 From: h Date: Tue, 8 Sep 2026 20:38:27 +0200 Subject: [PATCH] Single part at the root, COMPOSE_FILE in .env --- README.md | 4 +- copier.yml | 20 +++++++++- .../{settings.json => settings.json.jinja} | 2 +- template/frontend/.env.example | 1 - template/frontend/Makefile.jinja | 17 -------- .../.dockerignore | 0 template/{{ part_dir }}/.env.example.jinja | 2 + template/{frontend => {{ part_dir }}}/.npmrc | 0 template/{{ part_dir }}/Makefile.jinja | 40 +++++++++++++++++++ .../biome.jsonc.jinja | 0 .../docker-compose.local.yml.jinja | 0 .../docker-compose.prod.yml.jinja | 0 .../docker-compose.yml.jinja | 12 +++--- .../package.json.jinja | 0 .../src/app.d.ts.jinja | 0 .../{frontend => {{ part_dir }}}/src/app.html | 0 .../src/lib/api/client.ts | 0 .../src/lib/index.ts | 0 ...ry == 'shadcn' %}utils.ts{% endif %}.jinja | 0 .../src/routes/+layout.svelte.jinja | 0 .../src/routes/+page.svelte.jinja | 0 .../src/routes/layout.css.jinja | 0 ... == 'static' %}+layout.ts{% endif %}.jinja | 0 .../svelte.config.js.jinja | 0 .../tsconfig.json.jinja | 0 .../vite.config.ts | 0 ... layout == 'part' %}.gitignore{% endif %}} | 0 ...f layout == 'part' %}CLAUDE.md{% endif %}} | 0 ...rve != 'api' %}Dockerfile{% endif %}.jinja | 0 ...ve == 'static' %}serve.ts{% endif %}.jinja | 0 ...shadcn' %}components.json{% endif %}.jinja | 0 31 files changed, 70 insertions(+), 28 deletions(-) rename template/.claude/{settings.json => settings.json.jinja} (59%) delete mode 100644 template/frontend/.env.example delete mode 100644 template/frontend/Makefile.jinja rename template/{frontend => {{ part_dir }}}/.dockerignore (100%) create mode 100644 template/{{ part_dir }}/.env.example.jinja rename template/{frontend => {{ part_dir }}}/.npmrc (100%) create mode 100644 template/{{ part_dir }}/Makefile.jinja rename template/{frontend => {{ part_dir }}}/biome.jsonc.jinja (100%) rename template/{frontend => {{ part_dir }}}/docker-compose.local.yml.jinja (100%) rename template/{frontend => {{ part_dir }}}/docker-compose.prod.yml.jinja (100%) rename template/{frontend => {{ part_dir }}}/docker-compose.yml.jinja (69%) rename template/{frontend => {{ part_dir }}}/package.json.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/app.d.ts.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/app.html (100%) rename template/{frontend => {{ part_dir }}}/src/lib/api/client.ts (100%) rename template/{frontend => {{ part_dir }}}/src/lib/index.ts (100%) rename template/{frontend => {{ part_dir }}}/src/lib/{% if ui_library == 'shadcn' %}utils.ts{% endif %}.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/routes/+layout.svelte.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/routes/+page.svelte.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/routes/layout.css.jinja (100%) rename template/{frontend => {{ part_dir }}}/src/routes/{% if frontend_adapter == 'static' %}+layout.ts{% endif %}.jinja (100%) rename template/{frontend => {{ part_dir }}}/svelte.config.js.jinja (100%) rename template/{frontend => {{ part_dir }}}/tsconfig.json.jinja (100%) rename template/{frontend => {{ part_dir }}}/vite.config.ts (100%) rename template/{frontend/.gitignore => {{ part_dir }}/{% if layout == 'part' %}.gitignore{% endif %}} (100%) rename template/{frontend/CLAUDE.md => {{ part_dir }}/{% if layout == 'part' %}CLAUDE.md{% endif %}} (100%) rename template/{frontend => {{ part_dir }}}/{% if serve != 'api' %}Dockerfile{% endif %}.jinja (100%) rename template/{frontend => {{ part_dir }}}/{% if serve == 'static' %}serve.ts{% endif %}.jinja (100%) rename template/{frontend => {{ part_dir }}}/{% if ui_library == 'shadcn' %}components.json{% endif %}.jinja (100%) diff --git a/README.md b/README.md index c1f9b7b..d0c3257 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # frontend-svelte -Copier template for a `frontend/` part: SvelteKit on Bun with Tailwind v4, ultracite (biome) and optional shadcn-svelte, served by a Bun server with SSR, as a static SPA behind `serve.ts`, or as a static SPA that the backend api serves from `frontend/build`. +Copier template for a SvelteKit frontend, either as a `frontend/` part next to other parts or alone at the root of the project (`layout`): SvelteKit on Bun with Tailwind v4, ultracite (biome) and optional shadcn-svelte, served by a Bun server with SSR, as a static SPA behind `serve.ts`, or as a static SPA that the backend api serves from `frontend/build`. -Ships `frontend/docker-compose.yml` with root-relative paths, `docker-compose.local.yml` (published ports) and `docker-compose.prod.yml` (the `caddy` network), a `Makefile` with `fmt`, `check`, `build` and `dev`, `.env.example`, `CLAUDE.md`, `.claude/rules` for Svelte and ultracite, the Svelte MCP server and `.impeccable/` scaffolding. Meant to be applied on top of the `infra` template. +Ships `frontend/docker-compose.yml` with root-relative paths, `docker-compose.local.yml` (published ports) and `docker-compose.prod.yml` (the `caddy` network), a `Makefile` with `fmt`, `check`, `build` and `dev`, `.env.example`, `CLAUDE.md`, `.claude/rules` for Svelte and ultracite, the Svelte MCP server and `.impeccable/` scaffolding. Applied on top of the `infra` template, `layout: parts` or `layout: single` respectively. ```sh copier copy --trust -d project_name="My Thing" -d serve=bun diff --git a/copier.yml b/copier.yml index 06fb929..a086192 100644 --- a/copier.yml +++ b/copier.yml @@ -13,6 +13,14 @@ project_slug: help: Package name, snake_case default: "{{ project_name | lower | replace(' ', '_') | replace('-', '_') | replace('.', '_') }}" +layout: + type: str + help: Where the frontend lives + choices: + In frontend/ next to other parts: part + At the root of the project: root + default: part + serve: type: str help: How the frontend is served @@ -44,5 +52,15 @@ frontend_adapter: when: false default: "{{ 'bun' if serve == 'bun' else 'static' }}" +part_dir: + type: str + when: false + default: "{{ 'frontend' if layout == 'part' else '.' }}" + +prefix: + type: str + when: false + default: "{{ 'frontend/' if layout == 'part' else '' }}" + _tasks: - - "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd frontend && bun install'{% else %}true{% endif %}" + - "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd {{ part_dir }} && bun install'{% else %}true{% endif %}" diff --git a/template/.claude/settings.json b/template/.claude/settings.json.jinja similarity index 59% rename from template/.claude/settings.json rename to template/.claude/settings.json.jinja index dc9a94c..c04b1b8 100644 --- a/template/.claude/settings.json +++ b/template/.claude/settings.json.jinja @@ -4,7 +4,7 @@ { "hooks": [ { - "command": "cd frontend && bun run fix --skip=correctness/noUnusedImports", + "command": "{% if layout == 'part' %}cd frontend && {% endif %}bun run fix --skip=correctness/noUnusedImports", "type": "command" } ], diff --git a/template/frontend/.env.example b/template/frontend/.env.example deleted file mode 100644 index dd6c544..0000000 --- a/template/frontend/.env.example +++ /dev/null @@ -1 +0,0 @@ -PUBLIC_API_BASE_URL=/api diff --git a/template/frontend/Makefile.jinja b/template/frontend/Makefile.jinja deleted file mode 100644 index 871a517..0000000 --- a/template/frontend/Makefile.jinja +++ /dev/null @@ -1,17 +0,0 @@ -COMPOSE := docker compose --project-directory .. - -.PHONY: fmt check build dev - -fmt: - bun run fix - -check: - bun run check - bun run lint - -build: -{% if serve == 'api' %} $(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build" -{% else %} $(COMPOSE) build frontend -{% endif %} -dev: - bun run dev diff --git a/template/frontend/.dockerignore b/template/{{ part_dir }}/.dockerignore similarity index 100% rename from template/frontend/.dockerignore rename to template/{{ part_dir }}/.dockerignore diff --git a/template/{{ part_dir }}/.env.example.jinja b/template/{{ part_dir }}/.env.example.jinja new file mode 100644 index 0000000..fbebbc6 --- /dev/null +++ b/template/{{ part_dir }}/.env.example.jinja @@ -0,0 +1,2 @@ +{% if layout == 'root' %}COMPOSE_FILE=docker-compose.yml:docker-compose.local.yml +{% endif %}PUBLIC_API_BASE_URL=/api diff --git a/template/frontend/.npmrc b/template/{{ part_dir }}/.npmrc similarity index 100% rename from template/frontend/.npmrc rename to template/{{ part_dir }}/.npmrc diff --git a/template/{{ part_dir }}/Makefile.jinja b/template/{{ part_dir }}/Makefile.jinja new file mode 100644 index 0000000..b245007 --- /dev/null +++ b/template/{{ part_dir }}/Makefile.jinja @@ -0,0 +1,40 @@ +COMPOSE := docker compose{% if layout == 'part' %} --project-directory ..{% endif %} +{% if layout == 'root' %}SERVICES := $(COMPOSE) --profile services +EXTERNAL := $(COMPOSE) --profile external +{% endif %} +.PHONY: {% if layout == 'root' %}env external recreate rebuild restart logs down {% endif %}fmt check build dev + +fmt: + bun run fix + +check: + bun run check + bun run lint + +build: +{% if serve == 'api' %} $(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build" +{% else %} $(COMPOSE) build frontend +{% endif %} +dev: + bun run dev +{% if layout == 'root' %} +env: + @test -f .env || cp .env.example .env + +external: + $(EXTERNAL) up -d + +recreate: external + $(SERVICES) up -d --force-recreate + +rebuild: build recreate + +restart: + $(SERVICES) restart + +logs: + $(SERVICES) logs -f --tail=100 + +down: + $(EXTERNAL) --profile services down +{% endif %} \ No newline at end of file diff --git a/template/frontend/biome.jsonc.jinja b/template/{{ part_dir }}/biome.jsonc.jinja similarity index 100% rename from template/frontend/biome.jsonc.jinja rename to template/{{ part_dir }}/biome.jsonc.jinja diff --git a/template/frontend/docker-compose.local.yml.jinja b/template/{{ part_dir }}/docker-compose.local.yml.jinja similarity index 100% rename from template/frontend/docker-compose.local.yml.jinja rename to template/{{ part_dir }}/docker-compose.local.yml.jinja diff --git a/template/frontend/docker-compose.prod.yml.jinja b/template/{{ part_dir }}/docker-compose.prod.yml.jinja similarity index 100% rename from template/frontend/docker-compose.prod.yml.jinja rename to template/{{ part_dir }}/docker-compose.prod.yml.jinja diff --git a/template/frontend/docker-compose.yml.jinja b/template/{{ part_dir }}/docker-compose.yml.jinja similarity index 69% rename from template/frontend/docker-compose.yml.jinja rename to template/{{ part_dir }}/docker-compose.yml.jinja index 4bdffdf..c9a4675 100644 --- a/template/frontend/docker-compose.yml.jinja +++ b/template/{{ part_dir }}/docker-compose.yml.jinja @@ -1,15 +1,15 @@ services: {% if serve != 'api' %} frontend: - build: frontend + build: {{ part_dir }} image: {{ project_slug }}/frontend profiles: [frontend, services] restart: unless-stopped env_file: - path: .env required: false - - path: frontend/.env +{% if layout == 'part' %} - path: frontend/.env required: false - command: {% if serve == 'bun' %}[build/index.js]{% else %}[run, serve.ts]{% endif %} +{% endif %} command: {% if serve == 'bun' %}[build/index.js]{% else %}[run, serve.ts]{% endif %} {% endif %} frontend-dev: image: oven/bun:alpine @@ -19,12 +19,12 @@ services: env_file: - path: .env required: false - - path: frontend/.env +{% if layout == 'part' %} - path: frontend/.env required: false - environment: +{% endif %} environment: API_PROXY_TARGET: http://api:8080 volumes: - - ./frontend:/app + - {{ './frontend' if layout == 'part' else '.' }}:/app - frontend_node_modules:/app/node_modules - frontend_svelte_kit:/app/.svelte-kit command: [sh, -c, "bun install && bun run dev --host 0.0.0.0 --port 3000"] diff --git a/template/frontend/package.json.jinja b/template/{{ part_dir }}/package.json.jinja similarity index 100% rename from template/frontend/package.json.jinja rename to template/{{ part_dir }}/package.json.jinja diff --git a/template/frontend/src/app.d.ts.jinja b/template/{{ part_dir }}/src/app.d.ts.jinja similarity index 100% rename from template/frontend/src/app.d.ts.jinja rename to template/{{ part_dir }}/src/app.d.ts.jinja diff --git a/template/frontend/src/app.html b/template/{{ part_dir }}/src/app.html similarity index 100% rename from template/frontend/src/app.html rename to template/{{ part_dir }}/src/app.html diff --git a/template/frontend/src/lib/api/client.ts b/template/{{ part_dir }}/src/lib/api/client.ts similarity index 100% rename from template/frontend/src/lib/api/client.ts rename to template/{{ part_dir }}/src/lib/api/client.ts diff --git a/template/frontend/src/lib/index.ts b/template/{{ part_dir }}/src/lib/index.ts similarity index 100% rename from template/frontend/src/lib/index.ts rename to template/{{ part_dir }}/src/lib/index.ts diff --git a/template/frontend/src/lib/{% if ui_library == 'shadcn' %}utils.ts{% endif %}.jinja b/template/{{ part_dir }}/src/lib/{% if ui_library == 'shadcn' %}utils.ts{% endif %}.jinja similarity index 100% rename from template/frontend/src/lib/{% if ui_library == 'shadcn' %}utils.ts{% endif %}.jinja rename to template/{{ part_dir }}/src/lib/{% if ui_library == 'shadcn' %}utils.ts{% endif %}.jinja diff --git a/template/frontend/src/routes/+layout.svelte.jinja b/template/{{ part_dir }}/src/routes/+layout.svelte.jinja similarity index 100% rename from template/frontend/src/routes/+layout.svelte.jinja rename to template/{{ part_dir }}/src/routes/+layout.svelte.jinja diff --git a/template/frontend/src/routes/+page.svelte.jinja b/template/{{ part_dir }}/src/routes/+page.svelte.jinja similarity index 100% rename from template/frontend/src/routes/+page.svelte.jinja rename to template/{{ part_dir }}/src/routes/+page.svelte.jinja diff --git a/template/frontend/src/routes/layout.css.jinja b/template/{{ part_dir }}/src/routes/layout.css.jinja similarity index 100% rename from template/frontend/src/routes/layout.css.jinja rename to template/{{ part_dir }}/src/routes/layout.css.jinja diff --git a/template/frontend/src/routes/{% if frontend_adapter == 'static' %}+layout.ts{% endif %}.jinja b/template/{{ part_dir }}/src/routes/{% if frontend_adapter == 'static' %}+layout.ts{% endif %}.jinja similarity index 100% rename from template/frontend/src/routes/{% if frontend_adapter == 'static' %}+layout.ts{% endif %}.jinja rename to template/{{ part_dir }}/src/routes/{% if frontend_adapter == 'static' %}+layout.ts{% endif %}.jinja diff --git a/template/frontend/svelte.config.js.jinja b/template/{{ part_dir }}/svelte.config.js.jinja similarity index 100% rename from template/frontend/svelte.config.js.jinja rename to template/{{ part_dir }}/svelte.config.js.jinja diff --git a/template/frontend/tsconfig.json.jinja b/template/{{ part_dir }}/tsconfig.json.jinja similarity index 100% rename from template/frontend/tsconfig.json.jinja rename to template/{{ part_dir }}/tsconfig.json.jinja diff --git a/template/frontend/vite.config.ts b/template/{{ part_dir }}/vite.config.ts similarity index 100% rename from template/frontend/vite.config.ts rename to template/{{ part_dir }}/vite.config.ts diff --git a/template/frontend/.gitignore b/template/{{ part_dir }}/{% if layout == 'part' %}.gitignore{% endif %} similarity index 100% rename from template/frontend/.gitignore rename to template/{{ part_dir }}/{% if layout == 'part' %}.gitignore{% endif %} diff --git a/template/frontend/CLAUDE.md b/template/{{ part_dir }}/{% if layout == 'part' %}CLAUDE.md{% endif %} similarity index 100% rename from template/frontend/CLAUDE.md rename to template/{{ part_dir }}/{% if layout == 'part' %}CLAUDE.md{% endif %} diff --git a/template/frontend/{% if serve != 'api' %}Dockerfile{% endif %}.jinja b/template/{{ part_dir }}/{% if serve != 'api' %}Dockerfile{% endif %}.jinja similarity index 100% rename from template/frontend/{% if serve != 'api' %}Dockerfile{% endif %}.jinja rename to template/{{ part_dir }}/{% if serve != 'api' %}Dockerfile{% endif %}.jinja diff --git a/template/frontend/{% if serve == 'static' %}serve.ts{% endif %}.jinja b/template/{{ part_dir }}/{% if serve == 'static' %}serve.ts{% endif %}.jinja similarity index 100% rename from template/frontend/{% if serve == 'static' %}serve.ts{% endif %}.jinja rename to template/{{ part_dir }}/{% if serve == 'static' %}serve.ts{% endif %}.jinja diff --git a/template/frontend/{% if ui_library == 'shadcn' %}components.json{% endif %}.jinja b/template/{{ part_dir }}/{% if ui_library == 'shadcn' %}components.json{% endif %}.jinja similarity index 100% rename from template/frontend/{% if ui_library == 'shadcn' %}components.json{% endif %}.jinja rename to template/{{ part_dir }}/{% if ui_library == 'shadcn' %}components.json{% endif %}.jinja