@kevinmarrec/create-app 0.11.0 → 0.12.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 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
- - [Bun](https://bun.sh) as package manager & runtime
10
-
11
- - [Vue](https://vuejs.org) as frontend framework
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.0";
11
+ var version = "0.12.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/utils/fs.ts
15
- const ignorePredicate = (filename) => [".git"].includes(filename);
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 (!(await fs_default.emptyCheck(targetDir) || options.force)) {
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.11.0",
4
+ "version": "0.12.0",
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.7",
57
- "@kevinmarrec/stylelint-config": "^1.5.7",
58
- "@kevinmarrec/tsconfig": "^1.5.7",
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.13",
61
- "bumpp": "^10.3.1",
60
+ "@vitest/coverage-v8": "^4.0.14",
61
+ "bumpp": "^10.3.2",
62
62
  "eslint": "^9.39.1",
63
- "knip": "^5.70.1",
64
- "stylelint": "^16.26.0",
65
- "tsdown": "^0.16.6",
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.13",
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:
@@ -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",
@@ -1,15 +1,36 @@
1
1
  # Project Template
2
2
 
3
- A full-stack application template with a Vue.js frontend, Bun-based API backend, PostgreSQL database, and Docker Compose setup for local development.
3
+ A full-stack application template with Vue.js frontend, Bun-based API backend, and PostgreSQL database.
4
4
 
5
- ## Overview
6
-
7
- This template provides a modern full-stack application structure with:
5
+ ## Tech Stack
8
6
 
9
- - **Frontend (app)**: Vue 3 application with Vite, UnoCSS, TypeScript, and TanStack Query
10
- - **Backend (api)**: Bun-based API server with oRPC, Better Auth, and Drizzle ORM
11
- - **Database**: PostgreSQL with Drizzle migrations
12
- - **Infrastructure**: Docker Compose with Traefik reverse proxy, PostgreSQL, and Metabase
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
- > **Note**: If `.env.development` (API) or `.env` (App) files already exist in the template, you may skip this step. Otherwise, create them as described below.
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
- # api/.env.development
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 variables:**
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
- #### App Environment Variables
92
+ **Optional:** `LOG_LEVEL` (default: `info`), `HOST` (default: `0.0.0.0`), `PORT` (default: `4000`)
83
93
 
84
- Create a `.env` file in the `app/` directory with the following variables:
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 variables:**
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
- To access the HTTPS URLs (`https://*.dev.localhost`), you need to set up trusted local TLS certificates using [mkcert](https://github.com/FiloSottile/mkcert).
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 will generate the certificates in `.docker/traefik/certs/` and install the mkcert root CA into your system trust store.
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 Docker Services and Access the Application
126
-
127
- Start all services (Traefik, PostgreSQL, Metabase, API, and App):
130
+ ### 4. Start Services
128
131
 
129
132
  ```bash
130
- docker compose up -d
133
+ bun run dev up -d
131
134
  ```
132
135
 
133
- Once the services are running, you can access:
136
+ **Services:**
134
137
 
135
138
  - Frontend: https://app.dev.localhost
136
139
  - API: https://api.dev.localhost
137
- - Traefik Dashboard: https://traefik.dev.localhost
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
- ### 5. Stopping Services
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
- docker compose down -v
149
+ bun run dev down # Stop services
150
+ bun run dev down -v # Stop and remove volumes (⚠️ deletes data)
152
151
  ```
153
152
 
154
- ## Development
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
- #### API Server
155
+ **Root:**
161
156
 
162
- ```bash
163
- cd api
164
- bun run dev
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
- #### Frontend App
162
+ **API (`cd api`):**
170
163
 
171
- ```bash
172
- cd app
173
- bun run dev
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
- The app will run on `http://localhost:5173` (Vite default port).
169
+ **App (`cd app`):**
177
170
 
178
- ### Database Management
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
- #### Generate Migrations
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
- ## Available Scripts
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
- - `bun run dev` - Start development server with hot reload
211
- - `bun run build` - Build production binary
212
- - `bun run db:generate` - Generate database migrations
213
- - `bun run db:migrate` - Run database migrations
214
-
215
- ### App Scripts (`cd app`)
216
-
217
- - `bun run dev` - Start development server with HMR (Hot Module Replacement)
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
- - Hot reload enabled
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
- ### App
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
- This creates a compiled binary at `api/dist/api`.
209
+ Output: `api/dist/api`
288
210
 
289
- ### Build App
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
- This creates a static site in `app/dist/`.
217
+ Output: `app/dist/`
297
218
 
298
219
  ## Troubleshooting
299
220
 
300
- ### Port Already in Use
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
- ### SSL Certificate Warnings
223
+ **SSL warnings:** Complete [TLS setup](#3-set-up-local-tls-certificates). Without certificates, Traefik may fail to start.
305
224
 
306
- If you see SSL certificate warnings when accessing `https://*.dev.localhost` URLs, ensure you have completed the [Local TLS Setup](#3-set-up-local-tls-certificates) to generate trusted certificates using mkcert.
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
- docker compose logs -f <service-name>
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.11.3",
14
- "better-auth": "^1.4.1",
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.1.0"
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
  }
@@ -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.string(),
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.optional(v.string(), ''),
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.string(),
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.decimal(),
32
- v.transform(input => Number(input)),
44
+ v.digits(),
45
+ v.toNumber(),
33
46
  ),
34
47
  }),
35
48
  })
