chore(cli): remove all explicit return types (#573)

This commit is contained in:
Aman Varshney
2025-09-11 10:00:59 +05:30
committed by GitHub
parent 2b97093a1d
commit 0bfb3cfda0
45 changed files with 113 additions and 157 deletions

View File

@@ -7,3 +7,4 @@ alwaysApply: true
- Do not include emojis. - Do not include emojis.
- Use TypeScript type aliases instead of interface declarations. - 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. - 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

View File

@@ -1,6 +1,5 @@
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import type { Addons, Frontend, ProjectConfig } from "./types";
import { getUserPkgManager } from "./utils/get-package-manager"; import { getUserPkgManager } from "./utils/get-package-manager";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -26,7 +25,7 @@ export const DEFAULT_CONFIG_BASE = {
serverDeploy: "none", serverDeploy: "none",
} as const; } as const;
export function getDefaultConfig(): ProjectConfig { export function getDefaultConfig() {
return { return {
...DEFAULT_CONFIG_BASE, ...DEFAULT_CONFIG_BASE,
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName), projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
@@ -160,7 +159,7 @@ export const dependencyVersionMap = {
export type AvailableDependencies = keyof typeof 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"], pwa: ["tanstack-router", "react-router", "solid", "next"],
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"], tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"],
biome: [], biome: [],

View File

@@ -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 ( if (
frontends.some((f) => frontends.some((f) =>
["react-router", "tanstack-router", "nuxt", "svelte", "solid"].includes( ["react-router", "tanstack-router", "nuxt", "svelte", "solid"].includes(

View File

@@ -115,7 +115,7 @@ export async function setupAuth(config: ProjectConfig) {
} }
} }
export function generateAuthSecret(length = 32): string { export function generateAuthSecret(length = 32) {
const characters = const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = ""; let result = "";

View File

@@ -13,7 +13,6 @@ import type {
AddInput, AddInput,
CreateInput, CreateInput,
DirectoryConflict, DirectoryConflict,
InitResult,
ProjectConfig, ProjectConfig,
} from "../../types"; } from "../../types";
import { trackProjectCreation } from "../../utils/analytics"; import { trackProjectCreation } from "../../utils/analytics";
@@ -40,7 +39,7 @@ import { installDependencies } from "./install-dependencies";
export async function createProjectHandler( export async function createProjectHandler(
input: CreateInput & { projectName?: string }, input: CreateInput & { projectName?: string },
): Promise<InitResult> { ) {
const startTime = Date.now(); const startTime = Date.now();
const timeScaffolded = new Date().toISOString(); const timeScaffolded = new Date().toISOString();
@@ -58,7 +57,7 @@ export async function createProjectHandler(
currentPathInput = input.projectName; currentPathInput = input.projectName;
} else if (input.yes) { } else if (input.yes) {
const defaultConfig = getDefaultConfig(); const defaultConfig = getDefaultConfig();
let defaultName = defaultConfig.relativePath; let defaultName: string = defaultConfig.relativePath;
let counter = 1; let counter = 1;
while ( while (
(await fs.pathExists(path.resolve(process.cwd(), defaultName))) && (await fs.pathExists(path.resolve(process.cwd(), defaultName))) &&
@@ -210,7 +209,7 @@ export async function createProjectHandler(
async function handleDirectoryConflictProgrammatically( async function handleDirectoryConflictProgrammatically(
currentPathInput: string, currentPathInput: string,
strategy: DirectoryConflict, strategy: DirectoryConflict,
): Promise<{ finalPathInput: string; shouldClearDirectory: boolean }> { ) {
const currentPath = path.resolve(process.cwd(), currentPathInput); const currentPath = path.resolve(process.cwd(), currentPathInput);
if (!(await fs.pathExists(currentPath))) { if (!(await fs.pathExists(currentPath))) {

View File

@@ -24,7 +24,7 @@ export async function createReadme(projectDir: string, options: ProjectConfig) {
} }
} }
function generateReadmeContent(options: ProjectConfig): string { function generateReadmeContent(options: ProjectConfig) {
const { const {
projectName, projectName,
packageManager, packageManager,
@@ -163,7 +163,7 @@ function generateStackDescription(
backend: string, backend: string,
api: API, api: API,
isConvex: boolean, isConvex: boolean,
): string { ) {
const parts: string[] = []; const parts: string[] = [];
const hasTanstackRouter = frontend.includes("tanstack-router"); const hasTanstackRouter = frontend.includes("tanstack-router");
@@ -210,7 +210,7 @@ function generateRunningInstructions(
webPort: string, webPort: string,
hasNative: boolean, hasNative: boolean,
isConvex: boolean, isConvex: boolean,
): string { ) {
const instructions: string[] = []; const instructions: string[] = [];
const hasFrontendNone = frontend.length === 0 || frontend.includes("none"); const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
@@ -265,7 +265,7 @@ function generateProjectStructure(
isConvex: boolean, isConvex: boolean,
api: API, api: API,
auth: Auth, auth: Auth,
): string { ) {
const structure: string[] = [`${projectName}/`, "├── apps/"]; const structure: string[] = [`${projectName}/`, "├── apps/"];
const hasFrontendNone = frontend.length === 0 || frontend.includes("none"); const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
@@ -349,7 +349,7 @@ function generateFeaturesList(
frontend: Frontend[], frontend: Frontend[],
backend: string, backend: string,
api: API, api: API,
): string { ) {
const isConvex = backend === "convex"; const isConvex = backend === "convex";
const isBackendNone = backend === "none"; const isBackendNone = backend === "none";
const hasTanstackRouter = frontend.includes("tanstack-router"); const hasTanstackRouter = frontend.includes("tanstack-router");
@@ -493,7 +493,7 @@ function generateDatabaseSetup(
orm: ORM, orm: ORM,
dbSetup: DatabaseSetup, dbSetup: DatabaseSetup,
serverDeploy?: string, serverDeploy?: string,
): string { ) {
if (database === "none") { if (database === "none") {
return ""; return "";
} }
@@ -591,7 +591,7 @@ function generateScriptsList(
hasNative: boolean, hasNative: boolean,
addons: Addons[], addons: Addons[],
backend: string, backend: string,
): string { ) {
const isConvex = backend === "convex"; const isConvex = backend === "convex";
const isBackendNone = backend === "none"; const isBackendNone = backend === "none";
@@ -657,7 +657,7 @@ function generateDeploymentCommands(
packageManagerRunCmd: string, packageManagerRunCmd: string,
webDeploy?: string, webDeploy?: string,
serverDeploy?: string, serverDeploy?: string,
): string { ) {
const lines: string[] = []; const lines: string[] = [];
if (webDeploy === "alchemy" || serverDeploy === "alchemy") { if (webDeploy === "alchemy" || serverDeploy === "alchemy") {

View File

@@ -1,11 +1,8 @@
import path from "node:path"; import path from "node:path";
import fs from "fs-extra"; import fs from "fs-extra";
import type { ProjectConfig } from "../../types";
import { readBtsConfig } from "../../utils/bts-config"; import { readBtsConfig } from "../../utils/bts-config";
export async function detectProjectConfig( export async function detectProjectConfig(projectDir: string) {
projectDir: string,
): Promise<Partial<ProjectConfig> | null> {
try { try {
const btsConfig = await readBtsConfig(projectDir); const btsConfig = await readBtsConfig(projectDir);
if (btsConfig) { if (btsConfig) {

View File

@@ -203,7 +203,7 @@ export async function displayPostInstallInstructions(
consola.box(output); consola.box(output);
} }
function getNativeInstructions(isConvex: boolean): string { function getNativeInstructions(isConvex: boolean) {
const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL"; const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
const exampleUrl = isConvex const exampleUrl = isConvex
? "https://<YOUR_CONVEX_URL>" ? "https://<YOUR_CONVEX_URL>"
@@ -226,7 +226,7 @@ function getNativeInstructions(isConvex: boolean): string {
return instructions; return instructions;
} }
function getLintingInstructions(runCmd?: string): string { function getLintingInstructions(runCmd?: string) {
return `${pc.bold("Linting and formatting:")}\n${pc.cyan( return `${pc.bold("Linting and formatting:")}\n${pc.cyan(
"•", "•",
)} Format and lint fix: ${`${runCmd} check`}\n`; )} Format and lint fix: ${`${runCmd} check`}\n`;
@@ -239,7 +239,7 @@ async function getDatabaseInstructions(
runtime?: Runtime, runtime?: Runtime,
dbSetup?: DatabaseSetup, dbSetup?: DatabaseSetup,
serverDeploy?: string, serverDeploy?: string,
): Promise<string> { ) {
const instructions: string[] = []; const instructions: string[] = [];
if (dbSetup === "docker") { 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( return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan(
"•", "•",
)} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\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/"}`; )} 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( return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
"NOTE:", "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`; )} 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( return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan(
"•", "•",
)} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\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`}`; )} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
} }
function getNoOrmWarning(): string { function getNoOrmWarning() {
return `\n${pc.yellow( return `\n${pc.yellow(
"WARNING:", "WARNING:",
)} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`; )} 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( return `\n${pc.yellow(
"WARNING:", "WARNING:",
)} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`; )} '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, runCmd?: string,
webDeploy?: string, webDeploy?: string,
serverDeploy?: string, serverDeploy?: string,
): string { ) {
const instructions: string[] = []; const instructions: string[] = [];
if (webDeploy === "wrangler") { if (webDeploy === "wrangler") {
@@ -441,7 +441,7 @@ function getWranglerDeployInstructions(
return instructions.length ? `\n${instructions.join("\n")}` : ""; 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`; 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, runCmd?: string,
webDeploy?: string, webDeploy?: string,
serverDeploy?: string, serverDeploy?: string,
): string { ) {
const instructions: string[] = []; const instructions: string[] = [];
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") { if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {

View File

@@ -34,7 +34,7 @@ async function writeEnvFile(
await addEnvVariablesToFile(envPath, variables); await addEnvVariablesToFile(envPath, variables);
} }
function getDatabaseUrl(database: Database, projectName: string): string { function getDatabaseUrl(database: Database, projectName: string) {
switch (database) { switch (database) {
case "postgres": case "postgres":
return `postgresql://postgres:password@localhost:5432/${projectName}`; return `postgresql://postgres:password@localhost:5432/${projectName}`;

View File

@@ -31,9 +31,7 @@ async function checkAtlasCLI() {
} }
} }
async function initMongoDBAtlas( async function initMongoDBAtlas(serverDir: string) {
serverDir: string,
): Promise<MongoDBConfig | null> {
try { try {
const hasAtlas = await checkAtlasCLI(); const hasAtlas = await checkAtlasCLI();

View File

@@ -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 dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
const url = dbUrlMatch?.[1]; const url = dbUrlMatch?.[1];
if (url) { if (url) {

View File

@@ -14,13 +14,6 @@ type TursoConfig = {
authToken: string; authToken: string;
}; };
type TursoGroup = {
name: string;
locations: string;
version: string;
status: string;
};
async function isTursoInstalled() { async function isTursoInstalled() {
return commandExists("turso"); return commandExists("turso");
} }
@@ -71,7 +64,7 @@ async function installTursoCLI(isMac: boolean) {
} }
} }
async function getTursoGroups(): Promise<TursoGroup[]> { async function getTursoGroups() {
const s = spinner(); const s = spinner();
try { try {
s.start("Fetching Turso groups..."); 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(); const groups = await getTursoGroups();
if (groups.length === 0) { if (groups.length === 0) {

View File

@@ -76,7 +76,7 @@ export async function setupSvelteAlchemyDeploy(
} }
} }
function updateAdapterInConfig(configObject: Node): void { function updateAdapterInConfig(configObject: Node) {
if (!Node.isObjectLiteralExpression(configObject)) return; if (!Node.isObjectLiteralExpression(configObject)) return;
const kitProperty = configObject.getProperty("kit"); const kitProperty = configObject.getProperty("kit");

View File

@@ -218,10 +218,7 @@ export function createBtsCli() {
* } * }
* ``` * ```
*/ */
export async function init( export async function init(projectName?: string, options?: CreateInput) {
projectName?: string,
options?: CreateInput,
): Promise<InitResult> {
const opts = (options ?? {}) as CreateInput; const opts = (options ?? {}) as CreateInput;
const programmaticOpts = { ...opts, verbose: true }; const programmaticOpts = { ...opts, verbose: true };
const prev = process.env.BTS_PROGRAMMATIC; const prev = process.env.BTS_PROGRAMMATIC;

View File

@@ -75,7 +75,7 @@ const ADDON_GROUPS = {
export async function getAddonsChoice( export async function getAddonsChoice(
addons?: Addons[], addons?: Addons[],
frontends?: Frontend[], frontends?: Frontend[],
): Promise<Addons[]> { ) {
if (addons !== undefined) return addons; if (addons !== undefined) return addons;
const allAddons = AddonsSchema.options.filter((addon) => addon !== "none"); const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
@@ -131,7 +131,7 @@ export async function getAddonsChoice(
export async function getAddonsToAdd( export async function getAddonsToAdd(
frontend: Frontend[], frontend: Frontend[],
existingAddons: Addons[] = [], existingAddons: Addons[] = [],
): Promise<Addons[]> { ) {
const groupedOptions: Record<string, AddonOption[]> = { const groupedOptions: Record<string, AddonOption[]> = {
Documentation: [], Documentation: [],
Linting: [], Linting: [],

View File

@@ -7,7 +7,7 @@ export async function getApiChoice(
Api?: API | undefined, Api?: API | undefined,
frontend?: Frontend[], frontend?: Frontend[],
backend?: Backend, backend?: Backend,
): Promise<API> { ) {
if (backend === "convex" || backend === "none") { if (backend === "convex" || backend === "none") {
return "none"; return "none";
} }

View File

@@ -6,7 +6,7 @@ import { exitCancelled } from "../utils/errors";
export async function getBackendFrameworkChoice( export async function getBackendFrameworkChoice(
backendFramework?: Backend, backendFramework?: Backend,
frontends?: Frontend[], frontends?: Frontend[],
): Promise<Backend> { ) {
if (backendFramework !== undefined) return backendFramework; if (backendFramework !== undefined) return backendFramework;
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid"); const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");

View File

@@ -55,7 +55,7 @@ export async function gatherConfig(
projectName: string, projectName: string,
projectDir: string, projectDir: string,
relativePath: string, relativePath: string,
): Promise<ProjectConfig> { ) {
const result = await group<PromptGroupResults>( const result = await group<PromptGroupResults>(
{ {
frontend: () => frontend: () =>
@@ -75,10 +75,14 @@ export async function gatherConfig(
results.runtime, results.runtime,
), ),
api: ({ results }) => api: ({ results }) =>
getApiChoice(flags.api, results.frontend, results.backend), getApiChoice(
flags.api,
results.frontend,
results.backend,
) as Promise<API>,
auth: ({ results }) => auth: ({ results }) =>
getAuthChoice( getAuthChoice(
flags.auth as import("../types").Auth | undefined, flags.auth,
results.database !== "none", results.database !== "none",
results.backend, results.backend,
results.frontend, results.frontend,
@@ -91,7 +95,7 @@ export async function gatherConfig(
results.frontend, results.frontend,
results.backend, results.backend,
results.api, results.api,
), ) as Promise<Examples[]>,
dbSetup: ({ results }) => dbSetup: ({ results }) =>
getDBSetupChoice( getDBSetupChoice(
results.database ?? "none", results.database ?? "none",

View File

@@ -8,7 +8,7 @@ export async function getDBSetupChoice(
_orm?: ORM, _orm?: ORM,
backend?: Backend, backend?: Backend,
runtime?: Runtime, runtime?: Runtime,
): Promise<DatabaseSetup> { ) {
if (backend === "convex") { if (backend === "convex") {
return "none"; return "none";
} }

View File

@@ -7,7 +7,7 @@ export async function getDatabaseChoice(
database?: Database, database?: Database,
backend?: Backend, backend?: Backend,
runtime?: Runtime, runtime?: Runtime,
): Promise<Database> { ) {
if (backend === "convex" || backend === "none") { if (backend === "convex" || backend === "none") {
return "none"; return "none";
} }

View File

@@ -13,7 +13,7 @@ export async function getExamplesChoice(
frontends?: Frontend[], frontends?: Frontend[],
backend?: Backend, backend?: Backend,
api?: API, api?: API,
): Promise<Examples[]> { ) {
if (examples !== undefined) return examples; if (examples !== undefined) return examples;
if (api === "none") { if (api === "none") {

View File

@@ -8,7 +8,7 @@ export async function getFrontendChoice(
frontendOptions?: Frontend[], frontendOptions?: Frontend[],
backend?: Backend, backend?: Backend,
auth?: string, auth?: string,
): Promise<Frontend[]> { ) {
if (frontendOptions !== undefined) return frontendOptions; if (frontendOptions !== undefined) return frontendOptions;
const frontendTypes = await multiselect({ const frontendTypes = await multiselect({

View File

@@ -27,7 +27,7 @@ export async function getORMChoice(
database?: Database, database?: Database,
backend?: Backend, backend?: Backend,
runtime?: Runtime, runtime?: Runtime,
): Promise<ORM> { ) {
if (backend === "convex") { if (backend === "convex") {
return "none"; return "none";
} }

View File

@@ -3,9 +3,7 @@ import type { PackageManager } from "../types";
import { exitCancelled } from "../utils/errors"; import { exitCancelled } from "../utils/errors";
import { getUserPkgManager } from "../utils/get-package-manager"; import { getUserPkgManager } from "../utils/get-package-manager";
export async function getPackageManagerChoice( export async function getPackageManagerChoice(packageManager?: PackageManager) {
packageManager?: PackageManager,
): Promise<PackageManager> {
if (packageManager !== undefined) return packageManager; if (packageManager !== undefined) return packageManager;
const detectedPackageManager = getUserPkgManager(); const detectedPackageManager = getUserPkgManager();

View File

@@ -7,13 +7,13 @@ import { DEFAULT_CONFIG } from "../constants";
import { ProjectNameSchema } from "../types"; import { ProjectNameSchema } from "../types";
import { exitCancelled } from "../utils/errors"; import { exitCancelled } from "../utils/errors";
function isPathWithinCwd(targetPath: string): boolean { function isPathWithinCwd(targetPath: string) {
const resolved = path.resolve(targetPath); const resolved = path.resolve(targetPath);
const rel = path.relative(process.cwd(), resolved); const rel = path.relative(process.cwd(), resolved);
return !rel.startsWith("..") && !path.isAbsolute(rel); return !rel.startsWith("..") && !path.isAbsolute(rel);
} }
function validateDirectoryName(name: string): string | undefined { function validateDirectoryName(name: string) {
if (name === ".") return undefined; if (name === ".") return undefined;
const result = ProjectNameSchema.safeParse(name); const result = ProjectNameSchema.safeParse(name);
@@ -23,7 +23,7 @@ function validateDirectoryName(name: string): string | undefined {
return undefined; return undefined;
} }
export async function getProjectName(initialName?: string): Promise<string> { export async function getProjectName(initialName?: string) {
if (initialName) { if (initialName) {
if (initialName === ".") { if (initialName === ".") {
return initialName; return initialName;
@@ -41,7 +41,7 @@ export async function getProjectName(initialName?: string): Promise<string> {
let isValid = false; let isValid = false;
let projectPath = ""; let projectPath = "";
let defaultName = DEFAULT_CONFIG.projectName; let defaultName: string = DEFAULT_CONFIG.projectName;
let counter = 1; let counter = 1;
while ( while (

View File

@@ -3,10 +3,7 @@ import { DEFAULT_CONFIG } from "../constants";
import type { Backend, Runtime } from "../types"; import type { Backend, Runtime } from "../types";
import { exitCancelled } from "../utils/errors"; import { exitCancelled } from "../utils/errors";
export async function getRuntimeChoice( export async function getRuntimeChoice(runtime?: Runtime, backend?: Backend) {
runtime?: Runtime,
backend?: Backend,
): Promise<Runtime> {
if (backend === "convex" || backend === "none") { if (backend === "convex" || backend === "none") {
return "none"; return "none";
} }

View File

@@ -36,7 +36,7 @@ export async function getServerDeploymentChoice(
runtime?: Runtime, runtime?: Runtime,
backend?: Backend, backend?: Backend,
webDeploy?: WebDeploy, webDeploy?: WebDeploy,
): Promise<ServerDeploy> { ) {
if (deployment !== undefined) return deployment; if (deployment !== undefined) return deployment;
if (backend === "none" || backend === "convex") { if (backend === "none" || backend === "convex") {
@@ -82,7 +82,7 @@ export async function getServerDeploymentToAdd(
runtime?: Runtime, runtime?: Runtime,
existingDeployment?: ServerDeploy, existingDeployment?: ServerDeploy,
backend?: Backend, backend?: Backend,
): Promise<ServerDeploy> { ) {
if (backend !== "hono") { if (backend !== "hono") {
return "none"; return "none";
} }

View File

@@ -4,7 +4,7 @@ import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
import { WEB_FRAMEWORKS } from "../utils/compatibility"; import { WEB_FRAMEWORKS } from "../utils/compatibility";
import { exitCancelled } from "../utils/errors"; import { exitCancelled } from "../utils/errors";
function hasWebFrontend(frontends: Frontend[]): boolean { function hasWebFrontend(frontends: Frontend[]) {
return frontends.some((f) => WEB_FRAMEWORKS.includes(f)); return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
} }
@@ -41,7 +41,7 @@ export async function getDeploymentChoice(
_runtime?: Runtime, _runtime?: Runtime,
_backend?: Backend, _backend?: Backend,
frontend: Frontend[] = [], frontend: Frontend[] = [],
): Promise<WebDeploy> { ) {
if (deployment !== undefined) return deployment; if (deployment !== undefined) return deployment;
if (!hasWebFrontend(frontend)) { if (!hasWebFrontend(frontend)) {
return "none"; return "none";
@@ -72,7 +72,7 @@ export async function getDeploymentChoice(
export async function getDeploymentToAdd( export async function getDeploymentToAdd(
frontend: Frontend[], frontend: Frontend[],
existingDeployment?: WebDeploy, existingDeployment?: WebDeploy,
): Promise<WebDeploy> { ) {
if (!hasWebFrontend(frontend)) { if (!hasWebFrontend(frontend)) {
return "none"; return "none";
} }

View File

@@ -30,7 +30,7 @@ export function getCompatibleAddons(
allAddons: Addons[], allAddons: Addons[],
frontend: Frontend[], frontend: Frontend[],
existingAddons: Addons[] = [], existingAddons: Addons[] = [],
): Addons[] { ) {
return allAddons.filter((addon) => { return allAddons.filter((addon) => {
if (existingAddons.includes(addon)) return false; if (existingAddons.includes(addon)) return false;

View File

@@ -5,10 +5,7 @@ import consola from "consola";
let biome: Biome | null = null; let biome: Biome | null = null;
let projectKey: number | null = null; let projectKey: number | null = null;
async function initializeBiome(): Promise<{ async function initializeBiome() {
biome: Biome;
projectKey: number;
}> {
if (biome && projectKey !== null) return { biome, projectKey }; if (biome && projectKey !== null) return { biome, projectKey };
try { try {
@@ -39,19 +36,18 @@ async function initializeBiome(): Promise<{
}); });
return { biome, projectKey }; return { biome, projectKey };
} catch (error) { } catch (_error) {
consola.error("Failed to initialize Biome:", error); return null;
throw error;
} }
} }
function isSupportedFile(filePath: string): boolean { function isSupportedFile(filePath: string) {
const ext = path.extname(filePath).toLowerCase(); const ext = path.extname(filePath).toLowerCase();
const supportedExtensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".jsonc"]; const supportedExtensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".jsonc"];
return supportedExtensions.includes(ext); return supportedExtensions.includes(ext);
} }
function shouldSkipFile(filePath: string): boolean { function shouldSkipFile(filePath: string) {
const basename = path.basename(filePath); const basename = path.basename(filePath);
const skipPatterns = [ const skipPatterns = [
".hbs", ".hbs",
@@ -65,16 +61,16 @@ function shouldSkipFile(filePath: string): boolean {
return skipPatterns.some((pattern) => basename.includes(pattern)); return skipPatterns.some((pattern) => basename.includes(pattern));
} }
export async function formatFileWithBiome( export async function formatFileWithBiome(filePath: string, content: string) {
filePath: string,
content: string,
): Promise<string | null> {
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) { if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) {
return null; return null;
} }
try { 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, { const result = biomeInstance.formatContent(key, content, {
filePath: path.basename(filePath), filePath: path.basename(filePath),
@@ -88,8 +84,7 @@ export async function formatFileWithBiome(
} }
return result.content; return result.content;
} catch (error) { } catch (_error) {
consola.warn(`Failed to format ${filePath} with Biome:`, error);
return null; return null;
} }
} }

View File

@@ -62,9 +62,7 @@ ${configContent}`;
await fs.writeFile(configPath, finalContent, "utf-8"); await fs.writeFile(configPath, finalContent, "utf-8");
} }
export async function readBtsConfig( export async function readBtsConfig(projectDir: string) {
projectDir: string,
): Promise<BetterTStackConfig | null> {
try { try {
const configPath = path.join(projectDir, BTS_CONFIG_FILE); const configPath = path.join(projectDir, BTS_CONFIG_FILE);

View File

@@ -12,7 +12,7 @@ import { validateAddonCompatibility } from "./addon-compatibility";
import { WEB_FRAMEWORKS } from "./compatibility"; import { WEB_FRAMEWORKS } from "./compatibility";
import { exitWithError } from "./errors"; import { exitWithError } from "./errors";
export function isWebFrontend(value: Frontend): boolean { export function isWebFrontend(value: Frontend) {
return WEB_FRAMEWORKS.includes(value); return WEB_FRAMEWORKS.includes(value);
} }
@@ -138,7 +138,7 @@ export function isFrontendAllowedWithBackend(
return true; return true;
} }
export function allowedApisForFrontends(frontends: Frontend[] = []): API[] { export function allowedApisForFrontends(frontends: Frontend[] = []) {
const includesNuxt = frontends.includes("nuxt"); const includesNuxt = frontends.includes("nuxt");
const includesSvelte = frontends.includes("svelte"); const includesSvelte = frontends.includes("svelte");
const includesSolid = frontends.includes("solid"); const includesSolid = frontends.includes("solid");

View File

@@ -14,9 +14,7 @@ import type {
WebDeploy, WebDeploy,
} from "../types"; } from "../types";
export function processArrayOption<T>( export function processArrayOption<T>(options: (T | "none")[] | undefined) {
options: (T | "none")[] | undefined,
): T[] {
if (!options || options.length === 0) return []; if (!options || options.length === 0) return [];
if (options.includes("none" as T | "none")) return []; if (options.includes("none" as T | "none")) return [];
return options.filter((item): item is T => item !== "none"); return options.filter((item): item is T => item !== "none");
@@ -25,7 +23,7 @@ export function processArrayOption<T>(
export function deriveProjectName( export function deriveProjectName(
projectName?: string, projectName?: string,
projectDirectory?: string, projectDirectory?: string,
): string { ) {
if (projectName) { if (projectName) {
return projectName; return projectName;
} }
@@ -35,10 +33,7 @@ export function deriveProjectName(
return ""; return "";
} }
export function processFlags( export function processFlags(options: CLIInput, projectName?: string) {
options: CLIInput,
projectName?: string,
): Partial<ProjectConfig> {
const config: Partial<ProjectConfig> = {}; const config: Partial<ProjectConfig> = {};
if (options.api) { if (options.api) {
@@ -109,7 +104,7 @@ export function processFlags(
return config; return config;
} }
export function getProvidedFlags(options: CLIInput): Set<string> { export function getProvidedFlags(options: CLIInput) {
return new Set( return new Set(
Object.keys(options).filter( Object.keys(options).filter(
(key) => options[key as keyof CLIInput] !== undefined, (key) => options[key as keyof CLIInput] !== undefined,
@@ -120,7 +115,7 @@ export function getProvidedFlags(options: CLIInput): Set<string> {
export function validateNoneExclusivity<T>( export function validateNoneExclusivity<T>(
options: (T | "none")[] | undefined, options: (T | "none")[] | undefined,
optionName: string, optionName: string,
): void { ) {
if (!options || options.length === 0) return; if (!options || options.length === 0) return;
if (options.includes("none" as T | "none") && options.length > 1) { 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.frontend, "frontend options");
validateNoneExclusivity(options.addons, "addons"); validateNoneExclusivity(options.addons, "addons");
validateNoneExclusivity(options.examples, "examples"); validateNoneExclusivity(options.examples, "examples");

View File

@@ -20,7 +20,7 @@ import { exitWithError } from "./errors";
export function validateDatabaseOrmAuth( export function validateDatabaseOrmAuth(
cfg: Partial<ProjectConfig>, cfg: Partial<ProjectConfig>,
flags?: Set<string>, flags?: Set<string>,
): void { ) {
const db = cfg.database; const db = cfg.database;
const orm = cfg.orm; const orm = cfg.orm;
const has = (k: string) => (flags ? flags.has(k) : true); const has = (k: string) => (flags ? flags.has(k) : true);
@@ -91,7 +91,7 @@ export function validateDatabaseOrmAuth(
export function validateDatabaseSetup( export function validateDatabaseSetup(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
): void { ) {
const { dbSetup, database, runtime } = config; const { dbSetup, database, runtime } = config;
if ( if (
@@ -188,7 +188,7 @@ export function validateDatabaseSetup(
export function validateConvexConstraints( export function validateConvexConstraints(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
): void { ) {
const { backend } = config; const { backend } = config;
if (backend !== "convex") { if (backend !== "convex") {
@@ -243,7 +243,7 @@ export function validateConvexConstraints(
export function validateBackendNoneConstraints( export function validateBackendNoneConstraints(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
): void { ) {
const { backend } = config; const { backend } = config;
if (backend !== "none") { if (backend !== "none") {
@@ -299,7 +299,7 @@ export function validateBackendConstraints(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
options: CLIInput, options: CLIInput,
): void { ) {
const { backend } = config; const { backend } = config;
if (config.auth === "clerk" && backend !== "convex") { if (config.auth === "clerk" && backend !== "convex") {
@@ -353,7 +353,7 @@ export function validateBackendConstraints(
export function validateFrontendConstraints( export function validateFrontendConstraints(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
): void { ) {
const { frontend } = config; const { frontend } = config;
if (frontend && frontend.length > 0) { if (frontend && frontend.length > 0) {
@@ -375,7 +375,7 @@ export function validateFrontendConstraints(
export function validateApiConstraints( export function validateApiConstraints(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
options: CLIInput, options: CLIInput,
): void { ) {
if (config.api === "none") { if (config.api === "none") {
if ( if (
options.examples && options.examples &&
@@ -393,7 +393,7 @@ export function validateFullConfig(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
providedFlags: Set<string>, providedFlags: Set<string>,
options: CLIInput, options: CLIInput,
): void { ) {
validateDatabaseOrmAuth(config, providedFlags); validateDatabaseOrmAuth(config, providedFlags);
validateDatabaseSetup(config, providedFlags); validateDatabaseSetup(config, providedFlags);
@@ -430,7 +430,7 @@ export function validateFullConfig(
export function validateConfigForProgrammaticUse( export function validateConfigForProgrammaticUse(
config: Partial<ProjectConfig>, config: Partial<ProjectConfig>,
): void { ) {
try { try {
validateDatabaseOrmAuth(config); validateDatabaseOrmAuth(config);

View File

@@ -1,4 +1,5 @@
import os from "node:os"; import os from "node:os";
import { $ } from "execa";
import pc from "picocolors"; import pc from "picocolors";
import type { Database } from "../types"; import type { Database } from "../types";
import { commandExists } from "./command-exists"; import { commandExists } from "./command-exists";
@@ -9,7 +10,6 @@ export async function isDockerInstalled() {
export async function isDockerRunning() { export async function isDockerRunning() {
try { try {
const { $ } = await import("execa");
await $`docker info`; await $`docker info`;
return true; return true;
} catch { } catch {
@@ -20,7 +20,7 @@ export async function isDockerRunning() {
export function getDockerInstallInstructions( export function getDockerInstallInstructions(
platform: string, platform: string,
database: Database, database: Database,
): string { ) {
const isMac = platform === "darwin"; const isMac = platform === "darwin";
const isWindows = platform === "win32"; const isWindows = platform === "win32";
const isLinux = platform === "linux"; 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)}`; return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
} }
export async function getDockerStatus(database: Database): Promise<{ export async function getDockerStatus(database: Database) {
installed: boolean;
running: boolean;
message?: string;
}> {
const platform = os.platform(); const platform = os.platform();
const installed = await isDockerInstalled(); const installed = await isDockerInstalled();

View File

@@ -2,7 +2,7 @@ import { cancel } from "@clack/prompts";
import consola from "consola"; import consola from "consola";
import pc from "picocolors"; import pc from "picocolors";
function isProgrammatic(): boolean { function isProgrammatic() {
return process.env.BTS_PROGRAMMATIC === "1"; return process.env.BTS_PROGRAMMATIC === "1";
} }

View File

@@ -1,6 +1,6 @@
import type { ProjectConfig } from "../types"; import type { ProjectConfig } from "../types";
export function generateReproducibleCommand(config: ProjectConfig): string { export function generateReproducibleCommand(config: ProjectConfig) {
const flags: string[] = []; const flags: string[] = [];
if (config.frontend && config.frontend.length > 0) { if (config.frontend && config.frontend.length > 0) {

View File

@@ -11,7 +11,7 @@ import type { PackageManager } from "../types";
export function getPackageExecutionCommand( export function getPackageExecutionCommand(
packageManager: PackageManager | null | undefined, packageManager: PackageManager | null | undefined,
commandWithArgs: string, commandWithArgs: string,
): string { ) {
switch (packageManager) { switch (packageManager) {
case "pnpm": case "pnpm":
return `pnpm dlx ${commandWithArgs}`; return `pnpm dlx ${commandWithArgs}`;

View File

@@ -8,10 +8,7 @@ import { exitCancelled, handleError } from "./errors";
export async function handleDirectoryConflict( export async function handleDirectoryConflict(
currentPathInput: string, currentPathInput: string,
silent = false, silent = false,
): Promise<{ ) {
finalPathInput: string;
shouldClearDirectory: boolean;
}> {
while (true) { while (true) {
const resolvedPath = path.resolve(process.cwd(), currentPathInput); const resolvedPath = path.resolve(process.cwd(), currentPathInput);
const dirExists = await fs.pathExists(resolvedPath); const dirExists = await fs.pathExists(resolvedPath);
@@ -86,7 +83,7 @@ export async function handleDirectoryConflict(
export async function setupProjectDirectory( export async function setupProjectDirectory(
finalPathInput: string, finalPathInput: string,
shouldClearDirectory: boolean, shouldClearDirectory: boolean,
): Promise<{ finalResolvedPath: string; finalBaseName: string }> { ) {
let finalResolvedPath: string; let finalResolvedPath: string;
let finalBaseName: string; let finalBaseName: string;

View File

@@ -2,7 +2,7 @@ import path from "node:path";
import { ProjectNameSchema } from "../types"; import { ProjectNameSchema } from "../types";
import { exitWithError } from "./errors"; import { exitWithError } from "./errors";
export function validateProjectName(name: string): void { export function validateProjectName(name: string) {
const result = ProjectNameSchema.safeParse(name); const result = ProjectNameSchema.safeParse(name);
if (!result.success) { if (!result.success) {
exitWithError( 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); const result = ProjectNameSchema.safeParse(name);
if (!result.success) { if (!result.success) {
throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`); throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
@@ -24,7 +24,7 @@ export function extractAndValidateProjectName(
projectName?: string, projectName?: string,
projectDirectory?: string, projectDirectory?: string,
throwOnError = false, throwOnError = false,
): string { ) {
const derivedName = const derivedName =
projectName || projectName ||
(projectDirectory (projectDirectory

View File

@@ -41,9 +41,7 @@ type SponsorEntry = {
export const SPONSORS_JSON_URL = export const SPONSORS_JSON_URL =
"https://sponsors.better-t-stack.dev/sponsors.json"; "https://sponsors.better-t-stack.dev/sponsors.json";
export async function fetchSponsors( export async function fetchSponsors(url: string = SPONSORS_JSON_URL) {
url: string = SPONSORS_JSON_URL,
): Promise<SponsorEntry> {
const s = spinner(); const s = spinner();
s.start("Fetching sponsors…"); s.start("Fetching sponsors…");

View File

@@ -4,7 +4,7 @@
* - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics. * - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
* - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled). * - 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_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
const BTS_TELEMETRY = process.env.BTS_TELEMETRY; const BTS_TELEMETRY = process.env.BTS_TELEMETRY;

View File

@@ -19,7 +19,7 @@ export const tsProject = new Project({
export function ensureArrayProperty( export function ensureArrayProperty(
obj: ObjectLiteralExpression, obj: ObjectLiteralExpression,
name: string, name: string,
): ArrayLiteralExpression { ) {
return (obj return (obj
.getProperty(name) .getProperty(name)
?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? ?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ??

View File

@@ -48,7 +48,7 @@ export function processAndValidateFlags(
options: CLIInput, options: CLIInput,
providedFlags: Set<string>, providedFlags: Set<string>,
projectName?: string, projectName?: string,
): Partial<ProjectConfig> { ) {
if (options.yolo) { if (options.yolo) {
const cfg = processFlags(options, projectName); const cfg = processFlags(options, projectName);
const validatedProjectName = extractAndValidateProjectName( const validatedProjectName = extractAndValidateProjectName(
@@ -89,7 +89,7 @@ export function processAndValidateFlags(
export function processProvidedFlagsWithoutValidation( export function processProvidedFlagsWithoutValidation(
options: CLIInput, options: CLIInput,
projectName?: string, projectName?: string,
): Partial<ProjectConfig> { ) {
if (!options.yolo) { if (!options.yolo) {
const providedFlags = getProvidedFlags(options); const providedFlags = getProvidedFlags(options);
validateYesFlagCombination(options, providedFlags); validateYesFlagCombination(options, providedFlags);

View File

@@ -25,7 +25,7 @@ const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
"install", "install",
]; ];
export function generateStackSummary(stack: StackState): string { export function generateStackSummary(stack: StackState) {
const selectedTechs = CATEGORY_ORDER.flatMap((category) => { const selectedTechs = CATEGORY_ORDER.flatMap((category) => {
const options = TECH_OPTIONS[category]; const options = TECH_OPTIONS[category];
const selectedValue = stack[category as keyof StackState]; 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"; return selectedTechs.length > 0 ? selectedTechs.join(" • ") : "Custom stack";
} }
export function generateStackCommand(stack: StackState): string { export function generateStackCommand(stack: StackState) {
const packageManagerCommands = { const packageManagerCommands = {
npm: "npx create-better-t-stack@latest", npm: "npx create-better-t-stack@latest",
pnpm: "pnpm 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(" ")}`; return `${base} ${projectName} ${flags.join(" ")}`;
} }
export function generateStackUrlFromState( export function generateStackUrlFromState(stack: StackState, baseUrl?: string) {
stack: StackState,
baseUrl?: string,
): string {
const origin = baseUrl || "https://better-t-stack.dev"; const origin = baseUrl || "https://better-t-stack.dev";
const stackParams = new URLSearchParams(); const stackParams = new URLSearchParams();
@@ -147,10 +144,7 @@ export function generateStackUrlFromState(
return `${origin}/new${searchString ? `?${searchString}` : ""}`; return `${origin}/new${searchString ? `?${searchString}` : ""}`;
} }
export function generateStackSharingUrl( export function generateStackSharingUrl(stack: StackState, baseUrl?: string) {
stack: StackState,
baseUrl?: string,
): string {
const origin = baseUrl || "https://better-t-stack.dev"; const origin = baseUrl || "https://better-t-stack.dev";
const stackParams = new URLSearchParams(); const stackParams = new URLSearchParams();