mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add convex
This commit is contained in:
@@ -1,80 +1,94 @@
|
||||
import * as path from "node:path";
|
||||
import path from "node:path";
|
||||
import consola from "consola"; // Import consola
|
||||
import fs from "fs-extra";
|
||||
import type { ProjectConfig } from "../types";
|
||||
import type { AvailableDependencies } from "../constants";
|
||||
import type { ProjectConfig, ProjectFrontend } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
export async function setupApi(config: ProjectConfig): Promise<void> {
|
||||
const { api, projectName, frontend } = config;
|
||||
const { api, projectName, frontend, backend, packageManager } = config;
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const isConvex = backend === "convex";
|
||||
const webDir = path.join(projectDir, "apps/web");
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const nativeDir = path.join(projectDir, "apps/native");
|
||||
const webDirExists = await fs.pathExists(webDir);
|
||||
const hasReactWeb = frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
|
||||
);
|
||||
const hasNuxtWeb = frontend.includes("nuxt");
|
||||
const hasSvelteWeb = frontend.includes("svelte");
|
||||
const nativeDirExists = await fs.pathExists(nativeDir);
|
||||
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/server", "@orpc/client"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (api === "trpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@trpc/server", "@trpc/client"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (config.backend === "hono") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@hono/trpc-server"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (config.backend === "elysia") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@elysiajs/trpc"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!isConvex && api !== "none") {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const serverDirExists = await fs.pathExists(serverDir);
|
||||
|
||||
if (webDirExists) {
|
||||
if (hasReactWeb) {
|
||||
const hasReactWeb = frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
|
||||
);
|
||||
const hasNuxtWeb = frontend.includes("nuxt");
|
||||
const hasSvelteWeb = frontend.includes("svelte");
|
||||
|
||||
if (serverDirExists) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/react-query", "@orpc/client", "@orpc/server"],
|
||||
projectDir: webDir,
|
||||
dependencies: ["@orpc/server", "@orpc/client"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (api === "trpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: [
|
||||
"@trpc/tanstack-react-query",
|
||||
"@trpc/client",
|
||||
"@trpc/server",
|
||||
],
|
||||
projectDir: webDir,
|
||||
dependencies: ["@trpc/server", "@trpc/client"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (config.backend === "hono") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@hono/trpc-server"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (config.backend === "elysia") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@elysiajs/trpc"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (hasNuxtWeb) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/vue-query", "@orpc/client", "@orpc/server"],
|
||||
projectDir: webDir,
|
||||
});
|
||||
}
|
||||
} else if (hasSvelteWeb) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/svelte-query", "@orpc/client", "@orpc/server"],
|
||||
projectDir: webDir,
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
if (webDirExists) {
|
||||
if (hasReactWeb) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/react-query", "@orpc/client", "@orpc/server"],
|
||||
projectDir: webDir,
|
||||
});
|
||||
} else if (api === "trpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: [
|
||||
"@trpc/tanstack-react-query",
|
||||
"@trpc/client",
|
||||
"@trpc/server",
|
||||
],
|
||||
projectDir: webDir,
|
||||
});
|
||||
}
|
||||
} else if (hasNuxtWeb) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@orpc/vue-query", "@orpc/client", "@orpc/server"],
|
||||
projectDir: webDir,
|
||||
});
|
||||
}
|
||||
} else if (hasSvelteWeb) {
|
||||
if (api === "orpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: [
|
||||
"@orpc/svelte-query",
|
||||
"@orpc/client",
|
||||
"@orpc/server",
|
||||
],
|
||||
projectDir: webDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frontend.includes("native")) {
|
||||
const nativeDir = path.join(projectDir, "apps/native");
|
||||
if (await fs.pathExists(nativeDir)) {
|
||||
if (nativeDirExists) {
|
||||
if (api === "trpc") {
|
||||
await addPackageDependency({
|
||||
dependencies: [
|
||||
@@ -92,4 +106,131 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reactBasedFrontends: ProjectFrontend[] = [
|
||||
"react-router",
|
||||
"tanstack-router",
|
||||
"tanstack-start",
|
||||
"next",
|
||||
"native",
|
||||
];
|
||||
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
|
||||
|
||||
if (needsReactQuery && !isConvex) {
|
||||
const reactQueryDeps: AvailableDependencies[] = ["@tanstack/react-query"];
|
||||
const reactQueryDevDeps: AvailableDependencies[] = [
|
||||
"@tanstack/react-query-devtools",
|
||||
];
|
||||
|
||||
const hasReactWeb = frontend.some(
|
||||
(f) => f !== "native" && reactBasedFrontends.includes(f),
|
||||
);
|
||||
const hasNative = frontend.includes("native");
|
||||
|
||||
if (hasReactWeb && webDirExists) {
|
||||
const webPkgJsonPath = path.join(webDir, "package.json");
|
||||
if (await fs.pathExists(webPkgJsonPath)) {
|
||||
try {
|
||||
await addPackageDependency({
|
||||
dependencies: reactQueryDeps,
|
||||
devDependencies: reactQueryDevDeps,
|
||||
projectDir: webDir,
|
||||
});
|
||||
} catch (error) {}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNative && nativeDirExists) {
|
||||
const nativePkgJsonPath = path.join(nativeDir, "package.json");
|
||||
if (await fs.pathExists(nativePkgJsonPath)) {
|
||||
try {
|
||||
await addPackageDependency({
|
||||
dependencies: reactQueryDeps,
|
||||
projectDir: nativeDir,
|
||||
});
|
||||
} catch (error) {}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else if (needsReactQuery && isConvex) {
|
||||
}
|
||||
|
||||
if (isConvex) {
|
||||
if (webDirExists) {
|
||||
const webPkgJsonPath = path.join(webDir, "package.json");
|
||||
if (await fs.pathExists(webPkgJsonPath)) {
|
||||
try {
|
||||
const webDepsToAdd: AvailableDependencies[] = ["convex"];
|
||||
if (frontend.includes("tanstack-start")) {
|
||||
webDepsToAdd.push("@convex-dev/react-query");
|
||||
}
|
||||
|
||||
await addPackageDependency({
|
||||
dependencies: webDepsToAdd,
|
||||
projectDir: webDir,
|
||||
});
|
||||
} catch (error) {}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeDirExists) {
|
||||
const nativePkgJsonPath = path.join(nativeDir, "package.json");
|
||||
if (await fs.pathExists(nativePkgJsonPath)) {
|
||||
try {
|
||||
await addPackageDependency({
|
||||
dependencies: ["convex"],
|
||||
projectDir: nativeDir,
|
||||
});
|
||||
} catch (error) {}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
const backendPackageName = `@${projectName}/backend`;
|
||||
const backendWorkspaceVersion =
|
||||
packageManager === "npm" ? "*" : "workspace:*";
|
||||
const addWorkspaceDepManually = async (
|
||||
pkgJsonPath: string,
|
||||
depName: string,
|
||||
depVersion: string,
|
||||
) => {
|
||||
try {
|
||||
const pkgJson = await fs.readJson(pkgJsonPath);
|
||||
if (!pkgJson.dependencies) {
|
||||
pkgJson.dependencies = {};
|
||||
}
|
||||
if (pkgJson.dependencies[depName] !== depVersion) {
|
||||
pkgJson.dependencies[depName] = depVersion;
|
||||
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
||||
} else {
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
if (webDirExists) {
|
||||
const webPkgJsonPath = path.join(webDir, "package.json");
|
||||
if (await fs.pathExists(webPkgJsonPath)) {
|
||||
await addWorkspaceDepManually(
|
||||
webPkgJsonPath,
|
||||
backendPackageName,
|
||||
backendWorkspaceVersion,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeDirExists) {
|
||||
const nativePkgJsonPath = path.join(nativeDir, "package.json");
|
||||
if (await fs.pathExists(nativePkgJsonPath)) {
|
||||
await addWorkspaceDepManually(
|
||||
nativePkgJsonPath,
|
||||
backendPackageName,
|
||||
backendWorkspaceVersion,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import type { ProjectConfig } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
export async function setupAuth(config: ProjectConfig): Promise<void> {
|
||||
const { projectName, auth, frontend } = config;
|
||||
if (!auth) {
|
||||
const { projectName, auth, frontend, backend } = config;
|
||||
|
||||
if (backend === "convex" || !auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,12 +19,15 @@ export async function setupAuth(config: ProjectConfig): Promise<void> {
|
||||
|
||||
const clientDirExists = await fs.pathExists(clientDir);
|
||||
const nativeDirExists = await fs.pathExists(nativeDir);
|
||||
const serverDirExists = await fs.pathExists(serverDir);
|
||||
|
||||
try {
|
||||
await addPackageDependency({
|
||||
dependencies: ["better-auth"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (serverDirExists) {
|
||||
await addPackageDependency({
|
||||
dependencies: ["better-auth"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
|
||||
const hasWebFrontend = frontend.some((f) =>
|
||||
[
|
||||
@@ -48,10 +52,12 @@ export async function setupAuth(config: ProjectConfig): Promise<void> {
|
||||
dependencies: ["better-auth", "@better-auth/expo"],
|
||||
projectDir: nativeDir,
|
||||
});
|
||||
await addPackageDependency({
|
||||
dependencies: ["@better-auth/expo"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (serverDirExists) {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@better-auth/expo"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
consola.error(pc.red("Failed to configure authentication dependencies"));
|
||||
|
||||
@@ -8,6 +8,11 @@ export async function setupBackendDependencies(
|
||||
config: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const { projectName, backend, runtime, api } = config;
|
||||
|
||||
if (backend === "convex") {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const framework = backend;
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
@@ -47,9 +52,11 @@ export async function setupBackendDependencies(
|
||||
devDependencies.push("@types/bun");
|
||||
}
|
||||
|
||||
await addPackageDependency({
|
||||
dependencies,
|
||||
devDependencies,
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (dependencies.length > 0 || devDependencies.length > 0) {
|
||||
await addPackageDependency({
|
||||
dependencies,
|
||||
devDependencies,
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,44 +28,46 @@ import {
|
||||
|
||||
export async function createProject(options: ProjectConfig) {
|
||||
const projectDir = path.resolve(process.cwd(), options.projectName);
|
||||
const isConvex = options.backend === "convex";
|
||||
|
||||
try {
|
||||
await fs.ensureDir(projectDir);
|
||||
|
||||
await copyBaseTemplate(projectDir, options);
|
||||
await setupFrontendTemplates(projectDir, options);
|
||||
|
||||
await setupBackendFramework(projectDir, options);
|
||||
await setupBackendDependencies(options);
|
||||
|
||||
await setupDbOrmTemplates(projectDir, options);
|
||||
|
||||
await setupDatabase(options);
|
||||
|
||||
await setupAuthTemplate(projectDir, options);
|
||||
await setupAuth(options);
|
||||
|
||||
if (!isConvex) {
|
||||
await setupDbOrmTemplates(projectDir, options);
|
||||
await setupAuthTemplate(projectDir, options);
|
||||
}
|
||||
if (options.examples.length > 0 && options.examples[0] !== "none") {
|
||||
await setupExamplesTemplate(projectDir, options);
|
||||
}
|
||||
await setupAddonsTemplate(projectDir, options);
|
||||
|
||||
await setupApi(options);
|
||||
|
||||
if (!isConvex) {
|
||||
await setupBackendDependencies(options);
|
||||
await setupDatabase(options);
|
||||
await setupRuntime(options);
|
||||
if (options.examples.length > 0 && options.examples[0] !== "none") {
|
||||
await setupExamples(options);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.addons.length > 0 && options.addons[0] !== "none") {
|
||||
await setupAddons(options);
|
||||
}
|
||||
|
||||
await setupExamplesTemplate(projectDir, options);
|
||||
await handleExtras(projectDir, options);
|
||||
|
||||
if (options.examples.length > 0 && options.examples[0] !== "none") {
|
||||
await setupExamples(options);
|
||||
if (!isConvex && options.auth) {
|
||||
await setupAuth(options);
|
||||
}
|
||||
|
||||
await setupApi(options);
|
||||
|
||||
await setupRuntime(options);
|
||||
|
||||
await handleExtras(projectDir, options);
|
||||
await setupEnvironmentVariables(options);
|
||||
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
await createReadme(projectDir, options);
|
||||
|
||||
await initializeGit(projectDir, options.git);
|
||||
|
||||
log.success("Project template successfully scaffolded!");
|
||||
@@ -89,6 +91,10 @@ export async function createProject(options: ProjectConfig) {
|
||||
cancel(pc.red(`Error during project creation: ${error.message}`));
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
} else {
|
||||
cancel(pc.red(`An unexpected error occurred: ${String(error)}`));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,25 @@ import { setupNeonPostgres } from "./neon-setup";
|
||||
import type { ProjectConfig } from "../types";
|
||||
|
||||
export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
||||
const { projectName, database, orm, dbSetup } = config;
|
||||
const { projectName, database, orm, dbSetup, backend } = config;
|
||||
|
||||
if (backend === "convex" || database === "none") {
|
||||
if (backend !== "convex") {
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const serverDbDir = path.join(serverDir, "src/db");
|
||||
if (await fs.pathExists(serverDbDir)) {
|
||||
await fs.remove(serverDbDir);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const s = spinner();
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
|
||||
if (database === "none") {
|
||||
await fs.remove(path.join(serverDir, "src/db"));
|
||||
if (!(await fs.pathExists(serverDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { generateAuthSecret } from "./auth-setup";
|
||||
|
||||
interface EnvVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
value: string | null | undefined;
|
||||
condition: boolean;
|
||||
}
|
||||
|
||||
@@ -21,41 +21,59 @@ async function addEnvVariablesToFile(
|
||||
}
|
||||
|
||||
let modified = false;
|
||||
let contentToAdd = "";
|
||||
|
||||
for (const { key, value, condition } of variables) {
|
||||
if (condition) {
|
||||
const regex = new RegExp(`^${key}=.*$`, "m");
|
||||
const valueToWrite = value ?? "";
|
||||
|
||||
if (regex.test(envContent)) {
|
||||
if (value) {
|
||||
envContent = envContent.replace(regex, `${key}=${value}`);
|
||||
const existingMatch = envContent.match(regex);
|
||||
if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
|
||||
envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
envContent += `\n${key}=${value}`;
|
||||
contentToAdd += `${key}=${valueToWrite}\n`;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contentToAdd) {
|
||||
if (envContent.length > 0 && !envContent.endsWith("\n")) {
|
||||
envContent += "\n";
|
||||
}
|
||||
envContent += contentToAdd;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
await fs.writeFile(filePath, envContent.trim());
|
||||
await fs.writeFile(filePath, envContent.trimEnd());
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupEnvironmentVariables(
|
||||
config: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const { projectName } = config;
|
||||
const {
|
||||
projectName,
|
||||
backend,
|
||||
frontend,
|
||||
database,
|
||||
orm,
|
||||
auth,
|
||||
examples,
|
||||
dbSetup,
|
||||
} = config;
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const options = config;
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const envPath = path.join(serverDir, ".env");
|
||||
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = options.frontend.includes("tanstack-start");
|
||||
const hasNextJs = options.frontend.includes("next");
|
||||
const hasNuxt = options.frontend.includes("nuxt");
|
||||
const hasSvelte = options.frontend.includes("svelte");
|
||||
const hasReactRouter = frontend.includes("react-router");
|
||||
const hasTanStackRouter = frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = frontend.includes("tanstack-start");
|
||||
const hasNextJs = frontend.includes("next");
|
||||
const hasNuxt = frontend.includes("nuxt");
|
||||
const hasSvelte = frontend.includes("svelte");
|
||||
const hasWebFrontend =
|
||||
hasReactRouter ||
|
||||
hasTanStackRouter ||
|
||||
@@ -64,30 +82,99 @@ export async function setupEnvironmentVariables(
|
||||
hasNuxt ||
|
||||
hasSvelte;
|
||||
|
||||
if (hasWebFrontend) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
if (await fs.pathExists(clientDir)) {
|
||||
let envVarName = "VITE_SERVER_URL";
|
||||
let serverUrl = "http://localhost:3000";
|
||||
|
||||
if (hasNextJs) {
|
||||
envVarName = "NEXT_PUBLIC_SERVER_URL";
|
||||
} else if (hasNuxt) {
|
||||
envVarName = "NUXT_PUBLIC_SERVER_URL";
|
||||
} else if (hasSvelte) {
|
||||
envVarName = "PUBLIC_SERVER_URL";
|
||||
}
|
||||
|
||||
if (backend === "convex") {
|
||||
if (hasNextJs) envVarName = "NEXT_PUBLIC_CONVEX_URL";
|
||||
else if (hasNuxt) envVarName = "NUXT_PUBLIC_CONVEX_URL";
|
||||
else if (hasSvelte) envVarName = "PUBLIC_CONVEX_URL";
|
||||
else envVarName = "VITE_CONVEX_URL";
|
||||
|
||||
serverUrl = "https://<YOUR_CONVEX_URL>";
|
||||
}
|
||||
|
||||
const clientVars: EnvVariable[] = [
|
||||
{
|
||||
key: envVarName,
|
||||
value: serverUrl,
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
||||
}
|
||||
}
|
||||
|
||||
if (frontend.includes("native")) {
|
||||
const nativeDir = path.join(projectDir, "apps/native");
|
||||
if (await fs.pathExists(nativeDir)) {
|
||||
let envVarName = "EXPO_PUBLIC_SERVER_URL";
|
||||
let serverUrl = "http://localhost:3000";
|
||||
|
||||
if (backend === "convex") {
|
||||
envVarName = "EXPO_PUBLIC_CONVEX_URL";
|
||||
serverUrl = "https://<YOUR_CONVEX_URL>";
|
||||
}
|
||||
|
||||
const nativeVars: EnvVariable[] = [
|
||||
{
|
||||
key: envVarName,
|
||||
value: serverUrl,
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
||||
}
|
||||
}
|
||||
|
||||
if (backend === "convex") {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
if (!(await fs.pathExists(serverDir))) {
|
||||
return;
|
||||
}
|
||||
const envPath = path.join(serverDir, ".env");
|
||||
|
||||
let corsOrigin = "http://localhost:3001";
|
||||
if (hasReactRouter || hasSvelte) {
|
||||
corsOrigin = "http://localhost:5173";
|
||||
} else if (hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt) {
|
||||
corsOrigin = "http://localhost:3001";
|
||||
}
|
||||
|
||||
let databaseUrl = "";
|
||||
let databaseUrl: string | null = null;
|
||||
const specializedSetup =
|
||||
options.dbSetup === "turso" ||
|
||||
options.dbSetup === "prisma-postgres" ||
|
||||
options.dbSetup === "mongodb-atlas" ||
|
||||
options.dbSetup === "neon";
|
||||
dbSetup === "turso" ||
|
||||
dbSetup === "prisma-postgres" ||
|
||||
dbSetup === "mongodb-atlas" ||
|
||||
dbSetup === "neon";
|
||||
|
||||
if (!specializedSetup) {
|
||||
if (options.database === "postgres") {
|
||||
databaseUrl =
|
||||
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public";
|
||||
} else if (options.database === "mysql") {
|
||||
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
||||
} else if (options.database === "mongodb") {
|
||||
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
||||
} else if (options.database === "sqlite") {
|
||||
databaseUrl = "file:./local.db";
|
||||
if (database !== "none" && !specializedSetup) {
|
||||
switch (database) {
|
||||
case "postgres":
|
||||
databaseUrl =
|
||||
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public";
|
||||
break;
|
||||
case "mysql":
|
||||
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
||||
break;
|
||||
case "mongodb":
|
||||
databaseUrl = "mongodb://localhost:27017/mydatabase";
|
||||
break;
|
||||
case "sqlite":
|
||||
databaseUrl = "file:./local.db";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,59 +187,24 @@ export async function setupEnvironmentVariables(
|
||||
{
|
||||
key: "BETTER_AUTH_SECRET",
|
||||
value: generateAuthSecret(),
|
||||
condition: !!options.auth,
|
||||
condition: !!auth,
|
||||
},
|
||||
{
|
||||
key: "BETTER_AUTH_URL",
|
||||
value: "http://localhost:3000",
|
||||
condition: !!options.auth,
|
||||
condition: !!auth,
|
||||
},
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: databaseUrl,
|
||||
condition:
|
||||
options.database !== "none" && databaseUrl !== "" && !specializedSetup,
|
||||
condition: database !== "none" && !specializedSetup,
|
||||
},
|
||||
{
|
||||
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
||||
value: "",
|
||||
condition: options.examples?.includes("ai") || false,
|
||||
condition: examples?.includes("ai") || false,
|
||||
},
|
||||
];
|
||||
|
||||
await addEnvVariablesToFile(envPath, serverVars);
|
||||
|
||||
if (hasWebFrontend) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
let envVarName = "VITE_SERVER_URL";
|
||||
|
||||
if (hasNextJs) {
|
||||
envVarName = "NEXT_PUBLIC_SERVER_URL";
|
||||
} else if (hasNuxt) {
|
||||
envVarName = "NUXT_PUBLIC_SERVER_URL";
|
||||
} else if (hasSvelte) {
|
||||
envVarName = "PUBLIC_SERVER_URL";
|
||||
}
|
||||
|
||||
const clientVars: EnvVariable[] = [
|
||||
{
|
||||
key: envVarName,
|
||||
value: "http://localhost:3000",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
||||
}
|
||||
|
||||
if (options.frontend.includes("native")) {
|
||||
const nativeDir = path.join(projectDir, "apps/native");
|
||||
const nativeVars: EnvVariable[] = [
|
||||
{
|
||||
key: "EXPO_PUBLIC_SERVER_URL",
|
||||
value: "http://localhost:3000",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,24 @@ import type { ProjectConfig } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
export async function setupExamples(config: ProjectConfig): Promise<void> {
|
||||
const { projectName, examples, frontend } = config;
|
||||
const { projectName, examples, frontend, backend } = config;
|
||||
|
||||
if (
|
||||
backend === "convex" ||
|
||||
!examples ||
|
||||
examples.length === 0 ||
|
||||
examples[0] === "none"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
|
||||
if (examples.includes("ai")) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const clientDirExists = await fs.pathExists(clientDir);
|
||||
const serverDirExists = await fs.pathExists(serverDir);
|
||||
|
||||
const hasNuxt = frontend.includes("nuxt");
|
||||
const hasSvelte = frontend.includes("svelte");
|
||||
@@ -22,6 +33,7 @@ export async function setupExamples(config: ProjectConfig): Promise<void> {
|
||||
dependencies.push("@ai-sdk/vue");
|
||||
} else if (hasSvelte) {
|
||||
dependencies.push("@ai-sdk/svelte");
|
||||
} else {
|
||||
}
|
||||
await addPackageDependency({
|
||||
dependencies,
|
||||
@@ -29,9 +41,11 @@ export async function setupExamples(config: ProjectConfig): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
await addPackageDependency({
|
||||
dependencies: ["ai", "@ai-sdk/google"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (serverDirExists) {
|
||||
await addPackageDependency({
|
||||
dependencies: ["ai", "@ai-sdk/google"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { consola } from "consola";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectDatabase, ProjectOrm, ProjectRuntime } from "../types";
|
||||
import type {
|
||||
ProjectBackend,
|
||||
ProjectDatabase,
|
||||
ProjectOrm,
|
||||
ProjectRuntime,
|
||||
} from "../types";
|
||||
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
||||
|
||||
import type { ProjectConfig } from "../types";
|
||||
@@ -17,16 +22,20 @@ export function displayPostInstallInstructions(
|
||||
addons,
|
||||
runtime,
|
||||
frontend,
|
||||
backend,
|
||||
} = config;
|
||||
|
||||
const isConvex = backend === "convex";
|
||||
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
||||
const cdCmd = `cd ${projectName}`;
|
||||
const hasHuskyOrBiome =
|
||||
addons?.includes("husky") || addons?.includes("biome");
|
||||
|
||||
const databaseInstructions =
|
||||
database !== "none"
|
||||
!isConvex && database !== "none"
|
||||
? getDatabaseInstructions(database, orm, runCmd, runtime)
|
||||
: "";
|
||||
|
||||
const tauriInstructions = addons?.includes("tauri")
|
||||
? getTauriInstructions(runCmd)
|
||||
: "";
|
||||
@@ -34,7 +43,7 @@ export function displayPostInstallInstructions(
|
||||
? getLintingInstructions(runCmd)
|
||||
: "";
|
||||
const nativeInstructions = frontend?.includes("native")
|
||||
? getNativeInstructions()
|
||||
? getNativeInstructions(isConvex)
|
||||
: "";
|
||||
const pwaInstructions =
|
||||
addons?.includes("pwa") &&
|
||||
@@ -45,6 +54,7 @@ export function displayPostInstallInstructions(
|
||||
const starlightInstructions = addons?.includes("starlight")
|
||||
? getStarlightInstructions(runCmd)
|
||||
: "";
|
||||
|
||||
const hasWeb = frontend?.some((f) =>
|
||||
[
|
||||
"tanstack-router",
|
||||
@@ -56,78 +66,86 @@ export function displayPostInstallInstructions(
|
||||
].includes(f),
|
||||
);
|
||||
const hasNative = frontend?.includes("native");
|
||||
|
||||
const bunWebNativeWarning =
|
||||
packageManager === "bun" && hasNative && hasWeb
|
||||
? getBunWebNativeWarning()
|
||||
: "";
|
||||
const noOrmWarning =
|
||||
database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
||||
!isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
||||
|
||||
const hasTanstackRouter = frontend?.includes("tanstack-router");
|
||||
const hasTanstackStart = frontend?.includes("tanstack-start");
|
||||
const hasReactRouter = frontend?.includes("react-router");
|
||||
const hasNuxt = frontend?.includes("nuxt");
|
||||
const hasSvelte = frontend?.includes("svelte");
|
||||
const hasWebFrontend =
|
||||
hasTanstackRouter ||
|
||||
hasReactRouter ||
|
||||
hasTanstackStart ||
|
||||
hasNuxt ||
|
||||
hasSvelte;
|
||||
const hasNativeFrontend = frontend?.includes("native");
|
||||
const hasFrontend = hasWebFrontend || hasNativeFrontend;
|
||||
|
||||
const webPort = hasReactRouter || hasSvelte ? "5173" : "3001";
|
||||
|
||||
const tazeCommand = getPackageExecutionCommand(packageManager, "taze -r");
|
||||
consola.box(
|
||||
`${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}
|
||||
${
|
||||
!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""
|
||||
}${pc.cyan(depsInstalled ? "2." : "3.")} ${runCmd} dev
|
||||
|
||||
${pc.bold("Your project will be available at:")}
|
||||
${
|
||||
hasFrontend
|
||||
? `${
|
||||
hasWebFrontend
|
||||
? `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`
|
||||
: ""
|
||||
}`
|
||||
: `${pc.yellow(
|
||||
"NOTE:",
|
||||
)} You are creating a backend-only app (no frontend selected)\n`
|
||||
}${pc.cyan("•")} Backend: http://localhost:3000
|
||||
${
|
||||
addons?.includes("starlight")
|
||||
? `${pc.cyan("•")} Docs: http://localhost:4321\n`
|
||||
: ""
|
||||
}${nativeInstructions ? `\n${nativeInstructions.trim()}` : ""}${
|
||||
databaseInstructions ? `\n${databaseInstructions.trim()}` : ""
|
||||
}${tauriInstructions ? `\n${tauriInstructions.trim()}` : ""}${
|
||||
lintingInstructions ? `\n${lintingInstructions.trim()}` : ""
|
||||
}${pwaInstructions ? `\n${pwaInstructions.trim()}` : ""}${
|
||||
starlightInstructions ? `\n${starlightInstructions.trim()}` : ""
|
||||
}${noOrmWarning ? `\n${noOrmWarning.trim()}` : ""}${
|
||||
bunWebNativeWarning ? `\n${bunWebNativeWarning.trim()}` : ""
|
||||
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
||||
let stepCounter = 2;
|
||||
|
||||
if (!depsInstalled) {
|
||||
output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
||||
}
|
||||
|
||||
if (isConvex) {
|
||||
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup ${pc.dim("(this will guide you through Convex project setup)")}\n`;
|
||||
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
||||
} else {
|
||||
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
||||
}
|
||||
|
||||
output += `${pc.bold("Your project will be available at:")}\n`;
|
||||
|
||||
if (hasWeb) {
|
||||
output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
||||
} else if (!hasNative && !addons?.includes("starlight")) {
|
||||
output += `${pc.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)\n`;
|
||||
}
|
||||
|
||||
if (!isConvex) {
|
||||
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
||||
}
|
||||
|
||||
if (addons?.includes("starlight")) {
|
||||
output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
||||
}
|
||||
|
||||
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
||||
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
||||
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
|
||||
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
||||
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
||||
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
||||
|
||||
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
||||
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
||||
|
||||
output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`;
|
||||
output += `${pc.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:\n`;
|
||||
output += pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack");
|
||||
|
||||
consola.box(output);
|
||||
}
|
||||
|
||||
${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}
|
||||
function getNativeInstructions(isConvex: boolean): string {
|
||||
const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
|
||||
const exampleUrl = isConvex
|
||||
? "https://<YOUR_CONVEX_URL>"
|
||||
: "http://<YOUR_LOCAL_IP>:3000";
|
||||
const envFileName = ".env";
|
||||
const ipNote = isConvex
|
||||
? "your Convex deployment URL (find after running 'dev:setup')"
|
||||
: "your local IP address";
|
||||
|
||||
${pc.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:
|
||||
${pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack")}`,
|
||||
);
|
||||
}
|
||||
|
||||
function getNativeInstructions(): string {
|
||||
return `${pc.yellow(
|
||||
"NOTE:",
|
||||
)} For Expo connectivity issues, update apps/native/.env \nwith your local IP:\n${"EXPO_PUBLIC_SERVER_URL=http://192.168.0.103:3000"}\n`;
|
||||
)} For Expo connectivity issues, update apps/native/${envFileName} \nwith ${ipNote}:\n${`${envVar}=${exampleUrl}`}\n`;
|
||||
}
|
||||
|
||||
function getLintingInstructions(runCmd?: string): string {
|
||||
return `${pc.bold("Linting and formatting:")}\n${pc.cyan(
|
||||
"•",
|
||||
)} Format and lint fix: ${`${runCmd} check`}\n\n`;
|
||||
)} Format and lint fix: ${`${runCmd} check`}\n`;
|
||||
}
|
||||
|
||||
function getDatabaseInstructions(
|
||||
@@ -161,10 +179,19 @@ function getDatabaseInstructions(
|
||||
} else if (orm === "drizzle") {
|
||||
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
||||
instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
||||
if (database === "sqlite") {
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`,
|
||||
);
|
||||
}
|
||||
} else if (orm === "none") {
|
||||
instructions.push(
|
||||
`${pc.yellow("NOTE:")} Manual database schema setup required.`,
|
||||
);
|
||||
}
|
||||
|
||||
return instructions.length
|
||||
? `${pc.bold("Database commands:")}\n${instructions.join("\n")}\n\n`
|
||||
? `${pc.bold("Database commands:")}\n${instructions.join("\n")}`
|
||||
: "";
|
||||
}
|
||||
|
||||
@@ -175,31 +202,31 @@ function getTauriInstructions(runCmd?: string): string {
|
||||
"•",
|
||||
)} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow(
|
||||
"NOTE:",
|
||||
)} Tauri requires Rust and platform-specific dependencies.\nSee: ${"https://v2.tauri.app/start/prerequisites/"}\n\n`;
|
||||
)} Tauri requires Rust and platform-specific dependencies.\nSee: ${"https://v2.tauri.app/start/prerequisites/"}`;
|
||||
}
|
||||
|
||||
function getPwaInstructions(): string {
|
||||
return `${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
|
||||
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
|
||||
"NOTE:",
|
||||
)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n`;
|
||||
)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
||||
}
|
||||
|
||||
function getStarlightInstructions(runCmd?: string): string {
|
||||
return `${pc.bold("Documentation with Starlight:")}\n${pc.cyan(
|
||||
return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan(
|
||||
"•",
|
||||
)} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan(
|
||||
"•",
|
||||
)} Build docs site: ${`cd apps/docs && ${runCmd} build`}\n`;
|
||||
)} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
|
||||
}
|
||||
|
||||
function getNoOrmWarning(): string {
|
||||
return `\n${pc.yellow(
|
||||
"WARNING:",
|
||||
)} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.\n`;
|
||||
)} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.`;
|
||||
}
|
||||
|
||||
function getBunWebNativeWarning(): string {
|
||||
return `\n${pc.yellow(
|
||||
"WARNING:",
|
||||
)} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.\n`;
|
||||
)} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { log } from "@clack/prompts";
|
||||
import { $, execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import { dependencyVersionMap } from "../constants";
|
||||
import type { ProjectConfig } from "../types";
|
||||
|
||||
export async function updatePackageConfigurations(
|
||||
@@ -11,7 +10,11 @@ export async function updatePackageConfigurations(
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
await updateRootPackageJson(projectDir, options);
|
||||
await updateServerPackageJson(projectDir, options);
|
||||
if (options.backend !== "convex") {
|
||||
await updateServerPackageJson(projectDir, options);
|
||||
} else {
|
||||
await updateConvexPackageJson(projectDir, options);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRootPackageJson(
|
||||
@@ -19,75 +22,148 @@ async function updateRootPackageJson(
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
||||
if (await fs.pathExists(rootPackageJsonPath)) {
|
||||
const packageJson = await fs.readJson(rootPackageJsonPath);
|
||||
packageJson.name = options.projectName;
|
||||
if (!(await fs.pathExists(rootPackageJsonPath))) return;
|
||||
|
||||
const turboScripts = {
|
||||
dev: "turbo dev",
|
||||
build: "turbo build",
|
||||
"check-types": "turbo check-types",
|
||||
"dev:native": "turbo -F native dev",
|
||||
"dev:web": "turbo -F web dev",
|
||||
"dev:server": "turbo -F server dev",
|
||||
"db:push": "turbo -F server db:push",
|
||||
"db:studio": "turbo -F server db:studio",
|
||||
};
|
||||
const packageJson = await fs.readJson(rootPackageJsonPath);
|
||||
packageJson.name = options.projectName;
|
||||
|
||||
const pnpmScripts = {
|
||||
dev: "pnpm -r dev",
|
||||
build: "pnpm -r build",
|
||||
"check-types": "pnpm -r check-types",
|
||||
"dev:native": "pnpm --filter native dev",
|
||||
"dev:web": "pnpm --filter web dev",
|
||||
"dev:server": "pnpm --filter server dev",
|
||||
"db:push": "pnpm --filter server db:push",
|
||||
"db:studio": "pnpm --filter server db:studio",
|
||||
};
|
||||
if (!packageJson.scripts) {
|
||||
packageJson.scripts = {};
|
||||
}
|
||||
const scripts = packageJson.scripts;
|
||||
|
||||
const npmScripts = {
|
||||
dev: "npm run dev --workspaces",
|
||||
build: "npm run build --workspaces",
|
||||
"check-types": "npm run check-types --workspaces",
|
||||
"dev:native": "npm run dev --workspace native",
|
||||
"dev:web": "npm run dev --workspace web",
|
||||
"dev:server": "npm run dev --workspace server",
|
||||
"db:push": "npm run db:push --workspace server",
|
||||
"db:studio": "npm run db:studio --workspace server",
|
||||
};
|
||||
const backendPackageName =
|
||||
options.backend === "convex" ? `@${options.projectName}/backend` : "server";
|
||||
|
||||
const bunScripts = {
|
||||
dev: "bun run --filter '*' dev",
|
||||
build: "bun run --filter '*' build",
|
||||
"check-types": "bun run --filter '*' check-types",
|
||||
"dev:native": "bun run --filter native dev",
|
||||
"dev:web": "bun run --filter web dev",
|
||||
"dev:server": "bun run --filter server dev",
|
||||
"db:push": "bun run --filter server db:push",
|
||||
"db:studio": "bun run --filter server db:studio",
|
||||
};
|
||||
let serverDevScript = "";
|
||||
if (options.addons.includes("turborepo")) {
|
||||
serverDevScript = `turbo -F ${backendPackageName} dev`;
|
||||
} else if (options.packageManager === "bun") {
|
||||
serverDevScript = `bun run --filter ${backendPackageName} dev`;
|
||||
} else if (options.packageManager === "pnpm") {
|
||||
serverDevScript = `pnpm --filter ${backendPackageName} dev`;
|
||||
} else if (options.packageManager === "npm") {
|
||||
serverDevScript = `npm run dev --workspace ${backendPackageName}`;
|
||||
}
|
||||
|
||||
if (options.addons.includes("turborepo")) {
|
||||
packageJson.scripts = turboScripts;
|
||||
} else {
|
||||
if (options.packageManager === "pnpm") {
|
||||
packageJson.scripts = pnpmScripts;
|
||||
} else if (options.packageManager === "npm") {
|
||||
packageJson.scripts = npmScripts;
|
||||
} else if (options.packageManager === "bun") {
|
||||
packageJson.scripts = bunScripts;
|
||||
} else {
|
||||
packageJson.scripts = {};
|
||||
}
|
||||
let devScript = "";
|
||||
if (options.packageManager === "pnpm") {
|
||||
devScript = "pnpm -r dev";
|
||||
} else if (options.packageManager === "npm") {
|
||||
devScript = "npm run dev --workspaces";
|
||||
} else if (options.packageManager === "bun") {
|
||||
devScript = "bun run --filter '*' dev";
|
||||
}
|
||||
|
||||
const needsDbScripts =
|
||||
options.backend !== "convex" &&
|
||||
options.database !== "none" &&
|
||||
options.orm !== "none";
|
||||
|
||||
if (options.addons.includes("turborepo")) {
|
||||
scripts.dev = "turbo dev";
|
||||
scripts.build = "turbo build";
|
||||
scripts["check-types"] = "turbo check-types";
|
||||
scripts["dev:native"] = "turbo -F native dev";
|
||||
scripts["dev:web"] = "turbo -F web dev";
|
||||
scripts["dev:server"] = serverDevScript;
|
||||
if (options.backend === "convex") {
|
||||
scripts["dev:setup"] = `turbo -F ${backendPackageName} setup`;
|
||||
}
|
||||
if (needsDbScripts) {
|
||||
scripts["db:push"] = `turbo -F ${backendPackageName} db:push`;
|
||||
scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
|
||||
}
|
||||
} else if (options.packageManager === "pnpm") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "pnpm -r build";
|
||||
scripts["check-types"] = "pnpm -r check-types";
|
||||
scripts["dev:native"] = "pnpm --filter native dev";
|
||||
scripts["dev:web"] = "pnpm --filter web dev";
|
||||
scripts["dev:server"] = serverDevScript;
|
||||
if (options.backend === "convex") {
|
||||
scripts["dev:setup"] = `pnpm --filter ${backendPackageName} setup`;
|
||||
}
|
||||
if (needsDbScripts) {
|
||||
scripts["db:push"] = `pnpm --filter ${backendPackageName} db:push`;
|
||||
scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
|
||||
}
|
||||
} else if (options.packageManager === "npm") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "npm run build --workspaces";
|
||||
scripts["check-types"] = "npm run check-types --workspaces";
|
||||
scripts["dev:native"] = "npm run dev --workspace native";
|
||||
scripts["dev:web"] = "npm run dev --workspace web";
|
||||
scripts["dev:server"] = serverDevScript;
|
||||
if (options.backend === "convex") {
|
||||
scripts["dev:setup"] = `npm run setup --workspace ${backendPackageName}`;
|
||||
}
|
||||
if (needsDbScripts) {
|
||||
scripts["db:push"] = `npm run db:push --workspace ${backendPackageName}`;
|
||||
scripts["db:studio"] =
|
||||
`npm run db:studio --workspace ${backendPackageName}`;
|
||||
}
|
||||
} else if (options.packageManager === "bun") {
|
||||
scripts.dev = devScript;
|
||||
scripts.build = "bun run --filter '*' build";
|
||||
scripts["check-types"] = "bun run --filter '*' check-types";
|
||||
scripts["dev:native"] = "bun run --filter native dev";
|
||||
scripts["dev:web"] = "bun run --filter web dev";
|
||||
scripts["dev:server"] = serverDevScript;
|
||||
if (options.backend === "convex") {
|
||||
scripts["dev:setup"] = `bun run --filter ${backendPackageName} setup`;
|
||||
}
|
||||
if (needsDbScripts) {
|
||||
scripts["db:push"] = `bun run --filter ${backendPackageName} db:push`;
|
||||
scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.addons.includes("biome")) {
|
||||
scripts.check = "biome check --write .";
|
||||
}
|
||||
if (options.addons.includes("husky")) {
|
||||
scripts.prepare = "husky";
|
||||
packageJson["lint-staged"] = {
|
||||
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
||||
"biome check --write .",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execa(options.packageManager, ["-v"], {
|
||||
cwd: projectDir,
|
||||
});
|
||||
packageJson.packageManager = `${options.packageManager}@${stdout.trim()}`;
|
||||
|
||||
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
||||
} catch (e) {
|
||||
log.warn(`Could not determine ${options.packageManager} version.`);
|
||||
}
|
||||
|
||||
if (!packageJson.workspaces) {
|
||||
packageJson.workspaces = [];
|
||||
}
|
||||
const workspaces = packageJson.workspaces;
|
||||
|
||||
if (options.backend === "convex") {
|
||||
if (!workspaces.includes("packages/*")) {
|
||||
workspaces.push("packages/*");
|
||||
}
|
||||
const needsAppsDir =
|
||||
options.frontend.length > 0 || options.addons.includes("starlight");
|
||||
if (needsAppsDir && !workspaces.includes("apps/*")) {
|
||||
workspaces.push("apps/*");
|
||||
}
|
||||
} else {
|
||||
if (!workspaces.includes("apps/*")) {
|
||||
workspaces.push("apps/*");
|
||||
}
|
||||
if (!workspaces.includes("packages/*")) {
|
||||
workspaces.push("packages/*");
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
async function updateServerPackageJson(
|
||||
@@ -99,28 +175,53 @@ async function updateServerPackageJson(
|
||||
"apps/server/package.json",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(serverPackageJsonPath)) {
|
||||
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
||||
if (!(await fs.pathExists(serverPackageJsonPath))) return;
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.database === "sqlite" && options.orm === "drizzle") {
|
||||
serverPackageJson.scripts["db:local"] = "turso dev --db-file local.db";
|
||||
}
|
||||
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
||||
|
||||
if (options.orm === "prisma") {
|
||||
serverPackageJson.scripts["db:push"] =
|
||||
"prisma db push --schema ./prisma/schema";
|
||||
serverPackageJson.scripts["db:studio"] = "prisma studio";
|
||||
} else if (options.orm === "drizzle") {
|
||||
serverPackageJson.scripts["db:push"] = "drizzle-kit push";
|
||||
serverPackageJson.scripts["db:studio"] = "drizzle-kit studio";
|
||||
}
|
||||
if (!serverPackageJson.scripts) {
|
||||
serverPackageJson.scripts = {};
|
||||
}
|
||||
const scripts = serverPackageJson.scripts;
|
||||
|
||||
if (options.database !== "none") {
|
||||
if (options.database === "sqlite" && options.orm === "drizzle") {
|
||||
scripts["db:local"] = "turso dev --db-file local.db";
|
||||
}
|
||||
|
||||
await fs.writeJson(serverPackageJsonPath, serverPackageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
if (options.orm === "prisma") {
|
||||
scripts["db:push"] = "prisma db push --schema ./prisma/schema.prisma";
|
||||
scripts["db:studio"] = "prisma studio";
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:push"] = "drizzle-kit push";
|
||||
scripts["db:studio"] = "drizzle-kit studio";
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(serverPackageJsonPath, serverPackageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
|
||||
async function updateConvexPackageJson(
|
||||
projectDir: string,
|
||||
options: ProjectConfig,
|
||||
): Promise<void> {
|
||||
const convexPackageJsonPath = path.join(
|
||||
projectDir,
|
||||
"packages/backend/package.json",
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(convexPackageJsonPath))) return;
|
||||
|
||||
const convexPackageJson = await fs.readJson(convexPackageJsonPath);
|
||||
convexPackageJson.name = `@${options.projectName}/backend`;
|
||||
|
||||
if (!convexPackageJson.scripts) {
|
||||
convexPackageJson.scripts = {};
|
||||
}
|
||||
|
||||
await fs.writeJson(convexPackageJsonPath, convexPackageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
export async function initializeGit(
|
||||
|
||||
@@ -5,13 +5,18 @@ import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
export async function setupRuntime(config: ProjectConfig): Promise<void> {
|
||||
const { projectName, runtime, backend } = config;
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
if (backend === "next") {
|
||||
|
||||
if (backend === "convex" || backend === "next" || runtime === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectDir = path.resolve(process.cwd(), projectName);
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
|
||||
if (!(await fs.pathExists(serverDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtime === "bun") {
|
||||
await setupBunRuntime(serverDir, backend);
|
||||
} else if (runtime === "node") {
|
||||
@@ -24,6 +29,8 @@ async function setupBunRuntime(
|
||||
backend: ProjectBackend,
|
||||
): Promise<void> {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (!(await fs.pathExists(packageJsonPath))) return;
|
||||
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
@@ -45,6 +52,8 @@ async function setupNodeRuntime(
|
||||
backend: ProjectBackend,
|
||||
): Promise<void> {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (!(await fs.pathExists(packageJsonPath))) return;
|
||||
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import path from "node:path";
|
||||
import consola from "consola";
|
||||
import fs from "fs-extra";
|
||||
import { globby } from "globby";
|
||||
import pc from "picocolors";
|
||||
@@ -28,26 +27,28 @@ async function processAndCopyFiles(
|
||||
if (relativeSrcPath.endsWith(".hbs")) {
|
||||
relativeDestPath = relativeSrcPath.slice(0, -4);
|
||||
}
|
||||
if (path.basename(relativeSrcPath) === "_gitignore") {
|
||||
const basename = path.basename(relativeSrcPath);
|
||||
if (basename === "_gitignore") {
|
||||
relativeDestPath = path.join(path.dirname(relativeSrcPath), ".gitignore");
|
||||
}
|
||||
if (path.basename(relativeSrcPath) === "_npmrc") {
|
||||
} else if (basename === "_npmrc") {
|
||||
relativeDestPath = path.join(path.dirname(relativeSrcPath), ".npmrc");
|
||||
}
|
||||
|
||||
const destPath = path.join(destDir, relativeDestPath);
|
||||
|
||||
await fs.ensureDir(path.dirname(destPath));
|
||||
try {
|
||||
await fs.ensureDir(path.dirname(destPath));
|
||||
|
||||
if (!overwrite && (await fs.pathExists(destPath))) {
|
||||
continue;
|
||||
}
|
||||
if (!overwrite && (await fs.pathExists(destPath))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (srcPath.endsWith(".hbs")) {
|
||||
await processTemplate(srcPath, destPath, context);
|
||||
} else {
|
||||
await fs.copy(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
if (srcPath.endsWith(".hbs")) {
|
||||
await processTemplate(srcPath, destPath, context);
|
||||
} else {
|
||||
await fs.copy(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@ export async function copyBaseTemplate(
|
||||
): Promise<void> {
|
||||
const templateDir = path.join(PKG_ROOT, "templates/base");
|
||||
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
||||
await fs.ensureDir(path.join(projectDir, "packages"));
|
||||
}
|
||||
|
||||
export async function setupFrontendTemplates(
|
||||
@@ -69,6 +71,7 @@ export async function setupFrontendTemplates(
|
||||
const hasNuxtWeb = context.frontend.includes("nuxt");
|
||||
const hasSvelteWeb = context.frontend.includes("svelte");
|
||||
const hasNative = context.frontend.includes("native");
|
||||
const isConvex = context.backend === "convex";
|
||||
|
||||
if (hasReactWeb || hasNuxtWeb || hasSvelteWeb) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
@@ -81,6 +84,7 @@ export async function setupFrontendTemplates(
|
||||
);
|
||||
if (await fs.pathExists(webBaseDir)) {
|
||||
await processAndCopyFiles("**/*", webBaseDir, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
const reactFramework = context.frontend.find((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start", "next"].includes(
|
||||
@@ -99,33 +103,47 @@ export async function setupFrontendTemplates(
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
const apiWebBaseDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/web/react/base`,
|
||||
);
|
||||
if (await fs.pathExists(apiWebBaseDir)) {
|
||||
await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
||||
if (!isConvex && context.api !== "none") {
|
||||
const apiWebBaseDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/web/react/base`,
|
||||
);
|
||||
if (await fs.pathExists(apiWebBaseDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
apiWebBaseDir,
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasNuxtWeb) {
|
||||
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
||||
if (await fs.pathExists(nuxtBaseDir)) {
|
||||
await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
const apiWebNuxtDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/web/nuxt`,
|
||||
);
|
||||
if (await fs.pathExists(apiWebNuxtDir)) {
|
||||
await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
||||
if (!isConvex && context.api !== "none") {
|
||||
const apiWebNuxtDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/web/nuxt`,
|
||||
);
|
||||
if (await fs.pathExists(apiWebNuxtDir)) {
|
||||
await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else if (hasSvelteWeb) {
|
||||
const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
|
||||
if (await fs.pathExists(svelteBaseDir)) {
|
||||
await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
if (context.api === "orpc") {
|
||||
if (!isConvex && context.api === "orpc") {
|
||||
const apiWebSvelteDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/web/svelte`,
|
||||
@@ -137,6 +155,7 @@ export async function setupFrontendTemplates(
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,22 +168,10 @@ export async function setupFrontendTemplates(
|
||||
const nativeBaseDir = path.join(PKG_ROOT, "templates/frontend/native");
|
||||
if (await fs.pathExists(nativeBaseDir)) {
|
||||
await processAndCopyFiles("**/*", nativeBaseDir, nativeAppDir, context);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (context.api === "trpc") {
|
||||
const apiNativeSrcDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/native`,
|
||||
);
|
||||
if (await fs.pathExists(apiNativeSrcDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
apiNativeSrcDir,
|
||||
nativeAppDir,
|
||||
context,
|
||||
);
|
||||
}
|
||||
} else if (context.api === "orpc") {
|
||||
if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
|
||||
const apiNativeSrcDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/native`,
|
||||
@@ -176,6 +183,7 @@ export async function setupFrontendTemplates(
|
||||
nativeAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,53 +193,89 @@ export async function setupBackendFramework(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if ((context.backend as string) === "none") return;
|
||||
if (context.backend === "convex") {
|
||||
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
||||
const convexSrcDir = path.join(
|
||||
PKG_ROOT,
|
||||
"templates/backend/convex/packages/backend",
|
||||
);
|
||||
|
||||
await fs.ensureDir(convexBackendDestDir);
|
||||
|
||||
if (await fs.pathExists(convexSrcDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
convexSrcDir,
|
||||
convexBackendDestDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
if (await fs.pathExists(serverAppDir)) {
|
||||
await fs.remove(serverAppDir);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
await fs.ensureDir(serverAppDir);
|
||||
|
||||
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server-base");
|
||||
const serverBaseDir = path.join(
|
||||
PKG_ROOT,
|
||||
"templates/backend/server/server-base",
|
||||
);
|
||||
if (await fs.pathExists(serverBaseDir)) {
|
||||
await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
||||
} else {
|
||||
consola.warn(
|
||||
pc.yellow(`Warning: server-base template not found at ${serverBaseDir}`),
|
||||
);
|
||||
}
|
||||
|
||||
const frameworkSrcDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/backend/${context.backend}`,
|
||||
`templates/backend/server/${context.backend}`,
|
||||
);
|
||||
if (await fs.pathExists(frameworkSrcDir)) {
|
||||
await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context);
|
||||
} else {
|
||||
consola.warn(
|
||||
pc.yellow(
|
||||
`Warning: Backend template directory not found, skipping: ${frameworkSrcDir}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const apiServerBaseDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/server/base`,
|
||||
);
|
||||
if (await fs.pathExists(apiServerBaseDir)) {
|
||||
await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context);
|
||||
}
|
||||
|
||||
const apiServerFrameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/server/${context.backend}`,
|
||||
);
|
||||
if (await fs.pathExists(apiServerFrameworkDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
apiServerFrameworkDir,
|
||||
frameworkSrcDir,
|
||||
serverAppDir,
|
||||
context,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (context.api !== "none") {
|
||||
const apiServerBaseDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/server/base`,
|
||||
);
|
||||
if (await fs.pathExists(apiServerBaseDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
apiServerBaseDir,
|
||||
serverAppDir,
|
||||
context,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
const apiServerFrameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/api/${context.api}/server/${context.backend}`,
|
||||
);
|
||||
if (await fs.pathExists(apiServerFrameworkDir)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
apiServerFrameworkDir,
|
||||
serverAppDir,
|
||||
context,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +283,12 @@ export async function setupDbOrmTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if (context.orm === "none" || context.database === "none") return;
|
||||
if (
|
||||
context.backend === "convex" ||
|
||||
context.orm === "none" ||
|
||||
context.database === "none"
|
||||
)
|
||||
return;
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
await fs.ensureDir(serverAppDir);
|
||||
@@ -252,11 +301,6 @@ export async function setupDbOrmTemplates(
|
||||
if (await fs.pathExists(dbOrmSrcDir)) {
|
||||
await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
|
||||
} else {
|
||||
consola.warn(
|
||||
pc.yellow(
|
||||
`Warning: Database/ORM template directory not found, skipping: ${dbOrmSrcDir}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +308,7 @@ export async function setupAuthTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if (!context.auth) return;
|
||||
if (context.backend === "convex" || !context.auth) return;
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
@@ -290,6 +334,7 @@ export async function setupAuthTemplate(
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (context.backend === "next") {
|
||||
@@ -304,6 +349,7 @@ export async function setupAuthTemplate(
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,12 +370,7 @@ export async function setupAuthTemplate(
|
||||
}
|
||||
if (authDbSrc && (await fs.pathExists(authDbSrc))) {
|
||||
await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
|
||||
} else {
|
||||
consola.warn(
|
||||
pc.yellow(
|
||||
`Warning: Auth template for ${orm}/${db} not found at ${authDbSrc}`,
|
||||
),
|
||||
);
|
||||
} else if (authDbSrc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,6 +383,7 @@ export async function setupAuthTemplate(
|
||||
);
|
||||
if (await fs.pathExists(authWebBaseSrc)) {
|
||||
await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
const reactFramework = context.frontend.find((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start", "next"].includes(
|
||||
@@ -360,12 +402,14 @@ export async function setupAuthTemplate(
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else if (hasNuxtWeb) {
|
||||
const authWebNuxtSrc = path.join(PKG_ROOT, "templates/auth/web/nuxt");
|
||||
if (await fs.pathExists(authWebNuxtSrc)) {
|
||||
await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
|
||||
} else {
|
||||
}
|
||||
} else if (hasSvelteWeb) {
|
||||
if (context.api === "orpc") {
|
||||
@@ -380,6 +424,7 @@ export async function setupAuthTemplate(
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,11 +435,6 @@ export async function setupAuthTemplate(
|
||||
if (await fs.pathExists(authNativeSrc)) {
|
||||
await processAndCopyFiles("**/*", authNativeSrc, nativeAppDir, context);
|
||||
} else {
|
||||
consola.warn(
|
||||
pc.yellow(
|
||||
`Warning: Auth native template not found at ${authNativeSrc}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,8 +470,6 @@ export async function setupExamplesTemplate(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if (!context.examples || context.examples.length === 0) return;
|
||||
|
||||
const serverAppDir = path.join(projectDir, "apps/server");
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
|
||||
@@ -445,6 +483,13 @@ export async function setupExamplesTemplate(
|
||||
const hasSvelteWeb = context.frontend.includes("svelte");
|
||||
|
||||
for (const example of context.examples) {
|
||||
if (
|
||||
!context.examples ||
|
||||
context.examples.length === 0 ||
|
||||
context.examples[0] === "none"
|
||||
)
|
||||
continue;
|
||||
|
||||
if (example === "none") continue;
|
||||
|
||||
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
||||
@@ -452,23 +497,23 @@ export async function setupExamplesTemplate(
|
||||
if (serverAppDirExists) {
|
||||
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
||||
if (await fs.pathExists(exampleServerSrc)) {
|
||||
if (context.orm !== "none") {
|
||||
const exampleOrmBaseSrc = path.join(
|
||||
exampleServerSrc,
|
||||
context.orm,
|
||||
"base",
|
||||
);
|
||||
if (await fs.pathExists(exampleOrmBaseSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleOrmBaseSrc,
|
||||
serverAppDir,
|
||||
context,
|
||||
false,
|
||||
if (context.backend !== "convex") {
|
||||
if (context.orm !== "none" && context.database !== "none") {
|
||||
const exampleOrmBaseSrc = path.join(
|
||||
exampleServerSrc,
|
||||
context.orm,
|
||||
"base",
|
||||
);
|
||||
}
|
||||
if (await fs.pathExists(exampleOrmBaseSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleOrmBaseSrc,
|
||||
serverAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
if (context.database !== "none") {
|
||||
const exampleDbSchemaSrc = path.join(
|
||||
exampleServerSrc,
|
||||
context.orm,
|
||||
@@ -484,63 +529,81 @@ export async function setupExamplesTemplate(
|
||||
);
|
||||
}
|
||||
}
|
||||
const generalServerFiles = await globby(["*.ts", "*.hbs"], {
|
||||
cwd: exampleServerSrc,
|
||||
onlyFiles: true,
|
||||
deep: 1,
|
||||
ignore: [`${context.orm}/**`],
|
||||
});
|
||||
for (const file of generalServerFiles) {
|
||||
const srcPath = path.join(exampleServerSrc, file);
|
||||
const destPath = path.join(serverAppDir, file.replace(".hbs", ""));
|
||||
if (srcPath.endsWith(".hbs")) {
|
||||
await processTemplate(srcPath, destPath, context);
|
||||
} else {
|
||||
await fs.copy(srcPath, destPath, { overwrite: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasReactWeb && webAppDirExists) {
|
||||
const exampleWebSrc = path.join(exampleBaseDir, "web/react");
|
||||
if (await fs.pathExists(exampleWebSrc)) {
|
||||
const reactFramework = context.frontend.find((f) =>
|
||||
[
|
||||
"next",
|
||||
"react-router",
|
||||
"tanstack-router",
|
||||
"tanstack-start",
|
||||
].includes(f),
|
||||
);
|
||||
if (reactFramework) {
|
||||
const exampleWebFrameworkSrc = path.join(
|
||||
exampleWebSrc,
|
||||
reactFramework,
|
||||
if (webAppDirExists) {
|
||||
if (hasReactWeb) {
|
||||
const exampleWebSrc = path.join(exampleBaseDir, "web/react");
|
||||
if (await fs.pathExists(exampleWebSrc)) {
|
||||
const reactFramework = context.frontend.find((f) =>
|
||||
[
|
||||
"next",
|
||||
"react-router",
|
||||
"tanstack-router",
|
||||
"tanstack-start",
|
||||
].includes(f),
|
||||
);
|
||||
if (await fs.pathExists(exampleWebFrameworkSrc)) {
|
||||
if (reactFramework) {
|
||||
const exampleWebFrameworkSrc = path.join(
|
||||
exampleWebSrc,
|
||||
reactFramework,
|
||||
);
|
||||
if (await fs.pathExists(exampleWebFrameworkSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleWebFrameworkSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasNuxtWeb) {
|
||||
if (context.api === "orpc") {
|
||||
const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
|
||||
if (await fs.pathExists(exampleWebNuxtSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleWebFrameworkSrc,
|
||||
exampleWebNuxtSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasNuxtWeb && webAppDirExists) {
|
||||
if (context.api === "orpc") {
|
||||
const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
|
||||
if (await fs.pathExists(exampleWebNuxtSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleWebNuxtSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else if (hasSvelteWeb && webAppDirExists) {
|
||||
if (context.api === "orpc") {
|
||||
const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
|
||||
if (await fs.pathExists(exampleWebSvelteSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleWebSvelteSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
} else if (hasSvelteWeb) {
|
||||
if (context.api === "orpc") {
|
||||
const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
|
||||
if (await fs.pathExists(exampleWebSvelteSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
exampleWebSvelteSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user