@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 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.10.1";
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,8 +1,8 @@
1
1
  {
2
2
  "name": "@kevinmarrec/create-app",
3
3
  "type": "module",
4
- "version": "0.10.1",
5
- "packageManager": "bun@1.3.2",
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.6",
57
- "@kevinmarrec/stylelint-config": "^1.5.6",
58
- "@kevinmarrec/tsconfig": "^1.5.6",
59
- "@types/bun": "^1.3.2",
60
- "@vitest/coverage-v8": "^4.0.9",
61
- "bumpp": "^10.3.1",
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.69.1",
64
- "stylelint": "^16.25.0",
65
- "tsdown": "^0.16.4",
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.9",
68
- "vue-tsc": "^3.1.4"
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,5 @@
1
+ -- Main database
2
+ CREATE DATABASE app;
3
+
4
+ -- Analytics database
5
+ CREATE DATABASE analytics;
@@ -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",