@kevinmarrec/create-app 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +11 -11
  4. package/template/.docker/analytics/{service.yaml → service.yml} +1 -1
  5. package/template/.docker/metabase/{service.yaml → service.yml} +1 -1
  6. package/template/.docker/traefik/certs/.gitkeep +0 -0
  7. package/template/.docker/traefik/{service.yaml → service.yml} +1 -1
  8. package/template/.github/workflows/ci.yml +13 -12
  9. package/template/.vscode/settings.json +4 -3
  10. package/template/README.md +6 -3
  11. package/template/api/.env.development +1 -0
  12. package/template/api/Dockerfile +5 -25
  13. package/template/api/package.json +7 -7
  14. package/template/api/src/auth/index.ts +4 -4
  15. package/template/api/src/main.ts +4 -13
  16. package/template/api/src/utils/stopper.ts +19 -0
  17. package/template/app/package.json +10 -11
  18. package/template/app/src/App.vue +9 -9
  19. package/template/app/src/composables/auth.ts +26 -24
  20. package/template/app/src/composables/content.ts +10 -3
  21. package/template/app/src/lib/orpc.ts +4 -10
  22. package/template/app/src/main.ts +9 -7
  23. package/template/app/src/utils/fetch.ts +16 -0
  24. package/template/app/vite.config.ts +1 -0
  25. package/template/{compose.yaml → compose.yml} +8 -8
  26. package/template/knip.config.ts +0 -3
  27. package/template/package.json +7 -9
  28. package/template/.github/scripts/build-stats.md.ts +0 -52
  29. /package/template/.docker/mailpit/{service.yaml → service.yml} +0 -0
  30. /package/template/.docker/postgres/{service.yaml → service.yml} +0 -0
  31. /package/template/.docker/studio/{service.yaml → service.yml} +0 -0
