create-mikstack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +54 -0
  2. package/dist/index.js +410 -0
  3. package/package.json +43 -0
  4. package/templates/adapters/cloudflare/package.json.partial +5 -0
  5. package/templates/adapters/cloudflare/svelte.config.js +19 -0
  6. package/templates/adapters/node/Dockerfile +30 -0
  7. package/templates/adapters/node/docker-compose.prod.yml +27 -0
  8. package/templates/adapters/node/package.json.partial +5 -0
  9. package/templates/adapters/node/svelte.config.js +19 -0
  10. package/templates/adapters/vercel/package.json.partial +5 -0
  11. package/templates/adapters/vercel/svelte.config.js +19 -0
  12. package/templates/base/.env.example +23 -0
  13. package/templates/base/.gitignore.append +2 -0
  14. package/templates/base/.mcp.json +9 -0
  15. package/templates/base/.prettierignore +10 -0
  16. package/templates/base/.vscode/extensions.json +3 -0
  17. package/templates/base/AGENTS.md +123 -0
  18. package/templates/base/README.md +27 -0
  19. package/templates/base/agents.md +28 -0
  20. package/templates/base/docker-compose.yml +15 -0
  21. package/templates/base/drizzle-zero.config.ts +17 -0
  22. package/templates/base/drizzle.config.ts +17 -0
  23. package/templates/base/eslint.config.ts +65 -0
  24. package/templates/base/package.json.partial +43 -0
  25. package/templates/base/prettier.config.js +6 -0
  26. package/templates/base/src/app.d.ts +12 -0
  27. package/templates/base/src/app.html +11 -0
  28. package/templates/base/src/hooks.server.ts +15 -0
  29. package/templates/base/src/lib/auth-client.ts +6 -0
  30. package/templates/base/src/lib/server/auth.ts +52 -0
  31. package/templates/base/src/lib/server/db/index.ts +19 -0
  32. package/templates/base/src/lib/server/db/schema.ts +117 -0
  33. package/templates/base/src/lib/server/db/seed.ts +21 -0
  34. package/templates/base/src/lib/server/emails/magic-link.ts +77 -0
  35. package/templates/base/src/lib/server/emails/send.ts +55 -0
  36. package/templates/base/src/lib/server/notifications/definitions.ts +12 -0
  37. package/templates/base/src/lib/server/notifications.ts +38 -0
  38. package/templates/base/src/lib/z.svelte.ts +14 -0
  39. package/templates/base/src/lib/zero/context.ts +9 -0
  40. package/templates/base/src/lib/zero/db-provider.server.ts +11 -0
  41. package/templates/base/src/lib/zero/mutators.ts +35 -0
  42. package/templates/base/src/lib/zero/queries.ts +21 -0
  43. package/templates/base/src/lib/zero/schema.ts +1 -0
  44. package/templates/base/src/routes/+layout.server.ts +5 -0
  45. package/templates/base/src/routes/+layout.svelte +7 -0
  46. package/templates/base/src/routes/+page.server.ts +7 -0
  47. package/templates/base/src/routes/+page.svelte +319 -0
  48. package/templates/base/src/routes/api/dev/emails/+server.ts +89 -0
  49. package/templates/base/src/routes/api/dev/emails/[id]/+server.ts +24 -0
  50. package/templates/base/src/routes/api/notifications/[...path]/+server.ts +10 -0
  51. package/templates/base/src/routes/api/zero/get-queries/+server.ts +29 -0
  52. package/templates/base/src/routes/api/zero/mutate/+server.ts +31 -0
  53. package/templates/base/src/routes/sign-in/+page.svelte +97 -0
  54. package/templates/base/tsconfig.json +40 -0
  55. package/templates/github-actions-bun/.github/workflows/ci.yml +22 -0
  56. package/templates/github-actions-npm/.github/workflows/ci.yml +25 -0
  57. package/templates/github-actions-pnpm/.github/workflows/ci.yml +27 -0
  58. package/templates/i18n/lingui.config.ts +16 -0
  59. package/templates/i18n/package.json.partial +14 -0
  60. package/templates/i18n/src/lib/i18n.ts +10 -0
  61. package/templates/i18n/src/locales/en.po +6 -0
  62. package/templates/i18n/src/po.d.ts +3 -0
  63. package/templates/i18n/vite.config.ts +7 -0
  64. package/templates/supply-chain-bun/bunfig.toml +3 -0
  65. package/templates/testing/package.json.partial +11 -0
  66. package/templates/testing/src/example.test.ts +7 -0
  67. package/templates/testing/src/lib/server/db/test-utils.ts +25 -0
  68. package/templates/testing/vitest.config.ts +9 -0
  69. package/templates/ui/.vscode/extensions.json +8 -0
  70. package/templates/ui/package.json.partial +13 -0
  71. package/templates/ui/src/app.css +94 -0
  72. package/templates/ui/src/routes/+layout.svelte +12 -0
  73. package/templates/ui/stylelint.config.js +7 -0
  74. package/templates/ui/vite.config.ts +6 -0
  75. package/templates/ui-dependency/package.json.partial +5 -0
  76. package/templates/ui-vendor/src/lib/components/ui/Accordion/Accordion.svelte +71 -0
  77. package/templates/ui-vendor/src/lib/components/ui/Accordion/index.ts +1 -0
  78. package/templates/ui-vendor/src/lib/components/ui/Alert/Alert.svelte +60 -0
  79. package/templates/ui-vendor/src/lib/components/ui/Alert/index.ts +1 -0
  80. package/templates/ui-vendor/src/lib/components/ui/Badge/Badge.svelte +48 -0
  81. package/templates/ui-vendor/src/lib/components/ui/Badge/index.ts +1 -0
  82. package/templates/ui-vendor/src/lib/components/ui/Button/Button.svelte +77 -0
  83. package/templates/ui-vendor/src/lib/components/ui/Button/index.ts +1 -0
  84. package/templates/ui-vendor/src/lib/components/ui/Card/Card.svelte +49 -0
  85. package/templates/ui-vendor/src/lib/components/ui/Card/index.ts +1 -0
  86. package/templates/ui-vendor/src/lib/components/ui/Dialog/Dialog.svelte +70 -0
  87. package/templates/ui-vendor/src/lib/components/ui/Dialog/index.ts +1 -0
  88. package/templates/ui-vendor/src/lib/components/ui/FormField/FormField.svelte +53 -0
  89. package/templates/ui-vendor/src/lib/components/ui/FormField/index.ts +1 -0
  90. package/templates/ui-vendor/src/lib/components/ui/Input/Input.svelte +27 -0
  91. package/templates/ui-vendor/src/lib/components/ui/Input/index.ts +1 -0
  92. package/templates/ui-vendor/src/lib/components/ui/Separator/Separator.svelte +26 -0
  93. package/templates/ui-vendor/src/lib/components/ui/Separator/index.ts +1 -0
  94. package/templates/ui-vendor/src/lib/components/ui/Skeleton/Skeleton.svelte +40 -0
  95. package/templates/ui-vendor/src/lib/components/ui/Skeleton/index.ts +1 -0
  96. package/templates/ui-vendor/src/lib/components/ui/Switch/Switch.svelte +86 -0
  97. package/templates/ui-vendor/src/lib/components/ui/Switch/index.ts +1 -0
  98. package/templates/ui-vendor/src/lib/components/ui/Textarea/Textarea.svelte +29 -0
  99. package/templates/ui-vendor/src/lib/components/ui/Textarea/index.ts +1 -0
