Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
13 changes: 3 additions & 10 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 All @@ -119,7 +112,7 @@ async function selectTursoGroup(): Promise<string | null> {
options: groupOptions,
});

if (isCancel(selectedGroup)) return exitCancelled("Operation cancelled");
if (isCancel(selectedGroup)) exitCancelled("Operation cancelled");

return selectedGroup as string;
}
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