Initial template
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available Svelte MCP Tools:
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns issues and suggestions.
|
||||
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||
|
||||
### 4. playground-link
|
||||
|
||||
Generates a Svelte Playground link with the provided code.
|
||||
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Ultracite Code Standards
|
||||
|
||||
This project uses **Ultracite**, a zero-config preset that enforces strict code quality standards through automated formatting and linting.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- **Format code**: `bun x ultracite fix`
|
||||
- **Check for issues**: `bun x ultracite check`
|
||||
- **Diagnose setup**: `bun x ultracite doctor`
|
||||
|
||||
Biome (the underlying engine) provides robust linting and formatting. Most issues are automatically fixable.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity.
|
||||
|
||||
### Type Safety & Explicitness
|
||||
|
||||
- Use explicit types for function parameters and return values when they enhance clarity
|
||||
- Prefer `unknown` over `any` when the type is genuinely unknown
|
||||
- Use const assertions (`as const`) for immutable values and literal types
|
||||
- Leverage TypeScript's type narrowing instead of type assertions
|
||||
- Use meaningful variable names instead of magic numbers - extract constants with descriptive names
|
||||
|
||||
### Modern JavaScript/TypeScript
|
||||
|
||||
- Use arrow functions for callbacks and short functions
|
||||
- Prefer `for...of` loops over `.forEach()` and indexed `for` loops
|
||||
- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access
|
||||
- Prefer template literals over string concatenation
|
||||
- Use destructuring for object and array assignments
|
||||
- Use `const` by default, `let` only when reassignment is needed, never `var`
|
||||
|
||||
### Async & Promises
|
||||
|
||||
- Always `await` promises in async functions - don't forget to use the return value
|
||||
- Use `async/await` syntax instead of promise chains for better readability
|
||||
- Handle errors appropriately in async code with try-catch blocks
|
||||
- Don't use async functions as Promise executors
|
||||
|
||||
### React & JSX
|
||||
|
||||
- Use function components over class components
|
||||
- Call hooks at the top level only, never conditionally
|
||||
- Specify all dependencies in hook dependency arrays correctly
|
||||
- Use the `key` prop for elements in iterables (prefer unique IDs over array indices)
|
||||
- Nest children between opening and closing tags instead of passing as props
|
||||
- Don't define components inside other components
|
||||
- Use semantic HTML and ARIA attributes for accessibility:
|
||||
- Provide meaningful alt text for images
|
||||
- Use proper heading hierarchy
|
||||
- Add labels for form inputs
|
||||
- Include keyboard event handlers alongside mouse events
|
||||
- Use semantic elements (`<button>`, `<nav>`, etc.) instead of divs with roles
|
||||
|
||||
### Error Handling & Debugging
|
||||
|
||||
- Remove `console.log`, `debugger`, and `alert` statements from production code
|
||||
- Throw `Error` objects with descriptive messages, not strings or other values
|
||||
- Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them
|
||||
- Prefer early returns over nested conditionals for error cases
|
||||
|
||||
### Code Organization
|
||||
|
||||
- Keep functions focused and under reasonable cognitive complexity limits
|
||||
- Extract complex conditions into well-named boolean variables
|
||||
- Use early returns to reduce nesting
|
||||
- Prefer simple conditionals over nested ternary operators
|
||||
- Group related code together and separate concerns
|
||||
|
||||
### Security
|
||||
|
||||
- Add `rel="noopener"` when using `target="_blank"` on links
|
||||
- Avoid `dangerouslySetInnerHTML` unless absolutely necessary
|
||||
- Don't use `eval()` or assign directly to `document.cookie`
|
||||
- Validate and sanitize user input
|
||||
|
||||
### Performance
|
||||
|
||||
- Avoid spread syntax in accumulators within loops
|
||||
- Use top-level regex literals instead of creating them in loops
|
||||
- Prefer specific imports over namespace imports
|
||||
- Avoid barrel files (index files that re-export everything)
|
||||
- Use proper image components (e.g., Next.js `<Image>`) over `<img>` tags
|
||||
|
||||
### Framework-Specific Guidance
|
||||
|
||||
**Next.js:**
|
||||
- Use Next.js `<Image>` component for images
|
||||
- Use `next/head` or App Router metadata API for head elements
|
||||
- Use Server Components for async data fetching instead of async Client Components
|
||||
|
||||
**React 19+:**
|
||||
- Use ref as a prop instead of `React.forwardRef`
|
||||
|
||||
**Solid/Svelte/Vue/Qwik:**
|
||||
- Use `class` and `for` attributes (not `className` or `htmlFor`)
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
- Write assertions inside `it()` or `test()` blocks
|
||||
- Avoid done callbacks in async tests - use async/await instead
|
||||
- Don't use `.only` or `.skip` in committed code
|
||||
- Keep test suites reasonably flat - avoid excessive `describe` nesting
|
||||
|
||||
## When Biome Can't Help
|
||||
|
||||
Biome's linter will catch most issues automatically. Focus your attention on:
|
||||
|
||||
1. **Business logic correctness** - Biome can't validate your algorithms
|
||||
2. **Meaningful naming** - Use descriptive names for functions, variables, and types
|
||||
3. **Architecture decisions** - Component structure, data flow, and API design
|
||||
4. **Edge cases** - Handle boundary conditions and error states
|
||||
5. **User experience** - Accessibility, performance, and usability considerations
|
||||
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
|
||||
|
||||
---
|
||||
|
||||
Most formatting and common issues are automatically fixed by Biome. Run `bun x ultracite fix` before committing to ensure compliance.
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "cd frontend && bun run fix --skip=correctness/noUnusedImports",
|
||||
"type": "command"
|
||||
}
|
||||
],
|
||||
"matcher": "Write|Edit"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.svelte-kit
|
||||
build
|
||||
.env
|
||||
.env.*
|
||||
@@ -0,0 +1 @@
|
||||
PUBLIC_API_BASE_URL=/api
|
||||
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Impeccable
|
||||
**/.impeccable/config.local.json
|
||||
**/.impeccable/hook.cache.json
|
||||
**/.impeccable/hook.pending.json
|
||||
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
@@ -0,0 +1,6 @@
|
||||
## Checking commands
|
||||
After writing code, always run from `frontend/`:
|
||||
```shell
|
||||
bun fix
|
||||
bun check
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
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
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"extends": ["ultracite/biome/core", "ultracite/biome/svelte"],
|
||||
"files": {
|
||||
"includes": [
|
||||
"!.claude",
|
||||
"!.svelte-kit",
|
||||
"!build",
|
||||
"!.mcp.json",
|
||||
"!.impeccable"
|
||||
]
|
||||
},
|
||||
"javascript": { "globals": ["Bun"] },
|
||||
"overrides": [
|
||||
{%- if ui_library == 'shadcn' %}
|
||||
{
|
||||
"includes": ["src/lib/components/ui/**"],
|
||||
"linter": { "enabled": false },
|
||||
"assist": { "enabled": false }
|
||||
},
|
||||
{
|
||||
"includes": ["src/lib/utils.ts"],
|
||||
"linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } }
|
||||
},
|
||||
{%- endif %}
|
||||
{
|
||||
"includes": ["**/*.svelte"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useFilenamingConvention": {
|
||||
"level": "error",
|
||||
"options": { "filenameCases": ["kebab-case", "PascalCase"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["src/app.d.ts"],
|
||||
"linter": { "rules": { "style": { "noNamespace": "off" } } }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
{% if serve != 'api' %} frontend:
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
|
||||
{% endif %} frontend-dev:
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
@@ -0,0 +1,15 @@
|
||||
{%- set dash = project_slug | replace('_', '-') -%}
|
||||
{% if serve != 'api' -%}
|
||||
services:
|
||||
frontend:
|
||||
networks:
|
||||
caddy:
|
||||
aliases:
|
||||
- {{ dash }}-frontend
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
{%- else -%}
|
||||
services: {}
|
||||
{%- endif %}
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
{% if serve != 'api' %} frontend:
|
||||
build: frontend
|
||||
image: {{ project_slug }}/frontend
|
||||
profiles: [frontend, services]
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
- path: frontend/.env
|
||||
required: false
|
||||
command: {% if serve == 'bun' %}[build/index.js]{% else %}[run, serve.ts]{% endif %}
|
||||
|
||||
{% endif %} frontend-dev:
|
||||
image: oven/bun:alpine
|
||||
profiles: [frontend-dev]
|
||||
restart: unless-stopped
|
||||
working_dir: /app
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
- path: frontend/.env
|
||||
required: false
|
||||
environment:
|
||||
API_PROXY_TARGET: http://api:8080
|
||||
volumes:
|
||||
- ./frontend:/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"]
|
||||
|
||||
volumes:
|
||||
frontend_node_modules:
|
||||
frontend_svelte_kit:
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "{{ project_slug }}",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "ultracite check",
|
||||
"fix": "ultracite fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.5.2",
|
||||
"@sveltejs/kit": "^2.63.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@types/bun": "^1.3.0",
|
||||
{% if frontend_adapter == 'bun' %} "svelte-adapter-bun": "^1.0.1",
|
||||
{% else %} "@sveltejs/adapter-static": "^3.0.10",
|
||||
{% endif %}{% if ui_library == 'shadcn' %} "@internationalized/date": "^3.12.0",
|
||||
"@lucide/svelte": "^1.23.0",
|
||||
"bits-ui": "^2.16.3",
|
||||
"clsx": "^2.1.1",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"shadcn-svelte": "^1.3.0",
|
||||
"svelte-sonner": "^1.1.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwind-variants": "^3.2.2",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
{% endif %} "svelte": "^5.56.1",
|
||||
"svelte-check": "^4.6.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"typescript": "^6.0.3",
|
||||
"ultracite": "7.9.0",
|
||||
"vite": "^8.0.16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<meta content="scale" name="text-scale">
|
||||
<link href="%sveltekit.assets%/favicon.svg" rel="icon" type="image/svg+xml">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
const BASE = env.PUBLIC_API_BASE_URL ?? "/api";
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
type Json = Record<string, unknown> | unknown[];
|
||||
|
||||
async function handle<T>(res: Response): Promise<T> {
|
||||
if (res.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
if (res.ok) {
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
let detail = res.statusText;
|
||||
try {
|
||||
const { detail: parsed } = (await res.json()) as { detail?: string };
|
||||
if (parsed) {
|
||||
detail = parsed;
|
||||
}
|
||||
} catch {
|
||||
detail = res.statusText;
|
||||
}
|
||||
throw new ApiError(res.status, detail);
|
||||
}
|
||||
|
||||
async function request<T>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: Json
|
||||
): Promise<T> {
|
||||
const init: RequestInit = { credentials: "include", method };
|
||||
if (body !== undefined) {
|
||||
init.headers = { "Content-Type": "application/json" };
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
const res = await fetch(BASE + path, init);
|
||||
return handle<T>(res);
|
||||
}
|
||||
|
||||
export const api = {
|
||||
del: <T>(path: string) => request<T>("DELETE", path),
|
||||
get: <T>(path: string) => request<T>("GET", path),
|
||||
patch: <T>(path: string, body?: Json) => request<T>("PATCH", path, body),
|
||||
post: <T>(path: string, body?: Json) => request<T>("POST", path, body),
|
||||
put: <T>(path: string, body?: Json) => request<T>("PUT", path, body),
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
// biome-ignore-all lint/suspicious/noEmptySource: temporary
|
||||
@@ -0,0 +1,15 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||
export type WithoutChildren<T> = T extends { children?: any }
|
||||
? Omit<T, "children">
|
||||
: T;
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
||||
ref?: U | null;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import "./layout.css";
|
||||
{%- if ui_library == 'shadcn' %}
|
||||
import { ModeWatcher } from "mode-watcher";
|
||||
{%- endif %}
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
{% if ui_library == 'shadcn' %}<ModeWatcher />
|
||||
|
||||
{% endif %}{@render children()}
|
||||
@@ -0,0 +1,13 @@
|
||||
<svelte:head>
|
||||
<title>{{ project_name }}</title>
|
||||
</svelte:head>
|
||||
|
||||
<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 %}"
|
||||
>
|
||||
<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 %}">
|
||||
Edit
|
||||
<code class="font-mono">src/routes/+page.svelte</code>.
|
||||
</p>
|
||||
</main>
|
||||
@@ -0,0 +1,129 @@
|
||||
{% if ui_library == 'shadcn' -%}
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn-svelte/tailwind.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.922 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.205 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
{%- else -%}
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans:
|
||||
system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[role="button"]:not(:disabled),
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
{%- endif %}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const ssr = false;
|
||||
export const prerender = false;
|
||||
@@ -0,0 +1,36 @@
|
||||
{% 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 %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
changeOrigin: true,
|
||||
target: process.env.API_PROXY_TARGET ?? "http://localhost:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
{% if frontend_adapter == 'bun' -%}
|
||||
FROM oven/bun:alpine
|
||||
|
||||
ENV TERM=xterm-256color
|
||||
ENV COLORTERM=truecolor
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock* ./
|
||||
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun --bun svelte-kit sync
|
||||
RUN bun --bun run build
|
||||
|
||||
ENTRYPOINT ["bun"]
|
||||
CMD ["build/index.js"]
|
||||
{%- else -%}
|
||||
FROM oven/bun:alpine AS deps
|
||||
|
||||
ENV TERM=xterm-256color
|
||||
ENV COLORTERM=truecolor
|
||||
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
|
||||
|
||||
|
||||
FROM deps AS builder
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun --bun svelte-kit sync
|
||||
RUN bun run build
|
||||
|
||||
|
||||
FROM oven/bun:alpine AS runtime
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/build ./build
|
||||
COPY --from=builder /app/serve.ts ./serve.ts
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["bun"]
|
||||
CMD ["run", "serve.ts"]
|
||||
{%- endif %}
|
||||
@@ -0,0 +1,36 @@
|
||||
const ROOT = `${import.meta.dir}/build`;
|
||||
const PORT = Number(Bun.env.PORT ?? 3000);
|
||||
const IMMUTABLE = "/_app/immutable/";
|
||||
|
||||
const fallback = Bun.file(`${ROOT}/index.html`);
|
||||
|
||||
function headersFor(pathname: string): HeadersInit {
|
||||
if (pathname.startsWith(IMMUTABLE)) {
|
||||
return { "Cache-Control": "public, max-age=31536000, immutable" };
|
||||
}
|
||||
return { "Cache-Control": "no-cache" };
|
||||
}
|
||||
|
||||
Bun.serve({
|
||||
async fetch(request) {
|
||||
const { pathname } = new URL(request.url);
|
||||
|
||||
if (pathname.includes("..")) {
|
||||
return new Response("Bad Request", { status: 400 });
|
||||
}
|
||||
|
||||
const file = Bun.file(`${ROOT}${pathname}`);
|
||||
if (await file.exists()) {
|
||||
return new Response(file, { headers: headersFor(pathname) });
|
||||
}
|
||||
|
||||
return new Response(fallback, {
|
||||
headers: {
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
},
|
||||
});
|
||||
},
|
||||
idleTimeout: 30,
|
||||
port: PORT,
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"tailwind": {
|
||||
"css": "src/routes/layout.css",
|
||||
"baseColor": "neutral"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry",
|
||||
"style": "luma",
|
||||
"iconLibrary": "lucide",
|
||||
"menuColor": "default-translucent",
|
||||
"menuAccent": "subtle"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
config.local.json
|
||||
hook.cache.json
|
||||
hook.pending.json
|
||||
@@ -0,0 +1 @@
|
||||
{{ _copier_answers|to_nice_yaml -}}
|
||||
Reference in New Issue
Block a user