create-quickstart-app
Version:
CLI tool to generate Next.js projects with various configurations
527 lines (442 loc) • 16.5 kB
text/typescript
import { execa } from "execa";
import fs from "fs-extra";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import { createSpinner } from "../utils/spinner.js";
import { logger } from "../utils/logger.js";
import { getGlobalOptions } from "./nextjs.js";
import { GlobalOptions } from "../types/index.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = path.join(__dirname, "..", "..");
export async function setupORM(
projectPath: string,
orm: GlobalOptions["orm"],
database?: GlobalOptions["database"]
): Promise<void> {
try {
if (orm === "Prisma") {
await setupPrisma(projectPath, database);
} else if (orm === "Drizzle" && database !== "MongoDB") {
await setupDrizzle(projectPath, database);
} else if (orm === "Mongoose" && database === "MongoDB") {
await setupMongoose(projectPath);
}
} catch (error) {
logger.error("Failed to setup ORM", error);
throw error;
}
}
async function setupPrisma(
projectPath: string,
database?: GlobalOptions["database"]
): Promise<void> {
const spinner = createSpinner("Installing Prisma...");
const globalOptions = getGlobalOptions();
try {
// Install Prisma dependencies
await execa("npm", ["install", "prisma", "--save-dev"], {
cwd: projectPath,
});
await execa("npm", ["install", "@prisma/client"], { cwd: projectPath });
spinner.succeed("Prisma installed successfully");
// Initialize Prisma
const initSpinner = createSpinner("Initializing Prisma...");
await execa("npx", ["prisma", "init", "--datasource-provider", database!], {
cwd: projectPath,
});
initSpinner.succeed("Prisma initialized");
// Configure database in schema.prisma
// if (database && database !== "None") {
// const schemaSpinner = createSpinner(
// `Configuring Prisma for ${database}...`
// );
// const schemaPath = path.join(projectPath, "prisma", "schema.prisma");
// let schemaContent = await fs.readFile(schemaPath, "utf-8");
// // Replace the default database provider with the selected one
// switch (database) {
// case "PostgreSQL":
// schemaContent = schemaContent.replace(
// 'provider = "postgresql"',
// 'provider = "postgresql"'
// );
// break;
// case "SQLite":
// schemaContent = schemaContent.replace(
// 'provider = "postgresql"',
// 'provider = "sqlite"'
// );
// schemaContent = schemaContent.replace(
// 'url = env("DATABASE_URL")',
// 'url = "file:./dev.db"'
// );
// break;
// case "MySQL":
// schemaContent = schemaContent.replace(
// 'provider = "postgresql"',
// 'provider = "mysql"'
// );
// break;
// }
// // Update the schema file
// await fs.writeFile(schemaPath, schemaContent);
// schemaSpinner.succeed(`Prisma configured for ${database}`);
// // Add example model
// const modelSpinner = createSpinner("Adding example model...");
// const exampleModel = `\nmodel Example {
// id String @id @default(cuid())
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// }\n`;
// await fs.appendFile(schemaPath, exampleModel);
// modelSpinner.succeed("Example model added to schema");
// }
// Update .env file with proper database URL if not SQLite
// if (database && database !== "SQLite" && database !== "None") {
// const envPath = path.join(projectPath, ".env");
// let envContent = "";
// if (await fs.pathExists(envPath)) {
// envContent = await fs.readFile(envPath, "utf-8");
// }
// if (!envContent.includes("DATABASE_URL")) {
// const dbUrlSpinner = createSpinner("Setting up database URL...");
// let databaseUrl = "";
// switch (database) {
// case "PostgreSQL":
// databaseUrl =
// 'DATABASE_URL="postgresql://postgres:password@localhost:5432/mydb?schema=public"';
// break;
// case "MySQL":
// databaseUrl =
// 'DATABASE_URL="mysql://root:password@localhost:3306/mydb"';
// break;
// }
// await fs.appendFile(envPath, `\n${databaseUrl}\n`);
// dbUrlSpinner.succeed("Database URL added to .env file");
// }
// }
// Init prismadb in lib
const prismadbSpinner = createSpinner("Initializing prismadb in lib...");
const libFolderSourcePath = path.join(rootDir, "lib");
const libFolderDestPath = path.join(
projectPath,
globalOptions.useSrcDir
? "src/lib"
: "lib"
);
await fs.copy(libFolderSourcePath, libFolderDestPath);
prismadbSpinner.succeed("Prisma client initialized");
logger.success("Prisma setup completed successfully");
} catch (error) {
spinner.fail("Failed to setup Prisma");
throw error;
}
}
async function setupDrizzle(
projectPath: string,
database?: "PostgreSQL" | "SQLite" | "MySQL" | "None"
): Promise<void> {
const spinner = createSpinner("Installing Drizzle...");
try {
// Install Drizzle core
await execa("npm", ["install", "drizzle-orm"], { cwd: projectPath });
// Install Drizzle kit
await execa("npm", ["install", "drizzle-kit", "--save-dev"], {
cwd: projectPath,
});
// Install database driver based on selection
if (database) {
switch (database) {
case "PostgreSQL":
await execa("npm", ["install", "pg", "@types/pg"], {
cwd: projectPath,
});
break;
case "SQLite":
await execa(
"npm",
["install", "better-sqlite3", "@types/better-sqlite3"],
{ cwd: projectPath }
);
break;
case "MySQL":
await execa("npm", ["install", "mysql2"], { cwd: projectPath });
break;
}
}
spinner.succeed("Drizzle and database drivers installed");
// Create drizzle directory structure
const configSpinner = createSpinner("Setting up Drizzle configuration...");
const drizzleDir = path.join(projectPath, "drizzle");
await fs.ensureDir(drizzleDir);
await fs.ensureDir(path.join(drizzleDir, "migrations"));
// Create schema.ts
let schemaContent = "";
switch (database) {
case "PostgreSQL":
schemaContent = `import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const examples = pgTable('examples', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
`;
break;
case "SQLite":
schemaContent = `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const examples = sqliteTable('examples', {
id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().defaultNow(),
});
`;
break;
case "MySQL":
schemaContent = `import { mysqlTable, serial, varchar, timestamp } from 'drizzle-orm/mysql-core';
export const examples = mysqlTable('examples', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
`;
break;
default:
schemaContent = `// Please configure your database schema based on your selected provider
`;
}
await fs.writeFile(path.join(drizzleDir, "schema.ts"), schemaContent);
// Create database config
let dbConfigContent = "";
switch (database) {
case "PostgreSQL":
dbConfigContent = `import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const db = drizzle(pool, { schema });
`;
break;
case "SQLite":
dbConfigContent = `import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
import * as schema from './schema';
const sqlite = new Database('sqlite.db');
export const db = drizzle(sqlite, { schema });
`;
break;
case "MySQL":
dbConfigContent = `import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';
const poolConnection = mysql.createPool({
uri: process.env.DATABASE_URL,
});
export const db = drizzle(poolConnection, { schema });
`;
break;
default:
dbConfigContent = `// Please configure your database connection based on your selected provider
`;
}
await fs.writeFile(path.join(drizzleDir, "index.ts"), dbConfigContent);
// Create drizzle.config.ts
const drizzleConfigContent = `import type { Config } from 'drizzle-kit';
export default {
schema: './drizzle/schema.ts',
out: './drizzle/migrations',
} satisfies Config;
`;
await fs.writeFile(
path.join(projectPath, "drizzle.config.ts"),
drizzleConfigContent
);
configSpinner.succeed("Drizzle configuration created");
// Update package.json with drizzle scripts
const packageJsonSpinner = createSpinner(
"Updating package.json with Drizzle scripts..."
);
const packageJsonPath = path.join(projectPath, "package.json");
const packageJson = await fs.readJson(packageJsonPath);
if (!packageJson.scripts) {
packageJson.scripts = {};
}
packageJson.scripts["db:generate"] = "drizzle-kit generate:sqlite";
packageJson.scripts["db:migrate"] = "drizzle-kit migrate:sqlite";
packageJson.scripts["db:studio"] = "drizzle-kit studio";
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
packageJsonSpinner.succeed("Package.json updated with Drizzle scripts");
// Update env file if needed
if (database !== "SQLite" && database !== "None") {
const envSpinner = createSpinner("Setting up database URL...");
const envPath = path.join(projectPath, ".env");
let envContent = "";
if (await fs.pathExists(envPath)) {
envContent = await fs.readFile(envPath, "utf-8");
}
if (!envContent.includes("DATABASE_URL")) {
let databaseUrl = "";
switch (database) {
case "PostgreSQL":
databaseUrl =
'DATABASE_URL="postgres://postgres:password@localhost:5432/mydb"';
break;
case "MySQL":
databaseUrl =
'DATABASE_URL="mysql://root:password@localhost:3306/mydb"';
break;
}
await fs.appendFile(envPath, `\n${databaseUrl}\n`);
envSpinner.succeed("Database URL added to .env file");
}
}
logger.success("Drizzle setup completed successfully");
} catch (error) {
spinner.fail("Failed to setup Drizzle");
throw error;
}
}
async function setupMongoose(projectPath: string): Promise<void> {
const spinner = createSpinner("Installing Mongoose...");
const globalOptions = getGlobalOptions();
try {
// Install Mongoose
await execa("npm", ["install", "mongoose"], { cwd: projectPath });
spinner.succeed("Mongoose installed successfully");
// Create lib directory if it doesn't exist
const libDirPath = path.join(
projectPath,
globalOptions.useSrcDir ? "src/lib" : "lib"
);
await fs.ensureDir(libDirPath);
// Create mongoose connection file
const dbConfigSpinner = createSpinner("Creating Mongoose configuration...");
const mongooseConnectContent = `import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
try {
cached.conn = await cached.promise;
} catch (e) {
cached.promise = null;
throw e;
}
return cached.conn;
}
export default dbConnect;
`;
await fs.writeFile(
path.join(libDirPath, "mongoose.ts"),
mongooseConnectContent
);
dbConfigSpinner.succeed("Mongoose configuration created");
// Create models directory and example model
const modelsSpinner = createSpinner("Creating example model...");
const modelsDirPath = path.join(
projectPath,
globalOptions.useSrcDir ? "src/models" : "models"
);
await fs.ensureDir(modelsDirPath);
const exampleModelContent = `import mongoose from 'mongoose';
const ExampleSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please provide a name'],
maxlength: [60, 'Name cannot be more than 60 characters'],
},
description: {
type: String,
required: false,
},
createdAt: {
type: Date,
default: Date.now,
},
updatedAt: {
type: Date,
default: Date.now,
},
});
export default mongoose.models.Example || mongoose.model('Example', ExampleSchema);
`;
await fs.writeFile(
path.join(modelsDirPath, "Example.ts"),
exampleModelContent
);
modelsSpinner.succeed("Example model created");
// Create environment file with MongoDB URI if it doesn't exist
const envPath = path.join(projectPath, ".env");
let envContent = "";
if (await fs.pathExists(envPath)) {
envContent = await fs.readFile(envPath, "utf-8");
}
if (!envContent.includes("MONGODB_URI")) {
const envSpinner = createSpinner("Setting up MongoDB URI...");
const mongoDbUrl = 'MONGODB_URI="mongodb://localhost:27017/myapp"';
await fs.appendFile(envPath, `\n${mongoDbUrl}\n`);
envSpinner.succeed("MongoDB URI added to .env file");
}
// Create an API example to use the model
const apiSpinner = createSpinner("Creating example API route...");
const apiDirPath = path.join(
projectPath,
globalOptions.useSrcDir
? "src/app/api/examples/route.ts"
: "app/api/examples/route.ts"
);
await fs.ensureDir(path.dirname(apiDirPath));
const apiRouteContent = `import { NextResponse } from 'next/server';
import dbConnect from '${
globalOptions.useSrcDir
? "../../../lib/mongoose"
: "../../../lib/mongoose"
}';
import Example from '${
globalOptions.useSrcDir
? "../../../models/Example"
: "../../../models/Example"
}';
export async function GET() {
try {
await dbConnect();
const examples = await Example.find({});
return NextResponse.json(examples);
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch examples' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const body = await request.json();
await dbConnect();
const example = await Example.create(body);
return NextResponse.json(example, { status: 201 });
} catch (error) {
return NextResponse.json({ error: 'Failed to create example' }, { status: 500 });
}
}
`;
await fs.writeFile(apiDirPath, apiRouteContent);
apiSpinner.succeed("Example API route created");
logger.success("Mongoose setup completed successfully");
} catch (error) {
spinner.fail("Failed to setup Mongoose");
throw error;
}
}