diff --git a/package.json b/package.json index 87a0d345..5c20c4d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kirimase", - "version": "0.0.52", + "version": "0.0.53", "description": "A Rails-like CLI for building full-stack Next.js apps faster", "main": "index.js", "type": "module", diff --git a/src/commands/add/auth/clerk/generators.ts b/src/commands/add/auth/clerk/generators.ts index 7094b217..4fb12e24 100644 --- a/src/commands/add/auth/clerk/generators.ts +++ b/src/commands/add/auth/clerk/generators.ts @@ -7,7 +7,7 @@ const generateMiddlewareTs = () => { // This example protects all routes including api/trpc routes // Please edit this to allow other routes to be public as needed. // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware -export default authMiddleware({}); +export default authMiddleware({ ignoredRoutes: ["/"] }); export const config = { matcher: ['/((?!.+\\\\\.[\\\\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], @@ -19,7 +19,7 @@ const generateSignInPageTs = () => { export default function Page() { return (
- +
); }`; @@ -30,7 +30,7 @@ const generateSignUpPageTs = () => { export default function Page() { return (
- +
); }`; diff --git a/src/commands/add/auth/clerk/index.ts b/src/commands/add/auth/clerk/index.ts index 72a88044..cc5909c3 100644 --- a/src/commands/add/auth/clerk/index.ts +++ b/src/commands/add/auth/clerk/index.ts @@ -16,7 +16,11 @@ import { updateConfigFile, } from "../../../../utils.js"; import { addToDotEnv } from "../../orm/drizzle/generators.js"; -import { addContextProviderToLayout, addToInstallList } from "../../utils.js"; +import { + addContextProviderToAppLayout, + addContextProviderToAuthLayout, + addToInstallList, +} from "../../utils.js"; import { clerkGenerators } from "./generators.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; import { libAuthUtilsTs } from "../next-auth/generators.js"; @@ -28,6 +32,7 @@ export const addClerk = async () => { clerk: { middleware, signInPage, signUpPage }, shared: { auth: { authUtils }, + init, }, } = getFilePaths(); const { @@ -37,7 +42,8 @@ export const addClerk = async () => { generateSignUpPageTs, homePageWithUserButton, } = clerkGenerators; - addContextProviderToLayout("ClerkProvider"); + addContextProviderToAuthLayout("ClerkProvider"); + addContextProviderToAppLayout("ClerkProvider"); addToDotEnv( [ { key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", value: "", public: true }, @@ -64,7 +70,10 @@ export const addClerk = async () => { ); replaceFile( - rootPath.concat("app/page.tsx"), + formatFilePath(init.dashboardRoute, { + removeExtension: false, + prefix: "rootPath", + }), homePageWithUserButton(componentLib) ); diff --git a/src/commands/add/auth/clerk/utils.ts b/src/commands/add/auth/clerk/utils.ts index 88e741b4..b28541b4 100644 --- a/src/commands/add/auth/clerk/utils.ts +++ b/src/commands/add/auth/clerk/utils.ts @@ -2,10 +2,28 @@ import fs from "fs"; import { createFile, replaceFile } from "../../../../utils.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; -export const updateClerkMiddlewareForStripe = (rootPath: string) => { +// export const updateClerkMiddlewareForStripe = (rootPath: string) => { +// const { clerk } = getFilePaths(); +// const initMWContent = `export default authMiddleware({});`; +// const updatedMWContent = `export default authMiddleware({ ignoredRoutes: ["/api/webhooks/stripe"] });`; +// const mwPath = formatFilePath(clerk.middleware, { +// prefix: "rootPath", +// removeExtension: false, +// }); +// const mwExists = fs.existsSync(mwPath); +// if (mwExists) { +// const mwContent = fs.readFileSync(mwPath, "utf-8"); +// const newUtilsContent = mwContent.replace(initMWContent, updatedMWContent); +// replaceFile(mwPath, newUtilsContent); +// } else { +// console.error("Middleware does not exist"); +// } +// }; + +export const addToClerkIgnoredRoutes = (newPath: string) => { const { clerk } = getFilePaths(); - const initMWContent = `export default authMiddleware({});`; - const updatedMWContent = `export default authMiddleware({ ignoredRoutes: "/api/webhooks/stripe" });`; + const initMWContent = "ignoredRoutes: ["; + const updatedMWContent = "ignoredRoutes: [" + ` "${newPath}", `; const mwPath = formatFilePath(clerk.middleware, { prefix: "rootPath", removeExtension: false, diff --git a/src/commands/add/auth/kinde/index.ts b/src/commands/add/auth/kinde/index.ts index 9faf7d9e..ca525b4a 100644 --- a/src/commands/add/auth/kinde/index.ts +++ b/src/commands/add/auth/kinde/index.ts @@ -46,7 +46,7 @@ export const addKinde = async () => { ); // update root page createFile( - formatFilePath("app/page.tsx", { + formatFilePath(shared.init.dashboardRoute, { prefix: "rootPath", removeExtension: false, }), @@ -69,7 +69,10 @@ export const addKinde = async () => { { key: "KINDE_ISSUER_URL", value: "https://kirimase.kinde.com" }, { key: "KINDE_SITE_URL", value: "http://localhost:3000" }, { key: "KINDE_POST_LOGOUT_REDIRECT_URL", value: "http://localhost:3000" }, - { key: "KINDE_POST_LOGIN_REDIRECT_URL", value: "http://localhost:3000" }, + { + key: "KINDE_POST_LOGIN_REDIRECT_URL", + value: "http://localhost:3000/dashboard", + }, ]); // install @kinde-oss/kinde-auth-nextjs // await installPackages( diff --git a/src/commands/add/auth/lucia/generators.ts b/src/commands/add/auth/lucia/generators.ts index 75fe713d..7516aab6 100644 --- a/src/commands/add/auth/lucia/generators.ts +++ b/src/commands/add/auth/lucia/generators.ts @@ -60,17 +60,10 @@ const generateSignUpPage = (withShadCn: boolean) => { prefix: "alias", })}"; import Link from "next/link"; -import { getPageSession } from "${formatFilePath(lucia.libAuthLucia, { - prefix: "alias", - removeExtension: true, - })}"; -import { redirect } from "next/navigation"; import { Input } from "${alias}/components/ui/input"; import { Label } from "${alias}/components/ui/label"; const Page = async () => { - const session = await getPageSession(); - if (session) redirect("/"); return (

Create an account

@@ -104,15 +97,8 @@ export default Page; prefix: "alias", })}"; import Link from "next/link"; -import { redirect } from "next/navigation"; -import { getUserAuth } from "${formatFilePath(shared.auth.authUtils, { - prefix: "alias", - removeExtension: true, - })}"; const Page = async () => { - const { session } = await getUserAuth(); - if (session) redirect("/"); return (

