create-better-t-stack 2.17.1 → 2.18.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/dist/index.js CHANGED
@@ -46,8 +46,8 @@ const DEFAULT_CONFIG = {
46
46
  api: "trpc"
47
47
  };
48
48
  const dependencyVersionMap = {
49
- "better-auth": "^1.2.8",
50
- "@better-auth/expo": "^1.2.8",
49
+ "better-auth": "^1.2.9",
50
+ "@better-auth/expo": "^1.2.9",
51
51
  "drizzle-orm": "^0.38.4",
52
52
  "drizzle-kit": "^0.30.5",
53
53
  "@libsql/client": "^0.14.0",
@@ -86,9 +86,9 @@ const dependencyVersionMap = {
86
86
  "@ai-sdk/svelte": "^2.1.9",
87
87
  "@ai-sdk/react": "^1.2.12",
88
88
  "@prisma/extension-accelerate": "^1.3.0",
89
- "@orpc/server": "^1.4.1",
90
- "@orpc/client": "^1.4.1",
91
- "@orpc/tanstack-query": "^1.4.1",
89
+ "@orpc/server": "^1.5.0",
90
+ "@orpc/client": "^1.5.0",
91
+ "@orpc/tanstack-query": "^1.5.0",
92
92
  "@trpc/tanstack-react-query": "^11.0.0",
93
93
  "@trpc/server": "^11.0.0",
94
94
  "@trpc/client": "^11.0.0",
@@ -2894,7 +2894,7 @@ async function getFrontendChoice(frontendOptions, backend) {
2894
2894
  },
2895
2895
  {
2896
2896
  value: "tanstack-start",
2897
- label: "TanStack Start (beta)",
2897
+ label: "TanStack Start (devinxi)",
2898
2898
  hint: "SSR, Server Functions, API Routes and more with TanStack Router"
2899
2899
  }
2900
2900
  ];
@@ -3120,70 +3120,6 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
3120
3120
  };
3121
3121
  }
3122
3122
 
3123
- //#endregion
3124
- //#region src/prompts/project-name.ts
3125
- const INVALID_CHARS = [
3126
- "<",
3127
- ">",
3128
- ":",
3129
- "\"",
3130
- "|",
3131
- "?",
3132
- "*"
3133
- ];
3134
- const MAX_LENGTH = 255;
3135
- function validateDirectoryName(name) {
3136
- if (name === ".") return void 0;
3137
- if (!name) return "Project name cannot be empty";
3138
- if (name.length > MAX_LENGTH) return `Project name must be less than ${MAX_LENGTH} characters`;
3139
- if (INVALID_CHARS.some((char) => name.includes(char))) return "Project name contains invalid characters";
3140
- if (name.startsWith(".") || name.startsWith("-")) return "Project name cannot start with a dot or dash";
3141
- if (name.toLowerCase() === "node_modules") return "Project name is reserved";
3142
- return void 0;
3143
- }
3144
- async function getProjectName(initialName) {
3145
- if (initialName) {
3146
- if (initialName === ".") return initialName;
3147
- const finalDirName = path.basename(initialName);
3148
- const validationError = validateDirectoryName(finalDirName);
3149
- if (!validationError) return initialName;
3150
- }
3151
- let isValid = false;
3152
- let projectPath = "";
3153
- let defaultName = DEFAULT_CONFIG.projectName;
3154
- let counter = 1;
3155
- while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
3156
- defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
3157
- counter++;
3158
- }
3159
- while (!isValid) {
3160
- const response = await text({
3161
- message: "Enter your project name or path (relative to current directory)",
3162
- placeholder: defaultName,
3163
- initialValue: initialName,
3164
- defaultValue: defaultName,
3165
- validate: (value) => {
3166
- const nameToUse = value.trim() || defaultName;
3167
- const finalDirName = path.basename(nameToUse);
3168
- const validationError = validateDirectoryName(finalDirName);
3169
- if (validationError) return validationError;
3170
- if (nameToUse !== ".") {
3171
- const projectDir = path.resolve(process.cwd(), nameToUse);
3172
- if (!projectDir.startsWith(process.cwd())) return "Project path must be within current directory";
3173
- }
3174
- return void 0;
3175
- }
3176
- });
3177
- if (isCancel(response)) {
3178
- cancel(pc.red("Operation cancelled."));
3179
- process.exit(0);
3180
- }
3181
- projectPath = response || defaultName;
3182
- isValid = true;
3183
- }
3184
- return projectPath;
3185
- }
3186
-
3187
3123
  //#endregion
