create-better-t-stack 2.22.10 → 2.23.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
@@ -11,6 +11,7 @@ import { PostHog } from "posthog-node";
11
11
  import gradient from "gradient-string";
12
12
  import * as JSONC from "jsonc-parser";
13
13
  import { $, execa } from "execa";
14
+ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
14
15
  import { globby } from "globby";
15
16
  import handlebars from "handlebars";
16
17
  import os from "node:os";
@@ -44,7 +45,8 @@ const DEFAULT_CONFIG = {
44
45
  dbSetup: "none",
45
46
  backend: "hono",
46
47
  runtime: "bun",
47
- api: "trpc"
48
+ api: "trpc",
49
+ webDeploy: "none"
48
50
  };
49
51
  const dependencyVersionMap = {
50
52
  "better-auth": "^1.2.10",
@@ -59,8 +61,8 @@ const dependencyVersionMap = {
59
61
  "@prisma/client": "^6.9.0",
60
62
  prisma: "^6.9.0",
61
63
  mongoose: "^8.14.0",
62
- "vite-plugin-pwa": "^0.21.2",
63
- "@vite-pwa/assets-generator": "^0.2.6",
64
+ "vite-plugin-pwa": "^1.0.1",
65
+ "@vite-pwa/assets-generator": "^1.0.0",
64
66
  "@tauri-apps/cli": "^2.4.0",
65
67
  "@biomejs/biome": "^2.0.0",
66
68
  husky: "^9.1.7",
@@ -102,13 +104,18 @@ const dependencyVersionMap = {
102
104
  "@tanstack/react-query": "^5.80.5",
103
105
  "@tanstack/solid-query": "^5.75.0",
104
106
  "@tanstack/solid-query-devtools": "^5.75.0",
105
- wrangler: "^4.20.0"
107
+ wrangler: "^4.23.0",
108
+ "@cloudflare/vite-plugin": "^1.9.0",
109
+ "@opennextjs/cloudflare": "^1.3.0",
110
+ "nitro-cloudflare-dev": "^0.2.2",
111
+ "@sveltejs/adapter-cloudflare": "^7.0.4"
106
112
  };
107
113
  const ADDON_COMPATIBILITY = {
108
114
  pwa: [
109
115
  "tanstack-router",
110
116
  "react-router",
111
- "solid"
117
+ "solid",
118
+ "next"
112
119
  ],
113
120
  tauri: [
114
121
  "tanstack-router",
@@ -211,6 +218,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
211
218
  ];
212
219
  return !invalidChars.some((char) => name.includes(char));
213
220
  }, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
221
+ const WebDeploySchema = z.enum(["workers", "none"]).describe("Web deployment");
214
222
 
215
223
  //#endregion
216
224
  //#region src/utils/addon-compatibility.ts
@@ -318,7 +326,7 @@ async function getAddonsToAdd(frontend, existingAddons = []) {
318
326
  const response = await multiselect({
319
327
  message: "Select addons",
320
328
  options,
321
- required: true
329
+ required: false
322
330
  });
323
331
  if (isCancel(response)) {
324
332
  cancel(pc.red("Operation cancelled"));
@@ -809,7 +817,7 @@ async function getRuntimeChoice(runtime, backend) {
809
817
  }];
810
818
  if (backend === "hono") runtimeOptions.push({
811
819
  value: "workers",
812
- label: "Cloudflare Workers (beta)",
820
+ label: "Cloudflare Workers",
813
821
  hint: "Edge runtime on Cloudflare's global network"
814
822
  });
815
823
  const response = await select({
@@ -824,6 +832,79 @@ async function getRuntimeChoice(runtime, backend) {
824
832
  return response;
825
833
  }
826
834
 
835
+ //#endregion
836
+ //#region src/prompts/web-deploy.ts
837
+ const WORKERS_COMPATIBLE_FRONTENDS = [
838
+ "tanstack-router",
839
+ "react-router",
840
+ "solid",
841
+ "next",
842
+ "nuxt",
843
+ "svelte"
844
+ ];
845
+ function getDeploymentDisplay(deployment) {
846
+ if (deployment === "workers") return {
847
+ label: "Cloudflare Workers",
848
+ hint: "Deploy to Cloudflare Workers using Wrangler"
849
+ };
850
+ return {
851
+ label: deployment,
852
+ hint: `Add ${deployment} deployment`
853
+ };
854
+ }
855
+ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
856
+ if (deployment !== void 0) return deployment;
857
+ const hasCompatibleFrontend = frontend.some((f) => WORKERS_COMPATIBLE_FRONTENDS.includes(f));
858
+ if (!hasCompatibleFrontend) return "none";
859
+ const options = [{
860
+ value: "workers",
861
+ label: "Cloudflare Workers",
862
+ hint: "Deploy to Cloudflare Workers using Wrangler"
863
+ }, {
864
+ value: "none",
865
+ label: "None",
866
+ hint: "Manual setup"
867
+ }];
868
+ const response = await select({
869
+ message: "Select web deployment",
870
+ options,
871
+ initialValue: DEFAULT_CONFIG.webDeploy
872
+ });
873
+ if (isCancel(response)) {
874
+ cancel(pc.red("Operation cancelled"));
875
+ process.exit(0);
876
+ }
877
+ return response;
878
+ }
879
+ async function getDeploymentToAdd(frontend, existingDeployment) {
880
+ const options = [];
881
+ if (frontend.some((f) => WORKERS_COMPATIBLE_FRONTENDS.includes(f)) && existingDeployment !== "workers") {
882
+ const { label, hint } = getDeploymentDisplay("workers");
883
+ options.push({
884
+ value: "workers",
885
+ label,
886
+ hint
887
+ });
888
+ }
889
+ if (existingDeployment && existingDeployment !== "none") return "none";
890
+ if (options.length > 0) options.push({
891
+ value: "none",
892
+ label: "None",
893
+ hint: "Skip deployment setup"
894
+ });
895
+ if (options.length === 0) return "none";
896
+ const response = await select({
897
+ message: "Select web deployment",
898
+ options,
899
+ initialValue: DEFAULT_CONFIG.webDeploy
900
+ });
901
+ if (isCancel(response)) {
902
+ cancel(pc.red("Operation cancelled"));
903
+ process.exit(0);
904
+ }
905
+ return response;
906
+ }
907
+
827
908
  //#endregion
828
909
  //#region src/prompts/config-prompts.ts
829
910
  async function gatherConfig(flags, projectName, projectDir, relativePath) {
@@ -838,6 +919,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
838
919
  addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
839
920
  examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
840
921
  dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
922
+ webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
841
923
  git: () => getGitChoice(flags.git),
842
924
  packageManager: () => getPackageManagerChoice(flags.packageManager),
843
925
  install: () => getinstallChoice(flags.install)
@@ -853,6 +935,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
853
935
  result.auth = false;
854
936
  result.dbSetup = "none";
855
937
  result.examples = ["todo"];
938
+ result.webDeploy = "none";
856
939
  }
857
940
  if (result.backend === "none") {
858
941
  result.runtime = "none";
@@ -862,6 +945,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
862
945
  result.auth = false;
863
946
  result.dbSetup = "none";
864
947
  result.examples = [];
948
+ result.webDeploy = "none";
865
949
  }
866
950
  return {
867
951
  projectName,
@@ -879,7 +963,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
879
963
  packageManager: result.packageManager,
880
964
  install: result.install,
881
965
  dbSetup: result.dbSetup,
882
- api: result.api
966
+ api: result.api,
967
+ webDeploy: result.webDeploy
883
968
  };
884
969
  }
885
970
 
@@ -1013,6 +1098,7 @@ function displayConfig(config) {
1013
1098
  configDisplay.push(`${pc.blue("Install Dependencies:")} ${installText}`);
1014
1099
  }
1015
1100
  if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
1101
+ if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
1016
1102
  if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
1017
1103
  return configDisplay.join("\n");
1018
1104
  }
@@ -1034,6 +1120,7 @@ function generateReproducibleCommand(config) {
1034
1120
  if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
1035
1121
  else flags.push("--examples none");
1036
1122
  flags.push(`--db-setup ${config.dbSetup}`);
1123
+ flags.push(`--web-deploy ${config.webDeploy}`);
1037
1124
  flags.push(config.git ? "--git" : "--no-git");
1038
1125
  flags.push(`--package-manager ${config.packageManager}`);
1039
1126
  flags.push(config.install ? "--install" : "--no-install");
@@ -1210,6 +1297,7 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1210
1297
  if (options.runtime) config.runtime = options.runtime;
1211
1298
  if (options.dbSetup) config.dbSetup = options.dbSetup;
1212
1299
  if (options.packageManager) config.packageManager = options.packageManager;
1300
+ if (options.webDeploy) config.webDeploy = options.webDeploy;
1213
1301
  if (projectName) {
1214
1302
  const result = ProjectNameSchema.safeParse(path.basename(projectName));
1215
1303
  if (!result.success) {
@@ -1395,6 +1483,13 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1395
1483
  consola$1.fatal("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
1396
1484
  process.exit(1);
1397
1485
  }
1486
+ if (config.webDeploy === "workers" && config.frontend && config.frontend.length > 0) {
1487
+ const incompatibleFrontends = config.frontend.filter((f) => f === "tanstack-start");
1488
+ if (incompatibleFrontends.length > 0) {
1489
+ consola$1.fatal(`The following frontends are not compatible with '--web-deploy workers': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or remove '--web-deploy workers'.`);
1490
+ process.exit(1);
1491
+ }
1492
+ }
1398
1493
  return config;
1399
1494
  }
1400
1495
  function getProvidedFlags(options) {
@@ -1418,7 +1513,8 @@ async function writeBtsConfig(projectConfig) {
1418
1513
  auth: projectConfig.auth,
1419
1514
  packageManager: projectConfig.packageManager,
1420
1515
  dbSetup: projectConfig.dbSetup,
1421
- api: projectConfig.api
1516
+ api: projectConfig.api,
1517
+ webDeploy: projectConfig.webDeploy
1422
1518
  };
1423
1519
  const baseContent = {
1424
1520
  $schema: "https://better-t-stack.dev/schema.json",
@@ -1434,7 +1530,8 @@ async function writeBtsConfig(projectConfig) {
1434
1530
  auth: btsConfig.auth,
1435
1531
  packageManager: btsConfig.packageManager,
1436
1532
  dbSetup: btsConfig.dbSetup,
1437
- api: btsConfig.api
1533
+ api: btsConfig.api,
1534
+ webDeploy: btsConfig.webDeploy
1438
1535
  };
1439
1536
  let configContent = JSON.stringify(baseContent);
1440
1537
  const formatResult = JSONC.format(configContent, void 0, {
@@ -1614,6 +1711,57 @@ async function setupTauri(config) {
1614
1711
  }
1615
1712
  }
1616
1713
 
1714
+ //#endregion
1715
+ //#region src/utils/ts-morph.ts
1716
+ const tsProject = new Project({
1717
+ useInMemoryFileSystem: false,
1718
+ skipAddingFilesFromTsConfig: true,
1719
+ manipulationSettings: {
1720
+ quoteKind: QuoteKind.Single,
1721
+ indentationText: IndentationText.TwoSpaces
1722
+ }
1723
+ });
1724
+ function ensureArrayProperty(obj, name) {
1725
+ return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
1726
+ name,
1727
+ initializer: "[]"
1728
+ }).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
1729
+ }
1730
+
1731
+ //#endregion
1732
+ //#region src/helpers/setup/vite-pwa-setup.ts
1733
+ async function addPwaToViteConfig(viteConfigPath, projectName) {
1734
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
1735
+ if (!sourceFile) throw new Error("vite config not found");
1736
+ const hasImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
1737
+ if (!hasImport) sourceFile.insertImportDeclaration(0, {
1738
+ namedImports: ["VitePWA"],
1739
+ moduleSpecifier: "vite-plugin-pwa"
1740
+ });
1741
+ const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
1742
+ const expression = expr.getExpression();
1743
+ return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
1744
+ });
1745
+ if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
1746
+ const callExpr = defineCall;
1747
+ const configObject = callExpr.getArguments()[0];
1748
+ if (!configObject) throw new Error("defineConfig argument is not an object literal");
1749
+ const pluginsArray = ensureArrayProperty(configObject, "plugins");
1750
+ const alreadyPresent = pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("));
1751
+ if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
1752
+ registerType: "autoUpdate",
1753
+ manifest: {
1754
+ name: "${projectName}",
1755
+ short_name: "${projectName}",
1756
+ description: "${projectName} - PWA Application",
1757
+ theme_color: "#0c0c0c",
1758
+ },
1759
+ pwaAssets: { disabled: false, config: true },
1760
+ devOptions: { enabled: true },
1761
+ })`);
1762
+ await tsProject.save();
1763
+ }
1764
+
1617
1765
  //#endregion
1618
1766
  //#region src/helpers/setup/addons-setup.ts
1619
1767
  async function setupAddons(config, isAddCommand = false) {
@@ -1706,6 +1854,8 @@ async function setupPwa(projectDir, frontends) {
1706
1854
  };
1707
1855
  await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
1708
1856
  }
1857
+ const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
1858
+ if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
1709
1859
  }
1710
1860
 
1711
1861
  //#endregion
@@ -1726,7 +1876,8 @@ async function detectProjectConfig(projectDir) {
1726
1876
  auth: btsConfig.auth,
1727
1877
  packageManager: btsConfig.packageManager,
1728
1878
  dbSetup: btsConfig.dbSetup,
1729
- api: btsConfig.api
1879
+ api: btsConfig.api,
1880
+ webDeploy: btsConfig.webDeploy
1730
1881
  };
1731
1882
  return null;
1732
1883
  } catch (_error) {
@@ -2124,10 +2275,30 @@ async function handleExtras(projectDir, context) {
2124
2275
  if (await fs.pathExists(runtimeWorkersDir)) await processAndCopyFiles("**/*", runtimeWorkersDir, projectDir, context, false);
2125
2276
  }
2126
2277
  }
2278
+ async function setupDeploymentTemplates(projectDir, context) {
2279
+ if (context.webDeploy === "none") return;
2280
+ if (context.webDeploy === "workers") {
2281
+ const webAppDir = path.join(projectDir, "apps/web");
2282
+ if (!await fs.pathExists(webAppDir)) return;
2283
+ const frontends = context.frontend;
2284
+ const templateMap = {
2285
+ "tanstack-router": "react/tanstack-router",
2286
+ "react-router": "react/react-router",
2287
+ solid: "solid",
2288
+ next: "react/next",
2289
+ nuxt: "nuxt",
2290
+ svelte: "svelte"
2291
+ };
2292
+ for (const f of frontends) if (templateMap[f]) {
2293
+ const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/web/${templateMap[f]}`);
2294
+ if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
2295
+ }
2296
+ }
2297
+ }
2127
2298
 
