@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.
- package/README.md +98 -0
- package/dist/add.d.ts +40 -0
- package/dist/add.js +114 -0
- package/dist/create.d.ts +2 -1
- package/dist/create.js +3 -2
- package/dist/index.js +39 -1
- package/dist/prompt.d.ts +0 -1
- package/dist/prompt.js +6 -7
- package/dist/templates/fullstack-nest-svelte/README.md +20 -8
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +14 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +11 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +20 -15
- package/dist/templates/fullstack-nest-svelte/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/api/src/health/health.controller.ts +15 -5
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +8 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/components.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +9 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/app.css +72 -8
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +19 -12
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.controller.ts +31 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.module.ts +22 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.service.ts +44 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/login.dto.ts +12 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/register.dto.ts +13 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt-auth.guard.ts +5 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt.strategy.ts +23 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/user.entity.ts +16 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/migrations/1720200000000-InitUsers.ts +23 -0
- package/dist/templates/modules/auth-jwt/module.manifest.json +31 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/demo.processor.ts +12 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/dto/create-job.dto.ts +9 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.controller.ts +29 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +15 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +8 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +20 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +14 -0
- package/dist/templates/modules/bullmq/files/infra/docker/worker.compose.example.yml +18 -0
- package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +22 -0
- package/dist/templates/modules/bullmq/module.manifest.json +28 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +29 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +9 -0
- package/dist/templates/modules/file-upload/module.manifest.json +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/dto/start-job.dto.ts +11 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +13 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.ts +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts +17 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +25 -0
- package/dist/templates/modules/job-progress/module.manifest.json +23 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/dto/put-object.dto.ts +8 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +31 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts +29 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +11 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +37 -0
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +33 -0
- package/dist/templates/modules/object-storage-s3/module.manifest.json +31 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/cache.controller.ts +21 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/dto/set-cache.dto.ts +14 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +12 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +43 -0
- package/dist/templates/modules/redis/module.manifest.json +21 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/dto/publish-event.dto.ts +8 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.controller.ts +25 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +12 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +17 -0
- package/dist/templates/modules/sse/module.manifest.json +16 -0
- package/dist/templates/todo/README.md +40 -0
- package/dist/templates/todo/apps/api/Dockerfile +22 -0
- package/dist/templates/todo/apps/api/nest-cli.json +5 -0
- package/dist/templates/todo/apps/api/package.json +44 -0
- package/dist/templates/todo/apps/api/src/app.module.ts +19 -0
- package/dist/templates/todo/apps/api/src/common/all-exceptions.filter.ts +43 -0
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +12 -0
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +23 -0
- package/dist/templates/todo/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/todo/apps/api/src/health/health.controller.ts +23 -0
- package/dist/templates/todo/apps/api/src/health/health.module.ts +7 -0
- package/dist/templates/todo/apps/api/src/main.ts +29 -0
- package/dist/templates/todo/apps/api/src/migrations/1720100000000-InitTodos.ts +22 -0
- package/dist/templates/todo/apps/api/src/todos/dto/create-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/dto/update-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/todo.entity.ts +16 -0
- package/dist/templates/todo/apps/api/src/todos/todos.controller.ts +38 -0
- package/dist/templates/todo/apps/api/src/todos/todos.module.ts +12 -0
- package/dist/templates/todo/apps/api/src/todos/todos.service.ts +41 -0
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +23 -0
- package/dist/templates/todo/apps/api/tsconfig.json +21 -0
- package/dist/templates/todo/apps/web/Dockerfile +22 -0
- package/dist/templates/todo/apps/web/components.json +15 -0
- package/dist/templates/todo/apps/web/package.json +35 -0
- package/dist/templates/todo/apps/web/src/app.css +81 -0
- package/dist/templates/todo/apps/web/src/app.d.ts +11 -0
- package/dist/templates/todo/apps/web/src/app.html +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +10 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +8 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +16 -0
- package/dist/templates/todo/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/todo/apps/web/src/routes/+layout.svelte +9 -0
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +95 -0
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +24 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +24 -0
- package/dist/templates/todo/apps/web/static/.gitkeep +0 -0
- package/dist/templates/todo/apps/web/svelte.config.js +15 -0
- package/dist/templates/todo/apps/web/tsconfig.json +9 -0
- package/dist/templates/todo/apps/web/vite.config.ts +7 -0
- package/dist/templates/todo/dot-env.example +16 -0
- package/dist/templates/todo/dot-gitignore +9 -0
- package/dist/templates/todo/infra/docker/docker-compose.yml +29 -0
- package/dist/templates/todo/infra/k3s/api-deployment.yaml +24 -0
- package/dist/templates/todo/infra/k3s/configmap.yaml +10 -0
- package/dist/templates/todo/infra/k3s/ingress.yaml +18 -0
- package/dist/templates/todo/infra/k3s/namespace.yaml +4 -0
- package/dist/templates/todo/infra/k3s/secret.example.yaml +10 -0
- package/dist/templates/todo/infra/k3s/services.yaml +21 -0
- package/dist/templates/todo/infra/k3s/web-deployment.yaml +20 -0
- package/dist/templates/todo/package.json +13 -0
- package/dist/templates.d.ts +10 -0
- package/dist/templates.js +33 -0
- package/package.json +14 -4
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @podosoft/podokit
|
|
2
|
+
|
|
3
|
+
**PodoKit** is an opinionated but extensible starter toolkit and CLI for building full-stack TypeScript applications with **NestJS**, **SvelteKit**, **TailwindCSS**, **shadcn-svelte**, **Docker**, and **k3s**.
|
|
4
|
+
|
|
5
|
+
Stop rewriting the same backend bootstrap, frontend setup, environment config, health checks, Docker Compose, and CI every time you start a project. `podo create` gives you a consistent, production-minded foundation in seconds.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @podosoft/podokit create my-app
|
|
11
|
+
cd my-app
|
|
12
|
+
npm install
|
|
13
|
+
cp .env.example .env
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- API: http://localhost:3000 (health at `/health`)
|
|
18
|
+
- Web: http://localhost:5173
|
|
19
|
+
|
|
20
|
+
When run in a terminal, `podo create` lists the templates with descriptions and asks which one (and which package manager) to use. Pass flags (or `--yes`) to skip the prompts.
|
|
21
|
+
|
|
22
|
+
The `todo` template (`--template todo`) generates a working todo app (SvelteKit UI + NestJS API + PostgreSQL) with Swagger docs:
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
podo create <name> [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--template <t> Template to scaffold (default: fullstack-nest-svelte)
|
|
33
|
+
- fullstack-nest-svelte : clean NestJS + SvelteKit starter
|
|
34
|
+
- todo : fullstack + a Todo CRUD example
|
|
35
|
+
- base : minimal npm workspace
|
|
36
|
+
--dir <path> Target directory (default: ./<name>)
|
|
37
|
+
--pm <name> Package manager: npm | pnpm | yarn (default: npm)
|
|
38
|
+
-y, --yes Skip prompts and accept defaults
|
|
39
|
+
-h, --help Show help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Interactive
|
|
46
|
+
npx @podosoft/podokit create my-app
|
|
47
|
+
|
|
48
|
+
# Non-interactive, explicit choices
|
|
49
|
+
npx @podosoft/podokit create my-app --template fullstack-nest-svelte --pm pnpm --yes
|
|
50
|
+
|
|
51
|
+
# Minimal workspace
|
|
52
|
+
npx @podosoft/podokit create my-lib --template base --yes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Add features with modules
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd my-app
|
|
59
|
+
npx @podosoft/podokit add auth-jwt # JWT auth: register, login, guard, /auth/me
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`podo add <module>` overlays files, merges dependencies, appends env vars, and wires the module into the NestJS app. Run `podo add` with no argument to list available modules.
|
|
63
|
+
|
|
64
|
+
## What you get (`fullstack-nest-svelte`)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
my-app/
|
|
68
|
+
├── apps/
|
|
69
|
+
│ ├── api/ # NestJS: config validation, /health, global
|
|
70
|
+
│ │ └── src/ # ValidationPipe, standard error envelope
|
|
71
|
+
│ └── web/ # SvelteKit: Tailwind v4, shadcn-svelte,
|
|
72
|
+
│ └── src/ # typesafe-i18n, server-side API proxy
|
|
73
|
+
├── infra/
|
|
74
|
+
│ ├── docker/ # docker-compose (PostgreSQL, Redis)
|
|
75
|
+
│ └── k3s/ # namespace, deployments, service, ingress, secret example
|
|
76
|
+
├── .env.example
|
|
77
|
+
├── package.json # npm workspace
|
|
78
|
+
└── README.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Highlights of the generated app:
|
|
82
|
+
|
|
83
|
+
- **Backend (NestJS)** — bootstrap with a global `ValidationPipe` and exception filter, typed environment validation, a `/health` endpoint, and a stable `{ success, error: { code, ... } }` response envelope.
|
|
84
|
+
- **Frontend (SvelteKit)** — TailwindCSS v4 (config-less), **shadcn-svelte components preinstalled** (button, input, card, checkbox, label), a typesafe-i18n scaffold, and a **server-side proxy** so the browser never calls the API directly.
|
|
85
|
+
- **Infra** — Docker Compose for local PostgreSQL and Redis, plus example k3s manifests (standard `Ingress`, `secret.example.yaml`).
|
|
86
|
+
|
|
87
|
+
## Status
|
|
88
|
+
|
|
89
|
+
PodoKit is early (`0.x`). The CLI and templates work end-to-end, but APIs and templates may change before `1.0`. Feedback and issues are welcome.
|
|
90
|
+
|
|
91
|
+
## Links
|
|
92
|
+
|
|
93
|
+
- Repository & issues: https://github.com/podosoft-dev/podokit
|
|
94
|
+
- Changelog: https://github.com/podosoft-dev/podokit/blob/main/CHANGELOG.md
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
[Apache-2.0](https://github.com/podosoft-dev/podokit/blob/main/LICENSE)
|
package/dist/add.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface Injection {
|
|
2
|
+
file: string;
|
|
3
|
+
marker: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ModuleManifest {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
requires?: string[];
|
|
10
|
+
targetApp: string;
|
|
11
|
+
dependencies?: Record<string, string>;
|
|
12
|
+
devDependencies?: Record<string, string>;
|
|
13
|
+
scripts?: Record<string, string>;
|
|
14
|
+
env?: string[];
|
|
15
|
+
inject?: Injection[];
|
|
16
|
+
instructions?: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface AddOptions {
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
module: string;
|
|
21
|
+
modulesDir: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AddResult {
|
|
24
|
+
module: string;
|
|
25
|
+
instructions: string[];
|
|
26
|
+
/** Required modules that were auto-added because they were missing. */
|
|
27
|
+
added: string[];
|
|
28
|
+
}
|
|
29
|
+
/** List modules available under `modulesDir` (each has a module.manifest.json). */
|
|
30
|
+
export declare function listModules(modulesDir: string): {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}[];
|
|
34
|
+
/**
|
|
35
|
+
* Apply a module to an existing generated project: overlay files, merge the
|
|
36
|
+
* target app's package.json dependencies and scripts, append env example lines,
|
|
37
|
+
* and inject wiring at markers. Missing required modules are added first.
|
|
38
|
+
*/
|
|
39
|
+
export declare function addModule(options: AddOptions): AddResult;
|
|
40
|
+
export {};
|
package/dist/add.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listModules = listModules;
|
|
4
|
+
exports.addModule = addModule;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
|
|
8
|
+
/** List modules available under `modulesDir` (each has a module.manifest.json). */
|
|
9
|
+
function listModules(modulesDir) {
|
|
10
|
+
if (!(0, node_fs_1.existsSync)(modulesDir))
|
|
11
|
+
return [];
|
|
12
|
+
return (0, node_fs_1.readdirSync)(modulesDir)
|
|
13
|
+
.filter((name) => (0, node_fs_1.existsSync)((0, node_path_1.join)(modulesDir, name, "module.manifest.json")))
|
|
14
|
+
.map((name) => {
|
|
15
|
+
const manifest = readManifest((0, node_path_1.join)(modulesDir, name));
|
|
16
|
+
return { name, description: manifest.description };
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function readManifest(moduleDir) {
|
|
20
|
+
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(moduleDir, "module.manifest.json"), "utf8"));
|
|
21
|
+
}
|
|
22
|
+
function readJson(path) {
|
|
23
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
24
|
+
}
|
|
25
|
+
function projectName(projectRoot) {
|
|
26
|
+
const pkg = readJson((0, node_path_1.join)(projectRoot, "package.json"));
|
|
27
|
+
return typeof pkg.name === "string" ? pkg.name : "app";
|
|
28
|
+
}
|
|
29
|
+
function appendEnv(projectRoot, lines) {
|
|
30
|
+
const file = (0, node_path_1.join)(projectRoot, ".env.example");
|
|
31
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
32
|
+
return;
|
|
33
|
+
const current = (0, node_fs_1.readFileSync)(file, "utf8");
|
|
34
|
+
const missing = lines.filter((line) => !current.split("\n").includes(line));
|
|
35
|
+
if (missing.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
const separator = current.endsWith("\n") ? "" : "\n";
|
|
38
|
+
(0, node_fs_1.writeFileSync)(file, `${current}${separator}\n${missing.join("\n")}\n`);
|
|
39
|
+
}
|
|
40
|
+
/** Heuristic: is `module` already applied to the project? */
|
|
41
|
+
function isApplied(projectRoot, modulesDir, module) {
|
|
42
|
+
const manifestPath = (0, node_path_1.join)(modulesDir, module, "module.manifest.json");
|
|
43
|
+
if (!(0, node_fs_1.existsSync)(manifestPath))
|
|
44
|
+
return false;
|
|
45
|
+
const manifest = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, "utf8"));
|
|
46
|
+
const firstInject = manifest.inject?.[0];
|
|
47
|
+
if (firstInject) {
|
|
48
|
+
const target = (0, node_path_1.join)(projectRoot, firstInject.file);
|
|
49
|
+
return (0, node_fs_1.existsSync)(target) && (0, node_fs_1.readFileSync)(target, "utf8").includes(firstInject.text);
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply a module to an existing generated project: overlay files, merge the
|
|
55
|
+
* target app's package.json dependencies and scripts, append env example lines,
|
|
56
|
+
* and inject wiring at markers. Missing required modules are added first.
|
|
57
|
+
*/
|
|
58
|
+
function addModule(options) {
|
|
59
|
+
return applyModule(options.projectRoot, options.module, options.modulesDir, new Set());
|
|
60
|
+
}
|
|
61
|
+
function applyModule(projectRoot, module, modulesDir, applied) {
|
|
62
|
+
const moduleDir = (0, node_path_1.join)(modulesDir, module);
|
|
63
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(moduleDir, "module.manifest.json"))) {
|
|
64
|
+
const available = listModules(modulesDir).map((m) => m.name);
|
|
65
|
+
throw new Error(`Unknown module "${module}".${available.length ? ` Available: ${available.join(", ")}.` : ""}`);
|
|
66
|
+
}
|
|
67
|
+
const manifest = readManifest(moduleDir);
|
|
68
|
+
const appPkgPath = (0, node_path_1.join)(projectRoot, "apps", manifest.targetApp, "package.json");
|
|
69
|
+
if (!(0, node_fs_1.existsSync)(appPkgPath)) {
|
|
70
|
+
throw new Error(`This does not look like a PodoKit project: ${(0, node_path_1.join)("apps", manifest.targetApp, "package.json")} not found. Run inside a generated project.`);
|
|
71
|
+
}
|
|
72
|
+
applied.add(module);
|
|
73
|
+
// 0) apply required modules first (auto-add if missing)
|
|
74
|
+
const added = [];
|
|
75
|
+
for (const required of manifest.requires ?? []) {
|
|
76
|
+
if (applied.has(required) || isApplied(projectRoot, modulesDir, required))
|
|
77
|
+
continue;
|
|
78
|
+
const result = applyModule(projectRoot, required, modulesDir, applied);
|
|
79
|
+
added.push(required, ...result.added);
|
|
80
|
+
}
|
|
81
|
+
const appName = projectName(projectRoot);
|
|
82
|
+
const vars = { projectName: appName };
|
|
83
|
+
// 1) overlay files
|
|
84
|
+
const filesDir = (0, node_path_1.join)(moduleDir, "files");
|
|
85
|
+
if ((0, node_fs_1.existsSync)(filesDir)) {
|
|
86
|
+
(0, podokit_template_engine_1.copyTemplate)(filesDir, projectRoot, vars);
|
|
87
|
+
}
|
|
88
|
+
// 2) merge dependencies and scripts into the target app
|
|
89
|
+
if (manifest.dependencies || manifest.devDependencies || manifest.scripts) {
|
|
90
|
+
const overlay = {};
|
|
91
|
+
if (manifest.dependencies)
|
|
92
|
+
overlay.dependencies = manifest.dependencies;
|
|
93
|
+
if (manifest.devDependencies)
|
|
94
|
+
overlay.devDependencies = manifest.devDependencies;
|
|
95
|
+
if (manifest.scripts)
|
|
96
|
+
overlay.scripts = manifest.scripts;
|
|
97
|
+
const merged = (0, podokit_template_engine_1.mergePackageJson)(readJson(appPkgPath), overlay);
|
|
98
|
+
(0, node_fs_1.writeFileSync)(appPkgPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
99
|
+
}
|
|
100
|
+
// 3) append env example lines
|
|
101
|
+
if (manifest.env?.length) {
|
|
102
|
+
appendEnv(projectRoot, manifest.env);
|
|
103
|
+
}
|
|
104
|
+
// 4) inject wiring at markers
|
|
105
|
+
for (const injection of manifest.inject ?? []) {
|
|
106
|
+
const target = (0, node_path_1.join)(projectRoot, injection.file);
|
|
107
|
+
if (!(0, node_fs_1.existsSync)(target)) {
|
|
108
|
+
throw new Error(`Cannot wire module: ${injection.file} not found.`);
|
|
109
|
+
}
|
|
110
|
+
(0, node_fs_1.writeFileSync)(target, (0, podokit_template_engine_1.insertAtMarker)((0, node_fs_1.readFileSync)(target, "utf8"), injection.marker, injection.text));
|
|
111
|
+
}
|
|
112
|
+
const instructions = (manifest.instructions ?? []).map((line) => line.replace(/<app>/g, appName));
|
|
113
|
+
return { module, instructions, added };
|
|
114
|
+
}
|
package/dist/create.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { DEFAULT_TEMPLATE } from "./templates";
|
|
1
2
|
export type PackageManager = "npm" | "pnpm" | "yarn";
|
|
2
|
-
export
|
|
3
|
+
export { DEFAULT_TEMPLATE };
|
|
3
4
|
export interface CreateOptions {
|
|
4
5
|
/** Project name; also the default directory name. */
|
|
5
6
|
name: string;
|
package/dist/create.js
CHANGED
|
@@ -6,7 +6,8 @@ exports.create = create;
|
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const node_path_1 = require("node:path");
|
|
8
8
|
const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
|
|
9
|
-
|
|
9
|
+
const templates_1 = require("./templates");
|
|
10
|
+
Object.defineProperty(exports, "DEFAULT_TEMPLATE", { enumerable: true, get: function () { return templates_1.DEFAULT_TEMPLATE; } });
|
|
10
11
|
const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-._]*[a-z0-9])?$/i;
|
|
11
12
|
/** Validate a project name: no path separators, npm-friendly characters. */
|
|
12
13
|
function assertValidName(name) {
|
|
@@ -24,7 +25,7 @@ function isEmptyDir(dir) {
|
|
|
24
25
|
function create(options) {
|
|
25
26
|
const { name, templatesDir } = options;
|
|
26
27
|
assertValidName(name);
|
|
27
|
-
const template = options.template ??
|
|
28
|
+
const template = options.template ?? templates_1.DEFAULT_TEMPLATE;
|
|
28
29
|
const packageManager = options.packageManager ?? "npm";
|
|
29
30
|
const projectDir = options.targetDir
|
|
30
31
|
? (0, node_path_1.isAbsolute)(options.targetDir)
|
package/dist/index.js
CHANGED
|
@@ -6,20 +6,28 @@ const node_path_1 = require("node:path");
|
|
|
6
6
|
const promises_1 = require("node:readline/promises");
|
|
7
7
|
const create_1 = require("./create");
|
|
8
8
|
const prompt_1 = require("./prompt");
|
|
9
|
+
const templates_1 = require("./templates");
|
|
10
|
+
const add_1 = require("./add");
|
|
9
11
|
const HELP = `podo — PodoKit project generator
|
|
10
12
|
|
|
11
13
|
Usage:
|
|
12
14
|
podo create <name> [options]
|
|
15
|
+
podo add <module>
|
|
13
16
|
|
|
14
17
|
Options:
|
|
15
|
-
--template <t> Template
|
|
18
|
+
--template <t> Template to scaffold (see below)
|
|
16
19
|
--dir <path> Target directory (default: ./<name>)
|
|
17
20
|
--pm <name> Package manager: npm | pnpm | yarn (default: npm)
|
|
18
21
|
-y, --yes Skip prompts and accept defaults
|
|
19
22
|
-h, --help Show this help
|
|
20
23
|
|
|
24
|
+
Templates:
|
|
25
|
+
${(0, templates_1.templateListText)()}
|
|
26
|
+
|
|
21
27
|
Example:
|
|
22
28
|
npx @podosoft/podokit create my-app
|
|
29
|
+
npx @podosoft/podokit create my-app --template todo
|
|
30
|
+
cd my-app && npx @podosoft/podokit add auth-jwt
|
|
23
31
|
`;
|
|
24
32
|
function parseArgs(argv) {
|
|
25
33
|
const parsed = { help: false, yes: false };
|
|
@@ -59,6 +67,32 @@ async function main(argv) {
|
|
|
59
67
|
process.stdout.write(HELP);
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
70
|
+
const modulesDir = (0, node_path_1.join)(__dirname, "templates", "modules");
|
|
71
|
+
if (args.command === "add") {
|
|
72
|
+
const moduleName = args.name;
|
|
73
|
+
if (!moduleName) {
|
|
74
|
+
const available = (0, add_1.listModules)(modulesDir);
|
|
75
|
+
const list = available.length
|
|
76
|
+
? available.map((m) => ` ${m.name} ${m.description}`).join("\n")
|
|
77
|
+
: " (none available)";
|
|
78
|
+
process.stdout.write(`Usage: podo add <module>\n\nModules:\n${list}\n`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const result = (0, add_1.addModule)({ projectRoot: process.cwd(), module: moduleName, modulesDir });
|
|
83
|
+
if (result.added.length) {
|
|
84
|
+
process.stdout.write(`\nAlso added required module(s): ${result.added.join(", ")}\n`);
|
|
85
|
+
}
|
|
86
|
+
process.stdout.write(`\nAdded ${result.module}.\n`);
|
|
87
|
+
if (result.instructions.length) {
|
|
88
|
+
process.stdout.write(`\nNext steps:\n${result.instructions.map((i) => ` ${i}`).join("\n")}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
fail(err.message);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
62
96
|
if (args.command !== "create") {
|
|
63
97
|
fail(`Unknown command "${args.command}". Run "podo --help".`);
|
|
64
98
|
}
|
|
@@ -74,6 +108,10 @@ async function main(argv) {
|
|
|
74
108
|
const interactive = Boolean(process.stdin.isTTY) && !args.yes;
|
|
75
109
|
const rl = interactive ? (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout }) : undefined;
|
|
76
110
|
const ask = async (question) => (rl ? (await rl.question(question)).trim() : "");
|
|
111
|
+
// Show the template menu with descriptions before prompting for one.
|
|
112
|
+
if (interactive && !args.template) {
|
|
113
|
+
process.stdout.write(`\nTemplates:\n${(0, templates_1.templateListText)()}\n\n`);
|
|
114
|
+
}
|
|
77
115
|
const templatesDir = (0, node_path_1.join)(__dirname, "templates");
|
|
78
116
|
try {
|
|
79
117
|
const resolved = await (0, prompt_1.resolveCreateOptions)({ template: args.template, pm: args.pm }, ask, interactive);
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type PackageManager } from "./create";
|
|
2
|
-
export declare const TEMPLATES: readonly ["fullstack-nest-svelte", "base"];
|
|
3
2
|
export declare const PACKAGE_MANAGERS: PackageManager[];
|
|
4
3
|
/** Asks a single question and resolves to the trimmed answer (empty if skipped). */
|
|
5
4
|
export type Ask = (question: string) => Promise<string>;
|
package/dist/prompt.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PACKAGE_MANAGERS =
|
|
3
|
+
exports.PACKAGE_MANAGERS = void 0;
|
|
4
4
|
exports.resolveCreateOptions = resolveCreateOptions;
|
|
5
|
-
const
|
|
6
|
-
exports.TEMPLATES = ["fullstack-nest-svelte", "base"];
|
|
5
|
+
const templates_1 = require("./templates");
|
|
7
6
|
exports.PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
|
|
8
7
|
function isPackageManager(value) {
|
|
9
8
|
return exports.PACKAGE_MANAGERS.includes(value);
|
|
@@ -18,12 +17,12 @@ function isPackageManager(value) {
|
|
|
18
17
|
async function resolveCreateOptions(args, ask, interactive) {
|
|
19
18
|
let template = args.template;
|
|
20
19
|
if (!template && interactive) {
|
|
21
|
-
const answer = await ask(`Template
|
|
20
|
+
const answer = await ask(`Template [${templates_1.DEFAULT_TEMPLATE}]: `);
|
|
22
21
|
template = answer || undefined;
|
|
23
22
|
}
|
|
24
|
-
template = template ??
|
|
25
|
-
if (!
|
|
26
|
-
throw new Error(`Unknown template "${template}". Choose one of: ${
|
|
23
|
+
template = template ?? templates_1.DEFAULT_TEMPLATE;
|
|
24
|
+
if (!(0, templates_1.isKnownTemplate)(template)) {
|
|
25
|
+
throw new Error(`Unknown template "${template}". Choose one of: ${templates_1.TEMPLATE_NAMES.join(", ")}.`);
|
|
27
26
|
}
|
|
28
27
|
let pm = args.pm;
|
|
29
28
|
if (!pm && interactive) {
|
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
# {{projectName}}
|
|
2
2
|
|
|
3
|
-
Full-stack TypeScript
|
|
3
|
+
Full-stack TypeScript starter generated with [PodoKit](https://github.com/podosoft-dev/podokit).
|
|
4
4
|
|
|
5
|
-
- `apps/api` — NestJS API (
|
|
6
|
-
- `apps/web` — SvelteKit app (TailwindCSS v4, shadcn-svelte, typesafe-i18n) that talks to the API through a server-side proxy
|
|
7
|
-
- `infra/` — Docker Compose and k3s manifests
|
|
5
|
+
- `apps/api` — NestJS API: schema-validated env (zod), `/health` + `/health/ready`, Swagger docs at `/api-docs`, a standard error envelope, and TypeORM + PostgreSQL wired up (no domain entities yet — add your own).
|
|
6
|
+
- `apps/web` — SvelteKit app (TailwindCSS v4, shadcn-svelte, typesafe-i18n) that talks to the API through a server-side proxy.
|
|
7
|
+
- `infra/` — Docker Compose (PostgreSQL, Redis) and k3s manifests.
|
|
8
8
|
|
|
9
9
|
## Getting started
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
{{packageManager}} install
|
|
13
13
|
cp .env.example .env
|
|
14
|
+
|
|
15
|
+
# start local PostgreSQL + Redis
|
|
16
|
+
docker compose -f infra/docker/docker-compose.yml up -d
|
|
17
|
+
|
|
14
18
|
{{packageManager}} run dev
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
- API: http://localhost:3000
|
|
21
|
+
- API: http://localhost:3000 — health at `/health`, docs at `/api-docs`
|
|
18
22
|
- Web: http://localhost:5173
|
|
19
23
|
|
|
20
|
-
##
|
|
24
|
+
## Database & migrations
|
|
25
|
+
|
|
26
|
+
The API uses TypeORM with PostgreSQL and ships no domain entities yet. Add an
|
|
27
|
+
entity under `apps/api/src`, register it in `src/database/data-source.ts`, then
|
|
28
|
+
generate and run a migration:
|
|
21
29
|
|
|
22
30
|
```bash
|
|
23
|
-
|
|
31
|
+
{{packageManager}} run migration:generate -w {{projectName}}-api -- src/migrations/Init
|
|
32
|
+
{{packageManager}} run migration:run -w {{projectName}}-api
|
|
24
33
|
```
|
|
25
34
|
|
|
35
|
+
Want a worked example? Generate the `todo` template instead:
|
|
36
|
+
`npx @podosoft/podokit create my-app --template todo`.
|
|
37
|
+
|
|
26
38
|
## Deploy
|
|
27
39
|
|
|
28
|
-
Docker Compose
|
|
40
|
+
Docker Compose in `infra/docker`; example k3s manifests in `infra/k3s`
|
|
29
41
|
(use `secret.example.yaml` as a template — never commit real secrets).
|
|
@@ -7,16 +7,27 @@
|
|
|
7
7
|
"build": "nest build",
|
|
8
8
|
"start": "node dist/main",
|
|
9
9
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
10
|
-
"test": "jest"
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"typeorm": "typeorm-ts-node-commonjs -d src/database/data-source.ts",
|
|
12
|
+
"migration:run": "npm run typeorm -- migration:run",
|
|
13
|
+
"migration:revert": "npm run typeorm -- migration:revert",
|
|
14
|
+
"migration:generate": "npm run typeorm -- migration:generate"
|
|
11
15
|
},
|
|
12
16
|
"dependencies": {
|
|
13
17
|
"@nestjs/common": "^10.4.0",
|
|
18
|
+
"@nestjs/config": "^3.2.3",
|
|
14
19
|
"@nestjs/core": "^10.4.0",
|
|
15
20
|
"@nestjs/platform-express": "^10.4.0",
|
|
21
|
+
"@nestjs/swagger": "^7.4.0",
|
|
22
|
+
"@nestjs/typeorm": "^10.0.2",
|
|
16
23
|
"class-transformer": "^0.5.1",
|
|
17
24
|
"class-validator": "^0.14.1",
|
|
25
|
+
"dotenv": "^16.4.5",
|
|
26
|
+
"pg": "^8.12.0",
|
|
18
27
|
"reflect-metadata": "^0.2.2",
|
|
19
|
-
"rxjs": "^7.8.1"
|
|
28
|
+
"rxjs": "^7.8.1",
|
|
29
|
+
"typeorm": "^0.3.20",
|
|
30
|
+
"zod": "^3.23.8"
|
|
20
31
|
},
|
|
21
32
|
"devDependencies": {
|
|
22
33
|
"@nestjs/cli": "^10.4.0",
|
|
@@ -27,6 +38,7 @@
|
|
|
27
38
|
"jest": "^29.7.0",
|
|
28
39
|
"supertest": "^7.0.0",
|
|
29
40
|
"ts-jest": "^29.2.5",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
30
42
|
"typescript": "^5.6.3"
|
|
31
43
|
}
|
|
32
44
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common";
|
|
2
|
+
import { ConfigModule } from "@nestjs/config";
|
|
3
|
+
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
4
|
+
import { validateEnv } from "./config/env.validation";
|
|
5
|
+
import { dataSourceOptions } from "./database/data-source";
|
|
2
6
|
import { HealthModule } from "./health/health.module";
|
|
7
|
+
// podokit:imports
|
|
3
8
|
|
|
4
9
|
@Module({
|
|
5
|
-
imports: [
|
|
10
|
+
imports: [
|
|
11
|
+
ConfigModule.forRoot({ isGlobal: true, validate: validateEnv }),
|
|
12
|
+
TypeOrmModule.forRoot(dataSourceOptions),
|
|
13
|
+
HealthModule,
|
|
14
|
+
// podokit:module-imports
|
|
15
|
+
],
|
|
6
16
|
})
|
|
7
17
|
export class AppModule {}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Schema-validated environment. Fails fast at boot if something is wrong.
|
|
4
|
+
const schema = z.object({
|
|
5
|
+
NODE_ENV: z.string().default("development"),
|
|
6
|
+
PORT: z.coerce.number().default(3000),
|
|
7
|
+
CORS_ORIGIN: z.string().optional(),
|
|
8
|
+
POSTGRES_HOST: z.string().default("localhost"),
|
|
9
|
+
POSTGRES_PORT: z.coerce.number().default(5432),
|
|
10
|
+
POSTGRES_USER: z.string().default("podokit"),
|
|
11
|
+
POSTGRES_PASSWORD: z.string().default("podokit"),
|
|
12
|
+
POSTGRES_DB: z.string().default("podokit"),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type AppEnv = z.infer<typeof schema>;
|
|
7
16
|
|
|
8
|
-
export function validateEnv(
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
11
|
-
throw new Error(`Invalid
|
|
17
|
+
export function validateEnv(config: Record<string, unknown>): AppEnv {
|
|
18
|
+
const parsed = schema.safeParse(config);
|
|
19
|
+
if (!parsed.success) {
|
|
20
|
+
throw new Error(`Invalid environment:\n${parsed.error.toString()}`);
|
|
12
21
|
}
|
|
13
|
-
return
|
|
14
|
-
nodeEnv: env.NODE_ENV ?? "development",
|
|
15
|
-
port,
|
|
16
|
-
corsOrigin: env.CORS_ORIGIN,
|
|
17
|
-
};
|
|
22
|
+
return parsed.data;
|
|
18
23
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { DataSource, type DataSourceOptions } from "typeorm";
|
|
4
|
+
|
|
5
|
+
export const dataSourceOptions: DataSourceOptions = {
|
|
6
|
+
type: "postgres",
|
|
7
|
+
host: process.env.POSTGRES_HOST ?? "localhost",
|
|
8
|
+
port: Number(process.env.POSTGRES_PORT ?? 5432),
|
|
9
|
+
username: process.env.POSTGRES_USER ?? "podokit",
|
|
10
|
+
password: process.env.POSTGRES_PASSWORD ?? "podokit",
|
|
11
|
+
database: process.env.POSTGRES_DB ?? "podokit",
|
|
12
|
+
// Entities are auto-discovered by file name (*.entity.ts / .js).
|
|
13
|
+
entities: [join(__dirname, "..", "**", "*.entity{.ts,.js}")],
|
|
14
|
+
migrations: [join(__dirname, "..", "migrations", "*{.ts,.js}")],
|
|
15
|
+
synchronize: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Used by the TypeORM CLI for migrations (see package.json scripts).
|
|
19
|
+
export default new DataSource(dataSourceOptions);
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { Controller, Get } from "@nestjs/common";
|
|
2
|
+
import { InjectDataSource } from "@nestjs/typeorm";
|
|
3
|
+
import { DataSource } from "typeorm";
|
|
2
4
|
|
|
3
5
|
@Controller("health")
|
|
4
6
|
export class HealthController {
|
|
7
|
+
constructor(@InjectDataSource() private readonly dataSource: DataSource) {}
|
|
8
|
+
|
|
5
9
|
@Get()
|
|
6
10
|
liveness(): { status: string; uptime: number; timestamp: string } {
|
|
7
|
-
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
return { status: "ok", uptime: process.uptime(), timestamp: new Date().toISOString() };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Get("ready")
|
|
15
|
+
async readiness(): Promise<{ status: string; db: string }> {
|
|
16
|
+
try {
|
|
17
|
+
await this.dataSource.query("SELECT 1");
|
|
18
|
+
return { status: "ready", db: "up" };
|
|
19
|
+
} catch {
|
|
20
|
+
return { status: "degraded", db: "down" };
|
|
21
|
+
}
|
|
12
22
|
}
|
|
13
23
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NestFactory } from "@nestjs/core";
|
|
2
2
|
import { ValidationPipe } from "@nestjs/common";
|
|
3
|
+
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
|
3
4
|
import { AppModule } from "./app.module";
|
|
4
5
|
import { AllExceptionsFilter } from "./common/all-exceptions.filter";
|
|
5
6
|
|
|
@@ -14,6 +15,13 @@ async function bootstrap(): Promise<void> {
|
|
|
14
15
|
const corsOrigin = process.env.CORS_ORIGIN?.split(",").map((o) => o.trim());
|
|
15
16
|
app.enableCors({ origin: corsOrigin ?? true, credentials: true });
|
|
16
17
|
|
|
18
|
+
const config = new DocumentBuilder()
|
|
19
|
+
.setTitle("{{projectName}} API")
|
|
20
|
+
.setDescription("Generated with PodoKit")
|
|
21
|
+
.setVersion("0.0.0")
|
|
22
|
+
.build();
|
|
23
|
+
SwaggerModule.setup("api-docs", app, SwaggerModule.createDocument(app, config));
|
|
24
|
+
|
|
17
25
|
const port = Number(process.env.PORT ?? 3000);
|
|
18
26
|
await app.listen(port);
|
|
19
27
|
}
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"test": "echo \"add Playwright tests here\" && exit 0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"mode-watcher": "^0.5.0"
|
|
14
|
+
"mode-watcher": "^0.5.0",
|
|
15
|
+
"bits-ui": "^2.18.1",
|
|
16
|
+
"clsx": "^2.1.1",
|
|
17
|
+
"tailwind-merge": "^3.6.0",
|
|
18
|
+
"tailwind-variants": "^3.2.2",
|
|
19
|
+
"@lucide/svelte": "^1.23.0",
|
|
20
|
+
"@internationalized/date": "^3.12.2"
|
|
15
21
|
},
|
|
16
22
|
"devDependencies": {
|
|
17
23
|
"@sveltejs/adapter-node": "^5.2.0",
|
|
@@ -23,6 +29,7 @@
|
|
|
23
29
|
"tailwindcss": "^4.0.0",
|
|
24
30
|
"typesafe-i18n": "^5.26.2",
|
|
25
31
|
"typescript": "^5.6.3",
|
|
26
|
-
"vite": "^5.4.0"
|
|
32
|
+
"vite": "^5.4.0",
|
|
33
|
+
"tw-animate-css": "^1.4.0"
|
|
27
34
|
}
|
|
28
35
|
}
|