3188
3124
  //#region src/types.ts
3189
3125
  const DatabaseSchema = z.enum([
@@ -3257,6 +3193,69 @@ const APISchema = z.enum([
3257
3193
  "orpc",
3258
3194
  "none"
3259
3195
  ]).describe("API type");
3196
+ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
3197
+ const invalidChars = [
3198
+ "<",
3199
+ ">",
3200
+ ":",
3201
+ "\"",
3202
+ "|",
3203
+ "?",
3204
+ "*"
3205
+ ];
3206
+ return !invalidChars.some((char) => name.includes(char));
3207
+ }, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
3208
+
3209
+ //#endregion
3210
+ //#region src/prompts/project-name.ts
3211
+ function validateDirectoryName(name) {
3212
+ if (name === ".") return void 0;
3213
+ const result = ProjectNameSchema.safeParse(name);
3214
+ if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
3215
+ return void 0;
3216
+ }
3217
+ async function getProjectName(initialName) {
3218
+ if (initialName) {
3219
+ if (initialName === ".") return initialName;
3220
+ const finalDirName = path.basename(initialName);
3221
+ const validationError = validateDirectoryName(finalDirName);
3222
+ if (!validationError) return initialName;
3223
+ }
3224
+ let isValid = false;
3225
+ let projectPath = "";
3226
+ let defaultName = DEFAULT_CONFIG.projectName;
3227
+ let counter = 1;
3228
+ while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
3229
+ defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
3230
+ counter++;
3231
+ }
3232
+ while (!isValid) {
3233
+ const response = await text({
3234
+ message: "Enter your project name or path (relative to current directory)",
3235
+ placeholder: defaultName,
3236
+ initialValue: initialName,
3237
+ defaultValue: defaultName,
3238
+ validate: (value) => {
3239
+ const nameToUse = value.trim() || defaultName;
3240
+ const finalDirName = path.basename(nameToUse);
3241
+ const validationError = validateDirectoryName(finalDirName);
3242
+ if (validationError) return validationError;
3243
+ if (nameToUse !== ".") {
3244
+ const projectDir = path.resolve(process.cwd(), nameToUse);
3245
+ if (!projectDir.startsWith(process.cwd())) return "Project path must be within current directory";
3246
+ }
3247
+ return void 0;
3248
+ }
3249
+ });
3250
+ if (isCancel(response)) {
3251
+ cancel(pc.red("Operation cancelled."));
3252
+ process.exit(0);
3253
+ }
3254
+ projectPath = response || defaultName;
3255
+ isValid = true;
3256
+ }
3257
+ return projectPath;
3258
+ }
3260
3259
 
3261
3260
  //#endregion
3262
3261
  //#region src/utils/get-latest-cli-version.ts