@@ -0,0 +1,89 @@
1
+ import { dev } from "$app/environment";
2
+ import { error } from "@sveltejs/kit";
3
+ import { desc, eq } from "drizzle-orm";
4
+ import { db } from "$lib/server/db";
5
+ import { notificationDelivery } from "$lib/server/db/schema";
6
+ import type { RequestHandler } from "./$types";
7
+
8
+ export const GET: RequestHandler = async () => {
9
+ if (!dev) error(404);
10
+
11
+ const deliveries = await db
12
+ .select({
13
+ id: notificationDelivery.id,
14
+ type: notificationDelivery.type,
15
+ channel: notificationDelivery.channel,
16
+ recipientEmail: notificationDelivery.recipientEmail,
17
+ status: notificationDelivery.status,
18
+ error: notificationDelivery.error,
19
+ createdAt: notificationDelivery.createdAt,
20
+ })
21
+ .from(notificationDelivery)
22
+ .where(eq(notificationDelivery.channel, "email"))
23
+ .orderBy(desc(notificationDelivery.createdAt))
24
+ .limit(50);
25
+
26
+ const rows = deliveries
27
+ .map(
28
+ (d) => `
29
+ <tr>
30
+ <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb">
31
+ <a href="/api/dev/emails/${d.id}" target="_blank" style="color:#2563eb">${esc(d.type)}</a>
32
+ </td>
33
+ <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb">${esc(d.recipientEmail ?? "—")}</td>
34
+ <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb">
35
+ <span style="padding:2px 8px;border-radius:9999px;font-size:12px;background:${statusColor(d.status)}">${esc(d.status)}</span>
36
+ ${d.error ? `<span style="color:#ef4444;font-size:12px"> ${esc(d.error)}</span>` : ""}
37
+ </td>
38
+ <td style="padding:8px 12px;border-bottom:1px solid #e5e7eb;color:#6b7280;font-size:13px">${d.createdAt.toLocaleString()}</td>
39
+ </tr>`,
40
+ )
41
+ .join("");
42
+
43
+ const html = `<!DOCTYPE html>
44
+ <html>
45
+ <head><meta charset="utf-8"><title>Dev Emails</title></head>
46
+ <body style="font-family:system-ui,sans-serif;margin:0;padding:24px;background:#f9fafb">
47
+ <h1 style="font-size:20px;margin:0 0 16px">Dev Email Log</h1>
48
+ ${
49
+ deliveries.length === 0
50
+ ? '<p style="color:#6b7280">No emails sent yet.</p>'
51
+ : `<table style="width:100%;border-collapse:collapse;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.1)">
52
+ <thead>
53
+ <tr style="background:#f3f4f6;text-align:left">
54
+ <th style="padding:8px 12px;font-weight:600">Type</th>
55
+ <th style="padding:8px 12px;font-weight:600">To</th>
56
+ <th style="padding:8px 12px;font-weight:600">Status</th>
57
+ <th style="padding:8px 12px;font-weight:600">Sent</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>${rows}</tbody>
61
+ </table>`
62
+ }
63
+ </body>
64
+ </html>`;
65
+
66
+ return new Response(html, {
67
+ headers: { "content-type": "text/html; charset=utf-8" },
68
+ });
69
+ };
70
+
71
+ function esc(s: string): string {
72
+ return s
73
+ .replace(/&/g, "&amp;")
74
+ .replace(/</g, "&lt;")
75
+ .replace(/>/g, "&gt;")
76
+ .replace(/"/g, "&quot;");
77
+ }
78
+
79
+ function statusColor(status: string): string {
80
+ switch (status) {
81
+ case "sent":
82
+ case "delivered":
83
+ return "#dcfce7";
84
+ case "failed":
85
+ return "#fee2e2";
86
+ default:
87
+ return "#fef3c7";
88
+ }
89
+ }
@@ -0,0 +1,24 @@
1
+ import { dev } from "$app/environment";
2
+ import { error } from "@sveltejs/kit";
3
+ import { eq } from "drizzle-orm";
4
+ import { db } from "$lib/server/db";
5
+ import { notificationDelivery } from "$lib/server/db/schema";
6
+ import type { RequestHandler } from "./$types";
7
+
8
+ export const GET: RequestHandler = async ({ params }) => {
9
+ if (!dev) error(404);
10
+
11
+ const [delivery] = await db
12
+ .select({ content: notificationDelivery.content })
13
+ .from(notificationDelivery)
14
+ .where(eq(notificationDelivery.id, params.id));
15
+
16
+ if (!delivery?.content) error(404);
17
+
18
+ const content = delivery.content as { html?: string };
19
+ if (!content.html) error(404, "No HTML content for this delivery");
20
+
21
+ return new Response(content.html, {
22
+ headers: { "content-type": "text/html; charset=utf-8" },
23
+ });
24
+ };
@@ -0,0 +1,10 @@
1
+ import { notif } from "$lib/server/notifications";
2
+ import type { RequestHandler } from "./$types";
3
+
4
+ const handle: RequestHandler = async ({ request, locals }) => {
5
+ return notif.handler(request, locals.user?.id ?? null);
6
+ };
7
+
8
+ export const GET = handle;
9
+ export const POST = handle;
10
+ export const PUT = handle;
@@ -0,0 +1,29 @@
1
+ import type { RequestHandler } from "./$types";
2
+ import { handleQueryRequest } from "@rocicorp/zero/server";
3
+ import { mustGetQuery } from "@rocicorp/zero";
4
+ import { schema } from "$lib/zero/schema";
5
+ import { queries } from "$lib/zero/queries";
6
+
7
+ export const POST: RequestHandler = async ({ request, locals }) => {
8
+ if (!locals.user) {
9
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
10
+ }
11
+
12
+ try {
13
+ const ctx = { userID: locals.user.id };
14
+
15
+ const response = await handleQueryRequest(
16
+ (name, args) => {
17
+ const query = mustGetQuery(queries, name);
18
+ return query.fn({ args, ctx });
19
+ },
20
+ schema,
21
+ request,
22
+ );
23
+
24
+ return Response.json(response);
25
+ } catch (error) {
26
+ console.error("Query error:", error);
27
+ return Response.json({ error: "Internal server error" }, { status: 500 });
28
+ }
29
+ };
@@ -0,0 +1,31 @@
1
+ import type { RequestHandler } from "./$types";
2
+ import { handleMutateRequest } from "@rocicorp/zero/server";
3
+ import { mustGetMutator } from "@rocicorp/zero";
4
+ import { dbProvider } from "$lib/zero/db-provider.server";
5
+ import { mutators } from "$lib/zero/mutators";
6
+
7
+ export const POST: RequestHandler = async ({ request, locals }) => {
8
+ if (!locals.user) {
9
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
10
+ }
11
+
12
+ try {
13
+ const ctx = { userID: locals.user.id };
14
+
15
+ const result = await handleMutateRequest(
16
+ dbProvider,
17
+ async (transact) => {
18
+ return await transact(async (tx, name, args) => {
19
+ const mutator = mustGetMutator(mutators, name);
20
+ return await mutator.fn({ tx, ctx, args });
21
+ });
22
+ },
23
+ request,
24
+ );
25
+
26
+ return Response.json(result);
27
+ } catch (error) {
28
+ console.error("Mutate error:", error);
29
+ return Response.json({ error: "Internal server error" }, { status: 500 });
30
+ }
31
+ };
@@ -0,0 +1,97 @@
1
+ <script lang="ts">
2
+ import EnvelopeSimple from "phosphor-svelte/lib/EnvelopeSimple";
3
+ import CheckCircle from "phosphor-svelte/lib/CheckCircle";
4
+ import Button from "{{uiPrefix}}/Button";
5
+ import FormField from "{{uiPrefix}}/FormField";
6
+ import Input from "{{uiPrefix}}/Input";
7
+ import { createForm } from "@mikstack/form";
8
+ import * as v from "valibot";
9
+ import { authClient } from "$lib/auth-client";
10
+
11
+ const form = createForm({
12
+ schema: v.object({
13
+ email: v.pipe(v.string(), v.email("Please enter a valid email address")),
14
+ }),
15
+ initialValues: { email: "" },
16
+ async onSubmit(data) {
17
+ const { error } = await authClient.signIn.magicLink({
18
+ email: data.email,
19
+ callbackURL: "/",
20
+ });
21
+ if (error) throw new Error(error.message ?? "Failed to send magic link");
22
+ return { sent: true };
23
+ },
24
+ });
25
+
26
+ const emailField = form.fields.email;
27
+ </script>
28
+
29
+ <div class="sign-in">
30
+ <h1>Sign in to {{projectName}}</h1>
31
+
32
+ {#if form.result}
33
+ <div class="success">
34
+ <CheckCircle size={24} weight="duotone" />
35
+ <p>Check your email for a magic link to sign in.</p>
36
+ </div>
37
+ {:else}
38
+ <form id={form.id} onsubmit={form.onsubmit} class="sign-in-form">
39
+ <FormField for={emailField.as("email").id}>
40
+ {#snippet label(attrs)}
41
+ <label {...attrs}>Email</label>
42
+ {/snippet}
43
+ <Input {...emailField.as("email")} placeholder="you@example.com" />
44
+ {#snippet error(attrs)}
45
+ {#each emailField.issues() as issue (issue.message)}
46
+ <p {...attrs}>{issue.message}</p>
47
+ {/each}
48
+ {/snippet}
49
+ </FormField>
50
+
51
+ {#if form.error}
52
+ <p class="form-error">{form.error}</p>
53
+ {/if}
54
+
55
+ <Button type="submit" disabled={form.pending}>
56
+ <EnvelopeSimple size={16} weight="bold" />
57
+ {form.pending ? "Sending magic link..." : "Sign in with magic link"}
58
+ </Button>
59
+ </form>
60
+ {/if}
61
+ </div>
62
+
63
+ <style>
64
+ .sign-in {
65
+ max-width: 24rem;
66
+ margin: 0 auto;
67
+ padding: var(--space-8) var(--space-4);
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--space-5);
71
+ }
72
+
73
+ h1 {
74
+ font-size: var(--text-2xl);
75
+ }
76
+
77
+ .sign-in-form {
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: var(--space-3);
81
+ }
82
+
83
+ .form-error {
84
+ font-size: var(--text-sm);
85
+ color: var(--danger);
86
+ }
87
+
88
+ .success {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: var(--space-2);
92
+ padding: var(--space-4);
93
+ border-radius: var(--radius-md);
94
+ background-color: var(--surface-2);
95
+ color: var(--text-1);
96
+ }
97
+ </style>
@@ -0,0 +1,40 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "rewriteRelativeImportExtensions": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "esModuleInterop": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "resolveJsonModule": true,
10
+ "skipLibCheck": true,
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "moduleResolution": "bundler",
14
+ "noUncheckedIndexedAccess": true
15
+ },
16
+ "include": [
17
+ ".svelte-kit/ambient.d.ts",
18
+ ".svelte-kit/non-ambient.d.ts",
19
+ ".svelte-kit/types/**/$types.d.ts",
20
+ "vite.config.js",
21
+ "vite.config.ts",
22
+ "src/**/*.js",
23
+ "src/**/*.ts",
24
+ "src/**/*.svelte",
25
+ "tests/**/*.js",
26
+ "tests/**/*.ts",
27
+ "tests/**/*.svelte",
28
+ "*.ts",
29
+ "*.js"
30
+ ],
31
+ "exclude": [
32
+ "node_modules/**",
33
+ "src/service-worker.js",
34
+ "src/service-worker/**/*.js",
35
+ "src/service-worker.ts",
36
+ "src/service-worker/**/*.ts",
37
+ "src/service-worker.d.ts",
38
+ "src/service-worker/**/*.d.ts"
39
+ ]
40
+ }
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - uses: oven-sh/setup-bun@v2
16
+
17
+ - run: bun install --frozen-lockfile
18
+
19
+ - run: bun run lint
20
+ - run: bun run format:check
21
+ - run: bun run check
22
+ - run: bun run build
@@ -0,0 +1,25 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - uses: actions/setup-node@v6
16
+ with:
17
+ node-version: 22
18
+ cache: npm
19
+
20
+ - run: npm ci
21
+
22
+ - run: npm run lint
23
+ - run: npm run format:check
24
+ - run: npm run check
25
+ - run: npm run build
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - uses: pnpm/action-setup@v4
16
+
17
+ - uses: actions/setup-node@v6
18
+ with:
19
+ node-version: 22
20
+ cache: pnpm
21
+
22
+ - run: pnpm install --frozen-lockfile
23
+
24
+ - run: pnpm run lint
25
+ - run: pnpm run format:check
26
+ - run: pnpm run check
27
+ - run: pnpm run build
@@ -0,0 +1,16 @@
1
+ import { extractor } from "@mikstack/svelte-lingui/extractor";
2
+ import type { LinguiConfig } from "@lingui/conf";
3
+
4
+ const config: LinguiConfig = {
5
+ locales: ["en"],
6
+ sourceLocale: "en",
7
+ catalogs: [
8
+ {
9
+ path: "src/locales/{locale}",
10
+ include: ["src"],
11
+ },
12
+ ],
13
+ extractors: [extractor],
14
+ };
15
+
16
+ export default config;
@@ -0,0 +1,14 @@
1
+ {
2
+ "scripts": {
3
+ "i18n:extract": "lingui extract",
4
+ "i18n:extract:watch": "lingui extract --watch"
5
+ },
6
+ "dependencies": {
7
+ "@lingui/core": "^5.9.0",
8
+ "@mikstack/svelte-lingui": "^0.1.0"
9
+ },
10
+ "devDependencies": {
11
+ "@lingui/cli": "^5.9.0",
12
+ "@lingui/conf": "^5.9.0"
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import { setupI18n } from "@lingui/core";
2
+ import { setI18n } from "@mikstack/svelte-lingui";
3
+ import { messages } from "../locales/en.po";
4
+
5
+ const i18n = setupI18n();
6
+
7
+ export function initI18n(): void {
8
+ i18n.loadAndActivate({ locale: "en", messages });
9
+ setI18n(i18n);
10
+ }
@@ -0,0 +1,6 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: en\n"
4
+ "MIME-Version: 1.0\n"
5
+ "Content-Type: text/plain; charset=UTF-8\n"
6
+ "Content-Transfer-Encoding: 8bit\n"
@@ -0,0 +1,3 @@
1
+ declare module "*.po" {
2
+ export const messages: Record<string, string>;
3
+ }
@@ -0,0 +1,7 @@
1
+ import { sveltekit } from "@sveltejs/kit/vite";
2
+ import { linguiPo } from "@mikstack/svelte-lingui/vite";
3
+ import { defineConfig } from "vite";
4
+
5
+ export default defineConfig({
6
+ plugins: [linguiPo(), sveltekit()],
7
+ });
@@ -0,0 +1,3 @@
1
+ [install]
2
+ # Reject packages published less than 3 days ago
3
+ minPublishAge = "3d"
@@ -0,0 +1,11 @@
1
+ {
2
+ "scripts": {
3
+ "test": "vitest run",
4
+ "test:watch": "vitest"
5
+ },
6
+ "devDependencies": {
7
+ "vitest": "^4.0.18",
8
+ "@vitest/ui": "^4.0.18",
9
+ "@testcontainers/postgresql": "^11.11.0"
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ describe("example", () => {
4
+ it("should work", () => {
5
+ expect(1 + 1).toBe(2);
6
+ });
7
+ });
@@ -0,0 +1,25 @@
1
+ import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
2
+ import { drizzle } from "drizzle-orm/postgres-js";
3
+ import postgres from "postgres";
4
+ import * as schema from "./schema";
5
+ import type { DrizzleDB } from "./index";
6
+
7
+ export type TestDatabase = {
8
+ container: StartedPostgreSqlContainer;
9
+ client: ReturnType<typeof postgres>;
10
+ db: DrizzleDB;
11
+ };
12
+
13
+ export async function createTestDatabase(): Promise<TestDatabase> {
14
+ const container = await new PostgreSqlContainer("postgres:17-alpine").start();
15
+ const connectionUri = container.getConnectionUri();
16
+ const client = postgres(connectionUri);
17
+ const db = drizzle(client, { schema, casing: "snake_case" });
18
+
19
+ return { container, client, db };
20
+ }
21
+
22
+ export async function stopTestDatabase(testDb: TestDatabase): Promise<void> {
23
+ await testDb.client.end();
24
+ await testDb.container.stop();
25
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import { sveltekit } from "@sveltejs/kit/vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [sveltekit()],
6
+ test: {
7
+ include: ["src/**/*.test.ts"],
8
+ },
9
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "recommendations": [
3
+ "svelte.svelte-vscode",
4
+ "dbaeumer.vscode-eslint",
5
+ "esbenp.prettier-vscode",
6
+ "stylelint.vscode-stylelint"
7
+ ]
8
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "scripts": {
3
+ "lint:css": "stylelint \"src/**/*.{css,svelte}\""
4
+ },
5
+ "dependencies": {
6
+ "phosphor-svelte": "^3.1.0",
7
+ "svelte-sonner": "^1.0.0"
8
+ },
9
+ "devDependencies": {
10
+ "stylelint": "^17.1.1",
11
+ "stylelint-config-standard": "^40.0.0"
12
+ }
13
+ }
@@ -0,0 +1,94 @@
1
+ /* Reset */
2
+ *,
3
+ *::before,
4
+ *::after {
5
+ box-sizing: border-box;
6
+ margin: 0;
7
+ }
8
+
9
+ body {
10
+ min-height: 100dvh;
11
+ line-height: 1.5;
12
+ -webkit-font-smoothing: antialiased;
13
+ }
14
+
15
+ img,
16
+ picture,
17
+ video,
18
+ canvas,
19
+ svg {
20
+ display: block;
21
+ max-width: 100%;
22
+ }
23
+
24
+ input,
25
+ button,
26
+ textarea,
27
+ select {
28
+ font: inherit;
29
+ color: inherit;
30
+ }
31
+
32
+ /* Design Tokens */
33
+ :root {
34
+ /* Typography */
35
+ font-family:
36
+ system-ui,
37
+ -apple-system,
38
+ "Segoe UI",
39
+ Roboto,
40
+ "Helvetica Neue",
41
+ Arial,
42
+ sans-serif;
43
+
44
+ --text-xs: 0.75rem;
45
+ --text-sm: 0.875rem;
46
+ --text-base: 1rem;
47
+ --text-lg: 1.125rem;
48
+ --text-xl: 1.25rem;
49
+ --text-2xl: 1.5rem;
50
+ --text-3xl: 1.875rem;
51
+
52
+ /* Spacing */
53
+ --space-1: 0.25rem;
54
+ --space-2: 0.5rem;
55
+ --space-3: 0.75rem;
56
+ --space-4: 1rem;
57
+ --space-5: 1.5rem;
58
+ --space-6: 2rem;
59
+ --space-7: 3rem;
60
+ --space-8: 4rem;
61
+
62
+ /* Border Radii */
63
+ --radius-sm: 0.25rem;
64
+ --radius-md: 0.5rem;
65
+ --radius-lg: 0.75rem;
66
+
67
+ /* Light Mode Colors (oklch) */
68
+ --surface-1: oklch(100% 0 0);
69
+ --surface-2: oklch(97% 0 0);
70
+ --surface-3: oklch(93% 0 0);
71
+ --text-1: oklch(20% 0 0);
72
+ --text-2: oklch(40% 0 0);
73
+ --accent: oklch(55% 0.2 260);
74
+ --danger: oklch(55% 0.2 25);
75
+ --border: oklch(85% 0 0);
76
+ --focus: oklch(55% 0.2 260 / 50%);
77
+
78
+ color: var(--text-1);
79
+ background-color: var(--surface-1);
80
+ }
81
+
82
+ @media (prefers-color-scheme: dark) {
83
+ :root {
84
+ --surface-1: oklch(15% 0 0);
85
+ --surface-2: oklch(20% 0 0);
86
+ --surface-3: oklch(25% 0 0);
87
+ --text-1: oklch(93% 0 0);
88
+ --text-2: oklch(70% 0 0);
89
+ --accent: oklch(70% 0.18 260);
90
+ --danger: oklch(70% 0.18 25);
91
+ --border: oklch(30% 0 0);
92
+ --focus: oklch(70% 0.18 260 / 50%);
93
+ }
94
+ }
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import "../app.css";
4
+ // {{#if:i18n}}
5
+ import { initI18n } from "$lib/i18n";
6
+ initI18n();
7
+ // {{/if:i18n}}
8
+
9
+ let { children }: { children: Snippet } = $props();
10
+ </script>
11
+
12
+ {@render children()}
@@ -0,0 +1,7 @@
1
+ /** @type {import('stylelint').Config} */
2
+ export default {
3
+ extends: ["stylelint-config-standard"],
4
+ rules: {
5
+ "selector-pseudo-class-no-unknown": [true, { ignorePseudoClasses: ["global"] }],
6
+ },
7
+ };
@@ -0,0 +1,6 @@
1
+ import { sveltekit } from "@sveltejs/kit/vite";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [sveltekit()],
6
+ });