create-better-t-stack 3.8.3 → 3.9.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
@@ -90,7 +90,7 @@ You can disable telemetry by setting the `BTS_TELEMETRY_DISABLED` environment va
90
90
 
91
91
  ```bash
92
92
  # Disable telemetry for a single run
93
- BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack my-app
93
+ BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack
94
94
 
95
95
  # Disable telemetry globally in your shell profile (.bashrc, .zshrc, etc.)
96
96
  export BTS_TELEMETRY_DISABLED=1
@@ -101,85 +101,85 @@ export BTS_TELEMETRY_DISABLED=1
101
101
  Create a project with default configuration:
102
102
 
103
103
  ```bash
104
- npx create-better-t-stack my-app --yes
104
+ npx create-better-t-stack --yes
105
105
  ```
106
106
 
107
107
  Create a project with specific options:
108
108
 
109
109
  ```bash
110
- npx create-better-t-stack my-app --database postgres --orm drizzle --auth --addons pwa biome
110
+ npx create-better-t-stack --database postgres --orm drizzle --auth --addons pwa biome
111
111
  ```
112
112
 
113
113
  Create a project with Elysia backend and Node.js runtime:
114
114
 
115
115
  ```bash
116
- npx create-better-t-stack my-app --backend elysia --runtime node
116
+ npx create-better-t-stack --backend elysia --runtime node
117
117
  ```
118
118
 
119
119
  Create a project with multiple frontend options (one web + one native):
120
120
 
121
121
  ```bash
122
- npx create-better-t-stack my-app --frontend tanstack-router native-bare
122
+ npx create-better-t-stack --frontend tanstack-router native-bare
123
123
  ```
124
124
 
125
125
  Create a project with examples:
126
126
 
127
127
  ```bash
128
- npx create-better-t-stack my-app --examples todo ai
128
+ npx create-better-t-stack --examples todo ai
129
129
  ```
130
130
 
131
131
  Create a project with Turso database setup:
132
132
 
133
133
  ```bash
134
- npx create-better-t-stack my-app --database sqlite --orm drizzle --db-setup turso
134
+ npx create-better-t-stack --database sqlite --orm drizzle --db-setup turso
135
135
  ```
136
136
 
137
137
  Create a project with Supabase PostgreSQL setup:
138
138
 
139
139
  ```bash
140
- npx create-better-t-stack my-app --database postgres --orm drizzle --db-setup supabase --auth
140
+ npx create-better-t-stack --database postgres --orm drizzle --db-setup supabase --auth
141
141
  ```
142
142
 
143
143
  Create a project with Convex backend:
144
144
 
145
145
  ```bash
146
- npx create-better-t-stack my-app --backend convex --frontend tanstack-router
146
+ npx create-better-t-stack --backend convex --frontend tanstack-router
147
147
  ```
148
148
 
149
149
  Create a project with documentation site:
150
150
 
151
151
  ```bash
152
- npx create-better-t-stack my-app --addons starlight
152
+ npx create-better-t-stack --addons starlight
153
153
  ```
154
154
 
155
155
  Create a minimal TypeScript project with no backend:
156
156
 
157
157
  ```bash
158
- npx create-better-t-stack my-app --backend none --frontend tanstack-router
158
+ npx create-better-t-stack --backend none --frontend tanstack-router
159
159
  ```
160
160
 
161
161
  Create a backend-only project with no frontend:
162
162
 
163
163
  ```bash
164
- npx create-better-t-stack my-app --frontend none --backend hono --database postgres --orm drizzle
164
+ npx create-better-t-stack --frontend none --backend hono --database postgres --orm drizzle
165
165
  ```
166
166
 
167
167
  Create a simple frontend-only project:
168
168
 
169
169
  ```bash
170
- npx create-better-t-stack my-app --backend none --frontend next --addons none --examples none
170
+ npx create-better-t-stack --backend none --frontend next --addons none --examples none
171
171
  ```
172
172
 
173
173
  Create a Cloudflare Workers project:
174
174
 
175
175
  ```bash
176
- npx create-better-t-stack my-app --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
176
+ npx create-better-t-stack --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
177
177
  ```
178
178
 
179
179
  Create a minimal API-only project:
180
180
 
181
181
  ```bash
