Compare commits
12
Commits
695742019d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a7fbab3c4 | ||
|
|
0fadb740fb | ||
|
|
6c9d5063b9 | ||
|
|
5cee5f99e9 | ||
|
|
5e449a560b | ||
|
|
ab67b5a5da | ||
|
|
c93a8fc68d | ||
|
|
180f16defd | ||
|
|
556f87ef60 | ||
|
|
7ca0813567 | ||
|
|
73132fc2d0 | ||
|
|
0c01260da2 |
No files matched your search
@@ -1,8 +1,6 @@
|
|||||||
# frontend-svelte
|
# frontend-svelte
|
||||||
|
|
||||||
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`.
|
Copier template for a SvelteKit frontend on Bun with Tailwind v4, biome or ESLint + Prettier and optional shadcn-svelte, plain or with the [claddcn](https://git.kotikot.com/lib/claddcn-svelte) skin: SSR on Bun, a static SPA behind `serve.ts`, or a static SPA served by the backend api. A `frontend/` part or the whole project (`layout`), on top of `infra`.
|
||||||
|
|
||||||
Ships `frontend/docker-compose.yml` with root-relative paths and profiles per service, `docker-compose.override.yml.example` (the `caddy` network aliases, copied by `make env`), 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
|
```sh
|
||||||
copier copy --trust -d project_name="My Thing" -d serve=bun <this-repo> <dest>
|
copier copy --trust -d project_name="My Thing" -d serve=bun <this-repo> <dest>
|
||||||
|
|||||||
+12
-5
@@ -35,12 +35,16 @@ ui_library:
|
|||||||
choices:
|
choices:
|
||||||
none (Tailwind v4): none
|
none (Tailwind v4): none
|
||||||
shadcn-svelte: shadcn
|
shadcn-svelte: shadcn
|
||||||
|
shadcn-svelte with the claddcn skin: claddcn
|
||||||
default: none
|
default: none
|
||||||
|
|
||||||
include_impeccable:
|
linter:
|
||||||
type: bool
|
type: str
|
||||||
help: .impeccable/ scaffolding for the impeccable skill
|
help: Linter and formatter
|
||||||
default: true
|
choices:
|
||||||
|
Biome with the ultracite preset: biome
|
||||||
|
ESLint + Prettier (the sv create setup): eslint
|
||||||
|
default: biome
|
||||||
|
|
||||||
install_deps:
|
install_deps:
|
||||||
type: bool
|
type: bool
|
||||||
@@ -63,4 +67,7 @@ prefix:
|
|||||||
default: "{{ 'frontend/' if layout == 'part' else '' }}"
|
default: "{{ 'frontend/' if layout == 'part' else '' }}"
|
||||||
|
|
||||||
_tasks:
|
_tasks:
|
||||||
- "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd {{ part_dir }} && bun install'{% else %}true{% endif %}"
|
- "{% if _copier_operation == 'copy' and ui_library == 'claddcn' %}npx -y git+https://git.kotikot.com/lib/claddcn-svelte.git init --cwd {{ part_dir }} --no-install{% else %}true{% endif %}"
|
||||||
|
# Generated values (project name) can push lines past the formatter width,
|
||||||
|
# so every fresh copy is formatted once by its own linter.
|
||||||
|
- "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd {{ part_dir }} && bun install && bun --bun run fix'{% else %}true{% endif %}"
|
||||||
File renamed without changes.
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"command": "{% if layout == 'part' %}cd frontend && {% endif %}bun run fix --skip=correctness/noUnusedImports",
|
"command": "{% if layout == 'part' %}cd frontend && {% endif %}bun run fix{% if linter == 'biome' %} --skip=correctness/noUnusedImports{% endif %}",
|
||||||
"type": "command"
|
"type": "command"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"svelte": {
|
|
||||||
"type": "http",
|
|
||||||
"url": "https://mcp.svelte.dev/mcp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
config.local.json
|
|
||||||
hook.cache.json
|
|
||||||
hook.pending.json
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
build
|
build
|
||||||
.env
|
# .env goes into the build: $env/static/public (and static/private) are
|
||||||
|
# inlined at build time from it. It stays in the builder stage, the runtime
|
||||||
|
# image gets build/ only. Local overrides (.env.local and the like) stay out.
|
||||||
.env.*
|
.env.*
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
COMPOSE := {% if layout == 'part' %}cd .. && {% endif %}docker compose
|
COMPOSE := {% if layout == 'part' %}cd .. && {% endif %}docker compose
|
||||||
{% if serve != 'api' %}IMAGE := {{ project_slug }}/frontend
|
|
||||||
{% endif %}changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
|
|
||||||
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
|
|
||||||
|
|
||||||
.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down deploy {% endif %}fmt check build pre-deploy post-deploy dev
|
.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down {% endif %}fmt check build dev
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
bun run fix
|
bun run fix
|
||||||
@@ -16,20 +13,19 @@ build:
|
|||||||
{% if serve == 'api' %} $(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
|
{% if serve == 'api' %} $(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
|
||||||
{% else %} $(COMPOSE) build frontend
|
{% else %} $(COMPOSE) build frontend
|
||||||
{% endif %}
|
{% endif %}
|
||||||
pre-deploy:
|
|
||||||
@if $(call changed,refs/deploy/frontend,.) || {% if serve == 'api' %}! test -d build{% else %}! docker image inspect $(IMAGE) >/dev/null 2>&1{% endif %}; then \
|
|
||||||
$(MAKE) build && $(call stamp,refs/deploy/frontend); \
|
|
||||||
fi
|
|
||||||
|
|
||||||
post-deploy:
|
|
||||||
@:
|
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
bun run dev
|
bun run dev
|
||||||
{% if layout == 'root' %}
|
{% if layout == 'root' %}
|
||||||
env:
|
env:
|
||||||
@test -f .env || cp .env.example .env
|
@fresh=; test -f .env || { cp .env.example .env; fresh=1; }; \
|
||||||
@test -f docker-compose.override.yml || cp docker-compose.override.yml.example docker-compose.override.yml
|
if ! test -f docker-compose.override.yml; then \
|
||||||
|
cp docker-compose.override.yml.example docker-compose.override.yml; \
|
||||||
|
for port in $$(sed -n 's/.*127\.0\.0\.1:\([0-9]*\):.*/\1/p' docker-compose.override.yml | sort -u); do \
|
||||||
|
random=$$((20000 + $$(od -An -N2 -tu2 /dev/urandom) % 40000)); \
|
||||||
|
sed "s/127\.0\.0\.1:$$port:/127.0.0.1:$$random:/" docker-compose.override.yml > docker-compose.override.yml.tmp && mv docker-compose.override.yml.tmp docker-compose.override.yml; \
|
||||||
|
test -z "$$fresh" || { sed "s|localhost:$$port/|localhost:$$random/|" .env > .env.tmp && mv .env.tmp .env; }; \
|
||||||
|
done; \
|
||||||
|
fi
|
||||||
|
|
||||||
recreate:
|
recreate:
|
||||||
$(COMPOSE) up -d --force-recreate
|
$(COMPOSE) up -d --force-recreate
|
||||||
@@ -45,8 +41,4 @@ logs:
|
|||||||
|
|
||||||
down:
|
down:
|
||||||
$(COMPOSE) down
|
$(COMPOSE) down
|
||||||
|
|
||||||
deploy: pre-deploy
|
|
||||||
$(COMPOSE) up -d
|
|
||||||
$(MAKE) post-deploy
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -6,6 +6,8 @@ services:
|
|||||||
caddy:
|
caddy:
|
||||||
aliases:
|
aliases:
|
||||||
- {{ dash }}-frontend
|
- {{ dash }}-frontend
|
||||||
|
# ports:
|
||||||
|
# - "127.0.0.1:3000:3000"
|
||||||
|
|
||||||
{% endif %} frontend-dev:
|
{% endif %} frontend-dev:
|
||||||
networks:
|
networks:
|
||||||
@@ -13,6 +15,8 @@ services:
|
|||||||
caddy:
|
caddy:
|
||||||
aliases:
|
aliases:
|
||||||
- {{ dash }}-frontend
|
- {{ dash }}-frontend
|
||||||
|
# ports:
|
||||||
|
# - "127.0.0.1:3000:3000"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
caddy:
|
caddy:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
{% if serve != 'api' %} frontend:
|
{% if serve != 'api' %} frontend:
|
||||||
build: {{ part_dir }}
|
build: {{ part_dir }}
|
||||||
image: {{ project_slug }}/frontend
|
image: {{ project_slug | replace('_', '-') }}/frontend
|
||||||
profiles: [frontend, services]
|
profiles: [frontend, services]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -1,41 +1,73 @@
|
|||||||
{
|
{
|
||||||
"name": "{{ project_slug }}",
|
"name": "{{ project_slug }}",
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"imports": {
|
||||||
|
"#lib": "./src/lib/index.js",
|
||||||
|
"#lib/*": "./src/lib/*"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"dev": "vite dev",
|
||||||
|
{%- if linter == 'biome' %}
|
||||||
|
"fix": "ultracite fix",
|
||||||
"lint": "ultracite check",
|
"lint": "ultracite check",
|
||||||
"fix": "ultracite fix"
|
{%- else %}
|
||||||
|
"fix": "prettier --write . && eslint --fix .",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
{%- endif %}
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.2",
|
{%- set deps = {
|
||||||
"@sveltejs/kit": "^2.63.0",
|
"@sveltejs/kit": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"@tailwindcss/vite": "^4.3.0",
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"@types/bun": "^1.3.0",
|
"@types/bun": "^1.3.0",
|
||||||
{% if frontend_adapter == 'bun' %} "svelte-adapter-bun": "^1.0.1",
|
"svelte": "^5.57.1",
|
||||||
{% else %} "@sveltejs/adapter-static": "^3.0.10",
|
"svelte-check": "^4.7.6",
|
||||||
{% endif %}{% if ui_library == 'shadcn' %} "@internationalized/date": "^3.12.0",
|
"tailwindcss": "^4.3.0",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
|
"vite": "^8.0.16",
|
||||||
|
} -%}
|
||||||
|
{%- if linter == 'biome' %}{% set _ = deps.update({
|
||||||
|
"@biomejs/biome": "^2.5.15",
|
||||||
|
"ultracite": "^7.12.4",
|
||||||
|
}) %}{% else %}{% set _ = deps.update({
|
||||||
|
"@eslint/compat": "^2.1.1",
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"eslint": "^10.12.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-svelte": "^3.23.0",
|
||||||
|
"globals": "^17.13.0",
|
||||||
|
"prettier": "^3.9.9",
|
||||||
|
"prettier-plugin-svelte": "^4.1.1",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||||
|
"typescript-eslint": "^8.71.1",
|
||||||
|
}) %}{% endif -%}
|
||||||
|
{%- if frontend_adapter == 'bun' %}{% set _ = deps.update({"@sveltejs/adapter-bun": "^1.0.0"}) %}{% else %}{% set _ = deps.update({"@sveltejs/adapter-static": "^4.0.0"}) %}{% endif -%}
|
||||||
|
{%- if ui_library != 'none' %}{% set _ = deps.update({
|
||||||
|
"@internationalized/date": "^3.12.0",
|
||||||
"@lucide/svelte": "^1.23.0",
|
"@lucide/svelte": "^1.23.0",
|
||||||
"bits-ui": "^2.16.3",
|
"bits-ui": "^2.16.3",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"shadcn-svelte": "^1.3.0",
|
"shadcn-svelte": "^1.7.0",
|
||||||
"svelte-sonner": "^1.1.0",
|
"svelte-sonner": "^1.1.0",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwind-variants": "^3.2.2",
|
"tailwind-variants": "^3.2.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
{% endif %} "svelte": "^5.56.1",
|
}) %}{% endif -%}
|
||||||
"svelte-check": "^4.6.0",
|
{%- if ui_library == 'claddcn' %}{% set _ = deps.update({
|
||||||
"tailwindcss": "^4.3.0",
|
"@fontsource-variable/geist": "^5.3.0",
|
||||||
"typescript": "^6.0.3",
|
"@fontsource-variable/geist-mono": "^5.3.0",
|
||||||
"ultracite": "7.9.0",
|
}) %}{% endif %}
|
||||||
"vite": "^8.0.16"
|
{%- for name, version in deps | dictsort %}
|
||||||
|
"{{ name }}": "{{ version }}"{{ "," if not loop.last }}
|
||||||
|
{%- endfor %}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineEnvVars } from "@sveltejs/kit/env";
|
||||||
|
|
||||||
|
export const variables = defineEnvVars({
|
||||||
|
PUBLIC_API_BASE_URL: { public: true, schema: (input) => input ?? "/api" },
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { env } from "$env/dynamic/public";
|
import { PUBLIC_API_BASE_URL } from "$app/env/public";
|
||||||
|
|
||||||
const BASE = env.PUBLIC_API_BASE_URL ?? "/api";
|
const BASE = PUBLIC_API_BASE_URL;
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
status: number;
|
status: number;
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
// place files you want to import through the `$lib` alias in this folder.
|
|
||||||
// biome-ignore-all lint/suspicious/noEmptySource: temporary
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// place files you want to import through the `#lib` alias in this folder.
|
||||||
|
{% if linter == 'biome' %}// biome-ignore-all lint/suspicious/noEmptySource: temporary
|
||||||
|
{% endif %}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
{%- if ui_library == 'shadcn' %}
|
{%- if ui_library != 'none' %}
|
||||||
import { ModeWatcher } from "mode-watcher";
|
import { ModeWatcher } from "mode-watcher";
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% if ui_library == 'shadcn' %}<ModeWatcher />
|
{% if ui_library != 'none' %}<ModeWatcher />
|
||||||
|
|
||||||
{% endif %}{@render children()}
|
{% endif %}{@render children()}
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="flex min-h-dvh flex-col items-center justify-center gap-4 px-6 text-center {% if ui_library == 'shadcn' %}bg-background text-foreground{% else %}bg-white text-neutral-900 dark:bg-neutral-950 dark:text-neutral-50{% endif %}"
|
class="flex min-h-dvh flex-col items-center justify-center gap-4 px-6 text-center {% if ui_library != 'none' %}bg-background text-foreground{% else %}bg-white text-neutral-900 dark:bg-neutral-950 dark:text-neutral-50{% endif %}"
|
||||||
>
|
>
|
||||||
<h1 class="font-bold text-4xl tracking-tight sm:text-5xl">{{ project_name }}</h1>
|
<h1 class="font-bold text-4xl tracking-tight sm:text-5xl">{{ project_name }}</h1>
|
||||||
<p class="max-w-prose text-base {% if ui_library == 'shadcn' %}text-muted-foreground{% else %}text-neutral-500{% endif %}">
|
<p class="max-w-prose text-base {% if ui_library != 'none' %}text-muted-foreground{% else %}text-neutral-500{% endif %}">
|
||||||
Edit
|
Edit
|
||||||
<code class="font-mono">src/routes/+page.svelte</code>.
|
<code class="font-mono">src/routes/+page.svelte</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
{% if ui_library == 'shadcn' -%}
|
{% if ui_library != 'none' -%}
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
@import "shadcn-svelte/tailwind.css";
|
@import "shadcn-svelte/tailwind.css";
|
||||||
|
{%- if ui_library == 'claddcn' %}
|
||||||
|
@import "./claddcn.css";
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
{% if ui_library == 'shadcn' %}:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(1 0 0);
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
@@ -100,7 +103,7 @@
|
|||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
{% endif %}@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
{% if frontend_adapter == 'bun' -%}
|
|
||||||
import adapter from "svelte-adapter-bun";
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
compilerOptions: {
|
|
||||||
runes: ({ filename }) =>
|
|
||||||
// biome-ignore lint/performance/useTopLevelRegex: svelte default behaviour
|
|
||||||
filename.split(/[/\\]/).includes("node_modules") ? undefined : true,
|
|
||||||
},
|
|
||||||
kit: {
|
|
||||||
adapter: adapter(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
{%- else -%}
|
|
||||||
import adapter from "@sveltejs/adapter-static";
|
|
||||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
kit: {
|
|
||||||
adapter: adapter({
|
|
||||||
assets: "build",
|
|
||||||
fallback: "index.html",
|
|
||||||
pages: "build",
|
|
||||||
precompress: false,
|
|
||||||
strict: false,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
preprocess: vitePreprocess(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
{%- endif %}
|
|
||||||
@@ -1,20 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
"extends": "$app/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rewriteRelativeImportExtensions": true,
|
"rewriteRelativeImportExtensions": true,
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true
|
||||||
"moduleResolution": "bundler"
|
},
|
||||||
}
|
"include": ["src", "vite.config.ts"]
|
||||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
|
||||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
|
||||||
//
|
|
||||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
|
||||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
|
||||||
import { defineConfig } from "vite";
|
|
||||||
|
|
||||||
const allowedHosts = process.env.ALLOWED_HOSTS
|
|
||||||
? process.env.ALLOWED_HOSTS.split(",")
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [tailwindcss(), sveltekit()],
|
|
||||||
server: {
|
|
||||||
allowedHosts,
|
|
||||||
proxy: {
|
|
||||||
"/api": {
|
|
||||||
changeOrigin: true,
|
|
||||||
target: process.env.API_PROXY_TARGET ?? "http://localhost:8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{% if frontend_adapter == 'bun' -%}
|
||||||
|
import adapter from "@sveltejs/adapter-bun";
|
||||||
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
const PATH_SEP = /[/\\]/;
|
||||||
|
{%- else -%}
|
||||||
|
import adapter from "@sveltejs/adapter-static";
|
||||||
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
|
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
const allowedHosts = process.env.ALLOWED_HOSTS
|
||||||
|
? process.env.ALLOWED_HOSTS.split(",")
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
sveltekit({
|
||||||
|
{%- if frontend_adapter == 'bun' %}
|
||||||
|
adapter: adapter(),
|
||||||
|
compilerOptions: {
|
||||||
|
runes: ({ filename }) =>
|
||||||
|
filename.split(PATH_SEP).includes("node_modules") ? undefined : true,
|
||||||
|
},
|
||||||
|
{%- else %}
|
||||||
|
adapter: adapter({
|
||||||
|
assets: "build",
|
||||||
|
fallback: "index.html",
|
||||||
|
pages: "build",
|
||||||
|
precompress: false,
|
||||||
|
strict: false,
|
||||||
|
}),
|
||||||
|
preprocess: [vitePreprocess()],
|
||||||
|
{%- endif %}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
allowedHosts,
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
changeOrigin: true,
|
||||||
|
target: process.env.API_PROXY_TARGET ?? "http://localhost:8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -16,8 +16,3 @@ node_modules
|
|||||||
# Vite
|
# Vite
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
# Impeccable
|
|
||||||
**/.impeccable/config.local.json
|
|
||||||
**/.impeccable/hook.cache.json
|
|
||||||
**/.impeccable/hook.pending.json
|
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
# {{ project_name }}
|
# {{ project_name }}
|
||||||
|
|
||||||
Runs in Docker, locally the same way as in production. `COMPOSE_PROFILES` in `.env` selects the services that run on this machine. `docker-compose.override.yml` holds what differs between machines: published ports and the aliases on the external `caddy` network.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make env
|
make env # .env and docker-compose.override.yml from the examples
|
||||||
make recreate
|
make recreate # the services in COMPOSE_PROFILES
|
||||||
|
make dev # vite on the host
|
||||||
|
make check
|
||||||
```
|
```
|
||||||
|
|
||||||
`make env` creates `.env` and `docker-compose.override.yml` from their examples. `make recreate` starts the selected profiles, `make rebuild` builds the image first, `make logs`, `make restart` and `make down` do what they say.
|
|
||||||
|
|
||||||
To deploy, `git pull && make deploy`. It runs `make pre-deploy`, which rebuilds the image only when something in the repository changed, then `docker compose up -d`. A deploy system with its own `up` step calls `make pre-deploy` before it and `make post-deploy` after. What was built is remembered as `refs/deploy/*` in the clone.
|
|
||||||
|
|
||||||
`make dev` runs the Vite dev server on the host, `make fmt` formats, `make check` runs svelte-check and the linter.
|
|
||||||
+8
-7
@@ -2,22 +2,22 @@
|
|||||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
"extends": ["ultracite/biome/core", "ultracite/biome/svelte"],
|
"extends": ["ultracite/biome/core", "ultracite/biome/svelte"],
|
||||||
"files": {
|
"files": {
|
||||||
|
{%- if ui_library == 'none' %}
|
||||||
|
"includes": ["!.claude", "!.svelte-kit", "!build", "!.mcp.json"]
|
||||||
|
{%- else %}
|
||||||
"includes": [
|
"includes": [
|
||||||
"!.claude",
|
"!.claude",
|
||||||
"!.svelte-kit",
|
"!.svelte-kit",
|
||||||
"!build",
|
"!build",
|
||||||
"!.mcp.json",
|
"!.mcp.json",
|
||||||
"!.impeccable"
|
"!src/lib/components/ui"{% if ui_library == 'claddcn' %},
|
||||||
|
"!src/routes/claddcn.css"{% endif %}
|
||||||
]
|
]
|
||||||
|
{%- endif %}
|
||||||
},
|
},
|
||||||
"javascript": { "globals": ["Bun"] },
|
"javascript": { "globals": ["Bun"] },
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{%- if ui_library == 'shadcn' %}
|
{%- if ui_library != 'none' %}
|
||||||
{
|
|
||||||
"includes": ["src/lib/components/ui/**"],
|
|
||||||
"linter": { "enabled": false },
|
|
||||||
"assist": { "enabled": false }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"includes": ["src/lib/utils.ts"],
|
"includes": ["src/lib/utils.ts"],
|
||||||
"linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } }
|
"linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } }
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"includes": ["**/*.svelte"],
|
"includes": ["**/*.svelte"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"a11y": { "useAnchorContent": "off" },
|
||||||
"style": {
|
"style": {
|
||||||
"useFilenamingConvention": {
|
"useFilenamingConvention": {
|
||||||
"level": "error",
|
"level": "error",
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Package managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
|
bun.lock
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
# Written by copier and the infra template, not by hand
|
||||||
|
.copier/
|
||||||
|
*.md
|
||||||
|
renovate.jsonc
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/static/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
|
||||||
|
"tailwindStylesheet": "./src/routes/layout.css"
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { includeIgnoreFile } from "@eslint/compat";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import prettier from "eslint-config-prettier";
|
||||||
|
import svelte from "eslint-plugin-svelte";
|
||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
import globals from "globals";
|
||||||
|
import ts from "typescript-eslint";
|
||||||
|
|
||||||
|
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
|
||||||
|
|
||||||
|
{% if frontend_adapter == 'bun' -%}
|
||||||
|
const svelteConfig = {
|
||||||
|
compilerOptions: {
|
||||||
|
runes: ({ filename }) =>
|
||||||
|
filename.split(/[/\\]/).includes("node_modules") ? undefined : true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
{%- else -%}
|
||||||
|
const svelteConfig = {};
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
includeIgnoreFile(gitignorePath),
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs.recommended,
|
||||||
|
prettier,
|
||||||
|
...svelte.configs.prettier,
|
||||||
|
{
|
||||||
|
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||||
|
rules: {
|
||||||
|
// typescript-eslint recommends against no-undef on TypeScript projects:
|
||||||
|
// https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||||
|
"no-undef": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{%- if ui_library != 'none' %}
|
||||||
|
{
|
||||||
|
// shadcn-svelte primitives are copied in and changed upstream only.
|
||||||
|
files: ["src/lib/components/ui/**"{% if ui_library == 'shadcn' %}, "src/lib/utils.ts"{% endif %}],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"svelte/no-navigation-without-resolve": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{%- endif %}
|
||||||
|
{
|
||||||
|
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
extraFileExtensions: [".svelte"],
|
||||||
|
parser: ts.parser,
|
||||||
|
projectService: true,
|
||||||
|
svelteConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,24 +1,48 @@
|
|||||||
{% if frontend_adapter == 'bun' -%}
|
{% if frontend_adapter == 'bun' -%}
|
||||||
FROM oven/bun:alpine
|
FROM oven/bun:1-alpine AS deps
|
||||||
|
|
||||||
ENV TERM=xterm-256color
|
ENV TERM=xterm-256color
|
||||||
ENV COLORTERM=truecolor
|
ENV COLORTERM=truecolor
|
||||||
|
ENV BUN_INSTALL_CACHE_DIR=/bun-cache
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json bun.lock* ./
|
COPY package.json bun.lock* ./
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
RUN --mount=type=cache,target=/bun-cache bun install --frozen-lockfile
|
||||||
|
|
||||||
|
|
||||||
|
# .env is in the context ($env/static/* are inlined at build) and stays in
|
||||||
|
# this stage: the runtime image below gets build/ and production deps only.
|
||||||
|
FROM deps AS builder
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN bun --bun svelte-kit sync
|
RUN bun --bun svelte-kit sync
|
||||||
RUN bun --bun run build
|
RUN bun --bun run build
|
||||||
|
|
||||||
|
|
||||||
|
FROM oven/bun:1-alpine AS runtime
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
# The cache mount only holds files; without the variable bun caches into
|
||||||
|
# ~/.bun inside the image.
|
||||||
|
ENV BUN_INSTALL_CACHE_DIR=/bun-cache
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json bun.lock* ./
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/bun-cache bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
ENTRYPOINT ["bun"]
|
ENTRYPOINT ["bun"]
|
||||||
CMD ["build/index.js"]
|
CMD ["build/index.js"]
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
FROM oven/bun:alpine AS deps
|
FROM oven/bun:1-alpine AS deps
|
||||||
|
|
||||||
ENV TERM=xterm-256color
|
ENV TERM=xterm-256color
|
||||||
ENV COLORTERM=truecolor
|
ENV COLORTERM=truecolor
|
||||||
@@ -39,7 +63,7 @@ RUN bun --bun svelte-kit sync
|
|||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
FROM oven/bun:alpine AS runtime
|
FROM oven/bun:1-alpine AS runtime
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -5,11 +5,11 @@
|
|||||||
"baseColor": "neutral"
|
"baseColor": "neutral"
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "$lib/components",
|
"components": "#lib/components",
|
||||||
"utils": "$lib/utils",
|
"utils": "#lib/utils",
|
||||||
"ui": "$lib/components/ui",
|
"ui": "#lib/components/ui",
|
||||||
"hooks": "$lib/hooks",
|
"hooks": "#lib/hooks",
|
||||||
"lib": "$lib"
|
"lib": "#lib"
|
||||||
},
|
},
|
||||||
"typescript": true,
|
"typescript": true,
|
||||||
"registry": "https://shadcn-svelte.com/registry",
|
"registry": "https://shadcn-svelte.com/registry",
|
||||||
Reference in new issue
Block a user