Add todo example, remove yarn, change schema structure, update readme

This commit is contained in:
Aman Varshney
2025-03-24 00:04:53 +05:30
parent 5076bf4176
commit 4cc13bf382
42 changed files with 525 additions and 443 deletions

View File

@@ -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";
}

View 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);
}
}

View File

@@ -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,

View 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`;
}
}

View 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";
}