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
|
- **Monorepo**: Turborepo for optimized build system and workspace management
|
||||||
- **Frontend**: React, TanStack Router, TanStack Query, Tailwind CSS with shadcn/ui components
|
- **Frontend**: React, TanStack Router, TanStack Query, Tailwind CSS with shadcn/ui components
|
||||||
- **Native Apps**: Create React Native apps with Expo for iOS and Android
|
- **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
|
- **API Layer**: End-to-end type safety with tRPC
|
||||||
- **Runtime Options**: Choose between Bun or Node.js for your server
|
- **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
|
- **ORM Selection**: Choose between Drizzle ORM or Prisma
|
||||||
- **Authentication**: Optional auth setup with Better-Auth
|
- **Authentication**: Optional auth setup with Better-Auth
|
||||||
- **Progressive Web App**: Add PWA support with service workers and installable apps
|
- **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:
|
Options:
|
||||||
-V, --version Output the version number
|
-V, --version Output the version number
|
||||||
-y, --yes Use default configuration
|
-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)
|
--orm <type> ORM type (none, drizzle, prisma)
|
||||||
--auth Include authentication
|
--auth Include authentication
|
||||||
--no-auth Exclude authentication
|
--no-auth Exclude authentication
|
||||||
@@ -58,11 +58,8 @@ Options:
|
|||||||
--package-manager <pm> Package manager (npm, pnpm, bun)
|
--package-manager <pm> Package manager (npm, pnpm, bun)
|
||||||
--install Install dependencies
|
--install Install dependencies
|
||||||
--no-install Skip installing dependencies
|
--no-install Skip installing dependencies
|
||||||
--turso Set up Turso for SQLite database
|
--db-setup <setup> Database setup (turso, prisma-postgres, mongodb-atlas, none)
|
||||||
--no-turso Skip Turso setup
|
--backend <framework> Backend framework (hono, express, elysia)
|
||||||
--prisma-postgres Set up Prisma Postgres
|
|
||||||
--no-prisma-postgres Skip Prisma Postgres setup
|
|
||||||
--backend <framework> Backend framework (hono, elysia)
|
|
||||||
--runtime <runtime> Runtime (bun, node)
|
--runtime <runtime> Runtime (bun, node)
|
||||||
-h, --help Display help
|
-h, --help Display help
|
||||||
```
|
```
|
||||||
@@ -93,3 +90,8 @@ Create a project with examples:
|
|||||||
```bash
|
```bash
|
||||||
npx create-better-t-stack my-app --examples todo ai
|
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": {
|
"bin": {
|
||||||
"create-better-t-stack": "dist/index.js"
|
"create-better-t-stack": "dist/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["template", "dist"],
|
||||||
"template",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"better-t-stack",
|
"better-t-stack",
|
||||||
"typescript",
|
"typescript",
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
|||||||
git: true,
|
git: true,
|
||||||
packageManager: getUserPkgManager(),
|
packageManager: getUserPkgManager(),
|
||||||
noInstall: false,
|
noInstall: false,
|
||||||
turso: false,
|
dbSetup: "none",
|
||||||
prismaPostgres: false,
|
|
||||||
backend: "hono",
|
backend: "hono",
|
||||||
runtime: "bun",
|
runtime: "bun",
|
||||||
};
|
};
|
||||||
@@ -62,6 +61,11 @@ export const dependencyVersionMap = {
|
|||||||
"@hono/trpc-server": "^0.3.4",
|
"@hono/trpc-server": "^0.3.4",
|
||||||
hono: "^4.7.5",
|
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: "^4.2.8",
|
||||||
"@ai-sdk/google": "^1.2.3",
|
"@ai-sdk/google": "^1.2.3",
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ export async function setupBackendDependencies(
|
|||||||
dependencies.push("@elysiajs/node");
|
dependencies.push("@elysiajs/node");
|
||||||
devDependencies.push("tsx", "@types/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") {
|
if (runtime === "bun") {
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
options.database,
|
options.database,
|
||||||
options.orm,
|
options.orm,
|
||||||
options.packageManager,
|
options.packageManager,
|
||||||
options.turso ?? options.database === "sqlite",
|
options.dbSetup === "turso",
|
||||||
options.prismaPostgres,
|
options.dbSetup === "prisma-postgres",
|
||||||
|
options.dbSetup === "mongodb-atlas",
|
||||||
);
|
);
|
||||||
|
|
||||||
await setupAuthTemplate(
|
await setupAuthTemplate(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
ProjectPackageManager,
|
ProjectPackageManager,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { addPackageDependency } from "../utils/add-package-deps";
|
import { addPackageDependency } from "../utils/add-package-deps";
|
||||||
|
import { setupMongoDBAtlas } from "./mongodb-atlas-setup";
|
||||||
import { setupPrismaPostgres } from "./prisma-postgres-setup";
|
import { setupPrismaPostgres } from "./prisma-postgres-setup";
|
||||||
import { setupTurso } from "./turso-setup";
|
import { setupTurso } from "./turso-setup";
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export async function setupDatabase(
|
|||||||
packageManager: ProjectPackageManager,
|
packageManager: ProjectPackageManager,
|
||||||
setupTursoDb: boolean,
|
setupTursoDb: boolean,
|
||||||
setupPrismaPostgresDb: boolean,
|
setupPrismaPostgresDb: boolean,
|
||||||
|
setupMongoDBAtlasDb: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const s = spinner();
|
const s = spinner();
|
||||||
const serverDir = path.join(projectDir, "apps/server");
|
const serverDir = path.join(projectDir, "apps/server");
|
||||||
@@ -68,6 +70,18 @@ export async function setupDatabase(
|
|||||||
await setupPrismaPostgres(projectDir, packageManager);
|
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) {
|
} catch (error) {
|
||||||
s.stop(pc.red("Failed to set up database"));
|
s.stop(pc.red("Failed to set up database"));
|
||||||
|
|||||||
@@ -44,14 +44,18 @@ export async function setupEnvironmentVariables(
|
|||||||
|
|
||||||
if (options.database !== "none") {
|
if (options.database !== "none") {
|
||||||
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
||||||
const databaseUrlLine =
|
let databaseUrlLine = "";
|
||||||
options.database === "sqlite"
|
if (options.database === "sqlite") {
|
||||||
? ""
|
databaseUrlLine = "";
|
||||||
: `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
} 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;
|
envContent += databaseUrlLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.database === "sqlite" && !options.turso) {
|
if (options.database === "sqlite" && options.dbSetup !== "turso") {
|
||||||
if (!envContent.includes("TURSO_CONNECTION_URL")) {
|
if (!envContent.includes("TURSO_CONNECTION_URL")) {
|
||||||
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
|
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function setupExamples(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
examples.includes("ai") &&
|
examples.includes("ai") &&
|
||||||
backend === "hono" &&
|
(backend === "hono" || backend === "express") &&
|
||||||
hasWebFrontend &&
|
hasWebFrontend &&
|
||||||
webAppExists
|
webAppExists
|
||||||
) {
|
) {
|
||||||
@@ -89,6 +89,7 @@ async function updateServerIndexWithAIRoute(projectDir: string): Promise<void> {
|
|||||||
if (await fs.pathExists(serverIndexPath)) {
|
if (await fs.pathExists(serverIndexPath)) {
|
||||||
let indexContent = await fs.readFile(serverIndexPath, "utf8");
|
let indexContent = await fs.readFile(serverIndexPath, "utf8");
|
||||||
const isHono = indexContent.includes("hono");
|
const isHono = indexContent.includes("hono");
|
||||||
|
const isExpress = indexContent.includes("express");
|
||||||
|
|
||||||
if (isHono) {
|
if (isHono) {
|
||||||
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";\nimport { stream } from "hono/streaming";`;
|
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()));
|
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
|
// Add imports and route handler for Hono
|
||||||
if (indexContent.includes("import {")) {
|
if (indexContent.includes("import {")) {
|
||||||
const lastImportIndex = indexContent.lastIndexOf("import");
|
const lastImportIndex = indexContent.lastIndexOf("import");
|
||||||
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
||||||
@@ -141,11 +143,68 @@ ${indexContent.substring(exportIndex)}`;
|
|||||||
${aiRouteHandler}`;
|
${aiRouteHandler}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (isExpress) {
|
||||||
|
// Express implementation
|
||||||
|
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";`;
|
||||||
|
|
||||||
|
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);
|
await fs.writeFile(serverIndexPath, indexContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function updateHeaderWithAILink(
|
async function updateHeaderWithAILink(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
|
|||||||
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 pc from "picocolors";
|
||||||
import type {
|
import type {
|
||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
|
ProjectDBSetup,
|
||||||
ProjectDatabase,
|
ProjectDatabase,
|
||||||
ProjectFrontend,
|
ProjectFrontend,
|
||||||
ProjectOrm,
|
ProjectOrm,
|
||||||
@@ -18,6 +19,7 @@ export function displayPostInstallInstructions(
|
|||||||
addons: ProjectAddons[],
|
addons: ProjectAddons[],
|
||||||
runtime: ProjectRuntime,
|
runtime: ProjectRuntime,
|
||||||
frontends: ProjectFrontend[],
|
frontends: ProjectFrontend[],
|
||||||
|
dbSetup?: ProjectDBSetup,
|
||||||
) {
|
) {
|
||||||
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
||||||
const cdCmd = `cd ${projectName}`;
|
const cdCmd = `cd ${projectName}`;
|
||||||
|
|||||||
@@ -372,9 +372,9 @@ function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (orm === "prisma") {
|
if (orm === "prisma") {
|
||||||
return database === "sqlite"
|
if (database === "sqlite") return "template/with-prisma-sqlite";
|
||||||
? "template/with-prisma-sqlite"
|
if (database === "postgres") return "template/with-prisma-postgres";
|
||||||
: "template/with-prisma-postgres";
|
if (database === "mongodb") return "template/with-prisma-mongodb";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "template/base";
|
return "template/base";
|
||||||
@@ -388,9 +388,9 @@ function getAuthLibDir(orm: ProjectOrm, database: ProjectDatabase): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (orm === "prisma") {
|
if (orm === "prisma") {
|
||||||
return database === "sqlite"
|
if (database === "sqlite") return "with-prisma-sqlite-lib";
|
||||||
? "with-prisma-sqlite-lib"
|
if (database === "postgres") return "with-prisma-postgres-lib";
|
||||||
: "with-prisma-postgres-lib";
|
if (database === "mongodb") return "with-prisma-mongodb-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Invalid ORM or database configuration for auth setup");
|
throw new Error("Invalid ORM or database configuration for auth setup");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { commandExists } from "../utils/command-exists";
|
||||||
|
|
||||||
type TursoConfig = {
|
type TursoConfig = {
|
||||||
dbUrl: string;
|
dbUrl: string;
|
||||||
@@ -26,12 +27,7 @@ type TursoGroup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function isTursoInstalled() {
|
async function isTursoInstalled() {
|
||||||
try {
|
return commandExists("turso");
|
||||||
const result = await $`turso --version`;
|
|
||||||
return result.exitCode === 0;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isTursoLoggedIn() {
|
async function isTursoLoggedIn() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
ProjectBackend,
|
ProjectBackend,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
|
ProjectDBSetup,
|
||||||
ProjectDatabase,
|
ProjectDatabase,
|
||||||
ProjectExamples,
|
ProjectExamples,
|
||||||
ProjectFrontend,
|
ProjectFrontend,
|
||||||
@@ -37,8 +38,11 @@ async function main() {
|
|||||||
.version(getLatestCLIVersion())
|
.version(getLatestCLIVersion())
|
||||||
.argument("[project-directory]", "Project name/directory")
|
.argument("[project-directory]", "Project name/directory")
|
||||||
.option("-y, --yes", "Use default configuration")
|
.option("-y, --yes", "Use default configuration")
|
||||||
.option("--database <type>", "Database type (none, sqlite, postgres)")
|
.option(
|
||||||
.option("--orm <type>", "ORM type (none, drizzle, prisma)")
|
"--database <type>",
|
||||||
|
"Database type (none, sqlite, postgres, mongodb)",
|
||||||
|
)
|
||||||
|
.option("--orm <type>", "ORM type (drizzle, prisma)")
|
||||||
.option("--auth", "Include authentication")
|
.option("--auth", "Include authentication")
|
||||||
.option("--no-auth", "Exclude authentication")
|
.option("--no-auth", "Exclude authentication")
|
||||||
.option(
|
.option(
|
||||||
@@ -56,11 +60,14 @@ async function main() {
|
|||||||
.option("--package-manager <pm>", "Package manager (npm, pnpm, bun)")
|
.option("--package-manager <pm>", "Package manager (npm, pnpm, bun)")
|
||||||
.option("--install", "Install dependencies")
|
.option("--install", "Install dependencies")
|
||||||
.option("--no-install", "Skip installing dependencies")
|
.option("--no-install", "Skip installing dependencies")
|
||||||
.option("--turso", "Set up Turso for SQLite database")
|
.option(
|
||||||
.option("--no-turso", "Skip Turso setup")
|
"--db-setup <setup>",
|
||||||
.option("--prisma-postgres", "Set up Prisma Postgres")
|
"Database setup (turso, prisma-postgres, mongodb-atlas, none)",
|
||||||
.option("--no-prisma-postgres", "Skip Prisma Postgres setup")
|
)
|
||||||
.option("--backend <framework>", "Backend framework (hono, elysia)")
|
.option(
|
||||||
|
"--backend <framework>",
|
||||||
|
"Backend framework (hono, express, elysia)",
|
||||||
|
)
|
||||||
.option("--runtime <runtime>", "Runtime (bun, node)")
|
.option("--runtime <runtime>", "Runtime (bun, node)")
|
||||||
.parse();
|
.parse();
|
||||||
|
|
||||||
@@ -125,20 +132,41 @@ async function main() {
|
|||||||
function validateOptions(options: CLIOptions): void {
|
function validateOptions(options: CLIOptions): void {
|
||||||
if (
|
if (
|
||||||
options.database &&
|
options.database &&
|
||||||
!["none", "sqlite", "postgres"].includes(options.database)
|
!["none", "sqlite", "postgres", "mongodb"].includes(options.database)
|
||||||
) {
|
) {
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
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);
|
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(
|
cancel(
|
||||||
pc.red(
|
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);
|
process.exit(1);
|
||||||
@@ -163,51 +191,62 @@ function validateOptions(options: CLIOptions): void {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("turso" in options && options.turso === true) {
|
if (options.dbSetup && options.dbSetup !== "none") {
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// Check for database setup compatibility
|
||||||
"turso" in options &&
|
if (options.dbSetup === "turso") {
|
||||||
options.turso === true &&
|
if (options.database && options.database !== "sqlite") {
|
||||||
options.database &&
|
|
||||||
options.database !== "sqlite"
|
|
||||||
) {
|
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
pc.red(
|
||||||
`Turso setup requires a SQLite database. Cannot use --turso with --database ${options.database}`,
|
`Turso setup requires a SQLite database. Cannot use --db-setup turso with --database ${options.database}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (options.orm === "prisma") {
|
||||||
"turso" in options &&
|
|
||||||
options.turso === true &&
|
|
||||||
options.orm === "prisma"
|
|
||||||
) {
|
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
pc.red(
|
||||||
"Turso setup is not compatible with Prisma. Cannot use --turso with --orm prisma",
|
"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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("prismaPostgres" in options && options.prismaPostgres === true) {
|
if (options.orm && options.orm !== "prisma") {
|
||||||
if (
|
|
||||||
(options.database && options.database !== "postgres") ||
|
|
||||||
(options.orm && options.orm !== "prisma")
|
|
||||||
) {
|
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
pc.red(
|
||||||
"Prisma PostgreSQL setup requires PostgreSQL database with Prisma ORM. Cannot use --prisma-postgres with incompatible database or ORM options.",
|
"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);
|
process.exit(1);
|
||||||
@@ -226,10 +265,13 @@ function validateOptions(options: CLIOptions): void {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
if (
|
||||||
|
options.backend &&
|
||||||
|
!["hono", "elysia", "express"].includes(options.backend)
|
||||||
|
) {
|
||||||
cancel(
|
cancel(
|
||||||
pc.red(
|
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);
|
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;
|
let examples: ProjectExamples[] | undefined;
|
||||||
if ("examples" in options) {
|
if ("examples" in options) {
|
||||||
if (options.examples === false) {
|
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 backend = options.backend as ProjectBackend | undefined;
|
||||||
const runtime = options.runtime as ProjectRuntime | undefined;
|
const runtime = options.runtime as ProjectRuntime | undefined;
|
||||||
const packageManager = options.packageManager as
|
const packageManager = options.packageManager as
|
||||||
| ProjectPackageManager
|
| ProjectPackageManager
|
||||||
| undefined;
|
| 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> = {};
|
const config: Partial<ProjectConfig> = {};
|
||||||
|
|
||||||
if (projectDirectory) config.projectName = projectDirectory;
|
if (projectDirectory) config.projectName = projectDirectory;
|
||||||
@@ -532,9 +570,7 @@ function processFlags(
|
|||||||
if (packageManager) config.packageManager = packageManager;
|
if (packageManager) config.packageManager = packageManager;
|
||||||
if ("git" in options) config.git = options.git;
|
if ("git" in options) config.git = options.git;
|
||||||
if ("install" in options) config.noInstall = !options.install;
|
if ("install" in options) config.noInstall = !options.install;
|
||||||
if (tursoOption !== undefined) config.turso = tursoOption;
|
if (dbSetup !== undefined) config.dbSetup = dbSetup;
|
||||||
if (prismaPostgresOption !== undefined)
|
|
||||||
config.prismaPostgres = prismaPostgresOption;
|
|
||||||
if (backend) config.backend = backend;
|
if (backend) config.backend = backend;
|
||||||
if (runtime) config.runtime = runtime;
|
if (runtime) config.runtime = runtime;
|
||||||
if (frontend !== undefined) config.frontend = frontend;
|
if (frontend !== undefined) config.frontend = frontend;
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ export async function getBackendFrameworkChoice(
|
|||||||
label: "Hono",
|
label: "Hono",
|
||||||
hint: "Lightweight, ultrafast web framework",
|
hint: "Lightweight, ultrafast web framework",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "express",
|
||||||
|
label: "Express",
|
||||||
|
hint: "Fast, unopinionated, minimalist web framework for Node.js",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: "elysia",
|
value: "elysia",
|
||||||
label: "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 pc from "picocolors";
|
||||||
import type {
|
import type {
|
||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
ProjectBackend,
|
ProjectBackend,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
|
ProjectDBSetup,
|
||||||
ProjectDatabase,
|
ProjectDatabase,
|
||||||
ProjectExamples,
|
ProjectExamples,
|
||||||
ProjectFrontend,
|
ProjectFrontend,
|
||||||
@@ -15,16 +16,15 @@ import { getAddonsChoice } from "./addons";
|
|||||||
import { getAuthChoice } from "./auth";
|
import { getAuthChoice } from "./auth";
|
||||||
import { getBackendFrameworkChoice } from "./backend-framework";
|
import { getBackendFrameworkChoice } from "./backend-framework";
|
||||||
import { getDatabaseChoice } from "./database";
|
import { getDatabaseChoice } from "./database";
|
||||||
|
import { getDBSetupChoice } from "./db-setup";
|
||||||
import { getExamplesChoice } from "./examples";
|
import { getExamplesChoice } from "./examples";
|
||||||
import { getFrontendChoice } from "./frontend-option";
|
import { getFrontendChoice } from "./frontend-option";
|
||||||
import { getGitChoice } from "./git";
|
import { getGitChoice } from "./git";
|
||||||
import { getNoInstallChoice } from "./install";
|
import { getNoInstallChoice } from "./install";
|
||||||
import { getORMChoice } from "./orm";
|
import { getORMChoice } from "./orm";
|
||||||
import { getPackageManagerChoice } from "./package-manager";
|
import { getPackageManagerChoice } from "./package-manager";
|
||||||
import { getPrismaSetupChoice } from "./prisma-postgres";
|
|
||||||
import { getProjectName } from "./project-name";
|
import { getProjectName } from "./project-name";
|
||||||
import { getRuntimeChoice } from "./runtime";
|
import { getRuntimeChoice } from "./runtime";
|
||||||
import { getTursoSetupChoice } from "./turso";
|
|
||||||
|
|
||||||
type PromptGroupResults = {
|
type PromptGroupResults = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -36,8 +36,7 @@ type PromptGroupResults = {
|
|||||||
git: boolean;
|
git: boolean;
|
||||||
packageManager: ProjectPackageManager;
|
packageManager: ProjectPackageManager;
|
||||||
noInstall: boolean;
|
noInstall: boolean;
|
||||||
turso: boolean;
|
dbSetup: ProjectDBSetup;
|
||||||
prismaPostgres: boolean;
|
|
||||||
backend: ProjectBackend;
|
backend: ProjectBackend;
|
||||||
runtime: ProjectRuntime;
|
runtime: ProjectRuntime;
|
||||||
frontend: ProjectFrontend[];
|
frontend: ProjectFrontend[];
|
||||||
@@ -46,6 +45,32 @@ type PromptGroupResults = {
|
|||||||
export async function gatherConfig(
|
export async function gatherConfig(
|
||||||
flags: Partial<ProjectConfig>,
|
flags: Partial<ProjectConfig>,
|
||||||
): Promise<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>(
|
const result = await group<PromptGroupResults>(
|
||||||
{
|
{
|
||||||
projectName: async () => {
|
projectName: async () => {
|
||||||
@@ -56,21 +81,15 @@ export async function gatherConfig(
|
|||||||
runtime: () => getRuntimeChoice(flags.runtime),
|
runtime: () => getRuntimeChoice(flags.runtime),
|
||||||
database: () => getDatabaseChoice(flags.database),
|
database: () => getDatabaseChoice(flags.database),
|
||||||
orm: ({ results }) =>
|
orm: ({ results }) =>
|
||||||
getORMChoice(flags.orm, results.database !== "none"),
|
getORMChoice(flags.orm, results.database !== "none", results.database),
|
||||||
auth: ({ results }) =>
|
auth: ({ results }) =>
|
||||||
getAuthChoice(
|
getAuthChoice(
|
||||||
flags.auth,
|
flags.auth,
|
||||||
results.database !== "none",
|
results.database !== "none",
|
||||||
results.frontend,
|
results.frontend,
|
||||||
),
|
),
|
||||||
turso: ({ results }) =>
|
dbSetup: ({ results }) =>
|
||||||
results.database === "sqlite" && results.orm !== "prisma"
|
getDBSetupChoice(results.database ?? "none", flags.dbSetup),
|
||||||
? getTursoSetupChoice(flags.turso)
|
|
||||||
: Promise.resolve(false),
|
|
||||||
prismaPostgres: ({ results }) =>
|
|
||||||
results.database === "postgres" && results.orm === "prisma"
|
|
||||||
? getPrismaSetupChoice(flags.prismaPostgres)
|
|
||||||
: Promise.resolve(false),
|
|
||||||
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
||||||
examples: ({ results }) =>
|
examples: ({ results }) =>
|
||||||
getExamplesChoice(
|
getExamplesChoice(
|
||||||
@@ -102,8 +121,7 @@ export async function gatherConfig(
|
|||||||
git: result.git,
|
git: result.git,
|
||||||
packageManager: result.packageManager,
|
packageManager: result.packageManager,
|
||||||
noInstall: result.noInstall,
|
noInstall: result.noInstall,
|
||||||
turso: result.turso,
|
dbSetup: result.dbSetup,
|
||||||
prismaPostgres: result.prismaPostgres,
|
|
||||||
backend: result.backend,
|
backend: result.backend,
|
||||||
runtime: result.runtime,
|
runtime: result.runtime,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ export async function getDatabaseChoice(
|
|||||||
label: "PostgreSQL",
|
label: "PostgreSQL",
|
||||||
hint: "Traditional relational database",
|
hint: "Traditional relational database",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "mongodb",
|
||||||
|
label: "MongoDB",
|
||||||
|
hint: "NoSQL document-oriented database",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
initialValue: DEFAULT_CONFIG.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>({
|
response = await multiselect<ProjectExamples>({
|
||||||
message: "Include examples",
|
message: "Include examples",
|
||||||
options: [
|
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 pc from "picocolors";
|
||||||
import { DEFAULT_CONFIG } from "../constants";
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { ProjectOrm } from "../types";
|
import type { ProjectDatabase, ProjectOrm } from "../types";
|
||||||
|
|
||||||
export async function getORMChoice(
|
export async function getORMChoice(
|
||||||
orm: ProjectOrm | undefined,
|
orm: ProjectOrm | undefined,
|
||||||
hasDatabase: boolean,
|
hasDatabase: boolean,
|
||||||
|
database?: ProjectDatabase,
|
||||||
): Promise<ProjectOrm> {
|
): Promise<ProjectOrm> {
|
||||||
if (!hasDatabase) return "none";
|
if (!hasDatabase) return "none";
|
||||||
if (orm !== undefined) return orm;
|
if (orm !== undefined) return orm;
|
||||||
|
|
||||||
|
if (database === "mongodb") {
|
||||||
|
log.info("Only Prisma is supported with MongoDB.");
|
||||||
|
return "prisma";
|
||||||
|
}
|
||||||
|
|
||||||
const response = await select<ProjectOrm>({
|
const response = await select<ProjectOrm>({
|
||||||
message: "Select ORM",
|
message: "Select ORM",
|
||||||
options: [
|
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 ProjectDatabase = "sqlite" | "postgres" | "mongodb" | "none";
|
||||||
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
export type ProjectOrm = "drizzle" | "prisma";
|
||||||
export type ProjectPackageManager = "npm" | "pnpm" | "bun";
|
export type ProjectPackageManager = "npm" | "pnpm" | "bun";
|
||||||
export type ProjectAddons = "pwa" | "biome" | "tauri" | "husky";
|
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 ProjectRuntime = "node" | "bun";
|
||||||
export type ProjectExamples = "todo" | "ai";
|
export type ProjectExamples = "todo" | "ai";
|
||||||
export type ProjectFrontend =
|
export type ProjectFrontend =
|
||||||
@@ -10,6 +10,11 @@ export type ProjectFrontend =
|
|||||||
| "tanstack-router"
|
| "tanstack-router"
|
||||||
| "tanstack-start"
|
| "tanstack-start"
|
||||||
| "native";
|
| "native";
|
||||||
|
export type ProjectDBSetup =
|
||||||
|
| "turso"
|
||||||
|
| "prisma-postgres"
|
||||||
|
| "mongodb-atlas"
|
||||||
|
| "none";
|
||||||
|
|
||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -22,9 +27,8 @@ export interface ProjectConfig {
|
|||||||
examples: ProjectExamples[];
|
examples: ProjectExamples[];
|
||||||
git: boolean;
|
git: boolean;
|
||||||
packageManager: ProjectPackageManager;
|
packageManager: ProjectPackageManager;
|
||||||
noInstall?: boolean;
|
noInstall: boolean;
|
||||||
turso?: boolean;
|
dbSetup: ProjectDBSetup;
|
||||||
prismaPostgres: boolean;
|
|
||||||
frontend: ProjectFrontend[];
|
frontend: ProjectFrontend[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +43,7 @@ export type CLIOptions = {
|
|||||||
git?: boolean;
|
git?: boolean;
|
||||||
packageManager?: string;
|
packageManager?: string;
|
||||||
install?: boolean;
|
install?: boolean;
|
||||||
turso?: boolean;
|
dbSetup?: string;
|
||||||
prismaPostgres?: boolean;
|
|
||||||
backend?: string;
|
backend?: string;
|
||||||
runtime?: 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}`);
|
configDisplay.push(`${pc.blue("Skip Install:")} ${config.noInstall}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.turso !== undefined) {
|
if (config.dbSetup !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Turso Setup:")} ${config.turso}`);
|
configDisplay.push(`${pc.blue("Database Setup:")} ${config.dbSetup}`);
|
||||||
}
|
|
||||||
|
|
||||||
if (config.prismaPostgres !== undefined) {
|
|
||||||
configDisplay.push(
|
|
||||||
`${pc.blue("Prisma Postgres Setup:")} ${config.prismaPostgres ? "Yes" : "No"}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return configDisplay.join("\n");
|
return configDisplay.join("\n");
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
flags.push(`--orm ${config.orm}`);
|
flags.push(`--orm ${config.orm}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.database === "sqlite") {
|
if (config.dbSetup && config.dbSetup !== "none") {
|
||||||
flags.push(config.turso ? "--turso" : "--no-turso");
|
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,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev --turbopack",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"check": "biome check --write .",
|
"check": "biome check --write .",
|
||||||
"postinstall": "fumadocs-mdx"
|
"postinstall": "fumadocs-mdx"
|
||||||
|
|||||||
@@ -74,32 +74,45 @@ const StackArchitect = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stack.database === "none" && stack.orm !== "none") {
|
if (stack.database === "none") {
|
||||||
|
if (stack.orm !== "none") {
|
||||||
setStack((prev) => ({ ...prev, orm: "none" }));
|
setStack((prev) => ({ ...prev, orm: "none" }));
|
||||||
}
|
}
|
||||||
|
if (stack.auth === "true") {
|
||||||
if (stack.database !== "postgres" || stack.orm !== "prisma") {
|
|
||||||
if (stack.prismaPostgres === "true") {
|
|
||||||
setStack((prev) => ({ ...prev, prismaPostgres: "false" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack.database !== "sqlite" || stack.orm === "prisma") {
|
|
||||||
if (stack.turso === "true") {
|
|
||||||
setStack((prev) => ({ ...prev, turso: "false" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack.database === "none" && stack.auth === "true") {
|
|
||||||
setStack((prev) => ({ ...prev, auth: "false" }));
|
setStack((prev) => ({ ...prev, auth: "false" }));
|
||||||
}
|
}
|
||||||
}, [
|
if (stack.dbSetup !== "none") {
|
||||||
stack.database,
|
setStack((prev) => ({ ...prev, dbSetup: "none" }));
|
||||||
stack.orm,
|
}
|
||||||
stack.prismaPostgres,
|
}
|
||||||
stack.turso,
|
|
||||||
stack.auth,
|
if (stack.database === "mongodb" && stack.orm === "drizzle") {
|
||||||
]);
|
setStack((prev) => ({ ...prev, orm: "prisma" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.dbSetup, stack.auth]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cmd = generateCommand(stack);
|
const cmd = generateCommand(stack);
|
||||||
@@ -113,6 +126,33 @@ const StackArchitect = () => {
|
|||||||
|
|
||||||
notes.frontend = [];
|
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 = [];
|
notes.addons = [];
|
||||||
if (!hasWebFrontend) {
|
if (!hasWebFrontend) {
|
||||||
notes.addons.push("PWA and Tauri are only available with React Web.");
|
notes.addons.push("PWA and Tauri are only available with React Web.");
|
||||||
@@ -125,6 +165,8 @@ const StackArchitect = () => {
|
|||||||
notes.orm.push(
|
notes.orm.push(
|
||||||
"ORM options are only available when a database is selected.",
|
"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 = [];
|
notes.auth = [];
|
||||||
@@ -132,23 +174,6 @@ const StackArchitect = () => {
|
|||||||
notes.auth.push("Authentication requires a database.");
|
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 = [];
|
notes.examples = [];
|
||||||
if (!hasWebFrontend) {
|
if (!hasWebFrontend) {
|
||||||
notes.examples.push(
|
notes.examples.push(
|
||||||
@@ -198,12 +223,8 @@ const StackArchitect = () => {
|
|||||||
flags.push("--no-auth");
|
flags.push("--no-auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackState.turso === "true") {
|
if (stackState.dbSetup !== "none") {
|
||||||
flags.push("--turso");
|
flags.push(`--db-setup ${stackState.dbSetup}`);
|
||||||
}
|
|
||||||
|
|
||||||
if (stackState.prismaPostgres === "true") {
|
|
||||||
flags.push("--prisma-postgres");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackState.backendFramework !== "hono") {
|
if (stackState.backendFramework !== "hono") {
|
||||||
@@ -263,7 +284,6 @@ const StackArchitect = () => {
|
|||||||
if (currentSelection.length === 1) {
|
if (currentSelection.length === 1) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
frontend: currentSelection.filter((id) => id !== techId),
|
frontend: currentSelection.filter((id) => id !== techId),
|
||||||
@@ -296,6 +316,10 @@ const StackArchitect = () => {
|
|||||||
prev.frontend.includes("react-router") ||
|
prev.frontend.includes("react-router") ||
|
||||||
prev.frontend.includes("tanstack-start");
|
prev.frontend.includes("tanstack-start");
|
||||||
|
|
||||||
|
const hasPWACompatibleFrontend =
|
||||||
|
prev.frontend.includes("tanstack-router") ||
|
||||||
|
prev.frontend.includes("react-router");
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
currentArray.splice(index, 1);
|
currentArray.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
@@ -318,8 +342,7 @@ const StackArchitect = () => {
|
|||||||
if (
|
if (
|
||||||
category === "addons" &&
|
category === "addons" &&
|
||||||
(techId === "pwa" || techId === "tauri") &&
|
(techId === "pwa" || techId === "tauri") &&
|
||||||
!prev.frontend.includes("tanstack-router") &&
|
!hasPWACompatibleFrontend
|
||||||
!prev.frontend.includes("react-router")
|
|
||||||
) {
|
) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
@@ -342,45 +365,40 @@ const StackArchitect = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (category === "database") {
|
if (category === "database") {
|
||||||
|
let updatedState = { ...prev, database: techId };
|
||||||
|
|
||||||
if (techId === "none") {
|
if (techId === "none") {
|
||||||
return {
|
updatedState = {
|
||||||
...prev,
|
...updatedState,
|
||||||
database: techId,
|
|
||||||
orm: "none",
|
orm: "none",
|
||||||
turso: "false",
|
dbSetup: "none",
|
||||||
prismaPostgres: "false",
|
|
||||||
auth: "false",
|
auth: "false",
|
||||||
};
|
};
|
||||||
|
} else if (prev.database === "none") {
|
||||||
|
updatedState.orm = techId === "mongodb" ? "prisma" : "drizzle";
|
||||||
|
updatedState.dbSetup = "none";
|
||||||
|
|
||||||
|
const hasCompatibleFrontend =
|
||||||
|
prev.frontend.length > 0 && !prev.frontend.includes("none");
|
||||||
|
if (hasCompatibleFrontend) {
|
||||||
|
updatedState.auth = "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
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") {
|
|
||||||
} else {
|
} 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;
|
return updatedState;
|
||||||
@@ -396,31 +414,41 @@ const StackArchitect = () => {
|
|||||||
orm: techId,
|
orm: techId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (techId === "prisma") {
|
if (updatedState.dbSetup !== "none") {
|
||||||
updatedState.turso = "false";
|
if (
|
||||||
if (prev.database === "postgres") {
|
(updatedState.dbSetup === "turso" && techId === "prisma") ||
|
||||||
} else {
|
(updatedState.dbSetup === "prisma-postgres" &&
|
||||||
updatedState.prismaPostgres = "false";
|
techId !== "prisma")
|
||||||
|
) {
|
||||||
|
updatedState.dbSetup = "none";
|
||||||
}
|
}
|
||||||
} else if (techId === "drizzle" || techId === "none") {
|
|
||||||
updatedState.prismaPostgres = "false";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedState;
|
return updatedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (category === "dbSetup") {
|
||||||
category === "turso" &&
|
if (prev.database === "none" && techId !== "none") {
|
||||||
(prev.database !== "sqlite" || prev.orm === "prisma")
|
|
||||||
) {
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const updatedState = {
|
||||||
category === "prismaPostgres" &&
|
...prev,
|
||||||
(prev.database !== "postgres" || prev.orm !== "prisma")
|
dbSetup: techId,
|
||||||
) {
|
};
|
||||||
return prev;
|
|
||||||
|
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 (
|
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(() => {
|
const copyToClipboard = useCallback(() => {
|
||||||
navigator.clipboard.writeText(command);
|
navigator.clipboard.writeText(command);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
@@ -698,12 +717,16 @@ const StackArchitect = () => {
|
|||||||
|
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
(activeTab === "orm" && stack.database === "none") ||
|
(activeTab === "orm" && stack.database === "none") ||
|
||||||
(activeTab === "turso" &&
|
(activeTab === "dbSetup" &&
|
||||||
|
((tech.id !== "none" && stack.database === "none") ||
|
||||||
|
(tech.id === "turso" &&
|
||||||
(stack.database !== "sqlite" ||
|
(stack.database !== "sqlite" ||
|
||||||
stack.orm === "prisma")) ||
|
stack.orm === "prisma")) ||
|
||||||
(activeTab === "prismaPostgres" &&
|
(tech.id === "prisma-postgres" &&
|
||||||
(stack.database !== "postgres" ||
|
(stack.database !== "postgres" ||
|
||||||
stack.orm !== "prisma")) ||
|
stack.orm !== "prisma")) ||
|
||||||
|
(tech.id === "mongodb-atlas" &&
|
||||||
|
stack.database !== "mongodb"))) ||
|
||||||
(activeTab === "examples" &&
|
(activeTab === "examples" &&
|
||||||
(((tech.id === "todo" || tech.id === "ai") &&
|
(((tech.id === "todo" || tech.id === "ai") &&
|
||||||
!hasWebFrontendSelected) ||
|
!hasWebFrontendSelected) ||
|
||||||
@@ -866,7 +889,7 @@ const StackArchitect = () => {
|
|||||||
}
|
}
|
||||||
</span>
|
</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">
|
<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)?.icon}{" "}
|
||||||
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
|
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
|
||||||
@@ -882,38 +905,19 @@ const StackArchitect = () => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{stack.turso === "true" &&
|
{stack.dbSetup !== "none" && (
|
||||||
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">
|
<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)
|
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
|
||||||
?.icon
|
?.icon
|
||||||
}{" "}
|
}{" "}
|
||||||
{
|
{
|
||||||
TECH_OPTIONS.turso.find((t) => t.id === stack.turso)
|
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
|
||||||
?.name
|
?.name
|
||||||
}
|
}
|
||||||
</span>
|
</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.addons.map((addonId) => {
|
{stack.addons.map((addonId) => {
|
||||||
const addon = TECH_OPTIONS.addons.find(
|
const addon = TECH_OPTIONS.addons.find(
|
||||||
(a) => a.id === addonId,
|
(a) => a.id === addonId,
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ export const TECH_OPTIONS = {
|
|||||||
icon: "🦊",
|
icon: "🦊",
|
||||||
color: "from-purple-500 to-purple-700",
|
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: [
|
database: [
|
||||||
{
|
{
|
||||||
@@ -91,6 +98,13 @@ export const TECH_OPTIONS = {
|
|||||||
icon: "🐘",
|
icon: "🐘",
|
||||||
color: "from-indigo-400 to-indigo-600",
|
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",
|
id: "none",
|
||||||
name: "No Database",
|
name: "No Database",
|
||||||
@@ -115,6 +129,44 @@ export const TECH_OPTIONS = {
|
|||||||
icon: "◮",
|
icon: "◮",
|
||||||
color: "from-purple-400 to-purple-600",
|
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: [
|
auth: [
|
||||||
{
|
{
|
||||||
@@ -133,42 +185,6 @@ export const TECH_OPTIONS = {
|
|||||||
color: "from-red-400 to-red-600",
|
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: [
|
packageManager: [
|
||||||
{
|
{
|
||||||
id: "npm",
|
id: "npm",
|
||||||
@@ -293,9 +309,8 @@ export const PRESET_TEMPLATES = [
|
|||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
database: "sqlite",
|
database: "sqlite",
|
||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
|
dbSetup: "none",
|
||||||
auth: "true",
|
auth: "true",
|
||||||
turso: "false",
|
|
||||||
prismaPostgres: "false",
|
|
||||||
packageManager: "bun",
|
packageManager: "bun",
|
||||||
addons: [],
|
addons: [],
|
||||||
examples: [],
|
examples: [],
|
||||||
@@ -314,9 +329,8 @@ export const PRESET_TEMPLATES = [
|
|||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
database: "sqlite",
|
database: "sqlite",
|
||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
|
dbSetup: "none",
|
||||||
auth: "true",
|
auth: "true",
|
||||||
turso: "false",
|
|
||||||
prismaPostgres: "false",
|
|
||||||
packageManager: "bun",
|
packageManager: "bun",
|
||||||
addons: [],
|
addons: [],
|
||||||
examples: [],
|
examples: [],
|
||||||
@@ -335,9 +349,8 @@ export const PRESET_TEMPLATES = [
|
|||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
database: "postgres",
|
database: "postgres",
|
||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
|
dbSetup: "none",
|
||||||
auth: "false",
|
auth: "false",
|
||||||
turso: "false",
|
|
||||||
prismaPostgres: "false",
|
|
||||||
packageManager: "bun",
|
packageManager: "bun",
|
||||||
addons: [],
|
addons: [],
|
||||||
examples: [],
|
examples: [],
|
||||||
@@ -356,9 +369,8 @@ export const PRESET_TEMPLATES = [
|
|||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
database: "sqlite",
|
database: "sqlite",
|
||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
|
dbSetup: "turso",
|
||||||
auth: "true",
|
auth: "true",
|
||||||
turso: "true",
|
|
||||||
prismaPostgres: "false",
|
|
||||||
packageManager: "bun",
|
packageManager: "bun",
|
||||||
addons: ["pwa", "biome", "husky", "tauri"],
|
addons: ["pwa", "biome", "husky", "tauri"],
|
||||||
examples: ["todo", "ai"],
|
examples: ["todo", "ai"],
|
||||||
@@ -374,10 +386,9 @@ export type StackState = {
|
|||||||
runtime: string;
|
runtime: string;
|
||||||
backendFramework: string;
|
backendFramework: string;
|
||||||
database: string;
|
database: string;
|
||||||
orm: string | null;
|
orm: string;
|
||||||
|
dbSetup: string;
|
||||||
auth: string;
|
auth: string;
|
||||||
turso: string;
|
|
||||||
prismaPostgres: string;
|
|
||||||
packageManager: string;
|
packageManager: string;
|
||||||
addons: string[];
|
addons: string[];
|
||||||
examples: string[];
|
examples: string[];
|
||||||
@@ -392,9 +403,8 @@ export const DEFAULT_STACK: StackState = {
|
|||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
database: "sqlite",
|
database: "sqlite",
|
||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
|
dbSetup: "none",
|
||||||
auth: "true",
|
auth: "true",
|
||||||
turso: "false",
|
|
||||||
prismaPostgres: "false",
|
|
||||||
packageManager: "bun",
|
packageManager: "bun",
|
||||||
addons: [],
|
addons: [],
|
||||||
examples: [],
|
examples: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user