diff --git a/.changeset/clean-eyes-remain.md b/.changeset/clean-eyes-remain.md new file mode 100644 index 0000000..e63acba --- /dev/null +++ b/.changeset/clean-eyes-remain.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": patch +--- + +Prevent web-deploy when no web frontend is selected diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index b53997f..e0c494d 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; -import type { ProjectConfig } from "./types"; +import type { ProjectConfig, Frontend } from "./types"; import { getUserPkgManager } from "./utils/get-package-manager"; const __filename = fileURLToPath(import.meta.url); @@ -126,3 +126,14 @@ export const ADDON_COMPATIBILITY = { starlight: [], none: [], } as const; + +// TODO: need to refactor this +export const WEB_FRAMEWORKS: readonly Frontend[] = [ + "tanstack-router", + "react-router", + "tanstack-start", + "next", + "nuxt", + "svelte", + "solid", +]; diff --git a/apps/cli/src/prompts/web-deploy.ts b/apps/cli/src/prompts/web-deploy.ts index 8d31ce0..0b49151 100644 --- a/apps/cli/src/prompts/web-deploy.ts +++ b/apps/cli/src/prompts/web-deploy.ts @@ -1,8 +1,12 @@ import { cancel, isCancel, select } from "@clack/prompts"; import pc from "picocolors"; -import { DEFAULT_CONFIG } from "../constants"; +import { DEFAULT_CONFIG, WEB_FRAMEWORKS } from "../constants"; import type { Backend, Frontend, Runtime, WebDeploy } from "../types"; +function hasWebFrontend(frontends: Frontend[]): boolean { + return frontends.some((f) => WEB_FRAMEWORKS.includes(f)); +} + type DeploymentOption = { value: WebDeploy; label: string; @@ -29,15 +33,18 @@ export async function getDeploymentChoice( deployment?: WebDeploy, _runtime?: Runtime, _backend?: Backend, - _frontend: Frontend[] = [], + frontend: Frontend[] = [], ): Promise { if (deployment !== undefined) return deployment; + if (!hasWebFrontend(frontend)) { + return "none"; + } const options: DeploymentOption[] = [ { value: "workers", label: "Cloudflare Workers", - hint: "Deploy to Cloudflare Workers using Wrangler", + hint: "Deploy to Cloudflare Workers using Wrangler", }, { value: "none", label: "None", hint: "Manual setup" }, ]; @@ -57,9 +64,13 @@ export async function getDeploymentChoice( } export async function getDeploymentToAdd( - _frontend: Frontend[], + frontend: Frontend[], existingDeployment?: WebDeploy, ): Promise { + if (!hasWebFrontend(frontend)) { + return "none"; + } + const options: DeploymentOption[] = []; if (existingDeployment !== "workers") { diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts index 1d04deb..85448c3 100644 --- a/apps/cli/src/validation.ts +++ b/apps/cli/src/validation.ts @@ -16,6 +16,7 @@ import { type Runtime, type WebDeploy, } from "./types"; +import { WEB_FRAMEWORKS } from "./constants"; export function processAndValidateFlags( options: CLIInput, @@ -125,15 +126,8 @@ export function processAndValidateFlags( const validOptions = options.frontend.filter( (f): f is Frontend => f !== "none", ); - const webFrontends = validOptions.filter( - (f) => - f === "tanstack-router" || - f === "react-router" || - f === "tanstack-start" || - f === "next" || - f === "nuxt" || - f === "svelte" || - f === "solid", + const webFrontends = validOptions.filter((f) => + WEB_FRAMEWORKS.includes(f), ); const nativeFrontends = validOptions.filter( (f) => f === "native-nativewind" || f === "native-unistyles", @@ -451,6 +445,16 @@ export function processAndValidateFlags( process.exit(1); } + const hasWebFrontendFlag = (config.frontend ?? []).some((f) => + WEB_FRAMEWORKS.includes(f), + ); + if (config.webDeploy && config.webDeploy !== "none" && !hasWebFrontendFlag) { + consola.fatal( + "'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.", + ); + process.exit(1); + } + return config; } diff --git a/apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs b/apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs index 26ceb84..8c18520 100644 --- a/apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs +++ b/apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs @@ -68,7 +68,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle"; {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} import { expo } from "@better-auth/expo"; {{/if}} -import { db } from "@/db"; +import { db } from "../db"; import * as schema from "../db/schema/auth"; import { env } from "cloudflare:workers"; diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index 2f76136..480c0b2 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -805,6 +805,24 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { } } + const webFrontendsSelected = nextStack.webFrontend.some((f) => f !== "none"); + if (!webFrontendsSelected && nextStack.webDeploy !== "none") { + notes.webDeploy.notes.push( + "Web deployment requires a web frontend. It will be disabled.", + ); + notes.webFrontend.notes.push( + "No web frontend selected: Deployment has been disabled.", + ); + notes.webDeploy.hasIssue = true; + notes.webFrontend.hasIssue = true; + nextStack.webDeploy = "none"; + changed = true; + changes.push({ + category: "webDeploy", + message: "Web deployment set to 'none' (requires web frontend)", + }); + } + return { adjustedStack: changed ? nextStack : null, notes,