mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add automatic prisma postgres setup
fix tanstack auth template
This commit is contained in:
5
.changeset/curvy-poems-camp.md
Normal file
5
.changeset/curvy-poems-camp.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Add automatic prisma postgres setup
|
||||
@@ -7,10 +7,7 @@
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"template"
|
||||
],
|
||||
"files": ["dist", "template"],
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"scaffold",
|
||||
|
||||
@@ -19,6 +19,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
||||
packageManager: getUserPkgManager(),
|
||||
noInstall: false,
|
||||
turso: false,
|
||||
prismaPostgres: false,
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
};
|
||||
@@ -62,6 +63,8 @@ export const dependencyVersionMap = {
|
||||
|
||||
ai: "^4.2.8",
|
||||
"@ai-sdk/google": "^1.2.3",
|
||||
|
||||
"@prisma/extension-accelerate": "^1.3.0",
|
||||
} as const;
|
||||
|
||||
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
||||
|
||||
@@ -53,6 +53,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
projectDir,
|
||||
options.database,
|
||||
options.orm,
|
||||
options.packageManager,
|
||||
options.turso ?? options.database === "sqlite",
|
||||
);
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ The API is running at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
${
|
||||
addons.includes("pwa") && hasReactRouter
|
||||
? "\n## PWA Support with React Router v7\n\nThere is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n\nIf you encounter problems with the PWA functionality, you may need to manually modify\nthe service worker registration or consider waiting for a fix from VitePWA.\n"
|
||||
? "\n## PWA Support with React Router v7\n\nThere is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n"
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,20 @@ import path from "node:path";
|
||||
import { log, spinner } from "@clack/prompts";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectDatabase, ProjectOrm } from "../types";
|
||||
import type {
|
||||
ProjectDatabase,
|
||||
ProjectOrm,
|
||||
ProjectPackageManager,
|
||||
} from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
import { setupPrismaPostgres } from "./prisma-postgres-setup";
|
||||
import { setupTurso } from "./turso-setup";
|
||||
|
||||
export async function setupDatabase(
|
||||
projectDir: string,
|
||||
databaseType: ProjectDatabase,
|
||||
orm: ProjectOrm,
|
||||
packageManager: ProjectPackageManager,
|
||||
setupTursoDb = true,
|
||||
): Promise<void> {
|
||||
const s = spinner();
|
||||
@@ -52,6 +58,10 @@ export async function setupDatabase(
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
|
||||
if (databaseType === "postgres" && orm === "prisma") {
|
||||
await setupPrismaPostgres(projectDir, true, packageManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -89,7 +89,7 @@ function getDatabaseInstructions(
|
||||
|
||||
if (runtime === "bun") {
|
||||
instructions.push(
|
||||
`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors, follow the guidance provided in the error messages`,
|
||||
`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
178
apps/cli/src/helpers/prisma-postgres-setup.ts
Normal file
178
apps/cli/src/helpers/prisma-postgres-setup.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import path from "node:path";
|
||||
import { cancel, isCancel, log, password } from "@clack/prompts";
|
||||
import { execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectPackageManager } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
type PrismaConfig = {
|
||||
databaseUrl: string;
|
||||
};
|
||||
|
||||
async function initPrismaDatabase(
|
||||
serverDir: string,
|
||||
packageManager: ProjectPackageManager,
|
||||
): Promise<PrismaConfig | null> {
|
||||
try {
|
||||
log.info(pc.blue("Initializing Prisma PostgreSQL"));
|
||||
|
||||
const prismaDir = path.join(serverDir, "prisma");
|
||||
await fs.ensureDir(prismaDir);
|
||||
|
||||
const initCmd =
|
||||
packageManager === "npm"
|
||||
? "npx"
|
||||
: packageManager === "pnpm"
|
||||
? "pnpm dlx"
|
||||
: "bunx";
|
||||
|
||||
await execa(initCmd, ["prisma", "init", "--db"], {
|
||||
cwd: serverDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
log.info(
|
||||
pc.yellow(
|
||||
"Please copy the Prisma Postgres URL from the output above.\nIt looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=...",
|
||||
),
|
||||
);
|
||||
|
||||
const databaseUrl = await password({
|
||||
message: "Paste your Prisma Postgres database URL:",
|
||||
validate(value) {
|
||||
if (!value) return "Please enter a database URL";
|
||||
if (!value.startsWith("prisma+postgres://")) {
|
||||
return "URL should start with prisma+postgres://";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (isCancel(databaseUrl)) {
|
||||
cancel("Database setup cancelled");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
databaseUrl: databaseUrl as string,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
log.error(pc.red(error.message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeEnvFile(projectDir: string, config?: PrismaConfig) {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
let envContent = "";
|
||||
|
||||
if (await fs.pathExists(envPath)) {
|
||||
envContent = await fs.readFile(envPath, "utf8");
|
||||
}
|
||||
|
||||
const databaseUrlLine = config
|
||||
? `DATABASE_URL="${config.databaseUrl}"`
|
||||
: `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
||||
|
||||
if (!envContent.includes("DATABASE_URL=")) {
|
||||
envContent += `\n${databaseUrlLine}`;
|
||||
} else {
|
||||
envContent = envContent.replace(
|
||||
/DATABASE_URL=.*(\r?\n|$)/,
|
||||
`${databaseUrlLine}$1`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, envContent.trim());
|
||||
}
|
||||
|
||||
function displayManualSetupInstructions() {
|
||||
log.info(`Manual Prisma PostgreSQL Setup Instructions:
|
||||
|
||||
1. Visit https://console.prisma.io and create an account
|
||||
2. Create a new PostgreSQL database from the dashboard
|
||||
3. Get your database URL
|
||||
4. Add the database URL to the .env file in apps/server/.env
|
||||
|
||||
DATABASE_URL="your_database_url"`);
|
||||
}
|
||||
|
||||
export async function setupPrismaPostgres(
|
||||
projectDir: string,
|
||||
shouldSetupPrisma: boolean,
|
||||
packageManager: ProjectPackageManager = "npm",
|
||||
) {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
|
||||
if (!shouldSetupPrisma) {
|
||||
await writeEnvFile(projectDir);
|
||||
log.info(
|
||||
pc.blue(
|
||||
"Using default Postgres configuration. You'll need to provide your own database.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await initPrismaDatabase(serverDir, packageManager);
|
||||
|
||||
if (config) {
|
||||
await writeEnvFile(projectDir, config);
|
||||
await addPrismaAccelerateExtension(serverDir);
|
||||
log.success(
|
||||
pc.green("Prisma PostgreSQL database configured successfully!"),
|
||||
);
|
||||
} else {
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(pc.red(`Error during Prisma PostgreSQL setup: ${error}`));
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
log.info("Setup completed with manual configuration required.");
|
||||
}
|
||||
}
|
||||
|
||||
async function addPrismaAccelerateExtension(serverDir: string) {
|
||||
try {
|
||||
addPackageDependency({
|
||||
dependencies: ["@prisma/extension-accelerate"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
|
||||
const prismaIndexPath = path.join(serverDir, "prisma/index.ts");
|
||||
const prismaIndexContent = `
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { withAccelerate } from "@prisma/extension-accelerate";
|
||||
|
||||
const prisma = new PrismaClient().$extends(withAccelerate());
|
||||
|
||||
export default prisma;
|
||||
`;
|
||||
await fs.writeFile(prismaIndexPath, prismaIndexContent.trim());
|
||||
|
||||
const dbFilePath = path.join(serverDir, "src/db/index.ts");
|
||||
if (await fs.pathExists(dbFilePath)) {
|
||||
let dbFileContent = await fs.readFile(dbFilePath, "utf8");
|
||||
|
||||
if (!dbFileContent.includes("@prisma/extension-accelerate")) {
|
||||
dbFileContent = `import { withAccelerate } from "@prisma/extension-accelerate";\n${dbFileContent}`;
|
||||
|
||||
dbFileContent = dbFileContent.replace(
|
||||
"export const db = new PrismaClient();",
|
||||
"export const db = new PrismaClient().$extends(withAccelerate());",
|
||||
);
|
||||
|
||||
await fs.writeFile(dbFilePath, dbFileContent);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
pc.yellow("Could not add Prisma Accelerate extension automatically"),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,8 @@ async function main() {
|
||||
.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("--runtime <runtime>", "Runtime (bun, node)")
|
||||
.parse();
|
||||
@@ -198,6 +200,20 @@ function validateOptions(options: CLIOptions): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ("prismaPostgres" in options && options.prismaPostgres === true) {
|
||||
if (
|
||||
(options.database && options.database !== "postgres") ||
|
||||
(options.orm && options.orm !== "prisma")
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database with Prisma ORM. Cannot use --prisma-postgres with incompatible database or ORM options.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
options.packageManager &&
|
||||
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
||||
@@ -384,16 +400,36 @@ function processFlags(
|
||||
}
|
||||
}
|
||||
|
||||
const database = options.database as ProjectDatabase | undefined;
|
||||
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;
|
||||
@@ -473,21 +509,25 @@ function processFlags(
|
||||
| ProjectPackageManager
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
...(projectDirectory && { projectName: projectDirectory }),
|
||||
...(database !== undefined && { database }),
|
||||
...(orm !== undefined && { orm }),
|
||||
...(auth !== undefined && { auth }),
|
||||
...(packageManager && { packageManager }),
|
||||
...("git" in options && { git: options.git }),
|
||||
...("install" in options && { noInstall: !options.install }),
|
||||
...(tursoOption !== undefined && { turso: tursoOption }),
|
||||
...(backend && { backend }),
|
||||
...(runtime && { runtime }),
|
||||
...(frontend !== undefined && { frontend }),
|
||||
...(addons !== undefined && { addons }),
|
||||
...(examples !== undefined && { examples }),
|
||||
};
|
||||
const config: Partial<ProjectConfig> = {};
|
||||
|
||||
if (projectDirectory) config.projectName = projectDirectory;
|
||||
if (database !== undefined) config.database = database;
|
||||
if (orm !== undefined) config.orm = orm;
|
||||
if (auth !== undefined) config.auth = auth;
|
||||
if (packageManager) config.packageManager = packageManager;
|
||||
if ("git" in options) config.git = options.git;
|
||||
if ("install" in options) config.noInstall = !options.install;
|
||||
if (tursoOption !== undefined) config.turso = tursoOption;
|
||||
if (prismaPostgresOption !== undefined)
|
||||
config.prismaPostgres = prismaPostgresOption;
|
||||
if (backend) config.backend = backend;
|
||||
if (runtime) config.runtime = runtime;
|
||||
if (frontend !== undefined) config.frontend = frontend;
|
||||
if (addons !== undefined) config.addons = addons;
|
||||
if (examples !== undefined) config.examples = examples;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ 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";
|
||||
@@ -36,6 +37,7 @@ type PromptGroupResults = {
|
||||
packageManager: ProjectPackageManager;
|
||||
noInstall: boolean;
|
||||
turso: boolean;
|
||||
prismaPostgres: boolean;
|
||||
backend: ProjectBackend;
|
||||
runtime: ProjectRuntime;
|
||||
frontend: ProjectFrontend[];
|
||||
@@ -65,6 +67,10 @@ export async function gatherConfig(
|
||||
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),
|
||||
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
||||
examples: ({ results }) =>
|
||||
getExamplesChoice(
|
||||
@@ -97,6 +103,7 @@ export async function gatherConfig(
|
||||
packageManager: result.packageManager,
|
||||
noInstall: result.noInstall,
|
||||
turso: result.turso,
|
||||
prismaPostgres: result.prismaPostgres,
|
||||
backend: result.backend,
|
||||
runtime: result.runtime,
|
||||
};
|
||||
|
||||
21
apps/cli/src/prompts/prisma-postgres.ts
Normal file
21
apps/cli/src/prompts/prisma-postgres.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export interface ProjectConfig {
|
||||
packageManager: ProjectPackageManager;
|
||||
noInstall?: boolean;
|
||||
turso?: boolean;
|
||||
prismaPostgres: boolean;
|
||||
frontend: ProjectFrontend[];
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ export type CLIOptions = {
|
||||
packageManager?: string;
|
||||
install?: boolean;
|
||||
turso?: boolean;
|
||||
prismaPostgres?: boolean;
|
||||
backend?: string;
|
||||
runtime?: string;
|
||||
};
|
||||
|
||||
@@ -64,5 +64,11 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
||||
configDisplay.push(`${pc.blue("Turso Setup:")} ${config.turso}`);
|
||||
}
|
||||
|
||||
if (config.prismaPostgres !== undefined) {
|
||||
configDisplay.push(
|
||||
`${pc.blue("Prisma Postgres Setup:")} ${config.prismaPostgres ? "Yes" : "No"}`,
|
||||
);
|
||||
}
|
||||
|
||||
return configDisplay.join("\n");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import StackArchitect from "./StackArchitech";
|
||||
|
||||
export default function CustomizableSection() {
|
||||
return (
|
||||
<section className="w-full max-w-6xl mx-auto space-y-16 mt-20 relative z-10 px-4 sm:px-6">
|
||||
<section className="w-full max-w-7xl mx-auto space-y-16 mt-20 relative z-10 px-4 sm:px-6">
|
||||
<div className="text-center space-y-8 relative">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@@ -181,6 +181,24 @@ const TECH_OPTIONS = {
|
||||
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",
|
||||
@@ -302,6 +320,7 @@ interface StackState {
|
||||
orm: string | null;
|
||||
auth: string;
|
||||
turso: string;
|
||||
prismaPostgres: string;
|
||||
packageManager: string;
|
||||
addons: string[];
|
||||
examples: string[];
|
||||
@@ -318,6 +337,7 @@ const DEFAULT_STACK: StackState = {
|
||||
orm: "drizzle",
|
||||
auth: "true",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
packageManager: "bun",
|
||||
addons: [],
|
||||
examples: [],
|
||||
@@ -349,6 +369,24 @@ const StackArchitect = () => {
|
||||
}
|
||||
}, [stack.frontend, stack.auth]);
|
||||
|
||||
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 !== "sqlite" || stack.orm === "prisma") {
|
||||
if (stack.turso === "true") {
|
||||
setStack((prev) => ({ ...prev, turso: "false" }));
|
||||
}
|
||||
}
|
||||
}, [stack.database, stack.orm, stack.prismaPostgres, stack.turso]);
|
||||
|
||||
useEffect(() => {
|
||||
const cmd = generateCommand(stack);
|
||||
setCommand(cmd);
|
||||
@@ -387,13 +425,26 @@ const StackArchitect = () => {
|
||||
"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(
|
||||
"Todo and Ai example are only available with React Web.",
|
||||
"Todo and AI examples are only available with React Web.",
|
||||
);
|
||||
}
|
||||
if (stack.backendFramework === "elysia") {
|
||||
notes.examples.push("AI example is only compatible with Hono backend.");
|
||||
}
|
||||
|
||||
setCompatNotes(notes);
|
||||
}, [stack]);
|
||||
@@ -438,6 +489,10 @@ const StackArchitect = () => {
|
||||
flags.push("--turso");
|
||||
}
|
||||
|
||||
if (stackState.prismaPostgres === "true") {
|
||||
flags.push("--prisma-postgres");
|
||||
}
|
||||
|
||||
if (stackState.backendFramework !== "hono") {
|
||||
flags.push(`--backend ${stackState.backendFramework}`);
|
||||
}
|
||||
@@ -481,18 +536,14 @@ const StackArchitect = () => {
|
||||
...prev,
|
||||
frontend: ["none"],
|
||||
auth: "false",
|
||||
examples: prev.examples.filter(
|
||||
(ex) => ex !== "todo" && ex !== "ai",
|
||||
),
|
||||
examples: [],
|
||||
addons: prev.addons.filter(
|
||||
(addon) => addon !== "pwa" && addon !== "tauri",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle web router types (tanstack-router or react-router)
|
||||
if (webTypes.includes(techId)) {
|
||||
// If clicking on an already selected web router, do nothing
|
||||
if (
|
||||
currentSelection.includes(techId) &&
|
||||
currentSelection.length === 1
|
||||
@@ -500,7 +551,6 @@ const StackArchitect = () => {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// If selecting a web router while another one is active, replace it
|
||||
if (currentSelection.some((id) => webTypes.includes(id))) {
|
||||
const nonWebSelections = currentSelection.filter(
|
||||
(id) => !webTypes.includes(id),
|
||||
@@ -508,11 +558,10 @@ const StackArchitect = () => {
|
||||
return {
|
||||
...prev,
|
||||
frontend: [...nonWebSelections, techId],
|
||||
auth: prev.auth, // Keep existing auth setting
|
||||
auth: prev.auth,
|
||||
};
|
||||
}
|
||||
|
||||
// If no web router was selected before
|
||||
if (currentSelection.includes("none")) {
|
||||
return {
|
||||
...prev,
|
||||
@@ -531,11 +580,10 @@ const StackArchitect = () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Handle native selection
|
||||
if (techId === "native") {
|
||||
if (currentSelection.includes(techId)) {
|
||||
if (currentSelection.length === 1) {
|
||||
return prev; // Don't allow removing the last frontend
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
@@ -571,11 +619,20 @@ const StackArchitect = () => {
|
||||
} else {
|
||||
if (
|
||||
category === "examples" &&
|
||||
techId === "todo" &&
|
||||
(techId === "todo" || techId === "ai") &&
|
||||
!hasWebFrontend
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "examples" &&
|
||||
techId === "ai" &&
|
||||
prev.backendFramework === "elysia"
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "addons" &&
|
||||
(techId === "pwa" || techId === "tauri") &&
|
||||
@@ -583,6 +640,15 @@ const StackArchitect = () => {
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "addons" &&
|
||||
techId === "husky" &&
|
||||
!currentArray.includes("biome")
|
||||
) {
|
||||
currentArray.push("biome");
|
||||
}
|
||||
|
||||
currentArray.push(techId);
|
||||
}
|
||||
|
||||
@@ -597,8 +663,10 @@ const StackArchitect = () => {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
orm: null,
|
||||
orm: "none",
|
||||
turso: "false",
|
||||
prismaPostgres: "false",
|
||||
auth: hasWebFrontend(prev.frontend) ? prev.auth : "false",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -607,25 +675,63 @@ const StackArchitect = () => {
|
||||
...prev,
|
||||
database: techId,
|
||||
orm: "drizzle",
|
||||
turso: techId === "sqlite" ? prev.turso : "false",
|
||||
prismaPostgres:
|
||||
techId === "postgres" && prev.orm === "prisma"
|
||||
? prev.prismaPostgres
|
||||
: "false",
|
||||
};
|
||||
}
|
||||
|
||||
const updatedState = {
|
||||
...prev,
|
||||
database: techId,
|
||||
};
|
||||
|
||||
if (techId === "sqlite") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
turso: prev.turso,
|
||||
};
|
||||
updatedState.prismaPostgres = "false";
|
||||
} else if (techId === "postgres" && prev.orm === "prisma") {
|
||||
} else {
|
||||
updatedState.turso = "false";
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
turso: "false",
|
||||
};
|
||||
return updatedState;
|
||||
}
|
||||
|
||||
if (category === "turso" && prev.database !== "sqlite") {
|
||||
if (category === "orm") {
|
||||
if (prev.database === "none") {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const updatedState = {
|
||||
...prev,
|
||||
orm: techId,
|
||||
};
|
||||
|
||||
if (techId === "prisma") {
|
||||
updatedState.turso = "false";
|
||||
if (prev.database === "postgres") {
|
||||
} else {
|
||||
updatedState.prismaPostgres = "false";
|
||||
}
|
||||
} else if (techId === "drizzle" || techId === "none") {
|
||||
updatedState.prismaPostgres = "false";
|
||||
}
|
||||
|
||||
return updatedState;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "turso" &&
|
||||
(prev.database !== "sqlite" || prev.orm === "prisma")
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (
|
||||
category === "prismaPostgres" &&
|
||||
(prev.database !== "postgres" || prev.orm !== "prisma")
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -638,6 +744,13 @@ const StackArchitect = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
const hasWebFrontend = useCallback((frontendOptions: string[]) => {
|
||||
return (
|
||||
frontendOptions.includes("tanstack-router") ||
|
||||
frontendOptions.includes("react-router")
|
||||
);
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = useCallback(() => {
|
||||
navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
@@ -742,19 +855,27 @@ const StackArchitect = () => {
|
||||
stack[activeTab as keyof StackState] === tech.id;
|
||||
}
|
||||
|
||||
const hasWebFrontend =
|
||||
const hasWebFrontendSelected =
|
||||
stack.frontend.includes("tanstack-router") ||
|
||||
stack.frontend.includes("react-router");
|
||||
|
||||
const isDisabled =
|
||||
(activeTab === "orm" && stack.database === "none") ||
|
||||
(activeTab === "turso" && stack.database !== "sqlite") ||
|
||||
(activeTab === "auth" && !hasWebFrontend) ||
|
||||
(activeTab === "turso" &&
|
||||
(stack.database !== "sqlite" ||
|
||||
stack.orm === "prisma")) ||
|
||||
(activeTab === "prismaPostgres" &&
|
||||
(stack.database !== "postgres" ||
|
||||
stack.orm !== "prisma")) ||
|
||||
(activeTab === "auth" && !hasWebFrontendSelected) ||
|
||||
(activeTab === "examples" &&
|
||||
((tech.id === "todo" && !hasWebFrontend) ||
|
||||
(tech.id === "ai" && !hasWebFrontend))) ||
|
||||
(((tech.id === "todo" || tech.id === "ai") &&
|
||||
!hasWebFrontendSelected) ||
|
||||
(tech.id === "ai" &&
|
||||
stack.backendFramework === "elysia"))) ||
|
||||
(activeTab === "addons" &&
|
||||
(tech.id === "pwa" || tech.id === "tauri") &&
|
||||
!hasWebFrontend);
|
||||
!hasWebFrontendSelected);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -868,22 +989,49 @@ const StackArchitect = () => {
|
||||
}
|
||||
</span>
|
||||
|
||||
{stack.orm && (
|
||||
{stack.orm && stack.database !== "none" && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-cyan-100 dark:bg-cyan-900/30 text-cyan-800 dark:text-cyan-300 border border-cyan-300 dark:border-cyan-700/30">
|
||||
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.icon}{" "}
|
||||
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{hasWebFrontend(stack.frontend) && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 border border-green-300 dark:border-green-700/30">
|
||||
{TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.icon}{" "}
|
||||
{TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{stack.turso === "true" && stack.database === "sqlite" && (
|
||||
{stack.turso === "true" &&
|
||||
stack.database === "sqlite" &&
|
||||
stack.orm !== "prisma" && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-pink-100 dark:bg-pink-900/30 text-pink-800 dark:text-pink-300 border border-pink-300 dark:border-pink-700/30">
|
||||
{TECH_OPTIONS.turso.find((t) => t.id === stack.turso)?.icon}{" "}
|
||||
{TECH_OPTIONS.turso.find((t) => t.id === stack.turso)?.name}
|
||||
{
|
||||
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 px-1.5 py-0.5 rounded text-xs bg-indigo-100 dark:bg-indigo-900/30 text-indigo-800 dark:text-indigo-300 border border-indigo-300 dark:border-indigo-700/30">
|
||||
{
|
||||
TECH_OPTIONS.prismaPostgres.find(
|
||||
(t) => t.id === stack.prismaPostgres,
|
||||
)?.icon
|
||||
}{" "}
|
||||
{
|
||||
TECH_OPTIONS.prismaPostgres.find(
|
||||
(t) => t.id === stack.prismaPostgres,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useEffect, useState } from "react";
|
||||
import { Tweet } from "react-tweet";
|
||||
|
||||
const TWEET_IDS = [
|
||||
"1907728148294447538",
|
||||
"1907723601731530820",
|
||||
"1904144343125860404",
|
||||
"1904215768272654825",
|
||||
"1904233896851521980",
|
||||
|
||||
Reference in New Issue
Block a user