mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Update cli prompts
This commit is contained in:
5
.changeset/ready-bags-prove.md
Normal file
5
.changeset/ready-bags-prove.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update CLI prompts
|
||||||
@@ -50,8 +50,7 @@ Options:
|
|||||||
--auth Include authentication
|
--auth Include authentication
|
||||||
--no-auth Exclude authentication
|
--no-auth Exclude authentication
|
||||||
--frontend <types...> Frontend types (web, native, none)
|
--frontend <types...> Frontend types (web, native, none)
|
||||||
--addons <types...> Additional addons (pwa, tauri, biome, husky)
|
--addons <types...> Additional addons (pwa, tauri, biome, husky, none)
|
||||||
--no-addons Skip all additional addons
|
|
||||||
--examples <types...> Examples to include (todo, ai)
|
--examples <types...> Examples to include (todo, ai)
|
||||||
--no-examples Skip all examples
|
--no-examples Skip all examples
|
||||||
--git Initialize git repository
|
--git Initialize git repository
|
||||||
|
|||||||
@@ -7,11 +7,35 @@
|
|||||||
"bin": {
|
"bin": {
|
||||||
"create-better-t-stack": "dist/index.js"
|
"create-better-t-stack": "dist/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["dist", "template"],
|
||||||
"dist",
|
"keywords": [
|
||||||
"template"
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/better-t-stack/create-better-t-stack.git",
|
"url": "git+https://github.com/better-t-stack/create-better-t-stack.git",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { log } from "@clack/prompts";
|
||||||
import { $, execa } from "execa";
|
import { $, execa } from "execa";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
|
import pc from "picocolors";
|
||||||
import { PKG_ROOT } from "../constants";
|
import { PKG_ROOT } from "../constants";
|
||||||
import type { ProjectConfig } from "../types";
|
import type { ProjectConfig } from "../types";
|
||||||
|
|
||||||
@@ -79,7 +81,26 @@ export async function initializeGit(
|
|||||||
projectDir: string,
|
projectDir: string,
|
||||||
useGit: boolean,
|
useGit: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (useGit) {
|
if (!useGit) return;
|
||||||
await $({ cwd: projectDir })`git init`;
|
|
||||||
|
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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,8 @@ async function main() {
|
|||||||
.option("--frontend <types...>", "Frontend types (web, native, none)")
|
.option("--frontend <types...>", "Frontend types (web, native, none)")
|
||||||
.option(
|
.option(
|
||||||
"--addons <types...>",
|
"--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("--examples <types...>", "Examples to include (todo, ai)")
|
||||||
.option("--no-examples", "Skip all examples")
|
.option("--no-examples", "Skip all examples")
|
||||||
.option("--git", "Initialize git repository")
|
.option("--git", "Initialize git repository")
|
||||||
@@ -285,8 +284,12 @@ function validateOptions(options: CLIOptions): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.addons && options.addons.length > 0) {
|
if (
|
||||||
const validAddons = ["pwa", "tauri", "biome", "husky"];
|
options.addons &&
|
||||||
|
Array.isArray(options.addons) &&
|
||||||
|
options.addons.length > 0
|
||||||
|
) {
|
||||||
|
const validAddons = ["pwa", "tauri", "biome", "husky", "none"];
|
||||||
const invalidAddons = options.addons.filter(
|
const invalidAddons = options.addons.filter(
|
||||||
(addon: string) => !validAddons.includes(addon),
|
(addon: string) => !validAddons.includes(addon),
|
||||||
);
|
);
|
||||||
@@ -300,6 +303,11 @@ function validateOptions(options: CLIOptions): void {
|
|||||||
process.exit(1);
|
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 webSpecificAddons = ["pwa", "tauri"];
|
||||||
const hasWebSpecificAddons = options.addons.some((addon) =>
|
const hasWebSpecificAddons = options.addons.some((addon) =>
|
||||||
webSpecificAddons.includes(addon),
|
webSpecificAddons.includes(addon),
|
||||||
@@ -382,10 +390,10 @@ function processFlags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let addons: ProjectAddons[] | undefined;
|
let addons: ProjectAddons[] | undefined;
|
||||||
if ("addons" in options) {
|
if (options.addons && Array.isArray(options.addons)) {
|
||||||
if (options.addons === undefined) {
|
if (options.addons.includes("none")) {
|
||||||
addons = [];
|
addons = [];
|
||||||
} else if (options.addons) {
|
} else {
|
||||||
addons = options.addons.filter(
|
addons = options.addons.filter(
|
||||||
(addon): addon is ProjectAddons =>
|
(addon): addon is ProjectAddons =>
|
||||||
addon === "pwa" ||
|
addon === "pwa" ||
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function getAddonsChoice(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const response = await multiselect<ProjectAddons>({
|
const response = await multiselect<ProjectAddons>({
|
||||||
message: "Which Addons would you like to add?",
|
message: "Select addons",
|
||||||
options,
|
options,
|
||||||
initialValues,
|
initialValues,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getAuthChoice(
|
|||||||
if (auth !== undefined) return auth;
|
if (auth !== undefined) return auth;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
message: "Would you like to add authentication with Better-Auth?",
|
message: "Add authentication with Better-Auth?",
|
||||||
initialValue: DEFAULT_CONFIG.auth,
|
initialValue: DEFAULT_CONFIG.auth,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getBackendFrameworkChoice(
|
|||||||
if (backendFramework !== undefined) return backendFramework;
|
if (backendFramework !== undefined) return backendFramework;
|
||||||
|
|
||||||
const response = await select<ProjectBackend>({
|
const response = await select<ProjectBackend>({
|
||||||
message: "Which backend framework would you like to use?",
|
message: "Select backend framework",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "hono",
|
value: "hono",
|
||||||
@@ -19,7 +19,7 @@ export async function getBackendFrameworkChoice(
|
|||||||
{
|
{
|
||||||
value: "elysia",
|
value: "elysia",
|
||||||
label: "Elysia",
|
label: "Elysia",
|
||||||
hint: "TypeScript framework with end-to-end type safety)",
|
hint: "Ergonomic web framework for building backend servers",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
initialValue: DEFAULT_CONFIG.backend,
|
initialValue: DEFAULT_CONFIG.backend,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getDatabaseChoice(
|
|||||||
if (database !== undefined) return database;
|
if (database !== undefined) return database;
|
||||||
|
|
||||||
const response = await select<ProjectDatabase>({
|
const response = await select<ProjectDatabase>({
|
||||||
message: "Which database would you like to use?",
|
message: "Select database",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "none",
|
value: "none",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function getExamplesChoice(
|
|||||||
|
|
||||||
if (backend === "elysia") {
|
if (backend === "elysia") {
|
||||||
response = await multiselect<ProjectExamples>({
|
response = await multiselect<ProjectExamples>({
|
||||||
message: "Which examples would you like to include?",
|
message: "Include examples",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "todo",
|
value: "todo",
|
||||||
@@ -40,7 +40,7 @@ export async function getExamplesChoice(
|
|||||||
|
|
||||||
if (backend === "hono") {
|
if (backend === "hono") {
|
||||||
response = await multiselect<ProjectExamples>({
|
response = await multiselect<ProjectExamples>({
|
||||||
message: "Which examples would you like to include?",
|
message: "Include examples",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "todo",
|
value: "todo",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getFrontendChoice(
|
|||||||
if (frontendOptions !== undefined) return frontendOptions;
|
if (frontendOptions !== undefined) return frontendOptions;
|
||||||
|
|
||||||
const response = await multiselect<ProjectFrontend>({
|
const response = await multiselect<ProjectFrontend>({
|
||||||
message: "Which frontend applications would you like to create?",
|
message: "Choose frontends",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "web",
|
value: "web",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function getGitChoice(git?: boolean): Promise<boolean> {
|
|||||||
if (git !== undefined) return git;
|
if (git !== undefined) return git;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
message: "Initialize a new git repository?",
|
message: "Initialize git repository?",
|
||||||
initialValue: DEFAULT_CONFIG.git,
|
initialValue: DEFAULT_CONFIG.git,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export async function getNoInstallChoice(
|
|||||||
if (noInstall !== undefined) return noInstall;
|
if (noInstall !== undefined) return noInstall;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
message: "Do you want to install project dependencies?",
|
message: "Install dependencies?",
|
||||||
initialValue: !DEFAULT_CONFIG.noInstall,
|
initialValue: !DEFAULT_CONFIG.noInstall,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export async function getORMChoice(
|
|||||||
if (orm !== undefined) return orm;
|
if (orm !== undefined) return orm;
|
||||||
|
|
||||||
const response = await select<ProjectOrm>({
|
const response = await select<ProjectOrm>({
|
||||||
message: "Which ORM would you like to use?",
|
message: "Select ORM",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "drizzle",
|
value: "drizzle",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export async function getPackageManagerChoice(
|
|||||||
const detectedPackageManager = getUserPkgManager();
|
const detectedPackageManager = getUserPkgManager();
|
||||||
|
|
||||||
const response = await select<ProjectPackageManager>({
|
const response = await select<ProjectPackageManager>({
|
||||||
message: "Which package manager do you want to use?",
|
message: "Choose package manager",
|
||||||
options: [
|
options: [
|
||||||
{ value: "npm", label: "npm", hint: "Node Package Manager" },
|
{ value: "npm", label: "npm", hint: "Node Package Manager" },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getRuntimeChoice(
|
|||||||
if (runtime !== undefined) return runtime;
|
if (runtime !== undefined) return runtime;
|
||||||
|
|
||||||
const response = await select<ProjectRuntime>({
|
const response = await select<ProjectRuntime>({
|
||||||
message: "Which runtime would you like to use?",
|
message: "Select runtime",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "bun",
|
value: "bun",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function getTursoSetupChoice(turso?: boolean): Promise<boolean> {
|
|||||||
if (turso !== undefined) return turso;
|
if (turso !== undefined) return turso;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
message: "Set up a Turso database for this project?",
|
message: "Set up Turso database?",
|
||||||
initialValue: DEFAULT_CONFIG.turso,
|
initialValue: DEFAULT_CONFIG.turso,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flags.push(config.auth ? "--auth" : "--no-auth");
|
flags.push(config.auth ? "--auth" : "--no-auth");
|
||||||
|
|
||||||
flags.push(config.git ? "--git" : "--no-git");
|
flags.push(config.git ? "--git" : "--no-git");
|
||||||
|
|
||||||
flags.push(config.noInstall ? "--no-install" : "--install");
|
flags.push(config.noInstall ? "--no-install" : "--install");
|
||||||
|
|
||||||
if (config.runtime) {
|
if (config.runtime) {
|
||||||
@@ -38,7 +36,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
if (config.addons && config.addons.length > 0) {
|
if (config.addons && config.addons.length > 0) {
|
||||||
flags.push(`--addons ${config.addons.join(" ")}`);
|
flags.push(`--addons ${config.addons.join(" ")}`);
|
||||||
} else {
|
} else {
|
||||||
flags.push("--no-addons");
|
flags.push("--addons none");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.examples && config.examples.length > 0) {
|
if (config.examples && config.examples.length > 0) {
|
||||||
@@ -55,7 +53,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
const pkgManager = config.packageManager;
|
const pkgManager = config.packageManager;
|
||||||
|
|
||||||
if (pkgManager === "npm") {
|
if (pkgManager === "npm") {
|
||||||
baseCommand = "npm create better-t-stack@latest";
|
baseCommand = "npx create-better-t-stack@latest";
|
||||||
} else if (pkgManager === "pnpm") {
|
} else if (pkgManager === "pnpm") {
|
||||||
baseCommand = "pnpm create better-t-stack@latest";
|
baseCommand = "pnpm create better-t-stack@latest";
|
||||||
} else if (pkgManager === "bun") {
|
} else if (pkgManager === "bun") {
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ const TECH_OPTIONS = {
|
|||||||
{
|
{
|
||||||
id: "ai",
|
id: "ai",
|
||||||
name: "AI Example",
|
name: "AI Example",
|
||||||
description: "AI integration example",
|
description: "AI integration example using AI SDK",
|
||||||
icon: "🤖",
|
icon: "🤖",
|
||||||
color: "from-purple-500 to-purple-700",
|
color: "from-purple-500 to-purple-700",
|
||||||
default: false,
|
default: false,
|
||||||
@@ -441,6 +441,8 @@ const StackArchitect = () => {
|
|||||||
|
|
||||||
if (stackState.addons.length > 0) {
|
if (stackState.addons.length > 0) {
|
||||||
flags.push(`--addons ${stackState.addons.join(" ")}`);
|
flags.push(`--addons ${stackState.addons.join(" ")}`);
|
||||||
|
} else {
|
||||||
|
flags.push("--addons none");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackState.examples.length > 0) {
|
if (stackState.examples.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user