Update cli prompts

This commit is contained in:
Aman Varshney
2025-04-02 13:59:37 +05:30
parent 12e297d94d
commit 0d7ac632e3
19 changed files with 91 additions and 34 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": patch
---
Update CLI prompts

View File

@@ -50,8 +50,7 @@ Options:
--auth Include authentication
--no-auth Exclude authentication
--frontend <types...> Frontend types (web, native, none)
--addons <types...> Additional addons (pwa, tauri, biome, husky)
--no-addons Skip all additional addons
--addons <types...> Additional addons (pwa, tauri, biome, husky, none)
--examples <types...> Examples to include (todo, ai)
--no-examples Skip all examples
--git Initialize git repository

View File

@@ -7,11 +7,35 @@
"bin": {
"create-better-t-stack": "dist/index.js"
},
"files": [
"dist",
"template"
"files": ["dist", "template"],
"keywords": [
"typescript",
"scaffold",
"generator",
"boilerplate",
"starter",
"cli",
"create-app",
"t3-stack",
"turborepo",
"trpc",
"monorepo",
"fullstack",
"type-safety",
"react",
"react-native",
"expo",
"hono",
"elysia",
"drizzle",
"prisma",
"tanstack",
"tailwind",
"shadcn",
"pwa",
"tauri",
"biome"
],
"keywords": [],
"repository": {
"type": "git",
"url": "git+https://github.com/better-t-stack/create-better-t-stack.git",

View File

@@ -1,6 +1,8 @@
import path from "node:path";
import { log } from "@clack/prompts";
import { $, execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import { PKG_ROOT } from "../constants";
import type { ProjectConfig } from "../types";
@@ -79,7 +81,26 @@ export async function initializeGit(
projectDir: string,
useGit: boolean,
): Promise<void> {
if (useGit) {
await $({ cwd: projectDir })`git init`;
if (!useGit) return;
const gitVersionResult = await $({
cwd: projectDir,
reject: false,
stderr: "pipe",
})`git --version`;
if (gitVersionResult.exitCode !== 0) {
log.warn(pc.yellow("Git is not installed"));
return;
}
const result = await $({
cwd: projectDir,
reject: false,
stderr: "pipe",
})`git init`;
if (result.exitCode !== 0) {
throw new Error(`Git initialization failed: ${result.stderr}`);
}
}

View File

@@ -44,9 +44,8 @@ async function main() {
.option("--frontend <types...>", "Frontend types (web, native, none)")
.option(
"--addons <types...>",
"Additional addons (pwa, tauri, biome, husky)",
"Additional addons (pwa, tauri, biome, husky, none)",
)
.option("--no-addons", "Skip all additional addons")
.option("--examples <types...>", "Examples to include (todo, ai)")
.option("--no-examples", "Skip all examples")
.option("--git", "Initialize git repository")
@@ -285,8 +284,12 @@ function validateOptions(options: CLIOptions): void {
}
}
if (options.addons && options.addons.length > 0) {
const validAddons = ["pwa", "tauri", "biome", "husky"];
if (
options.addons &&
Array.isArray(options.addons) &&
options.addons.length > 0
) {
const validAddons = ["pwa", "tauri", "biome", "husky", "none"];
const invalidAddons = options.addons.filter(
(addon: string) => !validAddons.includes(addon),
);
@@ -300,6 +303,11 @@ function validateOptions(options: CLIOptions): void {
process.exit(1);
}
if (options.addons.includes("none") && options.addons.length > 1) {
cancel(pc.red(`Cannot combine 'none' with other addons.`));
process.exit(1);
}
const webSpecificAddons = ["pwa", "tauri"];
const hasWebSpecificAddons = options.addons.some((addon) =>
webSpecificAddons.includes(addon),
@@ -382,10 +390,10 @@ function processFlags(
}
let addons: ProjectAddons[] | undefined;
if ("addons" in options) {
if (options.addons === undefined) {
if (options.addons && Array.isArray(options.addons)) {
if (options.addons.includes("none")) {
addons = [];
} else if (options.addons) {
} else {
addons = options.addons.filter(
(addon): addon is ProjectAddons =>
addon === "pwa" ||

View File

@@ -44,7 +44,7 @@ export async function getAddonsChoice(
);
const response = await multiselect<ProjectAddons>({
message: "Which Addons would you like to add?",
message: "Select addons",
options,
initialValues,
required: false,

View File

@@ -24,7 +24,7 @@ export async function getAuthChoice(
if (auth !== undefined) return auth;
const response = await confirm({
message: "Would you like to add authentication with Better-Auth?",
message: "Add authentication with Better-Auth?",
initialValue: DEFAULT_CONFIG.auth,
});

View File

@@ -9,7 +9,7 @@ export async function getBackendFrameworkChoice(
if (backendFramework !== undefined) return backendFramework;
const response = await select<ProjectBackend>({
message: "Which backend framework would you like to use?",
message: "Select backend framework",
options: [
{
value: "hono",
@@ -19,7 +19,7 @@ export async function getBackendFrameworkChoice(
{
value: "elysia",
label: "Elysia",
hint: "TypeScript framework with end-to-end type safety)",
hint: "Ergonomic web framework for building backend servers",
},
],
initialValue: DEFAULT_CONFIG.backend,

View File

@@ -9,7 +9,7 @@ export async function getDatabaseChoice(
if (database !== undefined) return database;
const response = await select<ProjectDatabase>({
message: "Which database would you like to use?",
message: "Select database",
options: [
{
value: "none",

View File

@@ -25,7 +25,7 @@ export async function getExamplesChoice(
if (backend === "elysia") {
response = await multiselect<ProjectExamples>({
message: "Which examples would you like to include?",
message: "Include examples",
options: [
{
value: "todo",
@@ -40,7 +40,7 @@ export async function getExamplesChoice(
if (backend === "hono") {
response = await multiselect<ProjectExamples>({
message: "Which examples would you like to include?",
message: "Include examples",
options: [
{
value: "todo",

View File

@@ -9,7 +9,7 @@ export async function getFrontendChoice(
if (frontendOptions !== undefined) return frontendOptions;
const response = await multiselect<ProjectFrontend>({
message: "Which frontend applications would you like to create?",
message: "Choose frontends",
options: [
{
value: "web",

View File

@@ -6,7 +6,7 @@ export async function getGitChoice(git?: boolean): Promise<boolean> {
if (git !== undefined) return git;
const response = await confirm({
message: "Initialize a new git repository?",
message: "Initialize git repository?",
initialValue: DEFAULT_CONFIG.git,
});

View File

@@ -8,7 +8,7 @@ export async function getNoInstallChoice(
if (noInstall !== undefined) return noInstall;
const response = await confirm({
message: "Do you want to install project dependencies?",
message: "Install dependencies?",
initialValue: !DEFAULT_CONFIG.noInstall,
});

View File

@@ -11,7 +11,7 @@ export async function getORMChoice(
if (orm !== undefined) return orm;
const response = await select<ProjectOrm>({
message: "Which ORM would you like to use?",
message: "Select ORM",
options: [
{
value: "drizzle",

View File

@@ -11,7 +11,7 @@ export async function getPackageManagerChoice(
const detectedPackageManager = getUserPkgManager();
const response = await select<ProjectPackageManager>({
message: "Which package manager do you want to use?",
message: "Choose package manager",
options: [
{ value: "npm", label: "npm", hint: "Node Package Manager" },
{

View File

@@ -9,7 +9,7 @@ export async function getRuntimeChoice(
if (runtime !== undefined) return runtime;
const response = await select<ProjectRuntime>({
message: "Which runtime would you like to use?",
message: "Select runtime",
options: [
{
value: "bun",

View File

@@ -6,7 +6,7 @@ export async function getTursoSetupChoice(turso?: boolean): Promise<boolean> {
if (turso !== undefined) return turso;
const response = await confirm({
message: "Set up a Turso database for this project?",
message: "Set up Turso database?",
initialValue: DEFAULT_CONFIG.turso,
});

View File

@@ -18,9 +18,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
}
flags.push(config.auth ? "--auth" : "--no-auth");
flags.push(config.git ? "--git" : "--no-git");
flags.push(config.noInstall ? "--no-install" : "--install");
if (config.runtime) {
@@ -38,7 +36,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
if (config.addons && config.addons.length > 0) {
flags.push(`--addons ${config.addons.join(" ")}`);
} else {
flags.push("--no-addons");
flags.push("--addons none");
}
if (config.examples && config.examples.length > 0) {
@@ -55,7 +53,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
const pkgManager = config.packageManager;
if (pkgManager === "npm") {
baseCommand = "npm create better-t-stack@latest";
baseCommand = "npx create-better-t-stack@latest";
} else if (pkgManager === "pnpm") {
baseCommand = "pnpm create better-t-stack@latest";
} else if (pkgManager === "bun") {

View File

@@ -243,7 +243,7 @@ const TECH_OPTIONS = {
{
id: "ai",
name: "AI Example",
description: "AI integration example",
description: "AI integration example using AI SDK",
icon: "🤖",
color: "from-purple-500 to-purple-700",
default: false,
@@ -441,6 +441,8 @@ const StackArchitect = () => {
if (stackState.addons.length > 0) {
flags.push(`--addons ${stackState.addons.join(" ")}`);
} else {
flags.push("--addons none");
}
if (stackState.examples.length > 0) {