create-better-t-stack 2.22.9 → 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 +488 -43
- package/package.json +2 -1
- package/templates/deploy/web/nuxt/wrangler.jsonc.hbs +51 -0
- package/templates/deploy/web/react/next/open-next.config.ts +6 -0
- package/templates/deploy/web/react/next/wrangler.jsonc.hbs +22 -0
- package/templates/deploy/web/react/react-router/wrangler.jsonc.hbs +8 -0
- package/templates/deploy/web/react/tanstack-router/wrangler.jsonc.hbs +8 -0
- package/templates/deploy/web/solid/wrangler.jsonc.hbs +8 -0
- package/templates/deploy/web/svelte/wrangler.jsonc.hbs +51 -0
- package/templates/frontend/react/react-router/vite.config.ts.hbs +1 -22
- package/templates/frontend/react/tanstack-router/vite.config.ts.hbs +3 -24
- package/templates/frontend/react/tanstack-start/package.json.hbs +6 -6
- package/templates/frontend/react/web-base/_gitignore +5 -0
- package/templates/frontend/solid/_gitignore +3 -0
- package/templates/frontend/solid/package.json.hbs +2 -2
- package/templates/frontend/solid/src/main.tsx.hbs +4 -1
- package/templates/frontend/solid/vite.config.ts.hbs +18 -0
- package/templates/frontend/svelte/package.json.hbs +1 -1
- package/templates/frontend/solid/vite.config.js.hbs +0 -39
- /package/templates/frontend/svelte/{svelte.config.js → svelte.config.js.hbs} +0 -0
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.
|
|
63
|
-
"@vite-pwa/assets-generator": "^0.
|
|
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.
|
|
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:
|
|
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
|
|
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`));
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
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`));
|
|
2174
2592
|
} catch (error) {
|
|
2175
2593
|
const message = error instanceof Error ? error.message : String(error);
|
|
2176
|
-
exitWithError(`Error adding
|
|
2594
|
+
exitWithError(`Error adding deployment: ${message}`);
|
|
2177
2595
|
}
|
|
2178
2596
|
}
|
|
2179
2597
|
|
|
@@ -3609,7 +4027,7 @@ This project uses Convex as a backend. You'll need to set up Convex before runni
|
|
|
3609
4027
|
${packageManagerRunCmd} dev:setup
|
|
3610
4028
|
\`\`\`
|
|
3611
4029
|
|
|
3612
|
-
Follow the prompts to create a new Convex project and connect it to your application.` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm)}
|
|
4030
|
+
Follow the prompts to create a new Convex project and connect it to your application.` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm, options.dbSetup)}
|
|
3613
4031
|
|
|
3614
4032
|
Then, run the development server:
|
|
3615
4033
|
|
|
@@ -3764,15 +4182,16 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
|
|
|
3764
4182
|
else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
|
|
3765
4183
|
return addonsList.join("\n");
|
|
3766
4184
|
}
|
|
3767
|
-
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm) {
|
|
4185
|
+
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup) {
|
|
3768
4186
|
if (database === "none") return "";
|
|
3769
4187
|
let setup = "## Database Setup\n\n";
|
|
3770
4188
|
if (database === "sqlite") setup += `This project uses SQLite${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
|
|
3771
4189
|
|
|
3772
4190
|
1. Start the local SQLite database:
|
|
3773
|
-
|
|
4191
|
+
${dbSetup === "d1" ? "Local development for a Cloudflare D1 database will already be running as part of the `wrangler dev` command." : `\`\`\`bash
|
|
3774
4192
|
cd apps/server && ${packageManagerRunCmd} db:local
|
|
3775
4193
|
\`\`\`
|
|
4194
|
+
`}
|
|
3776
4195
|
|
|
3777
4196
|
2. Update your \`.env\` file in the \`apps/server\` directory with the appropriate connection details if needed.
|
|
3778
4197
|
`;
|
|
@@ -3860,7 +4279,7 @@ async function initializeGit(projectDir, useGit) {
|
|
|
3860
4279
|
})`git init`;
|
|
3861
4280
|
if (result.exitCode !== 0) throw new Error(`Git initialization failed: ${result.stderr}`);
|
|
3862
4281
|
await $({ cwd: projectDir })`git add -A`;
|
|
3863
|
-
await $({ cwd: projectDir })`git commit -m ${"
|
|
4282
|
+
await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
|
|
3864
4283
|
}
|
|
3865
4284
|
|
|
3866
4285
|
//#endregion
|
|
@@ -3966,7 +4385,7 @@ function getTauriInstructions(runCmd) {
|
|
|
3966
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/`;
|
|
3967
4386
|
}
|
|
3968
4387
|
function getPwaInstructions() {
|
|
3969
|
-
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA
|
|
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`;
|
|
3970
4389
|
}
|
|
3971
4390
|
function getStarlightInstructions(runCmd) {
|
|
3972
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`}`;
|
|
@@ -4110,7 +4529,7 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
4110
4529
|
if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
|
|
4111
4530
|
const scripts = serverPackageJson.scripts;
|
|
4112
4531
|
if (options.database !== "none") {
|
|
4113
|
-
if (options.database === "sqlite" && options.orm === "drizzle") scripts["db:local"] = "turso dev --db-file local.db";
|
|
4532
|
+
if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
|
|
4114
4533
|
if (options.orm === "prisma") {
|
|
4115
4534
|
scripts["db:push"] = "prisma db push --schema ./prisma/schema";
|
|
4116
4535
|
scripts["db:studio"] = "prisma studio";
|
|
@@ -4150,6 +4569,7 @@ async function createProject(options) {
|
|
|
4150
4569
|
}
|
|
4151
4570
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
4152
4571
|
await setupAddonsTemplate(projectDir, options);
|
|
4572
|
+
await setupDeploymentTemplates(projectDir, options);
|
|
4153
4573
|
await setupApi(options);
|
|
4154
4574
|
if (!isConvex) {
|
|
4155
4575
|
await setupBackendDependencies(options);
|
|
@@ -4160,6 +4580,7 @@ async function createProject(options) {
|
|
|
4160
4580
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
4161
4581
|
if (!isConvex && options.auth) await setupAuth(options);
|
|
4162
4582
|
await handleExtras(projectDir, options);
|
|
4583
|
+
await setupWebDeploy(options);
|
|
4163
4584
|
await setupEnvironmentVariables(options);
|
|
4164
4585
|
await updatePackageConfigurations(projectDir, options);
|
|
4165
4586
|
await createReadme(projectDir, options);
|
|
@@ -4251,28 +4672,50 @@ async function createProjectHandler(input) {
|
|
|
4251
4672
|
}
|
|
4252
4673
|
async function addAddonsHandler(input) {
|
|
4253
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
|
+
}
|
|
4254
4681
|
if (!input.addons || input.addons.length === 0) {
|
|
4255
|
-
const projectDir = input.projectDir || process.cwd();
|
|
4256
|
-
const detectedConfig = await detectProjectConfig(projectDir);
|
|
4257
|
-
if (!detectedConfig) {
|
|
4258
|
-
cancel(pc.red("Could not detect project configuration. Please ensure this is a valid Better-T Stack project."));
|
|
4259
|
-
process.exit(1);
|
|
4260
|
-
}
|
|
4261
4682
|
const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || []);
|
|
4262
|
-
if (addonsPrompt.length
|
|
4263
|
-
outro(pc.yellow("No addons to add or all compatible addons are already present."));
|
|
4264
|
-
return;
|
|
4265
|
-
}
|
|
4266
|
-
input.addons = addonsPrompt;
|
|
4683
|
+
if (addonsPrompt.length > 0) input.addons = addonsPrompt;
|
|
4267
4684
|
}
|
|
4268
|
-
if (!input.
|
|
4269
|
-
|
|
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."));
|
|
4270
4711
|
return;
|
|
4271
4712
|
}
|
|
4272
|
-
await
|
|
4273
|
-
|
|
4274
|
-
|
|
4713
|
+
if (input.install) await installDependencies({
|
|
4714
|
+
projectDir,
|
|
4715
|
+
packageManager
|
|
4275
4716
|
});
|
|
4717
|
+
else log.info(pc.yellow(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`));
|
|
4718
|
+
outro(pc.green("Add command completed successfully!"));
|
|
4276
4719
|
} catch (error) {
|
|
4277
4720
|
console.error(error);
|
|
4278
4721
|
process.exit(1);
|
|
@@ -4362,7 +4805,8 @@ const router = t.router({
|
|
|
4362
4805
|
dbSetup: DatabaseSetupSchema.optional(),
|
|
4363
4806
|
backend: BackendSchema.optional(),
|
|
4364
4807
|
runtime: RuntimeSchema.optional(),
|
|
4365
|
-
api: APISchema.optional()
|
|
4808
|
+
api: APISchema.optional(),
|
|
4809
|
+
webDeploy: WebDeploySchema.optional()
|
|
4366
4810
|
}).optional().default({})])).mutation(async ({ input }) => {
|
|
4367
4811
|
const [projectName, options] = input;
|
|
4368
4812
|
const combinedInput = {
|
|
@@ -4371,10 +4815,11 @@ const router = t.router({
|
|
|
4371
4815
|
};
|
|
4372
4816
|
await createProjectHandler(combinedInput);
|
|
4373
4817
|
}),
|
|
4374
|
-
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({
|
|
4375
4819
|
addons: zod.array(AddonsSchema).optional().default([]),
|
|
4820
|
+
webDeploy: WebDeploySchema.optional(),
|
|
4376
4821
|
projectDir: zod.string().optional(),
|
|
4377
|
-
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"),
|
|
4378
4823
|
packageManager: PackageManagerSchema.optional()
|
|
4379
4824
|
}).optional().default({})])).mutation(async ({ input }) => {
|
|
4380
4825
|
const [options] = input;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.
|
|
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,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 {
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
"lucide-react": "^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": "^
|
|
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": "^
|
|
42
|
-
"web-vitals": "^
|
|
41
|
+
"vite": "^7.0.2",
|
|
42
|
+
"web-vitals": "^5.0.3"
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite --port 3001",
|
|
7
|
-
"build": "vite build
|
|
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": "^
|
|
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
|
+
});
|
|
@@ -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
|
-
});
|
|
File without changes
|