2128
2299
  //#endregion
2129
2300
  //#region src/helpers/project-generation/add-addons.ts
2130
- function exitWithError(message) {
2301
+ function exitWithError$1(message) {
2131
2302
  cancel(pc.red(message));
2132
2303
  process.exit(1);
2133
2304
  }
@@ -2135,9 +2306,9 @@ async function addAddonsToProject(input) {
2135
2306
  try {
2136
2307
  const projectDir = input.projectDir || process.cwd();
2137
2308
  const isBetterTStack = await isBetterTStackProject(projectDir);
2138
- if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.");
2309
+ if (!isBetterTStack) exitWithError$1("This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.");
2139
2310
  const detectedConfig = await detectProjectConfig(projectDir);
2140
- if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.");
2311
+ if (!detectedConfig) exitWithError$1("Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.");
2141
2312
  const config = {
2142
2313
  projectName: detectedConfig.projectName || path.basename(projectDir),
2143
2314
  projectDir,
@@ -2154,11 +2325,12 @@ async function addAddonsToProject(input) {
2154
2325
  packageManager: input.packageManager || detectedConfig.packageManager || "npm",
2155
2326
  install: input.install || false,
2156
2327
  dbSetup: detectedConfig.dbSetup || "none",
2157
- api: detectedConfig.api || "none"
2328
+ api: detectedConfig.api || "none",
2329
+ webDeploy: detectedConfig.webDeploy || "none"
2158
2330
  };
2159
2331
  for (const addon of input.addons) {
2160
2332
  const { isCompatible, reason } = validateAddonCompatibility(addon, config.frontend);
2161
- if (!isCompatible) exitWithError(reason || `${addon} addon is not compatible with current frontend configuration`);
2333
+ if (!isCompatible) exitWithError$1(reason || `${addon} addon is not compatible with current frontend configuration`);
2162
2334
  }
2163
2335
  log.info(pc.green(`Adding ${input.addons.join(", ")} to ${config.frontend.join("/")}`));
2164
2336
  await setupAddonsTemplate(projectDir, config);
@@ -2170,10 +2342,256 @@ async function addAddonsToProject(input) {
2170
2342
  projectDir,
2171
2343
  packageManager: config.packageManager
2172
2344
  });
2173
- else log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
2345
+ else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
2174
2346
  } catch (error) {
2175
2347
  const message = error instanceof Error ? error.message : String(error);
2176
- exitWithError(`Error adding addons: ${message}`);
2348
+ exitWithError$1(`Error adding addons: ${message}`);
2349
+ }
2350
+ }
2351
+
2352
+ //#endregion
2353
+ //#region src/helpers/setup/workers-nuxt-setup.ts
2354
+ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
2355
+ const webAppDir = path.join(projectDir, "apps/web");
2356
+ if (!await fs.pathExists(webAppDir)) return;
2357
+ await addPackageDependency({
2358
+ devDependencies: ["nitro-cloudflare-dev", "wrangler"],
2359
+ projectDir: webAppDir
2360
+ });
2361
+ const pkgPath = path.join(webAppDir, "package.json");
2362
+ if (await fs.pathExists(pkgPath)) {
2363
+ const pkg = await fs.readJson(pkgPath);
2364
+ pkg.scripts = {
2365
+ ...pkg.scripts,
2366
+ deploy: `${packageManager} run build && wrangler deploy`,
2367
+ "cf-typegen": "wrangler types"
2368
+ };
2369
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2370
+ }
2371
+ const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
2372
+ if (!await fs.pathExists(nuxtConfigPath)) return;
2373
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
2374
+ if (!sourceFile) return;
2375
+ const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2376
+ const expression = expr.getExpression();
2377
+ return Node.isIdentifier(expression) && expression.getText() === "defineNuxtConfig";
2378
+ });
2379
+ if (!defineCall) return;
2380
+ const configObj = defineCall.getArguments()[0];
2381
+ if (!configObj) return;
2382
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2383
+ const compatProp = configObj.getProperty("compatibilityDate");
2384
+ if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) compatProp.setInitializer(`'${today}'`);
2385
+ else configObj.addPropertyAssignment({
2386
+ name: "compatibilityDate",
2387
+ initializer: `'${today}'`
2388
+ });
2389
+ const nitroInitializer = `{
2390
+ preset: "cloudflare_module",
2391
+ cloudflare: {
2392
+ deployConfig: true,
2393
+ nodeCompat: true
2394
+ }
2395
+ }`;
2396
+ const nitroProp = configObj.getProperty("nitro");
2397
+ if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) nitroProp.setInitializer(nitroInitializer);
2398
+ else configObj.addPropertyAssignment({
2399
+ name: "nitro",
2400
+ initializer: nitroInitializer
2401
+ });
2402
+ const modulesProp = configObj.getProperty("modules");
2403
+ if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
2404
+ const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
2405
+ if (arrayExpr) {
2406
+ const alreadyHas = arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev");
2407
+ if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
2408
+ }
2409
+ } else configObj.addPropertyAssignment({
2410
+ name: "modules",
2411
+ initializer: "['nitro-cloudflare-dev']"
2412
+ });
2413
+ await tsProject.save();
2414
+ }
2415
+
2416
+ //#endregion
2417
+ //#region src/helpers/setup/workers-svelte-setup.ts
2418
+ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
2419
+ const webAppDir = path.join(projectDir, "apps/web");
2420
+ if (!await fs.pathExists(webAppDir)) return;
2421
+ await addPackageDependency({
2422
+ devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
2423
+ projectDir: webAppDir
2424
+ });
2425
+ const pkgPath = path.join(webAppDir, "package.json");
2426
+ if (await fs.pathExists(pkgPath)) {
2427
+ const pkg = await fs.readJson(pkgPath);
2428
+ pkg.scripts = {
2429
+ ...pkg.scripts,
2430
+ deploy: `${packageManager} run build && wrangler deploy`,
2431
+ "cf-typegen": "wrangler types ./src/worker-configuration.d.ts"
2432
+ };
2433
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
2434
+ }
2435
+ const possibleConfigFiles = [path.join(webAppDir, "svelte.config.js"), path.join(webAppDir, "svelte.config.ts")];
2436
+ const existingConfigPath = (await Promise.all(possibleConfigFiles.map(async (p) => await fs.pathExists(p) ? p : ""))).find((p) => p);
2437
+ if (existingConfigPath) {
2438
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(existingConfigPath);
2439
+ if (!sourceFile) return;
2440
+ const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
2441
+ if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
2442
+ else {
2443
+ const alreadyHasCloudflare = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare");
2444
+ if (!alreadyHasCloudflare) sourceFile.insertImportDeclaration(0, {
2445
+ defaultImport: "adapter",
2446
+ moduleSpecifier: "@sveltejs/adapter-cloudflare"
2447
+ });
2448
+ }
2449
+ await tsProject.save();
2450
+ }
2451
+ }
2452
+
2453
+ //#endregion
2454
+ //#region src/helpers/setup/workers-vite-setup.ts
2455
+ async function setupWorkersVitePlugin(projectDir) {
2456
+ const webAppDir = path.join(projectDir, "apps/web");
2457
+ const viteConfigPath = path.join(webAppDir, "vite.config.ts");
2458
+ if (!await fs.pathExists(viteConfigPath)) throw new Error("vite.config.ts not found in web app directory");
2459
+ await addPackageDependency({
2460
+ devDependencies: ["@cloudflare/vite-plugin", "wrangler"],
2461
+ projectDir: webAppDir
2462
+ });
2463
+ const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2464
+ if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
2465
+ const hasCloudflareImport = sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
2466
+ if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
2467
+ namedImports: ["cloudflare"],
2468
+ moduleSpecifier: "@cloudflare/vite-plugin"
2469
+ });
2470
+ const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2471
+ const expression = expr.getExpression();
2472
+ return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2473
+ });
2474
+ if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2475
+ const callExpr = defineCall;
2476
+ const configObject = callExpr.getArguments()[0];
2477
+ if (!configObject) throw new Error("defineConfig argument is not an object literal");
2478
+ const pluginsArray = ensureArrayProperty(configObject, "plugins");
2479
+ const hasCloudflarePlugin = pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("));
2480
+ if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
2481
+ await tsProject.save();
2482
+ }
2483
+
2484
+ //#endregion
2485
+ //#region src/helpers/setup/web-deploy-setup.ts
2486
+ async function setupWebDeploy(config) {
2487
+ const { webDeploy, frontend, projectDir } = config;
2488
+ const { packageManager } = config;
2489
+ if (webDeploy === "none") return;
2490
+ if (webDeploy !== "workers") return;
2491
+ const isNext = frontend.includes("next");
2492
+ const isNuxt = frontend.includes("nuxt");
2493
+ const isSvelte = frontend.includes("svelte");
2494
+ const isTanstackRouter = frontend.includes("tanstack-router");
2495
+ const isReactRouter = frontend.includes("react-router");
2496
+ const isSolid = frontend.includes("solid");
2497
+ if (isNext) await setupNextWorkersDeploy(projectDir, packageManager);
2498
+ else if (isNuxt) await setupNuxtWorkersDeploy(projectDir, packageManager);
2499
+ else if (isSvelte) await setupSvelteWorkersDeploy(projectDir, packageManager);
2500
+ else if (isTanstackRouter || isReactRouter || isSolid) await setupWorkersWebDeploy(projectDir, packageManager);
2501
+ }
2502
+ async function setupWorkersWebDeploy(projectDir, pkgManager) {
2503
+ const webAppDir = path.join(projectDir, "apps/web");
2504
+ if (!await fs.pathExists(webAppDir)) return;
2505
+ const packageJsonPath = path.join(webAppDir, "package.json");
2506
+ if (await fs.pathExists(packageJsonPath)) {
2507
+ const packageJson = await fs.readJson(packageJsonPath);
2508
+ packageJson.scripts = {
2509
+ ...packageJson.scripts,
2510
+ "wrangler:dev": "wrangler dev --port=3001",
2511
+ deploy: `${pkgManager} run build && wrangler deploy`
2512
+ };
2513
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2514
+ }
2515
+ await setupWorkersVitePlugin(projectDir);
2516
+ }
2517
+ async function setupNextWorkersDeploy(projectDir, _packageManager) {
2518
+ const webAppDir = path.join(projectDir, "apps/web");
2519
+ if (!await fs.pathExists(webAppDir)) return;
2520
+ await addPackageDependency({
2521
+ dependencies: ["@opennextjs/cloudflare"],
2522
+ devDependencies: ["wrangler"],
2523
+ projectDir: webAppDir
2524
+ });
2525
+ const packageJsonPath = path.join(webAppDir, "package.json");
2526
+ if (await fs.pathExists(packageJsonPath)) {
2527
+ const pkg = await fs.readJson(packageJsonPath);
2528
+ pkg.scripts = {
2529
+ ...pkg.scripts,
2530
+ preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
2531
+ deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
2532
+ upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
2533
+ "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
2534
+ };
2535
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
2536
+ }
2537
+ }
2538
+
2539
+ //#endregion
2540
+ //#region src/helpers/project-generation/add-deployment.ts
2541
+ function exitWithError(message) {
2542
+ cancel(pc.red(message));
2543
+ process.exit(1);
2544
+ }
2545
+ async function addDeploymentToProject(input) {
2546
+ try {
2547
+ const projectDir = input.projectDir || process.cwd();
2548
+ const isBetterTStack = await isBetterTStackProject(projectDir);
2549
+ if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.");
2550
+ const detectedConfig = await detectProjectConfig(projectDir);
2551
+ if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.");
2552
+ if (detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} deployment is already configured for this project.`);
2553
+ if (input.webDeploy === "workers") {
2554
+ const compatibleFrontends = [
2555
+ "tanstack-router",
2556
+ "react-router",
2557
+ "solid",
2558
+ "next",
2559
+ "svelte"
2560
+ ];
2561
+ const hasCompatible = detectedConfig.frontend?.some((f) => compatibleFrontends.includes(f));
2562
+ if (!hasCompatible) exitWithError("Cloudflare Workers deployment requires a compatible web frontend (tanstack-router, react-router, solid, next, or svelte).");
2563
+ }
2564
+ const config = {
2565
+ projectName: detectedConfig.projectName || path.basename(projectDir),
2566
+ projectDir,
2567
+ relativePath: ".",
2568
+ database: detectedConfig.database || "none",
2569
+ orm: detectedConfig.orm || "none",
2570
+ backend: detectedConfig.backend || "none",
2571
+ runtime: detectedConfig.runtime || "none",
2572
+ frontend: detectedConfig.frontend || [],
2573
+ addons: detectedConfig.addons || [],
2574
+ examples: detectedConfig.examples || [],
2575
+ auth: detectedConfig.auth || false,
2576
+ git: false,
2577
+ packageManager: input.packageManager || detectedConfig.packageManager || "npm",
2578
+ install: input.install || false,
2579
+ dbSetup: detectedConfig.dbSetup || "none",
2580
+ api: detectedConfig.api || "none",
2581
+ webDeploy: input.webDeploy
2582
+ };
2583
+ log.info(pc.green(`Adding ${input.webDeploy} deployment to ${config.frontend.join("/")}`));
2584
+ await setupDeploymentTemplates(projectDir, config);
2585
+ await setupWebDeploy(config);
2586
+ await updateBtsConfig(projectDir, { webDeploy: input.webDeploy });
2587
+ if (config.install) await installDependencies({
2588
+ projectDir,
2589
+ packageManager: config.packageManager
2590
+ });
2591
+ else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
2592
+ } catch (error) {
2593
+ const message = error instanceof Error ? error.message : String(error);
2594
+ exitWithError(`Error adding deployment: ${message}`);
2177
2595
  }
2178
2596
  }
2179
2597
 
@@ -3861,7 +4279,7 @@ async function initializeGit(projectDir, useGit) {
3861
4279
  })`git init`;
3862
4280
  if (result.exitCode !== 0) throw new Error(`Git initialization failed: ${result.stderr}`);
3863
4281
  await $({ cwd: projectDir })`git add -A`;
3864
- await $({ cwd: projectDir })`git commit -m ${"Initial commit"}`;
4282
+ await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
3865
4283
  }
3866
4284
 
3867
4285
  //#endregion
@@ -3967,7 +4385,7 @@ function getTauriInstructions(runCmd) {
3967
4385
  return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\nSee: https://v2.tauri.app/start/prerequisites/`;
3968
4386
  }
3969
4387
  function getPwaInstructions() {
3970
- return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
4388
+ return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA \nand React Router v7.See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
3971
4389
  }
3972
4390
  function getStarlightInstructions(runCmd) {
3973
4391
  return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
@@ -4151,6 +4569,7 @@ async function createProject(options) {
4151
4569
  }
4152
4570
  if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
4153
4571
  await setupAddonsTemplate(projectDir, options);
4572
+ await setupDeploymentTemplates(projectDir, options);
4154
4573
  await setupApi(options);
4155
4574
  if (!isConvex) {
4156
4575
  await setupBackendDependencies(options);
@@ -4161,6 +4580,7 @@ async function createProject(options) {
4161
4580
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
4162
4581
  if (!isConvex && options.auth) await setupAuth(options);
4163
4582
  await handleExtras(projectDir, options);
4583
+ await setupWebDeploy(options);
4164
4584
  await setupEnvironmentVariables(options);
4165
4585
  await updatePackageConfigurations(projectDir, options);
4166
4586
  await createReadme(projectDir, options);
@@ -4252,28 +4672,50 @@ async function createProjectHandler(input) {
4252
4672
  }
4253
4673
  async function addAddonsHandler(input) {
4254
4674
  try {
4675
+ const projectDir = input.projectDir || process.cwd();
4676
+ const detectedConfig = await detectProjectConfig(projectDir);
4677
+ if (!detectedConfig) {
4678
+ cancel(pc.red("Could not detect project configuration. Please ensure this is a valid Better-T Stack project."));
4679
+ process.exit(1);
4680
+ }
4255
4681
  if (!input.addons || input.addons.length === 0) {
4256
- const projectDir = input.projectDir || process.cwd();
4257
- const detectedConfig = await detectProjectConfig(projectDir);
4258
- if (!detectedConfig) {
4259
- cancel(pc.red("Could not detect project configuration. Please ensure this is a valid Better-T Stack project."));
4260
- process.exit(1);
4261
- }
4262
4682
  const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || []);
4263
- if (addonsPrompt.length === 0) {
4264
- outro(pc.yellow("No addons to add or all compatible addons are already present."));
4265
- return;
4266
- }
4267
- input.addons = addonsPrompt;
4683
+ if (addonsPrompt.length > 0) input.addons = addonsPrompt;
4268
4684
  }
4269
- if (!input.addons || input.addons.length === 0) {
4270
- outro(pc.yellow("No addons specified to add."));
4685
+ if (!input.webDeploy) {
4686
+ const deploymentPrompt = await getDeploymentToAdd(detectedConfig.frontend || [], detectedConfig.webDeploy);
4687
+ if (deploymentPrompt !== "none") input.webDeploy = deploymentPrompt;
4688
+ }
4689
+ const packageManager = input.packageManager || detectedConfig.packageManager || "npm";
4690
+ let somethingAdded = false;
4691
+ if (input.addons && input.addons.length > 0) {
4692
+ await addAddonsToProject({
4693
+ ...input,
4694
+ install: false,
4695
+ suppressInstallMessage: true,
4696
+ addons: input.addons
4697
+ });
4698
+ somethingAdded = true;
4699
+ }
4700
+ if (input.webDeploy && input.webDeploy !== "none") {
4701
+ await addDeploymentToProject({
4702
+ ...input,
4703
+ install: false,
4704
+ suppressInstallMessage: true,
4705
+ webDeploy: input.webDeploy
4706
+ });
4707
+ somethingAdded = true;
4708
+ }
4709
+ if (!somethingAdded) {
4710
+ outro(pc.yellow("No addons or deployment configurations to add."));
4271
4711
  return;
4272
4712
  }
4273
- await addAddonsToProject({
4274
- ...input,
4275
- addons: input.addons
4713
+ if (input.install) await installDependencies({
4714
+ projectDir,
4715
+ packageManager
4276
4716
  });
4717
+ else log.info(pc.yellow(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`));
4718
+ outro(pc.green("Add command completed successfully!"));
4277
4719
  } catch (error) {
4278
4720
  console.error(error);
4279
4721
  process.exit(1);
@@ -4363,7 +4805,8 @@ const router = t.router({
4363
4805
  dbSetup: DatabaseSetupSchema.optional(),
4364
4806
  backend: BackendSchema.optional(),
4365
4807
  runtime: RuntimeSchema.optional(),
4366
- api: APISchema.optional()
4808
+ api: APISchema.optional(),
4809
+ webDeploy: WebDeploySchema.optional()
4367
4810
  }).optional().default({})])).mutation(async ({ input }) => {
4368
4811
  const [projectName, options] = input;
4369
4812
  const combinedInput = {
@@ -4372,10 +4815,11 @@ const router = t.router({
4372
4815
  };
4373
4816
  await createProjectHandler(combinedInput);
4374
4817
  }),
4375
- add: t.procedure.meta({ description: "Add addons to an existing Better-T Stack project" }).input(zod.tuple([zod.object({
4818
+ add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T Stack project" }).input(zod.tuple([zod.object({
4376
4819
  addons: zod.array(AddonsSchema).optional().default([]),
4820
+ webDeploy: WebDeploySchema.optional(),
4377
4821
  projectDir: zod.string().optional(),
4378
- install: zod.boolean().optional().default(false).describe("Install dependencies after adding addons"),
4822
+ install: zod.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
4379
4823
  packageManager: PackageManagerSchema.optional()
4380
4824
  }).optional().default({})])).mutation(async ({ input }) => {
4381
4825
  const [options] = input;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.22.10",
3
+ "version": "2.23.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",
@@ -64,6 +64,7 @@
64
64
  "picocolors": "^1.1.1",
65
65
  "posthog-node": "^5.1.1",
66
66
  "trpc-cli": "^0.9.2",
67
+ "ts-morph": "^26.0.0",
67
68
  "zod": "^3.25.67"
68
69
  },
69
70
  "devDependencies": {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * For more details on how to configure Wrangler, refer to:
3
+ * https://developers.cloudflare.com/workers/wrangler/configuration/
4
+ */
5
+ {
6
+ "$schema": "../../node_modules/wrangler/config-schema.json",
7
+ "name": "{{projectName}}",
8
+ "main": "./.output/server/index.mjs",
9
+ "compatibility_date": "2025-07-01",
10
+ "assets": {
11
+ "binding": "ASSETS",
12
+ "directory": "./.output/public/"
13
+ },
14
+ "observability": {
15
+ "enabled": true
16
+ }
17
+ /**
18
+ * Smart Placement
19
+ * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
20
+ */
21
+ // "placement": { "mode": "smart" },
22
+
23
+ /**
24
+ * Bindings
25
+ * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
26
+ * databases, object storage, AI inference, real-time communication and more.
27
+ * https://developers.cloudflare.com/workers/runtime-apis/bindings/
28
+ */
29
+
30
+ /**
31
+ * Environment Variables
32
+ * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
33
+ */
34
+ // "vars": { "MY_VARIABLE": "production_value" },
35
+ /**
36
+ * Note: Use secrets to store sensitive data.
37
+ * https://developers.cloudflare.com/workers/configuration/secrets/
38
+ */
39
+
40
+ /**
41
+ * Static Assets
42
+ * https://developers.cloudflare.com/workers/static-assets/binding/
43
+ */
44
+ // "assets": { "directory": "./public/", "binding": "ASSETS" },
45
+
46
+ /**
47
+ * Service Bindings (communicate between multiple Workers)
48
+ * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
49
+ */
50
+ // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
51
+ }
@@ -0,0 +1,6 @@
1
+ import { defineCloudflareConfig } from "@opennextjs/cloudflare/config";
2
+ // import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
3
+
4
+ export default defineCloudflareConfig({
5
+ // incrementalCache: r2IncrementalCache,
6
+ });
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "../../node_modules/wrangler/config-schema.json",
3
+ "main": ".open-next/worker.js",
4
+ "name": "{{projectName}}",
5
+ "compatibility_date": "2025-07-05",
6
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
7
+ "assets": {
8
+ "directory": ".open-next/assets",
9
+ "binding": "ASSETS"
10
+ },
11
+ // "r2_buckets": [
12
+ // // Use R2 incremental cache
13
+ // // See https://opennext.js.org/cloudflare/caching
14
+ // {
15
+ // "binding": "NEXT_INC_CACHE_R2_BUCKET",
16
+ // // Create the bucket before deploying
17
+ // // You can change the bucket name if you want
18
+ // // See https://developers.cloudflare.com/workers/wrangler/commands/#r2-bucket-create
19
+ // "bucket_name": "cache"
20
+ // }
21
+ // ]
22
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "../../node_modules/wrangler/config-schema.json",
3
+ "name": "{{projectName}}",
4
+ "compatibility_date": "2025-04-03",
5
+ "assets": {
6
+ "not_found_handling": "single-page-application"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "../../node_modules/wrangler/config-schema.json",
3
+ "name": "{{projectName}}",
4
+ "compatibility_date": "2025-04-03",
5
+ "assets": {
6
+ "not_found_handling": "single-page-application"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "../../node_modules/wrangler/config-schema.json",
3
+ "name": "{{projectName}}",
4
+ "compatibility_date": "2025-04-03",
5
+ "assets": {
6
+ "not_found_handling": "single-page-application"
7
+ }
8
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * For more details on how to configure Wrangler, refer to:
3
+ * https://developers.cloudflare.com/workers/wrangler/configuration/
4
+ */
5
+ {
6
+ "$schema": "../../node_modules/wrangler/config-schema.json",
7
+ "name": "{{projectName}}",
8
+ "main": ".svelte-kit/cloudflare/_worker.js",
9
+ "compatibility_date": "2025-07-05",
10
+ "assets": {
11
+ "binding": "ASSETS",
12
+ "directory": ".svelte-kit/cloudflare"
13
+ },
14
+ "observability": {
15
+ "enabled": true
16
+ }
17
+ /**
18
+ * Smart Placement
19
+ * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
20
+ */
21
+ // "placement": { "mode": "smart" },
22
+
23
+ /**
24
+ * Bindings
25
+ * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
26
+ * databases, object storage, AI inference, real-time communication and more.
27
+ * https://developers.cloudflare.com/workers/runtime-apis/bindings/
28
+ */
29
+
30
+ /**
31
+ * Environment Variables
32
+ * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
33
+ */
34
+ // "vars": { "MY_VARIABLE": "production_value" },
35
+ /**
36
+ * Note: Use secrets to store sensitive data.
37
+ * https://developers.cloudflare.com/workers/configuration/secrets/
38
+ */
39
+
40
+ /**
41
+ * Static Assets
42
+ * https://developers.cloudflare.com/workers/static-assets/binding/
43
+ */
44
+ // "assets": { "directory": "./public/", "binding": "ASSETS" },
45
+
46
+ /**
47
+ * Service Bindings (communicate between multiple Workers)
48
+ * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
49
+ */
50
+ // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
51
+ }
@@ -1,6 +1,3 @@
1
- {{#if (includes addons "pwa")}}
2
- import { VitePWA } from "vite-plugin-pwa";
3
- {{/if}}
4
1
  import { reactRouter } from "@react-router/dev/vite";
5
2
  import tailwindcss from "@tailwindcss/vite";
6
3
  import { defineConfig } from "vite";
@@ -11,23 +8,5 @@ export default defineConfig({
11
8
  tailwindcss(),
12
9
  reactRouter(),
13
10
  tsconfigPaths(),
14
- {{#if (includes addons "pwa")}}
15
- VitePWA({
16
- registerType: "autoUpdate",
17
- manifest: {
18
- name: "{{projectName}}",
19
- short_name: "{{projectName}}",
20
- description: "{{projectName}} - PWA Application",
21
- theme_color: "#0c0c0c",
22
- },
23
- pwaAssets: {
24
- disabled: false,
25
- config: true,
26
- },
27
- devOptions: {
28
- enabled: true,
29
- },
30
- }),
31
- {{/if}}
32
11
  ],
33
- });
12
+ });
@@ -1,8 +1,5 @@
1
- {{#if (includes addons "pwa")}}
2
- import { VitePWA } from "vite-plugin-pwa";
3
- {{/if}}
4
1
  import tailwindcss from "@tailwindcss/vite";
5
- import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
2
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
6
3
  import react from "@vitejs/plugin-react";
7
4
  import path from "node:path";
8
5
  import { defineConfig } from "vite";
@@ -10,30 +7,12 @@ import { defineConfig } from "vite";
10
7
  export default defineConfig({
11
8
  plugins: [
12
9
  tailwindcss(),
13
- TanStackRouterVite({}),
10
+ tanstackRouter({}),
14
11
  react(),
15
- {{#if (includes addons "pwa")}}
16
- VitePWA({
17
- registerType: "autoUpdate",
18
- manifest: {
19
- name: "{{projectName}}",
20
- short_name: "{{projectName}}",
21
- description: "{{projectName}} - PWA Application",
22
- theme_color: "#0c0c0c",
23
- },
24
- pwaAssets: {
25
- disabled: false,
26
- config: true,
27
- },
28
- devOptions: {
29
- enabled: true,
30
- },
31
- }),
32
- {{/if}}
33
12
  ],
34
13
  resolve: {
35
14
  alias: {
36
15
  "@": path.resolve(__dirname, "./src"),
37
16
  },
38
17
  },
39
- });
18
+ });
@@ -17,14 +17,14 @@
17
17
  "@tanstack/react-start": "^1.121.0-alpha.27",
18
18
  "@tanstack/router-plugin": "^1.121.0",
19
19
  "class-variance-authority": "^0.7.1",
20
- "clsx": "^2.1.1",
21
- "lucide-react": "^0.473.0",
20
+ "clsx": "^2.1.1",
21
+ "lucide-react": "^0.525.0",
22
22
  "next-themes": "^0.4.6",
23
23
  "react": "19.0.0",
24
24
  "react-dom": "19.0.0",
25
25
  "sonner": "^2.0.3",
26
26
  "tailwindcss": "^4.1.3",
27
- "tailwind-merge": "^2.6.0",
27
+ "tailwind-merge": "^3.3.1",
28
28
  "tw-animate-css": "^1.2.5",
29
29
  "vite-tsconfig-paths": "^5.1.4",
30
30
  "zod": "^3.25.16"
@@ -38,7 +38,7 @@
38
38
  "@vitejs/plugin-react": "^4.5.2",
39
39
  "jsdom": "^26.0.0",
40
40
  "typescript": "^5.7.2",
41
- "vite": "^6.3.5",
42
- "web-vitals": "^4.2.4"
41
+ "vite": "^7.0.2",
42
+ "web-vitals": "^5.0.3"
43
43
  }
44
- }
44
+ }
@@ -50,3 +50,8 @@ next-env.d.ts
50
50
 
51
51
  # Other
52
52
  dev-dist
53
+
54
+ .wrangler
55
+ .dev.vars*
56
+
57
+ .open-next
@@ -5,3 +5,6 @@ dist-ssr
5
5
  *.local
6
6
  .env
7
7
  .env.*
8
+
9
+ .wrangler
10
+ .dev.vars*
@@ -4,7 +4,7 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite --port 3001",
7
- "build": "vite build && tsc",
7
+ "build": "vite build",
8
8
  "serve": "vite preview",
9
9
  "test": "vitest run"
10
10
  },
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "typescript": "^5.7.2",
24
- "vite": "^6.0.11",
24
+ "vite": "^7.0.2",
25
25
  "vite-plugin-solid": "^2.11.2"
26
26
  }
27
27
  }
@@ -4,7 +4,7 @@ import { routeTree } from "./routeTree.gen";
4
4
  import "./styles.css";
5
5
  {{#if (eq api "orpc")}}
6
6
  import { QueryClientProvider } from "@tanstack/solid-query";
7
- import { queryClient } from "./utils/orpc";
7
+ import { orpc, queryClient } from "./utils/orpc";
8
8
  {{/if}}
9
9
 
10
10
  const router = createRouter({
@@ -12,6 +12,9 @@ const router = createRouter({
12
12
  defaultPreload: "intent",
13
13
  scrollRestoration: true,
14
14
  defaultPreloadStaleTime: 0,
15
+ {{#if (eq api "orpc")}}
16
+ context: { orpc, queryClient },
17
+ {{/if}}
15
18
  });
16
19
 
17
20
  declare module "@tanstack/solid-router" {
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from "vite";
2
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
3
+ import solidPlugin from "vite-plugin-solid";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+ import path from "node:path";
6
+
7
+ export default defineConfig({
8
+ plugins: [
9
+ tanstackRouter({ target: "solid", autoCodeSplitting: true }),
10
+ solidPlugin(),
11
+ tailwindcss(),
12
+ ],
13
+ resolve: {
14
+ alias: {
15
+ "@": path.resolve(__dirname, "./src"),
16
+ },
17
+ },
18
+ });
@@ -21,7 +21,7 @@
21
21
  "tailwindcss": "^4.1.4",
22
22
  "typescript": "^5.8.3",
23
23
  "@tanstack/svelte-query-devtools": "^5.74.6",
24
- "vite": "^6.3.3"
24
+ "vite": "^7.0.2"
25
25
  },
26
26
  "dependencies": {
27
27
  "@tanstack/svelte-form": "^1.7.0",
@@ -1,39 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
3
- import solidPlugin from "vite-plugin-solid";
4
- import tailwindcss from "@tailwindcss/vite";
5
- import path from "node:path";
6
- {{#if (includes addons "pwa")}}
7
- import { VitePWA } from "vite-plugin-pwa";
8
- {{/if}}
9
-
10
- export default defineConfig({
11
- plugins: [
12
- TanStackRouterVite({ target: "solid", autoCodeSplitting: true }),
13
- solidPlugin(),
14
- tailwindcss(),
15
- {{#if (includes addons "pwa")}}
16
- VitePWA({
17
- registerType: "autoUpdate",
18
- manifest: {
19
- name: "{{projectName}}",
20
- short_name: "{{projectName}}",
21
- description: "{{projectName}} - PWA Application",
22
- theme_color: "#0c0c0c",
23
- },
24
- pwaAssets: {
25
- disabled: false,
26
- config: true,
27
- },
28
- devOptions: {
29
- enabled: true,
30
- },
31
- }),
32
- {{/if}}
33
- ],
34
- resolve: {
35
- alias: {
36
- "@": path.resolve(__dirname, "./src"),
37
- },
38
- },
39
- });