@kevinmarrec/create-app 0.11.0 → 0.12.1
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 +29 -5
- package/dist/index.js +8 -13
- package/package.json +10 -10
- package/template/.docker/analytics/service.yaml +22 -0
- package/template/.docker/mailpit/service.yaml +13 -0
- package/template/.docker/metabase/service.yaml +27 -0
- package/template/.docker/postgres/service.yaml +20 -0
- package/template/.docker/studio/service.yaml +22 -0
- package/template/.docker/traefik/certs/.gitkeep +0 -0
- package/template/.docker/traefik/service.yaml +41 -0
- package/template/.github/renovate.json +6 -1
- package/template/.vscode/settings.json +10 -5
- package/template/README.md +92 -194
- package/template/api/Dockerfile +45 -0
- package/template/api/package.json +6 -4
- package/template/api/src/env.ts +18 -5
- package/template/api/tsconfig.json +1 -1
- package/template/app/package.json +12 -8
- package/template/app/src/env.d.ts +1 -5
- package/template/app/tsconfig.json +8 -3
- package/template/app/vite.config.ts +61 -41
- package/template/compose.yaml +14 -130
- package/template/knip.config.ts +3 -2
- package/template/package.json +5 -5
- package/template/tsconfig.json +1 -8
package/README.md
CHANGED
|
@@ -2,17 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## Description
|
|
4
4
|
|
|
5
|
-
CLI that scaffolds an opinionated [Bun](https://bun.sh) & [Vue](https://vuejs.org) fullstack application.
|
|
5
|
+
CLI that scaffolds an opinionated [Bun](https://bun.sh/) & [Vue](https://vuejs.org/) fullstack application.
|
|
6
6
|
|
|
7
7
|
## Opinions
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- [
|
|
9
|
+
### Core Stack
|
|
10
|
+
|
|
11
|
+
- [Bun](https://bun.sh/) as package manager & runtime
|
|
12
|
+
- [TypeScript](https://www.typescriptlang.org/) as the language
|
|
13
|
+
- [Vue 3](https://vuejs.org/) as frontend framework
|
|
14
|
+
- [Vite](https://vitejs.dev/) as build tool
|
|
15
|
+
- [Vite SSG](https://github.com/antfu-collective/vite-ssg) for static site generation
|
|
16
|
+
- [UnoCSS](https://unocss.dev/) for styling
|
|
17
|
+
- [TanStack Query](https://tanstack.com/query/latest) for data fetching
|
|
18
|
+
- [Unhead](https://unhead.unjs.io/) for head management
|
|
19
|
+
- [Vue I18n](https://github.com/kevinmarrec/vue-i18n) for internationalization
|
|
20
|
+
- [oRPC](https://orpc.dev/) for API layer
|
|
21
|
+
- [Better Auth](https://www.better-auth.com/) for authentication
|
|
22
|
+
- [Pino](https://getpino.io/) for logging
|
|
23
|
+
- [Drizzle ORM](https://orm.drizzle.team/) as SQL-first TypeScript ORM
|
|
24
|
+
- [Valibot](https://valibot.dev/) for validation
|
|
25
|
+
- [PostgreSQL](https://www.postgresql.org/) as database
|
|
26
|
+
- [Docker Compose](https://docs.docker.com/compose/) & [Traefik](https://traefik.io/) for infrastructure
|
|
27
|
+
|
|
28
|
+
### Development Tools
|
|
29
|
+
|
|
30
|
+
- [ESLint](https://eslint.org/) & [Stylelint](https://stylelint.io/) for linting
|
|
31
|
+
- [Knip](https://knip.dev/) for detecting unused dependencies, exports, and files
|
|
32
|
+
- [Metabase](https://www.metabase.com/) for Business Intelligence (BI)
|
|
33
|
+
- [Drizzle Studio](https://gateway.drizzle.team/) for database management (web GUI)
|
|
34
|
+
- [Umami](https://umami.is/) for web analytics
|
|
35
|
+
- [Mailpit](https://mailpit.axllent.org/) for email testing
|
|
12
36
|
|
|
13
37
|
## Usage
|
|
14
38
|
|
|
15
|
-
> Requires [Bun](https://bun.sh) v1.3 _or later_.
|
|
39
|
+
> Requires [Bun](https://bun.sh/) v1.3 _or later_.
|
|
16
40
|
|
|
17
41
|
```sh
|
|
18
42
|
bun create @kevinmarrec/app
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,12 @@ import { x } from "tinyexec";
|
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.
|
|
11
|
+
var version = "0.12.1";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/utils/fs.ts
|
|
15
|
-
const
|
|
15
|
+
const IGNORED_FILES = new Set([".git"]);
|
|
16
|
+
const ignorePredicate = (filename) => IGNORED_FILES.has(filename);
|
|
16
17
|
async function empty(dir) {
|
|
17
18
|
const entries = await fs.readdir(dir);
|
|
18
19
|
await Promise.all(entries.filter((entry) => !ignorePredicate(entry)).map((entry) => fs.rm(resolve(dir, entry), { recursive: true })));
|
|
@@ -33,7 +34,7 @@ var fs_default = {
|
|
|
33
34
|
//#endregion
|
|
34
35
|
//#region src/scaffold.ts
|
|
35
36
|
async function scaffold(root) {
|
|
36
|
-
await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root);
|
|
37
|
+
await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root, { recursive: true });
|
|
37
38
|
await fs_default.cp(join(import.meta.dirname, "../template"), root, { recursive: true });
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -65,14 +66,7 @@ async function run() {
|
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
if (options.help) {
|
|
68
|
-
process.stdout.write(
|
|
69
|
-
Usage: create-app [OPTIONS...] [DIRECTORY]
|
|
70
|
-
|
|
71
|
-
Options:
|
|
72
|
-
-f, --force Create the project even if the directory is not empty.
|
|
73
|
-
-h, --help Display this help message.
|
|
74
|
-
-v, --version Display the version number of this CLI.
|
|
75
|
-
`);
|
|
69
|
+
process.stdout.write("Usage: create-app [OPTIONS...] [DIRECTORY]\n\nOptions:\n -f, --force Create the project even if the directory is not empty.\n -h, --help Display this help message.\n -v, --version Display the version number of this CLI.\n");
|
|
76
70
|
process.exit(0);
|
|
77
71
|
}
|
|
78
72
|
if (options.version) {
|
|
@@ -81,7 +75,7 @@ Options:
|
|
|
81
75
|
}
|
|
82
76
|
process.stdout.write("\n");
|
|
83
77
|
intro(`create-app ${c.dim(`v${version}`)}`);
|
|
84
|
-
let projectName = positionals[0] || await text({
|
|
78
|
+
let projectName = positionals[0]?.trim() || await text({
|
|
85
79
|
message: "Project name",
|
|
86
80
|
placeholder: "my-app",
|
|
87
81
|
validate: (value) => {
|
|
@@ -92,7 +86,7 @@ Options:
|
|
|
92
86
|
projectName = projectName.trim();
|
|
93
87
|
const cwd = process.cwd();
|
|
94
88
|
const targetDir = resolve(cwd, projectName);
|
|
95
|
-
if (!
|
|
89
|
+
if (!options.force && !await fs_default.emptyCheck(targetDir)) {
|
|
96
90
|
await log.warn(`${targetDir === cwd ? "Current directory" : `Target directory ${c.blue(targetDir)}`} is not empty`);
|
|
97
91
|
maybeCancel(await confirm({
|
|
98
92
|
message: "Remove existing files and continue?",
|
|
@@ -136,6 +130,7 @@ Options:
|
|
|
136
130
|
//#region src/index.ts
|
|
137
131
|
run().catch((error) => {
|
|
138
132
|
console.error(error);
|
|
133
|
+
process.exitCode = 1;
|
|
139
134
|
});
|
|
140
135
|
|
|
141
136
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevinmarrec/create-app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"packageManager": "bun@1.3.3",
|
|
6
6
|
"description": "CLI that scaffolds an opinionated Bun & Vue fullstack application.",
|
|
7
7
|
"author": "Kevin Marrec <kevin@marrec.io>",
|
|
@@ -53,18 +53,18 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@faker-js/faker": "^10.1.0",
|
|
56
|
-
"@kevinmarrec/eslint-config": "^1.5.
|
|
57
|
-
"@kevinmarrec/stylelint-config": "^1.5.
|
|
58
|
-
"@kevinmarrec/tsconfig": "^1.5.
|
|
56
|
+
"@kevinmarrec/eslint-config": "^1.5.8",
|
|
57
|
+
"@kevinmarrec/stylelint-config": "^1.5.8",
|
|
58
|
+
"@kevinmarrec/tsconfig": "^1.5.8",
|
|
59
59
|
"@types/bun": "^1.3.3",
|
|
60
|
-
"@vitest/coverage-v8": "^4.0.
|
|
61
|
-
"bumpp": "^10.3.
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
61
|
+
"bumpp": "^10.3.2",
|
|
62
62
|
"eslint": "^9.39.1",
|
|
63
|
-
"knip": "^5.70.
|
|
64
|
-
"stylelint": "^16.26.
|
|
65
|
-
"tsdown": "^0.16.
|
|
63
|
+
"knip": "^5.70.2",
|
|
64
|
+
"stylelint": "^16.26.1",
|
|
65
|
+
"tsdown": "^0.16.8",
|
|
66
66
|
"typescript": "^5.9.3",
|
|
67
|
-
"vitest": "^4.0.
|
|
67
|
+
"vitest": "^4.0.14",
|
|
68
68
|
"vue-tsc": "^3.1.5"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
services:
|
|
2
|
+
analytics:
|
|
3
|
+
image: umamisoftware/umami:3.0.1
|
|
4
|
+
container_name: analytics
|
|
5
|
+
depends_on:
|
|
6
|
+
traefik:
|
|
7
|
+
condition: service_healthy
|
|
8
|
+
postgres:
|
|
9
|
+
condition: service_healthy
|
|
10
|
+
environment:
|
|
11
|
+
- DATABASE_URL=postgresql://user:password@postgres:5432/analytics
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: [CMD-SHELL, curl -f http://localhost:3000]
|
|
14
|
+
interval: 1s
|
|
15
|
+
timeout: 1s
|
|
16
|
+
retries: 20
|
|
17
|
+
labels:
|
|
18
|
+
traefik.enable: 'true'
|
|
19
|
+
traefik.http.routers.analytics.rule: Host(`analytics.dev.localhost`)
|
|
20
|
+
traefik.http.routers.analytics.entrypoints: websecure
|
|
21
|
+
traefik.http.routers.analytics.tls: 'true'
|
|
22
|
+
traefik.http.services.analytics.loadbalancer.server.port: '3000'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
services:
|
|
2
|
+
mailpit:
|
|
3
|
+
image: axllent/mailpit:v1.28.0
|
|
4
|
+
container_name: mailpit
|
|
5
|
+
depends_on:
|
|
6
|
+
traefik:
|
|
7
|
+
condition: service_healthy
|
|
8
|
+
labels:
|
|
9
|
+
traefik.enable: 'true'
|
|
10
|
+
traefik.http.routers.mailpit.rule: Host(`mail.dev.localhost`)
|
|
11
|
+
traefik.http.routers.mailpit.entrypoints: websecure
|
|
12
|
+
traefik.http.routers.mailpit.tls: 'true'
|
|
13
|
+
traefik.http.services.mailpit.loadbalancer.server.port: '8025'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
metabase:
|
|
3
|
+
image: metabase/metabase:v0.57.4
|
|
4
|
+
container_name: metabase
|
|
5
|
+
depends_on:
|
|
6
|
+
traefik:
|
|
7
|
+
condition: service_healthy
|
|
8
|
+
environment:
|
|
9
|
+
- MB_DB_TYPE=h2
|
|
10
|
+
- MB_DB_FILE=/var/lib/metabase/metabase.db
|
|
11
|
+
- MB_SITE_URL=https://metabase.dev.localhost
|
|
12
|
+
volumes:
|
|
13
|
+
- metabase_data:/var/lib/metabase
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: [CMD-SHELL, curl -f http://localhost:3000/api/health]
|
|
16
|
+
interval: 1s
|
|
17
|
+
timeout: 1s
|
|
18
|
+
retries: 20
|
|
19
|
+
labels:
|
|
20
|
+
traefik.enable: 'true'
|
|
21
|
+
traefik.http.routers.metabase.rule: Host(`metabase.dev.localhost`)
|
|
22
|
+
traefik.http.routers.metabase.entrypoints: websecure
|
|
23
|
+
traefik.http.routers.metabase.tls: 'true'
|
|
24
|
+
traefik.http.services.metabase.loadbalancer.server.port: '3000'
|
|
25
|
+
|
|
26
|
+
volumes:
|
|
27
|
+
metabase_data:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:18.1-alpine
|
|
4
|
+
container_name: postgres
|
|
5
|
+
environment:
|
|
6
|
+
- POSTGRES_USER=user
|
|
7
|
+
- POSTGRES_PASSWORD=password
|
|
8
|
+
ports:
|
|
9
|
+
- 5432:5432
|
|
10
|
+
volumes:
|
|
11
|
+
- postgres_data:/var/lib/postgresql/data
|
|
12
|
+
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: [CMD-SHELL, "psql postgresql://user:password@postgres:5432/app -c 'SELECT 1' 2> /dev/null"]
|
|
15
|
+
interval: 1s
|
|
16
|
+
timeout: 1s
|
|
17
|
+
retries: 20
|
|
18
|
+
|
|
19
|
+
volumes:
|
|
20
|
+
postgres_data:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
services:
|
|
2
|
+
studio:
|
|
3
|
+
image: ghcr.io/drizzle-team/gateway:1.1.1
|
|
4
|
+
container_name: studio
|
|
5
|
+
depends_on:
|
|
6
|
+
traefik:
|
|
7
|
+
condition: service_healthy
|
|
8
|
+
postgres:
|
|
9
|
+
condition: service_healthy
|
|
10
|
+
environment:
|
|
11
|
+
- MASTERPASS=foobar
|
|
12
|
+
volumes:
|
|
13
|
+
- studio_data:/app
|
|
14
|
+
labels:
|
|
15
|
+
traefik.enable: 'true'
|
|
16
|
+
traefik.http.routers.studio.rule: Host(`studio.dev.localhost`)
|
|
17
|
+
traefik.http.routers.studio.entrypoints: websecure
|
|
18
|
+
traefik.http.routers.studio.tls: 'true'
|
|
19
|
+
traefik.http.services.studio.loadbalancer.server.port: '4983'
|
|
20
|
+
|
|
21
|
+
volumes:
|
|
22
|
+
studio_data:
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
services:
|
|
2
|
+
traefik:
|
|
3
|
+
image: traefik:v3.6.2
|
|
4
|
+
container_name: traefik
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
security_opt:
|
|
7
|
+
- no-new-privileges:true
|
|
8
|
+
command:
|
|
9
|
+
# General configuration
|
|
10
|
+
- --api.dashboard=true
|
|
11
|
+
- --ping=true
|
|
12
|
+
- --log.level=INFO
|
|
13
|
+
# Entrypoints for HTTP and HTTPS
|
|
14
|
+
- --entrypoints.websecure.address=:443
|
|
15
|
+
- --entrypoints.web.address=:80
|
|
16
|
+
# Redirect HTTP to HTTPS
|
|
17
|
+
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
18
|
+
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
19
|
+
# Providers (Docker & File for dynamic config)
|
|
20
|
+
- --providers.docker=true
|
|
21
|
+
- --providers.docker.exposedbydefault=false
|
|
22
|
+
- --providers.file.directory=/etc/traefik/dynamic
|
|
23
|
+
- --providers.file.watch=true
|
|
24
|
+
ports:
|
|
25
|
+
- 80:80
|
|
26
|
+
- 443:443
|
|
27
|
+
volumes:
|
|
28
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
29
|
+
- ./dynamic:/etc/traefik/dynamic:ro # Mount the dynamic config directory
|
|
30
|
+
- ./certs:/certs:ro # Mount the certs directory
|
|
31
|
+
healthcheck:
|
|
32
|
+
test: [CMD-SHELL, traefik healthcheck --ping]
|
|
33
|
+
interval: 1s
|
|
34
|
+
timeout: 1s
|
|
35
|
+
retries: 20
|
|
36
|
+
labels:
|
|
37
|
+
traefik.enable: 'true'
|
|
38
|
+
traefik.http.routers.traefik.rule: Host(`traefik.dev.localhost`)
|
|
39
|
+
traefik.http.routers.traefik.entrypoints: websecure
|
|
40
|
+
traefik.http.routers.traefik.tls: 'true'
|
|
41
|
+
traefik.http.routers.traefik.service: api@internal
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
-
"extends": ["github>kevinmarrec/renovate-config"]
|
|
3
|
+
"extends": ["github>kevinmarrec/renovate-config"],
|
|
4
|
+
"docker-compose": {
|
|
5
|
+
"managerFilePatterns": [
|
|
6
|
+
"/^\\.docker\\/[^/]*\\/service\\.ya?ml$/"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
4
9
|
}
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"markdown",
|
|
40
40
|
"json",
|
|
41
41
|
"jsonc",
|
|
42
|
-
"yaml"
|
|
42
|
+
"yaml",
|
|
43
|
+
"dockercompose"
|
|
43
44
|
],
|
|
44
45
|
|
|
45
46
|
"css.validate": false,
|
|
@@ -51,10 +52,6 @@
|
|
|
51
52
|
"vue"
|
|
52
53
|
],
|
|
53
54
|
|
|
54
|
-
"material-icon-theme.folders.associations": {
|
|
55
|
-
"orpc": "context"
|
|
56
|
-
},
|
|
57
|
-
|
|
58
55
|
"javascript.format.semicolons": "remove",
|
|
59
56
|
"javascript.preferences.quoteStyle": "single",
|
|
60
57
|
"typescript.format.semicolons": "remove",
|
|
@@ -63,6 +60,14 @@
|
|
|
63
60
|
"vue.inlayHints.inlineHandlerLeading": true,
|
|
64
61
|
"vue.inlayHints.missingProps": true,
|
|
65
62
|
|
|
63
|
+
"files.associations": {
|
|
64
|
+
"**/.docker/*/service.yaml": "dockercompose"
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
"material-icon-theme.folders.associations": {
|
|
68
|
+
"orpc": "context"
|
|
69
|
+
},
|
|
70
|
+
|
|
66
71
|
// i18n Ally
|
|
67
72
|
"i18n-ally.enabledFrameworks": ["vue"],
|
|
68
73
|
"i18n-ally.keystyle": "nested",
|
package/template/README.md
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
# Project Template
|
|
2
2
|
|
|
3
|
-
A full-stack application template with
|
|
3
|
+
A full-stack application template with Vue.js frontend, Bun-based API backend, and PostgreSQL database.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
This template provides a modern full-stack application structure with:
|
|
5
|
+
## Tech Stack
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
### Core Stack
|
|
8
|
+
|
|
9
|
+
- [Bun](https://bun.sh/) as package manager & runtime
|
|
10
|
+
- [TypeScript](https://www.typescriptlang.org/) as the language
|
|
11
|
+
- [Vue 3](https://vuejs.org/) as frontend framework
|
|
12
|
+
- [Vite](https://vitejs.dev/) as build tool
|
|
13
|
+
- [Vite SSG](https://github.com/antfu-collective/vite-ssg) for static site generation
|
|
14
|
+
- [UnoCSS](https://unocss.dev/) for styling
|
|
15
|
+
- [TanStack Query](https://tanstack.com/query/latest) for data fetching
|
|
16
|
+
- [Unhead](https://unhead.unjs.io/) for head management
|
|
17
|
+
- [Vue I18n](https://github.com/kevinmarrec/vue-i18n) for internationalization
|
|
18
|
+
- [oRPC](https://orpc.dev/) for API layer
|
|
19
|
+
- [Better Auth](https://www.better-auth.com/) for authentication
|
|
20
|
+
- [Pino](https://getpino.io/) for logging
|
|
21
|
+
- [Drizzle ORM](https://orm.drizzle.team/) as SQL-first TypeScript ORM
|
|
22
|
+
- [Valibot](https://valibot.dev/) for validation
|
|
23
|
+
- [PostgreSQL](https://www.postgresql.org/) as database
|
|
24
|
+
- [Docker Compose](https://docs.docker.com/compose/) & [Traefik](https://traefik.io/) for infrastructure
|
|
25
|
+
|
|
26
|
+
### Development Tools
|
|
27
|
+
|
|
28
|
+
- [ESLint](https://eslint.org/) & [Stylelint](https://stylelint.io/) for linting
|
|
29
|
+
- [Knip](https://knip.dev/) for detecting unused dependencies, exports, and files
|
|
30
|
+
- [Metabase](https://www.metabase.com/) for Business Intelligence (BI)
|
|
31
|
+
- [Drizzle Studio](https://gateway.drizzle.team/) for database management (web GUI)
|
|
32
|
+
- [Umami](https://umami.is/) for web analytics
|
|
33
|
+
- [Mailpit](https://mailpit.axllent.org/) for email testing
|
|
13
34
|
|
|
14
35
|
## Prerequisites
|
|
15
36
|
|
|
@@ -27,14 +48,18 @@ project/
|
|
|
27
48
|
│ │ ├── auth/ # Authentication setup (Better Auth)
|
|
28
49
|
│ │ ├── database/ # Database schema and migrations (Drizzle)
|
|
29
50
|
│ │ ├── orpc/ # Router definitions (oRPC)
|
|
51
|
+
│ │ ├── utils/ # Utility functions (CORS, logger, etc.)
|
|
30
52
|
│ │ ├── env.ts # Environment variable loading and validation (Valibot)
|
|
31
53
|
│ │ └── main.ts # API entry point
|
|
32
54
|
│ └── package.json
|
|
33
55
|
├── app/ # Frontend Vue application
|
|
56
|
+
│ ├── public/ # Public static assets
|
|
34
57
|
│ ├── src/
|
|
35
58
|
│ │ ├── components/ # Vue components
|
|
36
59
|
│ │ ├── composables/ # Vue composables (auth, etc.)
|
|
37
60
|
│ │ ├── lib/ # Library utilities (oRPC client)
|
|
61
|
+
│ │ ├── locales/ # Internationalization files (i18n)
|
|
62
|
+
│ │ ├── App.vue # Main Vue component
|
|
38
63
|
│ │ └── main.ts # App entry point
|
|
39
64
|
│ └── package.json
|
|
40
65
|
├── compose.yaml # Docker Compose configuration
|
|
@@ -51,60 +76,40 @@ bun install
|
|
|
51
76
|
|
|
52
77
|
### 2. Set Up Environment Variables
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#### API Environment Variables
|
|
57
|
-
|
|
58
|
-
Create a `.env.development` file in the `api/` directory with the following variables:
|
|
79
|
+
#### API (`api/.env.development`)
|
|
59
80
|
|
|
60
81
|
```bash
|
|
61
|
-
|
|
82
|
+
ALLOWED_ORIGINS=https://app.dev.localhost
|
|
62
83
|
AUTH_SECRET=your-secret-key-here
|
|
63
84
|
DATABASE_URL=postgresql://user:password@postgres:5432/app
|
|
64
|
-
ALLOWED_ORIGINS=https://app.dev.localhost
|
|
65
85
|
LOG_LEVEL=info
|
|
66
86
|
HOST=0.0.0.0
|
|
67
87
|
PORT=4000
|
|
68
88
|
```
|
|
69
89
|
|
|
70
|
-
**Required
|
|
71
|
-
|
|
72
|
-
- `AUTH_SECRET` - Secret key for authentication encryption
|
|
73
|
-
- `DATABASE_URL` - PostgreSQL connection string
|
|
74
|
-
|
|
75
|
-
**Optional variables:**
|
|
76
|
-
|
|
77
|
-
- `ALLOWED_ORIGINS` - Comma-separated list of allowed CORS origins (default: empty)
|
|
78
|
-
- `LOG_LEVEL` - Logging level: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, or `silent` (default: `info`)
|
|
79
|
-
- `HOST` - Server host (default: `0.0.0.0`)
|
|
80
|
-
- `PORT` - Server port (default: `4000`)
|
|
90
|
+
**Required:** `ALLOWED_ORIGINS`, `AUTH_SECRET`, `DATABASE_URL`
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
**Optional:** `LOG_LEVEL` (default: `info`), `HOST` (default: `0.0.0.0`), `PORT` (default: `4000`)
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
#### App (`app/.env`)
|
|
85
95
|
|
|
86
96
|
```bash
|
|
87
|
-
# app/.env
|
|
88
97
|
VITE_API_URL=https://api.dev.localhost
|
|
98
|
+
VITE_ANALYTICS_URL=https://analytics.dev.localhost
|
|
99
|
+
VITE_ANALYTICS_WEBSITE_ID=your-website-id-here
|
|
89
100
|
```
|
|
90
101
|
|
|
91
|
-
**Required
|
|
92
|
-
|
|
93
|
-
- `VITE_API_URL` - API base URL for the frontend
|
|
102
|
+
**Required:** `VITE_API_URL`, `VITE_ANALYTICS_URL`, `VITE_ANALYTICS_WEBSITE_ID`
|
|
94
103
|
|
|
95
104
|
### 3. Set Up Local TLS Certificates
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Check their [installation guide](https://github.com/FiloSottile/mkcert#installation) for detailed instructions on installing mkcert.
|
|
100
|
-
|
|
101
|
-
Then, run the following command to generate the certificates:
|
|
106
|
+
Install [mkcert](https://github.com/FiloSottile/mkcert), then run:
|
|
102
107
|
|
|
103
108
|
```bash
|
|
104
109
|
.docker/traefik/bin/install_cert
|
|
105
110
|
```
|
|
106
111
|
|
|
107
|
-
This
|
|
112
|
+
This generates certificates in `.docker/traefik/certs/` and installs the root CA into your system trust store.
|
|
108
113
|
|
|
109
114
|
> [!IMPORTANT]
|
|
110
115
|
> If your system is running on WSL and you are using Firefox or Zen Browser, **you must import the mkcert root CA into your browser trust store.**
|
|
@@ -122,210 +127,103 @@ This will generate the certificates in `.docker/traefik/certs/` and install the
|
|
|
122
127
|
> - Check **"Trust this CA to identify websites"**
|
|
123
128
|
> - Click **OK**
|
|
124
129
|
|
|
125
|
-
### 4. Start
|
|
126
|
-
|
|
127
|
-
Start all services (Traefik, PostgreSQL, Metabase, API, and App):
|
|
130
|
+
### 4. Start Services
|
|
128
131
|
|
|
129
132
|
```bash
|
|
130
|
-
|
|
133
|
+
bun run dev up -d
|
|
131
134
|
```
|
|
132
135
|
|
|
133
|
-
|
|
136
|
+
**Services:**
|
|
134
137
|
|
|
135
138
|
- Frontend: https://app.dev.localhost
|
|
136
139
|
- API: https://api.dev.localhost
|
|
137
|
-
- Traefik
|
|
140
|
+
- Traefik: https://traefik.dev.localhost
|
|
138
141
|
- Metabase: https://metabase.dev.localhost
|
|
142
|
+
- Drizzle Studio: https://studio.dev.localhost (password: `foobar`)
|
|
143
|
+
- Umami: https://analytics.dev.localhost
|
|
144
|
+
- Mailpit: https://mail.dev.localhost
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
To stop all Docker services:
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
docker compose down
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
To stop and remove volumes (⚠️ this will delete database data):
|
|
146
|
+
**Stop services:**
|
|
149
147
|
|
|
150
148
|
```bash
|
|
151
|
-
|
|
149
|
+
bun run dev down # Stop services
|
|
150
|
+
bun run dev down -v # Stop and remove volumes (⚠️ deletes data)
|
|
152
151
|
```
|
|
153
152
|
|
|
154
|
-
##
|
|
155
|
-
|
|
156
|
-
### Running Services Locally (without Docker)
|
|
157
|
-
|
|
158
|
-
You can also run the services locally without Docker. Note that you'll still need PostgreSQL running (either locally or via Docker).
|
|
153
|
+
## Scripts
|
|
159
154
|
|
|
160
|
-
|
|
155
|
+
**Root:**
|
|
161
156
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
bun run
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
The API will run on `http://localhost:4000` (or the port specified in `PORT`).
|
|
157
|
+
- `bun run dev` - Docker Compose shortcut
|
|
158
|
+
- `bun run check` - Run all checks (lint, typecheck, unused deps)
|
|
159
|
+
- `bun run lint` - Run ESLint and Stylelint
|
|
160
|
+
- `bun run update` - Update dependencies
|
|
168
161
|
|
|
169
|
-
|
|
162
|
+
**API (`cd api`):**
|
|
170
163
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
bun run
|
|
174
|
-
|
|
164
|
+
- `bun run dev` - Dev server (auto-migrations, hot reload)
|
|
165
|
+
- `bun run build` - Build production binary
|
|
166
|
+
- `bun run db:generate` - Generate migrations
|
|
167
|
+
- `bun run db:migrate` - Run migrations
|
|
175
168
|
|
|
176
|
-
|
|
169
|
+
**App (`cd app`):**
|
|
177
170
|
|
|
178
|
-
|
|
171
|
+
- `bun run dev` - Dev server (HMR)
|
|
172
|
+
- `bun run build` - Build for production
|
|
173
|
+
- `bun run build:analyze` - Build with bundle analyzer
|
|
174
|
+
- `bun run preview` - Preview production build
|
|
179
175
|
|
|
180
|
-
|
|
176
|
+
## Database Migrations
|
|
181
177
|
|
|
182
178
|
After modifying the database schema in `api/src/database/schema/`, generate migrations:
|
|
183
179
|
|
|
184
180
|
```bash
|
|
185
|
-
cd api
|
|
186
|
-
bun run db:generate
|
|
181
|
+
cd api && bun run db:generate
|
|
187
182
|
```
|
|
188
183
|
|
|
189
|
-
#### Run Migrations
|
|
190
|
-
|
|
191
184
|
Migrations run automatically when starting the API in dev mode. To run manually:
|
|
192
185
|
|
|
193
186
|
```bash
|
|
194
|
-
cd api
|
|
195
|
-
bun run db:migrate
|
|
187
|
+
cd api && bun run db:migrate
|
|
196
188
|
```
|
|
197
189
|
|
|
198
|
-
##
|
|
199
|
-
|
|
200
|
-
### Root Level Scripts
|
|
201
|
-
|
|
202
|
-
- `bun run dev` - Alias for `docker compose`
|
|
203
|
-
- `bun run check` - Run all checks (ESLint, Stylelint, unused dependencies, TypeScript)
|
|
204
|
-
- `bun run lint` - Run linting (ESLint and Stylelint)
|
|
205
|
-
- `bun run lint:inspect` - Open ESLint config inspector
|
|
206
|
-
- `bun run update` - Update dependencies
|
|
207
|
-
|
|
208
|
-
### API Scripts (`cd api`)
|
|
190
|
+
## Docker Services
|
|
209
191
|
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
- `bun run build` - Build for production (static site)
|
|
219
|
-
- `bun run build:analyze` - Build with bundle analyzer
|
|
220
|
-
- `bun run preview` - Preview production build
|
|
221
|
-
|
|
222
|
-
## Docker Compose Services
|
|
223
|
-
|
|
224
|
-
### Traefik
|
|
225
|
-
|
|
226
|
-
Reverse proxy and load balancer that handles:
|
|
227
|
-
|
|
228
|
-
- HTTPS termination
|
|
229
|
-
- SSL certificate management (requires certificates in `.docker/traefik/certs/`)
|
|
230
|
-
- Routing to services based on hostnames
|
|
231
|
-
- Dashboard accessible at https://traefik.dev.localhost
|
|
232
|
-
|
|
233
|
-
### PostgreSQL
|
|
234
|
-
|
|
235
|
-
Database service with:
|
|
236
|
-
|
|
237
|
-
- Default database: `app`
|
|
238
|
-
- Default user: `user`
|
|
239
|
-
- Default password: `password`
|
|
240
|
-
- Port: `5432`
|
|
241
|
-
- Persistent volume: `postgres_data`
|
|
242
|
-
|
|
243
|
-
### Metabase
|
|
244
|
-
|
|
245
|
-
Business intelligence tool for data visualization and analytics with:
|
|
246
|
-
|
|
247
|
-
- Persistent volume: `metabase_data`
|
|
248
|
-
- Accessible via Traefik at https://metabase.dev.localhost (runs on port `3000` internally)
|
|
249
|
-
|
|
250
|
-
### API
|
|
251
|
-
|
|
252
|
-
Backend API service running Bun with:
|
|
192
|
+
- **Traefik** - Reverse proxy (HTTPS termination, routing)
|
|
193
|
+
- **PostgreSQL** - Database (`postgresql://user:password@postgres:5432/app`, port `5432`)
|
|
194
|
+
- **API** - Backend (`/health`, `/auth/*`, `/rpc/*`)
|
|
195
|
+
- **App** - Frontend (Vue + Vite)
|
|
196
|
+
- **Metabase** - BI tool
|
|
197
|
+
- **Drizzle Studio** - DB management (password: `foobar`)
|
|
198
|
+
- **Umami** - Analytics
|
|
199
|
+
- **Mailpit** - Email testing (REST API at `/api/v1/`)
|
|
253
200
|
|
|
254
|
-
|
|
255
|
-
- Automatic database migrations on startup
|
|
256
|
-
- Health check endpoint: `/health`
|
|
257
|
-
- Auth endpoints: `/auth/*`
|
|
258
|
-
- RPC endpoints: `/rpc/*`
|
|
259
|
-
- Accessible via Traefik at https://api.dev.localhost (runs on port `4000` internally)
|
|
201
|
+
## Production Build
|
|
260
202
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Frontend Vue application with:
|
|
264
|
-
|
|
265
|
-
- Vite dev server
|
|
266
|
-
- Hot module replacement
|
|
267
|
-
- Accessible via Traefik at https://app.dev.localhost (runs on port `5173` internally)
|
|
268
|
-
|
|
269
|
-
## Environment Variables
|
|
270
|
-
|
|
271
|
-
For detailed environment variable setup, see [Set Up Environment Variables](#2-set-up-environment-variables) in the Getting Started section.
|
|
272
|
-
|
|
273
|
-
**Quick reference:**
|
|
274
|
-
|
|
275
|
-
- **API** (`.env.development`): `AUTH_SECRET` (required), `DATABASE_URL` (required), `ALLOWED_ORIGINS`, `LOG_LEVEL`, `HOST`, `PORT`
|
|
276
|
-
- **App** (`.env`): `VITE_API_URL` (required)
|
|
277
|
-
|
|
278
|
-
## Building for Production
|
|
279
|
-
|
|
280
|
-
### Build API
|
|
203
|
+
**API:**
|
|
281
204
|
|
|
282
205
|
```bash
|
|
283
|
-
cd api
|
|
284
|
-
bun run build
|
|
206
|
+
cd api && bun run build
|
|
285
207
|
```
|
|
286
208
|
|
|
287
|
-
|
|
209
|
+
Output: `api/dist/api`
|
|
288
210
|
|
|
289
|
-
|
|
211
|
+
**App:**
|
|
290
212
|
|
|
291
213
|
```bash
|
|
292
|
-
cd app
|
|
293
|
-
bun run build
|
|
214
|
+
cd app && bun run build
|
|
294
215
|
```
|
|
295
216
|
|
|
296
|
-
|
|
217
|
+
Output: `app/dist/`
|
|
297
218
|
|
|
298
219
|
## Troubleshooting
|
|
299
220
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
If ports 80, 443, or 5432 are already in use, modify the port mappings in `compose.yaml`.
|
|
221
|
+
**Port conflicts:** Modify port mappings in `compose.yaml` if ports 80, 443, or 5432 are in use.
|
|
303
222
|
|
|
304
|
-
|
|
223
|
+
**SSL warnings:** Complete [TLS setup](#3-set-up-local-tls-certificates). Without certificates, Traefik may fail to start.
|
|
305
224
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
**Note**: Without proper certificates, Traefik may fail to start or services may not be accessible via HTTPS.
|
|
309
|
-
|
|
310
|
-
### Service Logs
|
|
311
|
-
|
|
312
|
-
To view logs for a specific service:
|
|
225
|
+
**View logs:**
|
|
313
226
|
|
|
314
227
|
```bash
|
|
315
|
-
|
|
228
|
+
bun run dev logs -f <service-name> # e.g., api, app, traefik
|
|
316
229
|
```
|
|
317
|
-
|
|
318
|
-
For example:
|
|
319
|
-
|
|
320
|
-
- `docker compose logs -f api` - View API logs
|
|
321
|
-
- `docker compose logs -f app` - View app logs
|
|
322
|
-
- `docker compose logs -f traefik` - View Traefik logs
|
|
323
|
-
|
|
324
|
-
## Tech Stack
|
|
325
|
-
|
|
326
|
-
- **Runtime**: Bun
|
|
327
|
-
- **Language**: TypeScript
|
|
328
|
-
- **Frontend**: Vue 3, Vite, UnoCSS, TanStack Query
|
|
329
|
-
- **Backend**: Bun, oRPC, Better Auth, Drizzle ORM
|
|
330
|
-
- **Database**: PostgreSQL
|
|
331
|
-
- **Infrastructure**: Docker Compose, Traefik
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# --- Stage 1: Dependency Installation & Caching (The Build Stage) ---
|
|
2
|
+
|
|
3
|
+
ARG TARGETARCH
|
|
4
|
+
|
|
5
|
+
# Use a bun-alpine image for a fast build environment
|
|
6
|
+
FROM oven/bun:1.3.3-slim AS build
|
|
7
|
+
|
|
8
|
+
# Set the working directory inside the container
|
|
9
|
+
WORKDIR /build
|
|
10
|
+
|
|
11
|
+
# 1. Copy necessary files for the cache layer
|
|
12
|
+
# IMPORTANT: The path to the root lock file is relative to the Docker build context.
|
|
13
|
+
# If you run 'docker build -f api/Dockerfile .', the context is the root (.).
|
|
14
|
+
# If you run 'docker build api', the context is api (and this approach fails).
|
|
15
|
+
# ASSUMPTION: You are running the build command from the root level.
|
|
16
|
+
|
|
17
|
+
# Copy workspace root files (This ensures the locked install)
|
|
18
|
+
COPY package.json bun.lock ./
|
|
19
|
+
|
|
20
|
+
# Copy service package.json files
|
|
21
|
+
COPY api/package.json ./api/package.json
|
|
22
|
+
COPY app/package.json ./app/package.json
|
|
23
|
+
|
|
24
|
+
# 2. Perform a frozen install
|
|
25
|
+
RUN bun install --filter api --frozen-lockfile
|
|
26
|
+
|
|
27
|
+
# Copy API source code
|
|
28
|
+
COPY api/ ./api/
|
|
29
|
+
|
|
30
|
+
# 3. Build the API
|
|
31
|
+
RUN bun --cwd api build --target bun-linux-$TARGETARCH-modern
|
|
32
|
+
|
|
33
|
+
# --- Stage 2: Final Image (The Runtime Stage) ---
|
|
34
|
+
|
|
35
|
+
# Use a minimal image for production (less attack surface, smaller size)
|
|
36
|
+
FROM debian:trixie-slim
|
|
37
|
+
|
|
38
|
+
# Copy the compiled binary from builder stage
|
|
39
|
+
COPY --from=build /build/api/dist/api /usr/local/bin/
|
|
40
|
+
|
|
41
|
+
# Expose port
|
|
42
|
+
EXPOSE 4000
|
|
43
|
+
|
|
44
|
+
# Run the binary
|
|
45
|
+
ENTRYPOINT ["/usr/local/bin/api"]
|
|
@@ -10,16 +10,18 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@libsql/client": "^0.15.15",
|
|
13
|
-
"@orpc/server": "^1.
|
|
14
|
-
"better-auth": "^1.4.
|
|
13
|
+
"@orpc/server": "^1.12.0",
|
|
14
|
+
"better-auth": "^1.4.3",
|
|
15
15
|
"drizzle-orm": "^0.44.7",
|
|
16
16
|
"pino": "^10.1.0",
|
|
17
|
-
"valibot": "^1.
|
|
17
|
+
"valibot": "^1.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
+
"@kevinmarrec/tsconfig": "^1.5.8",
|
|
20
21
|
"@types/bun": "^1.3.3",
|
|
21
22
|
"drizzle-kit": "^0.31.7",
|
|
22
23
|
"pg": "^8.16.3",
|
|
23
|
-
"pino-pretty": "^13.1.2"
|
|
24
|
+
"pino-pretty": "^13.1.2",
|
|
25
|
+
"typescript": "~5.9.3"
|
|
24
26
|
}
|
|
25
27
|
}
|
package/template/api/src/env.ts
CHANGED
|
@@ -2,16 +2,29 @@ import * as v from 'valibot'
|
|
|
2
2
|
|
|
3
3
|
const schema = v.object({
|
|
4
4
|
auth: v.object({
|
|
5
|
-
secret: v.
|
|
5
|
+
secret: v.pipe(
|
|
6
|
+
v.string(),
|
|
7
|
+
v.nonEmpty(),
|
|
8
|
+
),
|
|
6
9
|
}),
|
|
7
10
|
cors: v.object({
|
|
8
11
|
allowedOrigins: v.pipe(
|
|
9
|
-
v.
|
|
12
|
+
v.string(),
|
|
13
|
+
v.nonEmpty(),
|
|
10
14
|
v.transform(input => input.split(',').filter(Boolean)),
|
|
15
|
+
v.array(v.pipe(
|
|
16
|
+
v.string(),
|
|
17
|
+
v.trim(),
|
|
18
|
+
v.startsWith('https://'),
|
|
19
|
+
)),
|
|
11
20
|
),
|
|
12
21
|
}),
|
|
13
22
|
database: v.object({
|
|
14
|
-
url: v.
|
|
23
|
+
url: v.pipe(
|
|
24
|
+
v.string(),
|
|
25
|
+
v.nonEmpty(),
|
|
26
|
+
v.startsWith('postgresql://'),
|
|
27
|
+
),
|
|
15
28
|
}),
|
|
16
29
|
log: v.object({
|
|
17
30
|
level: v.optional(v.union([
|
|
@@ -28,8 +41,8 @@ const schema = v.object({
|
|
|
28
41
|
host: v.optional(v.string(), '0.0.0.0'),
|
|
29
42
|
port: v.pipe(
|
|
30
43
|
v.optional(v.string(), '4000'),
|
|
31
|
-
v.
|
|
32
|
-
v.
|
|
44
|
+
v.digits(),
|
|
45
|
+
v.toNumber(),
|
|
33
46
|
),
|
|
34
47
|
}),
|
|
35
48
|
})
|
|
@@ -10,25 +10,29 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@kevinmarrec/vue-i18n": "^1.1.3",
|
|
13
|
-
"@orpc/client": "^1.
|
|
14
|
-
"@orpc/tanstack-query": "^1.
|
|
15
|
-
"@tanstack/query-core": "^5.90.
|
|
16
|
-
"@tanstack/vue-query": "^5.
|
|
13
|
+
"@orpc/client": "^1.12.0",
|
|
14
|
+
"@orpc/tanstack-query": "^1.12.0",
|
|
15
|
+
"@tanstack/query-core": "^5.90.11",
|
|
16
|
+
"@tanstack/vue-query": "^5.92.0",
|
|
17
17
|
"@unhead/vue": "^2.0.19",
|
|
18
|
-
"@vueuse/core": "^14.
|
|
19
|
-
"better-auth": "^1.4.
|
|
18
|
+
"@vueuse/core": "^14.1.0",
|
|
19
|
+
"better-auth": "^1.4.3",
|
|
20
20
|
"unocss": "^66.5.9",
|
|
21
|
-
"vue": "^3.5.
|
|
21
|
+
"vue": "^3.5.25"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@kevinmarrec/
|
|
24
|
+
"@kevinmarrec/tsconfig": "^1.5.8",
|
|
25
|
+
"@kevinmarrec/unocss-config": "^1.5.8",
|
|
25
26
|
"@kevinmarrec/vite-plugin-dark-mode": "^1.1.2",
|
|
26
27
|
"@modyfi/vite-plugin-yaml": "^1.1.1",
|
|
27
28
|
"@unhead/addons": "^2.0.19",
|
|
28
29
|
"@vitejs/plugin-vue": "^6.0.2",
|
|
29
30
|
"beasties": "^0.3.5",
|
|
30
31
|
"rollup-plugin-visualizer": "^6.0.5",
|
|
32
|
+
"typescript": "~5.9.3",
|
|
33
|
+
"valibot": "^1.2.0",
|
|
31
34
|
"vite": "^7.2.4",
|
|
35
|
+
"vite-plugin-valibot-env": "^1.0.1",
|
|
32
36
|
"vite-plugin-vue-devtools": "^8.0.5",
|
|
33
37
|
"vite-ssg": "^28.2.2",
|
|
34
38
|
"vite-tsconfig-paths": "^5.1.4"
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
|
|
3
|
-
interface ImportMetaEnv {
|
|
4
|
-
readonly VITE_API_URL: string
|
|
5
|
-
readonly VITE_ANALYTICS_URL: string
|
|
6
|
-
readonly VITE_ANALYTICS_WEBSITE_ID: string
|
|
7
|
-
}
|
|
3
|
+
interface ImportMetaEnv extends ViteEnv {}
|
|
8
4
|
|
|
9
5
|
interface ImportMeta {
|
|
10
6
|
readonly env: ImportMetaEnv
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "
|
|
2
|
+
"extends": "@kevinmarrec/tsconfig",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"lib": ["ESNext", "DOM", "DOM.Iterable"]
|
|
5
|
-
|
|
4
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
5
|
+
"paths": {
|
|
6
|
+
"~/api/*": ["../api/src/*"],
|
|
7
|
+
"~/app/*": ["./src/*"]
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"exclude": ["**/dist/**"]
|
|
6
11
|
}
|
|
@@ -6,60 +6,80 @@ import unhead from '@unhead/addons/vite'
|
|
|
6
6
|
import vue from '@vitejs/plugin-vue'
|
|
7
7
|
import { visualizer } from 'rollup-plugin-visualizer'
|
|
8
8
|
import unocss from 'unocss/vite'
|
|
9
|
-
import
|
|
9
|
+
import * as v from 'valibot'
|
|
10
|
+
import { defineConfig, type PluginOption } from 'vite'
|
|
11
|
+
import valibot from 'vite-plugin-valibot-env'
|
|
10
12
|
import devtools from 'vite-plugin-vue-devtools'
|
|
11
13
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
12
14
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sourcemap: mode === 'analyze',
|
|
19
|
-
},
|
|
20
|
-
builder: {
|
|
21
|
-
async buildApp(builder) {
|
|
22
|
-
if (builder.config.mode === 'static') {
|
|
23
|
-
const { build } = await import('vite-ssg/node')
|
|
24
|
-
await build(builder.config.ssgOptions)
|
|
25
|
-
process.exit(0)
|
|
26
|
-
}
|
|
15
|
+
export const envSchema = v.object({
|
|
16
|
+
VITE_API_URL: v.pipe(v.string(), v.nonEmpty(), v.readonly()),
|
|
17
|
+
VITE_ANALYTICS_URL: v.pipe(v.string(), v.nonEmpty(), v.readonly()),
|
|
18
|
+
VITE_ANALYTICS_WEBSITE_ID: v.pipe(v.string(), v.nonEmpty(), v.readonly()),
|
|
19
|
+
})
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
optionsAPI: command !== 'build',
|
|
35
|
-
},
|
|
36
|
-
}),
|
|
37
|
-
yaml(),
|
|
21
|
+
declare global {
|
|
22
|
+
interface ViteEnv extends v.InferOutput<typeof envSchema> {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default defineConfig(({ command, mode }) => {
|
|
26
|
+
const plugins: PluginOption[] = [
|
|
38
27
|
darkMode(),
|
|
39
|
-
unocss(),
|
|
40
|
-
unhead(),
|
|
41
|
-
tsconfigPaths(),
|
|
42
28
|
devtools({
|
|
43
29
|
componentInspector: {
|
|
44
30
|
toggleComboKey: 'alt-s',
|
|
45
31
|
},
|
|
46
32
|
}),
|
|
47
|
-
|
|
33
|
+
tsconfigPaths(),
|
|
34
|
+
unhead(),
|
|
35
|
+
unocss(),
|
|
36
|
+
valibot(envSchema),
|
|
37
|
+
vue({
|
|
38
|
+
features: {
|
|
39
|
+
optionsAPI: command !== 'build',
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
yaml(),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
if (mode === 'analyze') {
|
|
46
|
+
plugins.push(visualizer({
|
|
48
47
|
filename: 'node_modules/.vite/stats.html',
|
|
49
48
|
brotliSize: true,
|
|
50
49
|
gzipSize: true,
|
|
51
50
|
open: true,
|
|
52
51
|
sourcemap: true,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
}))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
build: {
|
|
57
|
+
modulePreload: {
|
|
58
|
+
polyfill: false,
|
|
59
|
+
},
|
|
60
|
+
sourcemap: mode === 'analyze',
|
|
61
|
+
},
|
|
62
|
+
builder: {
|
|
63
|
+
async buildApp(builder) {
|
|
64
|
+
if (builder.config.mode === 'static') {
|
|
65
|
+
const { build } = await import('vite-ssg/node')
|
|
66
|
+
await build(builder.config.ssgOptions)
|
|
67
|
+
process.exit(0)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await builder.build(builder.environments.client)
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
plugins,
|
|
74
|
+
ssgOptions: {
|
|
75
|
+
script: 'async',
|
|
76
|
+
formatting: 'minify',
|
|
77
|
+
beastiesOptions: {
|
|
78
|
+
reduceInlineStyles: false,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
ssr: {
|
|
82
|
+
noExternal: ['@kevinmarrec/vue-i18n'],
|
|
60
83
|
},
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
noExternal: ['@kevinmarrec/vue-i18n'],
|
|
64
|
-
},
|
|
65
|
-
}))
|
|
84
|
+
}
|
|
85
|
+
})
|
package/template/compose.yaml
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
include:
|
|
2
|
+
- path: .docker/traefik/service.yaml
|
|
3
|
+
- path: .docker/postgres/service.yaml
|
|
4
|
+
- path: .docker/metabase/service.yaml
|
|
5
|
+
- path: .docker/studio/service.yaml
|
|
6
|
+
- path: .docker/analytics/service.yaml
|
|
7
|
+
- path: .docker/mailpit/service.yaml
|
|
8
|
+
|
|
1
9
|
x-common: &common
|
|
2
10
|
volumes:
|
|
3
11
|
- ./:/code
|
|
@@ -5,95 +13,17 @@ x-common: &common
|
|
|
5
13
|
user: 1000:1000
|
|
6
14
|
|
|
7
15
|
services:
|
|
8
|
-
traefik:
|
|
9
|
-
image: traefik:v3.6
|
|
10
|
-
container_name: traefik
|
|
11
|
-
restart: unless-stopped
|
|
12
|
-
security_opt:
|
|
13
|
-
- no-new-privileges:true
|
|
14
|
-
command:
|
|
15
|
-
# General configuration
|
|
16
|
-
- --api.dashboard=true
|
|
17
|
-
- --ping=true
|
|
18
|
-
- --log.level=INFO
|
|
19
|
-
# Entrypoints for HTTP and HTTPS
|
|
20
|
-
- --entrypoints.websecure.address=:443
|
|
21
|
-
- --entrypoints.web.address=:80
|
|
22
|
-
# Redirect HTTP to HTTPS
|
|
23
|
-
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
24
|
-
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
25
|
-
# Providers (Docker & File for dynamic config)
|
|
26
|
-
- --providers.docker=true
|
|
27
|
-
- --providers.docker.exposedbydefault=false
|
|
28
|
-
- --providers.file.directory=/etc/traefik/dynamic
|
|
29
|
-
- --providers.file.watch=true
|
|
30
|
-
ports:
|
|
31
|
-
- 80:80
|
|
32
|
-
- 443:443
|
|
33
|
-
volumes:
|
|
34
|
-
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
35
|
-
- ./.docker/traefik/dynamic:/etc/traefik/dynamic:ro # Mount the dynamic config directory
|
|
36
|
-
- ./.docker/traefik/certs:/certs:ro # Mount the certs directory
|
|
37
|
-
healthcheck:
|
|
38
|
-
test: [CMD-SHELL, traefik healthcheck --ping]
|
|
39
|
-
interval: 1s
|
|
40
|
-
timeout: 1s
|
|
41
|
-
retries: 20
|
|
42
|
-
labels:
|
|
43
|
-
traefik.enable: 'true'
|
|
44
|
-
traefik.http.routers.traefik.rule: Host(`traefik.dev.localhost`)
|
|
45
|
-
traefik.http.routers.traefik.entrypoints: websecure
|
|
46
|
-
traefik.http.routers.traefik.tls: 'true'
|
|
47
|
-
traefik.http.routers.traefik.service: api@internal
|
|
48
|
-
|
|
49
|
-
postgres:
|
|
50
|
-
image: postgres:18-alpine
|
|
51
|
-
container_name: postgres
|
|
52
|
-
environment:
|
|
53
|
-
- POSTGRES_USER=user
|
|
54
|
-
- POSTGRES_PASSWORD=password
|
|
55
|
-
ports:
|
|
56
|
-
- 5432:5432
|
|
57
|
-
volumes:
|
|
58
|
-
- postgres_data:/var/lib/postgresql/data
|
|
59
|
-
- ./.docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
60
|
-
healthcheck:
|
|
61
|
-
test: [CMD-SHELL, "psql postgresql://user:password@postgres:5432/app -c 'SELECT 1' 2> /dev/null"]
|
|
62
|
-
interval: 1s
|
|
63
|
-
timeout: 1s
|
|
64
|
-
retries: 20
|
|
65
|
-
|
|
66
|
-
metabase:
|
|
67
|
-
image: metabase/metabase:v0.57.x
|
|
68
|
-
container_name: metabase
|
|
69
|
-
environment:
|
|
70
|
-
- MB_DB_TYPE=h2
|
|
71
|
-
- MB_DB_FILE=/var/lib/metabase/metabase.db
|
|
72
|
-
- MB_SITE_URL=https://metabase.dev.localhost
|
|
73
|
-
volumes:
|
|
74
|
-
- metabase_data:/var/lib/metabase
|
|
75
|
-
healthcheck:
|
|
76
|
-
test: [CMD-SHELL, curl -f http://localhost:3000/api/health]
|
|
77
|
-
interval: 1s
|
|
78
|
-
timeout: 1s
|
|
79
|
-
retries: 20
|
|
80
|
-
labels:
|
|
81
|
-
traefik.enable: 'true'
|
|
82
|
-
traefik.http.routers.metabase.rule: Host(`metabase.dev.localhost`)
|
|
83
|
-
traefik.http.routers.metabase.entrypoints: websecure
|
|
84
|
-
traefik.http.routers.metabase.tls: 'true'
|
|
85
|
-
traefik.http.services.metabase.loadbalancer.server.port: '3000'
|
|
86
16
|
|
|
87
17
|
api:
|
|
88
18
|
<<: *common
|
|
19
|
+
image: oven/bun:1.3.3-alpine
|
|
20
|
+
container_name: api
|
|
21
|
+
init: true
|
|
89
22
|
depends_on:
|
|
90
23
|
traefik:
|
|
91
24
|
condition: service_healthy
|
|
92
25
|
postgres:
|
|
93
26
|
condition: service_healthy
|
|
94
|
-
container_name: api
|
|
95
|
-
image: oven/bun:1-alpine
|
|
96
|
-
init: true
|
|
97
27
|
command: [bun, --cwd, api, dev]
|
|
98
28
|
environment:
|
|
99
29
|
- FORCE_COLOR=1
|
|
@@ -106,11 +36,11 @@ services:
|
|
|
106
36
|
|
|
107
37
|
app:
|
|
108
38
|
<<: *common
|
|
109
|
-
|
|
110
|
-
- api
|
|
39
|
+
image: imbios/bun-node:1.3.3-24-alpine
|
|
111
40
|
container_name: app
|
|
112
|
-
image: imbios/bun-node:24-alpine
|
|
113
41
|
init: true
|
|
42
|
+
depends_on:
|
|
43
|
+
- api
|
|
114
44
|
command: [bun, --cwd, app, dev, --host, 0.0.0.0]
|
|
115
45
|
environment:
|
|
116
46
|
- FORCE_COLOR=1
|
|
@@ -121,52 +51,6 @@ services:
|
|
|
121
51
|
traefik.http.routers.app.tls: 'true'
|
|
122
52
|
traefik.http.services.app.loadbalancer.server.port: '5173'
|
|
123
53
|
|
|
124
|
-
studio:
|
|
125
|
-
depends_on:
|
|
126
|
-
traefik:
|
|
127
|
-
condition: service_healthy
|
|
128
|
-
postgres:
|
|
129
|
-
condition: service_healthy
|
|
130
|
-
container_name: studio
|
|
131
|
-
image: ghcr.io/drizzle-team/gateway:latest
|
|
132
|
-
environment:
|
|
133
|
-
- MASTERPASS=foobar
|
|
134
|
-
volumes:
|
|
135
|
-
- studio_data:/app
|
|
136
|
-
labels:
|
|
137
|
-
traefik.enable: 'true'
|
|
138
|
-
traefik.http.routers.studio.rule: Host(`studio.dev.localhost`)
|
|
139
|
-
traefik.http.routers.studio.entrypoints: websecure
|
|
140
|
-
traefik.http.routers.studio.tls: 'true'
|
|
141
|
-
traefik.http.services.studio.loadbalancer.server.port: '4983'
|
|
142
|
-
|
|
143
|
-
analytics:
|
|
144
|
-
depends_on:
|
|
145
|
-
traefik:
|
|
146
|
-
condition: service_healthy
|
|
147
|
-
postgres:
|
|
148
|
-
condition: service_healthy
|
|
149
|
-
container_name: analytics
|
|
150
|
-
image: umamisoftware/umami:3
|
|
151
|
-
environment:
|
|
152
|
-
- DATABASE_URL=postgresql://user:password@postgres:5432/analytics
|
|
153
|
-
healthcheck:
|
|
154
|
-
test: [CMD-SHELL, curl -f http://localhost:3000]
|
|
155
|
-
interval: 1s
|
|
156
|
-
timeout: 1s
|
|
157
|
-
retries: 20
|
|
158
|
-
labels:
|
|
159
|
-
traefik.enable: 'true'
|
|
160
|
-
traefik.http.routers.analytics.rule: Host(`analytics.dev.localhost`)
|
|
161
|
-
traefik.http.routers.analytics.entrypoints: websecure
|
|
162
|
-
traefik.http.routers.analytics.tls: 'true'
|
|
163
|
-
traefik.http.services.analytics.loadbalancer.server.port: '3000'
|
|
164
|
-
|
|
165
54
|
networks:
|
|
166
55
|
default:
|
|
167
56
|
name: dev
|
|
168
|
-
|
|
169
|
-
volumes:
|
|
170
|
-
postgres_data:
|
|
171
|
-
metabase_data:
|
|
172
|
-
studio_data:
|
package/template/knip.config.ts
CHANGED
|
@@ -2,8 +2,9 @@ import type { KnipConfig } from 'knip'
|
|
|
2
2
|
|
|
3
3
|
// Required for Knip to pass
|
|
4
4
|
Object.assign(import.meta.env, {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
ALLOWED_ORIGINS: 'https://foo.bar',
|
|
6
|
+
AUTH_SECRET: 'foo',
|
|
7
|
+
DATABASE_URL: 'postgresql://foo:bar@localhost:5432/foo',
|
|
7
8
|
})
|
|
8
9
|
|
|
9
10
|
export default {
|
package/template/package.json
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"update": "bunx taze -I -rwi"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@kevinmarrec/eslint-config": "^1.5.
|
|
26
|
-
"@kevinmarrec/stylelint-config": "^1.5.
|
|
27
|
-
"@kevinmarrec/tsconfig": "^1.5.
|
|
25
|
+
"@kevinmarrec/eslint-config": "^1.5.8",
|
|
26
|
+
"@kevinmarrec/stylelint-config": "^1.5.8",
|
|
27
|
+
"@kevinmarrec/tsconfig": "^1.5.8",
|
|
28
28
|
"eslint": "^9.39.1",
|
|
29
29
|
"filesize": "^11.0.13",
|
|
30
|
-
"knip": "^5.70.
|
|
31
|
-
"stylelint": "^16.26.
|
|
30
|
+
"knip": "^5.70.2",
|
|
31
|
+
"stylelint": "^16.26.1",
|
|
32
32
|
"tinyexec": "^1.0.2",
|
|
33
33
|
"tinyglobby": "^0.2.15",
|
|
34
34
|
"typescript": "~5.9.3",
|