@podosoft/podokit 0.1.0 → 0.2.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 (153) hide show
  1. package/README.md +98 -0
  2. package/dist/add.d.ts +40 -0
  3. package/dist/add.js +114 -0
  4. package/dist/create.d.ts +2 -1
  5. package/dist/create.js +3 -2
  6. package/dist/index.js +39 -1
  7. package/dist/prompt.d.ts +0 -1
  8. package/dist/prompt.js +6 -7
  9. package/dist/templates/fullstack-nest-svelte/README.md +20 -8
  10. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +14 -2
  11. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +11 -1
  12. package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +20 -15
  13. package/dist/templates/fullstack-nest-svelte/apps/api/src/database/data-source.ts +19 -0
  14. package/dist/templates/fullstack-nest-svelte/apps/api/src/health/health.controller.ts +15 -5
  15. package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +8 -0
  16. package/dist/templates/fullstack-nest-svelte/apps/web/components.json +1 -1
  17. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +9 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/web/src/app.css +72 -8
  19. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte +82 -0
  20. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/index.ts +17 -0
  21. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
  22. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
  23. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
  24. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card.svelte +22 -0
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/index.ts +25 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
  30. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
  31. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/index.ts +7 -0
  32. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte +48 -0
  33. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/index.ts +7 -0
  34. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte +20 -0
  35. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/utils.ts +11 -0
  36. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +19 -12
  37. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.controller.ts +31 -0
  38. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.module.ts +22 -0
  39. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.service.ts +44 -0
  40. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/login.dto.ts +12 -0
  41. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/register.dto.ts +13 -0
  42. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt-auth.guard.ts +5 -0
  43. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt.strategy.ts +23 -0
  44. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/user.entity.ts +16 -0
  45. package/dist/templates/modules/auth-jwt/files/apps/api/src/migrations/1720200000000-InitUsers.ts +23 -0
  46. package/dist/templates/modules/auth-jwt/module.manifest.json +31 -0
  47. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/demo.processor.ts +12 -0
  48. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/dto/create-job.dto.ts +9 -0
  49. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.controller.ts +29 -0
  50. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +15 -0
  51. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +8 -0
  52. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +20 -0
  53. package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +14 -0
  54. package/dist/templates/modules/bullmq/files/infra/docker/worker.compose.example.yml +18 -0
  55. package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +22 -0
  56. package/dist/templates/modules/bullmq/module.manifest.json +28 -0
  57. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +29 -0
  58. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +9 -0
  59. package/dist/templates/modules/file-upload/module.manifest.json +19 -0
  60. package/dist/templates/modules/job-progress/files/apps/api/src/progress/dto/start-job.dto.ts +11 -0
  61. package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +13 -0
  62. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.ts +19 -0
  63. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts +17 -0
  64. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +25 -0
  65. package/dist/templates/modules/job-progress/module.manifest.json +23 -0
  66. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/dto/put-object.dto.ts +8 -0
  67. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +31 -0
  68. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts +29 -0
  69. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +11 -0
  70. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +37 -0
  71. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +33 -0
  72. package/dist/templates/modules/object-storage-s3/module.manifest.json +31 -0
  73. package/dist/templates/modules/redis/files/apps/api/src/redis/cache.controller.ts +21 -0
  74. package/dist/templates/modules/redis/files/apps/api/src/redis/dto/set-cache.dto.ts +14 -0
  75. package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +12 -0
  76. package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +43 -0
  77. package/dist/templates/modules/redis/module.manifest.json +21 -0
  78. package/dist/templates/modules/sse/files/apps/api/src/events/dto/publish-event.dto.ts +8 -0
  79. package/dist/templates/modules/sse/files/apps/api/src/events/events.controller.ts +25 -0
  80. package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +12 -0
  81. package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +17 -0
  82. package/dist/templates/modules/sse/module.manifest.json +16 -0
  83. package/dist/templates/todo/README.md +40 -0
  84. package/dist/templates/todo/apps/api/Dockerfile +22 -0
  85. package/dist/templates/todo/apps/api/nest-cli.json +5 -0
  86. package/dist/templates/todo/apps/api/package.json +44 -0
  87. package/dist/templates/todo/apps/api/src/app.module.ts +19 -0
  88. package/dist/templates/todo/apps/api/src/common/all-exceptions.filter.ts +43 -0
  89. package/dist/templates/todo/apps/api/src/common/app-exception.ts +12 -0
  90. package/dist/templates/todo/apps/api/src/config/env.validation.ts +23 -0
  91. package/dist/templates/todo/apps/api/src/database/data-source.ts +19 -0
  92. package/dist/templates/todo/apps/api/src/health/health.controller.ts +23 -0
  93. package/dist/templates/todo/apps/api/src/health/health.module.ts +7 -0
  94. package/dist/templates/todo/apps/api/src/main.ts +29 -0
  95. package/dist/templates/todo/apps/api/src/migrations/1720100000000-InitTodos.ts +22 -0
  96. package/dist/templates/todo/apps/api/src/todos/dto/create-todo.dto.ts +10 -0
  97. package/dist/templates/todo/apps/api/src/todos/dto/update-todo.dto.ts +10 -0
  98. package/dist/templates/todo/apps/api/src/todos/todo.entity.ts +16 -0
  99. package/dist/templates/todo/apps/api/src/todos/todos.controller.ts +38 -0
  100. package/dist/templates/todo/apps/api/src/todos/todos.module.ts +12 -0
  101. package/dist/templates/todo/apps/api/src/todos/todos.service.ts +41 -0
  102. package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +23 -0
  103. package/dist/templates/todo/apps/api/tsconfig.json +21 -0
  104. package/dist/templates/todo/apps/web/Dockerfile +22 -0
  105. package/dist/templates/todo/apps/web/components.json +15 -0
  106. package/dist/templates/todo/apps/web/package.json +35 -0
  107. package/dist/templates/todo/apps/web/src/app.css +81 -0
  108. package/dist/templates/todo/apps/web/src/app.d.ts +11 -0
  109. package/dist/templates/todo/apps/web/src/app.html +11 -0
  110. package/dist/templates/todo/apps/web/src/lib/components/ui/button/button.svelte +82 -0
  111. package/dist/templates/todo/apps/web/src/lib/components/ui/button/index.ts +17 -0
  112. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
  113. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
  114. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
  115. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
  116. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
  117. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
  118. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card.svelte +22 -0
  119. package/dist/templates/todo/apps/web/src/lib/components/ui/card/index.ts +25 -0
  120. package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
  121. package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
  122. package/dist/templates/todo/apps/web/src/lib/components/ui/input/index.ts +7 -0
  123. package/dist/templates/todo/apps/web/src/lib/components/ui/input/input.svelte +48 -0
  124. package/dist/templates/todo/apps/web/src/lib/components/ui/label/index.ts +7 -0
  125. package/dist/templates/todo/apps/web/src/lib/components/ui/label/label.svelte +20 -0
  126. package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -0
  127. package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +10 -0
  128. package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +8 -0
  129. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +16 -0
  130. package/dist/templates/todo/apps/web/src/lib/utils.ts +11 -0
  131. package/dist/templates/todo/apps/web/src/routes/+layout.svelte +9 -0
  132. package/dist/templates/todo/apps/web/src/routes/+page.svelte +95 -0
  133. package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +12 -0
  134. package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +24 -0
  135. package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +24 -0
  136. package/dist/templates/todo/apps/web/static/.gitkeep +0 -0
  137. package/dist/templates/todo/apps/web/svelte.config.js +15 -0
  138. package/dist/templates/todo/apps/web/tsconfig.json +9 -0
  139. package/dist/templates/todo/apps/web/vite.config.ts +7 -0
  140. package/dist/templates/todo/dot-env.example +16 -0
  141. package/dist/templates/todo/dot-gitignore +9 -0
  142. package/dist/templates/todo/infra/docker/docker-compose.yml +29 -0
  143. package/dist/templates/todo/infra/k3s/api-deployment.yaml +24 -0
  144. package/dist/templates/todo/infra/k3s/configmap.yaml +10 -0
  145. package/dist/templates/todo/infra/k3s/ingress.yaml +18 -0
  146. package/dist/templates/todo/infra/k3s/namespace.yaml +4 -0
  147. package/dist/templates/todo/infra/k3s/secret.example.yaml +10 -0
  148. package/dist/templates/todo/infra/k3s/services.yaml +21 -0
  149. package/dist/templates/todo/infra/k3s/web-deployment.yaml +20 -0
  150. package/dist/templates/todo/package.json +13 -0
  151. package/dist/templates.d.ts +10 -0
  152. package/dist/templates.js +33 -0
  153. package/package.json +14 -4
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "$lib/utils.js";
4
+
5
+ type InputType = Exclude<HTMLInputTypeAttribute, "file">;
6
+
7
+ type Props = WithElementRef<
8
+ Omit<HTMLInputAttributes, "type"> &
9
+ ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
10
+ >;
11
+
12
+ let {
13
+ ref = $bindable(null),
14
+ value = $bindable(),
15
+ type,
16
+ files = $bindable(),
17
+ class: className,
18
+ "data-slot": dataSlot = "input",
19
+ ...restProps
20
+ }: Props = $props();
21
+ </script>
22
+
23
+ {#if type === "file"}
24
+ <input
25
+ bind:this={ref}
26
+ data-slot={dataSlot}
27
+ class={cn(
28
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
29
+ className
30
+ )}
31
+ type="file"
32
+ bind:files
33
+ bind:value
34
+ {...restProps}
35
+ />
36
+ {:else}
37
+ <input
38
+ bind:this={ref}
39
+ data-slot={dataSlot}
40
+ class={cn(
41
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
42
+ className
43
+ )}
44
+ {type}
45
+ bind:value
46
+ {...restProps}
47
+ />
48
+ {/if}
@@ -0,0 +1,7 @@
1
+ import Root from "./label.svelte";
2
+
3
+ export {
4
+ Root,
5
+ //
6
+ Root as Label,
7
+ };
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Label as LabelPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: LabelPrimitive.RootProps = $props();
10
+ </script>
11
+
12
+ <LabelPrimitive.Root
13
+ bind:ref
14
+ data-slot="label"
15
+ class={cn(
16
+ "gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
17
+ className
18
+ )}
19
+ {...restProps}
20
+ />
@@ -0,0 +1,7 @@
1
+ # i18n
2
+
3
+ Locales use [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n).
4
+
5
+ - `en.ts` is the base locale; `ko.ts` mirrors its shape.
6
+ - Run `npx typesafe-i18n` to generate the typed runtime and Svelte store,
7
+ then use `$LL.appTitle()` in components. Add keys to every locale.
@@ -0,0 +1,10 @@
1
+ import type { BaseTranslation } from "typesafe-i18n";
2
+
3
+ // Base locale. Run `npx typesafe-i18n` to generate the typed runtime,
4
+ // then access strings as `$LL.appTitle()` in components.
5
+ const en = {
6
+ appTitle: "{{projectName}}",
7
+ checkHealth: "Check API health",
8
+ } satisfies BaseTranslation;
9
+
10
+ export default en;
@@ -0,0 +1,8 @@
1
+ import type { Translation } from "typesafe-i18n";
2
+
3
+ const ko = {
4
+ appTitle: "{{projectName}}",
5
+ checkHealth: "API 상태 확인",
6
+ } satisfies Translation;
7
+
8
+ export default ko;
@@ -0,0 +1,16 @@
1
+ // Server-side proxy boundary. The browser never talks to the API directly:
2
+ // only these allowlisted headers are forwarded to BACKEND_INTERNAL_URL.
3
+ const FORWARDED_HEADERS = ["authorization", "cookie", "content-type"];
4
+
5
+ export function backendBaseUrl(): string {
6
+ return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
7
+ }
8
+
9
+ export function backendProxyHeaders(request: Request): Headers {
10
+ const headers = new Headers();
11
+ for (const name of FORWARDED_HEADERS) {
12
+ const value = request.headers.get(name);
13
+ if (value) headers.set(name, value);
14
+ }
15
+ return headers;
16
+ }
@@ -0,0 +1,11 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]): string {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export type WithoutChild<T> = T extends { child?: unknown } ? Omit<T, "child"> : T;
9
+ export type WithoutChildren<T> = T extends { children?: unknown } ? Omit<T, "children"> : T;
10
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
11
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import "../app.css";
3
+ import { ModeWatcher } from "mode-watcher";
4
+
5
+ let { children } = $props();
6
+ </script>
7
+
8
+ <ModeWatcher />
9
+ {@render children()}
@@ -0,0 +1,95 @@
1
+ <script lang="ts">
2
+ import { Button } from "$lib/components/ui/button";
3
+ import { Input } from "$lib/components/ui/input";
4
+ import { Checkbox } from "$lib/components/ui/checkbox";
5
+ import * as Card from "$lib/components/ui/card";
6
+
7
+ type Todo = { id: string; title: string; completed: boolean };
8
+
9
+ let todos = $state<Todo[]>([]);
10
+ let title = $state("");
11
+ let error = $state<string | null>(null);
12
+
13
+ async function load(): Promise<void> {
14
+ const res = await fetch("/api/todos");
15
+ if (res.ok) todos = await res.json();
16
+ else error = `Failed to load (HTTP ${res.status})`;
17
+ }
18
+
19
+ async function add(event: SubmitEvent): Promise<void> {
20
+ event.preventDefault();
21
+ const value = title.trim();
22
+ if (!value) return;
23
+ const res = await fetch("/api/todos", {
24
+ method: "POST",
25
+ headers: { "content-type": "application/json" },
26
+ body: JSON.stringify({ title: value }),
27
+ });
28
+ if (res.ok) {
29
+ title = "";
30
+ await load();
31
+ } else {
32
+ error = `Failed to add (HTTP ${res.status})`;
33
+ }
34
+ }
35
+
36
+ async function toggle(todo: Todo): Promise<void> {
37
+ await fetch(`/api/todos/${todo.id}`, {
38
+ method: "PATCH",
39
+ headers: { "content-type": "application/json" },
40
+ body: JSON.stringify({ completed: !todo.completed }),
41
+ });
42
+ await load();
43
+ }
44
+
45
+ async function remove(todo: Todo): Promise<void> {
46
+ await fetch(`/api/todos/${todo.id}`, { method: "DELETE" });
47
+ await load();
48
+ }
49
+
50
+ $effect(() => {
51
+ void load();
52
+ });
53
+ </script>
54
+
55
+ <main class="mx-auto flex min-h-full max-w-xl flex-col gap-6 p-8">
56
+ <div>
57
+ <h1 class="text-3xl font-bold">{{projectName}}</h1>
58
+ <p class="text-muted-foreground text-sm">Full-stack starter generated with PodoKit.</p>
59
+ </div>
60
+
61
+ <Card.Root>
62
+ <Card.Header>
63
+ <Card.Title>Todos</Card.Title>
64
+ <Card.Description>A NestJS + SvelteKit CRUD example.</Card.Description>
65
+ </Card.Header>
66
+ <Card.Content class="flex flex-col gap-4">
67
+ <form class="flex gap-2" onsubmit={add}>
68
+ <Input placeholder="Add a todo…" bind:value={title} />
69
+ <Button type="submit">Add</Button>
70
+ </form>
71
+
72
+ {#if error}
73
+ <p class="text-destructive text-sm">{error}</p>
74
+ {/if}
75
+
76
+ <ul class="divide-border flex flex-col divide-y">
77
+ {#each todos as todo (todo.id)}
78
+ <li class="flex items-center gap-3 py-3">
79
+ <Checkbox checked={todo.completed} onCheckedChange={() => toggle(todo)} />
80
+ <span
81
+ class="flex-1 text-sm"
82
+ class:line-through={todo.completed}
83
+ class:text-muted-foreground={todo.completed}
84
+ >
85
+ {todo.title}
86
+ </span>
87
+ <Button variant="ghost" size="sm" onclick={() => remove(todo)}>Delete</Button>
88
+ </li>
89
+ {:else}
90
+ <li class="text-muted-foreground py-6 text-center text-sm">No todos yet — add one above.</li>
91
+ {/each}
92
+ </ul>
93
+ </Card.Content>
94
+ </Card.Root>
95
+ </main>
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
3
+
4
+ export const GET: RequestHandler = async ({ request }) => {
5
+ const upstream = await fetch(`${backendBaseUrl()}/health`, {
6
+ headers: backendProxyHeaders(request),
7
+ });
8
+ return new Response(await upstream.text(), {
9
+ status: upstream.status,
10
+ headers: { "content-type": "application/json" },
11
+ });
12
+ };
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
3
+
4
+ export const GET: RequestHandler = async ({ request }) => {
5
+ const upstream = await fetch(`${backendBaseUrl()}/todos`, { headers: backendProxyHeaders(request) });
6
+ return new Response(await upstream.text(), {
7
+ status: upstream.status,
8
+ headers: { "content-type": "application/json" },
9
+ });
10
+ };
11
+
12
+ export const POST: RequestHandler = async ({ request }) => {
13
+ const headers = backendProxyHeaders(request);
14
+ headers.set("content-type", "application/json");
15
+ const upstream = await fetch(`${backendBaseUrl()}/todos`, {
16
+ method: "POST",
17
+ headers,
18
+ body: await request.text(),
19
+ });
20
+ return new Response(await upstream.text(), {
21
+ status: upstream.status,
22
+ headers: { "content-type": "application/json" },
23
+ });
24
+ };
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
3
+
4
+ export const PATCH: RequestHandler = async ({ request, params }) => {
5
+ const headers = backendProxyHeaders(request);
6
+ headers.set("content-type", "application/json");
7
+ const upstream = await fetch(`${backendBaseUrl()}/todos/${params.id}`, {
8
+ method: "PATCH",
9
+ headers,
10
+ body: await request.text(),
11
+ });
12
+ return new Response(await upstream.text(), {
13
+ status: upstream.status,
14
+ headers: { "content-type": "application/json" },
15
+ });
16
+ };
17
+
18
+ export const DELETE: RequestHandler = async ({ request, params }) => {
19
+ const upstream = await fetch(`${backendBaseUrl()}/todos/${params.id}`, {
20
+ method: "DELETE",
21
+ headers: backendProxyHeaders(request),
22
+ });
23
+ return new Response(null, { status: upstream.status });
24
+ };
File without changes
@@ -0,0 +1,15 @@
1
+ import adapter from "@sveltejs/adapter-node";
2
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
3
+
4
+ /** @type {import('@sveltejs/kit').Config} */
5
+ const config = {
6
+ preprocess: vitePreprocess(),
7
+ kit: {
8
+ adapter: adapter(),
9
+ alias: {
10
+ $i18n: "src/lib/i18n",
11
+ },
12
+ },
13
+ };
14
+
15
+ export default config;
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ import { sveltekit } from "@sveltejs/kit/vite";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import { defineConfig } from "vite";
4
+
5
+ export default defineConfig({
6
+ plugins: [tailwindcss(), sveltekit()],
7
+ });
@@ -0,0 +1,16 @@
1
+ # Copy to .env and adjust. Never commit real secrets.
2
+ NODE_ENV=development
3
+ PORT=3000
4
+
5
+ POSTGRES_HOST=localhost
6
+ POSTGRES_PORT=5432
7
+ POSTGRES_USER=podokit
8
+ POSTGRES_PASSWORD=podokit
9
+ POSTGRES_DB=podokit
10
+
11
+ REDIS_HOST=localhost
12
+ REDIS_PORT=6379
13
+
14
+ # Web -> API boundary (server-side only; not exposed to the browser)
15
+ BACKEND_INTERNAL_URL=http://localhost:3000
16
+ CORS_ORIGIN=http://localhost:5173
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .svelte-kit/
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ .DS_Store
9
+ *.log
@@ -0,0 +1,29 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16-alpine
4
+ environment:
5
+ POSTGRES_USER: ${POSTGRES_USER:-podokit}
6
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-podokit}
7
+ POSTGRES_DB: ${POSTGRES_DB:-podokit}
8
+ ports:
9
+ - "${POSTGRES_PORT:-5432}:5432"
10
+ volumes:
11
+ - pgdata:/var/lib/postgresql/data
12
+ healthcheck:
13
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-podokit}"]
14
+ interval: 5s
15
+ timeout: 5s
16
+ retries: 5
17
+
18
+ redis:
19
+ image: redis:7-alpine
20
+ ports:
21
+ - "${REDIS_PORT:-6379}:6379"
22
+ healthcheck:
23
+ test: ["CMD", "redis-cli", "ping"]
24
+ interval: 5s
25
+ timeout: 5s
26
+ retries: 5
27
+
28
+ volumes:
29
+ pgdata:
@@ -0,0 +1,24 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: api
5
+ namespace: podokit
6
+ spec:
7
+ replicas: 1
8
+ selector:
9
+ matchLabels: { app: api }
10
+ template:
11
+ metadata:
12
+ labels: { app: api }
13
+ spec:
14
+ containers:
15
+ - name: api
16
+ image: ghcr.io/example/podokit-api:latest
17
+ ports:
18
+ - containerPort: 3000
19
+ envFrom:
20
+ - configMapRef: { name: app-config }
21
+ - secretRef: { name: app-secrets }
22
+ readinessProbe:
23
+ httpGet: { path: /health, port: 3000 }
24
+ initialDelaySeconds: 5
@@ -0,0 +1,10 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: app-config
5
+ namespace: podokit
6
+ data:
7
+ NODE_ENV: "production"
8
+ PORT: "3000"
9
+ CORS_ORIGIN: "https://example.com"
10
+ BACKEND_INTERNAL_URL: "http://api:3000"
@@ -0,0 +1,18 @@
1
+ apiVersion: networking.k8s.io/v1
2
+ kind: Ingress
3
+ metadata:
4
+ name: app
5
+ namespace: podokit
6
+ spec:
7
+ rules:
8
+ - host: example.com
9
+ http:
10
+ paths:
11
+ - path: /api
12
+ pathType: Prefix
13
+ backend:
14
+ service: { name: api, port: { number: 3000 } }
15
+ - path: /
16
+ pathType: Prefix
17
+ backend:
18
+ service: { name: web, port: { number: 3000 } }
@@ -0,0 +1,4 @@
1
+ apiVersion: v1
2
+ kind: Namespace
3
+ metadata:
4
+ name: podokit
@@ -0,0 +1,10 @@
1
+ # Example only. Never commit real secrets. Create the real Secret out-of-band:
2
+ # kubectl -n podokit create secret generic app-secrets --from-literal=POSTGRES_PASSWORD=...
3
+ apiVersion: v1
4
+ kind: Secret
5
+ metadata:
6
+ name: app-secrets
7
+ namespace: podokit
8
+ type: Opaque
9
+ stringData:
10
+ POSTGRES_PASSWORD: "change-me"
@@ -0,0 +1,21 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: api
5
+ namespace: podokit
6
+ spec:
7
+ selector: { app: api }
8
+ ports:
9
+ - port: 3000
10
+ targetPort: 3000
11
+ ---
12
+ apiVersion: v1
13
+ kind: Service
14
+ metadata:
15
+ name: web
16
+ namespace: podokit
17
+ spec:
18
+ selector: { app: web }
19
+ ports:
20
+ - port: 3000
21
+ targetPort: 3000
@@ -0,0 +1,20 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: web
5
+ namespace: podokit
6
+ spec:
7
+ replicas: 1
8
+ selector:
9
+ matchLabels: { app: web }
10
+ template:
11
+ metadata:
12
+ labels: { app: web }
13
+ spec:
14
+ containers:
15
+ - name: web
16
+ image: ghcr.io/example/podokit-web:latest
17
+ ports:
18
+ - containerPort: 3000
19
+ envFrom:
20
+ - configMapRef: { name: app-config }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "engines": { "node": ">=20" },
6
+ "workspaces": ["apps/*"],
7
+ "scripts": {
8
+ "dev": "npm run dev --workspaces --if-present",
9
+ "build": "npm run build --workspaces --if-present",
10
+ "lint": "npm run lint --workspaces --if-present",
11
+ "test": "npm run test --workspaces --if-present"
12
+ }
13
+ }
@@ -0,0 +1,10 @@
1
+ export interface TemplateInfo {
2
+ name: string;
3
+ description: string;
4
+ }
5
+ export declare const TEMPLATES: TemplateInfo[];
6
+ export declare const DEFAULT_TEMPLATE: string;
7
+ export declare const TEMPLATE_NAMES: string[];
8
+ export declare function isKnownTemplate(name: string): boolean;
9
+ /** Human-readable list of templates with descriptions, for prompts and help. */
10
+ export declare function templateListText(): string;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TEMPLATE_NAMES = exports.DEFAULT_TEMPLATE = exports.TEMPLATES = void 0;
4
+ exports.isKnownTemplate = isKnownTemplate;
5
+ exports.templateListText = templateListText;
6
+ // Order matters: the first entry is the default and prompts list them in order.
7
+ exports.TEMPLATES = [
8
+ {
9
+ name: "fullstack-nest-svelte",
10
+ description: "NestJS + SvelteKit starter — TypeORM wired, Swagger, no domain code (clean)",
11
+ },
12
+ {
13
+ name: "todo",
14
+ description: "Fullstack starter plus a Todo CRUD example (DB entity, migration, UI)",
15
+ },
16
+ {
17
+ name: "base",
18
+ description: "Minimal npm workspace to build up from scratch",
19
+ },
20
+ ];
21
+ exports.DEFAULT_TEMPLATE = exports.TEMPLATES[0].name;
22
+ exports.TEMPLATE_NAMES = exports.TEMPLATES.map((t) => t.name);
23
+ function isKnownTemplate(name) {
24
+ return exports.TEMPLATE_NAMES.includes(name);
25
+ }
26
+ /** Human-readable list of templates with descriptions, for prompts and help. */
27
+ function templateListText() {
28
+ const width = Math.max(...exports.TEMPLATES.map((t) => t.name.length));
29
+ return exports.TEMPLATES.map((t) => {
30
+ const suffix = t.name === exports.DEFAULT_TEMPLATE ? " (default)" : "";
31
+ return ` ${t.name.padEnd(width)} ${t.description}${suffix}`;
32
+ }).join("\n");
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podosoft/podokit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,11 +8,21 @@
8
8
  "url": "git+https://github.com/podosoft-dev/podokit.git",
9
9
  "directory": "packages/cli"
10
10
  },
11
- "keywords": ["nestjs", "sveltekit", "starter", "cli", "scaffold", "tailwindcss", "shadcn"],
11
+ "keywords": [
12
+ "nestjs",
13
+ "sveltekit",
14
+ "starter",
15
+ "cli",
16
+ "scaffold",
17
+ "tailwindcss",
18
+ "shadcn"
19
+ ],
12
20
  "bin": {
13
21
  "podo": "dist/index.js"
14
22
  },
15
- "files": ["dist"],
23
+ "files": [
24
+ "dist"
25
+ ],
16
26
  "engines": {
17
27
  "node": ">=20"
18
28
  },
@@ -26,6 +36,6 @@
26
36
  "test": "vitest run"
27
37
  },
28
38
  "dependencies": {
29
- "@podosoft/podokit-template-engine": "^0.1.0"
39
+ "@podosoft/podokit-template-engine": "^0.2.0"
30
40
  }
31
41
  }