diff --git a/.changeset/curvy-geckos-hug.md b/.changeset/curvy-geckos-hug.md new file mode 100644 index 0000000000..06e450066c --- /dev/null +++ b/.changeset/curvy-geckos-hug.md @@ -0,0 +1,5 @@ +--- +"create-t3-app": major +--- + +Adds a new option to the CLI to avoid including the source directory during project creation. diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index ce5ef6ca50..72d947fbd6 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -20,6 +20,7 @@ interface CliFlags { noInstall: boolean; default: boolean; importAlias: string; + srcDirectory: boolean; /** @internal Used in CI. */ CI: boolean; @@ -52,6 +53,7 @@ const defaultOptions: CliResults = { flags: { noGit: false, noInstall: false, + srcDirectory: false, default: false, CI: false, tailwind: false, @@ -86,6 +88,11 @@ export const runCli = async (): Promise => { "Explicitly tell the CLI to not run the package manager's install command", false ) + .option( + "--srcDirectory", + "Explicitly tell the CLI to create a 'src' directory in the project", + false + ) .option( "-y, --default", "Bypass the CLI and use all default options to bootstrap a new t3-app", @@ -253,6 +260,11 @@ export const runCli = async (): Promise => { message: "Will you be using Tailwind CSS for styling?", }); }, + srcDirectory: () => { + return p.confirm({ + message: "Would you like to use 'src' directory?", + }); + }, trpc: () => { return p.confirm({ message: "Would you like to use tRPC?", @@ -350,6 +362,7 @@ export const runCli = async (): Promise => { flags: { ...cliResults.flags, appRouter: project.appRouter ?? cliResults.flags.appRouter, + srcDirectory: project.srcDirectory ?? cliResults.flags.srcDirectory, noGit: !project.git || cliResults.flags.noGit, noInstall: !project.install || cliResults.flags.noInstall, importAlias: project.importAlias ?? cliResults.flags.importAlias, diff --git a/cli/src/helpers/createProject.ts b/cli/src/helpers/createProject.ts index 81b385485f..10aba7980a 100644 --- a/cli/src/helpers/createProject.ts +++ b/cli/src/helpers/createProject.ts @@ -19,6 +19,7 @@ import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; interface CreateProjectOptions { projectName: string; packages: PkgInstallerMap; + srcDirectory: boolean; scopedAppName: string; noInstall: boolean; importAlias: string; @@ -28,6 +29,7 @@ interface CreateProjectOptions { export const createProject = async ({ projectName, + srcDirectory, scopedAppName, packages, noInstall, @@ -42,6 +44,7 @@ export const createProject = async ({ projectName, projectDir, pkgManager, + srcDirectory, scopedAppName, noInstall, appRouter, @@ -52,6 +55,7 @@ export const createProject = async ({ installPackages({ projectName, scopedAppName, + srcDirectory, projectDir, pkgManager, packages, @@ -68,11 +72,11 @@ export const createProject = async ({ path.join(projectDir, "next.config.js") ); - selectLayoutFile({ projectDir, packages }); - selectPageFile({ projectDir, packages }); + selectLayoutFile({ projectDir, packages, srcDirectory }); + selectPageFile({ projectDir, packages, srcDirectory }); } else { - selectAppFile({ projectDir, packages }); - selectIndexFile({ projectDir, packages }); + selectAppFile({ projectDir, packages, srcDirectory }); + selectIndexFile({ projectDir, packages, srcDirectory }); } // If no tailwind, select use css modules @@ -83,12 +87,27 @@ export const createProject = async ({ ); const indexModuleCssDest = path.join( projectDir, - "src", + srcDirectory ? "src" : "", appRouter ? "app" : "pages", "index.module.css" ); fs.copyFileSync(indexModuleCss, indexModuleCssDest); } + if (!srcDirectory) { + const tsconfigFile = path.join(projectDir, "tsconfig.json"); + const nextconfigFile = path.join(projectDir, "next.config.js"); + fs.writeFileSync( + tsconfigFile, + fs.readFileSync(tsconfigFile, "utf8").replace("./src/*", "./*") + ); + fs.writeFileSync( + nextconfigFile, + fs + .readFileSync(nextconfigFile, "utf8") + .replace("./src/env.js", "./env.js") + ); + } + return projectDir; }; diff --git a/cli/src/helpers/scaffoldProject.ts b/cli/src/helpers/scaffoldProject.ts index d4e6730798..715b99a64a 100644 --- a/cli/src/helpers/scaffoldProject.ts +++ b/cli/src/helpers/scaffoldProject.ts @@ -12,6 +12,7 @@ import { logger } from "~/utils/logger.js"; export const scaffoldProject = async ({ projectName, projectDir, + srcDirectory, pkgManager, noInstall, }: InstallerOptions) => { @@ -90,6 +91,21 @@ export const scaffoldProject = async ({ path.join(projectDir, ".gitignore") ); + if (!srcDirectory) { + await Promise.all([ + fs.rename( + path.join(projectDir, "src", "env.js"), + projectDir + "/" + "env.js" + ), + fs.rename( + path.join(projectDir, "src", "styles"), + projectDir + "/" + "styles" + ), + ]); + + await fs.rm(path.join(projectDir, "src"), { recursive: true }); + } + const scaffoldedName = projectName === "." ? "App" : chalk.cyan.bold(projectName); diff --git a/cli/src/helpers/selectBoilerplate.ts b/cli/src/helpers/selectBoilerplate.ts index 2b07079be6..3e0c6dbf6c 100644 --- a/cli/src/helpers/selectBoilerplate.ts +++ b/cli/src/helpers/selectBoilerplate.ts @@ -5,11 +5,12 @@ import { PKG_ROOT } from "~/consts.js"; import { type InstallerOptions } from "~/installers/index.js"; type SelectBoilerplateProps = Required< - Pick + Pick >; // This generates the _app.tsx file that is used to render the app export const selectAppFile = ({ projectDir, + srcDirectory, packages, }: SelectBoilerplateProps) => { const appFileDir = path.join(PKG_ROOT, "template/extras/src/pages/_app"); @@ -36,13 +37,18 @@ export const selectAppFile = ({ } const appSrc = path.join(appFileDir, appFile); - const appDest = path.join(projectDir, "src/pages/_app.tsx"); + + const appDest = path.join( + projectDir, + srcDirectory ? "src/pages/_app.tsx" : "pages/_app" + ); fs.copySync(appSrc, appDest); }; // Similar to _app, but for app router export const selectLayoutFile = ({ projectDir, + srcDirectory, packages, }: SelectBoilerplateProps) => { const layoutFileDir = path.join(PKG_ROOT, "template/extras/src/app/layout"); @@ -59,13 +65,19 @@ export const selectLayoutFile = ({ } const appSrc = path.join(layoutFileDir, layoutFile); - const appDest = path.join(projectDir, "src/app/layout.tsx"); + + const appDest = path.join( + projectDir, + srcDirectory ? "src/app/layout.tsx" : "app/layout.tsx" + ); + fs.copySync(appSrc, appDest); }; // This selects the proper index.tsx to be used that showcases the chosen tech export const selectIndexFile = ({ projectDir, + srcDirectory, packages, }: SelectBoilerplateProps) => { const indexFileDir = path.join(PKG_ROOT, "template/extras/src/pages/index"); @@ -88,13 +100,17 @@ export const selectIndexFile = ({ } const indexSrc = path.join(indexFileDir, indexFile); - const indexDest = path.join(projectDir, "src/pages/index.tsx"); + const indexDest = path.join( + projectDir, + srcDirectory ? "src/pages/index.tsx" : "pages/index" + ); fs.copySync(indexSrc, indexDest); }; // Similar to index, but for app router export const selectPageFile = ({ projectDir, + srcDirectory, packages, }: SelectBoilerplateProps) => { const indexFileDir = path.join(PKG_ROOT, "template/extras/src/app/page"); @@ -117,6 +133,10 @@ export const selectPageFile = ({ } const indexSrc = path.join(indexFileDir, indexFile); - const indexDest = path.join(projectDir, "src/app/page.tsx"); + + const indexDest = path.join( + projectDir, + srcDirectory ? "src/app/page.tsx" : "app/page.tsx" + ); fs.copySync(indexSrc, indexDest); }; diff --git a/cli/src/index.ts b/cli/src/index.ts index 228192ef3a..254df5f978 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -36,7 +36,7 @@ const main = async () => { const { appName, packages, - flags: { noGit, noInstall, importAlias, appRouter }, + flags: { noGit, noInstall, importAlias, appRouter, srcDirectory }, databaseProvider, } = await runCli(); @@ -48,6 +48,7 @@ const main = async () => { const projectDir = await createProject({ projectName: appDir, scopedAppName, + srcDirectory, packages: usePackages, databaseProvider, importAlias, diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index d9c5930b98..0497bb5b10 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -10,6 +10,7 @@ import { type AvailableDependencies } from "./dependencyVersionMap.js"; export const drizzleInstaller: Installer = ({ projectDir, packages, + srcDirectory, scopedAppName, databaseProvider, }) => { @@ -56,7 +57,10 @@ export const drizzleInstaller: Installer = ({ ? `with-auth-${databaseProvider}.ts` : `base-${databaseProvider}.ts` ); - const schemaDest = path.join(projectDir, "src/server/db/schema.ts"); + const schemaDest = path.join( + projectDir, + srcDirectory ? "src/server/db/schema.ts" : "server/db/schema.ts" + ); // Replace placeholder table prefix with project name let schemaContent = fs.readFileSync(schemaSrc, "utf-8"); @@ -73,7 +77,10 @@ export const drizzleInstaller: Installer = ({ extrasDir, `src/server/db/index-drizzle/with-${databaseProvider}.ts` ); - const clientDest = path.join(projectDir, "src/server/db/index.ts"); + const clientDest = path.join( + projectDir, + srcDirectory ? "src/server/db/index.ts" : "server/db/index.ts" + ); // add db:* scripts to package.json const packageJsonPath = path.join(projectDir, "package.json"); @@ -95,4 +102,14 @@ export const drizzleInstaller: Installer = ({ fs.writeJSONSync(packageJsonPath, packageJsonContent, { spaces: 2, }); + + if (!srcDirectory) { + const drizzleConfigFile = path.join(projectDir, "drizzle.config.ts"); + fs.writeFileSync( + drizzleConfigFile, + fs + .readFileSync(drizzleConfigFile, "utf8") + .replace("./src/server/db/schema.ts", "./server/db/schema.ts") + ); + } }; diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index 6924f6403f..14c120054c 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -7,6 +7,7 @@ import { type DatabaseProvider, type Installer } from "~/installers/index.js"; export const envVariablesInstaller: Installer = ({ projectDir, packages, + srcDirectory, databaseProvider, projectName, }) => { @@ -44,7 +45,10 @@ export const envVariablesInstaller: Installer = ({ "template/extras/src/env", envFile ); - const envSchemaDest = path.join(projectDir, "src/env.js"); + const envSchemaDest = path.join( + projectDir, + srcDirectory ? "src/env.js" : "env.js" + ); fs.copyFileSync(envSchemaSrc, envSchemaDest); } diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 776a16ba1c..348c10c680 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -34,6 +34,7 @@ export interface InstallerOptions { projectDir: string; pkgManager: PackageManager; noInstall: boolean; + srcDirectory: boolean; packages?: PkgInstallerMap; appRouter?: boolean; projectName: string; diff --git a/cli/src/installers/nextAuth.ts b/cli/src/installers/nextAuth.ts index 0886e90d7e..82fc640683 100644 --- a/cli/src/installers/nextAuth.ts +++ b/cli/src/installers/nextAuth.ts @@ -9,6 +9,7 @@ import { addPackageDependency } from "~/utils/addPackageDependency.js"; export const nextAuthInstaller: Installer = ({ projectDir, packages, + srcDirectory, appRouter, }) => { const usingPrisma = packages?.prisma.inUse; @@ -26,12 +27,16 @@ export const nextAuthInstaller: Installer = ({ const extrasDir = path.join(PKG_ROOT, "template/extras"); - const apiHandlerFile = "src/pages/api/auth/[...nextauth].ts"; - const routeHandlerFile = "src/app/api/auth/[...nextauth]/route.ts"; + const apiHandlerFile = "pages/api/auth/[...nextauth].ts"; + const routeHandlerFile = "app/api/auth/[...nextauth]/route.ts"; const srcToUse = appRouter ? routeHandlerFile : apiHandlerFile; - const apiHandlerSrc = path.join(extrasDir, srcToUse); - const apiHandlerDest = path.join(projectDir, srcToUse); + const apiHandlerSrc = path.join(extrasDir, "src", srcToUse); + const apiHandlerDest = path.join( + projectDir, + srcDirectory ? "src" : "", + srcToUse + ); const authConfigSrc = path.join( extrasDir, @@ -43,7 +48,10 @@ export const nextAuthInstaller: Installer = ({ ? "with-drizzle.ts" : "base.ts" ); - const authConfigDest = path.join(projectDir, "src/server/auth.ts"); + const authConfigDest = path.join( + projectDir, + srcDirectory ? "src/server/auth.ts" : "server/auth.ts" + ); fs.copySync(apiHandlerSrc, apiHandlerDest); fs.copySync(authConfigSrc, authConfigDest); diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index f92da12395..158230d155 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -8,6 +8,7 @@ import { addPackageDependency } from "~/utils/addPackageDependency.js"; export const prismaInstaller: Installer = ({ projectDir, + srcDirectory, packages, databaseProvider, }) => { @@ -63,7 +64,10 @@ export const prismaInstaller: Installer = ({ ? "src/server/db/db-prisma-planetscale.ts" : "src/server/db/db-prisma.ts" ); - const clientDest = path.join(projectDir, "src/server/db.ts"); + const clientDest = path.join( + projectDir, + srcDirectory ? "src/server/db.ts" : "server/db.ts" + ); // add postinstall and push script to package.json const packageJsonPath = path.join(projectDir, "package.json"); diff --git a/cli/src/installers/tailwind.ts b/cli/src/installers/tailwind.ts index e692392823..27c9b2dc95 100644 --- a/cli/src/installers/tailwind.ts +++ b/cli/src/installers/tailwind.ts @@ -5,7 +5,7 @@ import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; -export const tailwindInstaller: Installer = ({ projectDir }) => { +export const tailwindInstaller: Installer = ({ projectDir, srcDirectory }) => { addPackageDependency({ projectDir, dependencies: [ @@ -17,6 +17,10 @@ export const tailwindInstaller: Installer = ({ projectDir }) => { devMode: true, }); + const cssDestPath = srcDirectory + ? "src/styles/globals.css" + : "styles/globals.css"; + const extrasDir = path.join(PKG_ROOT, "template/extras"); const twCfgSrc = path.join(extrasDir, "config/tailwind.config.ts"); @@ -29,10 +33,20 @@ export const tailwindInstaller: Installer = ({ projectDir }) => { const prettierDest = path.join(projectDir, "prettier.config.js"); const cssSrc = path.join(extrasDir, "src/styles/globals.css"); - const cssDest = path.join(projectDir, "src/styles/globals.css"); + const cssDest = path.join(projectDir, cssDestPath); fs.copySync(twCfgSrc, twCfgDest); fs.copySync(postcssCfgSrc, postcssCfgDest); fs.copySync(cssSrc, cssDest); fs.copySync(prettierSrc, prettierDest); + + if (!srcDirectory) { + const tailwindConfigFile = path.join(projectDir, "tailwind.config.ts"); + fs.writeFileSync( + tailwindConfigFile, + fs + .readFileSync(tailwindConfigFile, "utf8") + .replace("./src/**/*.tsx", "./**/*.tsx") + ); + } }; diff --git a/cli/src/installers/trpc.ts b/cli/src/installers/trpc.ts index 96fd354331..9d59dd498c 100644 --- a/cli/src/installers/trpc.ts +++ b/cli/src/installers/trpc.ts @@ -8,6 +8,7 @@ import { addPackageDependency } from "~/utils/addPackageDependency.js"; export const trpcInstaller: Installer = ({ projectDir, packages, + srcDirectory, appRouter, }) => { addPackageDependency({ @@ -29,12 +30,17 @@ export const trpcInstaller: Installer = ({ const extrasDir = path.join(PKG_ROOT, "template/extras"); - const apiHandlerFile = "src/pages/api/trpc/[trpc].ts"; - const routeHandlerFile = "src/app/api/trpc/[trpc]/route.ts"; + const apiHandlerFile = "pages/api/trpc/[trpc].ts"; + const routeHandlerFile = "app/api/trpc/[trpc]/route.ts"; + const srcToUse = appRouter ? routeHandlerFile : apiHandlerFile; - const apiHandlerSrc = path.join(extrasDir, srcToUse); - const apiHandlerDest = path.join(projectDir, srcToUse); + const apiHandlerSrc = path.join(extrasDir, "src", srcToUse); + const apiHandlerDest = path.join( + projectDir, + srcDirectory ? "src" : "", + srcToUse + ); const trpcFile = usingAuth && usingDb @@ -50,10 +56,16 @@ export const trpcInstaller: Installer = ({ appRouter ? "trpc-app" : "trpc-pages", trpcFile ); - const trpcDest = path.join(projectDir, "src/server/api/trpc.ts"); + const trpcDest = path.join( + projectDir, + srcDirectory ? "src/server/api/trpc.ts" : "server/api/trpc.ts" + ); const rootRouterSrc = path.join(extrasDir, "src/server/api/root.ts"); - const rootRouterDest = path.join(projectDir, "src/server/api/root.ts"); + const rootRouterDest = path.join( + projectDir, + srcDirectory ? "src/server/api/root.ts" : "server/api/root.ts" + ); const exampleRouterFile = usingAuth && usingPrisma @@ -75,7 +87,9 @@ export const trpcInstaller: Installer = ({ ); const exampleRouterDest = path.join( projectDir, - "src/server/api/routers/post.ts" + srcDirectory + ? "src/server/api/routers/post.ts" + : "server/api/routers/post.ts" ); const copySrcDest: [string, string][] = [ @@ -96,11 +110,17 @@ export const trpcInstaller: Installer = ({ copySrcDest.push( [ path.join(trpcDir, "server.ts"), - path.join(projectDir, "src/trpc/server.ts"), + path.join( + projectDir, + srcDirectory ? "src/trpc/server.ts" : "trpc/server.ts" + ), ], [ path.join(trpcDir, "react.tsx"), - path.join(projectDir, "src/trpc/react.tsx"), + path.join( + projectDir, + srcDirectory ? "src/trpc/react.tsx" : "trpc/react.tsx" + ), ], [ path.join( @@ -108,11 +128,19 @@ export const trpcInstaller: Installer = ({ "src/app/_components", packages?.tailwind.inUse ? "post-tw.tsx" : "post.tsx" ), - path.join(projectDir, "src/app/_components/post.tsx"), + path.join( + projectDir, + srcDirectory + ? "src/app/_components/post.tsx" + : "app/_components/post.tsx" + ), ], [ path.join(extrasDir, "src/trpc/query-client.ts"), - path.join(projectDir, "src/trpc/query-client.ts"), + path.join( + projectDir, + srcDirectory ? "src/trpc/query-client.ts" : "trpc/query-client.ts" + ), ] ); } else { @@ -123,7 +151,10 @@ export const trpcInstaller: Installer = ({ }); const utilsSrc = path.join(extrasDir, "src/utils/api.ts"); - const utilsDest = path.join(projectDir, "src/utils/api.ts"); + const utilsDest = path.join( + projectDir, + srcDirectory ? "src/utils/api.ts" : "utils/api.ts" + ); copySrcDest.push([utilsSrc, utilsDest]); }