Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cursor/rules/better-t-stack-repo.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ alwaysApply: true
- Do not include emojis.
- Use TypeScript type aliases instead of interface declarations.
- In Handlebars templates, avoid generic if/else blocks. Write explicit conditions, such as: use if (eq orm "prisma") for Prisma, and else if (eq orm "drizzle") for Drizzle.
- Do not use explicit return types
5 changes: 2 additions & 3 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Addons, Frontend, ProjectConfig } from "./types";
import { getUserPkgManager } from "./utils/get-package-manager";

const __filename = fileURLToPath(import.meta.url);
Expand All @@ -26,7 +25,7 @@ export const DEFAULT_CONFIG_BASE = {
serverDeploy: "none",
} as const;

export function getDefaultConfig(): ProjectConfig {
export function getDefaultConfig() {
return {
...DEFAULT_CONFIG_BASE,
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
Expand Down Expand Up @@ -160,7 +159,7 @@ export const dependencyVersionMap = {

export type AvailableDependencies = keyof typeof dependencyVersionMap;

export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
export const ADDON_COMPATIBILITY = {
pwa: ["tanstack-router", "react-router", "solid", "next"],
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"],
biome: [],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/addons/addons-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
}
}

function getWebAppDir(projectDir: string, frontends: Frontend[]): string {
function getWebAppDir(projectDir: string, frontends: Frontend[]) {
if (
frontends.some((f) =>
["react-router", "tanstack-router", "nuxt", "svelte", "solid"].includes(
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/core/auth-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export async function setupAuth(config: ProjectConfig) {
}
}

export function generateAuthSecret(length = 32): string {
export function generateAuthSecret(length = 32) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
Expand Down
7 changes: 3 additions & 4 deletions apps/cli/src/helpers/core/command-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
AddInput,
CreateInput,
DirectoryConflict,
InitResult,
ProjectConfig,
} from "../../types";
import { trackProjectCreation } from "../../utils/analytics";
Expand All @@ -40,7 +39,7 @@ import { installDependencies } from "./install-dependencies";

export async function createProjectHandler(
input: CreateInput & { projectName?: string },
): Promise<InitResult> {
) {
const startTime = Date.now();
const timeScaffolded = new Date().toISOString();

Expand All @@ -58,7 +57,7 @@ export async function createProjectHandler(
currentPathInput = input.projectName;
} else if (input.yes) {
const defaultConfig = getDefaultConfig();
let defaultName = defaultConfig.relativePath;
let defaultName: string = defaultConfig.relativePath;
let counter = 1;
while (
(await fs.pathExists(path.resolve(process.cwd(), defaultName))) &&
Expand Down Expand Up @@ -210,7 +209,7 @@ export async function createProjectHandler(
async function handleDirectoryConflictProgrammatically(
currentPathInput: string,
strategy: DirectoryConflict,
): Promise<{ finalPathInput: string; shouldClearDirectory: boolean }> {
) {
const currentPath = path.resolve(process.cwd(), currentPathInput);

if (!(await fs.pathExists(currentPath))) {
Expand Down
16 changes: 8 additions & 8 deletions apps/cli/src/helpers/core/create-readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function createReadme(projectDir: string, options: ProjectConfig) {
}
}

function generateReadmeContent(options: ProjectConfig): string {
function generateReadmeContent(options: ProjectConfig) {
const {
projectName,
packageManager,
Expand Down Expand Up @@ -163,7 +163,7 @@ function generateStackDescription(
backend: string,
api: API,
isConvex: boolean,
): string {
) {
const parts: string[] = [];

const hasTanstackRouter = frontend.includes("tanstack-router");
Expand Down Expand Up @@ -210,7 +210,7 @@ function generateRunningInstructions(
webPort: string,
hasNative: boolean,
isConvex: boolean,
): string {
) {
const instructions: string[] = [];

const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
Expand Down Expand Up @@ -265,7 +265,7 @@ function generateProjectStructure(
isConvex: boolean,
api: API,
auth: Auth,
): string {
) {
const structure: string[] = [`${projectName}/`, "├── apps/"];

const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
Expand Down Expand Up @@ -349,7 +349,7 @@ function generateFeaturesList(
frontend: Frontend[],
backend: string,
api: API,
): string {
) {
const isConvex = backend === "convex";
const isBackendNone = backend === "none";
const hasTanstackRouter = frontend.includes("tanstack-router");
Expand Down Expand Up @@ -493,7 +493,7 @@ function generateDatabaseSetup(
orm: ORM,
dbSetup: DatabaseSetup,
serverDeploy?: string,
): string {
) {
if (database === "none") {
return "";
}
Expand Down Expand Up @@ -591,7 +591,7 @@ function generateScriptsList(
hasNative: boolean,
addons: Addons[],
backend: string,
): string {
) {
const isConvex = backend === "convex";
const isBackendNone = backend === "none";

Expand Down Expand Up @@ -657,7 +657,7 @@ function generateDeploymentCommands(
packageManagerRunCmd: string,
webDeploy?: string,
serverDeploy?: string,
): string {
) {
const lines: string[] = [];

if (webDeploy === "alchemy" || serverDeploy === "alchemy") {
Expand Down
5 changes: 1 addition & 4 deletions apps/cli/src/helpers/core/detect-project-config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import path from "node:path";
import fs from "fs-extra";
import type { ProjectConfig } from "../../types";
import { readBtsConfig } from "../../utils/bts-config";

export async function detectProjectConfig(
projectDir: string,
): Promise<Partial<ProjectConfig> | null> {
export async function detectProjectConfig(projectDir: string) {
try {
const btsConfig = await readBtsConfig(projectDir);
if (btsConfig) {
Expand Down
22 changes: 11 additions & 11 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export async function displayPostInstallInstructions(
consola.box(output);
}

function getNativeInstructions(isConvex: boolean): string {
function getNativeInstructions(isConvex: boolean) {
const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
const exampleUrl = isConvex
? "https://<YOUR_CONVEX_URL>"
Expand All @@ -226,7 +226,7 @@ function getNativeInstructions(isConvex: boolean): string {
return instructions;
}

function getLintingInstructions(runCmd?: string): string {
function getLintingInstructions(runCmd?: string) {
return `${pc.bold("Linting and formatting:")}\n${pc.cyan(
"•",
)} Format and lint fix: ${`${runCmd} check`}\n`;
Expand All @@ -239,7 +239,7 @@ async function getDatabaseInstructions(
runtime?: Runtime,
dbSetup?: DatabaseSetup,
serverDeploy?: string,
): Promise<string> {
) {
const instructions: string[] = [];

if (dbSetup === "docker") {
Expand Down Expand Up @@ -384,7 +384,7 @@ async function getDatabaseInstructions(
: "";
}

function getTauriInstructions(runCmd?: string): string {
function getTauriInstructions(runCmd?: string) {
return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan(
"•",
)} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan(
Expand All @@ -394,27 +394,27 @@ function getTauriInstructions(runCmd?: string): string {
)} Tauri requires Rust and platform-specific dependencies.\n See: ${"https://v2.tauri.app/start/prerequisites/"}`;
}

function getPwaInstructions(): string {
function getPwaInstructions() {
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
"NOTE:",
)} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
}

function getStarlightInstructions(runCmd?: string): string {
function getStarlightInstructions(runCmd?: string) {
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`}`;
}

function getNoOrmWarning(): string {
function getNoOrmWarning() {
return `\n${pc.yellow(
"WARNING:",
)} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
}

function getBunWebNativeWarning(): string {
function getBunWebNativeWarning() {
return `\n${pc.yellow(
"WARNING:",
)} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
Expand All @@ -424,7 +424,7 @@ function getWranglerDeployInstructions(
runCmd?: string,
webDeploy?: string,
serverDeploy?: string,
): string {
) {
const instructions: string[] = [];

if (webDeploy === "wrangler") {
Expand All @@ -441,15 +441,15 @@ function getWranglerDeployInstructions(
return instructions.length ? `\n${instructions.join("\n")}` : "";
}

function getClerkInstructions(): string {
function getClerkInstructions() {
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
}

function getAlchemyDeployInstructions(
runCmd?: string,
webDeploy?: string,
serverDeploy?: string,
): string {
) {
const instructions: string[] = [];

if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async function writeEnvFile(
await addEnvVariablesToFile(envPath, variables);
}

function getDatabaseUrl(database: Database, projectName: string): string {
function getDatabaseUrl(database: Database, projectName: string) {
switch (database) {
case "postgres":
return `postgresql://postgres:password@localhost:5432/${projectName}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ async function checkAtlasCLI() {
}
}

async function initMongoDBAtlas(
serverDir: string,
): Promise<MongoDBConfig | null> {
async function initMongoDBAtlas(serverDir: string) {
try {
const hasAtlas = await checkAtlasCLI();

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/helpers/database-providers/supabase-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function writeSupabaseEnvFile(projectDir: string, databaseUrl: string) {
}
}

function extractDbUrl(output: string): string | null {
function extractDbUrl(output: string) {
const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
const url = dbUrlMatch?.[1];
if (url) {
Expand Down
11 changes: 2 additions & 9 deletions apps/cli/src/helpers/database-providers/turso-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ type TursoConfig = {
authToken: string;
};

type TursoGroup = {
name: string;
locations: string;
version: string;
status: string;
};

async function isTursoInstalled() {
return commandExists("turso");
}
Expand Down Expand Up @@ -71,7 +64,7 @@ async function installTursoCLI(isMac: boolean) {
}
}

async function getTursoGroups(): Promise<TursoGroup[]> {
async function getTursoGroups() {
const s = spinner();
try {
s.start("Fetching Turso groups...");
Expand All @@ -97,7 +90,7 @@ async function getTursoGroups(): Promise<TursoGroup[]> {
}
}

async function selectTursoGroup(): Promise<string | null> {
async function selectTursoGroup() {
const groups = await getTursoGroups();

if (groups.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function setupSvelteAlchemyDeploy(
}
}

function updateAdapterInConfig(configObject: Node): void {
function updateAdapterInConfig(configObject: Node) {
if (!Node.isObjectLiteralExpression(configObject)) return;

const kitProperty = configObject.getProperty("kit");
Expand Down
5 changes: 1 addition & 4 deletions apps/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,7 @@ export function createBtsCli() {
* }
* ```
*/
export async function init(
projectName?: string,
options?: CreateInput,
): Promise<InitResult> {
export async function init(projectName?: string, options?: CreateInput) {
const opts = (options ?? {}) as CreateInput;
const programmaticOpts = { ...opts, verbose: true };
const prev = process.env.BTS_PROGRAMMATIC;
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const ADDON_GROUPS = {
export async function getAddonsChoice(
addons?: Addons[],
frontends?: Frontend[],
): Promise<Addons[]> {
) {
if (addons !== undefined) return addons;

const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
Expand Down Expand Up @@ -131,7 +131,7 @@ export async function getAddonsChoice(
export async function getAddonsToAdd(
frontend: Frontend[],
existingAddons: Addons[] = [],
): Promise<Addons[]> {
) {
const groupedOptions: Record<string, AddonOption[]> = {
Documentation: [],
Linting: [],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/prompts/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export async function getApiChoice(
Api?: API | undefined,
frontend?: Frontend[],
backend?: Backend,
): Promise<API> {
) {
if (backend === "convex" || backend === "none") {
return "none";
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/prompts/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { exitCancelled } from "../utils/errors";
export async function getBackendFrameworkChoice(
backendFramework?: Backend,
frontends?: Frontend[],
): Promise<Backend> {
) {
if (backendFramework !== undefined) return backendFramework;

const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
Expand Down
Loading