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:
@@ -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
|
||||||
@@ -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: [],
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
|
|||||||
@@ -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))) {
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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: [],
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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…");
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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) ??
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user