mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add express, automated mongodb atlas setup, fix stack architech
This commit is contained in:
5
.changeset/plain-symbols-hide.md
Normal file
5
.changeset/plain-symbols-hide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Add express backend, mongodb database and automated mongodb atlas setup
|
||||
@@ -24,10 +24,10 @@ Follow the prompts to configure your project or use the `--yes` flag for default
|
||||
- **Monorepo**: Turborepo for optimized build system and workspace management
|
||||
- **Frontend**: React, TanStack Router, TanStack Query, Tailwind CSS with shadcn/ui components
|
||||
- **Native Apps**: Create React Native apps with Expo for iOS and Android
|
||||
- **Backend Frameworks**: Choose between Hono or Elysia
|
||||
- **Backend Frameworks**: Choose between Hono, Express, or Elysia
|
||||
- **API Layer**: End-to-end type safety with tRPC
|
||||
- **Runtime Options**: Choose between Bun or Node.js for your server
|
||||
- **Database Options**: SQLite (via Turso), PostgreSQL, or no database
|
||||
- **Database Options**: SQLite (via Turso), PostgreSQL, MongoDB, or no database
|
||||
- **ORM Selection**: Choose between Drizzle ORM or Prisma
|
||||
- **Authentication**: Optional auth setup with Better-Auth
|
||||
- **Progressive Web App**: Add PWA support with service workers and installable apps
|
||||
@@ -45,7 +45,7 @@ Usage: create-better-t-stack [project-directory] [options]
|
||||
Options:
|
||||
-V, --version Output the version number
|
||||
-y, --yes Use default configuration
|
||||
--database <type> Database type (none, sqlite, postgres)
|
||||
--database <type> Database type (none, sqlite, postgres, mongodb)
|
||||
--orm <type> ORM type (none, drizzle, prisma)
|
||||
--auth Include authentication
|
||||
--no-auth Exclude authentication
|
||||
@@ -58,11 +58,8 @@ Options:
|
||||
--package-manager <pm> Package manager (npm, pnpm, bun)
|
||||
--install Install dependencies
|
||||
--no-install Skip installing dependencies
|
||||
--turso Set up Turso for SQLite database
|
||||
--no-turso Skip Turso setup
|
||||
--prisma-postgres Set up Prisma Postgres
|
||||
--no-prisma-postgres Skip Prisma Postgres setup
|
||||
--backend <framework> Backend framework (hono, elysia)
|
||||
--db-setup <setup> Database setup (turso, prisma-postgres, mongodb-atlas, none)
|
||||
--backend <framework> Backend framework (hono, express, elysia)
|
||||
--runtime <runtime> Runtime (bun, node)
|
||||
-h, --help Display help
|
||||
```
|
||||
@@ -93,3 +90,8 @@ Create a project with examples:
|
||||
```bash
|
||||
npx create-better-t-stack my-app --examples todo ai
|
||||
```
|
||||
|
||||
Create a project with Turso database setup:
|
||||
```bash
|
||||
npx create-better-t-stack my-app --db-setup turso
|
||||
```
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"template",
|
||||
"dist"
|
||||
],
|
||||
"files": ["template", "dist"],
|
||||
"keywords": [
|
||||
"better-t-stack",
|
||||
"typescript",
|
||||
|
||||
@@ -18,8 +18,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
||||
git: true,
|
||||
packageManager: getUserPkgManager(),
|
||||
noInstall: false,
|
||||
turso: false,
|
||||
prismaPostgres: false,
|
||||
dbSetup: "none",
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
};
|
||||
@@ -62,6 +61,11 @@ export const dependencyVersionMap = {
|
||||
"@hono/trpc-server": "^0.3.4",
|
||||
hono: "^4.7.5",
|
||||
|
||||
cors: "^2.8.5",
|
||||
express: "^5.1.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/cors": "^2.8.17",
|
||||
|
||||
ai: "^4.2.8",
|
||||
"@ai-sdk/google": "^1.2.3",
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ export async function setupBackendDependencies(
|
||||
dependencies.push("@elysiajs/node");
|
||||
devDependencies.push("tsx", "@types/node");
|
||||
}
|
||||
} else if (framework === "express") {
|
||||
dependencies.push("express", "cors");
|
||||
devDependencies.push("@types/express", "@types/cors");
|
||||
|
||||
if (runtime === "node") {
|
||||
devDependencies.push("tsx", "@types/node");
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime === "bun") {
|
||||
|
||||
@@ -54,8 +54,9 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
options.database,
|
||||
options.orm,
|
||||
options.packageManager,
|
||||
options.turso ?? options.database === "sqlite",
|
||||
options.prismaPostgres,
|
||||
options.dbSetup === "turso",
|
||||
options.dbSetup === "prisma-postgres",
|
||||
options.dbSetup === "mongodb-atlas",
|
||||
);
|
||||
|
||||
await setupAuthTemplate(
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
ProjectPackageManager,
|
||||
} from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
import { setupMongoDBAtlas } from "./mongodb-atlas-setup";
|
||||
import { setupPrismaPostgres } from "./prisma-postgres-setup";
|
||||
import { setupTurso } from "./turso-setup";
|
||||
|
||||
@@ -18,6 +19,7 @@ export async function setupDatabase(
|
||||
packageManager: ProjectPackageManager,
|
||||
setupTursoDb: boolean,
|
||||
setupPrismaPostgresDb: boolean,
|
||||
setupMongoDBAtlasDb: boolean,
|
||||
): Promise<void> {
|
||||
const s = spinner();
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
@@ -68,6 +70,18 @@ export async function setupDatabase(
|
||||
await setupPrismaPostgres(projectDir, packageManager);
|
||||
}
|
||||
}
|
||||
} else if (databaseType === "mongodb") {
|
||||
if (orm === "prisma") {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
|
||||
if (setupMongoDBAtlasDb) {
|
||||
await setupMongoDBAtlas(projectDir);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
s.stop(pc.red("Failed to set up database"));
|
||||
|
||||
@@ -44,14 +44,18 @@ export async function setupEnvironmentVariables(
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
||||
const databaseUrlLine =
|
||||
options.database === "sqlite"
|
||||
? ""
|
||||
: `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
||||
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.turso) {
|
||||
if (options.database === "sqlite" && options.dbSetup !== "turso") {
|
||||
if (!envContent.includes("TURSO_CONNECTION_URL")) {
|
||||
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function setupExamples(
|
||||
|
||||
if (
|
||||
examples.includes("ai") &&
|
||||
backend === "hono" &&
|
||||
(backend === "hono" || backend === "express") &&
|
||||
hasWebFrontend &&
|
||||
webAppExists
|
||||
) {
|
||||
@@ -89,6 +89,7 @@ async function updateServerIndexWithAIRoute(projectDir: string): Promise<void> {
|
||||
if (await fs.pathExists(serverIndexPath)) {
|
||||
let indexContent = await fs.readFile(serverIndexPath, "utf8");
|
||||
const isHono = indexContent.includes("hono");
|
||||
const isExpress = indexContent.includes("express");
|
||||
|
||||
if (isHono) {
|
||||
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";\nimport { stream } from "hono/streaming";`;
|
||||
@@ -110,6 +111,7 @@ app.post("/ai", async (c) => {
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});`;
|
||||
|
||||
// Add imports and route handler for Hono
|
||||
if (indexContent.includes("import {")) {
|
||||
const lastImportIndex = indexContent.lastIndexOf("import");
|
||||
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
||||
@@ -141,9 +143,66 @@ ${indexContent.substring(exportIndex)}`;
|
||||
${aiRouteHandler}`;
|
||||
}
|
||||
}
|
||||
} else if (isExpress) {
|
||||
// Express implementation
|
||||
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";`;
|
||||
|
||||
await fs.writeFile(serverIndexPath, indexContent);
|
||||
const aiRouteHandler = `
|
||||
// AI chat endpoint
|
||||
app.post("/ai", async (req, res) => {
|
||||
const { messages = [] } = req.body;
|
||||
|
||||
const result = streamText({
|
||||
model: google("gemini-1.5-flash"),
|
||||
messages,
|
||||
});
|
||||
|
||||
result.pipeDataStreamToResponse(res);
|
||||
});`;
|
||||
|
||||
// Add imports for Express
|
||||
if (
|
||||
indexContent.includes("import {") ||
|
||||
indexContent.includes("import ")
|
||||
) {
|
||||
const lastImportIndex = indexContent.lastIndexOf("import");
|
||||
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
||||
indexContent = `${indexContent.substring(0, endOfLastImport + 1)}
|
||||
${importSection}
|
||||
${indexContent.substring(endOfLastImport + 1)}`;
|
||||
} else {
|
||||
indexContent = `${importSection}
|
||||
|
||||
${indexContent}`;
|
||||
}
|
||||
|
||||
// Add route handler for Express
|
||||
const trpcHandlerIndex = indexContent.indexOf('app.use("/trpc"');
|
||||
if (trpcHandlerIndex !== -1) {
|
||||
indexContent = `${indexContent.substring(0, trpcHandlerIndex)}${aiRouteHandler}
|
||||
|
||||
${indexContent.substring(trpcHandlerIndex)}`;
|
||||
} else {
|
||||
const appListenIndex = indexContent.indexOf("app.listen(");
|
||||
if (appListenIndex !== -1) {
|
||||
// Find the line before app.listen
|
||||
const prevNewlineIndex = indexContent.lastIndexOf(
|
||||
"\n",
|
||||
appListenIndex,
|
||||
);
|
||||
indexContent = `${indexContent.substring(0, prevNewlineIndex)}${aiRouteHandler}
|
||||
|
||||
${indexContent.substring(prevNewlineIndex)}`;
|
||||
} else {
|
||||
// Fallback: append to the end
|
||||
indexContent = `${indexContent}
|
||||
|
||||
${aiRouteHandler}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(serverIndexPath, indexContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
121
apps/cli/src/helpers/mongodb-atlas-setup.ts
Normal file
121
apps/cli/src/helpers/mongodb-atlas-setup.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import path from "node:path";
|
||||
import { cancel, isCancel, log, text } from "@clack/prompts";
|
||||
import { execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import { commandExists } from "../utils/command-exists";
|
||||
|
||||
type MongoDBConfig = {
|
||||
connectionString: string;
|
||||
};
|
||||
|
||||
async function checkAtlasCLI(): Promise<boolean> {
|
||||
return commandExists("atlas");
|
||||
}
|
||||
|
||||
async function initMongoDBAtlas(
|
||||
serverDir: string,
|
||||
): Promise<MongoDBConfig | null> {
|
||||
try {
|
||||
const hasAtlas = await checkAtlasCLI();
|
||||
|
||||
if (!hasAtlas) {
|
||||
log.error(pc.red("MongoDB Atlas CLI not found."));
|
||||
log.info(
|
||||
pc.yellow(
|
||||
"Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/",
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info(pc.yellow("Setting up MongoDB Atlas..."));
|
||||
|
||||
await execa("atlas", ["deployments", "setup"], {
|
||||
cwd: serverDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
log.info(pc.yellow("Please enter your connection string"));
|
||||
|
||||
const connectionString = await text({
|
||||
message: "Paste your complete MongoDB connection string:",
|
||||
validate(value) {
|
||||
if (!value) return "Please enter a connection string";
|
||||
if (!value.startsWith("mongodb")) {
|
||||
return "URL should start with mongodb";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (isCancel(connectionString)) {
|
||||
cancel("MongoDB setup cancelled");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionString: connectionString as string,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
log.error(pc.red(error.message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeEnvFile(projectDir: string, config?: MongoDBConfig) {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
let envContent = "";
|
||||
|
||||
if (await fs.pathExists(envPath)) {
|
||||
envContent = await fs.readFile(envPath, "utf8");
|
||||
}
|
||||
|
||||
const mongoUrlLine = config
|
||||
? `DATABASE_URL="${config.connectionString}"`
|
||||
: `DATABASE_URL="mongodb://localhost:27017/mydb"`;
|
||||
|
||||
if (!envContent.includes("DATABASE_URL=")) {
|
||||
envContent += `\n${mongoUrlLine}`;
|
||||
} else {
|
||||
envContent = envContent.replace(
|
||||
/DATABASE_URL=.*(\r?\n|$)/,
|
||||
`${mongoUrlLine}$1`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, envContent.trim());
|
||||
}
|
||||
|
||||
function displayManualSetupInstructions() {
|
||||
log.info(`MongoDB Atlas Setup:
|
||||
|
||||
1. Install Atlas CLI: https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/
|
||||
2. Run 'atlas deployments setup' and follow prompts
|
||||
3. Get your connection string from the output
|
||||
4. Format: mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME
|
||||
5. Add to .env as DATABASE_URL="your_connection_string"`);
|
||||
}
|
||||
|
||||
export async function setupMongoDBAtlas(projectDir: string) {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
|
||||
try {
|
||||
const config = await initMongoDBAtlas(serverDir);
|
||||
|
||||
if (config) {
|
||||
await writeEnvFile(projectDir, config);
|
||||
log.success(
|
||||
pc.green("MongoDB Atlas connection string saved to .env file!"),
|
||||
);
|
||||
} else {
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(pc.red(`Error during MongoDB Atlas setup: ${error}`));
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { note } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type {
|
||||
ProjectAddons,
|
||||
ProjectDBSetup,
|
||||
ProjectDatabase,
|
||||
ProjectFrontend,
|
||||
ProjectOrm,
|
||||
@@ -18,6 +19,7 @@ export function displayPostInstallInstructions(
|
||||
addons: ProjectAddons[],
|
||||
runtime: ProjectRuntime,
|
||||
frontends: ProjectFrontend[],
|
||||
dbSetup?: ProjectDBSetup,
|
||||
) {
|
||||
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
||||
const cdCmd = `cd ${projectName}`;
|
||||
|
||||
@@ -372,9 +372,9 @@ function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
return database === "sqlite"
|
||||
? "template/with-prisma-sqlite"
|
||||
: "template/with-prisma-postgres";
|
||||
if (database === "sqlite") return "template/with-prisma-sqlite";
|
||||
if (database === "postgres") return "template/with-prisma-postgres";
|
||||
if (database === "mongodb") return "template/with-prisma-mongodb";
|
||||
}
|
||||
|
||||
return "template/base";
|
||||
@@ -388,9 +388,9 @@ function getAuthLibDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
return database === "sqlite"
|
||||
? "with-prisma-sqlite-lib"
|
||||
: "with-prisma-postgres-lib";
|
||||
if (database === "sqlite") return "with-prisma-sqlite-lib";
|
||||
if (database === "postgres") return "with-prisma-postgres-lib";
|
||||
if (database === "mongodb") return "with-prisma-mongodb-lib";
|
||||
}
|
||||
|
||||
throw new Error("Invalid ORM or database configuration for auth setup");
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { $ } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import { commandExists } from "../utils/command-exists";
|
||||
|
||||
type TursoConfig = {
|
||||
dbUrl: string;
|
||||
@@ -26,12 +27,7 @@ type TursoGroup = {
|
||||
};
|
||||
|
||||
async function isTursoInstalled() {
|
||||
try {
|
||||
const result = await $`turso --version`;
|
||||
return result.exitCode === 0;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return commandExists("turso");
|
||||
}
|
||||
|
||||
async function isTursoLoggedIn() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
ProjectAddons,
|
||||
ProjectBackend,
|
||||
ProjectConfig,
|
||||
ProjectDBSetup,
|
||||
ProjectDatabase,
|
||||
ProjectExamples,
|
||||
ProjectFrontend,
|
||||
@@ -37,8 +38,11 @@ async function main() {
|
||||
.version(getLatestCLIVersion())
|
||||
.argument("[project-directory]", "Project name/directory")
|
||||
.option("-y, --yes", "Use default configuration")
|
||||
.option("--database <type>", "Database type (none, sqlite, postgres)")
|
||||
.option("--orm <type>", "ORM type (none, drizzle, prisma)")
|
||||
.option(
|
||||
"--database <type>",
|
||||
"Database type (none, sqlite, postgres, mongodb)",
|
||||
)
|
||||
.option("--orm <type>", "ORM type (drizzle, prisma)")
|
||||
.option("--auth", "Include authentication")
|
||||
.option("--no-auth", "Exclude authentication")
|
||||
.option(
|
||||
@@ -56,11 +60,14 @@ async function main() {
|
||||
.option("--package-manager <pm>", "Package manager (npm, pnpm, bun)")
|
||||
.option("--install", "Install dependencies")
|
||||
.option("--no-install", "Skip installing dependencies")
|
||||
.option("--turso", "Set up Turso for SQLite database")
|
||||
.option("--no-turso", "Skip Turso setup")
|
||||
.option("--prisma-postgres", "Set up Prisma Postgres")
|
||||
.option("--no-prisma-postgres", "Skip Prisma Postgres setup")
|
||||
.option("--backend <framework>", "Backend framework (hono, elysia)")
|
||||
.option(
|
||||
"--db-setup <setup>",
|
||||
"Database setup (turso, prisma-postgres, mongodb-atlas, none)",
|
||||
)
|
||||
.option(
|
||||
"--backend <framework>",
|
||||
"Backend framework (hono, express, elysia)",
|
||||
)
|
||||
.option("--runtime <runtime>", "Runtime (bun, node)")
|
||||
.parse();
|
||||
|
||||
@@ -125,20 +132,41 @@ async function main() {
|
||||
function validateOptions(options: CLIOptions): void {
|
||||
if (
|
||||
options.database &&
|
||||
!["none", "sqlite", "postgres"].includes(options.database)
|
||||
!["none", "sqlite", "postgres", "mongodb"].includes(options.database)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, or postgres.`,
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, postgres, or mongodb.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.orm && !["none", "drizzle", "prisma"].includes(options.orm)) {
|
||||
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.dbSetup &&
|
||||
!["turso", "prisma-postgres", "mongodb-atlas", "none"].includes(
|
||||
options.dbSetup,
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid ORM type: ${options.orm}. Must be none, drizzle, or prisma.`,
|
||||
`Invalid database setup: ${options.dbSetup}. Must be turso, prisma-postgres, mongodb-atlas, or none.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.database === "mongodb" && options.orm === "drizzle") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"MongoDB is only available with Prisma. Cannot use --database mongodb with --orm drizzle",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -163,51 +191,62 @@ function validateOptions(options: CLIOptions): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ("turso" in options && options.turso === true) {
|
||||
if (options.dbSetup && options.dbSetup !== "none") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup requires a SQLite database. Cannot use --turso with --database none.",
|
||||
`Database setup requires a database. Cannot use --db-setup ${options.dbSetup} with --database none.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
"turso" in options &&
|
||||
options.turso === true &&
|
||||
options.database &&
|
||||
options.database !== "sqlite"
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Turso setup requires a SQLite database. Cannot use --turso with --database ${options.database}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
"turso" in options &&
|
||||
options.turso === true &&
|
||||
options.orm === "prisma"
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup is not compatible with Prisma. Cannot use --turso with --orm prisma",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ("prismaPostgres" in options && options.prismaPostgres === true) {
|
||||
if (
|
||||
(options.database && options.database !== "postgres") ||
|
||||
(options.orm && options.orm !== "prisma")
|
||||
) {
|
||||
// Check for database setup compatibility
|
||||
if (options.dbSetup === "turso") {
|
||||
if (options.database && options.database !== "sqlite") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database with Prisma ORM. Cannot use --prisma-postgres with incompatible database or ORM options.",
|
||||
`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 (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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -226,10 +265,13 @@ function validateOptions(options: CLIOptions): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
||||
if (
|
||||
options.backend &&
|
||||
!["hono", "elysia", "express"].includes(options.backend)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid backend framework: ${options.backend}. Must be hono or elysia.`,
|
||||
`Invalid backend framework: ${options.backend}. Must be hono, elysia, or express.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -409,42 +451,6 @@ function processFlags(
|
||||
}
|
||||
}
|
||||
|
||||
let database = options.database as ProjectDatabase | undefined;
|
||||
let orm: ProjectOrm | undefined;
|
||||
if (options.orm) {
|
||||
orm = options.orm as ProjectOrm;
|
||||
}
|
||||
|
||||
if ("prismaPostgres" in options && options.prismaPostgres === true) {
|
||||
if (!database) {
|
||||
database = "postgres" as ProjectDatabase;
|
||||
}
|
||||
if (!orm) {
|
||||
orm = "prisma" as ProjectOrm;
|
||||
}
|
||||
}
|
||||
|
||||
let auth: boolean | undefined = "auth" in options ? options.auth : undefined;
|
||||
let tursoOption: boolean | undefined =
|
||||
"turso" in options ? options.turso : undefined;
|
||||
|
||||
let prismaPostgresOption: boolean | undefined =
|
||||
"prismaPostgres" in options ? options.prismaPostgres : undefined;
|
||||
|
||||
if (
|
||||
database === "none" ||
|
||||
(database === "sqlite" && database !== undefined) ||
|
||||
(orm !== undefined && orm !== "prisma")
|
||||
) {
|
||||
prismaPostgresOption = false;
|
||||
}
|
||||
|
||||
if (database === "none") {
|
||||
orm = "none";
|
||||
auth = false;
|
||||
tursoOption = false;
|
||||
}
|
||||
|
||||
let examples: ProjectExamples[] | undefined;
|
||||
if ("examples" in options) {
|
||||
if (options.examples === false) {
|
||||
@@ -517,12 +523,44 @@ function processFlags(
|
||||
}
|
||||
}
|
||||
|
||||
let database = options.database as ProjectDatabase | undefined;
|
||||
let orm = options.orm as ProjectOrm | undefined;
|
||||
const auth = "auth" in options ? options.auth : undefined;
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config: Partial<ProjectConfig> = {};
|
||||
|
||||
if (projectDirectory) config.projectName = projectDirectory;
|
||||
@@ -532,9 +570,7 @@ function processFlags(
|
||||
if (packageManager) config.packageManager = packageManager;
|
||||
if ("git" in options) config.git = options.git;
|
||||
if ("install" in options) config.noInstall = !options.install;
|
||||
if (tursoOption !== undefined) config.turso = tursoOption;
|
||||
if (prismaPostgresOption !== undefined)
|
||||
config.prismaPostgres = prismaPostgresOption;
|
||||
if (dbSetup !== undefined) config.dbSetup = dbSetup;
|
||||
if (backend) config.backend = backend;
|
||||
if (runtime) config.runtime = runtime;
|
||||
if (frontend !== undefined) config.frontend = frontend;
|
||||
|
||||
@@ -16,6 +16,11 @@ export async function getBackendFrameworkChoice(
|
||||
label: "Hono",
|
||||
hint: "Lightweight, ultrafast web framework",
|
||||
},
|
||||
{
|
||||
value: "express",
|
||||
label: "Express",
|
||||
hint: "Fast, unopinionated, minimalist web framework for Node.js",
|
||||
},
|
||||
{
|
||||
value: "elysia",
|
||||
label: "Elysia",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { cancel, group } from "@clack/prompts";
|
||||
import { cancel, group, log } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type {
|
||||
ProjectAddons,
|
||||
ProjectBackend,
|
||||
ProjectConfig,
|
||||
ProjectDBSetup,
|
||||
ProjectDatabase,
|
||||
ProjectExamples,
|
||||
ProjectFrontend,
|
||||
@@ -15,16 +16,15 @@ import { getAddonsChoice } from "./addons";
|
||||
import { getAuthChoice } from "./auth";
|
||||
import { getBackendFrameworkChoice } from "./backend-framework";
|
||||
import { getDatabaseChoice } from "./database";
|
||||
import { getDBSetupChoice } from "./db-setup";
|
||||
import { getExamplesChoice } from "./examples";
|
||||
import { getFrontendChoice } from "./frontend-option";
|
||||
import { getGitChoice } from "./git";
|
||||
import { getNoInstallChoice } from "./install";
|
||||
import { getORMChoice } from "./orm";
|
||||
import { getPackageManagerChoice } from "./package-manager";
|
||||
import { getPrismaSetupChoice } from "./prisma-postgres";
|
||||
import { getProjectName } from "./project-name";
|
||||
import { getRuntimeChoice } from "./runtime";
|
||||
import { getTursoSetupChoice } from "./turso";
|
||||
|
||||
type PromptGroupResults = {
|
||||
projectName: string;
|
||||
@@ -36,8 +36,7 @@ type PromptGroupResults = {
|
||||
git: boolean;
|
||||
packageManager: ProjectPackageManager;
|
||||
noInstall: boolean;
|
||||
turso: boolean;
|
||||
prismaPostgres: boolean;
|
||||
dbSetup: ProjectDBSetup;
|
||||
backend: ProjectBackend;
|
||||
runtime: ProjectRuntime;
|
||||
frontend: ProjectFrontend[];
|
||||
@@ -46,6 +45,32 @@ type PromptGroupResults = {
|
||||
export async function gatherConfig(
|
||||
flags: Partial<ProjectConfig>,
|
||||
): Promise<ProjectConfig> {
|
||||
// Handle specific dbSetup scenarios to adjust database and ORM before prompts
|
||||
if (flags.dbSetup) {
|
||||
if (flags.dbSetup === "turso") {
|
||||
// Force database to be sqlite when turso is selected
|
||||
flags.database = "sqlite";
|
||||
|
||||
// If orm is explicitly set to prisma, warn and switch to drizzle
|
||||
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") {
|
||||
// Force database and orm for prisma-postgres
|
||||
flags.database = "postgres";
|
||||
flags.orm = "prisma";
|
||||
} else if (flags.dbSetup === "mongodb-atlas") {
|
||||
// Force database for mongodb-atlas
|
||||
flags.database = "mongodb";
|
||||
flags.orm = "prisma"; // MongoDB only works with Prisma
|
||||
}
|
||||
}
|
||||
|
||||
const result = await group<PromptGroupResults>(
|
||||
{
|
||||
projectName: async () => {
|
||||
@@ -56,21 +81,15 @@ export async function gatherConfig(
|
||||
runtime: () => getRuntimeChoice(flags.runtime),
|
||||
database: () => getDatabaseChoice(flags.database),
|
||||
orm: ({ results }) =>
|
||||
getORMChoice(flags.orm, results.database !== "none"),
|
||||
getORMChoice(flags.orm, results.database !== "none", results.database),
|
||||
auth: ({ results }) =>
|
||||
getAuthChoice(
|
||||
flags.auth,
|
||||
results.database !== "none",
|
||||
results.frontend,
|
||||
),
|
||||
turso: ({ results }) =>
|
||||
results.database === "sqlite" && results.orm !== "prisma"
|
||||
? getTursoSetupChoice(flags.turso)
|
||||
: Promise.resolve(false),
|
||||
prismaPostgres: ({ results }) =>
|
||||
results.database === "postgres" && results.orm === "prisma"
|
||||
? getPrismaSetupChoice(flags.prismaPostgres)
|
||||
: Promise.resolve(false),
|
||||
dbSetup: ({ results }) =>
|
||||
getDBSetupChoice(results.database ?? "none", flags.dbSetup),
|
||||
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
||||
examples: ({ results }) =>
|
||||
getExamplesChoice(
|
||||
@@ -102,8 +121,7 @@ export async function gatherConfig(
|
||||
git: result.git,
|
||||
packageManager: result.packageManager,
|
||||
noInstall: result.noInstall,
|
||||
turso: result.turso,
|
||||
prismaPostgres: result.prismaPostgres,
|
||||
dbSetup: result.dbSetup,
|
||||
backend: result.backend,
|
||||
runtime: result.runtime,
|
||||
};
|
||||
|
||||
@@ -26,6 +26,11 @@ export async function getDatabaseChoice(
|
||||
label: "PostgreSQL",
|
||||
hint: "Traditional relational database",
|
||||
},
|
||||
{
|
||||
value: "mongodb",
|
||||
label: "MongoDB",
|
||||
hint: "NoSQL document-oriented database",
|
||||
},
|
||||
],
|
||||
initialValue: DEFAULT_CONFIG.database,
|
||||
});
|
||||
|
||||
57
apps/cli/src/prompts/db-setup.ts
Normal file
57
apps/cli/src/prompts/db-setup.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { cancel, isCancel, select } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectDBSetup } from "../types";
|
||||
|
||||
export async function getDBSetupChoice(
|
||||
databaseType: string,
|
||||
dbSetup: ProjectDBSetup | undefined,
|
||||
): Promise<ProjectDBSetup> {
|
||||
if (dbSetup !== undefined) return dbSetup as ProjectDBSetup;
|
||||
|
||||
let options: Array<{ value: ProjectDBSetup; label: string; hint: string }> =
|
||||
[];
|
||||
|
||||
if (databaseType === "sqlite") {
|
||||
options = [
|
||||
{
|
||||
value: "turso" as const,
|
||||
label: "Turso",
|
||||
hint: "Cloud SQLite with libSQL",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else if (databaseType === "postgres") {
|
||||
options = [
|
||||
{
|
||||
value: "prisma-postgres" as const,
|
||||
label: "Prisma Postgres",
|
||||
hint: "Managed by Prisma",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else if (databaseType === "mongodb") {
|
||||
options = [
|
||||
{
|
||||
value: "mongodb-atlas" as const,
|
||||
label: "MongoDB Atlas",
|
||||
hint: "Cloud MongoDB service",
|
||||
},
|
||||
{ value: "none" as const, label: "None", hint: "Manual setup" },
|
||||
];
|
||||
} else {
|
||||
return "none";
|
||||
}
|
||||
|
||||
const response = await select<ProjectDBSetup>({
|
||||
message: `Select ${databaseType} setup option`,
|
||||
options,
|
||||
initialValue: "none",
|
||||
});
|
||||
|
||||
if (isCancel(response)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export async function getExamplesChoice(
|
||||
});
|
||||
}
|
||||
|
||||
if (backend === "hono") {
|
||||
if (backend === "hono" || backend === "express") {
|
||||
response = await multiselect<ProjectExamples>({
|
||||
message: "Include examples",
|
||||
options: [
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { cancel, isCancel, select } from "@clack/prompts";
|
||||
import { cancel, isCancel, log, select } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { ProjectOrm } from "../types";
|
||||
import type { ProjectDatabase, ProjectOrm } from "../types";
|
||||
|
||||
export async function getORMChoice(
|
||||
orm: ProjectOrm | undefined,
|
||||
hasDatabase: boolean,
|
||||
database?: ProjectDatabase,
|
||||
): Promise<ProjectOrm> {
|
||||
if (!hasDatabase) return "none";
|
||||
if (orm !== undefined) return orm;
|
||||
|
||||
if (database === "mongodb") {
|
||||
log.info("Only Prisma is supported with MongoDB.");
|
||||
return "prisma";
|
||||
}
|
||||
|
||||
const response = await select<ProjectOrm>({
|
||||
message: "Select ORM",
|
||||
options: [
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { cancel, confirm, isCancel } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
|
||||
export async function getPrismaSetupChoice(
|
||||
prismaSetup?: boolean,
|
||||
): Promise<boolean> {
|
||||
if (prismaSetup !== undefined) return prismaSetup;
|
||||
|
||||
const response = await confirm({
|
||||
message: "Set up Prisma Postgres database?",
|
||||
initialValue: DEFAULT_CONFIG.prismaPostgres,
|
||||
});
|
||||
|
||||
if (isCancel(response)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { cancel, confirm, isCancel } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
|
||||
export async function getTursoSetupChoice(turso?: boolean): Promise<boolean> {
|
||||
if (turso !== undefined) return turso;
|
||||
|
||||
const response = await confirm({
|
||||
message: "Set up Turso database?",
|
||||
initialValue: DEFAULT_CONFIG.turso,
|
||||
});
|
||||
|
||||
if (isCancel(response)) {
|
||||
cancel(pc.red("Operation cancelled"));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
export type ProjectDatabase = "sqlite" | "postgres" | "none";
|
||||
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
||||
export type ProjectDatabase = "sqlite" | "postgres" | "mongodb" | "none";
|
||||
export type ProjectOrm = "drizzle" | "prisma";
|
||||
export type ProjectPackageManager = "npm" | "pnpm" | "bun";
|
||||
export type ProjectAddons = "pwa" | "biome" | "tauri" | "husky";
|
||||
export type ProjectBackend = "hono" | "elysia";
|
||||
export type ProjectBackend = "hono" | "elysia" | "express";
|
||||
export type ProjectRuntime = "node" | "bun";
|
||||
export type ProjectExamples = "todo" | "ai";
|
||||
export type ProjectFrontend =
|
||||
@@ -10,6 +10,11 @@ export type ProjectFrontend =
|
||||
| "tanstack-router"
|
||||
| "tanstack-start"
|
||||
| "native";
|
||||
export type ProjectDBSetup =
|
||||
| "turso"
|
||||
| "prisma-postgres"
|
||||
| "mongodb-atlas"
|
||||
| "none";
|
||||
|
||||
export interface ProjectConfig {
|
||||
projectName: string;
|
||||
@@ -22,9 +27,8 @@ export interface ProjectConfig {
|
||||
examples: ProjectExamples[];
|
||||
git: boolean;
|
||||
packageManager: ProjectPackageManager;
|
||||
noInstall?: boolean;
|
||||
turso?: boolean;
|
||||
prismaPostgres: boolean;
|
||||
noInstall: boolean;
|
||||
dbSetup: ProjectDBSetup;
|
||||
frontend: ProjectFrontend[];
|
||||
}
|
||||
|
||||
@@ -39,8 +43,7 @@ export type CLIOptions = {
|
||||
git?: boolean;
|
||||
packageManager?: string;
|
||||
install?: boolean;
|
||||
turso?: boolean;
|
||||
prismaPostgres?: boolean;
|
||||
dbSetup?: string;
|
||||
backend?: string;
|
||||
runtime?: string;
|
||||
};
|
||||
|
||||
16
apps/cli/src/utils/command-exists.ts
Normal file
16
apps/cli/src/utils/command-exists.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { execa } from "execa";
|
||||
|
||||
export async function commandExists(command: string): Promise<boolean> {
|
||||
try {
|
||||
const isWindows = process.platform === "win32";
|
||||
if (isWindows) {
|
||||
const result = await execa("where", [command]);
|
||||
return result.exitCode === 0;
|
||||
}
|
||||
|
||||
const result = await execa("which", [command]);
|
||||
return result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -60,14 +60,8 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
||||
configDisplay.push(`${pc.blue("Skip Install:")} ${config.noInstall}`);
|
||||
}
|
||||
|
||||
if (config.turso !== undefined) {
|
||||
configDisplay.push(`${pc.blue("Turso Setup:")} ${config.turso}`);
|
||||
}
|
||||
|
||||
if (config.prismaPostgres !== undefined) {
|
||||
configDisplay.push(
|
||||
`${pc.blue("Prisma Postgres Setup:")} ${config.prismaPostgres ? "Yes" : "No"}`,
|
||||
);
|
||||
if (config.dbSetup !== undefined) {
|
||||
configDisplay.push(`${pc.blue("Database Setup:")} ${config.dbSetup}`);
|
||||
}
|
||||
|
||||
return configDisplay.join("\n");
|
||||
|
||||
@@ -12,8 +12,8 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
||||
flags.push(`--orm ${config.orm}`);
|
||||
}
|
||||
|
||||
if (config.database === "sqlite") {
|
||||
flags.push(config.turso ? "--turso" : "--no-turso");
|
||||
if (config.dbSetup && config.dbSetup !== "none") {
|
||||
flags.push(`--db-setup ${config.dbSetup}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
||||
import { fromNodeHeaders } from "better-auth/node";
|
||||
import { auth } from "./auth";
|
||||
|
||||
export async function createContext(opts: CreateExpressContextOptions) {
|
||||
const session = await auth.api.getSession({
|
||||
headers: fromNodeHeaders(opts.req.headers),
|
||||
});
|
||||
return {
|
||||
session,
|
||||
};
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||
@@ -0,0 +1,34 @@
|
||||
import "dotenv/config";
|
||||
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import { auth } from "./lib/auth";
|
||||
import { createContext } from "./lib/context";
|
||||
import { appRouter } from "./routers/index";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.CORS_ORIGIN || "",
|
||||
methods: ["GET", "POST", "OPTIONS"],
|
||||
allowedHeaders: ["Content-Type", "Authorization"],
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.all("/api/auth{/*path}", toNodeHandler(auth));
|
||||
app.use(express.json());
|
||||
|
||||
|
||||
app.use("/trpc", createExpressMiddleware({ router: appRouter, createContext }));
|
||||
|
||||
|
||||
app.get("/", (_req, res) => {
|
||||
res.status(200).send("OK");
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log("Server is running on port 3000");
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import prisma from "../../prisma";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: "mongodb",
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true },
|
||||
advanced: {
|
||||
defaultCookieAttributes: {
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
29
apps/cli/template/with-express/apps/server/src/index.ts
Normal file
29
apps/cli/template/with-express/apps/server/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import "dotenv/config";
|
||||
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import { createContext } from "./lib/context";
|
||||
import { appRouter } from "./routers/index";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.CORS_ORIGIN || "",
|
||||
methods: ["GET", "POST", "OPTIONS"],
|
||||
allowedHeaders: ["Content-Type", "Authorization"],
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use("/trpc", createExpressMiddleware({ router: appRouter, createContext }));
|
||||
|
||||
app.get("/", (_req, res) => {
|
||||
res.status(200).send("OK");
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log("Server is running on port 3000");
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
||||
|
||||
export async function createContext(opts: CreateExpressContextOptions) {
|
||||
return {
|
||||
session: null,
|
||||
};
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||
@@ -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 @map("_id")
|
||||
name String
|
||||
email String
|
||||
emailVerified Boolean
|
||||
image String?
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
|
||||
@@unique([email])
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @map("_id")
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([token])
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @map("_id")
|
||||
accountId String
|
||||
providerId String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
idToken String?
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
password String?
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id @map("_id")
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime?
|
||||
updatedAt DateTime?
|
||||
|
||||
@@map("verification")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["prismaSchemaFolder"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mongodb"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
model Todo {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
text String
|
||||
completed Boolean @default(false)
|
||||
|
||||
@@map("todo")
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"dev": "next dev --turbopack",
|
||||
"start": "next start",
|
||||
"check": "biome check --write .",
|
||||
"postinstall": "fumadocs-mdx"
|
||||
|
||||
@@ -74,32 +74,45 @@ const StackArchitect = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (stack.database === "none" && stack.orm !== "none") {
|
||||
setStack((prev) => ({ ...prev, orm: "none" }));
|
||||
}
|
||||
|
||||
if (stack.database !== "postgres" || stack.orm !== "prisma") {
|
||||
if (stack.prismaPostgres === "true") {
|
||||
setStack((prev) => ({ ...prev, prismaPostgres: "false" }));
|
||||
if (stack.database === "none") {
|
||||
if (stack.orm !== "none") {
|
||||
setStack((prev) => ({ ...prev, orm: "none" }));
|
||||
}
|
||||
if (stack.auth === "true") {
|
||||
setStack((prev) => ({ ...prev, auth: "false" }));
|
||||
}
|
||||
if (stack.dbSetup !== "none") {
|
||||
setStack((prev) => ({ ...prev, dbSetup: "none" }));
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.database !== "sqlite" || stack.orm === "prisma") {
|
||||
if (stack.turso === "true") {
|
||||
setStack((prev) => ({ ...prev, turso: "false" }));
|
||||
}
|
||||
if (stack.database === "mongodb" && stack.orm === "drizzle") {
|
||||
setStack((prev) => ({ ...prev, orm: "prisma" }));
|
||||
}
|
||||
|
||||
if (stack.database === "none" && stack.auth === "true") {
|
||||
setStack((prev) => ({ ...prev, auth: "false" }));
|
||||
if (stack.dbSetup === "turso") {
|
||||
if (stack.database !== "sqlite") {
|
||||
setStack((prev) => ({ ...prev, database: "sqlite" }));
|
||||
}
|
||||
if (stack.orm === "prisma") {
|
||||
setStack((prev) => ({ ...prev, orm: "drizzle" }));
|
||||
}
|
||||
} else if (stack.dbSetup === "prisma-postgres") {
|
||||
if (stack.database !== "postgres") {
|
||||
setStack((prev) => ({ ...prev, database: "postgres" }));
|
||||
}
|
||||
if (stack.orm !== "prisma") {
|
||||
setStack((prev) => ({ ...prev, orm: "prisma" }));
|
||||
}
|
||||
} else if (stack.dbSetup === "mongodb-atlas") {
|
||||
if (stack.database !== "mongodb") {
|
||||
setStack((prev) => ({ ...prev, database: "mongodb" }));
|
||||
}
|
||||
if (stack.orm !== "prisma") {
|
||||
setStack((prev) => ({ ...prev, orm: "prisma" }));
|
||||
}
|
||||
}
|
||||
}, [
|
||||
stack.database,
|
||||
stack.orm,
|
||||
stack.prismaPostgres,
|
||||
stack.turso,
|
||||
stack.auth,
|
||||
]);
|
||||
}, [stack.database, stack.orm, stack.dbSetup, stack.auth]);
|
||||
|
||||
useEffect(() => {
|
||||
const cmd = generateCommand(stack);
|
||||
@@ -113,6 +126,33 @@ const StackArchitect = () => {
|
||||
|
||||
notes.frontend = [];
|
||||
|
||||
notes.dbSetup = [];
|
||||
if (stack.database === "none") {
|
||||
notes.dbSetup.push("Database setup requires a database.");
|
||||
} else {
|
||||
if (stack.dbSetup === "turso") {
|
||||
if (stack.database !== "sqlite") {
|
||||
notes.dbSetup.push("Turso setup requires SQLite database.");
|
||||
}
|
||||
if (stack.orm === "prisma") {
|
||||
notes.dbSetup.push("Turso is not compatible with Prisma ORM.");
|
||||
}
|
||||
} else if (stack.dbSetup === "prisma-postgres") {
|
||||
if (stack.database !== "postgres") {
|
||||
notes.dbSetup.push(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database.",
|
||||
);
|
||||
}
|
||||
if (stack.orm !== "prisma") {
|
||||
notes.dbSetup.push("Prisma PostgreSQL setup requires Prisma ORM.");
|
||||
}
|
||||
} else if (stack.dbSetup === "mongodb-atlas") {
|
||||
if (stack.database !== "mongodb") {
|
||||
notes.dbSetup.push("MongoDB Atlas setup requires MongoDB database.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notes.addons = [];
|
||||
if (!hasWebFrontend) {
|
||||
notes.addons.push("PWA and Tauri are only available with React Web.");
|
||||
@@ -125,6 +165,8 @@ const StackArchitect = () => {
|
||||
notes.orm.push(
|
||||
"ORM options are only available when a database is selected.",
|
||||
);
|
||||
} else if (stack.database === "mongodb" && stack.orm === "drizzle") {
|
||||
notes.orm.push("MongoDB is only available with Prisma ORM.");
|
||||
}
|
||||
|
||||
notes.auth = [];
|
||||
@@ -132,23 +174,6 @@ const StackArchitect = () => {
|
||||
notes.auth.push("Authentication requires a database.");
|
||||
}
|
||||
|
||||
notes.turso = [];
|
||||
if (stack.database !== "sqlite") {
|
||||
notes.turso.push(
|
||||
"Turso integration is only available with SQLite database.",
|
||||
);
|
||||
}
|
||||
if (stack.orm === "prisma") {
|
||||
notes.turso.push("Turso is not compatible with Prisma ORM.");
|
||||
}
|
||||
|
||||
notes.prismaPostgres = [];
|
||||
if (stack.database !== "postgres" || stack.orm !== "prisma") {
|
||||
notes.prismaPostgres.push(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database with Prisma ORM.",
|
||||
);
|
||||
}
|
||||
|
||||
notes.examples = [];
|
||||
if (!hasWebFrontend) {
|
||||
notes.examples.push(
|
||||
@@ -198,12 +223,8 @@ const StackArchitect = () => {
|
||||
flags.push("--no-auth");
|
||||
}
|
||||
|
||||
if (stackState.turso === "true") {
|
||||
flags.push("--turso");
|
||||
}
|
||||
|
||||
if (stackState.prismaPostgres === "true") {
|
||||
flags.push("--prisma-postgres");
|
||||
if (stackState.dbSetup !== "none") {
|
||||
flags.push(`--db-setup ${stackState.dbSetup}`);
|
||||
}
|
||||
|
||||
if (stackState.backendFramework !== "hono") {
|
||||
@@ -263,7 +284,6 @@ const StackArchitect = () => {
|
||||
if (currentSelection.length === 1) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
frontend: currentSelection.filter((id) => id !== techId),
|
||||
@@ -296,6 +316,10 @@ const StackArchitect = () => {
|
||||
prev.frontend.includes("react-router") ||
|
||||
prev.frontend.includes("tanstack-start");
|
||||
|
||||
const hasPWACompatibleFrontend =
|
||||
prev.frontend.includes("tanstack-router") ||
|
||||
prev.frontend.includes("react-router");
|
||||
|
||||
if (index >= 0) {
|
||||
currentArray.splice(index, 1);
|
||||
} else {
|
||||
@@ -318,8 +342,7 @@ const StackArchitect = () => {
|
||||
if (
|
||||
category === "addons" &&
|
||||
(techId === "pwa" || techId === "tauri") &&
|
||||
!prev.frontend.includes("tanstack-router") &&
|
||||
!prev.frontend.includes("react-router")
|
||||
!hasPWACompatibleFrontend
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
@@ -342,45 +365,40 @@ const StackArchitect = () => {
|
||||
}
|
||||
|
||||
if (category === "database") {
|
||||
let updatedState = { ...prev, database: techId };
|
||||
|
||||
if (techId === "none") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
updatedState = {
|
||||
...updatedState,
|
||||
orm: "none",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
dbSetup: "none",
|
||||
auth: "false",
|
||||
};
|
||||
}
|
||||
} else if (prev.database === "none") {
|
||||
updatedState.orm = techId === "mongodb" ? "prisma" : "drizzle";
|
||||
updatedState.dbSetup = "none";
|
||||
|
||||
if (prev.database === "none") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
orm: "drizzle",
|
||||
turso: techId === "sqlite" ? prev.turso : "false",
|
||||
prismaPostgres:
|
||||
techId === "postgres" && prev.orm === "prisma"
|
||||
? prev.prismaPostgres
|
||||
: "false",
|
||||
auth:
|
||||
hasWebFrontend(prev.frontend) ||
|
||||
prev.frontend.includes("native")
|
||||
? "true"
|
||||
: "false",
|
||||
};
|
||||
}
|
||||
|
||||
const updatedState = {
|
||||
...prev,
|
||||
database: techId,
|
||||
};
|
||||
|
||||
if (techId === "sqlite") {
|
||||
updatedState.prismaPostgres = "false";
|
||||
} else if (techId === "postgres" && prev.orm === "prisma") {
|
||||
const hasCompatibleFrontend =
|
||||
prev.frontend.length > 0 && !prev.frontend.includes("none");
|
||||
if (hasCompatibleFrontend) {
|
||||
updatedState.auth = "true";
|
||||
}
|
||||
} else {
|
||||
updatedState.turso = "false";
|
||||
if (techId === "mongodb" && updatedState.orm === "drizzle") {
|
||||
updatedState.orm = "prisma";
|
||||
}
|
||||
|
||||
if (updatedState.dbSetup !== "none") {
|
||||
if (
|
||||
(updatedState.dbSetup === "turso" && techId !== "sqlite") ||
|
||||
(updatedState.dbSetup === "prisma-postgres" &&
|
||||
techId !== "postgres") ||
|
||||
(updatedState.dbSetup === "mongodb-atlas" &&
|
||||
techId !== "mongodb")
|
||||
) {
|
||||
updatedState.dbSetup = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedState;
|
||||
@@ -396,31 +414,41 @@ const StackArchitect = () => {
|
||||
orm: techId,
|
||||
};
|
||||
|
||||
if (techId === "prisma") {
|
||||
updatedState.turso = "false";
|
||||
if (prev.database === "postgres") {
|
||||
} else {
|
||||
updatedState.prismaPostgres = "false";
|
||||
if (updatedState.dbSetup !== "none") {
|
||||
if (
|
||||
(updatedState.dbSetup === "turso" && techId === "prisma") ||
|
||||
(updatedState.dbSetup === "prisma-postgres" &&
|
||||
techId !== "prisma")
|
||||
) {
|
||||
updatedState.dbSetup = "none";
|
||||
}
|
||||
} else if (techId === "drizzle" || techId === "none") {
|
||||
updatedState.prismaPostgres = "false";
|
||||
}
|
||||
|
||||
return updatedState;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "turso" &&
|
||||
(prev.database !== "sqlite" || prev.orm === "prisma")
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
if (category === "dbSetup") {
|
||||
if (prev.database === "none" && techId !== "none") {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "prismaPostgres" &&
|
||||
(prev.database !== "postgres" || prev.orm !== "prisma")
|
||||
) {
|
||||
return prev;
|
||||
const updatedState = {
|
||||
...prev,
|
||||
dbSetup: techId,
|
||||
};
|
||||
|
||||
if (techId === "turso") {
|
||||
updatedState.database = "sqlite";
|
||||
updatedState.orm = "drizzle";
|
||||
} else if (techId === "prisma-postgres") {
|
||||
updatedState.database = "postgres";
|
||||
updatedState.orm = "prisma";
|
||||
} else if (techId === "mongodb-atlas") {
|
||||
updatedState.database = "mongodb";
|
||||
updatedState.orm = "prisma";
|
||||
}
|
||||
|
||||
return updatedState;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -440,15 +468,6 @@ const StackArchitect = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
const hasWebFrontend = useCallback((frontendOptions: string[]) => {
|
||||
return (
|
||||
frontendOptions.includes("tanstack-router") ||
|
||||
frontendOptions.includes("react-router") ||
|
||||
frontendOptions.includes("tanstack-start") ||
|
||||
frontendOptions.includes("native")
|
||||
);
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = useCallback(() => {
|
||||
navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
@@ -698,12 +717,16 @@ const StackArchitect = () => {
|
||||
|
||||
const isDisabled =
|
||||
(activeTab === "orm" && stack.database === "none") ||
|
||||
(activeTab === "turso" &&
|
||||
(stack.database !== "sqlite" ||
|
||||
stack.orm === "prisma")) ||
|
||||
(activeTab === "prismaPostgres" &&
|
||||
(stack.database !== "postgres" ||
|
||||
stack.orm !== "prisma")) ||
|
||||
(activeTab === "dbSetup" &&
|
||||
((tech.id !== "none" && stack.database === "none") ||
|
||||
(tech.id === "turso" &&
|
||||
(stack.database !== "sqlite" ||
|
||||
stack.orm === "prisma")) ||
|
||||
(tech.id === "prisma-postgres" &&
|
||||
(stack.database !== "postgres" ||
|
||||
stack.orm !== "prisma")) ||
|
||||
(tech.id === "mongodb-atlas" &&
|
||||
stack.database !== "mongodb"))) ||
|
||||
(activeTab === "examples" &&
|
||||
(((tech.id === "todo" || tech.id === "ai") &&
|
||||
!hasWebFrontendSelected) ||
|
||||
@@ -866,7 +889,7 @@ const StackArchitect = () => {
|
||||
}
|
||||
</span>
|
||||
|
||||
{stack.orm && stack.database !== "none" && (
|
||||
{stack.orm !== "none" && stack.database !== "none" && (
|
||||
<span className="inline-flex items-center rounded border border-cyan-300 bg-cyan-100 px-1.5 py-0.5 text-cyan-800 text-xs dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300">
|
||||
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.icon}{" "}
|
||||
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
|
||||
@@ -882,37 +905,18 @@ const StackArchitect = () => {
|
||||
</span>
|
||||
)}
|
||||
|
||||
{stack.turso === "true" &&
|
||||
stack.database === "sqlite" &&
|
||||
stack.orm !== "prisma" && (
|
||||
<span className="inline-flex items-center rounded border border-pink-300 bg-pink-100 px-1.5 py-0.5 text-pink-800 text-xs dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300">
|
||||
{
|
||||
TECH_OPTIONS.turso.find((t) => t.id === stack.turso)
|
||||
?.icon
|
||||
}{" "}
|
||||
{
|
||||
TECH_OPTIONS.turso.find((t) => t.id === stack.turso)
|
||||
?.name
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{stack.prismaPostgres === "true" &&
|
||||
stack.database === "postgres" &&
|
||||
stack.orm === "prisma" && (
|
||||
<span className="inline-flex items-center rounded border border-indigo-300 bg-indigo-100 px-1.5 py-0.5 text-indigo-800 text-xs dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300">
|
||||
{
|
||||
TECH_OPTIONS.prismaPostgres.find(
|
||||
(t) => t.id === stack.prismaPostgres,
|
||||
)?.icon
|
||||
}{" "}
|
||||
{
|
||||
TECH_OPTIONS.prismaPostgres.find(
|
||||
(t) => t.id === stack.prismaPostgres,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
{stack.dbSetup !== "none" && (
|
||||
<span className="inline-flex items-center rounded border border-pink-300 bg-pink-100 px-1.5 py-0.5 text-pink-800 text-xs dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300">
|
||||
{
|
||||
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
|
||||
?.icon
|
||||
}{" "}
|
||||
{
|
||||
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
|
||||
?.name
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{stack.addons.map((addonId) => {
|
||||
const addon = TECH_OPTIONS.addons.find(
|
||||
|
||||
@@ -74,6 +74,13 @@ export const TECH_OPTIONS = {
|
||||
icon: "🦊",
|
||||
color: "from-purple-500 to-purple-700",
|
||||
},
|
||||
{
|
||||
id: "express",
|
||||
name: "Express",
|
||||
description: "Popular Node.js framework",
|
||||
icon: "🚂",
|
||||
color: "from-gray-500 to-gray-700",
|
||||
},
|
||||
],
|
||||
database: [
|
||||
{
|
||||
@@ -91,6 +98,13 @@ export const TECH_OPTIONS = {
|
||||
icon: "🐘",
|
||||
color: "from-indigo-400 to-indigo-600",
|
||||
},
|
||||
{
|
||||
id: "mongodb",
|
||||
name: "MongoDB",
|
||||
description: "NoSQL document database",
|
||||
icon: "🍃",
|
||||
color: "from-green-400 to-green-600",
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No Database",
|
||||
@@ -115,6 +129,44 @@ export const TECH_OPTIONS = {
|
||||
icon: "◮",
|
||||
color: "from-purple-400 to-purple-600",
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No ORM",
|
||||
description: "Skip ORM integration",
|
||||
icon: "🚫",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
},
|
||||
],
|
||||
dbSetup: [
|
||||
{
|
||||
id: "turso",
|
||||
name: "Turso",
|
||||
description: "SQLite cloud database powered by libSQL",
|
||||
icon: "☁️",
|
||||
color: "from-pink-400 to-pink-600",
|
||||
},
|
||||
{
|
||||
id: "prisma-postgres",
|
||||
name: "Prisma PostgreSQL",
|
||||
description: "Set up PostgreSQL with Prisma",
|
||||
icon: "🐘",
|
||||
color: "from-indigo-400 to-indigo-600",
|
||||
},
|
||||
{
|
||||
id: "mongodb-atlas",
|
||||
name: "MongoDB Atlas",
|
||||
description: "Cloud MongoDB setup with Atlas",
|
||||
icon: "🌩️",
|
||||
color: "from-green-400 to-green-600",
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "Basic Setup",
|
||||
description: "No cloud DB integration",
|
||||
icon: "💻",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
auth: [
|
||||
{
|
||||
@@ -133,42 +185,6 @@ export const TECH_OPTIONS = {
|
||||
color: "from-red-400 to-red-600",
|
||||
},
|
||||
],
|
||||
turso: [
|
||||
{
|
||||
id: "true",
|
||||
name: "Turso",
|
||||
description: "SQLite cloud database",
|
||||
icon: "☁️",
|
||||
color: "from-pink-400 to-pink-600",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "false",
|
||||
name: "No Turso",
|
||||
description: "Skip Turso integration",
|
||||
icon: "🚫",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
prismaPostgres: [
|
||||
{
|
||||
id: "true",
|
||||
name: "Prisma PostgreSQL",
|
||||
description: "Set up PostgreSQL with Prisma",
|
||||
icon: "🐘",
|
||||
color: "from-indigo-400 to-indigo-600",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "false",
|
||||
name: "Skip Prisma PostgreSQL",
|
||||
description: "Basic Prisma setup",
|
||||
icon: "🚫",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
packageManager: [
|
||||
{
|
||||
id: "npm",
|
||||
@@ -293,9 +309,8 @@ export const PRESET_TEMPLATES = [
|
||||
backendFramework: "hono",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
dbSetup: "none",
|
||||
auth: "true",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: [],
|
||||
examples: [],
|
||||
@@ -314,9 +329,8 @@ export const PRESET_TEMPLATES = [
|
||||
backendFramework: "hono",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
dbSetup: "none",
|
||||
auth: "true",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: [],
|
||||
examples: [],
|
||||
@@ -335,9 +349,8 @@ export const PRESET_TEMPLATES = [
|
||||
backendFramework: "hono",
|
||||
database: "postgres",
|
||||
orm: "drizzle",
|
||||
dbSetup: "none",
|
||||
auth: "false",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: [],
|
||||
examples: [],
|
||||
@@ -356,9 +369,8 @@ export const PRESET_TEMPLATES = [
|
||||
backendFramework: "hono",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
dbSetup: "turso",
|
||||
auth: "true",
|
||||
turso: "true",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: ["pwa", "biome", "husky", "tauri"],
|
||||
examples: ["todo", "ai"],
|
||||
@@ -374,10 +386,9 @@ export type StackState = {
|
||||
runtime: string;
|
||||
backendFramework: string;
|
||||
database: string;
|
||||
orm: string | null;
|
||||
orm: string;
|
||||
dbSetup: string;
|
||||
auth: string;
|
||||
turso: string;
|
||||
prismaPostgres: string;
|
||||
packageManager: string;
|
||||
addons: string[];
|
||||
examples: string[];
|
||||
@@ -392,9 +403,8 @@ export const DEFAULT_STACK: StackState = {
|
||||
backendFramework: "hono",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
dbSetup: "none",
|
||||
auth: "true",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: [],
|
||||
examples: [],
|
||||
|
||||
Reference in New Issue
Block a user