create-better-t-stack 2.22.10 → 2.23.1
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 +489 -40
- 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
|
|
|
@@ -3861,13 +4279,13 @@ 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 ${"
|
|
4282
|
+
await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
|
|
3865
4283
|
}
|
|
3866
4284
|
|
|
3867
4285
|
//#endregion
|
|
3868
4286
|
//#region src/helpers/project-generation/post-installation.ts
|
|
3869
4287
|
function displayPostInstallInstructions(config) {
|
|
3870
|
-
const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup } = config;
|
|
4288
|
+
const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy } = config;
|
|
3871
4289
|
const isConvex = backend === "convex";
|
|
3872
4290
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
|
3873
4291
|
const cdCmd = `cd ${relativePath}`;
|
|
@@ -3878,6 +4296,7 @@ function displayPostInstallInstructions(config) {
|
|
|
3878
4296
|
const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
|
|
3879
4297
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
3880
4298
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
4299
|
+
const workersDeployInstructions = webDeploy === "workers" ? getWorkersDeployInstructions(runCmd) : "";
|
|
3881
4300
|
const hasWeb = frontend?.some((f) => [
|
|
3882
4301
|
"tanstack-router",
|
|
3883
4302
|
"react-router",
|
|
@@ -3919,6 +4338,7 @@ function displayPostInstallInstructions(config) {
|
|
|
3919
4338
|
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
|
|
3920
4339
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
3921
4340
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
4341
|
+
if (workersDeployInstructions) output += `\n${workersDeployInstructions.trim()}\n`;
|
|
3922
4342
|
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
3923
4343
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
3924
4344
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
@@ -3967,7 +4387,7 @@ function getTauriInstructions(runCmd) {
|
|
|
3967
4387
|
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
4388
|
}
|
|
3969
4389
|
function getPwaInstructions() {
|
|
3970
|
-
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA
|
|
4390
|
+
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
4391
|
}
|
|
3972
4392
|
function getStarlightInstructions(runCmd) {
|
|
3973
4393
|
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`}`;
|
|
@@ -3978,6 +4398,9 @@ function getNoOrmWarning() {
|
|
|
3978
4398
|
function getBunWebNativeWarning() {
|
|
3979
4399
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`;
|
|
3980
4400
|
}
|
|
4401
|
+
function getWorkersDeployInstructions(runCmd) {
|
|
4402
|
+
return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd || "bun run"} deploy`}`;
|
|
4403
|
+
}
|
|
3981
4404
|
|
|
3982
4405
|
//#endregion
|
|
3983
4406
|
//#region src/helpers/project-generation/project-config.ts
|
|
@@ -4151,6 +4574,7 @@ async function createProject(options) {
|
|
|
4151
4574
|
}
|
|
4152
4575
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
4153
4576
|
await setupAddonsTemplate(projectDir, options);
|
|
4577
|
+
await setupDeploymentTemplates(projectDir, options);
|
|
4154
4578
|
await setupApi(options);
|
|
4155
4579
|
if (!isConvex) {
|
|
4156
4580
|
await setupBackendDependencies(options);
|
|
@@ -4161,6 +4585,7 @@ async function createProject(options) {
|
|
|
4161
4585
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
4162
4586
|
if (!isConvex && options.auth) await setupAuth(options);
|
|
4163
4587
|
await handleExtras(projectDir, options);
|
|
4588
|
+
await setupWebDeploy(options);
|
|
4164
4589
|
await setupEnvironmentVariables(options);
|
|
4165
4590
|
await updatePackageConfigurations(projectDir, options);
|
|
4166
4591
|
await createReadme(projectDir, options);
|
|
@@ -4252,28 +4677,50 @@ async function createProjectHandler(input) {
|
|
|
4252
4677
|
}
|
|
4253
4678
|
async function addAddonsHandler(input) {
|
|
4254
4679
|
try {
|
|
4680
|
+
const projectDir = input.projectDir || process.cwd();
|
|
4681
|
+
const detectedConfig = await detectProjectConfig(projectDir);
|
|
4682
|
+
if (!detectedConfig) {
|
|
4683
|
+
cancel(pc.red("Could not detect project configuration. Please ensure this is a valid Better-T Stack project."));
|
|
4684
|
+
process.exit(1);
|
|
4685
|
+
}
|
|
4255
4686
|
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
4687
|
const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || []);
|
|
4263
|
-
if (addonsPrompt.length
|
|
4264
|
-
outro(pc.yellow("No addons to add or all compatible addons are already present."));
|
|
4265
|
-
return;
|
|
4266
|
-
}
|
|
4267
|
-
input.addons = addonsPrompt;
|
|
4688
|
+
if (addonsPrompt.length > 0) input.addons = addonsPrompt;
|
|
4268
4689
|
}
|
|
4269
|
-
if (!input.
|
|
4270
|
-
|
|
4690
|
+
if (!input.webDeploy) {
|
|
4691
|
+
const deploymentPrompt = await getDeploymentToAdd(detectedConfig.frontend || [], detectedConfig.webDeploy);
|
|
4692
|
+
if (deploymentPrompt !== "none") input.webDeploy = deploymentPrompt;
|
|
4693
|
+
}
|
|
4694
|
+
const packageManager = input.packageManager || detectedConfig.packageManager || "npm";
|
|
4695
|
+
let somethingAdded = false;
|
|
4696
|
+
if (input.addons && input.addons.length > 0) {
|
|
4697
|
+
await addAddonsToProject({
|
|
4698
|
+
...input,
|
|
4699
|
+
install: false,
|
|
4700
|
+
suppressInstallMessage: true,
|
|
4701
|
+
addons: input.addons
|
|
4702
|
+
});
|
|
4703
|
+
somethingAdded = true;
|
|
4704
|
+
}
|
|
4705
|
+
if (input.webDeploy && input.webDeploy !== "none") {
|
|
4706
|
+
await addDeploymentToProject({
|
|
4707
|
+
...input,
|
|
4708
|
+
install: false,
|
|
4709
|
+
suppressInstallMessage: true,
|
|
4710
|
+
webDeploy: input.webDeploy
|
|
4711
|
+
});
|
|
4712
|
+
somethingAdded = true;
|
|
4713
|
+
}
|
|
4714
|
+
if (!somethingAdded) {
|
|
4715
|
+
outro(pc.yellow("No addons or deployment configurations to add."));
|
|
4271
4716
|
return;
|
|
4272
4717
|
}
|
|
4273
|
-
await
|
|
4274
|
-
|
|
4275
|
-
|
|
4718
|
+
if (input.install) await installDependencies({
|
|
4719
|
+
projectDir,
|
|
4720
|
+
packageManager
|
|
4276
4721
|
});
|
|
4722
|
+
else log.info(pc.yellow(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`));
|
|
4723
|
+
outro(pc.green("Add command completed successfully!"));
|
|
4277
4724
|
} catch (error) {
|
|
4278
4725
|
console.error(error);
|
|
4279
4726
|
process.exit(1);
|
|
@@ -4363,7 +4810,8 @@ const router = t.router({
|
|
|
4363
4810
|
dbSetup: DatabaseSetupSchema.optional(),
|
|
4364
4811
|
backend: BackendSchema.optional(),
|
|
4365
4812
|
runtime: RuntimeSchema.optional(),
|
|
4366
|
-
api: APISchema.optional()
|
|
4813
|
+
api: APISchema.optional(),
|
|
4814
|
+
webDeploy: WebDeploySchema.optional()
|
|
4367
4815
|
}).optional().default({})])).mutation(async ({ input }) => {
|
|
4368
4816
|
const [projectName, options] = input;
|
|
4369
4817
|
const combinedInput = {
|
|
@@ -4372,10 +4820,11 @@ const router = t.router({
|
|
|
4372
4820
|
};
|
|
4373
4821
|
await createProjectHandler(combinedInput);
|
|
4374
4822
|
}),
|
|
4375
|
-
add: t.procedure.meta({ description: "Add addons to an existing Better-T Stack project" }).input(zod.tuple([zod.object({
|
|
4823
|
+
add: t.procedure.meta({ description: "Add addons or deployment configurations to an existing Better-T Stack project" }).input(zod.tuple([zod.object({
|
|
4376
4824
|
addons: zod.array(AddonsSchema).optional().default([]),
|
|
4825
|
+
webDeploy: WebDeploySchema.optional(),
|
|
4377
4826
|
projectDir: zod.string().optional(),
|
|
4378
|
-
install: zod.boolean().optional().default(false).describe("Install dependencies after adding addons"),
|
|
4827
|
+
install: zod.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
4379
4828
|
packageManager: PackageManagerSchema.optional()
|
|
4380
4829
|
}).optional().default({})])).mutation(async ({ input }) => {
|
|
4381
4830
|
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.1",
|
|
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
|