Skip to content

Commit 3d78d1e

Browse files
add flags and prompt for orm, add none option in database and organize code
1 parent 9032a59 commit 3d78d1e

File tree

16 files changed

+451
-282
lines changed

16 files changed

+451
-282
lines changed

.changeset/loud-socks-reflect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
add flags and prompt for orm, add none option in database

apps/cli/src/consts.ts renamed to apps/cli/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ export const PKG_ROOT = path.join(distPath, "../");
99
export const DEFAULT_CONFIG: ProjectConfig = {
1010
projectName: "my-better-t-app",
1111
database: "sqlite",
12+
orm: "drizzle",
1213
auth: true,
1314
features: [],
1415
git: true,
1516
packageManager: "npm",
16-
orm: "drizzle",
1717
};

apps/cli/src/helpers/create-project.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export async function createProject(options: ProjectConfig) {
5252

5353
if (options.database === "sqlite") {
5454
await setupTurso(projectDir);
55+
} else if (options.database === "postgres") {
56+
// Handle postgres setup
5557
}
5658

5759
const installDepsResponse = await confirm({

apps/cli/src/index.ts

Lines changed: 42 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
1-
import path from "node:path";
2-
import {
3-
cancel,
4-
confirm,
5-
group,
6-
intro,
7-
log,
8-
multiselect,
9-
outro,
10-
select,
11-
spinner,
12-
text,
13-
} from "@clack/prompts";
1+
import { cancel, intro, log, outro, spinner } from "@clack/prompts";
142
import { Command } from "commander";
15-
import fs from "fs-extra";
163
import pc from "picocolors";
17-
import { DEFAULT_CONFIG } from "./consts";
4+
import { DEFAULT_CONFIG } from "./constants";
185
import { createProject } from "./helpers/create-project";
19-
import type {
20-
PackageManager,
21-
ProjectConfig,
22-
ProjectDatabase,
23-
ProjectFeature,
24-
ProjectORM,
25-
} from "./types";
6+
import { gatherConfig } from "./prompts/config-prompts";
7+
import type { PackageManager, ProjectConfig, ProjectFeature } from "./types";
8+
import { displayConfig } from "./utils/display-config";
269
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
27-
import { getUserPkgManager } from "./utils/get-package-manager";
2810
import { getVersion } from "./utils/get-version";
2911
import { renderTitle } from "./utils/render-title";
3012

@@ -35,215 +17,6 @@ process.on("SIGINT", () => {
3517

3618
const program = new Command();
3719

38-
async function gatherConfig(
39-
flags: Partial<ProjectConfig>,
40-
): Promise<ProjectConfig> {
41-
const result = await group(
42-
{
43-
projectName: async () => {
44-
if (flags.projectName) return flags.projectName;
45-
let isValid = false;
46-
let projectName: string | symbol = "";
47-
let defaultName = DEFAULT_CONFIG.projectName;
48-
let counter = 1;
49-
50-
while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName))) {
51-
defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
52-
counter++;
53-
}
54-
55-
while (!isValid) {
56-
const response = await text({
57-
message: "What is your project named? (directory name or path)",
58-
placeholder: defaultName,
59-
initialValue: flags.projectName,
60-
defaultValue: defaultName,
61-
validate: (value) => {
62-
const nameToUse = value.trim() || defaultName;
63-
const projectDir = path.resolve(process.cwd(), nameToUse);
64-
65-
if (fs.pathExistsSync(projectDir)) {
66-
const dirContents = fs.readdirSync(projectDir);
67-
if (dirContents.length > 0) {
68-
return `Directory "${nameToUse}" already exists and is not empty. Please choose a different name.`;
69-
}
70-
}
71-
72-
isValid = true;
73-
return undefined;
74-
},
75-
});
76-
77-
if (typeof response === "symbol") {
78-
cancel(pc.red("Operation cancelled."));
79-
process.exit(0);
80-
}
81-
82-
projectName = response || defaultName;
83-
}
84-
85-
return projectName as string;
86-
},
87-
database: () =>
88-
flags.database !== undefined
89-
? Promise.resolve(flags.database)
90-
: select<ProjectDatabase>({
91-
message: "Which database would you like to use?",
92-
options: [
93-
{
94-
value: "sqlite",
95-
label: "SQLite",
96-
hint: "by Turso (recommended)",
97-
},
98-
{
99-
value: "postgres",
100-
label: "PostgreSQL",
101-
hint: "Traditional relational database",
102-
},
103-
],
104-
}),
105-
orm: () =>
106-
flags.orm !== undefined
107-
? Promise.resolve(flags.orm)
108-
: select<ProjectORM>({
109-
message: "Which ORM would you like to use?",
110-
options: [
111-
{
112-
value: "drizzle",
113-
label: "Drizzle",
114-
hint: "Type-safe, lightweight ORM (recommended)",
115-
},
116-
// {
117-
// value: "prisma",
118-
// label: "Prisma (coming soon)",
119-
// hint: "Feature-rich ORM with great DX",
120-
// },
121-
],
122-
initialValue: "drizzle",
123-
}),
124-
auth: () =>
125-
flags.auth !== undefined
126-
? Promise.resolve(flags.auth)
127-
: confirm({
128-
message: "Would you like to add authentication with Better-Auth?",
129-
initialValue: DEFAULT_CONFIG.auth,
130-
}),
131-
features: () =>
132-
flags.features !== undefined
133-
? Promise.resolve(flags.features)
134-
: multiselect<ProjectFeature>({
135-
message: "Which features would you like to add?",
136-
options: [
137-
{
138-
value: "docker",
139-
label: "Docker setup",
140-
hint: "Containerize your application",
141-
},
142-
{
143-
value: "github-actions",
144-
label: "GitHub Actions",
145-
hint: "CI/CD workflows",
146-
},
147-
{
148-
value: "SEO",
149-
label: "Basic SEO setup",
150-
hint: "Search engine optimization configuration",
151-
},
152-
],
153-
required: false,
154-
}),
155-
git: () =>
156-
flags.git !== undefined
157-
? Promise.resolve(flags.git)
158-
: confirm({
159-
message: "Initialize a new git repository?",
160-
initialValue: DEFAULT_CONFIG.git,
161-
}),
162-
packageManager: async () => {
163-
if (flags.packageManager !== undefined) {
164-
return flags.packageManager;
165-
}
166-
const detectedPackageManager = getUserPkgManager();
167-
168-
const useDetected = await confirm({
169-
message: `Use ${detectedPackageManager} as your package manager?`,
170-
});
171-
172-
if (useDetected) return detectedPackageManager;
173-
174-
return select<PackageManager>({
175-
message: "Which package manager would you like to use?",
176-
options: [
177-
{ value: "npm", label: "npm", hint: "Node Package Manager" },
178-
{
179-
value: "pnpm",
180-
label: "pnpm",
181-
hint: "Fast, disk space efficient package manager",
182-
},
183-
{
184-
value: "yarn",
185-
label: "yarn",
186-
hint: "Fast, reliable, and secure dependency management",
187-
},
188-
{
189-
value: "bun",
190-
label: "bun",
191-
hint: "All-in-one JavaScript runtime & toolkit (recommended)",
192-
},
193-
],
194-
initialValue: "bun",
195-
});
196-
},
197-
},
198-
{
199-
onCancel: () => {
200-
cancel(pc.red("Operation cancelled."));
201-
process.exit(0);
202-
},
203-
},
204-
);
205-
206-
return {
207-
projectName: result.projectName ?? DEFAULT_CONFIG.projectName,
208-
database: result.database ?? DEFAULT_CONFIG.database,
209-
orm: result.orm ?? DEFAULT_CONFIG.orm,
210-
auth: result.auth ?? DEFAULT_CONFIG.auth,
211-
features: result.features ?? DEFAULT_CONFIG.features,
212-
git: result.git ?? DEFAULT_CONFIG.git,
213-
packageManager: result.packageManager ?? DEFAULT_CONFIG.packageManager,
214-
};
215-
}
216-
217-
function displayConfig(config: Partial<ProjectConfig>) {
218-
const configDisplay = [];
219-
220-
if (config.projectName) {
221-
configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
222-
}
223-
if (config.database) {
224-
configDisplay.push(`${pc.blue("Database:")} ${config.database}`);
225-
}
226-
if (config.orm) {
227-
configDisplay.push(`${pc.blue("ORM:")} ${config.orm}`);
228-
}
229-
if (config.auth !== undefined) {
230-
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
231-
}
232-
if (config.features?.length) {
233-
configDisplay.push(`${pc.blue("Features:")} ${config.features.join(", ")}`);
234-
}
235-
if (config.git !== undefined) {
236-
configDisplay.push(`${pc.blue("Git Init:")} ${config.git}`);
237-
}
238-
if (config.packageManager) {
239-
configDisplay.push(
240-
`${pc.blue("Package Manager:")} ${config.packageManager}`,
241-
);
242-
}
243-
244-
return configDisplay.join("\n");
245-
}
246-
24720
async function main() {
24821
const s = spinner();
24922
try {
@@ -256,6 +29,7 @@ async function main() {
25629
.version(getVersion())
25730
.argument("[project-directory]", "Project name/directory")
25831
.option("-y, --yes", "Use default configuration")
32+
.option("--no-database", "Skip database setup")
25933
.option("--sqlite", "Use SQLite database")
26034
.option("--postgres", "Use PostgreSQL database")
26135
.option("--auth", "Include authentication")
@@ -277,38 +51,28 @@ async function main() {
27751
const projectDirectory = program.args[0];
27852

27953
const flagConfig: Partial<ProjectConfig> = {
280-
projectName: projectDirectory || undefined,
281-
database: options.sqlite
282-
? "sqlite"
283-
: options.postgres
284-
? "postgres"
285-
: undefined,
286-
orm: options.drizzle ? "drizzle" : options.prisma ? "prisma" : undefined,
287-
auth: "auth" in options ? options.auth : undefined,
288-
packageManager: options.npm
289-
? "npm"
290-
: options.pnpm
291-
? "pnpm"
292-
: options.yarn
293-
? "yarn"
294-
: options.bun
295-
? "bun"
296-
: undefined,
297-
git: "git" in options ? options.git : undefined,
298-
features:
299-
options.docker || options.githubActions || options.seo
300-
? ([
301-
...(options.docker ? ["docker"] : []),
302-
...(options.githubActions ? ["github-actions"] : []),
303-
...(options.seo ? ["SEO"] : []),
304-
] as ProjectFeature[])
305-
: undefined,
54+
...(projectDirectory && { projectName: projectDirectory }),
55+
...(options.database === false && { database: "none" }),
56+
...(options.sqlite && { database: "sqlite" }),
57+
...(options.postgres && { database: "postgres" }),
58+
...(options.drizzle && { orm: "drizzle" }),
59+
...(options.prisma && { orm: "prisma" }),
60+
...("auth" in options && { auth: options.auth }),
61+
...(options.npm && { packageManager: "npm" }),
62+
...(options.pnpm && { packageManager: "pnpm" }),
63+
...(options.yarn && { packageManager: "yarn" }),
64+
...(options.bun && { packageManager: "bun" }),
65+
...("git" in options && { git: options.git }),
66+
...((options.docker || options.githubActions || options.seo) && {
67+
features: [
68+
...(options.docker ? ["docker"] : []),
69+
...(options.githubActions ? ["github-actions"] : []),
70+
...(options.seo ? ["SEO"] : []),
71+
] as ProjectFeature[],
72+
}),
30673
};
30774

308-
if (
309-
!options.yes &&
310-
Object.values(flagConfig).some((v) => v !== undefined)
311-
) {
75+
if (!options.yes && Object.keys(flagConfig).length > 0) {
31276
log.info(pc.yellow("Using these pre-selected options:"));
31377
log.message(displayConfig(flagConfig));
31478
log.message("");
@@ -317,23 +81,26 @@ async function main() {
31781
const config = options.yes
31882
? {
31983
...DEFAULT_CONFIG,
320-
yes: true,
32184
projectName: projectDirectory ?? DEFAULT_CONFIG.projectName,
322-
database: options.database ?? DEFAULT_CONFIG.database,
323-
orm: options.drizzle
324-
? "drizzle"
325-
: options.prisma
326-
? "prisma"
327-
: DEFAULT_CONFIG.orm, // Add this line
85+
database:
86+
options.database === false
87+
? "none"
88+
: (options.database ?? DEFAULT_CONFIG.database),
89+
orm:
90+
options.database === false
91+
? "none"
92+
: options.drizzle
93+
? "drizzle"
94+
: options.prisma
95+
? "prisma"
96+
: DEFAULT_CONFIG.orm,
32897
auth: options.auth ?? DEFAULT_CONFIG.auth,
32998
git: options.git ?? DEFAULT_CONFIG.git,
33099
packageManager:
331-
options.packageManager ?? DEFAULT_CONFIG.packageManager,
332-
features: [
333-
...(options.docker ? ["docker"] : []),
334-
...(options.githubActions ? ["github-actions"] : []),
335-
...(options.seo ? ["SEO"] : []),
336-
] as ProjectFeature[],
100+
flagConfig.packageManager ?? DEFAULT_CONFIG.packageManager,
101+
features: flagConfig.features?.length
102+
? flagConfig.features
103+
: DEFAULT_CONFIG.features,
337104
}
338105
: await gatherConfig(flagConfig);
339106

0 commit comments

Comments
 (0)