mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
chore(cli): remove all explicit return types (#573)
This commit is contained in:
@@ -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);
|
||||
@@ -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),
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -13,7 +13,6 @@ import type {
|
||||
AddInput,
|
||||
CreateInput,
|
||||
DirectoryConflict,
|
||||
InitResult,
|
||||
ProjectConfig,
|
||||
} from "../../types";
|
||||
import { trackProjectCreation } from "../../utils/analytics";
|
||||
@@ -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();
|
||||
|
||||
@@ -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))) &&
|
||||
@@ -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))) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function createReadme(projectDir: string, options: ProjectConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
function generateReadmeContent(options: ProjectConfig): string {
|
||||
function generateReadmeContent(options: ProjectConfig) {
|
||||
const {
|
||||
projectName,
|
||||
packageManager,
|
||||
@@ -163,7 +163,7 @@ function generateStackDescription(
|
||||
backend: string,
|
||||
api: API,
|
||||
isConvex: boolean,
|
||||
): string {
|
||||
) {
|
||||
const parts: string[] = [];
|
||||
|
||||
const hasTanstackRouter = frontend.includes("tanstack-router");
|
||||
@@ -210,7 +210,7 @@ function generateRunningInstructions(
|
||||
webPort: string,
|
||||
hasNative: boolean,
|
||||
isConvex: boolean,
|
||||
): string {
|
||||
) {
|
||||
const instructions: string[] = [];
|
||||
|
||||
const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -493,7 +493,7 @@ function generateDatabaseSetup(
|
||||
orm: ORM,
|
||||
dbSetup: DatabaseSetup,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
) {
|
||||
if (database === "none") {
|
||||
return "";
|
||||
}
|
||||
@@ -591,7 +591,7 @@ function generateScriptsList(
|
||||
hasNative: boolean,
|
||||
addons: Addons[],
|
||||
backend: string,
|
||||
): string {
|
||||
) {
|
||||
const isConvex = backend === "convex";
|
||||
const isBackendNone = backend === "none";
|
||||
|
||||
@@ -657,7 +657,7 @@ function generateDeploymentCommands(
|
||||
packageManagerRunCmd: string,
|
||||
webDeploy?: string,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
) {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (webDeploy === "alchemy" || serverDeploy === "alchemy") {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>"
|
||||
@@ -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`;
|
||||
@@ -239,7 +239,7 @@ async function getDatabaseInstructions(
|
||||
runtime?: Runtime,
|
||||
dbSetup?: DatabaseSetup,
|
||||
serverDeploy?: string,
|
||||
): Promise<string> {
|
||||
) {
|
||||
const instructions: string[] = [];
|
||||
|
||||
if (dbSetup === "docker") {
|
||||
@@ -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(
|
||||
@@ -394,13 +394,13 @@ 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(
|
||||
@@ -408,13 +408,13 @@ function getStarlightInstructions(runCmd?: string): string {
|
||||
)} 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.`;
|
||||
@@ -424,7 +424,7 @@ function getWranglerDeployInstructions(
|
||||
runCmd?: string,
|
||||
webDeploy?: string,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
) {
|
||||
const instructions: string[] = [];
|
||||
|
||||
if (webDeploy === "wrangler") {
|
||||
@@ -441,7 +441,7 @@ 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`;
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ function getAlchemyDeployInstructions(
|
||||
runCmd?: string,
|
||||
webDeploy?: string,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
) {
|
||||
const instructions: string[] = [];
|
||||
|
||||
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -14,13 +14,6 @@ type TursoConfig = {
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
type TursoGroup = {
|
||||
name: string;
|
||||
locations: string;
|
||||
version: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
async function isTursoInstalled() {
|
||||
return commandExists("turso");
|
||||
}
|
||||
@@ -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...");
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
@@ -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: [],
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getApiChoice(
|
||||
Api?: API | undefined,
|
||||
frontend?: Frontend[],
|
||||
backend?: Backend,
|
||||
): Promise<API> {
|
||||
) {
|
||||
if (backend === "convex" || backend === "none") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function gatherConfig(
|
||||
projectName: string,
|
||||
projectDir: string,
|
||||
relativePath: string,
|
||||
): Promise<ProjectConfig> {
|
||||
) {
|
||||
const result = await group<PromptGroupResults>(
|
||||
{
|
||||
frontend: () =>
|
||||
@@ -75,10 +75,14 @@ export async function gatherConfig(
|
||||
results.runtime,
|
||||
),
|
||||
api: ({ results }) =>
|
||||
getApiChoice(flags.api, results.frontend, results.backend),
|
||||
getApiChoice(
|
||||
flags.api,
|
||||
results.frontend,
|
||||
results.backend,
|
||||
) as Promise<API>,
|
||||
auth: ({ results }) =>
|
||||
getAuthChoice(
|
||||
flags.auth as import("../types").Auth | undefined,
|
||||
flags.auth,
|
||||
results.database !== "none",
|
||||
results.backend,
|
||||
results.frontend,
|
||||
@@ -91,7 +95,7 @@ export async function gatherConfig(
|
||||
results.frontend,
|
||||
results.backend,
|
||||
results.api,
|
||||
),
|
||||
) as Promise<Examples[]>,
|
||||
dbSetup: ({ results }) =>
|
||||
getDBSetupChoice(
|
||||
results.database ?? "none",
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function getDBSetupChoice(
|
||||
_orm?: ORM,
|
||||
backend?: Backend,
|
||||
runtime?: Runtime,
|
||||
): Promise<DatabaseSetup> {
|
||||
) {
|
||||
if (backend === "convex") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getDatabaseChoice(
|
||||
database?: Database,
|
||||
backend?: Backend,
|
||||
runtime?: Runtime,
|
||||
): Promise<Database> {
|
||||
) {
|
||||
if (backend === "convex" || backend === "none") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function getExamplesChoice(
|
||||
frontends?: Frontend[],
|
||||
backend?: Backend,
|
||||
api?: API,
|
||||
): Promise<Examples[]> {
|
||||
) {
|
||||
if (examples !== undefined) return examples;
|
||||
|
||||
if (api === "none") {
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function getFrontendChoice(
|
||||
frontendOptions?: Frontend[],
|
||||
backend?: Backend,
|
||||
auth?: string,
|
||||
): Promise<Frontend[]> {
|
||||
) {
|
||||
if (frontendOptions !== undefined) return frontendOptions;
|
||||
|
||||
const frontendTypes = await multiselect({
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function getORMChoice(
|
||||
database?: Database,
|
||||
backend?: Backend,
|
||||
runtime?: Runtime,
|
||||
): Promise<ORM> {
|
||||
) {
|
||||
if (backend === "convex") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ import type { PackageManager } from "../types";
|
||||
import { exitCancelled } from "../utils/errors";
|
||||
import { getUserPkgManager } from "../utils/get-package-manager";
|
||||
|
||||
export async function getPackageManagerChoice(
|
||||
packageManager?: PackageManager,
|
||||
): Promise<PackageManager> {
|
||||
export async function getPackageManagerChoice(packageManager?: PackageManager) {
|
||||
if (packageManager !== undefined) return packageManager;
|
||||
|
||||
const detectedPackageManager = getUserPkgManager();
|
||||
|
||||
@@ -7,13 +7,13 @@ import { DEFAULT_CONFIG } from "../constants";
|
||||
import { ProjectNameSchema } from "../types";
|
||||
import { exitCancelled } from "../utils/errors";
|
||||
|
||||
function isPathWithinCwd(targetPath: string): boolean {
|
||||
function isPathWithinCwd(targetPath: string) {
|
||||
const resolved = path.resolve(targetPath);
|
||||
const rel = path.relative(process.cwd(), resolved);
|
||||
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
||||
}
|
||||
|
||||
function validateDirectoryName(name: string): string | undefined {
|
||||
function validateDirectoryName(name: string) {
|
||||
if (name === ".") return undefined;
|
||||
|
||||
const result = ProjectNameSchema.safeParse(name);
|
||||
@@ -23,7 +23,7 @@ function validateDirectoryName(name: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function getProjectName(initialName?: string): Promise<string> {
|
||||
export async function getProjectName(initialName?: string) {
|
||||
if (initialName) {
|
||||
if (initialName === ".") {
|
||||
return initialName;
|
||||
@@ -41,7 +41,7 @@ export async function getProjectName(initialName?: string): Promise<string> {
|
||||
|
||||
let isValid = false;
|
||||
let projectPath = "";
|
||||
let defaultName = DEFAULT_CONFIG.projectName;
|
||||
let defaultName: string = DEFAULT_CONFIG.projectName;
|
||||
let counter = 1;
|
||||
|
||||
while (
|
||||
|
||||
@@ -3,10 +3,7 @@ import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { Backend, Runtime } from "../types";
|
||||
import { exitCancelled } from "../utils/errors";
|
||||
|
||||
export async function getRuntimeChoice(
|
||||
runtime?: Runtime,
|
||||
backend?: Backend,
|
||||
): Promise<Runtime> {
|
||||
export async function getRuntimeChoice(runtime?: Runtime, backend?: Backend) {
|
||||
if (backend === "convex" || backend === "none") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function getServerDeploymentChoice(
|
||||
runtime?: Runtime,
|
||||
backend?: Backend,
|
||||
webDeploy?: WebDeploy,
|
||||
): Promise<ServerDeploy> {
|
||||
) {
|
||||
if (deployment !== undefined) return deployment;
|
||||
|
||||
if (backend === "none" || backend === "convex") {
|
||||
@@ -82,7 +82,7 @@ export async function getServerDeploymentToAdd(
|
||||
runtime?: Runtime,
|
||||
existingDeployment?: ServerDeploy,
|
||||
backend?: Backend,
|
||||
): Promise<ServerDeploy> {
|
||||
) {
|
||||
if (backend !== "hono") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
|
||||
import { WEB_FRAMEWORKS } from "../utils/compatibility";
|
||||
import { exitCancelled } from "../utils/errors";
|
||||
|
||||
function hasWebFrontend(frontends: Frontend[]): boolean {
|
||||
function hasWebFrontend(frontends: Frontend[]) {
|
||||
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function getDeploymentChoice(
|
||||
_runtime?: Runtime,
|
||||
_backend?: Backend,
|
||||
frontend: Frontend[] = [],
|
||||
): Promise<WebDeploy> {
|
||||
) {
|
||||
if (deployment !== undefined) return deployment;
|
||||
if (!hasWebFrontend(frontend)) {
|
||||
return "none";
|
||||
@@ -72,7 +72,7 @@ export async function getDeploymentChoice(
|
||||
export async function getDeploymentToAdd(
|
||||
frontend: Frontend[],
|
||||
existingDeployment?: WebDeploy,
|
||||
): Promise<WebDeploy> {
|
||||
) {
|
||||
if (!hasWebFrontend(frontend)) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function getCompatibleAddons(
|
||||
allAddons: Addons[],
|
||||
frontend: Frontend[],
|
||||
existingAddons: Addons[] = [],
|
||||
): Addons[] {
|
||||
) {
|
||||
return allAddons.filter((addon) => {
|
||||
if (existingAddons.includes(addon)) return false;
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import consola from "consola";
|
||||
let biome: Biome | null = null;
|
||||
let projectKey: number | null = null;
|
||||
|
||||
async function initializeBiome(): Promise<{
|
||||
biome: Biome;
|
||||
projectKey: number;
|
||||
}> {
|
||||
async function initializeBiome() {
|
||||
if (biome && projectKey !== null) return { biome, projectKey };
|
||||
|
||||
try {
|
||||
@@ -39,19 +36,18 @@ async function initializeBiome(): Promise<{
|
||||
});
|
||||
|
||||
return { biome, projectKey };
|
||||
} catch (error) {
|
||||
consola.error("Failed to initialize Biome:", error);
|
||||
throw error;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isSupportedFile(filePath: string): boolean {
|
||||
function isSupportedFile(filePath: string) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const supportedExtensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".jsonc"];
|
||||
return supportedExtensions.includes(ext);
|
||||
}
|
||||
|
||||
function shouldSkipFile(filePath: string): boolean {
|
||||
function shouldSkipFile(filePath: string) {
|
||||
const basename = path.basename(filePath);
|
||||
const skipPatterns = [
|
||||
".hbs",
|
||||
@@ -65,16 +61,16 @@ function shouldSkipFile(filePath: string): boolean {
|
||||
return skipPatterns.some((pattern) => basename.includes(pattern));
|
||||
}
|
||||
|
||||
export async function formatFileWithBiome(
|
||||
filePath: string,
|
||||
content: string,
|
||||
): Promise<string | null> {
|
||||
export async function formatFileWithBiome(filePath: string, content: string) {
|
||||
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { biome: biomeInstance, projectKey: key } = await initializeBiome();
|
||||
const biomeResult = await initializeBiome();
|
||||
if (!biomeResult) return null;
|
||||
|
||||
const { biome: biomeInstance, projectKey: key } = biomeResult;
|
||||
|
||||
const result = biomeInstance.formatContent(key, content, {
|
||||
filePath: path.basename(filePath),
|
||||
@@ -88,8 +84,7 @@ export async function formatFileWithBiome(
|
||||
}
|
||||
|
||||
return result.content;
|
||||
} catch (error) {
|
||||
consola.warn(`Failed to format ${filePath} with Biome:`, error);
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,7 @@ ${configContent}`;
|
||||
await fs.writeFile(configPath, finalContent, "utf-8");
|
||||
}
|
||||
|
||||
export async function readBtsConfig(
|
||||
projectDir: string,
|
||||
): Promise<BetterTStackConfig | null> {
|
||||
export async function readBtsConfig(projectDir: string) {
|
||||
try {
|
||||
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { validateAddonCompatibility } from "./addon-compatibility";
|
||||
import { WEB_FRAMEWORKS } from "./compatibility";
|
||||
import { exitWithError } from "./errors";
|
||||
|
||||
export function isWebFrontend(value: Frontend): boolean {
|
||||
export function isWebFrontend(value: Frontend) {
|
||||
return WEB_FRAMEWORKS.includes(value);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export function isFrontendAllowedWithBackend(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function allowedApisForFrontends(frontends: Frontend[] = []): API[] {
|
||||
export function allowedApisForFrontends(frontends: Frontend[] = []) {
|
||||
const includesNuxt = frontends.includes("nuxt");
|
||||
const includesSvelte = frontends.includes("svelte");
|
||||
const includesSolid = frontends.includes("solid");
|
||||
|
||||
@@ -14,9 +14,7 @@ import type {
|
||||
WebDeploy,
|
||||
} from "../types";
|
||||
|
||||
export function processArrayOption<T>(
|
||||
options: (T | "none")[] | undefined,
|
||||
): T[] {
|
||||
export function processArrayOption<T>(options: (T | "none")[] | undefined) {
|
||||
if (!options || options.length === 0) return [];
|
||||
if (options.includes("none" as T | "none")) return [];
|
||||
return options.filter((item): item is T => item !== "none");
|
||||
@@ -25,7 +23,7 @@ export function processArrayOption<T>(
|
||||
export function deriveProjectName(
|
||||
projectName?: string,
|
||||
projectDirectory?: string,
|
||||
): string {
|
||||
) {
|
||||
if (projectName) {
|
||||
return projectName;
|
||||
}
|
||||
@@ -35,10 +33,7 @@ export function deriveProjectName(
|
||||
return "";
|
||||
}
|
||||
|
||||
export function processFlags(
|
||||
options: CLIInput,
|
||||
projectName?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
export function processFlags(options: CLIInput, projectName?: string) {
|
||||
const config: Partial<ProjectConfig> = {};
|
||||
|
||||
if (options.api) {
|
||||
@@ -109,7 +104,7 @@ export function processFlags(
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getProvidedFlags(options: CLIInput): Set<string> {
|
||||
export function getProvidedFlags(options: CLIInput) {
|
||||
return new Set(
|
||||
Object.keys(options).filter(
|
||||
(key) => options[key as keyof CLIInput] !== undefined,
|
||||
@@ -120,7 +115,7 @@ export function getProvidedFlags(options: CLIInput): Set<string> {
|
||||
export function validateNoneExclusivity<T>(
|
||||
options: (T | "none")[] | undefined,
|
||||
optionName: string,
|
||||
): void {
|
||||
) {
|
||||
if (!options || options.length === 0) return;
|
||||
|
||||
if (options.includes("none" as T | "none") && options.length > 1) {
|
||||
@@ -128,7 +123,7 @@ export function validateNoneExclusivity<T>(
|
||||
}
|
||||
}
|
||||
|
||||
export function validateArrayOptions(options: CLIInput): void {
|
||||
export function validateArrayOptions(options: CLIInput) {
|
||||
validateNoneExclusivity(options.frontend, "frontend options");
|
||||
validateNoneExclusivity(options.addons, "addons");
|
||||
validateNoneExclusivity(options.examples, "examples");
|
||||
|
||||
@@ -20,7 +20,7 @@ import { exitWithError } from "./errors";
|
||||
export function validateDatabaseOrmAuth(
|
||||
cfg: Partial<ProjectConfig>,
|
||||
flags?: Set<string>,
|
||||
): void {
|
||||
) {
|
||||
const db = cfg.database;
|
||||
const orm = cfg.orm;
|
||||
const has = (k: string) => (flags ? flags.has(k) : true);
|
||||
@@ -91,7 +91,7 @@ export function validateDatabaseOrmAuth(
|
||||
export function validateDatabaseSetup(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
): void {
|
||||
) {
|
||||
const { dbSetup, database, runtime } = config;
|
||||
|
||||
if (
|
||||
@@ -188,7 +188,7 @@ export function validateDatabaseSetup(
|
||||
export function validateConvexConstraints(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
): void {
|
||||
) {
|
||||
const { backend } = config;
|
||||
|
||||
if (backend !== "convex") {
|
||||
@@ -243,7 +243,7 @@ export function validateConvexConstraints(
|
||||
export function validateBackendNoneConstraints(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
): void {
|
||||
) {
|
||||
const { backend } = config;
|
||||
|
||||
if (backend !== "none") {
|
||||
@@ -299,7 +299,7 @@ export function validateBackendConstraints(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
options: CLIInput,
|
||||
): void {
|
||||
) {
|
||||
const { backend } = config;
|
||||
|
||||
if (config.auth === "clerk" && backend !== "convex") {
|
||||
@@ -353,7 +353,7 @@ export function validateBackendConstraints(
|
||||
export function validateFrontendConstraints(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
): void {
|
||||
) {
|
||||
const { frontend } = config;
|
||||
|
||||
if (frontend && frontend.length > 0) {
|
||||
@@ -375,7 +375,7 @@ export function validateFrontendConstraints(
|
||||
export function validateApiConstraints(
|
||||
config: Partial<ProjectConfig>,
|
||||
options: CLIInput,
|
||||
): void {
|
||||
) {
|
||||
if (config.api === "none") {
|
||||
if (
|
||||
options.examples &&
|
||||
@@ -393,7 +393,7 @@ export function validateFullConfig(
|
||||
config: Partial<ProjectConfig>,
|
||||
providedFlags: Set<string>,
|
||||
options: CLIInput,
|
||||
): void {
|
||||
) {
|
||||
validateDatabaseOrmAuth(config, providedFlags);
|
||||
validateDatabaseSetup(config, providedFlags);
|
||||
|
||||
@@ -430,7 +430,7 @@ export function validateFullConfig(
|
||||
|
||||
export function validateConfigForProgrammaticUse(
|
||||
config: Partial<ProjectConfig>,
|
||||
): void {
|
||||
) {
|
||||
try {
|
||||
validateDatabaseOrmAuth(config);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os from "node:os";
|
||||
import { $ } from "execa";
|
||||
import pc from "picocolors";
|
||||
import type { Database } from "../types";
|
||||
import { commandExists } from "./command-exists";
|
||||
@@ -9,7 +10,6 @@ export async function isDockerInstalled() {
|
||||
|
||||
export async function isDockerRunning() {
|
||||
try {
|
||||
const { $ } = await import("execa");
|
||||
await $`docker info`;
|
||||
return true;
|
||||
} catch {
|
||||
@@ -20,7 +20,7 @@ export async function isDockerRunning() {
|
||||
export function getDockerInstallInstructions(
|
||||
platform: string,
|
||||
database: Database,
|
||||
): string {
|
||||
) {
|
||||
const isMac = platform === "darwin";
|
||||
const isWindows = platform === "win32";
|
||||
const isLinux = platform === "linux";
|
||||
@@ -50,11 +50,7 @@ export function getDockerInstallInstructions(
|
||||
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
||||
}
|
||||
|
||||
export async function getDockerStatus(database: Database): Promise<{
|
||||
installed: boolean;
|
||||
running: boolean;
|
||||
message?: string;
|
||||
}> {
|
||||
export async function getDockerStatus(database: Database) {
|
||||
const platform = os.platform();
|
||||
const installed = await isDockerInstalled();
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { cancel } from "@clack/prompts";
|
||||
import consola from "consola";
|
||||
import pc from "picocolors";
|
||||
|
||||
function isProgrammatic(): boolean {
|
||||
function isProgrammatic() {
|
||||
return process.env.BTS_PROGRAMMATIC === "1";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ProjectConfig } from "../types";
|
||||
|
||||
export function generateReproducibleCommand(config: ProjectConfig): string {
|
||||
export function generateReproducibleCommand(config: ProjectConfig) {
|
||||
const flags: string[] = [];
|
||||
|
||||
if (config.frontend && config.frontend.length > 0) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { PackageManager } from "../types";
|
||||
export function getPackageExecutionCommand(
|
||||
packageManager: PackageManager | null | undefined,
|
||||
commandWithArgs: string,
|
||||
): string {
|
||||
) {
|
||||
switch (packageManager) {
|
||||
case "pnpm":
|
||||
return `pnpm dlx ${commandWithArgs}`;
|
||||
|
||||
@@ -8,10 +8,7 @@ import { exitCancelled, handleError } from "./errors";
|
||||
export async function handleDirectoryConflict(
|
||||
currentPathInput: string,
|
||||
silent = false,
|
||||
): Promise<{
|
||||
finalPathInput: string;
|
||||
shouldClearDirectory: boolean;
|
||||
}> {
|
||||
) {
|
||||
while (true) {
|
||||
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
||||
const dirExists = await fs.pathExists(resolvedPath);
|
||||
@@ -86,7 +83,7 @@ export async function handleDirectoryConflict(
|
||||
export async function setupProjectDirectory(
|
||||
finalPathInput: string,
|
||||
shouldClearDirectory: boolean,
|
||||
): Promise<{ finalResolvedPath: string; finalBaseName: string }> {
|
||||
) {
|
||||
let finalResolvedPath: string;
|
||||
let finalBaseName: string;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from "node:path";
|
||||
import { ProjectNameSchema } from "../types";
|
||||
import { exitWithError } from "./errors";
|
||||
|
||||
export function validateProjectName(name: string): void {
|
||||
export function validateProjectName(name: string) {
|
||||
const result = ProjectNameSchema.safeParse(name);
|
||||
if (!result.success) {
|
||||
exitWithError(
|
||||
@@ -13,7 +13,7 @@ export function validateProjectName(name: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateProjectNameThrow(name: string): void {
|
||||
export function validateProjectNameThrow(name: string) {
|
||||
const result = ProjectNameSchema.safeParse(name);
|
||||
if (!result.success) {
|
||||
throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
|
||||
@@ -24,7 +24,7 @@ export function extractAndValidateProjectName(
|
||||
projectName?: string,
|
||||
projectDirectory?: string,
|
||||
throwOnError = false,
|
||||
): string {
|
||||
) {
|
||||
const derivedName =
|
||||
projectName ||
|
||||
(projectDirectory
|
||||
|
||||
@@ -41,9 +41,7 @@ type SponsorEntry = {
|
||||
export const SPONSORS_JSON_URL =
|
||||
"https://sponsors.better-t-stack.dev/sponsors.json";
|
||||
|
||||
export async function fetchSponsors(
|
||||
url: string = SPONSORS_JSON_URL,
|
||||
): Promise<SponsorEntry> {
|
||||
export async function fetchSponsors(url: string = SPONSORS_JSON_URL) {
|
||||
const s = spinner();
|
||||
s.start("Fetching sponsors…");
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
|
||||
* - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled).
|
||||
*/
|
||||
export function isTelemetryEnabled(): boolean {
|
||||
export function isTelemetryEnabled() {
|
||||
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
||||
const BTS_TELEMETRY = process.env.BTS_TELEMETRY;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export const tsProject = new Project({
|
||||
export function ensureArrayProperty(
|
||||
obj: ObjectLiteralExpression,
|
||||
name: string,
|
||||
): ArrayLiteralExpression {
|
||||
) {
|
||||
return (obj
|
||||
.getProperty(name)
|
||||
?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ??
|
||||
|
||||
@@ -48,7 +48,7 @@ export function processAndValidateFlags(
|
||||
options: CLIInput,
|
||||
providedFlags: Set<string>,
|
||||
projectName?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
) {
|
||||
if (options.yolo) {
|
||||
const cfg = processFlags(options, projectName);
|
||||
const validatedProjectName = extractAndValidateProjectName(
|
||||
@@ -89,7 +89,7 @@ export function processAndValidateFlags(
|
||||
export function processProvidedFlagsWithoutValidation(
|
||||
options: CLIInput,
|
||||
projectName?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
) {
|
||||
if (!options.yolo) {
|
||||
const providedFlags = getProvidedFlags(options);
|
||||
validateYesFlagCombination(options, providedFlags);
|
||||
|
||||
@@ -25,7 +25,7 @@ const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
|
||||
"install",
|
||||
];
|
||||
|
||||
export function generateStackSummary(stack: StackState): string {
|
||||
export function generateStackSummary(stack: StackState) {
|
||||
const selectedTechs = CATEGORY_ORDER.flatMap((category) => {
|
||||
const options = TECH_OPTIONS[category];
|
||||
const selectedValue = stack[category as keyof StackState];
|
||||
@@ -51,7 +51,7 @@ export function generateStackSummary(stack: StackState): string {
|
||||
return selectedTechs.length > 0 ? selectedTechs.join(" • ") : "Custom stack";
|
||||
}
|
||||
|
||||
export function generateStackCommand(stack: StackState): string {
|
||||
export function generateStackCommand(stack: StackState) {
|
||||
const packageManagerCommands = {
|
||||
npm: "npx create-better-t-stack@latest",
|
||||
pnpm: "pnpm create better-t-stack@latest",
|
||||
@@ -126,10 +126,7 @@ export function generateStackCommand(stack: StackState): string {
|
||||
return `${base} ${projectName} ${flags.join(" ")}`;
|
||||
}
|
||||
|
||||
export function generateStackUrlFromState(
|
||||
stack: StackState,
|
||||
baseUrl?: string,
|
||||
): string {
|
||||
export function generateStackUrlFromState(stack: StackState, baseUrl?: string) {
|
||||
const origin = baseUrl || "https://better-t-stack.dev";
|
||||
|
||||
const stackParams = new URLSearchParams();
|
||||
@@ -147,10 +144,7 @@ export function generateStackUrlFromState(
|
||||
return `${origin}/new${searchString ? `?${searchString}` : ""}`;
|
||||
}
|
||||
|
||||
export function generateStackSharingUrl(
|
||||
stack: StackState,
|
||||
baseUrl?: string,
|
||||
): string {
|
||||
export function generateStackSharingUrl(stack: StackState, baseUrl?: string) {
|
||||
const origin = baseUrl || "https://better-t-stack.dev";
|
||||
|
||||
const stackParams = new URLSearchParams();
|
||||
|
||||
Reference in New Issue
Block a user