182
- npx create-better-t-stack my-app --frontend none --backend hono --api trpc --database none --addons none
182
+ npx create-better-t-stack --frontend none --backend hono --api trpc --database none --addons none
183
183
  ```
184
184
 
185
185
  ## Compatibility Notes
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createBtsCli } from "./src-BCBL3cO2.mjs";
2
+ import { n as createBtsCli } from "./src-DLvUK0Qf.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-BCBL3cO2.mjs";
2
+ import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-DLvUK0Qf.mjs";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -15,7 +15,7 @@ import { $, execa } from "execa";
15
15
  import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
16
16
  import { glob } from "tinyglobby";
17
17
  import handlebars from "handlebars";
18
- import { Biome } from "@biomejs/js-api/nodejs";
18
+ import { format } from "oxfmt";
19
19
  import yaml from "yaml";
20
20
  import os$1 from "node:os";
21
21
 
@@ -96,6 +96,7 @@ const dependencyVersionMap = {
96
96
  "@tauri-apps/cli": "^2.4.0",
97
97
  "@biomejs/biome": "^2.2.0",
98
98
  oxlint: "^1.32.0",
99
+ oxfmt: "^0.19.0",
99
100
  husky: "^9.1.7",
100
101
  "lint-staged": "^16.1.2",
101
102
  tsx: "^4.19.2",
@@ -363,7 +364,7 @@ function getAddonDisplay(addon) {
363
364
  break;
364
365
  case "oxlint":
365
366
  label = "Oxlint";
366
- hint = "Rust-powered linter";
367
+ hint = "Oxlint + Oxfmt (linting & formatting)";
367
368
  break;
368
369
  case "ultracite":
369
370
  label = "Ultracite";
@@ -2031,6 +2032,38 @@ async function setupFumadocs(config) {
2031
2032
  }
2032
2033
  }
2033
2034
 
2035
+ //#endregion
2036
+ //#region src/helpers/addons/oxlint-setup.ts
2037
+ async function setupOxlint(projectDir, packageManager) {
2038
+ await addPackageDependency({
2039
+ devDependencies: ["oxlint", "oxfmt"],
2040
+ projectDir
2041
+ });
2042
+ const packageJsonPath = path.join(projectDir, "package.json");
2043
+ if (await fs.pathExists(packageJsonPath)) {
2044
+ const packageJson = await fs.readJson(packageJsonPath);
2045
+ packageJson.scripts = {
2046
+ ...packageJson.scripts,
2047
+ check: "oxlint && oxfmt --write"
2048
+ };
2049
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2050
+ }
2051
+ const s = spinner();
2052
+ const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
2053
+ s.start("Initializing oxlint and oxfmt...");
2054
+ await execa(oxlintInitCommand, {
2055
+ cwd: projectDir,
2056
+ env: { CI: "true" },
2057
+ shell: true
2058
+ });
2059
+ await execa(getPackageExecutionCommand(packageManager, "oxfmt@latest --init"), {
2060
+ cwd: projectDir,
2061
+ env: { CI: "true" },
2062
+ shell: true
2063
+ });
2064
+ s.stop("oxlint and oxfmt initialized successfully!");
2065
+ }
2066
+
2034
2067
  //#endregion
2035
2068
  //#region src/helpers/addons/ruler-setup.ts
2036
2069
  async function setupRuler(config) {
@@ -2400,7 +2433,7 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
2400
2433
  await setupHusky(projectDir, linter);
2401
2434
  }
2402
2435
  }
2403
- if (addons.includes("oxlint")) await setupOxlint(projectDir, packageManager);
2436
+ if (hasOxlint) await setupOxlint(projectDir, packageManager);
2404
2437
  if (addons.includes("starlight")) await setupStarlight(config);
2405
2438
  if (addons.includes("ruler")) await setupRuler(config);
2406
2439
  if (addons.includes("fumadocs")) await setupFumadocs(config);
@@ -2442,7 +2475,7 @@ async function setupHusky(projectDir, linter) {
2442
2475
  ...packageJson.scripts,
2443
2476
  prepare: "husky"
2444
2477
  };
2445
- if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
2478
+ if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
2446
2479
  else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
2447
2480
  else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
2448
2481
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
@@ -2473,30 +2506,6 @@ async function setupPwa(projectDir, frontends) {
2473
2506
  const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
2474
2507
  if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
2475
2508
  }
2476
- async function setupOxlint(projectDir, packageManager) {
2477
- await addPackageDependency({
2478
- devDependencies: ["oxlint"],
2479
- projectDir
2480
- });
2481
- const packageJsonPath = path.join(projectDir, "package.json");
2482
- if (await fs.pathExists(packageJsonPath)) {
2483
- const packageJson = await fs.readJson(packageJsonPath);
2484
- packageJson.scripts = {
2485
- ...packageJson.scripts,
2486
- check: "oxlint"
2487
- };
2488
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2489
- }
2490
- const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
2491
- const s = spinner();
2492
- s.start("Initializing oxlint...");
2493
- await execa(oxlintInitCommand, {
2494
- cwd: projectDir,
2495
- env: { CI: "true" },
2496
- shell: true
2497
- });
2498
- s.stop("oxlint initialized successfully!");
2499
- }
2500
2509
 
2501
2510
  //#endregion
2502
2511
  //#region src/helpers/core/detect-project-config.ts
@@ -2552,61 +2561,16 @@ async function installDependencies({ projectDir, packageManager }) {
2552
2561
  }
2553
2562
 
2554
2563
  //#endregion
2555
- //#region src/utils/biome-formatter.ts
2556
- function initializeBiome() {
2557
- try {
2558
- const biome = new Biome();
2559
- const projectKey = biome.openProject("./").projectKey;
2560
- biome.applyConfiguration(projectKey, {
2561
- formatter: {
2562
- enabled: true,
2563
- indentStyle: "tab",
2564
- indentWidth: 2,
2565
- lineWidth: 80
2566
- },
2567
- linter: { enabled: false },
2568
- javascript: { formatter: { enabled: true } },
2569
- json: { formatter: { enabled: true } }
2570
- });
2571
- return {
2572
- biome,
2573
- projectKey
2574
- };
2575
- } catch {
2576
- return null;
2577
- }
2578
- }
2579
- function isSupportedFile(filePath) {
2580
- const ext = path.extname(filePath).toLowerCase();
2581
- return [
2582
- ".js",
2583
- ".jsx",
2584
- ".ts",
2585
- ".tsx",
2586
- ".json",
2587
- ".jsonc"
2588
- ].includes(ext);
2589
- }
2590
- function shouldSkipFile(filePath) {
2591
- const basename = path.basename(filePath);
2592
- return [
2593
- ".hbs",
2594
- "package-lock.json",
2595
- "yarn.lock",
2596
- "pnpm-lock.yaml",
2597
- "bun.lock",
2598
- ".d.ts"
2599
- ].some((pattern) => basename.includes(pattern));
2600
- }
2601
- function formatFileWithBiome(filePath, content) {
2602
- if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
2564
+ //#region src/utils/file-formatter.ts
2565
+ const formatOptions = {
2566
+ experimentalSortPackageJson: true,
2567
+ experimentalSortImports: { order: "asc" }
2568
+ };
2569
+ async function formatFile(filePath, content) {
2603
2570
  try {
2604
- const biomeResult = initializeBiome();
2605
- if (!biomeResult) return null;
2606
- const { biome, projectKey } = biomeResult;
2607
- const result = biome.formatContent(projectKey, content, { filePath: path.basename(filePath) });
2608
- if (result.diagnostics && result.diagnostics.length > 0) consola.debug(`Biome formatting diagnostics for ${filePath}:`, result.diagnostics);
2609
- return result.content;
2571
+ const result = await format(path.basename(filePath), content, formatOptions);
2572
+ if (result.errors && result.errors.length > 0) return null;
2573
+ return result.code;
2610
2574
  } catch {
2611
2575
  return null;
2612
2576
  }
@@ -2636,7 +2600,7 @@ async function processTemplate(srcPath, destPath, context) {
2636
2600
  content = handlebars.compile(templateContent)(context);
2637
2601
  } else content = await fs.readFile(srcPath, "utf-8");
2638
2602
  try {
2639
- const formattedContent = await formatFileWithBiome(destPath, content);
2603
+ const formattedContent = await formatFile(destPath, content);
2640
2604
  if (formattedContent) content = formattedContent;
2641
2605
  } catch (formatError) {
2642
2606
  consola.debug(`Failed to format ${destPath}:`, formatError);
@@ -5949,6 +5913,7 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
5949
5913
  for (const addon of addons) if (addon === "pwa") addonsList.push("- **PWA** - Progressive Web App support");
5950
5914
  else if (addon === "tauri") addonsList.push("- **Tauri** - Build native desktop applications");
5951
5915
  else if (addon === "biome") addonsList.push("- **Biome** - Linting and formatting");
5916
+ else if (addon === "oxlint") addonsList.push("- **Oxlint** - Oxlint + Oxfmt (linting & formatting)");
5952
5917
  else if (addon === "husky") addonsList.push("- **Husky** - Git hooks for code quality");
5953
5918
  else if (addon === "starlight") addonsList.push("- **Starlight** - Documentation site with Astro");
5954
5919
  else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
@@ -6024,6 +5989,8 @@ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNati
6024
5989
  }
6025
5990
  if (addons.includes("biome")) scripts += `