@@ -3442,8 +3441,22 @@ function processAndValidateFlags(options, providedFlags, projectName) {
3442
3441
  if (options.runtime) config.runtime = options.runtime;
3443
3442
  if (options.dbSetup) config.dbSetup = options.dbSetup;
3444
3443
  if (options.packageManager) config.packageManager = options.packageManager;
3445
- if (projectName) config.projectName = projectName;
3446
- else if (options.projectDirectory) config.projectName = path.basename(path.resolve(process.cwd(), options.projectDirectory));
3444
+ if (projectName) {
3445
+ const result = ProjectNameSchema.safeParse(path.basename(projectName));
3446
+ if (!result.success) {
3447
+ consola$1.fatal(`Invalid project name: ${result.error.issues[0]?.message || "Invalid project name"}`);
3448
+ process.exit(1);
3449
+ }
3450
+ config.projectName = projectName;
3451
+ } else if (options.projectDirectory) {
3452
+ const baseName = path.basename(path.resolve(process.cwd(), options.projectDirectory));
3453
+ const result = ProjectNameSchema.safeParse(baseName);
3454
+ if (!result.success) {
3455
+ consola$1.fatal(`Invalid project name: ${result.error.issues[0]?.message || "Invalid project name"}`);
3456
+ process.exit(1);
3457
+ }
3458
+ config.projectName = baseName;
3459
+ }
3447
3460
  if (options.frontend && options.frontend.length > 0) if (options.frontend.includes("none")) {
3448
3461
  if (options.frontend.length > 1) {
3449
3462
  consola$1.fatal(`Cannot combine 'none' with other frontend options.`);
@@ -3587,9 +3600,6 @@ function getProvidedFlags(options) {
3587
3600
 
3588
3601
  //#endregion
3589
3602
  //#region src/index.ts
3590
- const exit = () => process.exit(0);
3591
- process.on("SIGINT", exit);
3592
- process.on("SIGTERM", exit);
3593
3603
  const t = trpcServer.initTRPC.create();
3594
3604
  async function handleDirectoryConflict(currentPathInput) {
3595
3605
  while (true) {
@@ -3739,17 +3749,17 @@ async function createProjectHandler(input) {
3739
3749
  const router = t.router({ init: t.procedure.meta({
3740
3750
  description: "Create a new Better-T Stack project",
3741
3751
  default: true
3742
- }).input(zod.tuple([zod.string().optional().describe("project-name"), zod.object({
3743
- yes: zod.boolean().optional().default(false).describe("Use default configuration and skip prompts"),
3752
+ }).input(zod.tuple([ProjectNameSchema.optional(), zod.object({
3753
+ yes: zod.boolean().optional().default(false).describe("Use default configuration"),
3744
3754
  database: DatabaseSchema.optional(),
3745
3755
  orm: ORMSchema.optional(),
3746
- auth: zod.boolean().optional().describe("Include authentication"),
3747
- frontend: zod.array(FrontendSchema).optional().describe("Frontend frameworks"),
3748
- addons: zod.array(AddonsSchema).optional().describe("Additional addons"),
3749
- examples: zod.array(ExamplesSchema).optional().describe("Examples to include"),
3750
- git: zod.boolean().optional().describe("Initialize git repository"),
3756
+ auth: zod.boolean().optional(),
3757
+ frontend: zod.array(FrontendSchema).optional(),
3758
+ addons: zod.array(AddonsSchema).optional(),
3759
+ examples: zod.array(ExamplesSchema).optional(),
3760
+ git: zod.boolean().optional(),
3751
3761
  packageManager: PackageManagerSchema.optional(),
3752
- install: zod.boolean().optional().describe("Install dependencies"),
3762
+ install: zod.boolean().optional(),
3753
3763
  dbSetup: DatabaseSetupSchema.optional(),
3754
3764
  backend: BackendSchema.optional(),
3755
3765
  runtime: RuntimeSchema.optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.17.1",
3
+ "version": "2.18.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",
@@ -1,7 +1,7 @@
1
1
  {{#if (eq orm "prisma")}}
2
2
  import { betterAuth } from "better-auth";
3
3
  import { prismaAdapter } from "better-auth/adapters/prisma";
4
- {{#if (includes frontend "native")}}
4
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
5
5
  import { expo } from "@better-auth/expo";
6
6
  {{/if}}
7
7
  import prisma from "../../prisma";
@@ -14,24 +14,24 @@ export const auth = betterAuth({
14
14
  {{#if (eq database "mongodb")}}provider: "mongodb"{{/if}}
15
15
  }),
16
16
  trustedOrigins: [
17
- process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
17
+ process.env.CORS_ORIGIN || "",{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
18
18
  "my-better-t-app://",{{/if}}
19
19
  ],
20
20
  emailAndPassword: {
21
21
  enabled: true,
22
22
  }
23
23
 
24
- {{~#if (includes frontend "native")}}
24
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
25
25
  ,
26
26
  plugins: [expo()]
27
- {{/if~}}
27
+ {{/if}}
28
28
  });
29
29
  {{/if}}
30
30
 
31
31
  {{#if (eq orm "drizzle")}}
32
32
  import { betterAuth } from "better-auth";
33
33
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
34
- {{#if (includes frontend "native")}}
34
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
35
35
  import { expo } from "@better-auth/expo";
36
36
  {{/if}}
37
37
  import { db } from "../db";
@@ -45,64 +45,64 @@ export const auth = betterAuth({
45
45
  schema: schema,
46
46
  }),
47
47
  trustedOrigins: [
48
- process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
48
+ process.env.CORS_ORIGIN || "",{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
49
49
  "my-better-t-app://",{{/if}}
50
50
  ],
51
51
  emailAndPassword: {
52
52
  enabled: true,
53
53
  }
54
54
 
55
- {{~#if (includes frontend "native")}}
55
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
56
56
  ,
57
57
  plugins: [expo()]
58
- {{/if~}}
58
+ {{/if}}
59
59
  });
60
60
  {{/if}}
61
61
 
62
62
  {{#if (eq orm "mongoose")}}
63
63
  import { betterAuth } from "better-auth";
64
64
  import { mongodbAdapter } from "better-auth/adapters/mongodb";
65
- {{#if (includes frontend "native")}}
65
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
66
66
  import { expo } from "@better-auth/expo";
67
67
  {{/if}}
68
68
  import { client } from "../db";
69
69
 
70
70
  export const auth = betterAuth({
71
- database: mongodbAdapter(client),
72
- trustedOrigins: [
73
- process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
74
- "my-better-t-app://",{{/if}}
75
- ],
76
- emailAndPassword: {
77
- enabled: true,
78
- }
71
+ database: mongodbAdapter(client),
72
+ trustedOrigins: [
73
+ process.env.CORS_ORIGIN || "",{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
74
+ "my-better-t-app://",{{/if}}
75
+ ],
76
+ emailAndPassword: {
77
+ enabled: true,
78
+ }
79
79
 
80
- {{~#if (includes frontend "native")}}
81
- ,
82
- plugins: [expo()]
83
- {{/if~}}
80
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
81
+ ,
82
+ plugins: [expo()]
83
+ {{/if}}
84
84
  });
85
85
  {{/if}}
86
86
 
87
87
  {{#if (eq orm "none")}}
88
88
  import { betterAuth } from "better-auth";
89
- {{#if (includes frontend "native")}}
89
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
90
90
  import { expo } from "@better-auth/expo";
91
91
  {{/if}}
92
92
 
93
93
  export const auth = betterAuth({
94
94
  database: "", // Invalid configuration
95
95
  trustedOrigins: [
96
- process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
96
+ process.env.CORS_ORIGIN || "",{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
97
97
  "my-better-t-app://",{{/if}}
98
98
  ],
99
99
  emailAndPassword: {
100
100
  enabled: true,
101
101
  }
102
102
 
103
- {{~#if (includes frontend "native")}}
103
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
104
104
  ,
105
105
  plugins: [expo()]
106
- {{/if~}}
106
+ {{/if}}
107
107
  });
108
108
  {{/if}}
@@ -3,10 +3,9 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
6
- "start": "vinxi start",
7
- "build": "vinxi build",
6
+ "build": "vite build",
8
7
  "serve": "vite preview",
9
- "dev": "vinxi dev --port=3001"
8
+ "dev": "vite dev --port=3001"
10
9
  },
11
10
  "dependencies": {
12
11
  "@radix-ui/react-checkbox": "^1.1.4",
@@ -15,11 +14,11 @@
15
14
  "@radix-ui/react-slot": "^1.1.2",
16
15
  "@tanstack/react-form": "^1.0.5",
17
16
  "@tailwindcss/vite": "^4.1.8",
18
- "@tanstack/react-query": "^5.80.5",
19
- "@tanstack/react-router": "^1.120.15",
20
- "@tanstack/react-router-with-query": "^1.120.15",
21
- "@tanstack/react-start": "^1.120.15",
22
- "@tanstack/router-plugin": "^1.120.15",
17
+ "@tanstack/react-query": "^5.80.6",
18
+ "@tanstack/react-router": "^1.121.0-alpha.27",
19
+ "@tanstack/react-router-with-query": "^1.121.0",
20
+ "@tanstack/react-start": "^1.121.0-alpha.27",
21
+ "@tanstack/router-plugin": "^1.121.0",
23
22
  "class-variance-authority": "^0.7.1",
24
23
  "clsx": "^2.1.1",
25
24
  "lucide-react": "^0.473.0",
@@ -30,17 +29,16 @@
30
29
  "tailwindcss": "^4.1.3",
31
30
  "tailwind-merge": "^2.6.0",
32
31
  "tw-animate-css": "^1.2.5",
33
- "vinxi": "^0.5.3",
34
32
  "vite-tsconfig-paths": "^5.1.4",
35
33
  "zod": "^3.25.16"
36
34
  },
37
35
  "devDependencies": {
38
- "@tanstack/react-router-devtools": "^1.120.15",
36
+ "@tanstack/react-router-devtools": "^1.121.0-alpha.27",
39
37
  "@testing-library/dom": "^10.4.0",
40
38
  "@testing-library/react": "^16.2.0",
41
39
  "@types/react": "^19.0.12",
42
40
  "@types/react-dom": "^19.0.4",
43
- "@vitejs/plugin-react": "^4.3.4",
41
+ "@vitejs/plugin-react": "^4.5.2",
44
42
  "jsdom": "^26.0.0",
45
43
  "typescript": "^5.7.2",
46
44
  "vite": "^6.3.5",
@@ -8,7 +8,7 @@ import { routeTree } from "./routeTree.gen";
8
8
  import Loader from "./components/loader";
9
9
  import "./index.css";
10
10
  {{else}}
11
- import { createRouter as createTanstackRouter } from "@tanstack/react-router";
11
+ import { createRouter as createTanStackRouter } from "@tanstack/react-router";
12
12
  import Loader from "./components/loader";
13
13
  import "./index.css";
14
14
  import { routeTree } from "./routeTree.gen";
@@ -102,7 +102,7 @@ const trpc = createTRPCOptionsProxy({
102
102
  {{/if}}
103
103
 
104
104
  export const createRouter = () => {
105
- const router = createTanstackRouter({
105
+ const router = createTanStackRouter({
106
106
  routeTree,
107
107
  scrollRestoration: true,
108
108
  defaultPreloadStaleTime: 0,
@@ -111,7 +111,7 @@ export const createRouter = () => {
111
111
  {{else if (eq api "orpc")}}
112
112
  context: { orpc, queryClient },
113
113
  {{else}}
114
- context: { },
114
+ context: {},
115
115
  {{/if}}
116
116
  defaultPendingComponent: () => <Loader />,
117
117
  defaultNotFoundComponent: () => <div>Not Found</div>,
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vite";
2
+ import tsconfigPaths from "vite-tsconfig-paths";
3
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+
6
+ export default defineConfig({
7
+ plugins: [tsconfigPaths(), tailwindcss(), tanstackStart({})],
8
+ });
@@ -1,17 +0,0 @@
1
- import { defineConfig } from "@tanstack/react-start/config";
2
- import viteTsConfigPaths from "vite-tsconfig-paths";
3
- import tailwindcss from "@tailwindcss/vite";
4
-
5
- export default defineConfig({
6
- tsr: {
7
- appDirectory: "src",
8
- },
9
- vite: {
10
- plugins: [
11
- viteTsConfigPaths({
12
- projects: ["./tsconfig.json"],
13
- }),
14
- tailwindcss(),
15
- ],
16
- },
17
- });
@@ -1,6 +0,0 @@
1
- import {
2
- createStartAPIHandler,
3
- defaultAPIFileRouteHandler,
4
- } from "@tanstack/react-start/api";
5
-
6
- export default createStartAPIHandler(defaultAPIFileRouteHandler);
@@ -1,8 +0,0 @@
1
- import { StartClient } from "@tanstack/react-start";
2
- import { hydrateRoot } from "react-dom/client";
3
-
4
- import { createRouter } from "./router";
5
-
6
- const router = createRouter();
7
-
8
- hydrateRoot(document, <StartClient router={router} />);
@@ -1,12 +0,0 @@
1
- import { getRouterManifest } from "@tanstack/react-start/router-manifest";
2
- import {
3
- createStartHandler,
4
- defaultStreamHandler,
5
- } from "@tanstack/react-start/server";
6
-
7
- import { createRouter } from "./router";
8
-
9
- export default createStartHandler({
10
- createRouter,
11
- getRouterManifest,
12
- })(defaultStreamHandler);