mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat: add mysql database
This commit is contained in:
5
.changeset/clever-dryers-search.md
Normal file
5
.changeset/clever-dryers-search.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
add mysql database
|
||||
@@ -33,6 +33,8 @@ export const dependencyVersionMap = {
|
||||
"@libsql/client": "^0.14.0",
|
||||
postgres: "^3.4.5",
|
||||
|
||||
mysql2: "^3.14.0",
|
||||
|
||||
"@prisma/client": "^6.5.0",
|
||||
prisma: "^6.5.0",
|
||||
|
||||
|
||||
@@ -30,58 +30,44 @@ export async function setupDatabase(
|
||||
}
|
||||
|
||||
try {
|
||||
if (databaseType === "sqlite") {
|
||||
if (orm === "drizzle") {
|
||||
if (orm === "prisma") {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (orm === "drizzle") {
|
||||
if (databaseType === "sqlite") {
|
||||
addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "@libsql/client"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (orm === "prisma") {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
|
||||
if (setupTursoDb) {
|
||||
await setupTurso(projectDir, true);
|
||||
}
|
||||
} else if (databaseType === "postgres") {
|
||||
if (orm === "drizzle") {
|
||||
} else if (databaseType === "postgres") {
|
||||
addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "postgres"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (orm === "prisma") {
|
||||
} else if (databaseType === "mysql") {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
|
||||
if (
|
||||
databaseType === "postgres" &&
|
||||
orm === "prisma" &&
|
||||
setupPrismaPostgresDb
|
||||
) {
|
||||
await setupPrismaPostgres(projectDir, packageManager);
|
||||
}
|
||||
}
|
||||
} else if (databaseType === "mongodb") {
|
||||
if (orm === "prisma") {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
dependencies: ["drizzle-orm", "mysql2"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (setupMongoDBAtlasDb) {
|
||||
await setupMongoDBAtlas(projectDir);
|
||||
}
|
||||
if (databaseType === "sqlite" && setupTursoDb) {
|
||||
await setupTurso(projectDir, orm === "drizzle");
|
||||
} else if (
|
||||
databaseType === "postgres" &&
|
||||
orm === "prisma" &&
|
||||
setupPrismaPostgresDb
|
||||
) {
|
||||
await setupPrismaPostgres(projectDir, packageManager);
|
||||
} else if (databaseType === "mongodb" && setupMongoDBAtlasDb) {
|
||||
await setupMongoDBAtlas(projectDir);
|
||||
}
|
||||
} catch (error) {
|
||||
s.stop(pc.red("Failed to set up database"));
|
||||
|
||||
@@ -3,73 +3,50 @@ import fs from "fs-extra";
|
||||
import type { ProjectConfig } from "../types";
|
||||
import { generateAuthSecret } from "./auth-setup";
|
||||
|
||||
interface EnvVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
condition: boolean;
|
||||
}
|
||||
|
||||
async function addEnvVariablesToFile(
|
||||
filePath: string,
|
||||
variables: EnvVariable[],
|
||||
): Promise<void> {
|
||||
await fs.ensureDir(path.dirname(filePath));
|
||||
|
||||
let envContent = "";
|
||||
if (await fs.pathExists(filePath)) {
|
||||
envContent = await fs.readFile(filePath, "utf8");
|
||||
}
|
||||
|
||||
let modified = false;
|
||||
for (const { key, value, condition } of variables) {
|
||||
if (condition) {
|
||||
const regex = new RegExp(`^${key}=.*$`, "m");
|
||||
if (regex.test(envContent)) {
|
||||
if (value) {
|
||||
envContent = envContent.replace(regex, `${key}=${value}`);
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
envContent += `\n${key}=${value}`;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
await fs.writeFile(filePath, envContent.trim());
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupEnvironmentVariables(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
|
||||
const envPath = path.join(serverDir, ".env");
|
||||
let envContent = "";
|
||||
|
||||
if (await fs.pathExists(envPath)) {
|
||||
envContent = await fs.readFile(envPath, "utf8");
|
||||
}
|
||||
|
||||
if (!envContent.includes("CORS_ORIGIN")) {
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = options.frontend.includes("tanstack-start");
|
||||
|
||||
let corsOrigin = "http://localhost:3000";
|
||||
|
||||
if (hasReactRouter) {
|
||||
corsOrigin = "http://localhost:5173";
|
||||
} else if (hasTanStackRouter || hasTanStackStart) {
|
||||
corsOrigin = "http://localhost:3001";
|
||||
}
|
||||
|
||||
envContent += `\nCORS_ORIGIN=${corsOrigin}`;
|
||||
}
|
||||
|
||||
if (options.auth) {
|
||||
if (!envContent.includes("BETTER_AUTH_SECRET")) {
|
||||
envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`;
|
||||
}
|
||||
|
||||
if (!envContent.includes("BETTER_AUTH_URL")) {
|
||||
envContent += "\nBETTER_AUTH_URL=http://localhost:3000";
|
||||
}
|
||||
}
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
||||
let databaseUrlLine = "";
|
||||
if (options.database === "sqlite") {
|
||||
databaseUrlLine = "";
|
||||
} else if (options.database === "postgres") {
|
||||
databaseUrlLine = `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
||||
} else if (options.database === "mongodb") {
|
||||
databaseUrlLine = `\nDATABASE_URL="mongodb://localhost:27017/mydatabase"`;
|
||||
}
|
||||
envContent += databaseUrlLine;
|
||||
}
|
||||
|
||||
if (options.database === "sqlite" && options.dbSetup !== "turso") {
|
||||
if (!envContent.includes("DATABASE_URL")) {
|
||||
envContent += "\nDATABASE_URL=file:./local.db";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
options.examples?.includes("ai") &&
|
||||
!envContent.includes("GOOGLE_GENERATIVE_AI_API_KEY")
|
||||
) {
|
||||
envContent += "\nGOOGLE_GENERATIVE_AI_API_KEY=";
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, envContent.trim());
|
||||
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
@@ -77,39 +54,84 @@ export async function setupEnvironmentVariables(
|
||||
const hasWebFrontend =
|
||||
hasReactRouter || hasTanStackRouter || hasTanStackStart;
|
||||
|
||||
let corsOrigin = "http://localhost:3000";
|
||||
if (hasReactRouter) {
|
||||
corsOrigin = "http://localhost:5173";
|
||||
} else if (hasTanStackRouter || hasTanStackStart) {
|
||||
corsOrigin = "http://localhost:3001";
|
||||
}
|
||||
|
||||
let databaseUrl = "";
|
||||
const specializedSetup =
|
||||
options.dbSetup === "turso" ||
|
||||
options.dbSetup === "prisma-postgres" ||
|
||||
options.dbSetup === "mongodb-atlas";
|
||||
|
||||
if (!specializedSetup) {
|
||||
if (options.database === "postgres") {
|
||||
databaseUrl =
|
||||
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public";
|
||||
} else if (options.database === "mysql") {
|
||||
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
||||
} else if (options.database === "mongodb") {
|
||||
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
||||
} else if (options.database === "sqlite") {
|
||||
databaseUrl = "file:./local.db";
|
||||
}
|
||||
}
|
||||
|
||||
const serverVars: EnvVariable[] = [
|
||||
{
|
||||
key: "CORS_ORIGIN",
|
||||
value: corsOrigin,
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
key: "BETTER_AUTH_SECRET",
|
||||
value: generateAuthSecret(),
|
||||
condition: !!options.auth,
|
||||
},
|
||||
{
|
||||
key: "BETTER_AUTH_URL",
|
||||
value: "http://localhost:3000",
|
||||
condition: !!options.auth,
|
||||
},
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: databaseUrl,
|
||||
condition:
|
||||
options.database !== "none" && databaseUrl !== "" && !specializedSetup,
|
||||
},
|
||||
{
|
||||
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
||||
value: "",
|
||||
condition: options.examples?.includes("ai") || false,
|
||||
},
|
||||
];
|
||||
|
||||
await addEnvVariablesToFile(envPath, serverVars);
|
||||
|
||||
if (hasWebFrontend) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
await setupClientEnvFile(clientDir);
|
||||
const clientVars: EnvVariable[] = [
|
||||
{
|
||||
key: "VITE_SERVER_URL",
|
||||
value: "http://localhost:3000",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
||||
}
|
||||
|
||||
if (options.frontend.includes("native")) {
|
||||
const nativeDir = path.join(projectDir, "apps/native");
|
||||
const nativeEnvPath = path.join(nativeDir, ".env");
|
||||
let nativeEnvContent = "";
|
||||
|
||||
if (await fs.pathExists(nativeEnvPath)) {
|
||||
nativeEnvContent = await fs.readFile(nativeEnvPath, "utf8");
|
||||
}
|
||||
|
||||
if (!nativeEnvContent.includes("EXPO_PUBLIC_SERVER_URL")) {
|
||||
nativeEnvContent += "EXPO_PUBLIC_SERVER_URL=http://localhost:3000\n";
|
||||
}
|
||||
|
||||
await fs.writeFile(nativeEnvPath, nativeEnvContent.trim());
|
||||
const nativeVars: EnvVariable[] = [
|
||||
{
|
||||
key: "EXPO_PUBLIC_SERVER_URL",
|
||||
value: "http://localhost:3000",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupClientEnvFile(clientDir: string) {
|
||||
const clientEnvPath = path.join(clientDir, ".env");
|
||||
let clientEnvContent = "";
|
||||
|
||||
if (await fs.pathExists(clientEnvPath)) {
|
||||
clientEnvContent = await fs.readFile(clientEnvPath, "utf8");
|
||||
}
|
||||
|
||||
if (!clientEnvContent.includes("VITE_SERVER_URL")) {
|
||||
clientEnvContent += "VITE_SERVER_URL=http://localhost:3000\n";
|
||||
}
|
||||
|
||||
await fs.writeFile(clientEnvPath, clientEnvContent.trim());
|
||||
}
|
||||
|
||||
@@ -366,14 +366,15 @@ async function findGitignoreFiles(dir: string): Promise<string[]> {
|
||||
|
||||
function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
if (orm === "drizzle") {
|
||||
return database === "sqlite"
|
||||
? "template/with-drizzle-sqlite"
|
||||
: "template/with-drizzle-postgres";
|
||||
if (database === "sqlite") return "template/with-drizzle-sqlite";
|
||||
if (database === "postgres") return "template/with-drizzle-postgres";
|
||||
if (database === "mysql") return "template/with-drizzle-mysql";
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
if (database === "sqlite") return "template/with-prisma-sqlite";
|
||||
if (database === "postgres") return "template/with-prisma-postgres";
|
||||
if (database === "mysql") return "template/with-prisma-mysql";
|
||||
if (database === "mongodb") return "template/with-prisma-mongodb";
|
||||
}
|
||||
|
||||
@@ -382,14 +383,15 @@ function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
|
||||
function getAuthLibDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
if (orm === "drizzle") {
|
||||
return database === "sqlite"
|
||||
? "with-drizzle-sqlite-lib"
|
||||
: "with-drizzle-postgres-lib";
|
||||
if (database === "sqlite") return "with-drizzle-sqlite-lib";
|
||||
if (database === "postgres") return "with-drizzle-postgres-lib";
|
||||
if (database === "mysql") return "with-drizzle-mysql-lib";
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
if (database === "sqlite") return "with-prisma-sqlite-lib";
|
||||
if (database === "postgres") return "with-prisma-postgres-lib";
|
||||
if (database === "mysql") return "with-prisma-mysql-lib";
|
||||
if (database === "mongodb") return "with-prisma-mongodb-lib";
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ async function main() {
|
||||
.option("-y, --yes", "Use default configuration")
|
||||
.option(
|
||||
"--database <type>",
|
||||
"Database type (none, sqlite, postgres, mongodb)",
|
||||
"Database type (none, sqlite, postgres, mysql, mongodb)",
|
||||
)
|
||||
.option("--orm <type>", "ORM type (drizzle, prisma)")
|
||||
.option("--auth", "Include authentication")
|
||||
@@ -80,9 +80,7 @@ async function main() {
|
||||
const options = program.opts() as CLIOptions;
|
||||
const projectDirectory = program.args[0];
|
||||
|
||||
validateOptions(options);
|
||||
|
||||
const flagConfig = processFlags(options, projectDirectory);
|
||||
const flagConfig = processAndValidateFlags(options, projectDirectory);
|
||||
|
||||
if (!options.yes && Object.keys(flagConfig).length > 0) {
|
||||
log.info(pc.yellow("Using these pre-selected options:"));
|
||||
@@ -129,41 +127,39 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateOptions(options: CLIOptions): void {
|
||||
if (
|
||||
options.database &&
|
||||
!["none", "sqlite", "postgres", "mongodb"].includes(options.database)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, postgres, or mongodb.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
function processAndValidateFlags(
|
||||
options: CLIOptions,
|
||||
projectDirectory?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
const config: Partial<ProjectConfig> = {};
|
||||
|
||||
if (options.database) {
|
||||
if (
|
||||
!["none", "sqlite", "postgres", "mysql", "mongodb"].includes(
|
||||
options.database,
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, postgres, mysql, or mongodb.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.database = options.database as ProjectDatabase;
|
||||
}
|
||||
|
||||
if (options.orm && !["drizzle", "prisma"].includes(options.orm)) {
|
||||
cancel(
|
||||
pc.red(`Invalid ORM type: ${options.orm}. Must be drizzle or prisma.`),
|
||||
);
|
||||
process.exit(1);
|
||||
if (options.orm) {
|
||||
if (!["drizzle", "prisma"].includes(options.orm)) {
|
||||
cancel(
|
||||
pc.red(`Invalid ORM type: ${options.orm}. Must be drizzle or prisma.`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.orm = options.orm as ProjectOrm;
|
||||
}
|
||||
|
||||
if (
|
||||
options.dbSetup &&
|
||||
!["turso", "prisma-postgres", "mongodb-atlas", "none"].includes(
|
||||
options.dbSetup,
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database setup: ${options.dbSetup}. Must be turso, prisma-postgres, mongodb-atlas, or none.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.database === "mongodb" && options.orm === "drizzle") {
|
||||
if (config.database === "mongodb" && config.orm === "drizzle") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"MongoDB is only available with Prisma. Cannot use --database mongodb with --orm drizzle",
|
||||
@@ -172,7 +168,81 @@ function validateOptions(options: CLIOptions): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.database === "none") {
|
||||
if (options.dbSetup) {
|
||||
if (
|
||||
!["turso", "prisma-postgres", "mongodb-atlas", "none"].includes(
|
||||
options.dbSetup,
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database setup: ${options.dbSetup}. Must be turso, prisma-postgres, mongodb-atlas, or none.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.dbSetup !== "none") {
|
||||
config.dbSetup = options.dbSetup as ProjectDBSetup;
|
||||
|
||||
if (options.dbSetup === "turso") {
|
||||
if (options.database && options.database !== "sqlite") {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Turso setup requires a SQLite database. Cannot use --db-setup turso with --database ${options.database}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.database = "sqlite";
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup is not compatible with Prisma. Cannot use --db-setup turso with --orm prisma",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.orm = "drizzle";
|
||||
} else if (options.dbSetup === "prisma-postgres") {
|
||||
if (options.database && options.database !== "postgres") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database. Cannot use --db-setup prisma-postgres with a different database type.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.database = "postgres";
|
||||
|
||||
if (options.orm && options.orm !== "prisma") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires Prisma ORM. Cannot use --db-setup prisma-postgres with a different ORM.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.orm = "prisma";
|
||||
} else if (options.dbSetup === "mongodb-atlas") {
|
||||
if (options.database && options.database !== "mongodb") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"MongoDB Atlas setup requires MongoDB database. Cannot use --db-setup mongodb-atlas with a different database type.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.database = "mongodb";
|
||||
config.orm = "prisma";
|
||||
}
|
||||
} else {
|
||||
config.dbSetup = "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (config.database === "none") {
|
||||
if (options.auth === true) {
|
||||
cancel(
|
||||
pc.red(
|
||||
@@ -201,128 +271,30 @@ function validateOptions(options: CLIOptions): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dbSetup === "turso") {
|
||||
if (options.database && options.database !== "sqlite") {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Turso setup requires a SQLite database. Cannot use --db-setup turso with --database ${options.database}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup is not compatible with Prisma. Cannot use --db-setup turso with --orm prisma",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if ("auth" in options) {
|
||||
config.auth = options.auth;
|
||||
}
|
||||
|
||||
if (options.dbSetup === "prisma-postgres") {
|
||||
if (options.database && options.database !== "postgres") {
|
||||
if (options.backend) {
|
||||
if (!["hono", "elysia", "express"].includes(options.backend)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database. Cannot use --db-setup prisma-postgres with a different database type.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.orm && options.orm !== "prisma") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires Prisma ORM. Cannot use --db-setup prisma-postgres with a different ORM.",
|
||||
`Invalid backend framework: ${options.backend}. Must be hono, elysia, or express.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.backend = options.backend as ProjectBackend;
|
||||
}
|
||||
|
||||
if (options.dbSetup === "mongodb-atlas") {
|
||||
if (options.database && options.database !== "mongodb") {
|
||||
if (options.runtime) {
|
||||
if (!["bun", "node"].includes(options.runtime)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"MongoDB Atlas setup requires MongoDB database. Cannot use --db-setup mongodb-atlas with a different database type.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
options.packageManager &&
|
||||
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.backend &&
|
||||
!["hono", "elysia", "express"].includes(options.backend)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid backend framework: ${options.backend}. Must be hono, elysia, or express.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.runtime && !["bun", "node"].includes(options.runtime)) {
|
||||
cancel(pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.examples &&
|
||||
Array.isArray(options.examples) &&
|
||||
options.examples.length > 0
|
||||
) {
|
||||
const validExamples = ["todo", "ai"];
|
||||
const invalidExamples = options.examples.filter(
|
||||
(example: string) => !validExamples.includes(example),
|
||||
);
|
||||
|
||||
if (invalidExamples.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.examples.includes("ai") && options.backend === "elysia") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"AI example is only compatible with Hono backend. Cannot use --examples ai with --backend elysia",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.frontend &&
|
||||
!options.frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
) &&
|
||||
!options.frontend.includes("none")
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Examples require a web frontend. Cannot use --examples with --frontend native only",
|
||||
),
|
||||
pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.runtime = options.runtime as ProjectRuntime;
|
||||
}
|
||||
|
||||
if (options.frontend && options.frontend.length > 0) {
|
||||
@@ -346,33 +318,42 @@ function validateOptions(options: CLIOptions): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const webFrontends = options.frontend.filter(
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
);
|
||||
|
||||
if (webFrontends.length > 1) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router",
|
||||
),
|
||||
if (options.frontend.includes("none")) {
|
||||
if (options.frontend.length > 1) {
|
||||
cancel(pc.red(`Cannot combine 'none' with other frontend options.`));
|
||||
process.exit(1);
|
||||
}
|
||||
config.frontend = [];
|
||||
} else {
|
||||
const validOptions = options.frontend.filter(
|
||||
(f): f is ProjectFrontend =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start" ||
|
||||
f === "native",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.frontend.includes("none") && options.frontend.length > 1) {
|
||||
cancel(pc.red(`Cannot combine 'none' with other frontend options.`));
|
||||
process.exit(1);
|
||||
const webFrontends = validOptions.filter(
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
);
|
||||
|
||||
if (webFrontends.length > 1) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
config.frontend = validOptions;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
options.addons &&
|
||||
Array.isArray(options.addons) &&
|
||||
options.addons.length > 0
|
||||
) {
|
||||
if (options.addons && options.addons.length > 0) {
|
||||
const validAddons = ["pwa", "tauri", "biome", "husky", "none"];
|
||||
const invalidAddons = options.addons.filter(
|
||||
(addon: string) => !validAddons.includes(addon),
|
||||
@@ -387,108 +368,14 @@ function validateOptions(options: CLIOptions): void {
|
||||
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 hasWebSpecificAddons = options.addons.some((addon) =>
|
||||
webSpecificAddons.includes(addon),
|
||||
);
|
||||
|
||||
if (
|
||||
hasWebSpecificAddons &&
|
||||
options.frontend &&
|
||||
!options.frontend.some((f) =>
|
||||
["tanstack-router", "react-router"].includes(f),
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`PWA and Tauri addons require tanstack-router or react-router. Cannot use --addons ${options.addons
|
||||
.filter((a) => webSpecificAddons.includes(a))
|
||||
.join(", ")} with incompatible frontend options.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processFlags(
|
||||
options: CLIOptions,
|
||||
projectDirectory?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
let frontend: ProjectFrontend[] | undefined = undefined;
|
||||
|
||||
if (options.frontend) {
|
||||
if (options.frontend.includes("none")) {
|
||||
frontend = [];
|
||||
} else {
|
||||
frontend = options.frontend.filter(
|
||||
(f): f is ProjectFrontend =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start" ||
|
||||
f === "native",
|
||||
);
|
||||
|
||||
const webFrontends = frontend.filter(
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
);
|
||||
|
||||
if (webFrontends.length > 1) {
|
||||
const firstWebFrontend = webFrontends[0];
|
||||
frontend = frontend.filter(
|
||||
(f) => f === "native" || f === firstWebFrontend,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let examples: ProjectExamples[] | undefined;
|
||||
if ("examples" in options) {
|
||||
if (options.examples === false) {
|
||||
examples = [];
|
||||
} else if (Array.isArray(options.examples)) {
|
||||
examples = options.examples.filter(
|
||||
(ex): ex is ProjectExamples => ex === "todo" || ex === "ai",
|
||||
);
|
||||
|
||||
if (
|
||||
frontend &&
|
||||
frontend.length > 0 &&
|
||||
!frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
)
|
||||
) {
|
||||
examples = [];
|
||||
log.warn(
|
||||
pc.yellow("Examples require web frontend - ignoring examples flag"),
|
||||
);
|
||||
}
|
||||
|
||||
if (examples.includes("ai") && options.backend === "elysia") {
|
||||
examples = examples.filter((ex) => ex !== "ai");
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"AI example is not compatible with Elysia - removing AI example",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let addons: ProjectAddons[] | undefined;
|
||||
if (options.addons && Array.isArray(options.addons)) {
|
||||
if (options.addons.includes("none")) {
|
||||
addons = [];
|
||||
if (options.addons.length > 1) {
|
||||
cancel(pc.red(`Cannot combine 'none' with other addons.`));
|
||||
process.exit(1);
|
||||
}
|
||||
config.addons = [];
|
||||
} else {
|
||||
addons = options.addons.filter(
|
||||
const validOptions = options.addons.filter(
|
||||
(addon): addon is ProjectAddons =>
|
||||
addon === "pwa" ||
|
||||
addon === "tauri" ||
|
||||
@@ -496,85 +383,111 @@ function processFlags(
|
||||
addon === "husky",
|
||||
);
|
||||
|
||||
const hasCompatibleWebFrontend = frontend?.some(
|
||||
const webSpecificAddons = ["pwa", "tauri"];
|
||||
const hasWebSpecificAddons = validOptions.some((addon) =>
|
||||
webSpecificAddons.includes(addon),
|
||||
);
|
||||
|
||||
const hasCompatibleWebFrontend = config.frontend?.some(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
);
|
||||
|
||||
if (!hasCompatibleWebFrontend) {
|
||||
const webSpecificAddons = ["pwa", "tauri"];
|
||||
const filteredAddons = addons.filter(
|
||||
(addon) => !webSpecificAddons.includes(addon),
|
||||
if (hasWebSpecificAddons && !hasCompatibleWebFrontend) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"PWA and Tauri addons require tanstack-router or react-router. Cannot use these addons with your frontend selection.",
|
||||
),
|
||||
);
|
||||
|
||||
if (filteredAddons.length !== addons.length) {
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"PWA and Tauri addons require tanstack-router or react-router - removing these addons",
|
||||
),
|
||||
);
|
||||
addons = filteredAddons;
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (addons.includes("husky") && !addons.includes("biome")) {
|
||||
addons.push("biome");
|
||||
if (validOptions.includes("husky") && !validOptions.includes("biome")) {
|
||||
validOptions.push("biome");
|
||||
}
|
||||
|
||||
config.addons = validOptions;
|
||||
}
|
||||
}
|
||||
|
||||
let database = options.database as ProjectDatabase | undefined;
|
||||
let orm = options.orm as ProjectOrm | undefined;
|
||||
const auth = "auth" in options ? options.auth : undefined;
|
||||
if ("examples" in options) {
|
||||
if (options.examples === false) {
|
||||
config.examples = [];
|
||||
} else if (Array.isArray(options.examples)) {
|
||||
const validExamples = ["todo", "ai"];
|
||||
const invalidExamples = options.examples.filter(
|
||||
(example: string) => !validExamples.includes(example),
|
||||
);
|
||||
|
||||
const backend = options.backend as ProjectBackend | undefined;
|
||||
const runtime = options.runtime as ProjectRuntime | undefined;
|
||||
const packageManager = options.packageManager as
|
||||
| ProjectPackageManager
|
||||
| undefined;
|
||||
|
||||
let dbSetup: ProjectDBSetup | undefined = undefined;
|
||||
if (options.dbSetup) {
|
||||
if (options.dbSetup === "none") {
|
||||
dbSetup = "none";
|
||||
} else {
|
||||
dbSetup = options.dbSetup as ProjectDBSetup;
|
||||
|
||||
if (dbSetup === "turso") {
|
||||
database = "sqlite";
|
||||
|
||||
if (orm === "prisma") {
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"Turso is not compatible with Prisma - switching to Drizzle",
|
||||
),
|
||||
);
|
||||
orm = "drizzle";
|
||||
}
|
||||
} else if (dbSetup === "prisma-postgres") {
|
||||
database = "postgres";
|
||||
orm = "prisma";
|
||||
} else if (dbSetup === "mongodb-atlas") {
|
||||
database = "mongodb";
|
||||
orm = "prisma";
|
||||
if (invalidExamples.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.examples.includes("ai") &&
|
||||
(options.backend === "elysia" || config.backend === "elysia")
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"AI example is only compatible with Hono backend. Cannot use --examples ai with --backend elysia",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hasWebFrontend = config.frontend?.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
);
|
||||
|
||||
if (
|
||||
options.examples.length > 0 &&
|
||||
!hasWebFrontend &&
|
||||
(!options.frontend ||
|
||||
!options.frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
))
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Examples require a web frontend (tanstack-router, react-router, or tanstack-start). Cannot use --examples without a compatible frontend.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
config.examples = options.examples.filter(
|
||||
(ex): ex is ProjectExamples => ex === "todo" || ex === "ai",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const config: Partial<ProjectConfig> = {};
|
||||
if (options.packageManager) {
|
||||
if (!["npm", "pnpm", "bun"].includes(options.packageManager)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
config.packageManager = options.packageManager as ProjectPackageManager;
|
||||
}
|
||||
|
||||
if (projectDirectory) config.projectName = projectDirectory;
|
||||
if (database !== undefined) config.database = database;
|
||||
if (orm !== undefined) config.orm = orm;
|
||||
if (auth !== undefined) config.auth = auth;
|
||||
if (packageManager) config.packageManager = packageManager;
|
||||
if ("git" in options) config.git = options.git;
|
||||
if ("install" in options) config.noInstall = !options.install;
|
||||
if (dbSetup !== undefined) config.dbSetup = dbSetup;
|
||||
if (backend) config.backend = backend;
|
||||
if (runtime) config.runtime = runtime;
|
||||
if (frontend !== undefined) config.frontend = frontend;
|
||||
if (addons !== undefined) config.addons = addons;
|
||||
if (examples !== undefined) config.examples = examples;
|
||||
if ("git" in options) {
|
||||
config.git = options.git;
|
||||
}
|
||||
|
||||
if ("install" in options) {
|
||||
config.noInstall = !options.install;
|
||||
}
|
||||
|
||||
if (projectDirectory) {
|
||||
config.projectName = projectDirectory;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -45,27 +45,6 @@ type PromptGroupResults = {
|
||||
export async function gatherConfig(
|
||||
flags: Partial<ProjectConfig>,
|
||||
): Promise<ProjectConfig> {
|
||||
if (flags.dbSetup) {
|
||||
if (flags.dbSetup === "turso") {
|
||||
flags.database = "sqlite";
|
||||
|
||||
if (flags.orm === "prisma") {
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"Turso is not compatible with Prisma - switching to Drizzle",
|
||||
),
|
||||
);
|
||||
flags.orm = "drizzle";
|
||||
}
|
||||
} else if (flags.dbSetup === "prisma-postgres") {
|
||||
flags.database = "postgres";
|
||||
flags.orm = "prisma";
|
||||
} else if (flags.dbSetup === "mongodb-atlas") {
|
||||
flags.database = "mongodb";
|
||||
flags.orm = "prisma";
|
||||
}
|
||||
}
|
||||
|
||||
const result = await group<PromptGroupResults>(
|
||||
{
|
||||
projectName: async () => {
|
||||
@@ -84,7 +63,11 @@ export async function gatherConfig(
|
||||
results.frontend,
|
||||
),
|
||||
dbSetup: ({ results }) =>
|
||||
getDBSetupChoice(results.database ?? "none", flags.dbSetup),
|
||||
getDBSetupChoice(
|
||||
results.database ?? "none",
|
||||
flags.dbSetup,
|
||||
results.orm,
|
||||
),
|
||||
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
||||
examples: ({ results }) =>
|
||||
getExamplesChoice(
|
||||
|
||||
@@ -26,6 +26,11 @@ export async function getDatabaseChoice(
|
||||
label: "PostgreSQL",
|
||||
hint: "powerful, open source object-relational database system",
|
||||
},
|
||||
{
|
||||
value: "mysql",
|
||||
label: "MySQL",
|
||||
hint: "popular open-source relational database system",
|
||||
},
|
||||
{
|
||||
value: "mongodb",
|
||||
label: "MongoDB",
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { cancel, isCancel, select } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectDBSetup } from "../types";
|
||||
import type { ProjectDBSetup, ProjectOrm } from "../types";
|
||||
|
||||
export async function getDBSetupChoice(
|
||||
databaseType: string,
|
||||
dbSetup: ProjectDBSetup | undefined,
|
||||
orm?: ProjectOrm,
|
||||
): Promise<ProjectDBSetup> {
|
||||
if (dbSetup !== undefined) return dbSetup as ProjectDBSetup;
|
||||
|
||||
if (databaseType === "sqlite" && orm === "prisma") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
let options: Array<{ value: ProjectDBSetup; label: string; hint: string }> =
|
||||
[];
|
||||
|
||||
@@ -16,7 +21,7 @@ export async function getDBSetupChoice(
|
||||
{
|
||||
value: "turso" as const,
|
||||
label: "Turso",
|
||||
hint: "SQLite for Production. Powered by libSQL.",
|
||||
hint: "SQLite for Production. Powered by libSQL",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export type ProjectDatabase = "sqlite" | "postgres" | "mongodb" | "none";
|
||||
export type ProjectDatabase =
|
||||
| "sqlite"
|
||||
| "postgres"
|
||||
| "mongodb"
|
||||
| "mysql"
|
||||
| "none";
|
||||
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
||||
export type ProjectPackageManager = "npm" | "pnpm" | "bun";
|
||||
export type ProjectAddons = "pwa" | "biome" | "tauri" | "husky";
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
"persistent": true
|
||||
},
|
||||
"db:push": {
|
||||
"cache": false
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"db:studio": {
|
||||
"cache": false
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,9 @@ export const todoRouter = router({
|
||||
create: publicProcedure
|
||||
.input(z.object({ text: z.string().min(1) }))
|
||||
.mutation(async ({ input }) => {
|
||||
return await db
|
||||
.insert(todo)
|
||||
.values({
|
||||
text: input.text,
|
||||
})
|
||||
.returning();
|
||||
return await db.insert(todo).values({
|
||||
text: input.text,
|
||||
});
|
||||
}),
|
||||
|
||||
toggle: publicProcedure
|
||||
@@ -26,16 +23,12 @@ export const todoRouter = router({
|
||||
return await db
|
||||
.update(todo)
|
||||
.set({ completed: input.completed })
|
||||
.where(eq(todo.id, input.id))
|
||||
.returning();
|
||||
.where(eq(todo.id, input.id));
|
||||
}),
|
||||
|
||||
delete: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
return await db
|
||||
.delete(todo)
|
||||
.where(eq(todo.id, input.id))
|
||||
.returning();
|
||||
return await db.delete(todo).where(eq(todo.id, input.id));
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema/auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "mysql",
|
||||
schema: schema,
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
@@ -8,10 +8,4 @@ export const auth = betterAuth({
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true },
|
||||
advanced: {
|
||||
defaultCookieAttributes: {
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import prisma from "../../prisma";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: "mysql",
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true },
|
||||
});
|
||||
@@ -8,10 +8,4 @@ export const auth = betterAuth({
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true },
|
||||
advanced: {
|
||||
defaultCookieAttributes: {
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,10 +8,4 @@ export const auth = betterAuth({
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true },
|
||||
advanced: {
|
||||
defaultCookieAttributes: {
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/db/schema",
|
||||
out: "./src/db/migrations",
|
||||
dialect: "mysql",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || "",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
|
||||
export const db = drizzle({ connection: { uri: process.env.DATABASE_URL } });
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
mysqlTable,
|
||||
varchar,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
} from "drizzle-orm/mysql-core";
|
||||
|
||||
export const user = mysqlTable("user", {
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
email: varchar("email", { length: 255 }).notNull().unique(),
|
||||
emailVerified: boolean("email_verified").notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const session = mysqlTable("session", {
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
token: varchar("token", { length: 255 }).notNull().unique(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
ipAddress: text("ip_address"),
|
||||
userAgent: text("user_agent"),
|
||||
userId: varchar("user_id", { length: 36 })
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const account = mysqlTable("account", {
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
providerId: text("provider_id").notNull(),
|
||||
userId: varchar("user_id", { length: 36 })
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
idToken: text("id_token"),
|
||||
|
||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||
scope: text("scope"),
|
||||
password: text("password"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const verification = mysqlTable("verification", {
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
createdAt: timestamp("created_at"),
|
||||
updatedAt: timestamp("updated_at"),
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { mysqlTable, varchar, int, boolean } from "drizzle-orm/mysql-core";
|
||||
|
||||
export const todo = mysqlTable("todo", {
|
||||
id: int("id").primaryKey().autoincrement(),
|
||||
text: varchar("text", { length: 255 }).notNull(),
|
||||
completed: boolean("completed").default(false).notNull(),
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
let prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -0,0 +1,59 @@
|
||||
model User {
|
||||
id String @id
|
||||
name String @db.Text
|
||||
email String
|
||||
emailVerified Boolean
|
||||
image String? @db.Text
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
|
||||
@@unique([email])
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
ipAddress String? @db.Text
|
||||
userAgent String? @db.Text
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([token])
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id
|
||||
accountId String @db.Text
|
||||
providerId String @db.Text
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
accessToken String? @db.Text
|
||||
refreshToken String? @db.Text
|
||||
idToken String? @db.Text
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String? @db.Text
|
||||
password String? @db.Text
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id
|
||||
identifier String @db.Text
|
||||
value String @db.Text
|
||||
expiresAt DateTime
|
||||
createdAt DateTime?
|
||||
updatedAt DateTime?
|
||||
|
||||
@@map("verification")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["prismaSchemaFolder"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
model Todo {
|
||||
id Int @id @default(autoincrement())
|
||||
text String
|
||||
completed Boolean @default(false)
|
||||
|
||||
@@map("todo")
|
||||
}
|
||||
Reference in New Issue
Block a user