6026
5991
  - \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
5992
+ if (addons.includes("oxlint")) scripts += `
5993
+ - \`${packageManagerRunCmd} check\`: Run Oxlint and Oxfmt`;
6027
5994
  if (addons.includes("pwa")) scripts += `
6028
5995
  - \`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\`: Generate PWA assets`;
6029
5996
  if (addons.includes("tauri")) scripts += `
@@ -6175,10 +6142,12 @@ async function displayPostInstallInstructions(config) {
6175
6142
  const isBackendSelf = backend === "self";
6176
6143
  const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
6177
6144
  const cdCmd = `cd ${relativePath}`;
6178
- const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
6145
+ const hasHusky = addons?.includes("husky");
6146
+ const hasLinting = addons?.includes("biome") || addons?.includes("oxlint");
6179
6147
  const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
6180
6148
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
6181
- const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
6149
+ const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
6150
+ const lintingInstructions = hasLinting ? getLintingInstructions(runCmd) : "";
6182
6151
  const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || []) : "";
6183
6152
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
6184
6153
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
@@ -6232,6 +6201,7 @@ async function displayPostInstallInstructions(config) {
6232
6201
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
6233
6202
  if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
6234
6203
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
6204
+ if (huskyInstructions) output += `\n${huskyInstructions.trim()}\n`;
6235
6205
  if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
6236
6206
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
6237
6207
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
@@ -6253,6 +6223,9 @@ function getNativeInstructions(isConvex, isBackendSelf, _frontend) {
6253
6223
  if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
6254
6224
  return instructions;
6255
6225
  }
6226
+ function getHuskyInstructions(runCmd) {
6227
+ return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
6228
+ }
6256
6229
  function getLintingInstructions(runCmd) {
6257
6230
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
6258
6231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.8.3",
3
+ "version": "3.9.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,8 +51,10 @@
51
51
  "build": "tsdown --publint",
52
52
  "dev": "tsdown --watch",
53
53
  "check-types": "tsc --noEmit",
54
- "test": "bun run build && vitest run; rm -rf .smoke || true",
55
- "test:ui": "bun run build && vitest --ui",
54
+ "test": "bun run build && bun test",
55
+ "test:watch": "bun run build && bun test --watch",
56
+ "test:coverage": "bun run build && bun test --coverage",
57
+ "test:ci": "bun run build && AGENT=1 bun test --bail=5",
56
58
  "prepublishOnly": "npm run build"
57
59
  },
58
60
  "exports": {
@@ -65,9 +67,7 @@
65
67
  }
66
68
  },
67
69
  "dependencies": {
68
- "@better-t-stack/types": "^3.8.3",
69
- "@biomejs/js-api": "^4.0.0",
70
- "@biomejs/wasm-nodejs": "^2.3.8",
70
+ "@better-t-stack/types": "^3.9.0",
71
71
  "@clack/prompts": "^1.0.0-alpha.8",
72
72
  "@orpc/server": "^1.12.2",
73
73
  "consola": "^3.4.2",
@@ -76,6 +76,7 @@
76
76
  "gradient-string": "^3.0.0",
77
77
  "handlebars": "^4.7.8",
78
78
  "jsonc-parser": "^3.3.1",
79
+ "oxfmt": "^0.19.0",
79
80
  "picocolors": "^1.1.1",
80
81
  "tinyglobby": "^0.2.15",
81
82
  "trpc-cli": "^0.12.1",
@@ -84,12 +85,11 @@
84
85
  "zod": "^4.1.13"
85
86
  },
86
87
  "devDependencies": {
88
+ "@types/bun": "^1.2.17",
87
89
  "@types/fs-extra": "^11.0.4",
88
90
  "@types/node": "^24.10.2",
89
- "@vitest/ui": "^4.0.15",
90
91
  "publint": "^0.3.16",
91
92
  "tsdown": "^0.17.2",
92
- "typescript": "^5.9.3",
93
- "vitest": "^4.0.15"
93
+ "typescript": "^5.9.3"
94
94
  }
95
95
  }
@@ -1,4 +1,3 @@
1
- import type { auth } from "@{{projectName}}/auth";
2
1
  import { createAuthClient } from "better-auth/react";
3
2
  {{#if (eq payments "polar")}}
4
3
  import { polarClient } from "@polar-sh/better-auth";