mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add todo example, remove yarn, change schema structure, update readme
This commit is contained in:
@@ -15,6 +15,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
||||
git: true,
|
||||
packageManager: "npm",
|
||||
noInstall: false,
|
||||
examples: ["todo"],
|
||||
};
|
||||
|
||||
export const dependencyVersionMap = {
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import path from "node:path";
|
||||
import { cancel, spinner } from "@clack/prompts";
|
||||
import { $ } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import { PKG_ROOT } from "../constants";
|
||||
import type { ProjectConfig, ProjectDatabase, ProjectOrm } from "../types";
|
||||
import type { ProjectConfig } from "../types";
|
||||
import { setupAddons } from "./addons-setup";
|
||||
import { setupAuth } from "./auth-setup";
|
||||
import { createReadme } from "./create-readme";
|
||||
import { setupDatabase } from "./db-setup";
|
||||
import { setupEnvironmentVariables } from "./env-setup";
|
||||
import { setupExamples } from "./examples-setup";
|
||||
import { displayPostInstallInstructions } from "./post-installation";
|
||||
import { initializeGit, updatePackageConfigurations } from "./project-config";
|
||||
import {
|
||||
copyBaseTemplate,
|
||||
fixGitignoreFiles,
|
||||
setupAuthTemplate,
|
||||
setupOrmTemplate,
|
||||
} from "./template-manager";
|
||||
|
||||
export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
const s = spinner();
|
||||
@@ -18,99 +24,26 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
|
||||
try {
|
||||
await fs.ensureDir(projectDir);
|
||||
const templateDir = path.join(PKG_ROOT, "template/base");
|
||||
if (!(await fs.pathExists(templateDir))) {
|
||||
throw new Error(`Template directory not found: ${templateDir}`);
|
||||
}
|
||||
await fs.copy(templateDir, projectDir);
|
||||
|
||||
const gitignorePaths = [
|
||||
path.join(projectDir, "_gitignore"),
|
||||
path.join(projectDir, "packages/client/_gitignore"),
|
||||
path.join(projectDir, "packages/server/_gitignore"),
|
||||
];
|
||||
await copyBaseTemplate(projectDir);
|
||||
|
||||
for (const gitignorePath of gitignorePaths) {
|
||||
if (await fs.pathExists(gitignorePath)) {
|
||||
const targetPath = path.join(path.dirname(gitignorePath), ".gitignore");
|
||||
await fs.move(gitignorePath, targetPath);
|
||||
}
|
||||
}
|
||||
await fixGitignoreFiles(projectDir);
|
||||
|
||||
if (options.auth) {
|
||||
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
|
||||
if (await fs.pathExists(authTemplateDir)) {
|
||||
await fs.copy(authTemplateDir, projectDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
await setupAuthTemplate(projectDir, options.auth);
|
||||
|
||||
if (options.orm !== "none" && options.database !== "none") {
|
||||
const ormTemplateDir = path.join(
|
||||
PKG_ROOT,
|
||||
getOrmTemplateDir(options.orm, options.database),
|
||||
);
|
||||
await setupOrmTemplate(
|
||||
projectDir,
|
||||
options.orm,
|
||||
options.database,
|
||||
options.auth,
|
||||
);
|
||||
|
||||
if (await fs.pathExists(ormTemplateDir)) {
|
||||
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
|
||||
const serverSrcPath = path.join(projectDir, "packages/server/src");
|
||||
const baseLibPath = path.join(serverSrcPath, "lib");
|
||||
const withAuthLibPath = path.join(serverSrcPath, "with-auth-lib");
|
||||
|
||||
if (options.auth) {
|
||||
await fs.remove(baseLibPath);
|
||||
await fs.move(withAuthLibPath, baseLibPath);
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
const schemaPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/prisma/schema.prisma",
|
||||
);
|
||||
const withAuthSchemaPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/prisma/with-auth-schema.prisma",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(withAuthSchemaPath)) {
|
||||
await fs.remove(schemaPath);
|
||||
await fs.move(withAuthSchemaPath, schemaPath);
|
||||
}
|
||||
} else if (options.orm === "drizzle") {
|
||||
const schemaPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/db/schema.ts",
|
||||
);
|
||||
const withAuthSchemaPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/db/with-auth-schema.ts",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(withAuthSchemaPath)) {
|
||||
await fs.remove(schemaPath);
|
||||
await fs.move(withAuthSchemaPath, schemaPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await fs.remove(withAuthLibPath);
|
||||
if (options.orm === "prisma") {
|
||||
const withAuthSchema = path.join(
|
||||
projectDir,
|
||||
"packages/server/prisma/with-auth-schema.prisma",
|
||||
);
|
||||
if (await fs.pathExists(withAuthSchema)) {
|
||||
await fs.remove(withAuthSchema);
|
||||
}
|
||||
} else if (options.orm === "drizzle") {
|
||||
const withAuthSchema = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/db/with-auth-schema.ts",
|
||||
);
|
||||
if (await fs.pathExists(withAuthSchema)) {
|
||||
await fs.remove(withAuthSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await setupExamples(
|
||||
projectDir,
|
||||
options.examples,
|
||||
options.orm,
|
||||
options.auth,
|
||||
);
|
||||
|
||||
await setupDatabase(
|
||||
projectDir,
|
||||
@@ -118,18 +51,19 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
options.orm,
|
||||
options.turso ?? options.database === "sqlite",
|
||||
);
|
||||
|
||||
await setupAuth(projectDir, options.auth);
|
||||
|
||||
await setupEnvironmentVariables(projectDir, options);
|
||||
|
||||
if (options.git) {
|
||||
await $({ cwd: projectDir })`git init`;
|
||||
}
|
||||
await initializeGit(projectDir, options.git);
|
||||
|
||||
if (options.addons.length > 0) {
|
||||
await setupAddons(projectDir, options.addons, options.packageManager);
|
||||
}
|
||||
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
|
||||
await createReadme(projectDir, options);
|
||||
|
||||
displayPostInstallInstructions(
|
||||
@@ -151,69 +85,3 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePackageConfigurations(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
) {
|
||||
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
||||
if (await fs.pathExists(rootPackageJsonPath)) {
|
||||
const packageJson = await fs.readJson(rootPackageJsonPath);
|
||||
packageJson.name = options.projectName;
|
||||
|
||||
if (options.packageManager !== "bun") {
|
||||
packageJson.packageManager =
|
||||
options.packageManager === "npm"
|
||||
? "npm@10.9.2"
|
||||
: options.packageManager === "pnpm"
|
||||
? "pnpm@10.6.4"
|
||||
: options.packageManager === "yarn"
|
||||
? "yarn@4.1.0"
|
||||
: "bun@1.2.5";
|
||||
}
|
||||
|
||||
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
const serverPackageJsonPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/package.json",
|
||||
);
|
||||
if (await fs.pathExists(serverPackageJsonPath)) {
|
||||
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.database === "sqlite") {
|
||||
serverPackageJson.scripts["db:local"] = "turso dev --db-file local.db";
|
||||
}
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
serverPackageJson.scripts["db:push"] = "prisma db push";
|
||||
serverPackageJson.scripts["db:studio"] = "prisma studio";
|
||||
} else if (options.orm === "drizzle") {
|
||||
serverPackageJson.scripts["db:push"] = "drizzle-kit push";
|
||||
serverPackageJson.scripts["db:studio"] = "drizzle-kit studio";
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(serverPackageJsonPath, serverPackageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
if (orm === "drizzle") {
|
||||
return database === "sqlite"
|
||||
? "template/with-drizzle-sqlite"
|
||||
: "template/with-drizzle-postgres";
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
return database === "sqlite"
|
||||
? "template/with-prisma-sqlite"
|
||||
: "template/with-prisma-postgres";
|
||||
}
|
||||
|
||||
return "template/base";
|
||||
}
|
||||
|
||||
130
apps/cli/src/helpers/examples-setup.ts
Normal file
130
apps/cli/src/helpers/examples-setup.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { PKG_ROOT } from "../constants";
|
||||
import type { ProjectOrm } from "../types";
|
||||
|
||||
export async function setupExamples(
|
||||
projectDir: string,
|
||||
examples: string[],
|
||||
orm: ProjectOrm,
|
||||
auth: boolean,
|
||||
): Promise<void> {
|
||||
if (examples.includes("todo")) {
|
||||
await setupTodoExample(projectDir, orm, auth);
|
||||
} else {
|
||||
await cleanupTodoFiles(projectDir, orm);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupTodoExample(
|
||||
projectDir: string,
|
||||
orm: ProjectOrm,
|
||||
auth: boolean,
|
||||
): Promise<void> {
|
||||
const todoExampleDir = path.join(PKG_ROOT, "template/examples/todo");
|
||||
if (await fs.pathExists(todoExampleDir)) {
|
||||
const todoRouteDir = path.join(
|
||||
todoExampleDir,
|
||||
"packages/client/src/routes",
|
||||
);
|
||||
const targetRouteDir = path.join(projectDir, "packages/client/src/routes");
|
||||
await fs.copy(todoRouteDir, targetRouteDir, { overwrite: true });
|
||||
|
||||
if (orm !== "none") {
|
||||
const todoRouterSourceFile = path.join(
|
||||
todoExampleDir,
|
||||
`packages/server/src/routers/with-${orm}-todo.ts`,
|
||||
);
|
||||
const todoRouterTargetFile = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/routers/todo.ts",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(todoRouterSourceFile)) {
|
||||
await fs.copy(todoRouterSourceFile, todoRouterTargetFile, {
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await updateHeaderWithTodoLink(projectDir, auth);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateHeaderWithTodoLink(
|
||||
projectDir: string,
|
||||
auth: boolean,
|
||||
): Promise<void> {
|
||||
const headerPath = path.join(
|
||||
projectDir,
|
||||
"packages/client/src/components/header.tsx",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(headerPath)) {
|
||||
let headerContent = await fs.readFile(headerPath, "utf8");
|
||||
|
||||
if (auth) {
|
||||
headerContent = headerContent.replace(
|
||||
/const links = \[\s*{ to: "\/", label: "Home" },\s*{ to: "\/dashboard", label: "Dashboard" },/,
|
||||
`const links = [\n { to: "/", label: "Home" },\n { to: "/dashboard", label: "Dashboard" },\n { to: "/todos", label: "Todos" },`,
|
||||
);
|
||||
} else {
|
||||
headerContent = headerContent.replace(
|
||||
/const links = \[\s*{ to: "\/", label: "Home" },/,
|
||||
`const links = [\n { to: "/", label: "Home" },\n { to: "/todos", label: "Todos" },`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(headerPath, headerContent);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupTodoFiles(
|
||||
projectDir: string,
|
||||
orm: ProjectOrm,
|
||||
): Promise<void> {
|
||||
if (orm === "drizzle") {
|
||||
const todoSchemaFile = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/db/schema/todo.ts",
|
||||
);
|
||||
if (await fs.pathExists(todoSchemaFile)) {
|
||||
await fs.remove(todoSchemaFile);
|
||||
}
|
||||
} else if (orm === "prisma") {
|
||||
const todoPrismaFile = path.join(
|
||||
projectDir,
|
||||
"packages/server/prisma/schema/todo.prisma",
|
||||
);
|
||||
if (await fs.pathExists(todoPrismaFile)) {
|
||||
await fs.remove(todoPrismaFile);
|
||||
}
|
||||
}
|
||||
|
||||
const todoRouterFile = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/routers/todo.ts",
|
||||
);
|
||||
if (await fs.pathExists(todoRouterFile)) {
|
||||
await fs.remove(todoRouterFile);
|
||||
}
|
||||
|
||||
await updateRouterIndex(projectDir);
|
||||
}
|
||||
|
||||
async function updateRouterIndex(projectDir: string): Promise<void> {
|
||||
const routerFile = path.join(
|
||||
projectDir,
|
||||
"packages/server/src/routers/index.ts",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(routerFile)) {
|
||||
let routerContent = await fs.readFile(routerFile, "utf8");
|
||||
routerContent = routerContent.replace(
|
||||
/import { todoRouter } from ".\/todo";/,
|
||||
"",
|
||||
);
|
||||
routerContent = routerContent.replace(/todo: todoRouter,/, "");
|
||||
await fs.writeFile(routerFile, routerContent);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { log, spinner } from "@clack/prompts";
|
||||
import { $ } from "execa";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectAddons } from "../types";
|
||||
import type { PackageManager } from "../utils/get-package-manager";
|
||||
import type { PackageManager, ProjectAddons } from "../types";
|
||||
|
||||
export async function installDependencies({
|
||||
projectDir,
|
||||
@@ -26,7 +25,6 @@ export async function installDependencies({
|
||||
})`${packageManager} install`;
|
||||
break;
|
||||
case "pnpm":
|
||||
case "yarn":
|
||||
case "bun":
|
||||
await $({
|
||||
cwd: projectDir,
|
||||
|
||||
76
apps/cli/src/helpers/project-config.ts
Normal file
76
apps/cli/src/helpers/project-config.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import path from "node:path";
|
||||
import { $ } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import type { ProjectConfig, ProjectDatabase, ProjectOrm } from "../types";
|
||||
|
||||
export async function updatePackageConfigurations(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
await updateRootPackageJson(projectDir, options);
|
||||
await updateServerPackageJson(projectDir, options);
|
||||
}
|
||||
|
||||
async function updateRootPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
||||
if (await fs.pathExists(rootPackageJsonPath)) {
|
||||
const packageJson = await fs.readJson(rootPackageJsonPath);
|
||||
packageJson.name = options.projectName;
|
||||
|
||||
if (options.packageManager !== "bun") {
|
||||
packageJson.packageManager =
|
||||
options.packageManager === "npm"
|
||||
? "npm@10.9.2"
|
||||
: options.packageManager === "pnpm"
|
||||
? "pnpm@10.6.4"
|
||||
: "bun@1.2.5";
|
||||
}
|
||||
|
||||
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
async function updateServerPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const serverPackageJsonPath = path.join(
|
||||
projectDir,
|
||||
"packages/server/package.json",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(serverPackageJsonPath)) {
|
||||
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.database === "sqlite") {
|
||||
serverPackageJson.scripts["db:local"] = "turso dev --db-file local.db";
|
||||
}
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
serverPackageJson.scripts["db:push"] =
|
||||
"prisma db push --schema ./prisma/schema";
|
||||
serverPackageJson.scripts["db:studio"] = "prisma studio";
|
||||
} else if (options.orm === "drizzle") {
|
||||
serverPackageJson.scripts["db:push"] = "drizzle-kit push";
|
||||
serverPackageJson.scripts["db:studio"] = "drizzle-kit studio";
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(serverPackageJsonPath, serverPackageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeGit(
|
||||
projectDir: string,
|
||||
useGit: boolean,
|
||||
): Promise<void> {
|
||||
if (useGit) {
|
||||
await $({ cwd: projectDir })`git init`;
|
||||
}
|
||||
}
|
||||
83
apps/cli/src/helpers/template-manager.ts
Normal file
83
apps/cli/src/helpers/template-manager.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { PKG_ROOT } from "../constants";
|
||||
import type { ProjectDatabase, ProjectOrm } from "../types";
|
||||
|
||||
export async function copyBaseTemplate(projectDir: string): Promise<void> {
|
||||
const templateDir = path.join(PKG_ROOT, "template/base");
|
||||
if (!(await fs.pathExists(templateDir))) {
|
||||
throw new Error(`Template directory not found: ${templateDir}`);
|
||||
}
|
||||
await fs.copy(templateDir, projectDir);
|
||||
}
|
||||
|
||||
export async function setupAuthTemplate(
|
||||
projectDir: string,
|
||||
auth: boolean,
|
||||
): Promise<void> {
|
||||
if (!auth) return;
|
||||
|
||||
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
|
||||
if (await fs.pathExists(authTemplateDir)) {
|
||||
await fs.copy(authTemplateDir, projectDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupOrmTemplate(
|
||||
projectDir: string,
|
||||
orm: ProjectOrm,
|
||||
database: ProjectDatabase,
|
||||
auth: boolean,
|
||||
): Promise<void> {
|
||||
if (orm === "none" || database === "none") return;
|
||||
|
||||
const ormTemplateDir = path.join(PKG_ROOT, getOrmTemplateDir(orm, database));
|
||||
|
||||
if (await fs.pathExists(ormTemplateDir)) {
|
||||
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
|
||||
|
||||
const serverSrcPath = path.join(projectDir, "packages/server/src");
|
||||
const libPath = path.join(serverSrcPath, "lib");
|
||||
const withAuthLibPath = path.join(serverSrcPath, "with-auth-lib");
|
||||
|
||||
if (auth) {
|
||||
if (await fs.pathExists(withAuthLibPath)) {
|
||||
await fs.remove(libPath);
|
||||
await fs.move(withAuthLibPath, libPath);
|
||||
}
|
||||
} else {
|
||||
await fs.remove(withAuthLibPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function fixGitignoreFiles(projectDir: string): Promise<void> {
|
||||
const gitignorePaths = [
|
||||
path.join(projectDir, "_gitignore"),
|
||||
path.join(projectDir, "packages/client/_gitignore"),
|
||||
path.join(projectDir, "packages/server/_gitignore"),
|
||||
];
|
||||
|
||||
for (const gitignorePath of gitignorePaths) {
|
||||
if (await fs.pathExists(gitignorePath)) {
|
||||
const targetPath = path.join(path.dirname(gitignorePath), ".gitignore");
|
||||
await fs.move(gitignorePath, targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
if (orm === "drizzle") {
|
||||
return database === "sqlite"
|
||||
? "template/with-drizzle-sqlite"
|
||||
: "template/with-drizzle-postgres";
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
return database === "sqlite"
|
||||
? "template/with-prisma-sqlite"
|
||||
: "template/with-prisma-postgres";
|
||||
}
|
||||
|
||||
return "template/base";
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { DEFAULT_CONFIG } from "./constants";
|
||||
import { createProject } from "./helpers/create-project";
|
||||
import { installDependencies } from "./helpers/install-dependencies";
|
||||
import { gatherConfig } from "./prompts/config-prompts";
|
||||
import type { ProjectAddons, ProjectConfig } from "./types";
|
||||
import type { ProjectAddons, ProjectConfig, ProjectExamples } from "./types";
|
||||
import { displayConfig } from "./utils/display-config";
|
||||
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
|
||||
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
||||
@@ -35,11 +35,12 @@ async function main() {
|
||||
.option("--biome", "Include Biome for linting and formatting")
|
||||
.option("--husky", "Include Husky, lint-staged for Git hooks")
|
||||
.option("--no-addons", "Skip all additional addons")
|
||||
.option("--examples <examples>", "Include specified examples")
|
||||
.option("--no-examples", "Skip all examples")
|
||||
.option("--git", "Include git setup")
|
||||
.option("--no-git", "Skip git initialization")
|
||||
.option("--npm", "Use npm package manager")
|
||||
.option("--pnpm", "Use pnpm package manager")
|
||||
.option("--yarn", "Use yarn package manager")
|
||||
.option("--bun", "Use bun package manager")
|
||||
.option("--drizzle", "Use Drizzle ORM")
|
||||
.option("--prisma", "Use Prisma ORM (coming soon)")
|
||||
@@ -68,7 +69,6 @@ async function main() {
|
||||
...("auth" in options && { auth: options.auth }),
|
||||
...(options.npm && { packageManager: "npm" }),
|
||||
...(options.pnpm && { packageManager: " pnpm" }),
|
||||
...(options.yarn && { packageManager: "yarn" }),
|
||||
...(options.bun && { packageManager: "bun" }),
|
||||
...("git" in options && { git: options.git }),
|
||||
...("install" in options && { noInstall: !options.install }),
|
||||
@@ -88,6 +88,16 @@ async function main() {
|
||||
...(options.husky ? ["husky"] : []),
|
||||
] as ProjectAddons[]),
|
||||
}),
|
||||
...((options.examples || options.examples === false) && {
|
||||
examples:
|
||||
options.examples === false
|
||||
? []
|
||||
: typeof options.examples === "string"
|
||||
? (options.examples
|
||||
.split(",")
|
||||
.filter((e) => e === "todo") as ProjectExamples[])
|
||||
: [],
|
||||
}),
|
||||
};
|
||||
|
||||
if (!options.yes && Object.keys(flagConfig).length > 0) {
|
||||
@@ -123,6 +133,9 @@ async function main() {
|
||||
addons: flagConfig.addons?.length
|
||||
? flagConfig.addons
|
||||
: DEFAULT_CONFIG.addons,
|
||||
examples: flagConfig.examples?.length
|
||||
? flagConfig.examples
|
||||
: DEFAULT_CONFIG.examples,
|
||||
turso:
|
||||
"turso" in options
|
||||
? options.turso
|
||||
|
||||
@@ -5,11 +5,13 @@ import type {
|
||||
ProjectAddons,
|
||||
ProjectConfig,
|
||||
ProjectDatabase,
|
||||
ProjectExamples,
|
||||
ProjectOrm,
|
||||
} from "../types";
|
||||
import { getAddonsChoice } from "./addons";
|
||||
import { getAuthChoice } from "./auth";
|
||||
import { getDatabaseChoice } from "./database";
|
||||
import { getExamplesChoice } from "./examples";
|
||||
import { getGitChoice } from "./git";
|
||||
import { getNoInstallChoice } from "./install";
|
||||
import { getORMChoice } from "./orm";
|
||||
@@ -23,6 +25,7 @@ interface PromptGroupResults {
|
||||
orm: ProjectOrm;
|
||||
auth: boolean;
|
||||
addons: ProjectAddons[];
|
||||
examples: ProjectExamples[];
|
||||
git: boolean;
|
||||
packageManager: PackageManager;
|
||||
noInstall: boolean;
|
||||
@@ -47,6 +50,7 @@ export async function gatherConfig(
|
||||
? getTursoSetupChoice(flags.turso)
|
||||
: Promise.resolve(false),
|
||||
addons: () => getAddonsChoice(flags.addons),
|
||||
examples: () => getExamplesChoice(flags.examples),
|
||||
git: () => getGitChoice(flags.git),
|
||||
packageManager: () => getPackageManagerChoice(flags.packageManager),
|
||||
noInstall: () => getNoInstallChoice(flags.noInstall),
|
||||
@@ -65,6 +69,7 @@ export async function gatherConfig(
|
||||
orm: result.orm,
|
||||
auth: result.auth,
|
||||
addons: result.addons,
|
||||
examples: result.examples,
|
||||
git: result.git,
|
||||
packageManager: result.packageManager,
|
||||
noInstall: result.noInstall,
|
||||
|
||||
30
apps/cli/src/prompts/examples.ts
Normal file
30
apps/cli/src/prompts/examples.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { ProjectExamples } from "../types";
|
||||
|
||||
export async function getExamplesChoice(
|
||||
examples?: ProjectExamples[],
|
||||
): Promise<ProjectExamples[]> {
|
||||
if (examples !== undefined) return examples;
|
||||
|
||||
const response = await multiselect<ProjectExamples>({
|
||||
message: "Which examples would you like to include?",
|
||||
options: [
|
||||
{
|
||||
value: "todo",
|
||||
label: "Todo App",
|
||||
hint: "A simple CRUD example app",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
initialValues: DEFAULT_CONFIG.examples,
|
||||
});
|
||||
|
||||
if (isCancel(response)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -24,11 +24,6 @@ export async function getPackageManagerChoice(
|
||||
label: "pnpm",
|
||||
hint: "Fast, disk space efficient package manager",
|
||||
},
|
||||
{
|
||||
value: "yarn",
|
||||
label: "yarn",
|
||||
hint: "Fast, reliable, and secure dependency management",
|
||||
},
|
||||
],
|
||||
initialValue: detectedPackageManager,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export type ProjectDatabase = "sqlite" | "postgres" | "none";
|
||||
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
||||
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
||||
export type PackageManager = "npm" | "pnpm" | "bun";
|
||||
export type ProjectAddons = "pwa" | "tauri" | "biome" | "husky";
|
||||
export type ProjectExamples = "todo";
|
||||
|
||||
export interface ProjectConfig {
|
||||
projectName: string;
|
||||
@@ -9,6 +10,7 @@ export interface ProjectConfig {
|
||||
orm: ProjectOrm;
|
||||
auth: boolean;
|
||||
addons: ProjectAddons[];
|
||||
examples: ProjectExamples[];
|
||||
git: boolean;
|
||||
packageManager: PackageManager;
|
||||
noInstall?: boolean;
|
||||
|
||||
@@ -49,6 +49,20 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
||||
flags.push("--no-addons");
|
||||
}
|
||||
|
||||
if (config.examples && config.examples.length > 0) {
|
||||
flags.push(`--examples ${config.examples.join(",")}`);
|
||||
} else {
|
||||
flags.push("--no-examples");
|
||||
}
|
||||
|
||||
if (config.database === "sqlite") {
|
||||
if (config.turso) {
|
||||
flags.push("--turso");
|
||||
} else {
|
||||
flags.push("--no-turso");
|
||||
}
|
||||
}
|
||||
|
||||
const baseCommand = "npx create-better-t-stack";
|
||||
const projectName = config.projectName ? ` ${config.projectName}` : "";
|
||||
const flagString = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
||||
import type { PackageManager } from "../types";
|
||||
|
||||
export const getUserPkgManager: () => PackageManager = () => {
|
||||
const userAgent = process.env.npm_config_user_agent;
|
||||
|
||||
if (userAgent?.startsWith("yarn")) {
|
||||
return "yarn";
|
||||
}
|
||||
if (userAgent?.startsWith("pnpm")) {
|
||||
return "pnpm";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user