create-better-t-stack 2.48.2 → 2.49.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/cli.d.ts +1 -2
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +228 -101
- package/dist/index.js +1 -1
- package/dist/{src-BwIapwZk.js → src-DXyLKhXK.js} +167 -262
- package/package.json +9 -8
- package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +3 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
3
|
+
import { createRouterClient, os } from "@orpc/server";
|
|
3
4
|
import pc from "picocolors";
|
|
4
|
-
import { createCli
|
|
5
|
-
import z from "zod";
|
|
5
|
+
import { createCli } from "trpc-cli";
|
|
6
|
+
import z$1, { z } from "zod";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import consola, { consola as consola$1 } from "consola";
|
|
8
9
|
import fs from "fs-extra";
|
|
@@ -14,7 +15,7 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
|
|
|
14
15
|
import { glob } from "tinyglobby";
|
|
15
16
|
import handlebars from "handlebars";
|
|
16
17
|
import { Biome } from "@biomejs/js-api/nodejs";
|
|
17
|
-
import os from "node:os";
|
|
18
|
+
import os$1 from "node:os";
|
|
18
19
|
|
|
19
20
|
//#region src/utils/get-package-manager.ts
|
|
20
21
|
const getUserPkgManager = () => {
|
|
@@ -263,7 +264,7 @@ const AuthSchema = z.enum([
|
|
|
263
264
|
]).describe("Authentication provider");
|
|
264
265
|
const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
|
|
265
266
|
const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
|
|
266
|
-
|
|
267
|
+
return ![
|
|
267
268
|
"<",
|
|
268
269
|
">",
|
|
269
270
|
":",
|
|
@@ -271,8 +272,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
|
|
|
271
272
|
"|",
|
|
272
273
|
"?",
|
|
273
274
|
"*"
|
|
274
|
-
];
|
|
275
|
-
return !invalidChars.some((char) => name.includes(char));
|
|
275
|
+
].some((char) => name.includes(char));
|
|
276
276
|
}, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
|
|
277
277
|
const WebDeploySchema = z.enum([
|
|
278
278
|
"wrangler",
|
|
@@ -360,12 +360,11 @@ function validateApiFrontendCompatibility(api, frontends = []) {
|
|
|
360
360
|
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
361
361
|
if (backend === "convex" && frontend === "solid") return false;
|
|
362
362
|
if (auth === "clerk" && backend === "convex") {
|
|
363
|
-
|
|
363
|
+
if ([
|
|
364
364
|
"nuxt",
|
|
365
365
|
"svelte",
|
|
366
366
|
"solid"
|
|
367
|
-
];
|
|
368
|
-
if (incompatibleFrontends.includes(frontend)) return false;
|
|
367
|
+
].includes(frontend)) return false;
|
|
369
368
|
}
|
|
370
369
|
return true;
|
|
371
370
|
}
|
|
@@ -385,8 +384,7 @@ function isExampleTodoAllowed(backend, database) {
|
|
|
385
384
|
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
386
385
|
}
|
|
387
386
|
function isExampleAIAllowed(_backend, frontends = []) {
|
|
388
|
-
|
|
389
|
-
if (includesSolid) return false;
|
|
387
|
+
if (frontends.includes("solid")) return false;
|
|
390
388
|
return true;
|
|
391
389
|
}
|
|
392
390
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
@@ -398,8 +396,7 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
|
398
396
|
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
399
397
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
400
398
|
if (compatibleFrontends.length > 0) {
|
|
401
|
-
|
|
402
|
-
if (!hasCompatibleFrontend) {
|
|
399
|
+
if (!frontend.some((f) => compatibleFrontends.includes(f))) {
|
|
403
400
|
const frontendList = compatibleFrontends.join(", ");
|
|
404
401
|
return {
|
|
405
402
|
isCompatible: false,
|
|
@@ -912,7 +909,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
912
909
|
if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
913
910
|
const result = [];
|
|
914
911
|
if (frontendTypes.includes("web")) {
|
|
915
|
-
const
|
|
912
|
+
const webOptions = [
|
|
916
913
|
{
|
|
917
914
|
value: "tanstack-router",
|
|
918
915
|
label: "TanStack Router",
|
|
@@ -948,8 +945,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
948
945
|
label: "TanStack Start",
|
|
949
946
|
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
950
947
|
}
|
|
951
|
-
];
|
|
952
|
-
const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
948
|
+
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
953
949
|
const webFramework = await select({
|
|
954
950
|
message: "Choose web",
|
|
955
951
|
options: webOptions,
|
|
@@ -1069,20 +1065,18 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1069
1065
|
//#region src/prompts/payments.ts
|
|
1070
1066
|
async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
1071
1067
|
if (payments !== void 0) return payments;
|
|
1072
|
-
|
|
1073
|
-
if (!isPolarCompatible) return "none";
|
|
1074
|
-
const options = [{
|
|
1075
|
-
value: "polar",
|
|
1076
|
-
label: "Polar",
|
|
1077
|
-
hint: "Turn your software into a business. 6 lines of code."
|
|
1078
|
-
}, {
|
|
1079
|
-
value: "none",
|
|
1080
|
-
label: "None",
|
|
1081
|
-
hint: "No payments integration"
|
|
1082
|
-
}];
|
|
1068
|
+
if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
|
|
1083
1069
|
const response = await select({
|
|
1084
1070
|
message: "Select payments provider",
|
|
1085
|
-
options
|
|
1071
|
+
options: [{
|
|
1072
|
+
value: "polar",
|
|
1073
|
+
label: "Polar",
|
|
1074
|
+
hint: "Turn your software into a business. 6 lines of code."
|
|
1075
|
+
}, {
|
|
1076
|
+
value: "none",
|
|
1077
|
+
label: "None",
|
|
1078
|
+
hint: "No payments integration"
|
|
1079
|
+
}],
|
|
1086
1080
|
initialValue: DEFAULT_CONFIG.payments
|
|
1087
1081
|
});
|
|
1088
1082
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
@@ -1211,12 +1205,11 @@ function getDeploymentDisplay(deployment) {
|
|
|
1211
1205
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1212
1206
|
if (deployment !== void 0) return deployment;
|
|
1213
1207
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1214
|
-
const
|
|
1208
|
+
const options = [
|
|
1215
1209
|
"wrangler",
|
|
1216
1210
|
"alchemy",
|
|
1217
1211
|
"none"
|
|
1218
|
-
]
|
|
1219
|
-
const options = availableDeployments.map((deploy) => {
|
|
1212
|
+
].map((deploy) => {
|
|
1220
1213
|
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1221
1214
|
return {
|
|
1222
1215
|
value: deploy,
|
|
@@ -1322,14 +1315,12 @@ function validateDirectoryName(name) {
|
|
|
1322
1315
|
if (name === ".") return void 0;
|
|
1323
1316
|
const result = ProjectNameSchema.safeParse(name);
|
|
1324
1317
|
if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
|
|
1325
|
-
return void 0;
|
|
1326
1318
|
}
|
|
1327
1319
|
async function getProjectName(initialName) {
|
|
1328
1320
|
if (initialName) {
|
|
1329
1321
|
if (initialName === ".") return initialName;
|
|
1330
1322
|
const finalDirName = path.basename(initialName);
|
|
1331
|
-
|
|
1332
|
-
if (!validationError) {
|
|
1323
|
+
if (!validateDirectoryName(finalDirName)) {
|
|
1333
1324
|
const projectDir = path.resolve(process.cwd(), initialName);
|
|
1334
1325
|
if (isPathWithinCwd(projectDir)) return initialName;
|
|
1335
1326
|
consola.error(pc.red("Project path must be within current directory"));
|
|
@@ -1358,7 +1349,6 @@ async function getProjectName(initialName) {
|
|
|
1358
1349
|
const projectDir = path.resolve(process.cwd(), nameToUse);
|
|
1359
1350
|
if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
|
|
1360
1351
|
}
|
|
1361
|
-
return void 0;
|
|
1362
1352
|
}
|
|
1363
1353
|
});
|
|
1364
1354
|
if (isCancel(response)) return exitCancelled("Operation cancelled.");
|
|
@@ -1372,8 +1362,7 @@ async function getProjectName(initialName) {
|
|
|
1372
1362
|
//#region src/utils/get-latest-cli-version.ts
|
|
1373
1363
|
const getLatestCLIVersion = () => {
|
|
1374
1364
|
const packageJsonPath = path.join(PKG_ROOT, "package.json");
|
|
1375
|
-
|
|
1376
|
-
return packageJsonContent.version ?? "1.0.0";
|
|
1365
|
+
return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
|
|
1377
1366
|
};
|
|
1378
1367
|
|
|
1379
1368
|
//#endregion
|
|
@@ -1398,8 +1387,7 @@ const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
|
|
|
1398
1387
|
const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
1399
1388
|
function generateSessionId() {
|
|
1400
1389
|
const rand = Math.random().toString(36).slice(2);
|
|
1401
|
-
|
|
1402
|
-
return `cli_${now}${rand}`;
|
|
1390
|
+
return `cli_${Date.now().toString(36)}${rand}`;
|
|
1403
1391
|
}
|
|
1404
1392
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
1405
1393
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
@@ -1506,9 +1494,7 @@ function generateReproducibleCommand(config) {
|
|
|
1506
1494
|
async function handleDirectoryConflict(currentPathInput, silent = false) {
|
|
1507
1495
|
while (true) {
|
|
1508
1496
|
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
|
1509
|
-
|
|
1510
|
-
const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
|
|
1511
|
-
if (!dirIsNotEmpty) return {
|
|
1497
|
+
if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
|
|
1512
1498
|
finalPathInput: currentPathInput,
|
|
1513
1499
|
shouldClearDirectory: false
|
|
1514
1500
|
};
|
|
@@ -1622,14 +1608,12 @@ const renderTitle = () => {
|
|
|
1622
1608
|
const terminalWidth = process.stdout.columns || 80;
|
|
1623
1609
|
const titleLines = TITLE_TEXT.split("\n");
|
|
1624
1610
|
const titleWidth = Math.max(...titleLines.map((line) => line.length));
|
|
1625
|
-
if (terminalWidth < titleWidth)
|
|
1626
|
-
const simplifiedTitle = `
|
|
1611
|
+
if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1627
1612
|
╔══════════════════╗
|
|
1628
1613
|
║ Better T Stack ║
|
|
1629
1614
|
╚══════════════════╝
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
} else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1615
|
+
`));
|
|
1616
|
+
else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1633
1617
|
};
|
|
1634
1618
|
|
|
1635
1619
|
//#endregion
|
|
@@ -1755,8 +1739,7 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
1755
1739
|
"tanstack-start",
|
|
1756
1740
|
"next"
|
|
1757
1741
|
];
|
|
1758
|
-
|
|
1759
|
-
if (!hasSupportedFrontend) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
|
|
1742
|
+
if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
|
|
1760
1743
|
}
|
|
1761
1744
|
}
|
|
1762
1745
|
function validateBackendNoneConstraints(config, providedFlags) {
|
|
@@ -1987,8 +1970,7 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1987
1970
|
try {
|
|
1988
1971
|
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
|
1989
1972
|
if (!await fs.pathExists(configPath)) return;
|
|
1990
|
-
|
|
1991
|
-
let modifiedContent = configContent;
|
|
1973
|
+
let modifiedContent = await fs.readFile(configPath, "utf-8");
|
|
1992
1974
|
for (const [key, value] of Object.entries(updates)) {
|
|
1993
1975
|
const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
|
|
1994
1976
|
tabSize: 2,
|
|
@@ -2078,8 +2060,7 @@ async function setupFumadocs(config) {
|
|
|
2078
2060
|
initialValue: "next-mdx"
|
|
2079
2061
|
});
|
|
2080
2062
|
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2081
|
-
const
|
|
2082
|
-
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2063
|
+
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2083
2064
|
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2084
2065
|
await execa(fumadocsInitCommand, {
|
|
2085
2066
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2112,27 +2093,26 @@ async function setupRuler(config) {
|
|
|
2112
2093
|
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2113
2094
|
return;
|
|
2114
2095
|
}
|
|
2115
|
-
const EDITORS$1 = {
|
|
2116
|
-
amp: { label: "AMP" },
|
|
2117
|
-
copilot: { label: "GitHub Copilot" },
|
|
2118
|
-
claude: { label: "Claude Code" },
|
|
2119
|
-
codex: { label: "OpenAI Codex CLI" },
|
|
2120
|
-
cursor: { label: "Cursor" },
|
|
2121
|
-
windsurf: { label: "Windsurf" },
|
|
2122
|
-
cline: { label: "Cline" },
|
|
2123
|
-
aider: { label: "Aider" },
|
|
2124
|
-
firebase: { label: "Firebase Studio" },
|
|
2125
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2126
|
-
junie: { label: "Junie" },
|
|
2127
|
-
kilocode: { label: "Kilo Code" },
|
|
2128
|
-
opencode: { label: "OpenCode" },
|
|
2129
|
-
crush: { label: "Crush" },
|
|
2130
|
-
zed: { label: "Zed" },
|
|
2131
|
-
qwen: { label: "Qwen" }
|
|
2132
|
-
};
|
|
2133
2096
|
const selectedEditors = await autocompleteMultiselect({
|
|
2134
2097
|
message: "Select AI assistants for Ruler",
|
|
2135
|
-
options: Object.entries(
|
|
2098
|
+
options: Object.entries({
|
|
2099
|
+
amp: { label: "AMP" },
|
|
2100
|
+
copilot: { label: "GitHub Copilot" },
|
|
2101
|
+
claude: { label: "Claude Code" },
|
|
2102
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
2103
|
+
cursor: { label: "Cursor" },
|
|
2104
|
+
windsurf: { label: "Windsurf" },
|
|
2105
|
+
cline: { label: "Cline" },
|
|
2106
|
+
aider: { label: "Aider" },
|
|
2107
|
+
firebase: { label: "Firebase Studio" },
|
|
2108
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2109
|
+
junie: { label: "Junie" },
|
|
2110
|
+
kilocode: { label: "Kilo Code" },
|
|
2111
|
+
opencode: { label: "OpenCode" },
|
|
2112
|
+
crush: { label: "Crush" },
|
|
2113
|
+
zed: { label: "Zed" },
|
|
2114
|
+
qwen: { label: "Qwen" }
|
|
2115
|
+
}).map(([key, v]) => ({
|
|
2136
2116
|
value: key,
|
|
2137
2117
|
label: v.label
|
|
2138
2118
|
})),
|
|
@@ -2145,8 +2125,7 @@ async function setupRuler(config) {
|
|
|
2145
2125
|
return;
|
|
2146
2126
|
}
|
|
2147
2127
|
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2148
|
-
|
|
2149
|
-
let updatedConfig = currentConfig;
|
|
2128
|
+
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
2150
2129
|
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2151
2130
|
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2152
2131
|
await fs.writeFile(configFile, updatedConfig);
|
|
@@ -2189,7 +2168,7 @@ async function setupStarlight(config) {
|
|
|
2189
2168
|
const s = spinner();
|
|
2190
2169
|
try {
|
|
2191
2170
|
s.start("Setting up Starlight docs...");
|
|
2192
|
-
const
|
|
2171
|
+
const commandWithArgs = `create-astro@latest ${[
|
|
2193
2172
|
"docs",
|
|
2194
2173
|
"--template",
|
|
2195
2174
|
"starlight",
|
|
@@ -2198,9 +2177,7 @@ async function setupStarlight(config) {
|
|
|
2198
2177
|
"tailwind",
|
|
2199
2178
|
"--no-git",
|
|
2200
2179
|
"--skip-houston"
|
|
2201
|
-
]
|
|
2202
|
-
const starlightArgsString = starlightArgs.join(" ");
|
|
2203
|
-
const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
|
|
2180
|
+
].join(" ")}`;
|
|
2204
2181
|
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2205
2182
|
await execa(starlightInitCommand, {
|
|
2206
2183
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2246,7 +2223,7 @@ async function setupTauri(config) {
|
|
|
2246
2223
|
const hasNext = frontend.includes("next");
|
|
2247
2224
|
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2248
2225
|
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2249
|
-
const
|
|
2226
|
+
const commandWithArgs = `@tauri-apps/cli@latest ${[
|
|
2250
2227
|
"init",
|
|
2251
2228
|
`--app-name=${path.basename(projectDir)}`,
|
|
2252
2229
|
`--window-title=${path.basename(projectDir)}`,
|
|
@@ -2254,9 +2231,7 @@ async function setupTauri(config) {
|
|
|
2254
2231
|
`--dev-url=${devUrl}`,
|
|
2255
2232
|
`--before-dev-command="${packageManager} run dev"`,
|
|
2256
2233
|
`--before-build-command="${packageManager} run build"`
|
|
2257
|
-
]
|
|
2258
|
-
const tauriArgsString = tauriArgs.join(" ");
|
|
2259
|
-
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
2234
|
+
].join(" ")}`;
|
|
2260
2235
|
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2261
2236
|
await execa(tauriInitCommand, {
|
|
2262
2237
|
cwd: clientPackageDir,
|
|
@@ -2330,8 +2305,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2330
2305
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2331
2306
|
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2332
2307
|
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2333
|
-
const
|
|
2334
|
-
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
|
2308
|
+
const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
|
|
2335
2309
|
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2336
2310
|
await execa(ultraciteInitCommand, {
|
|
2337
2311
|
cwd: projectDir,
|
|
@@ -2371,8 +2345,7 @@ function ensureArrayProperty(obj, name) {
|
|
|
2371
2345
|
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2372
2346
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2373
2347
|
if (!sourceFile) throw new Error("vite config not found");
|
|
2374
|
-
|
|
2375
|
-
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2348
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
|
|
2376
2349
|
namedImports: ["VitePWA"],
|
|
2377
2350
|
moduleSpecifier: "vite-plugin-pwa"
|
|
2378
2351
|
});
|
|
@@ -2381,12 +2354,10 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
|
2381
2354
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2382
2355
|
});
|
|
2383
2356
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2384
|
-
const
|
|
2385
|
-
const configObject = callExpr.getArguments()[0];
|
|
2357
|
+
const configObject = defineCall.getArguments()[0];
|
|
2386
2358
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2387
2359
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2388
|
-
|
|
2389
|
-
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2360
|
+
if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
|
|
2390
2361
|
registerType: "autoUpdate",
|
|
2391
2362
|
manifest: {
|
|
2392
2363
|
name: "${projectName}",
|
|
@@ -2487,12 +2458,11 @@ async function setupHusky(projectDir, linter) {
|
|
|
2487
2458
|
}
|
|
2488
2459
|
}
|
|
2489
2460
|
async function setupPwa(projectDir, frontends) {
|
|
2490
|
-
|
|
2461
|
+
if (!frontends.some((f) => [
|
|
2491
2462
|
"react-router",
|
|
2492
2463
|
"tanstack-router",
|
|
2493
2464
|
"solid"
|
|
2494
|
-
].includes(f));
|
|
2495
|
-
if (!isCompatibleFrontend) return;
|
|
2465
|
+
].includes(f))) return;
|
|
2496
2466
|
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2497
2467
|
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2498
2468
|
await addPackageDependency({
|
|
@@ -2592,8 +2562,7 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
2592
2562
|
function initializeBiome() {
|
|
2593
2563
|
try {
|
|
2594
2564
|
const biome = new Biome();
|
|
2595
|
-
const
|
|
2596
|
-
const projectKey = result.projectKey;
|
|
2565
|
+
const projectKey = biome.openProject("./").projectKey;
|
|
2597
2566
|
biome.applyConfiguration(projectKey, {
|
|
2598
2567
|
formatter: {
|
|
2599
2568
|
enabled: true,
|
|
@@ -2615,27 +2584,25 @@ function initializeBiome() {
|
|
|
2615
2584
|
}
|
|
2616
2585
|
function isSupportedFile(filePath) {
|
|
2617
2586
|
const ext = path.extname(filePath).toLowerCase();
|
|
2618
|
-
|
|
2587
|
+
return [
|
|
2619
2588
|
".js",
|
|
2620
2589
|
".jsx",
|
|
2621
2590
|
".ts",
|
|
2622
2591
|
".tsx",
|
|
2623
2592
|
".json",
|
|
2624
2593
|
".jsonc"
|
|
2625
|
-
];
|
|
2626
|
-
return supportedExtensions.includes(ext);
|
|
2594
|
+
].includes(ext);
|
|
2627
2595
|
}
|
|
2628
2596
|
function shouldSkipFile(filePath) {
|
|
2629
2597
|
const basename = path.basename(filePath);
|
|
2630
|
-
|
|
2598
|
+
return [
|
|
2631
2599
|
".hbs",
|
|
2632
2600
|
"package-lock.json",
|
|
2633
2601
|
"yarn.lock",
|
|
2634
2602
|
"pnpm-lock.yaml",
|
|
2635
2603
|
"bun.lock",
|
|
2636
2604
|
".d.ts"
|
|
2637
|
-
];
|
|
2638
|
-
return skipPatterns.some((pattern) => basename.includes(pattern));
|
|
2605
|
+
].some((pattern) => basename.includes(pattern));
|
|
2639
2606
|
}
|
|
2640
2607
|
function formatFileWithBiome(filePath, content) {
|
|
2641
2608
|
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
|
|
@@ -2672,8 +2639,7 @@ async function processTemplate(srcPath, destPath, context) {
|
|
|
2672
2639
|
let content;
|
|
2673
2640
|
if (srcPath.endsWith(".hbs")) {
|
|
2674
2641
|
const templateContent = await fs.readFile(srcPath, "utf-8");
|
|
2675
|
-
|
|
2676
|
-
content = template(context);
|
|
2642
|
+
content = handlebars.compile(templateContent)(context);
|
|
2677
2643
|
} else content = await fs.readFile(srcPath, "utf-8");
|
|
2678
2644
|
try {
|
|
2679
2645
|
const formattedContent = await formatFileWithBiome(destPath, content);
|
|
@@ -3175,8 +3141,7 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3175
3141
|
async function addAddonsToProject(input) {
|
|
3176
3142
|
try {
|
|
3177
3143
|
const projectDir = input.projectDir || process.cwd();
|
|
3178
|
-
|
|
3179
|
-
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.");
|
|
3144
|
+
if (!await isBetterTStackProject(projectDir)) 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.");
|
|
3180
3145
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3181
3146
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3182
3147
|
const config = {
|
|
@@ -3323,15 +3288,13 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3323
3288
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3324
3289
|
}
|
|
3325
3290
|
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
|
3326
|
-
|
|
3291
|
+
await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
3327
3292
|
|
|
3328
3293
|
export default defineCloudflareConfig({});
|
|
3329
|
-
|
|
3330
|
-
await fs.writeFile(openNextConfigPath, openNextConfigContent);
|
|
3294
|
+
`);
|
|
3331
3295
|
const gitignorePath = path.join(webAppDir, ".gitignore");
|
|
3332
3296
|
if (await fs.pathExists(gitignorePath)) {
|
|
3333
|
-
|
|
3334
|
-
if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3297
|
+
if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3335
3298
|
} else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
|
3336
3299
|
}
|
|
3337
3300
|
|
|
@@ -3367,8 +3330,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3367
3330
|
quoteKind: QuoteKind.Double
|
|
3368
3331
|
} });
|
|
3369
3332
|
project.addSourceFileAtPath(nuxtConfigPath);
|
|
3370
|
-
const
|
|
3371
|
-
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3333
|
+
const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
|
|
3372
3334
|
if (!exportAssignment) return;
|
|
3373
3335
|
const defineConfigCall = exportAssignment.getExpression();
|
|
3374
3336
|
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
@@ -3389,8 +3351,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3389
3351
|
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
3390
3352
|
const initializer = modulesProperty.getInitializer();
|
|
3391
3353
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3392
|
-
|
|
3393
|
-
if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3354
|
+
if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3394
3355
|
}
|
|
3395
3356
|
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
3396
3357
|
name: "modules",
|
|
@@ -3477,8 +3438,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3477
3438
|
} });
|
|
3478
3439
|
project.addSourceFileAtPath(svelteConfigPath);
|
|
3479
3440
|
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
3480
|
-
const
|
|
3481
|
-
const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3441
|
+
const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3482
3442
|
if (adapterImport) {
|
|
3483
3443
|
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
3484
3444
|
adapterImport.removeDefaultImport();
|
|
@@ -3604,8 +3564,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3604
3564
|
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3605
3565
|
const initializer = pluginsProperty.getInitializer();
|
|
3606
3566
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3607
|
-
|
|
3608
|
-
if (!hasShim) initializer.addElement("alchemy()");
|
|
3567
|
+
if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
|
|
3609
3568
|
const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
|
|
3610
3569
|
let needsReactPlugin = false;
|
|
3611
3570
|
tanstackElements.forEach((element) => {
|
|
@@ -3623,8 +3582,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3623
3582
|
name: "target",
|
|
3624
3583
|
initializer: "\"cloudflare-module\""
|
|
3625
3584
|
});
|
|
3626
|
-
|
|
3627
|
-
if (!hasCustomViteReactPlugin) configObj.addPropertyAssignment({
|
|
3585
|
+
if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
|
|
3628
3586
|
name: "customViteReactPlugin",
|
|
3629
3587
|
initializer: "true"
|
|
3630
3588
|
});
|
|
@@ -3645,7 +3603,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3645
3603
|
console.warn("Failed to update vite.config.ts:", error);
|
|
3646
3604
|
}
|
|
3647
3605
|
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
|
|
3648
|
-
|
|
3606
|
+
await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
|
|
3649
3607
|
|
|
3650
3608
|
export default defineNitroConfig({
|
|
3651
3609
|
preset: "cloudflare-module",
|
|
@@ -3653,8 +3611,7 @@ export default defineNitroConfig({
|
|
|
3653
3611
|
nodeCompat: true,
|
|
3654
3612
|
},
|
|
3655
3613
|
});
|
|
3656
|
-
|
|
3657
|
-
await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
|
|
3614
|
+
`, "utf-8");
|
|
3658
3615
|
}
|
|
3659
3616
|
|
|
3660
3617
|
//#endregion
|
|
@@ -3772,8 +3729,7 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
|
3772
3729
|
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
|
3773
3730
|
const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
3774
3731
|
if (arrayExpr) {
|
|
3775
|
-
|
|
3776
|
-
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3732
|
+
if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3777
3733
|
}
|
|
3778
3734
|
} else configObj.addPropertyAssignment({
|
|
3779
3735
|
name: "modules",
|
|
@@ -3808,13 +3764,10 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
|
3808
3764
|
if (!sourceFile) return;
|
|
3809
3765
|
const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
|
|
3810
3766
|
if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
|
3811
|
-
else {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3767
|
+
else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
|
|
3768
|
+
defaultImport: "adapter",
|
|
3769
|
+
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3770
|
+
});
|
|
3818
3771
|
await tsProject.save();
|
|
3819
3772
|
}
|
|
3820
3773
|
}
|
|
@@ -3865,8 +3818,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3865
3818
|
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
|
|
3866
3819
|
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
3867
3820
|
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
3868
|
-
|
|
3869
|
-
if (!hasReactPlugin) {
|
|
3821
|
+
if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
|
|
3870
3822
|
const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
|
|
3871
3823
|
if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
|
|
3872
3824
|
else pluginsArray.addElement(`${reactPluginIdentifier}()`);
|
|
@@ -3886,8 +3838,7 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3886
3838
|
});
|
|
3887
3839
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3888
3840
|
if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
|
|
3889
|
-
|
|
3890
|
-
if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
|
|
3841
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
|
|
3891
3842
|
namedImports: ["cloudflare"],
|
|
3892
3843
|
moduleSpecifier: "@cloudflare/vite-plugin"
|
|
3893
3844
|
});
|
|
@@ -3896,12 +3847,10 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3896
3847
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3897
3848
|
});
|
|
3898
3849
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
3899
|
-
const
|
|
3900
|
-
const configObject = callExpr.getArguments()[0];
|
|
3850
|
+
const configObject = defineCall.getArguments()[0];
|
|
3901
3851
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
3902
3852
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
3903
|
-
|
|
3904
|
-
if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
|
|
3853
|
+
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
|
|
3905
3854
|
await tsProject.save();
|
|
3906
3855
|
}
|
|
3907
3856
|
|
|
@@ -3960,8 +3909,7 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
|
3960
3909
|
async function addDeploymentToProject(input) {
|
|
3961
3910
|
try {
|
|
3962
3911
|
const projectDir = input.projectDir || process.cwd();
|
|
3963
|
-
|
|
3964
|
-
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.");
|
|
3912
|
+
if (!await isBetterTStackProject(projectDir)) 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.");
|
|
3965
3913
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3966
3914
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3967
3915
|
if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
|
|
@@ -4131,8 +4079,7 @@ function getQueryDependencies(frontend) {
|
|
|
4131
4079
|
"native-unistyles"
|
|
4132
4080
|
];
|
|
4133
4081
|
const deps = {};
|
|
4134
|
-
|
|
4135
|
-
if (needsReactQuery) {
|
|
4082
|
+
if (frontend.some((f) => reactBasedFrontends.includes(f))) {
|
|
4136
4083
|
const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
|
|
4137
4084
|
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
4138
4085
|
if (hasReactWeb) deps.web = {
|
|
@@ -4295,8 +4242,7 @@ async function setupAuth(config) {
|
|
|
4295
4242
|
}
|
|
4296
4243
|
if (auth === "better-auth") {
|
|
4297
4244
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4298
|
-
|
|
4299
|
-
if (convexBackendDirExists) await addPackageDependency({
|
|
4245
|
+
if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
|
|
4300
4246
|
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4301
4247
|
customDependencies: { "better-auth": "1.3.8" },
|
|
4302
4248
|
projectDir: convexBackendDir
|
|
@@ -4334,7 +4280,7 @@ async function setupAuth(config) {
|
|
|
4334
4280
|
dependencies: ["better-auth"],
|
|
4335
4281
|
projectDir: serverDir
|
|
4336
4282
|
});
|
|
4337
|
-
|
|
4283
|
+
if (frontend.some((f) => [
|
|
4338
4284
|
"react-router",
|
|
4339
4285
|
"tanstack-router",
|
|
4340
4286
|
"tanstack-start",
|
|
@@ -4342,8 +4288,7 @@ async function setupAuth(config) {
|
|
|
4342
4288
|
"nuxt",
|
|
4343
4289
|
"svelte",
|
|
4344
4290
|
"solid"
|
|
4345
|
-
].includes(f))
|
|
4346
|
-
if (hasWebFrontend$1 && clientDirExists) {
|
|
4291
|
+
].includes(f)) && clientDirExists) {
|
|
4347
4292
|
if (auth === "better-auth") await addPackageDependency({
|
|
4348
4293
|
dependencies: ["better-auth"],
|
|
4349
4294
|
projectDir: clientDir
|
|
@@ -4411,8 +4356,7 @@ async function addEnvVariablesToFile(filePath, variables) {
|
|
|
4411
4356
|
let exampleContentToAdd = "";
|
|
4412
4357
|
for (const exampleVar of exampleVariables) {
|
|
4413
4358
|
const key = exampleVar.split("=")[0];
|
|
4414
|
-
|
|
4415
|
-
if (!regex.test(exampleEnvContent)) {
|
|
4359
|
+
if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
|
|
4416
4360
|
exampleContentToAdd += `${exampleVar}\n`;
|
|
4417
4361
|
exampleModified = true;
|
|
4418
4362
|
}
|
|
@@ -4432,8 +4376,7 @@ async function setupEnvironmentVariables(config) {
|
|
|
4432
4376
|
const hasNuxt = frontend.includes("nuxt");
|
|
4433
4377
|
const hasSvelte = frontend.includes("svelte");
|
|
4434
4378
|
const hasSolid = frontend.includes("solid");
|
|
4435
|
-
|
|
4436
|
-
if (hasWebFrontend$1) {
|
|
4379
|
+
if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
|
|
4437
4380
|
const clientDir = path.join(projectDir, "apps/web");
|
|
4438
4381
|
if (await fs.pathExists(clientDir)) {
|
|
4439
4382
|
let envVarName = "VITE_SERVER_URL";
|
|
@@ -4522,15 +4465,12 @@ async function setupEnvironmentVariables(config) {
|
|
|
4522
4465
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4523
4466
|
if (await fs.pathExists(convexBackendDir)) {
|
|
4524
4467
|
const envLocalPath = path.join(convexBackendDir, ".env.local");
|
|
4525
|
-
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set"))
|
|
4526
|
-
const convexCommands = `# Set Convex environment variables
|
|
4468
|
+
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
|
|
4527
4469
|
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
4528
4470
|
# npx convex env set SITE_URL http://localhost:3001
|
|
4529
4471
|
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
}
|
|
4533
|
-
const convexBackendVars = [{
|
|
4472
|
+
`);
|
|
4473
|
+
await addEnvVariablesToFile(envLocalPath, [{
|
|
4534
4474
|
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
4535
4475
|
value: "",
|
|
4536
4476
|
condition: true,
|
|
@@ -4539,8 +4479,7 @@ async function setupEnvironmentVariables(config) {
|
|
|
4539
4479
|
key: "SITE_URL",
|
|
4540
4480
|
value: "http://localhost:3001",
|
|
4541
4481
|
condition: true
|
|
4542
|
-
}];
|
|
4543
|
-
await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
4482
|
+
}]);
|
|
4544
4483
|
}
|
|
4545
4484
|
}
|
|
4546
4485
|
return;
|
|
@@ -4608,34 +4547,27 @@ async function setupEnvironmentVariables(config) {
|
|
|
4608
4547
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4609
4548
|
if (isUnifiedAlchemy) {
|
|
4610
4549
|
const rootEnvPath = path.join(projectDir, ".env");
|
|
4611
|
-
|
|
4550
|
+
await addEnvVariablesToFile(rootEnvPath, [{
|
|
4612
4551
|
key: "ALCHEMY_PASSWORD",
|
|
4613
4552
|
value: "please-change-this",
|
|
4614
4553
|
condition: true
|
|
4615
|
-
}];
|
|
4616
|
-
await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
|
|
4554
|
+
}]);
|
|
4617
4555
|
} else if (isIndividualAlchemy) {
|
|
4618
4556
|
if (webDeploy === "alchemy") {
|
|
4619
4557
|
const webDir = path.join(projectDir, "apps/web");
|
|
4620
|
-
if (await fs.pathExists(webDir)) {
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
}];
|
|
4626
|
-
await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
|
|
4627
|
-
}
|
|
4558
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
|
|
4559
|
+
key: "ALCHEMY_PASSWORD",
|
|
4560
|
+
value: "please-change-this",
|
|
4561
|
+
condition: true
|
|
4562
|
+
}]);
|
|
4628
4563
|
}
|
|
4629
4564
|
if (serverDeploy === "alchemy") {
|
|
4630
4565
|
const serverDir$1 = path.join(projectDir, "apps/server");
|
|
4631
|
-
if (await fs.pathExists(serverDir$1)) {
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
}];
|
|
4637
|
-
await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
|
|
4638
|
-
}
|
|
4566
|
+
if (await fs.pathExists(serverDir$1)) await addEnvVariablesToFile(path.join(serverDir$1, ".env"), [{
|
|
4567
|
+
key: "ALCHEMY_PASSWORD",
|
|
4568
|
+
value: "please-change-this",
|
|
4569
|
+
condition: true
|
|
4570
|
+
}]);
|
|
4639
4571
|
}
|
|
4640
4572
|
}
|
|
4641
4573
|
}
|
|
@@ -4718,13 +4650,8 @@ function getDatabaseUrl(database, projectName) {
|
|
|
4718
4650
|
//#region src/utils/command-exists.ts
|
|
4719
4651
|
async function commandExists(command) {
|
|
4720
4652
|
try {
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
const result$1 = await execa("where", [command]);
|
|
4724
|
-
return result$1.exitCode === 0;
|
|
4725
|
-
}
|
|
4726
|
-
const result = await execa("which", [command]);
|
|
4727
|
-
return result.exitCode === 0;
|
|
4653
|
+
if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
|
|
4654
|
+
return (await execa("which", [command])).exitCode === 0;
|
|
4728
4655
|
} catch {
|
|
4729
4656
|
return false;
|
|
4730
4657
|
}
|
|
@@ -4746,8 +4673,7 @@ async function checkAtlasCLI() {
|
|
|
4746
4673
|
}
|
|
4747
4674
|
async function initMongoDBAtlas(serverDir) {
|
|
4748
4675
|
try {
|
|
4749
|
-
|
|
4750
|
-
if (!hasAtlas) {
|
|
4676
|
+
if (!await checkAtlasCLI()) {
|
|
4751
4677
|
consola.error(pc.red("MongoDB Atlas CLI not found."));
|
|
4752
4678
|
log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
|
|
4753
4679
|
return null;
|
|
@@ -5152,9 +5078,8 @@ async function setupWithCreateDb(serverDir, packageManager, orm) {
|
|
|
5152
5078
|
consola$1.error("Failed to parse create-db response");
|
|
5153
5079
|
return null;
|
|
5154
5080
|
}
|
|
5155
|
-
const databaseUrl = orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString;
|
|
5156
5081
|
return {
|
|
5157
|
-
databaseUrl,
|
|
5082
|
+
databaseUrl: orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString,
|
|
5158
5083
|
claimUrl: createDbResponse.claimUrl
|
|
5159
5084
|
};
|
|
5160
5085
|
} catch (error) {
|
|
@@ -5316,7 +5241,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5316
5241
|
try {
|
|
5317
5242
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
5318
5243
|
const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
5319
|
-
|
|
5244
|
+
await addEnvVariablesToFile(envPath, [{
|
|
5320
5245
|
key: "DATABASE_URL",
|
|
5321
5246
|
value: dbUrlToUse,
|
|
5322
5247
|
condition: true
|
|
@@ -5324,8 +5249,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5324
5249
|
key: "DIRECT_URL",
|
|
5325
5250
|
value: dbUrlToUse,
|
|
5326
5251
|
condition: true
|
|
5327
|
-
}];
|
|
5328
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
5252
|
+
}]);
|
|
5329
5253
|
return true;
|
|
5330
5254
|
} catch (error) {
|
|
5331
5255
|
consola$1.error(pc.red("Failed to update .env file for Supabase."));
|
|
@@ -5334,8 +5258,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5334
5258
|
}
|
|
5335
5259
|
}
|
|
5336
5260
|
function extractDbUrl(output) {
|
|
5337
|
-
const
|
|
5338
|
-
const url = dbUrlMatch?.[1];
|
|
5261
|
+
const url = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1];
|
|
5339
5262
|
if (url) return url;
|
|
5340
5263
|
return null;
|
|
5341
5264
|
}
|
|
@@ -5431,8 +5354,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
5431
5354
|
await writeSupabaseEnvFile(projectDir, "");
|
|
5432
5355
|
return;
|
|
5433
5356
|
}
|
|
5434
|
-
|
|
5435
|
-
if (!initialized) {
|
|
5357
|
+
if (!await initializeSupabase(serverDir, packageManager)) {
|
|
5436
5358
|
displayManualSupabaseInstructions();
|
|
5437
5359
|
return;
|
|
5438
5360
|
}
|
|
@@ -5442,14 +5364,12 @@ async function setupSupabase(config, cliInput) {
|
|
|
5442
5364
|
return;
|
|
5443
5365
|
}
|
|
5444
5366
|
const dbUrl = extractDbUrl(supabaseOutput);
|
|
5445
|
-
if (dbUrl)
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
}
|
|
5452
|
-
} else {
|
|
5367
|
+
if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
|
|
5368
|
+
else {
|
|
5369
|
+
log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
|
|
5370
|
+
displayManualSupabaseInstructions(supabaseOutput);
|
|
5371
|
+
}
|
|
5372
|
+
else {
|
|
5453
5373
|
log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
|
|
5454
5374
|
displayManualSupabaseInstructions(supabaseOutput);
|
|
5455
5375
|
}
|
|
@@ -5467,8 +5387,7 @@ async function isTursoInstalled() {
|
|
|
5467
5387
|
}
|
|
5468
5388
|
async function isTursoLoggedIn() {
|
|
5469
5389
|
try {
|
|
5470
|
-
|
|
5471
|
-
return !output.stdout.includes("You are not logged in");
|
|
5390
|
+
return !(await $`turso auth whoami`).stdout.includes("You are not logged in");
|
|
5472
5391
|
} catch {
|
|
5473
5392
|
return false;
|
|
5474
5393
|
}
|
|
@@ -5627,10 +5546,9 @@ async function setupTurso(config, cliInput) {
|
|
|
5627
5546
|
return;
|
|
5628
5547
|
}
|
|
5629
5548
|
setupSpinner.start("Checking Turso CLI availability...");
|
|
5630
|
-
const platform = os.platform();
|
|
5549
|
+
const platform = os$1.platform();
|
|
5631
5550
|
const isMac = platform === "darwin";
|
|
5632
|
-
|
|
5633
|
-
if (isWindows) {
|
|
5551
|
+
if (platform === "win32") {
|
|
5634
5552
|
if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
|
5635
5553
|
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
|
|
5636
5554
|
await writeEnvFile(projectDir);
|
|
@@ -5638,8 +5556,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5638
5556
|
return;
|
|
5639
5557
|
}
|
|
5640
5558
|
if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
|
|
5641
|
-
|
|
5642
|
-
if (!isCliInstalled) {
|
|
5559
|
+
if (!await isTursoInstalled()) {
|
|
5643
5560
|
const shouldInstall = await confirm({
|
|
5644
5561
|
message: "Would you like to install Turso CLI?",
|
|
5645
5562
|
initialValue: true
|
|
@@ -5652,8 +5569,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5652
5569
|
}
|
|
5653
5570
|
await installTursoCLI(isMac);
|
|
5654
5571
|
}
|
|
5655
|
-
|
|
5656
|
-
if (!isLoggedIn) await loginToTurso();
|
|
5572
|
+
if (!await isTursoLoggedIn()) await loginToTurso();
|
|
5657
5573
|
const selectedGroup = await selectTursoGroup();
|
|
5658
5574
|
let success = false;
|
|
5659
5575
|
let dbName = "";
|
|
@@ -5910,8 +5826,7 @@ function generateStackDescription(frontend, backend, api, isConvex) {
|
|
|
5910
5826
|
const hasSvelte = frontend.includes("svelte");
|
|
5911
5827
|
const hasNuxt = frontend.includes("nuxt");
|
|
5912
5828
|
const hasSolid = frontend.includes("solid");
|
|
5913
|
-
|
|
5914
|
-
if (!hasFrontendNone) {
|
|
5829
|
+
if (!(frontend.length === 0 || frontend.includes("none"))) {
|
|
5915
5830
|
if (hasTanstackRouter) parts.push("React, TanStack Router");
|
|
5916
5831
|
else if (hasReactRouter) parts.push("React, React Router");
|
|
5917
5832
|
else if (hasNext) parts.push("Next.js");
|
|
@@ -5967,8 +5882,7 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
|
|
|
5967
5882
|
structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
|
|
5968
5883
|
}
|
|
5969
5884
|
}
|
|
5970
|
-
|
|
5971
|
-
if (hasNative) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5885
|
+
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5972
5886
|
if (addons.includes("starlight")) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
|
|
5973
5887
|
if (isConvex) {
|
|
5974
5888
|
structure.push("├── packages/");
|
|
@@ -6152,12 +6066,11 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
|
|
|
6152
6066
|
//#region src/helpers/core/git.ts
|
|
6153
6067
|
async function initializeGit(projectDir, useGit) {
|
|
6154
6068
|
if (!useGit) return;
|
|
6155
|
-
|
|
6069
|
+
if ((await $({
|
|
6156
6070
|
cwd: projectDir,
|
|
6157
6071
|
reject: false,
|
|
6158
6072
|
stderr: "pipe"
|
|
6159
|
-
})`git --version
|
|
6160
|
-
if (gitVersionResult.exitCode !== 0) {
|
|
6073
|
+
})`git --version`).exitCode !== 0) {
|
|
6161
6074
|
log.warn(pc.yellow("Git is not installed"));
|
|
6162
6075
|
return;
|
|
6163
6076
|
}
|
|
@@ -6187,7 +6100,7 @@ async function setupPayments(config) {
|
|
|
6187
6100
|
projectDir: serverDir
|
|
6188
6101
|
});
|
|
6189
6102
|
if (clientDirExists) {
|
|
6190
|
-
|
|
6103
|
+
if (frontend.some((f) => [
|
|
6191
6104
|
"react-router",
|
|
6192
6105
|
"tanstack-router",
|
|
6193
6106
|
"tanstack-start",
|
|
@@ -6195,8 +6108,7 @@ async function setupPayments(config) {
|
|
|
6195
6108
|
"nuxt",
|
|
6196
6109
|
"svelte",
|
|
6197
6110
|
"solid"
|
|
6198
|
-
].includes(f))
|
|
6199
|
-
if (hasWebFrontend$1) await addPackageDependency({
|
|
6111
|
+
].includes(f))) await addPackageDependency({
|
|
6200
6112
|
dependencies: ["@polar-sh/better-auth"],
|
|
6201
6113
|
projectDir: clientDir
|
|
6202
6114
|
});
|
|
@@ -6237,15 +6149,13 @@ function getDockerInstallInstructions(platform, database) {
|
|
|
6237
6149
|
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
|
6238
6150
|
}
|
|
6239
6151
|
async function getDockerStatus(database) {
|
|
6240
|
-
const platform = os.platform();
|
|
6241
|
-
|
|
6242
|
-
if (!installed) return {
|
|
6152
|
+
const platform = os$1.platform();
|
|
6153
|
+
if (!await isDockerInstalled()) return {
|
|
6243
6154
|
installed: false,
|
|
6244
6155
|
running: false,
|
|
6245
6156
|
message: getDockerInstallInstructions(platform, database)
|
|
6246
6157
|
};
|
|
6247
|
-
|
|
6248
|
-
if (!running) return {
|
|
6158
|
+
if (!await isDockerRunning()) return {
|
|
6249
6159
|
installed: true,
|
|
6250
6160
|
running: false,
|
|
6251
6161
|
message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`
|
|
@@ -6560,8 +6470,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6560
6470
|
const workspaces = packageJson.workspaces;
|
|
6561
6471
|
if (options.backend === "convex") {
|
|
6562
6472
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
6563
|
-
|
|
6564
|
-
if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6473
|
+
if ((options.frontend.length > 0 || options.addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6565
6474
|
} else {
|
|
6566
6475
|
if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6567
6476
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
@@ -6781,9 +6690,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
6781
6690
|
finalPathInput: currentPathInput,
|
|
6782
6691
|
shouldClearDirectory: false
|
|
6783
6692
|
};
|
|
6784
|
-
|
|
6785
|
-
const isNotEmpty = dirContents.length > 0;
|
|
6786
|
-
if (!isNotEmpty) return {
|
|
6693
|
+
if (!((await fs.readdir(currentPath)).length > 0)) return {
|
|
6787
6694
|
finalPathInput: currentPathInput,
|
|
6788
6695
|
shouldClearDirectory: false
|
|
6789
6696
|
};
|
|
@@ -6945,26 +6852,25 @@ function displaySponsorsBox(sponsors$1) {
|
|
|
6945
6852
|
|
|
6946
6853
|
//#endregion
|
|
6947
6854
|
//#region src/index.ts
|
|
6948
|
-
const
|
|
6949
|
-
|
|
6950
|
-
init: t.procedure.meta({
|
|
6855
|
+
const router = os.router({
|
|
6856
|
+
init: os.meta({
|
|
6951
6857
|
description: "Create a new Better-T-Stack project",
|
|
6952
6858
|
default: true,
|
|
6953
6859
|
negateBooleans: true
|
|
6954
|
-
}).input(z.tuple([ProjectNameSchema.optional(), z.object({
|
|
6955
|
-
yes: z.boolean().optional().default(false).describe("Use default configuration"),
|
|
6956
|
-
yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
6957
|
-
verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
|
|
6860
|
+
}).input(z$1.tuple([ProjectNameSchema.optional(), z$1.object({
|
|
6861
|
+
yes: z$1.boolean().optional().default(false).describe("Use default configuration"),
|
|
6862
|
+
yolo: z$1.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
6863
|
+
verbose: z$1.boolean().optional().default(false).describe("Show detailed result information"),
|
|
6958
6864
|
database: DatabaseSchema.optional(),
|
|
6959
6865
|
orm: ORMSchema.optional(),
|
|
6960
6866
|
auth: AuthSchema.optional(),
|
|
6961
6867
|
payments: PaymentsSchema.optional(),
|
|
6962
|
-
frontend: z.array(FrontendSchema).optional(),
|
|
6963
|
-
addons: z.array(AddonsSchema).optional(),
|
|
6964
|
-
examples: z.array(ExamplesSchema).optional(),
|
|
6965
|
-
git: z.boolean().optional(),
|
|
6868
|
+
frontend: z$1.array(FrontendSchema).optional(),
|
|
6869
|
+
addons: z$1.array(AddonsSchema).optional(),
|
|
6870
|
+
examples: z$1.array(ExamplesSchema).optional(),
|
|
6871
|
+
git: z$1.boolean().optional(),
|
|
6966
6872
|
packageManager: PackageManagerSchema.optional(),
|
|
6967
|
-
install: z.boolean().optional(),
|
|
6873
|
+
install: z$1.boolean().optional(),
|
|
6968
6874
|
dbSetup: DatabaseSetupSchema.optional(),
|
|
6969
6875
|
backend: BackendSchema.optional(),
|
|
6970
6876
|
runtime: RuntimeSchema.optional(),
|
|
@@ -6972,10 +6878,10 @@ const router = t.router({
|
|
|
6972
6878
|
webDeploy: WebDeploySchema.optional(),
|
|
6973
6879
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6974
6880
|
directoryConflict: DirectoryConflictSchema.optional(),
|
|
6975
|
-
renderTitle: z.boolean().optional(),
|
|
6976
|
-
disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
|
|
6977
|
-
manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
6978
|
-
})])).
|
|
6881
|
+
renderTitle: z$1.boolean().optional(),
|
|
6882
|
+
disableAnalytics: z$1.boolean().optional().default(false).describe("Disable analytics"),
|
|
6883
|
+
manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
6884
|
+
})])).handler(async ({ input }) => {
|
|
6979
6885
|
const [projectName, options] = input;
|
|
6980
6886
|
const combinedInput = {
|
|
6981
6887
|
projectName,
|
|
@@ -6984,18 +6890,18 @@ const router = t.router({
|
|
|
6984
6890
|
const result = await createProjectHandler(combinedInput);
|
|
6985
6891
|
if (options.verbose) return result;
|
|
6986
6892
|
}),
|
|
6987
|
-
add:
|
|
6988
|
-
addons: z.array(AddonsSchema).optional().default([]),
|
|
6893
|
+
add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
|
|
6894
|
+
addons: z$1.array(AddonsSchema).optional().default([]),
|
|
6989
6895
|
webDeploy: WebDeploySchema.optional(),
|
|
6990
6896
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6991
|
-
projectDir: z.string().optional(),
|
|
6992
|
-
install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
6897
|
+
projectDir: z$1.string().optional(),
|
|
6898
|
+
install: z$1.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
6993
6899
|
packageManager: PackageManagerSchema.optional()
|
|
6994
|
-
})])).
|
|
6900
|
+
})])).handler(async ({ input }) => {
|
|
6995
6901
|
const [options] = input;
|
|
6996
6902
|
await addAddonsHandler(options);
|
|
6997
6903
|
}),
|
|
6998
|
-
sponsors:
|
|
6904
|
+
sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
|
|
6999
6905
|
try {
|
|
7000
6906
|
renderTitle();
|
|
7001
6907
|
intro(pc.magenta("Better-T-Stack Sponsors"));
|
|
@@ -7005,7 +6911,7 @@ const router = t.router({
|
|
|
7005
6911
|
handleError(error, "Failed to display sponsors");
|
|
7006
6912
|
}
|
|
7007
6913
|
}),
|
|
7008
|
-
docs:
|
|
6914
|
+
docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
|
|
7009
6915
|
const DOCS_URL = "https://better-t-stack.dev/docs";
|
|
7010
6916
|
try {
|
|
7011
6917
|
await openUrl(DOCS_URL);
|
|
@@ -7014,7 +6920,7 @@ const router = t.router({
|
|
|
7014
6920
|
log.message(`Please visit ${DOCS_URL}`);
|
|
7015
6921
|
}
|
|
7016
6922
|
}),
|
|
7017
|
-
builder:
|
|
6923
|
+
builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
|
|
7018
6924
|
const BUILDER_URL = "https://better-t-stack.dev/new";
|
|
7019
6925
|
try {
|
|
7020
6926
|
await openUrl(BUILDER_URL);
|
|
@@ -7024,7 +6930,7 @@ const router = t.router({
|
|
|
7024
6930
|
}
|
|
7025
6931
|
})
|
|
7026
6932
|
});
|
|
7027
|
-
const caller =
|
|
6933
|
+
const caller = createRouterClient(router, { context: {} });
|
|
7028
6934
|
function createBtsCli() {
|
|
7029
6935
|
return createCli({
|
|
7030
6936
|
router,
|
|
@@ -7066,9 +6972,8 @@ function createBtsCli() {
|
|
|
7066
6972
|
* ```
|
|
7067
6973
|
*/
|
|
7068
6974
|
async function init(projectName, options) {
|
|
7069
|
-
const opts = options ?? {};
|
|
7070
6975
|
const programmaticOpts = {
|
|
7071
|
-
...
|
|
6976
|
+
...options ?? {},
|
|
7072
6977
|
verbose: true
|
|
7073
6978
|
};
|
|
7074
6979
|
const prev = process.env.BTS_PROGRAMMATIC;
|