@kevinmarrec/create-app 0.12.1 → 0.14.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 +1 -1
- package/dist/{index.js → index.mjs} +1 -1
- package/package.json +14 -16
- package/template/.docker/analytics/{service.yaml → service.yml} +1 -1
- package/template/.docker/metabase/{service.yaml → service.yml} +1 -1
- package/template/.docker/traefik/{service.yaml → service.yml} +1 -1
- package/template/.github/workflows/ci.yml +13 -12
- package/template/.vscode/settings.json +4 -3
- package/template/README.md +6 -3
- package/template/api/.env.development +1 -0
- package/template/api/Dockerfile +5 -25
- package/template/api/package.json +7 -7
- package/template/api/src/auth/index.ts +4 -4
- package/template/api/src/main.ts +4 -13
- package/template/api/src/utils/stopper.ts +19 -0
- package/template/app/package.json +12 -13
- package/template/app/src/App.vue +9 -9
- package/template/app/src/composables/auth.ts +23 -27
- package/template/app/src/composables/content.ts +10 -3
- package/template/app/src/lib/auth.ts +13 -0
- package/template/app/src/lib/orpc.ts +4 -10
- package/template/app/src/main.ts +9 -7
- package/template/app/src/utils/fetch.ts +9 -0
- package/template/app/vite.config.ts +1 -0
- package/template/{compose.yaml → compose.yml} +8 -8
- package/template/knip.config.ts +0 -3
- package/template/package.json +8 -10
- package/template/.github/scripts/build-stats.md.ts +0 -52
- /package/template/.docker/mailpit/{service.yaml → service.yml} +0 -0
- /package/template/.docker/postgres/{service.yaml → service.yml} +0 -0
- /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/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevinmarrec/create-app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"packageManager": "bun@1.3.
|
|
4
|
+
"version": "0.14.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",
|
|
@@ -21,9 +21,7 @@
|
|
|
21
21
|
"template/api",
|
|
22
22
|
"template/app"
|
|
23
23
|
],
|
|
24
|
-
"bin":
|
|
25
|
-
"create-app": "dist/index.js"
|
|
26
|
-
},
|
|
24
|
+
"bin": "./dist/index.mjs",
|
|
27
25
|
"files": [
|
|
28
26
|
"dist",
|
|
29
27
|
"template",
|
|
@@ -31,7 +29,7 @@
|
|
|
31
29
|
"template/.npmrc"
|
|
32
30
|
],
|
|
33
31
|
"scripts": {
|
|
34
|
-
"build": "tsdown
|
|
32
|
+
"build": "tsdown",
|
|
35
33
|
"check": "bun run check:eslint && bun run check:stylelint && bun run check:unused && bun run check:types",
|
|
36
34
|
"check:eslint": "eslint . --cache",
|
|
37
35
|
"check:stylelint": "stylelint '**/*.{css,scss,vue}' --ignorePath .gitignore --cache",
|
|
@@ -53,18 +51,18 @@
|
|
|
53
51
|
},
|
|
54
52
|
"devDependencies": {
|
|
55
53
|
"@faker-js/faker": "^10.1.0",
|
|
56
|
-
"@kevinmarrec/eslint-config": "^1.
|
|
57
|
-
"@kevinmarrec/stylelint-config": "^1.
|
|
58
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
59
|
-
"@types/bun": "^1.3.
|
|
60
|
-
"@vitest/coverage-v8": "^4.0.
|
|
54
|
+
"@kevinmarrec/eslint-config": "^1.6.0",
|
|
55
|
+
"@kevinmarrec/stylelint-config": "^1.6.0",
|
|
56
|
+
"@kevinmarrec/tsconfig": "^1.6.0",
|
|
57
|
+
"@types/bun": "^1.3.4",
|
|
58
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
61
59
|
"bumpp": "^10.3.2",
|
|
62
|
-
"eslint": "^9.39.
|
|
63
|
-
"knip": "^5.
|
|
60
|
+
"eslint": "^9.39.2",
|
|
61
|
+
"knip": "^5.73.4",
|
|
64
62
|
"stylelint": "^16.26.1",
|
|
65
|
-
"tsdown": "^0.
|
|
63
|
+
"tsdown": "^0.18.0",
|
|
66
64
|
"typescript": "^5.9.3",
|
|
67
|
-
"vitest": "^4.0.
|
|
68
|
-
"vue-tsc": "^3.1.
|
|
65
|
+
"vitest": "^4.0.15",
|
|
66
|
+
"vue-tsc": "^3.1.8"
|
|
69
67
|
}
|
|
70
68
|
}
|
|
@@ -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@
|
|
17
|
+
uses: kevinmarrec/workflows/setup-bun@17c15f6cd9619f71ff6db54691dc6ac0a73db2c6 # 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@
|
|
35
|
+
uses: kevinmarrec/workflows/setup-bun@17c15f6cd9619f71ff6db54691dc6ac0a73db2c6 # main
|
|
33
36
|
|
|
34
|
-
- name:
|
|
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:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
- name: Analyze project build size differences
|
|
41
|
+
uses: kevinmarrec/workflows/filesize-diff@17c15f6cd9619f71ff6db54691dc6ac0a73db2c6 # 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.
|
|
65
|
+
"**/.docker/*/service.yml": "dockercompose"
|
|
65
66
|
},
|
|
66
67
|
|
|
67
68
|
"material-icon-theme.folders.associations": {
|
package/template/README.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
package/template/api/Dockerfile
CHANGED
|
@@ -1,45 +1,25 @@
|
|
|
1
|
-
# --- Stage 1: Dependency Installation & Caching (The Build Stage) ---
|
|
2
|
-
|
|
3
1
|
ARG TARGETARCH
|
|
4
2
|
|
|
5
|
-
|
|
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
|
-
|
|
17
|
+
FROM gcr.io/distroless/base-debian13:nonroot AS runner
|
|
34
18
|
|
|
35
|
-
|
|
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
|
-
|
|
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.
|
|
14
|
-
"better-auth": "^1.4.
|
|
15
|
-
"drizzle-orm": "^0.
|
|
13
|
+
"@orpc/server": "^1.12.3",
|
|
14
|
+
"better-auth": "^1.4.7",
|
|
15
|
+
"drizzle-orm": "^0.45.1",
|
|
16
16
|
"pino": "^10.1.0",
|
|
17
17
|
"valibot": "^1.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
21
|
-
"@types/bun": "^1.3.
|
|
22
|
-
"drizzle-kit": "^0.31.
|
|
20
|
+
"@kevinmarrec/tsconfig": "^1.6.0",
|
|
21
|
+
"@types/bun": "^1.3.4",
|
|
22
|
+
"drizzle-kit": "^0.31.8",
|
|
23
23
|
"pg": "^8.16.3",
|
|
24
|
-
"pino-pretty": "^13.1.
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
19
|
-
logger[level](message)
|
|
19
|
+
logger[level](`[Auth] ${message}`)
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
emailAndPassword: {
|
package/template/api/src/main.ts
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
-
// Graceful Shutdown
|
|
28
|
+
stopper.bind(server)
|
|
31
29
|
|
|
32
|
-
|
|
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,32 +9,31 @@
|
|
|
9
9
|
"preview": "vite preview --open"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kevinmarrec/vue-i18n": "^1.
|
|
13
|
-
"@orpc/client": "^1.12.
|
|
14
|
-
"@
|
|
15
|
-
"@tanstack/query-core": "^5.90.11",
|
|
16
|
-
"@tanstack/vue-query": "^5.92.0",
|
|
12
|
+
"@kevinmarrec/vue-i18n": "^1.2.0",
|
|
13
|
+
"@orpc/client": "^1.12.3",
|
|
14
|
+
"@pinia/colada": "^0.18.1",
|
|
17
15
|
"@unhead/vue": "^2.0.19",
|
|
18
16
|
"@vueuse/core": "^14.1.0",
|
|
19
|
-
"better-auth": "^1.4.
|
|
20
|
-
"
|
|
17
|
+
"better-auth": "^1.4.7",
|
|
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.
|
|
25
|
-
"@kevinmarrec/unocss-config": "^1.
|
|
26
|
-
"@kevinmarrec/vite-plugin-dark-mode": "^1.
|
|
23
|
+
"@kevinmarrec/tsconfig": "^1.6.0",
|
|
24
|
+
"@kevinmarrec/unocss-config": "^1.6.0",
|
|
25
|
+
"@kevinmarrec/vite-plugin-dark-mode": "^1.2.0",
|
|
27
26
|
"@modyfi/vite-plugin-yaml": "^1.1.1",
|
|
28
27
|
"@unhead/addons": "^2.0.19",
|
|
29
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
28
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
30
29
|
"beasties": "^0.3.5",
|
|
31
30
|
"rollup-plugin-visualizer": "^6.0.5",
|
|
32
31
|
"typescript": "~5.9.3",
|
|
33
32
|
"valibot": "^1.2.0",
|
|
34
|
-
"vite": "^7.
|
|
33
|
+
"vite": "^7.3.0",
|
|
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",
|
|
38
|
-
"vite-tsconfig-paths": "^
|
|
37
|
+
"vite-tsconfig-paths": "^6.0.1"
|
|
39
38
|
}
|
|
40
39
|
}
|
package/template/app/src/App.vue
CHANGED
|
@@ -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="
|
|
58
|
-
{{
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { computed } from 'vue'
|
|
1
|
+
import { defineMutation } from '@pinia/colada'
|
|
2
|
+
import { set } from '@vueuse/core'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
baseURL: `${import.meta.env.VITE_API_URL}/auth`,
|
|
7
|
-
fetchOptions: {
|
|
8
|
-
credentials: 'include',
|
|
9
|
-
},
|
|
10
|
-
})
|
|
5
|
+
import { authClient, type AuthError } from '../lib/auth'
|
|
11
6
|
|
|
12
|
-
const
|
|
7
|
+
const authError = ref<AuthError>()
|
|
13
8
|
|
|
14
|
-
function
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
function defineAuthMutation<TVars, TData>(mutation: (vars: TVars) => Promise<TData>) {
|
|
10
|
+
return defineMutation({
|
|
11
|
+
mutation,
|
|
12
|
+
onMutate: () => {
|
|
13
|
+
set(authError, undefined)
|
|
14
|
+
},
|
|
15
|
+
onSettled: (_, error) => {
|
|
16
|
+
set(authError, error)
|
|
22
17
|
},
|
|
23
18
|
})
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const signOut = createAuthMutation(authClient.signOut)
|
|
21
|
+
const useSignIn = defineAuthMutation(authClient.signIn.email)
|
|
22
|
+
const useSignUp = defineAuthMutation(authClient.signUp.email)
|
|
23
|
+
const useSignOut = defineAuthMutation(authClient.signOut)
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
export function useAuth() {
|
|
26
|
+
const session = authClient.useSession()
|
|
32
27
|
|
|
33
28
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
error: authError,
|
|
30
|
+
user: computed(() => session.value.data?.user),
|
|
31
|
+
signIn: useSignIn(),
|
|
32
|
+
signUp: useSignUp(),
|
|
33
|
+
signOut: useSignOut(),
|
|
38
34
|
}
|
|
39
35
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { useQuery } from '@
|
|
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(
|
|
7
|
-
|
|
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,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createAuthClient, type ErrorContext } from 'better-auth/vue'
|
|
2
|
+
|
|
3
|
+
import { getFetchOptions } from '../utils/fetch'
|
|
4
|
+
|
|
5
|
+
export type AuthError = ErrorContext['error']
|
|
6
|
+
|
|
7
|
+
export const authClient = createAuthClient({
|
|
8
|
+
baseURL: `${import.meta.env.VITE_API_URL}/auth`,
|
|
9
|
+
fetchOptions: {
|
|
10
|
+
...getFetchOptions(),
|
|
11
|
+
onError: ({ error }) => Promise.reject(error),
|
|
12
|
+
},
|
|
13
|
+
})
|
|
@@ -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
|
|
18
|
-
|
|
19
|
-
export const orpc = createTanstackQueryUtils(client)
|
|
13
|
+
export const orpc = createORPCClient<RouterClient<Router>>(link)
|
package/template/app/src/main.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createI18n } from '@kevinmarrec/vue-i18n'
|
|
2
|
-
import {
|
|
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
|
-
|
|
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(
|
|
17
|
-
|
|
15
|
+
app.use(createPinia())
|
|
16
|
+
app.use(PiniaColada, {
|
|
17
|
+
queryOptions: {
|
|
18
|
+
enabled: !import.meta.env.SSR,
|
|
19
|
+
},
|
|
18
20
|
})
|
|
19
21
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
include:
|
|
2
|
-
- path: .docker/traefik/service.
|
|
3
|
-
- path: .docker/postgres/service.
|
|
4
|
-
- path: .docker/metabase/service.
|
|
5
|
-
- path: .docker/studio/service.
|
|
6
|
-
- path: .docker/analytics/service.
|
|
7
|
-
- path: .docker/mailpit/service.
|
|
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.
|
|
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.
|
|
39
|
+
image: imbios/bun-node:1.3.4-24-alpine
|
|
40
40
|
container_name: app
|
|
41
41
|
init: true
|
|
42
42
|
depends_on:
|
package/template/knip.config.ts
CHANGED
package/template/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "project",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": true,
|
|
5
|
-
"packageManager": "bun@1.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.
|
|
26
|
-
"@kevinmarrec/stylelint-config": "^1.
|
|
27
|
-
"@kevinmarrec/tsconfig": "^1.
|
|
28
|
-
"eslint": "^9.39.
|
|
29
|
-
"
|
|
30
|
-
"knip": "^5.70.2",
|
|
26
|
+
"@kevinmarrec/eslint-config": "^1.6.0",
|
|
27
|
+
"@kevinmarrec/stylelint-config": "^1.6.0",
|
|
28
|
+
"@kevinmarrec/tsconfig": "^1.6.0",
|
|
29
|
+
"eslint": "^9.39.2",
|
|
30
|
+
"knip": "^5.73.4",
|
|
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.
|
|
33
|
+
"vue-tsc": "^3.1.8"
|
|
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
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|