Create an account

@@ -168,16 +154,9 @@ const generateSignInPage = (withShadCn: boolean) => { })}"; import { Input } from "${alias}/components/ui/input"; import { Label } from "${alias}/components/ui/label"; -import { getPageSession } from "${formatFilePath(lucia.libAuthLucia, { - prefix: "alias", - removeExtension: true, - })}"; import Link from "next/link"; -import { redirect } from "next/navigation"; const Page = async () => { - const session = await getPageSession(); - if (session?.user) redirect("/"); return (

@@ -215,16 +194,9 @@ export default Page; removeExtension: true, prefix: "alias", })}"; -import { getUserAuth } from "${formatFilePath(shared.auth.authUtils, { - prefix: "alias", - removeExtension: true, - })}"; import Link from "next/link"; -import { redirect } from "next/navigation"; const Page = async () => { - const { session } = await getUserAuth(); - if (session?.user) redirect("/"); return (

@@ -466,11 +438,9 @@ import { getUserAuth } from "${formatFilePath(shared.auth.authUtils, { prefix: "alias", removeExtension: true, })}"; -import { redirect } from "next/navigation"; export default async function Home() { const { session } = await getUserAuth(); - if (!session) redirect("/sign-up"); return (

Profile

@@ -555,7 +525,7 @@ export const POST = async (request: NextRequest) => { return new Response(null, { status: 302, headers: { - Location: "/", // redirect to profile page + Location: "/dashboard", // redirect to profile page }, }); } catch (e) { @@ -640,7 +610,7 @@ export const POST = async (request: NextRequest) => { return new Response(null, { status: 302, headers: { - Location: "/", // redirect to profile page + Location: "/dashboard", // redirect to profile page }, }); } catch (e) { diff --git a/src/commands/add/auth/lucia/index.ts b/src/commands/add/auth/lucia/index.ts index 5220f79a..534cfb37 100644 --- a/src/commands/add/auth/lucia/index.ts +++ b/src/commands/add/auth/lucia/index.ts @@ -94,7 +94,13 @@ export const addLucia = async () => { }), viewsAndComponents.authFormComponent ); - replaceFile(rootPath.concat("app/page.tsx"), viewsAndComponents.homePage); + replaceFile( + formatFilePath(shared.init.dashboardRoute, { + removeExtension: false, + prefix: "rootPath", + }), + viewsAndComponents.homePage + ); createFile( rootPath.concat("app/loading.tsx"), viewsAndComponents.loadingPage diff --git a/src/commands/add/auth/next-auth/index.ts b/src/commands/add/auth/next-auth/index.ts index bf7a0125..747f2563 100644 --- a/src/commands/add/auth/next-auth/index.ts +++ b/src/commands/add/auth/next-auth/index.ts @@ -17,7 +17,11 @@ import { libAuthUtilsTs, } from "./generators.js"; import { AuthDriver, AuthProvider, AuthProviders } from "./utils.js"; -import { addContextProviderToLayout, addToInstallList } from "../../utils.js"; +import { + addContextProviderToAppLayout, + // addContextProviderToAuthLayout, + addToInstallList, +} from "../../utils.js"; import { addToDotEnv } from "../../orm/drizzle/generators.js"; import { addToPrismaSchema } from "../../../generate/utils.js"; import { prismaGenerate } from "../../orm/utils.js"; @@ -108,7 +112,13 @@ export const addNextAuth = async ( // 6. If trpc installed, add protectedProcedure // this wont run because it is installed before trpc updateTrpcWithSessionIfInstalled(); - replaceFile(rootPath.concat("app/page.tsx"), generateUpdatedRootRoute()); + replaceFile( + formatFilePath(shared.init.dashboardRoute, { + removeExtension: false, + prefix: "rootPath", + }), + generateUpdatedRootRoute() + ); // add to env addToDotEnv( @@ -165,7 +175,8 @@ export const addNextAuth = async ( addPackageToConfig("next-auth"); updateConfigFile({ auth: "next-auth" }); // 9. Instruct user to add the to their root layout. - addContextProviderToLayout("NextAuthProvider"); + // addContextProviderToAuthLayout("NextAuthProvider"); + addContextProviderToAppLayout("NextAuthProvider"); if (orm === "prisma") await prismaGenerate(preferredPackageManager); // consola.success("Successfully added Next Auth to your project!"); }; diff --git a/src/commands/add/auth/shared/index.ts b/src/commands/add/auth/shared/index.ts index 4f103628..9988461e 100644 --- a/src/commands/add/auth/shared/index.ts +++ b/src/commands/add/auth/shared/index.ts @@ -4,10 +4,7 @@ import { installShadcnUIComponents, readConfigFile, } from "../../../../utils.js"; -import { - addContextProviderToLayout, - addToShadcnComponentList, -} from "../../utils.js"; +import { addToShadcnComponentList } from "../../utils.js"; import { createAccountApiTs, createAccountCardComponent, diff --git a/src/commands/add/componentLib/shadcn-ui/index.ts b/src/commands/add/componentLib/shadcn-ui/index.ts index ad1b8d25..ef2381e2 100644 --- a/src/commands/add/componentLib/shadcn-ui/index.ts +++ b/src/commands/add/componentLib/shadcn-ui/index.ts @@ -13,7 +13,8 @@ import { } from "../../../../utils.js"; import { AvailablePackage, PMType } from "../../../../types.js"; import { - addContextProviderToLayout, + addContextProviderToAppLayout, + addContextProviderToRootLayout, addToInstallList, addToShadcnComponentList, } from "../../utils.js"; @@ -85,7 +86,7 @@ const manualInstallShadCn = async ( generateThemeToggler() ); // add context provider to layout - addContextProviderToLayout("ThemeProvider"); + addContextProviderToRootLayout("ThemeProvider"); }; export const installShadcnUI = async ( @@ -138,7 +139,7 @@ export const installShadcnUI = async ( "dropdown-menu", ]); - addContextProviderToLayout("ShadcnToast"); + addContextProviderToAppLayout("ShadcnToast"); // if (packages.includes("next-auth")) updateSignInComponentWithShadcnUI(); }; diff --git a/src/commands/add/index.ts b/src/commands/add/index.ts index ed7cf522..2d8fcca8 100644 --- a/src/commands/add/index.ts +++ b/src/commands/add/index.ts @@ -1,5 +1,6 @@ import { confirm } from "@inquirer/prompts"; import { + createFile, installPackages, readConfigFile, replaceFile, @@ -23,6 +24,9 @@ import { formatFilePath, getFilePaths } from "../filePaths/index.js"; import { addKinde } from "./auth/kinde/index.js"; import { addNavbarAndSettings } from "./misc/navbar/generators.js"; import { + createAppLayoutFile, + createAuthLayoutFile, + createLandingPage, generateGenericHomepage, generateGlobalsCss, generateUpdatedTWConfig, @@ -39,7 +43,8 @@ import { askPscale, } from "./prompts.js"; import { - addContextProviderToLayout, + addAuthCheckToAppLayout, + addContextProviderToAppLayout, addToInstallList, installPackagesFromList, installShadcnComponentList, @@ -131,6 +136,9 @@ export const addPackage = async (options?: InitOptions) => { spinner.start(); spinner.text = "Beginning Configuration Process"; + createAppLayoutFile(); + createLandingPage(); + if (config.componentLib === undefined) { if (promptResponse.componentLib === "shadcn-ui") { spinner.text = "Configuring Shadcn-UI"; @@ -159,7 +167,7 @@ export const addPackage = async (options?: InitOptions) => { updateConfigFile({ componentLib: null }); } if (!config.t3) { - addContextProviderToLayout("Navbar"); + addContextProviderToAppLayout("Navbar"); } } @@ -194,7 +202,7 @@ export const addPackage = async (options?: InitOptions) => { "Configuring " + promptResponse.auth[0].toUpperCase() + promptResponse.orm.slice(1); - + if (promptResponse.auth) createAuthLayoutFile(); if (promptResponse.auth === "next-auth") await addNextAuth(promptResponse.authProviders, options); if (promptResponse.auth === "clerk") await addClerk(); @@ -202,7 +210,7 @@ export const addPackage = async (options?: InitOptions) => { if (promptResponse.auth === "kinde") await addKinde(); if (!promptResponse.auth) { replaceFile( - formatFilePath("app/page.tsx", { + formatFilePath(shared.init.dashboardRoute, { prefix: "rootPath", removeExtension: false, }), @@ -214,6 +222,7 @@ export const addPackage = async (options?: InitOptions) => { await createAccountSettingsPage(); } addNavbarAndSettings(); + addAuthCheckToAppLayout(); } // check if misc diff --git a/src/commands/add/misc/defaultStyles/generators.ts b/src/commands/add/misc/defaultStyles/generators.ts index 055df970..665a2f1b 100644 --- a/src/commands/add/misc/defaultStyles/generators.ts +++ b/src/commands/add/misc/defaultStyles/generators.ts @@ -1,5 +1,7 @@ import { warn } from "console"; import { existsSync, readFileSync } from "fs"; +import { createFile, replaceFile } from "../../../../utils.js"; +import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; export const generateGlobalsCss = () => { return `@tailwind base; @@ -138,3 +140,245 @@ export const generateGenericHomepage = () => { ); }`; }; + +const defaultAppLayout = `export default async function AppLayout({ + children, +}: { + children: React.ReactNode; +}) { + return (
{children}
) +}`; + +export const createAppLayoutFile = () => { + const { shared } = getFilePaths(); + + createFile( + formatFilePath(shared.init.appLayout, { + prefix: "rootPath", + removeExtension: false, + }), + defaultAppLayout + ); +}; + +const defaultAuthLayout = `import { getUserAuth } from "@/lib/auth/utils"; +import { redirect } from "next/navigation"; + +export default async function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + const session = await getUserAuth(); + if (session?.session) redirect("/dashboard"); + + return (
{children}
); +} +`; + +export const createAuthLayoutFile = () => { + const { shared } = getFilePaths(); + + createFile( + formatFilePath(shared.auth.layoutPage, { + prefix: "rootPath", + removeExtension: false, + }), + defaultAuthLayout + ); +}; + +const landingPage = `/** + * v0 by Vercel. + * @see https://v0.dev/t/PmwTvNfrVgf + * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app + */ +import Link from "next/link"; + +export default function Component() { + return ( +
+
+ + + Acme Inc + + +
+
+
+
+
+
+
+
+

+ The complete platform
+ for building the Web +

+

+ Give your team the toolkit to stop configuring and start + innovating. Securely build, deploy, and scale the best web + experiences. +

+
+
+ + Get Started + + + Contact Sales + +
+
+
+
+
+
+
+
+
+
+ Key Features +
+

+ Faster iteration. More innovation. +

+

+ The platform for rapid progress. Let your team focus on + shipping features instead of managing infrastructure with + automated CI/CD. +

+
+
+
+
+
+
    +
  • +
    +

    Collaboration

    +

    + Make collaboration seamless with built-in code review + tools. +

    +
    +
  • +
  • +
    +

    Automation

    +

    + Automate your workflow with continuous integration. +

    +
    +
  • +
  • +
    +

    Scale

    +

    + Deploy to the cloud with a single click and scale with + ease. +

    +
    +
  • +
+
+
+
+
+ +
+
+
+
+

+ Sign Up for Updates +

+

+ Stay updated with the latest product news and updates. +

+
+
+
+ + +
+
+
+
+
+
+
+

+ © 2024 Acme Inc. All rights reserved. +

+ +
+
+ ); +} + +function MountainIcon(props: any) { + return ( + + + + ); +} +`; + +export const createLandingPage = () => { + replaceFile( + formatFilePath("app/page.tsx", { + prefix: "rootPath", + removeExtension: false, + }), + landingPage + ); +}; diff --git a/src/commands/add/misc/navbar/generators.ts b/src/commands/add/misc/navbar/generators.ts index 062b006e..e06ad365 100644 --- a/src/commands/add/misc/navbar/generators.ts +++ b/src/commands/add/misc/navbar/generators.ts @@ -49,7 +49,7 @@ export const addNavbarAndSettings = () => { if (componentLib === "shadcn-ui") createFile( - formatFilePath("app/settings/page.tsx", { + formatFilePath("app/(app)/settings/page.tsx", { removeExtension: false, prefix: "rootPath", }), @@ -187,7 +187,7 @@ type AdditionalLinks = { }; export const defaultLinks: SidebarLink[] = [ - { href: "/", title: "Home", icon: HomeIcon },${ + { href: "/dashboard", title: "Home", icon: HomeIcon },${ auth !== null ? `\n { href: "/account", title: "Account", icon: Cog },` : "" diff --git a/src/commands/add/misc/stripe/index.ts b/src/commands/add/misc/stripe/index.ts index 4c99c643..5f1466a1 100644 --- a/src/commands/add/misc/stripe/index.ts +++ b/src/commands/add/misc/stripe/index.ts @@ -44,7 +44,7 @@ import { generateSuccessToast, } from "./generators.js"; import { addPackage } from "../../index.js"; -import { updateClerkMiddlewareForStripe } from "../../auth/clerk/utils.js"; +import { addToClerkIgnoredRoutes } from "../../auth/clerk/utils.js"; import { AvailablePackage } from "../../../../types.js"; import { updateTRPCRouter } from "../../../generate/generators/trpcRoute.js"; import { createAccountPage } from "../../auth/shared/generators.js"; @@ -88,7 +88,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { } if (auth === "clerk") { - updateClerkMiddlewareForStripe(rootPath); + addToClerkIgnoredRoutes("/api/webhooks/stripe"); } // add attributes to usermodel diff --git a/src/commands/add/misc/trpc/index.ts b/src/commands/add/misc/trpc/index.ts index af6eaebb..7e025a57 100644 --- a/src/commands/add/misc/trpc/index.ts +++ b/src/commands/add/misc/trpc/index.ts @@ -14,7 +14,10 @@ import { serverRouterComputersTs, serverTrpcTs, } from "./generators.js"; -import { addContextProviderToLayout, addToInstallList } from "../../utils.js"; +import { + addContextProviderToAppLayout, + addToInstallList, +} from "../../utils.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; export const addTrpc = async () => { @@ -126,7 +129,7 @@ export const addTrpc = async () => { addPackageToConfig("trpc"); // 9. Instruct user to add the to their root layout. - addContextProviderToLayout("TrpcProvider"); + addContextProviderToAppLayout("TrpcProvider"); // addToDotEnv( // [ // { diff --git a/src/commands/add/utils.ts b/src/commands/add/utils.ts index 34afaa20..b60d360a 100644 --- a/src/commands/add/utils.ts +++ b/src/commands/add/utils.ts @@ -44,7 +44,74 @@ export const Packages: { componentLib: [{ name: "Shadcn UI (with next-themes)", value: "shadcn-ui" }], }; -export const addContextProviderToLayout = ( +export const addContextProviderToRootLayout = (provider: "ThemeProvider") => { + const { hasSrc, alias } = readConfigFile(); + const path = `${hasSrc ? "src/" : ""}app/layout.tsx`; + + const fileContent = fs.readFileSync(path, "utf-8"); + + // Add import statement after the last import + const importInsertionPoint = fileContent.lastIndexOf("import"); + const nextLineAfterLastImport = + fileContent.indexOf("\n", importInsertionPoint) + 1; + const beforeImport = fileContent.slice(0, nextLineAfterLastImport); + const afterImport = fileContent.slice(nextLineAfterLastImport); + + let importStatement: string; + switch (provider) { + case "ThemeProvider": + importStatement = `import { ThemeProvider } from "${alias}/components/ThemeProvider";`; + break; + } + + // check if the provider already exists + if (fileContent.includes(importStatement)) { + // consola.info(`Provider ${provider} already exists in layout.tsx`); + return; + } + const modifiedImportContent = `${beforeImport}${importStatement}\n${afterImport}`; + + const rootChildrenText = "{children}"; + let replacementText = ""; + switch (provider) { + case "ThemeProvider": + replacementText = `\n<${provider} attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>${rootChildrenText}\n`; + break; + default: + replacementText = `\n<${provider}>${rootChildrenText}\n`; + break; + } + + const searchValue = "{children}"; + const newLayoutContent = modifiedImportContent.replace( + searchValue, + replacementText + ); + replaceFile(path, newLayoutContent); +}; + +export const addAuthCheckToAppLayout = () => { + const { hasSrc } = readConfigFile(); + const path = `${hasSrc ? "src/" : ""}app/(app)/layout.tsx`; + const { shared } = getFilePaths(); + + const fileContent = fs.readFileSync(path, "utf-8"); + const importStatement = `import { checkAuth } from "${formatFilePath( + shared.auth.authUtils, + { prefix: "alias", removeExtension: true } + )}";\n`; + const searchText = ` children: React.ReactNode; +}) { +`; + const replacementText = ` children: React.ReactNode; +}) { + await checkAuth(); +`; + const newText = + importStatement + fileContent.replace(searchText, replacementText); + replaceFile(path, newText); +}; +export const addContextProviderToAppLayout = ( provider: | "NextAuthProvider" | "TrpcProvider" @@ -54,14 +121,17 @@ export const addContextProviderToLayout = ( | "ThemeProvider" ) => { const { hasSrc, alias } = readConfigFile(); - const path = `${hasSrc ? "src/" : ""}app/layout.tsx`; + const path = `${hasSrc ? "src/" : ""}app/(app)/layout.tsx`; const fileContent = fs.readFileSync(path, "utf-8"); // Add import statement after the last import - const importInsertionPoint = fileContent.lastIndexOf("import"); + const lastIndexOfImport = fileContent.lastIndexOf("import"); + const importInsertionPoint = lastIndexOfImport === -1 ? 0 : lastIndexOfImport; const nextLineAfterLastImport = - fileContent.indexOf("\n", importInsertionPoint) + 1; + lastIndexOfImport === -1 + ? 0 + : fileContent.indexOf("\n", importInsertionPoint) + 1; const beforeImport = fileContent.slice(0, nextLineAfterLastImport); const afterImport = fileContent.slice(nextLineAfterLastImport); @@ -120,9 +190,6 @@ export const addContextProviderToLayout = ( case "Navbar": replacementText = `
\n\n
\n\n{children}\n
\n
`; break; - case "ThemeProvider": - replacementText = `\n<${provider} attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>${rootChildrenText}\n`; - break; case "TrpcProvider": replacementText = `\n<${provider} cookies={cookies().toString()}>${rootChildrenText}\n`; break; @@ -141,6 +208,80 @@ export const addContextProviderToLayout = ( replaceFile(path, newLayoutContent); }; +export const addContextProviderToAuthLayout = ( + provider: + | "NextAuthProvider" + | "TrpcProvider" + | "ShadcnToast" + | "ClerkProvider" +) => { + const { hasSrc, alias } = readConfigFile(); + const path = `${hasSrc ? "src/" : ""}app/(auth)/layout.tsx`; + + const pathExists = fs.existsSync(path); + if (!pathExists) return; + const fileContent = fs.readFileSync(path, "utf-8"); + + // Add import statement after the last import + const importInsertionPoint = fileContent.lastIndexOf("import"); + const nextLineAfterLastImport = + fileContent.indexOf("\n", importInsertionPoint) + 1; + const beforeImport = fileContent.slice(0, nextLineAfterLastImport); + const afterImport = fileContent.slice(nextLineAfterLastImport); + + const { trpc, "next-auth": nextAuth } = getFilePaths(); + + let importStatement: string; + switch (provider) { + case "NextAuthProvider": + importStatement = `import NextAuthProvider from "${formatFilePath( + nextAuth.authProviderComponent, + { prefix: "alias", removeExtension: true } + )}";`; + break; + case "TrpcProvider": + importStatement = `import TrpcProvider from "${formatFilePath( + trpc.trpcProvider, + { removeExtension: true, prefix: "alias" } + )}";\nimport { cookies } from "next/headers";`; + break; + case "ShadcnToast": + importStatement = `import { Toaster } from "${alias}/components/ui/sonner";`; + break; + case "ClerkProvider": + importStatement = 'import { ClerkProvider } from "@clerk/nextjs";'; + break; + } + + // check if the provider already exists + if (fileContent.includes(importStatement)) { + // consola.info(`Provider ${provider} already exists in layout.tsx`); + return; + } + const modifiedImportContent = `${beforeImport}${importStatement}\n${afterImport}`; + + const rootChildrenText = "{children}"; + let replacementText = ""; + switch (provider) { + case "ShadcnToast": + replacementText = `${rootChildrenText}\n\n`; + break; + case "TrpcProvider": + replacementText = `\n<${provider} cookies={cookies().toString()}>${rootChildrenText}\n`; + break; + default: + replacementText = `\n<${provider}>${rootChildrenText}\n`; + break; + } + + const searchValue = "{children}"; + const newLayoutContent = modifiedImportContent.replace( + searchValue, + replacementText + ); + replaceFile(path, newLayoutContent); +}; + export const AuthSubTypeMapping: Record = { clerk: "managed", kinde: "managed", diff --git a/src/commands/filePaths/index.ts b/src/commands/filePaths/index.ts index bd9ff45a..dbf1f99f 100644 --- a/src/commands/filePaths/index.ts +++ b/src/commands/filePaths/index.ts @@ -16,14 +16,15 @@ export const paths: { t3: Paths; normal: Paths } = { }, auth: { authUtils: "lib/auth/utils.ts", - accountPage: "app/account/page.tsx", + accountPage: "app/(app)/account/page.tsx", authSchema: "lib/db/schema/auth.ts", accountApiRoute: "app/api/account/route.ts", signInComponent: "components/auth/SignIn.tsx", - accountCardComponent: "app/account/AccountCard.tsx", - userSettingsComponent: "app/account/UserSettings.tsx", - updateNameCardComponent: "app/account/UpdateNameCard.tsx", - updateEmailCardComponent: "app/account/UpdateEmailCard.tsx", + accountCardComponent: "app/(app)/account/AccountCard.tsx", + userSettingsComponent: "app/(app)/account/UserSettings.tsx", + updateNameCardComponent: "app/(app)/account/UpdateNameCard.tsx", + updateEmailCardComponent: "app/(app)/account/UpdateEmailCard.tsx", + layoutPage: "app/(auth)/layout.tsx", }, init: { envMjs: "lib/env.mjs", @@ -31,6 +32,9 @@ export const paths: { t3: Paths; normal: Paths } = { globalCss: "app/globals.css", navbarComponent: "components/Navbar.tsx", sidebarComponent: "components/Sidebar.tsx", + appLayout: "app/(app)/layout.tsx", + indexRoute: "app/page.tsx", + dashboardRoute: "app/(app)/dashboard/page.tsx", }, }, prisma: { dbIndex: "lib/db/index.ts" }, @@ -47,16 +51,16 @@ export const paths: { t3: Paths; normal: Paths } = { }, clerk: { middleware: "middleware.ts", - signInPage: "app/sign-in/[[...sign-in]]/page.tsx", - signUpPage: "app/sign-up/[[...sign-in]]/page.tsx", + signInPage: "app/(auth)/sign-in/[[...sign-in]]/page.tsx", + signUpPage: "app/(auth)/sign-up/[[...sign-in]]/page.tsx", }, lucia: { signUpApiRoute: "app/api/sign-up/route.ts", signInApiRoute: "app/api/sign-in/route.ts", signOutApiRoute: "app/api/sign-out/route.ts", appDTs: "app.d.ts", - signInPage: "app/sign-in/page.tsx", - signUpPage: "app/sign-up/page.tsx", + signInPage: "app/(auth)/sign-in/page.tsx", + signUpPage: "app/(auth)/sign-up/page.tsx", libAuthLucia: "lib/auth/lucia.ts", authFormComponent: "components/auth/Form.tsx", signOutButtonComponent: "components/auth/SignOutBtn.tsx", @@ -66,25 +70,25 @@ export const paths: { t3: Paths; normal: Paths } = { }, resend: { emailUtils: "lib/email/utils.ts", - resendPage: "app/resend/page.tsx", + resendPage: "app/(app)/resend/page.tsx", emailApiRoute: "app/api/email/route.ts", libEmailIndex: "lib/email/index.ts", firstEmailComponent: "components/emails/FirstEmail.tsx", }, stripe: { stripeIndex: "lib/stripe/index.ts", - accountBillingPage: "app/account/billing/page.tsx", + accountBillingPage: "app/(app)/account/billing/page.tsx", configSubscription: "config/subscriptions.ts", stripeSubscription: "lib/stripe/subscription.ts", accountRouterTrpc: "lib/server/routers/account.ts", - billingSuccessToast: "app/account/billing/SuccessToast.tsx", + billingSuccessToast: "app/(app)/account/billing/SuccessToast.tsx", subscriptionSchema: "lib/db/schema/subscriptions.ts", stripeWebhooksApiRoute: "app/api/webhooks/stripe/route.ts", manageSubscriptionApiRoute: "app/api/billing/manage-subscription/route.ts", - accountPlanSettingsComponent: "app/account/PlanSettings.tsx", + accountPlanSettingsComponent: "app/(app)/account/PlanSettings.tsx", billingManageSubscriptionComponent: - "app/account/billing/ManageSubscription.tsx", + "app/(app)/account/billing/ManageSubscription.tsx", }, "next-auth": { signOutButtonComponent: "components/auth/SignOutBtn.tsx", @@ -115,6 +119,7 @@ export const paths: { t3: Paths; normal: Paths } = { userSettingsComponent: "app/account/UserSettings.tsx", updateNameCardComponent: "app/account/UpdateNameCard.tsx", updateEmailCardComponent: "app/account/UpdateEmailCard.tsx", + layoutPage: "app/(auth)/layout.tsx", }, init: { envMjs: "env.js", @@ -122,6 +127,9 @@ export const paths: { t3: Paths; normal: Paths } = { globalCss: "styles/globals.css", navbarComponent: "components/Navbar.tsx", sidebarComponent: "components/Sidebar.tsx", + appLayout: "app/(app)/layout.tsx", + indexRoute: "app/page.tsx", + dashboardRoute: "app/(app)/dashboard/page.tsx", }, }, prisma: { dbIndex: "server/db.ts" }, diff --git a/src/commands/filePaths/types.d.ts b/src/commands/filePaths/types.d.ts index 9c7990b2..af309048 100644 --- a/src/commands/filePaths/types.d.ts +++ b/src/commands/filePaths/types.d.ts @@ -14,6 +14,9 @@ export type Paths = { globalCss: string; navbarComponent: string; sidebarComponent: string; + appLayout: string; + indexRoute: string; + dashboardRoute: string; }; orm: { servicesDir: string; @@ -28,6 +31,7 @@ export type Paths = { updateNameCardComponent: string; updateEmailCardComponent: string; accountCardComponent: string; + layoutPage: string; authSchema?: string; }; }; diff --git a/src/commands/generate/generators/model/queries/generators.ts b/src/commands/generate/generators/model/queries/generators.ts index 50dd0215..31746dd7 100644 --- a/src/commands/generate/generators/model/queries/generators.ts +++ b/src/commands/generate/generators/model/queries/generators.ts @@ -19,7 +19,10 @@ const generateDrizzleImports = (schema: Schema, relations: DBField[]) => { const { shared } = getFilePaths(); const dbIndex = getDbIndexPath(); - const children = schema.children.map((c) => formatTableName(c.tableName)); + const children = + schema.children !== undefined + ? schema.children.map((c) => formatTableName(c.tableName)) + : []; return `import { db } from "${formatFilePath(dbIndex, { prefix: "alias", diff --git a/src/commands/generate/generators/model/queries/index.ts b/src/commands/generate/generators/model/queries/index.ts index 94cbbdca..95329c55 100644 --- a/src/commands/generate/generators/model/queries/index.ts +++ b/src/commands/generate/generators/model/queries/index.ts @@ -6,14 +6,17 @@ export const generateQueryContent = (schema: ExtendedSchema, orm: ORMType) => { const relations = schema.fields.filter( (field) => field.type.toLowerCase() === "references" ); + console.log(schema); + const hasChildren = schema.children !== undefined; const imports = generateQueries[orm].imports(schema, relations); const getQuery = generateQueries[orm].get(schema, relations); const getByIdQuery = generateQueries[orm].getById(schema, relations); - const getByIdWithChildren = - schema.children && schema.children.length > 0 + const getByIdWithChildren = hasChildren + ? schema.children && schema.children.length > 0 ? generateQueries[orm].getByIdWithChildren(schema, relations) - : ""; + : "" + : ""; return `${imports} ${getQuery} diff --git a/src/index.ts b/src/index.ts index b35b159d..2cd5bb40 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { buildSchema } from "./commands/generate/index.js"; import { addPackage } from "./commands/add/index.js"; const program = new Command(); -program.name("kirimase").description("Kirimase CLI").version("0.0.52"); +program.name("kirimase").description("Kirimase CLI").version("0.0.53"); addCommonOptions(program.command("init")) .description("initialise and configure kirimase within directory")