@@ -1,5 +1,5 @@
1
1
  {
2
- "extends": "../tsconfig.json",
2
+ "extends": "@kevinmarrec/tsconfig",
3
3
  "compilerOptions": {
4
4
  "lib": ["ESNext"]
5
5
  }
@@ -10,25 +10,29 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@kevinmarrec/vue-i18n": "^1.1.3",
13
- "@orpc/client": "^1.11.3",
14
- "@orpc/tanstack-query": "^1.11.3",
15
- "@tanstack/query-core": "^5.90.10",
16
- "@tanstack/vue-query": "^5.91.2",
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.0.0",
19
- "better-auth": "^1.4.1",
18
+ "@vueuse/core": "^14.1.0",
19
+ "better-auth": "^1.4.3",
20
20
  "unocss": "^66.5.9",
21
- "vue": "^3.5.24"
21
+ "vue": "^3.5.25"
22
22
  },
23
23
  "devDependencies": {
24
- "@kevinmarrec/unocss-config": "^1.5.7",
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": "../tsconfig.json",
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 { defineConfig } from 'vite'
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 default defineConfig(({ command, mode }) => ({
14
- build: {
15
- modulePreload: {
16
- polyfill: false,
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
- await builder.build(builder.environments.client)
29
- },
30
- },
31
- plugins: [
32
- vue({
33
- features: {
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
- mode === 'analyze' && visualizer({
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
- ssgOptions: {
56
- script: 'async',
57
- formatting: 'minify',
58
- beastiesOptions: {
59
- reduceInlineStyles: false,
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
- ssr: {
63
- noExternal: ['@kevinmarrec/vue-i18n'],
64
- },
65
- }))
84
+ }
85
+ })
@@ -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
- depends_on:
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:
@@ -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
- AUTH_SECRET: '',
6
- DATABASE_URL: '',
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 {
@@ -22,13 +22,13 @@
22
22
  "update": "bunx taze -I -rwi"
23
23
  },
24
24
  "devDependencies": {
25
- "@kevinmarrec/eslint-config": "^1.5.7",
26
- "@kevinmarrec/stylelint-config": "^1.5.7",
27
- "@kevinmarrec/tsconfig": "^1.5.7",
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.1",
31
- "stylelint": "^16.26.0",
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",
@@ -1,10 +1,3 @@
1
1
  {
2
- "extends": "@kevinmarrec/tsconfig",
3
- "compilerOptions": {
4
- "paths": {
5
- "~/api/*": ["./api/src/*"],
6
- "~/app/*": ["./app/src/*"]
7
- }
8
- },
9
- "exclude": ["**/dist/**"]
2
+ "extends": "@kevinmarrec/tsconfig"
10
3
  }