package/README.md CHANGED
@@ -14,11 +14,11 @@ CLI that scaffolds an opinionated [Bun](https://bun.sh/) & [Vue](https://vuejs.o
14
14
  - [Vite](https://vitejs.dev/) as build tool
15
15
  - [Vite SSG](https://github.com/antfu-collective/vite-ssg) for static site generation
16
16
  - [UnoCSS](https://unocss.dev/) for styling
17
- - [TanStack Query](https://tanstack.com/query/latest) for data fetching
18
17
  - [Unhead](https://unhead.unjs.io/) for head management
19
18
  - [Vue I18n](https://github.com/kevinmarrec/vue-i18n) for internationalization
20
19
  - [oRPC](https://orpc.dev/) for API layer
21
20
  - [Better Auth](https://www.better-auth.com/) for authentication
21
+ - [Pinia Colada](https://pinia-colada.esm.dev/) for data fetching & state management
22
22
  - [Pino](https://getpino.io/) for logging
23
23
  - [Drizzle ORM](https://orm.drizzle.team/) as SQL-first TypeScript ORM
24
24
  - [Valibot](https://valibot.dev/) for validation
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { x } from "tinyexec";
8
8
  import fs from "node:fs/promises";
9
9
 
10
10
  //#region package.json
11
- var version = "0.12.0";
11
+ var version = "0.13.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/utils/fs.ts
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@kevinmarrec/create-app",
3
3
  "type": "module",
4
- "version": "0.12.0",
5
- "packageManager": "bun@1.3.3",
4
+ "version": "0.13.0",
5
+ "packageManager": "bun@1.3.4",
6
6
  "description": "CLI that scaffolds an opinionated Bun & Vue fullstack application.",
7
7
  "author": "Kevin Marrec <kevin@marrec.io>",
8
8
  "license": "MIT",
@@ -53,18 +53,18 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@faker-js/faker": "^10.1.0",
56
- "@kevinmarrec/eslint-config": "^1.5.8",
57
- "@kevinmarrec/stylelint-config": "^1.5.8",
58
- "@kevinmarrec/tsconfig": "^1.5.8",
59
- "@types/bun": "^1.3.3",
60
- "@vitest/coverage-v8": "^4.0.14",
56
+ "@kevinmarrec/eslint-config": "^1.5.9",
57
+ "@kevinmarrec/stylelint-config": "^1.5.9",
58
+ "@kevinmarrec/tsconfig": "^1.5.9",
59
+ "@types/bun": "^1.3.4",
60
+ "@vitest/coverage-v8": "^4.0.15",
61
61
  "bumpp": "^10.3.2",
62
62
  "eslint": "^9.39.1",
63
- "knip": "^5.70.2",
63
+ "knip": "^5.72.0",
64
64
  "stylelint": "^16.26.1",
65
- "tsdown": "^0.16.8",
65
+ "tsdown": "^0.17.2",
66
66
  "typescript": "^5.9.3",
67
- "vitest": "^4.0.14",
68
- "vue-tsc": "^3.1.5"
67
+ "vitest": "^4.0.15",
68
+ "vue-tsc": "^3.1.7"
69
69
  }
70
70
  }
@@ -1,6 +1,6 @@
1
1
  services:
2
2
  analytics:
3
- image: umamisoftware/umami:3.0.1
3
+ image: umamisoftware/umami:3.0.2
4
4
  container_name: analytics
5
5
  depends_on:
6
6
  traefik:
@@ -1,6 +1,6 @@
1
1
  services:
2
2
  metabase:
3
- image: metabase/metabase:v0.57.4
3
+ image: metabase/metabase:v0.57.5
4
4
  container_name: metabase
5
5
  depends_on:
6
6
  traefik:
File without changes
@@ -1,6 +1,6 @@
1
1
  services:
2
2
  traefik:
3
- image: traefik:v3.6.2
3
+ image: traefik:v3.6.4
4
4
  container_name: traefik
5
5
  restart: unless-stopped
6
6
  security_opt:
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - name: Setup Bun
17
- uses: kevinmarrec/workflows/setup-bun@v1
17
+ uses: kevinmarrec/workflows/setup-bun@main
18
18
 
19
19
  - name: Lint
20
20
  run: bun run lint
@@ -24,19 +24,20 @@ jobs:
24
24
 
25
25
  - name: Type check
26
26
  run: bun run check:types
27
- project-size:
28
- name: Analyze project build size
27
+ project-build-size-diff:
28
+ name: Analyze project build size differences
29
29
  runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: read
32
+ pull-requests: write
30
33
  steps:
31
34
  - name: Setup Bun
32
- uses: kevinmarrec/workflows/setup-bun@v1
35
+ uses: kevinmarrec/workflows/setup-bun@main
33
36
 
34
- - name: Check api build size
35
- run: |
36
- echo "## API" >> $GITHUB_STEP_SUMMARY
37
- bun run .github/scripts/build-stats.md.ts api >> $GITHUB_STEP_SUMMARY
37
+ - name: Build project
38
+ run: bun run build
38
39
 
39
- - name: Check app build size
40
- run: |
41
- echo "## App" >> $GITHUB_STEP_SUMMARY
42
- bun run .github/scripts/build-stats.md.ts app >> $GITHUB_STEP_SUMMARY
40
+ - name: Analyze project build size differences
41
+ uses: kevinmarrec/workflows/filesize-diff@main
42
+ with:
43
+ directories: app/dist,api/dist
@@ -30,6 +30,8 @@
30
30
  ],
31
31
 
32
32
  "eslint.validate": [
33
+ "github-actions-workflow",
34
+ "dockercompose",
33
35
  "javascript",
34
36
  "typescript",
35
37
  "vue",
@@ -39,8 +41,7 @@
39
41
  "markdown",
40
42
  "json",
41
43
  "jsonc",
42
- "yaml",
43
- "dockercompose"
44
+ "yaml"
44
45
  ],
45
46
 
46
47
  "css.validate": false,
@@ -61,7 +62,7 @@
61
62
  "vue.inlayHints.missingProps": true,
62
63
 
63
64
  "files.associations": {
64
- "**/.docker/*/service.yaml": "dockercompose"
65
+ "**/.docker/*/service.yml": "dockercompose"
65
66
  },
66
67
 
67
68
  "material-icon-theme.folders.associations": {
@@ -12,11 +12,11 @@ A full-stack application template with Vue.js frontend, Bun-based API backend, a
12
12
  - [Vite](https://vitejs.dev/) as build tool
13
13
  - [Vite SSG](https://github.com/antfu-collective/vite-ssg) for static site generation
14
14
  - [UnoCSS](https://unocss.dev/) for styling
15
- - [TanStack Query](https://tanstack.com/query/latest) for data fetching
16
15
  - [Unhead](https://unhead.unjs.io/) for head management
17
16
  - [Vue I18n](https://github.com/kevinmarrec/vue-i18n) for internationalization
18
17
  - [oRPC](https://orpc.dev/) for API layer
19
18
  - [Better Auth](https://www.better-auth.com/) for authentication
19
+ - [Pinia Colada](https://pinia-colada.esm.dev/) for data fetching & state management
20
20
  - [Pino](https://getpino.io/) for logging
21
21
  - [Drizzle ORM](https://orm.drizzle.team/) as SQL-first TypeScript ORM
22
22
  - [Valibot](https://valibot.dev/) for validation
@@ -59,10 +59,11 @@ project/
59
59
  │ │ ├── composables/ # Vue composables (auth, etc.)
60
60
  │ │ ├── lib/ # Library utilities (oRPC client)
61
61
  │ │ ├── locales/ # Internationalization files (i18n)
62
+ │ │ ├── utils/ # Utility functions (fetch, etc.)
62
63
  │ │ ├── App.vue # Main Vue component
63
64
  │ │ └── main.ts # App entry point
64
65
  │ └── package.json
65
- ├── compose.yaml # Docker Compose configuration
66
+ ├── compose.yml # Docker Compose configuration
66
67
  └── package.json # Root workspace configuration
67
68
  ```
68
69
 
@@ -131,6 +132,8 @@ This generates certificates in `.docker/traefik/certs/` and installs the root CA
131
132
 
132
133
  ```bash
133
134
  bun run dev up -d
135
+ # or
136
+ bun run dev up --wait
134
137
  ```
135
138
 
136
139
  **Services:**
@@ -218,7 +221,7 @@ Output: `app/dist/`
218
221
 
219
222
  ## Troubleshooting
220
223
 
221
- **Port conflicts:** Modify port mappings in `compose.yaml` if ports 80, 443, or 5432 are in use.
224
+ **Port conflicts:** Modify port mappings in `compose.yml` if ports 80, 443, or 5432 are in use.
222
225
 
223
226
  **SSL warnings:** Complete [TLS setup](#3-set-up-local-tls-certificates). Without certificates, Traefik may fail to start.
224
227
 
@@ -1,4 +1,5 @@
1
1
  ALLOWED_ORIGINS="https://app.dev.localhost"
2
2
  AUTH_SECRET="D0QykrmzDgVrP4GCKkFPT1CB1UplFk4bhIJk92SUtSQ="
3
3
  DATABASE_URL="postgresql://user:password@postgres:5432/app"
4
+ LOG_LEVEL=info
4
5
  NODE_ENV="development"
@@ -1,45 +1,25 @@
1
- # --- Stage 1: Dependency Installation & Caching (The Build Stage) ---
2
-
3
1
  ARG TARGETARCH
4
2
 
5
- # Use a bun-alpine image for a fast build environment
6
- FROM oven/bun:1.3.3-slim AS build
3
+ FROM oven/bun:1.3.4-slim AS build
7
4
 
8
- # Set the working directory inside the container
9
5
  WORKDIR /build
10
6
 
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
7
  COPY package.json bun.lock ./
19
-
20
- # Copy service package.json files
21
8
  COPY api/package.json ./api/package.json
22
9
  COPY app/package.json ./app/package.json
23
10
 
24
- # 2. Perform a frozen install
25
11
  RUN bun install --filter api --frozen-lockfile
26
12
 
27
- # Copy API source code
28
13
  COPY api/ ./api/
29
14
 
30
- # 3. Build the API
31
15
  RUN bun --cwd api build --target bun-linux-$TARGETARCH-modern
32
16
 
33
- # --- Stage 2: Final Image (The Runtime Stage) ---
17
+ FROM gcr.io/distroless/base-debian13:nonroot AS runner
34
18
 
35
- # Use a minimal image for production (less attack surface, smaller size)
36
- FROM debian:trixie-slim
19
+ COPY --from=build --chown=nonroot:nonroot /build/api/dist/api /usr/local/bin/api
37
20
 
38
- # Copy the compiled binary from builder stage
39
- COPY --from=build /build/api/dist/api /usr/local/bin/
40
-
41
- # Expose port
42
21
  EXPOSE 4000
43
22
 
44
- # Run the binary
23
+ USER nonroot
24
+
45
25
  ENTRYPOINT ["/usr/local/bin/api"]
@@ -10,18 +10,18 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@libsql/client": "^0.15.15",
13
- "@orpc/server": "^1.12.0",
14
- "better-auth": "^1.4.3",
15
- "drizzle-orm": "^0.44.7",
13
+ "@orpc/server": "^1.12.2",
14
+ "better-auth": "^1.4.5",
15
+ "drizzle-orm": "^0.45.0",
16
16
  "pino": "^10.1.0",
17
17
  "valibot": "^1.2.0"
18
18
  },
19
19
  "devDependencies": {
20
- "@kevinmarrec/tsconfig": "^1.5.8",
21
- "@types/bun": "^1.3.3",
22
- "drizzle-kit": "^0.31.7",
20
+ "@kevinmarrec/tsconfig": "^1.5.9",
21
+ "@types/bun": "^1.3.4",
22
+ "drizzle-kit": "^0.31.8",
23
23
  "pg": "^8.16.3",
24
- "pino-pretty": "^13.1.2",
24
+ "pino-pretty": "^13.1.3",
25
25
  "typescript": "~5.9.3"
26
26
  }
27
27
  }
@@ -1,10 +1,10 @@
1
1
  import { betterAuth } from 'better-auth'
2
2
  import { type DB, drizzleAdapter } from 'better-auth/adapters/drizzle'
3
- import type { BaseLogger } from 'pino'
3
+ import type { Logger } from 'pino'
4
4
 
5
5
  import { env } from '../env'
6
6
 
7
- export function createBetterAuth({ db, logger }: { db: DB, logger: BaseLogger }) {
7
+ export function createBetterAuth({ db, logger }: { db: DB, logger: Logger }) {
8
8
  return betterAuth({
9
9
  basePath: '/auth',
10
10
  secret: env.auth.secret,
@@ -14,9 +14,9 @@ export function createBetterAuth({ db, logger }: { db: DB, logger: BaseLogger })
14
14
  usePlural: true,
15
15
  }),
16
16
  logger: {
17
+ level: 'info',
17
18
  log: (level, message) => {
18
- if (level === 'error') return
19
- logger[level](message)
19
+ logger[level](`[Auth] ${message}`)
20
20
  },
21
21
  },
22
22
  emailAndPassword: {
@@ -1,5 +1,3 @@
1
- import process from 'node:process'
2
-
3
1
  import { createBetterAuth } from './auth'
4
2
  import { db } from './database'
5
3
  import { env } from './env'
@@ -7,9 +5,11 @@ import { createRpc } from './orpc'
7
5
  import { router } from './orpc/router'
8
6
  import { cors } from './utils/cors'
9
7
  import { logger } from './utils/logger'
8
+ import { createStopper } from './utils/stopper'
10
9
 
11
10
  const auth = createBetterAuth({ db, logger })
12
11
  const rpc = createRpc({ auth, db, logger }, router)
12
+ const stopper = createStopper({ logger })
13
13
 
14
14
  const server = Bun.serve({
15
15
  hostname: env.server.host,
@@ -25,15 +25,6 @@ const server = Bun.serve({
25
25
  },
26
26
  })
27
27
 
28
- logger.info(`Listening on ${server.url}`)
29
-
30
- // Graceful Shutdown
28
+ stopper.bind(server)
31
29
 
32
- async function gracefulShutdown() {
33
- logger.info('Gracefully shutting down...')
34
- await server.stop()
35
- process.exit(0)
36
- }
37
-
38
- process.on('SIGINT', gracefulShutdown)
39
- process.on('SIGTERM', gracefulShutdown)
30
+ logger.info(`Listening on ${server.url}`)
@@ -0,0 +1,19 @@
1
+ import process from 'node:process'
2
+
3
+ import type { Logger } from './logger'
4
+
5
+ export function createStopper({ logger }: { logger: Logger }) {
6
+ return {
7
+ bind: (server: Bun.Server<unknown>) => {
8
+ async function gracefulShutdown() {
9
+ logger.info('Received termination signal. Gracefully shutting down...')
10
+ await server.stop()
11
+ logger.info('Server stopped. Exiting process.')
12
+ process.exit(0)
13
+ }
14
+
15
+ process.on('SIGINT', gracefulShutdown)
16
+ process.on('SIGTERM', gracefulShutdown)
17
+ },
18
+ }
19
+ }
@@ -9,21 +9,20 @@
9
9
  "preview": "vite preview --open"
10
10
  },
11
11
  "dependencies": {
12
- "@kevinmarrec/vue-i18n": "^1.1.3",
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",
12
+ "@kevinmarrec/vue-i18n": "^1.1.5",
13
+ "@orpc/client": "^1.12.2",
14
+ "@pinia/colada": "^0.18.0",
17
15
  "@unhead/vue": "^2.0.19",
18
16
  "@vueuse/core": "^14.1.0",
19
- "better-auth": "^1.4.3",
20
- "unocss": "^66.5.9",
17
+ "better-auth": "^1.4.5",
18
+ "pinia": "^3.0.4",
19
+ "unocss": "^66.5.10",
21
20
  "vue": "^3.5.25"
22
21
  },
23
22
  "devDependencies": {
24
- "@kevinmarrec/tsconfig": "^1.5.8",
25
- "@kevinmarrec/unocss-config": "^1.5.8",
26
- "@kevinmarrec/vite-plugin-dark-mode": "^1.1.2",
23
+ "@kevinmarrec/tsconfig": "^1.5.9",
24
+ "@kevinmarrec/unocss-config": "^1.5.9",
25
+ "@kevinmarrec/vite-plugin-dark-mode": "^1.1.5",
27
26
  "@modyfi/vite-plugin-yaml": "^1.1.1",
28
27
  "@unhead/addons": "^2.0.19",
29
28
  "@vitejs/plugin-vue": "^6.0.2",
@@ -31,7 +30,7 @@
31
30
  "rollup-plugin-visualizer": "^6.0.5",
32
31
  "typescript": "~5.9.3",
33
32
  "valibot": "^1.2.0",
34
- "vite": "^7.2.4",
33
+ "vite": "^7.2.7",
35
34
  "vite-plugin-valibot-env": "^1.0.1",
36
35
  "vite-plugin-vue-devtools": "^8.0.5",
37
36
  "vite-ssg": "^28.2.2",
@@ -8,7 +8,7 @@ useHead({
8
8
  title: () => t('title'),
9
9
  })
10
10
 
11
- const { user, signIn, signUp, signOut } = useAuth()
11
+ const { user, error, signIn, signUp, signOut } = useAuth()
12
12
  const { publicContent, privateContent } = useContent()
13
13
 
14
14
  const email = ref('')
@@ -54,26 +54,26 @@ const password = ref('')
54
54
  >
55
55
  </div>
56
56
 
57
- <div v-if="signIn.error.value || signUp.error.value" class="text-sm text-red-600 dark:text-red-400">
58
- {{ signIn.error.value || signUp.error.value }}
57
+ <div v-if="error" class="text-sm text-red-600 dark:text-red-400">
58
+ {{ error }}
59
59
  </div>
60
60
 
61
61
  <div class="flex space-x-3">
62
62
  <button
63
- :disabled="signIn.isPending.value"
63
+ :disabled="signIn.isLoading.value"
64
64
  class="text-white font-medium px-4 py-2 rounded-md bg-blue-600 flex-1 transition-colors duration-200 disabled:bg-blue-400 hover:bg-blue-700"
65
65
  @click="signIn.mutate({ email, password })"
66
66
  >
67
- <span v-if="signIn.isPending.value">Signing In...</span>
67
+ <span v-if="signIn.isLoading.value">Signing In...</span>
68
68
  <span v-else>Sign In</span>
69
69
  </button>
70
70
 
71
71
  <button
72
- :disabled="signUp.isPending.value"
72
+ :disabled="signUp.isLoading.value"
73
73
  class="text-white font-medium px-4 py-2 rounded-md bg-green-600 flex-1 transition-colors duration-200 disabled:bg-green-400 hover:bg-green-700"
74
74
  @click="signUp.mutate({ email, password, name: 'User' })"
75
75
  >
76
- <span v-if="signUp.isPending.value">Signing Up...</span>
76
+ <span v-if="signUp.isLoading.value">Signing Up...</span>
77
77
  <span v-else>Sign Up</span>
78
78
  </button>
79
79
  </div>
@@ -105,11 +105,11 @@ const password = ref('')
105
105
  </div>
106
106
 
107
107
  <button
108
- :disabled="signOut.isPending.value"
108
+ :disabled="signOut.isLoading.value"
109
109
  class="text-white font-medium px-4 py-2 rounded-md bg-red-600 w-full transition-colors duration-200 disabled:bg-red-400 hover:bg-red-700"
110
110
  @click="signOut.mutate({})"
111
111
  >
112
- <span v-if="signOut.isPending.value">Signing Out...</span>
112
+ <span v-if="signOut.isLoading.value">Signing Out...</span>
113
113
  <span v-else>Sign Out</span>
114
114
  </button>
115
115
  </div>
@@ -1,39 +1,41 @@
1
- import { useMutation } from '@tanstack/vue-query'
2
- import { createAuthClient } from 'better-auth/vue'
3
- import { computed } from 'vue'
1
+ import { defineMutation } from '@pinia/colada'
2
+ import { set } from '@vueuse/core'
3
+ import { createAuthClient, type ErrorContext } from 'better-auth/vue'
4
+ import { computed, ref } from 'vue'
5
+
6
+ import { betterFetchOptions as fetchOptions } from '../utils/fetch'
4
7
 
5
8
  const authClient = createAuthClient({
6
9
  baseURL: `${import.meta.env.VITE_API_URL}/auth`,
7
- fetchOptions: {
8
- credentials: 'include',
9
- },
10
+ fetchOptions,
10
11
  })
11
12
 
12
- const session = authClient.useSession()
13
+ const authError = ref<ErrorContext['error'] | null>(null)
13
14
 
14
- function createAuthMutation<T extends (...args: any[]) => any>(fn: T) {
15
- return useMutation({
16
- mutationFn: async (input: Parameters<T>[0]) => {
17
- return fn(input, {
18
- onError: ({ error }: any) => {
19
- throw error
20
- },
21
- })
15
+ function defineAuthMutation<TVars, TData>(mutation: (vars: TVars) => Promise<TData>) {
16
+ return defineMutation({
17
+ mutation,
18
+ onMutate: () => {
19
+ set(authError, null)
20
+ },
21
+ onSettled: (_, error) => {
22
+ set(authError, error)
22
23
  },
23
24
  })
24
25
  }
25
26
 
26
- export function useAuth() {
27
- const signIn = createAuthMutation(authClient.signIn.email)
28
- const signUp = createAuthMutation(authClient.signUp.email)
29
- const signOut = createAuthMutation(authClient.signOut)
27
+ const useSignIn = defineAuthMutation(authClient.signIn.email)
28
+ const useSignUp = defineAuthMutation(authClient.signUp.email)
29
+ const useSignOut = defineAuthMutation(authClient.signOut)
30
30
 
31
- const user = computed(() => session.value.data?.user)
31
+ export function useAuth() {
32
+ const session = authClient.useSession()
32
33
 
33
34
  return {
34
- signIn,
35
- signUp,
36
- signOut,
37
- user,
35
+ error: authError,
36
+ user: computed(() => session.value.data?.user),
37
+ signIn: useSignIn(),
38
+ signUp: useSignUp(),
39
+ signOut: useSignOut(),
38
40
  }
39
41
  }
@@ -1,10 +1,17 @@
1
- import { useQuery } from '@tanstack/vue-query'
1
+ import { useQuery } from '@pinia/colada'
2
2
 
3
3
  import { orpc } from '~/app/lib/orpc'
4
4
 
5
5
  export function useContent() {
6
- const publicContent = useQuery(orpc.public.queryOptions({})).data
7
- const privateContent = useQuery(orpc.private.queryOptions({})).data
6
+ const publicContent = useQuery({
7
+ key: () => ['public'],
8
+ query: orpc.public,
9
+ }).data
10
+
11
+ const privateContent = useQuery({
12
+ key: () => ['private'],
13
+ query: orpc.private,
14
+ }).data
8
15
 
9
16
  return {
10
17
  publicContent,
@@ -1,19 +1,13 @@
1
1
  import { createORPCClient } from '@orpc/client'
2
2
  import { RPCLink } from '@orpc/client/fetch'
3
- import { createTanstackQueryUtils } from '@orpc/tanstack-query'
4
3
 
5
4
  import type { Router, RouterClient } from '~/api/orpc/router'
6
5
 
6
+ import { getFetchOptions } from '../utils/fetch'
7
+
7
8
  const link = new RPCLink({
8
9
  url: `${import.meta.env.VITE_API_URL}/rpc`,
9
- fetch: (request, init) =>
10
- globalThis.fetch(request, {
11
- ...init,
12
- credentials: 'include',
13
- signal: AbortSignal.timeout(30_000),
14
- }),
10
+ fetch: (request, init) => globalThis.fetch(request, getFetchOptions(init)),
15
11
  })
16
12
 
17
- const client: RouterClient<Router> = createORPCClient(link)
18
-
19
- export const orpc = createTanstackQueryUtils(client)
13
+ export const orpc = createORPCClient<RouterClient<Router>>(link)
@@ -1,5 +1,6 @@
1
1
  import { createI18n } from '@kevinmarrec/vue-i18n'
2
- import { VueQueryPlugin } from '@tanstack/vue-query'
2
+ import { PiniaColada } from '@pinia/colada'
3
+ import { createPinia } from 'pinia'
3
4
  import { ViteSSG } from 'vite-ssg/single-page'
4
5
 
5
6
  import App from './App.vue'
@@ -7,13 +8,14 @@ import App from './App.vue'
7
8
  import 'virtual:uno.css'
8
9
 
9
10
  export const createApp = ViteSSG(App, async ({ app }) => {
10
- const i18n = await createI18n({
11
+ app.use(await createI18n({
11
12
  messages: import.meta.glob('./locales/*.yml'),
12
- })
13
-
14
- app.use(i18n)
13
+ }))
15
14
 
16
- app.use(VueQueryPlugin, {
17
- enableDevtoolsV6Plugin: true,
15
+ app.use(createPinia())
16
+ app.use(PiniaColada, {
17
+ queryOptions: {
18
+ enabled: !import.meta.env.SSR,
19
+ },
18
20
  })
19
21
  })
@@ -0,0 +1,16 @@
1
+ import type { BetterFetchOption } from 'better-auth/vue'
2
+
3
+ const FETCH_TIMEOUT_MS = 30_000
4
+
5
+ export function getFetchOptions(init?: RequestInit): RequestInit {
6
+ return {
7
+ ...init,
8
+ credentials: 'include',
9
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
10
+ }
11
+ }
12
+
13
+ export const betterFetchOptions: BetterFetchOption = {
14
+ ...getFetchOptions(),
15
+ onError: ({ error }) => Promise.reject(error),
16
+ }
@@ -45,6 +45,7 @@ export default defineConfig(({ command, mode }) => {
45
45
  if (mode === 'analyze') {
46
46
  plugins.push(visualizer({
47
47
  filename: 'node_modules/.vite/stats.html',
48
+ template: 'flamegraph',
48
49
  brotliSize: true,
49
50
  gzipSize: true,
50
51
  open: true,
@@ -1,10 +1,10 @@
1
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
2
+ - path: .docker/traefik/service.yml
3
+ - path: .docker/postgres/service.yml
4
+ - path: .docker/metabase/service.yml
5
+ - path: .docker/studio/service.yml
6
+ - path: .docker/analytics/service.yml
7
+ - path: .docker/mailpit/service.yml
8
8
 
9
9
  x-common: &common
10
10
  volumes:
@@ -16,7 +16,7 @@ services:
16
16
 
17
17
  api:
18
18
  <<: *common
19
- image: oven/bun:1.3.3-alpine
19
+ image: oven/bun:1.3.4-alpine
20
20
  container_name: api
21
21
  init: true
22
22
  depends_on:
@@ -36,7 +36,7 @@ services:
36
36
 
37
37
  app:
38
38
  <<: *common
39
- image: imbios/bun-node:1.3.3-24-alpine
39
+ image: imbios/bun-node:1.3.4-24-alpine
40
40
  container_name: app
41
41
  init: true
42
42
  depends_on:
@@ -20,9 +20,6 @@ export default {
20
20
  },
21
21
  'app': {
22
22
  entry: ['src/main.ts'],
23
- ignoreDependencies: [
24
- '@vueuse/core',
25
- ],
26
23
  },
27
24
  },
28
25
  } satisfies KnipConfig
@@ -2,7 +2,7 @@
2
2
  "name": "project",
3
3
  "type": "module",
4
4
  "private": true,
5
- "packageManager": "bun@1.3.3",
5
+ "packageManager": "bun@1.3.4",
6
6
  "engines": {
7
7
  "node": "lts/*"
8
8
  },
@@ -11,6 +11,7 @@
11
11
  "app"
12
12
  ],
13
13
  "scripts": {
14
+ "build": "bun -F ./api -F ./app build",
14
15
  "check": "bun run check:eslint && bun run check:stylelint && bun run check:unused && bun run check:types",
15
16
  "check:eslint": "eslint . --cache",
16
17
  "check:stylelint": "stylelint '**/*.{css,scss,vue}' --ignorePath .gitignore --cache",
@@ -22,16 +23,13 @@
22
23
  "update": "bunx taze -I -rwi"
23
24
  },
24
25
  "devDependencies": {
25
- "@kevinmarrec/eslint-config": "^1.5.8",
26
- "@kevinmarrec/stylelint-config": "^1.5.8",
27
- "@kevinmarrec/tsconfig": "^1.5.8",
26
+ "@kevinmarrec/eslint-config": "^1.5.9",
27
+ "@kevinmarrec/stylelint-config": "^1.5.9",
28
+ "@kevinmarrec/tsconfig": "^1.5.9",
28
29
  "eslint": "^9.39.1",
29
- "filesize": "^11.0.13",
30
- "knip": "^5.70.2",
30
+ "knip": "^5.72.0",
31
31
  "stylelint": "^16.26.1",
32
- "tinyexec": "^1.0.2",
33
- "tinyglobby": "^0.2.15",
34
32
  "typescript": "~5.9.3",
35
- "vue-tsc": "^3.1.5"
33
+ "vue-tsc": "^3.1.7"
36
34
  }
37
35
  }
@@ -1,52 +0,0 @@
1
- import * as fs from 'node:fs'
2
- import path from 'node:path'
3
- import process from 'node:process'
4
- import { parseArgs } from 'node:util'
5
-
6
- import { filesize } from 'filesize'
7
- import { x } from 'tinyexec'
8
- import { glob } from 'tinyglobby'
9
-
10
- async function getFileStats(directory: string) {
11
- const fileStats = await Promise.all(
12
- (await glob(['**/*'], { cwd: directory })).map(async file => ({
13
- file,
14
- size: fs.statSync(path.join(directory, file)).size,
15
- })),
16
- )
17
-
18
- fileStats.sort((a, b) => {
19
- const scoreA = (a.file.startsWith('assets/') ? 2 : 0) + (a.file.endsWith('.js') ? 1 : 0)
20
- const scoreB = (b.file.startsWith('assets/') ? 2 : 0) + (b.file.endsWith('.js') ? 1 : 0)
21
- return scoreB - scoreA || a.file.localeCompare(b.file)
22
- })
23
-
24
- return fileStats
25
- }
26
-
27
- async function main() {
28
- const { positionals: [directory] } = parseArgs({
29
- args: process.argv.slice(2),
30
- allowPositionals: true,
31
- })
32
-
33
- if (!directory) {
34
- process.stdout.write('Usage: analyze <directory>\n')
35
- process.exit(1)
36
- }
37
-
38
- await x('bun', ['run', 'build'], { nodeOptions: { cwd: directory } })
39
-
40
- const fileStats = await getFileStats(path.join(directory, 'dist'))
41
- const markdownTable = [
42
- '| File | Size |',
43
- '| :--- | ---: |',
44
- ...fileStats.map(file => `| ${file.file} | ${filesize(file.size)} |`),
45
- ].join('\n')
46
- process.stdout.write(`${markdownTable}\n`)
47
- }
48
-
49
- main().catch((error) => {
50
- console.error(error)
51
- process.exit(1)
52
- })