@kevinmarrec/create-app 0.10.1 → 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 +29 -5
- package/dist/index.js +8 -13
- package/package.json +13 -13
- package/template/.docker/analytics/service.yaml +22 -0
- package/template/.docker/mailpit/service.yaml +13 -0
- package/template/.docker/metabase/service.yaml +27 -0
- package/template/.docker/postgres/init.sql +5 -0
- package/template/.docker/postgres/service.yaml +20 -0
- package/template/.docker/studio/service.yaml +22 -0
- package/template/.docker/traefik/service.yaml +41 -0
- package/template/.github/renovate.json +6 -1
- package/template/.vscode/settings.json +10 -5
- package/template/README.md +92 -193
- package/template/api/Dockerfile +45 -0
- package/template/api/package.json +7 -5
- package/template/api/src/env.ts +18 -5
- package/template/api/src/utils/cors.ts +1 -1
- package/template/api/tsconfig.json +1 -1
- package/template/app/.env +2 -0
- package/template/app/index.html +3 -0
- package/template/app/package.json +16 -12
- package/template/app/src/App.vue +0 -5
- package/template/app/src/env.d.ts +1 -3
- package/template/app/tsconfig.json +8 -3
- package/template/app/vite.config.ts +61 -41
- package/template/compose.yaml +14 -88
- package/template/knip.config.ts +3 -2
- package/template/package.json +8 -7
- package/template/tsconfig.json +1 -8
package/README.md
CHANGED
|
@@ -2,17 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## Description
|
|
4
4
|
|
|
5
|
-
CLI that scaffolds an opinionated [Bun](https://bun.sh) & [Vue](https://vuejs.org) fullstack application.
|
|
5
|
+
CLI that scaffolds an opinionated [Bun](https://bun.sh/) & [Vue](https://vuejs.org/) fullstack application.
|
|
6
6
|
|
|
7
7
|
## Opinions
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- [
|
|
9
|
+
### Core Stack
|
|
10
|
+
|
|
11
|
+
- [Bun](https://bun.sh/) as package manager & runtime
|
|
12
|
+
- [TypeScript](https://www.typescriptlang.org/) as the language
|
|
13
|
+
- [Vue 3](https://vuejs.org/) as frontend framework
|
|
14
|
+
- [Vite](https://vitejs.dev/) as build tool
|
|
15
|
+
- [Vite SSG](https://github.com/antfu-collective/vite-ssg) for static site generation
|
|
16
|
+
- [UnoCSS](https://unocss.dev/) for styling
|
|
17
|
+
- [TanStack Query](https://tanstack.com/query/latest) for data fetching
|
|
18
|
+
- [Unhead](https://unhead.unjs.io/) for head management
|
|
19
|
+
- [Vue I18n](https://github.com/kevinmarrec/vue-i18n) for internationalization
|
|
20
|
+
- [oRPC](https://orpc.dev/) for API layer
|
|
21
|
+
- [Better Auth](https://www.better-auth.com/) for authentication
|
|
22
|
+
- [Pino](https://getpino.io/) for logging
|
|
23
|
+
- [Drizzle ORM](https://orm.drizzle.team/) as SQL-first TypeScript ORM
|
|
24
|
+
- [Valibot](https://valibot.dev/) for validation
|
|
25
|
+
- [PostgreSQL](https://www.postgresql.org/) as database
|
|
26
|
+
- [Docker Compose](https://docs.docker.com/compose/) & [Traefik](https://traefik.io/) for infrastructure
|
|
27
|
+
|
|
28
|
+
### Development Tools
|
|
29
|
+
|
|
30
|
+
- [ESLint](https://eslint.org/) & [Stylelint](https://stylelint.io/) for linting
|
|
31
|
+
- [Knip](https://knip.dev/) for detecting unused dependencies, exports, and files
|
|
32
|
+
- [Metabase](https://www.metabase.com/) for Business Intelligence (BI)
|
|
33
|
+
- [Drizzle Studio](https://gateway.drizzle.team/) for database management (web GUI)
|
|
34
|
+
- [Umami](https://umami.is/) for web analytics
|
|
35
|
+
- [Mailpit](https://mailpit.axllent.org/) for email testing
|
|
12
36
|
|
|
13
37
|
## Usage
|
|
14
38
|
|
|
15
|
-
> Requires [Bun](https://bun.sh) v1.3 _or later_.
|
|
39
|
+
> Requires [Bun](https://bun.sh/) v1.3 _or later_.
|
|
16
40
|
|
|
17
41
|
```sh
|
|
18
42
|
bun create @kevinmarrec/app
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,12 @@ import { x } from "tinyexec";
|
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.
|
|
11
|
+
var version = "0.12.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/utils/fs.ts
|
|
15
|
-
const
|
|
15
|
+
const IGNORED_FILES = new Set([".git"]);
|
|
16
|
+
const ignorePredicate = (filename) => IGNORED_FILES.has(filename);
|
|
16
17
|
async function empty(dir) {
|
|
17
18
|
const entries = await fs.readdir(dir);
|
|
18
19
|
await Promise.all(entries.filter((entry) => !ignorePredicate(entry)).map((entry) => fs.rm(resolve(dir, entry), { recursive: true })));
|
|
@@ -33,7 +34,7 @@ var fs_default = {
|
|
|
33
34
|
//#endregion
|
|
34
35
|
//#region src/scaffold.ts
|
|
35
36
|
async function scaffold(root) {
|
|
36
|
-
await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root);
|
|
37
|
+
await fs_default.exists(root) ? await fs_default.empty(root) : await fs_default.mkdir(root, { recursive: true });
|
|
37
38
|
await fs_default.cp(join(import.meta.dirname, "../template"), root, { recursive: true });
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -65,14 +66,7 @@ async function run() {
|
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
if (options.help) {
|
|
68
|
-
process.stdout.write(
|
|
69
|
-
Usage: create-app [OPTIONS...] [DIRECTORY]
|
|
70
|
-
|
|
71
|
-
Options:
|
|
72
|
-
-f, --force Create the project even if the directory is not empty.
|
|
73
|
-
-h, --help Display this help message.
|
|
74
|
-
-v, --version Display the version number of this CLI.
|
|
75
|
-
`);
|
|
69
|
+
process.stdout.write("Usage: create-app [OPTIONS...] [DIRECTORY]\n\nOptions:\n -f, --force Create the project even if the directory is not empty.\n -h, --help Display this help message.\n -v, --version Display the version number of this CLI.\n");
|
|
76
70
|
process.exit(0);
|
|
77
71
|
}
|
|
78
72
|
if (options.version) {
|
|
@@ -81,7 +75,7 @@ Options:
|
|
|
81
75
|
}
|
|
82
76
|
process.stdout.write("\n");
|
|
83
77
|
intro(`create-app ${c.dim(`v${version}`)}`);
|
|
84
|
-
let projectName = positionals[0] || await text({
|
|
78
|
+
let projectName = positionals[0]?.trim() || await text({
|
|
85
79
|
message: "Project name",
|
|
86
80
|
placeholder: "my-app",
|
|
87
81
|
validate: (value) => {
|
|
@@ -92,7 +86,7 @@ Options:
|
|
|
92
86
|
projectName = projectName.trim();
|
|
93
87
|
const cwd = process.cwd();
|
|
94
88
|
const targetDir = resolve(cwd, projectName);
|
|
95
|
-
if (!
|
|
89
|
+
if (!options.force && !await fs_default.emptyCheck(targetDir)) {
|
|
96
90
|
await log.warn(`${targetDir === cwd ? "Current directory" : `Target directory ${c.blue(targetDir)}`} is not empty`);
|
|
97
91
|
maybeCancel(await confirm({
|
|
98
92
|
message: "Remove existing files and continue?",
|
|
@@ -136,6 +130,7 @@ Options:
|
|
|
136
130
|
//#region src/index.ts
|
|
137
131
|
run().catch((error) => {
|
|
138
132
|
console.error(error);
|
|
133
|
+
process.exitCode = 1;
|
|
139
134
|
});
|
|
140
135
|
|
|
141
136
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,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.12.0",
|
|
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>",
|
|
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.
|
|
57
|
-
"@kevinmarrec/stylelint-config": "^1.5.
|
|
58
|
-
"@kevinmarrec/tsconfig": "^1.5.
|
|
59
|
-
"@types/bun": "^1.3.
|
|
60
|
-
"@vitest/coverage-v8": "^4.0.
|
|
61
|
-
"bumpp": "^10.3.
|
|
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",
|
|
61
|
+
"bumpp": "^10.3.2",
|
|
62
62
|
"eslint": "^9.39.1",
|
|
63
|
-
"knip": "^5.
|
|
64
|
-
"stylelint": "^16.
|
|
65
|
-
"tsdown": "^0.16.
|
|
63
|
+
"knip": "^5.70.2",
|
|
64
|
+
"stylelint": "^16.26.1",
|
|
65
|
+
"tsdown": "^0.16.8",
|
|
66
66
|
"typescript": "^5.9.3",
|
|
67
|
-
"vitest": "^4.0.
|
|
68
|
-
"vue-tsc": "^3.1.
|
|
67
|
+
"vitest": "^4.0.14",
|
|
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",
|