From 68d80ebd4e8925c4838b2cc9b41c2411d5feff98 Mon Sep 17 00:00:00 2001 From: OmegaHawkeye Date: Sun, 11 Feb 2024 22:59:43 +0100 Subject: [PATCH 1/5] Add supabase as auth provider and configure file formatting with prettier --- package.json | 3 +- pnpm-lock.yaml | 9 + src/commands/add/auth/clerk/index.ts | 30 +- src/commands/add/auth/clerk/utils.ts | 8 +- src/commands/add/auth/kinde/index.ts | 25 +- src/commands/add/auth/lucia/index.ts | 40 +- src/commands/add/auth/lucia/utils.ts | 8 +- src/commands/add/auth/next-auth/generators.ts | 22 +- src/commands/add/auth/next-auth/index.ts | 36 +- src/commands/add/auth/next-auth/utils.ts | 4 +- src/commands/add/auth/shared/generators.ts | 1 + src/commands/add/auth/shared/index.ts | 37 +- src/commands/add/auth/supabase/generators.ts | 717 ++++++++++++++++++ src/commands/add/auth/supabase/index.ts | 158 ++++ src/commands/add/auth/supabase/utils.ts | 22 + .../add/componentLib/shadcn-ui/index.ts | 30 +- src/commands/add/index.ts | 36 +- .../add/misc/defaultStyles/generators.ts | 12 +- src/commands/add/misc/navbar/generators.ts | 12 +- src/commands/add/misc/resend/generators.ts | 4 +- src/commands/add/misc/resend/index.ts | 17 +- src/commands/add/misc/stripe/index.ts | 53 +- src/commands/add/misc/trpc/index.ts | 26 +- src/commands/add/orm/drizzle/generators.ts | 112 +-- src/commands/add/orm/drizzle/index.ts | 26 +- src/commands/add/orm/drizzle/utils.ts | 20 +- src/commands/add/orm/prisma/index.ts | 36 +- src/commands/add/orm/prisma/utils.ts | 8 +- src/commands/add/orm/utils.ts | 2 +- src/commands/add/utils.ts | 23 +- src/commands/filePaths/index.ts | 18 + src/commands/filePaths/types.d.ts | 9 + src/commands/generate/generators/apiRoute.ts | 4 +- .../generate/generators/model/index.ts | 31 +- .../generate/generators/model/schema/index.ts | 17 +- .../generate/generators/model/utils.ts | 12 +- .../generate/generators/model/views-shared.ts | 4 +- .../generate/generators/serverActions.ts | 4 +- src/commands/generate/generators/trpcRoute.ts | 17 +- .../generators/views-with-server-actions.ts | 49 +- src/commands/generate/generators/views.ts | 15 +- src/commands/generate/index.ts | 19 +- src/commands/generate/utils.ts | 17 +- src/commands/init/index.ts | 10 +- src/commands/init/utils.ts | 92 ++- src/index.ts | 9 +- src/types.d.ts | 5 +- src/utils.ts | 40 +- 48 files changed, 1493 insertions(+), 416 deletions(-) create mode 100644 src/commands/add/auth/supabase/generators.ts create mode 100644 src/commands/add/auth/supabase/index.ts create mode 100644 src/commands/add/auth/supabase/utils.ts diff --git a/package.json b/package.json index 5c20c4d1..0335b9c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kirimase", - "version": "0.0.53", + "version": "0.0.54", "description": "A Rails-like CLI for building full-stack Next.js apps faster", "main": "index.js", "type": "module", @@ -41,6 +41,7 @@ "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.6.0", "eslint": "^8.48.0", + "prettier": "^3.2.5", "typescript": "^5.1.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01016a75..2f76fa54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,9 @@ devDependencies: eslint: specifier: ^8.48.0 version: 8.48.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 typescript: specifier: ^5.1.6 version: 5.1.6 @@ -1176,6 +1179,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} diff --git a/src/commands/add/auth/clerk/index.ts b/src/commands/add/auth/clerk/index.ts index cc5909c3..43a253ae 100644 --- a/src/commands/add/auth/clerk/index.ts +++ b/src/commands/add/auth/clerk/index.ts @@ -6,11 +6,9 @@ // 6. Add lib/auth/utils.ts // 7. install package - @clerk/nextjs -import { consola } from "consola"; import { addPackageToConfig, createFile, - installPackages, readConfigFile, replaceFile, updateConfigFile, @@ -23,11 +21,10 @@ import { } from "../../utils.js"; import { clerkGenerators } from "./generators.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; -import { libAuthUtilsTs } from "../next-auth/generators.js"; import { updateTrpcWithSessionIfInstalled } from "../shared/index.js"; export const addClerk = async () => { - const { rootPath, preferredPackageManager, componentLib } = readConfigFile(); + const { rootPath, componentLib } = readConfigFile(); const { clerk: { middleware, signInPage, signUpPage }, shared: { @@ -35,6 +32,7 @@ export const addClerk = async () => { init, }, } = getFilePaths(); + const { generateAuthUtilsTs, generateMiddlewareTs, @@ -42,9 +40,11 @@ export const addClerk = async () => { generateSignUpPageTs, homePageWithUserButton, } = clerkGenerators; - addContextProviderToAuthLayout("ClerkProvider"); - addContextProviderToAppLayout("ClerkProvider"); - addToDotEnv( + + await addContextProviderToAuthLayout("ClerkProvider"); + await addContextProviderToAppLayout("ClerkProvider"); + + await addToDotEnv( [ { key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", value: "", public: true }, { key: "CLERK_SECRET_KEY", value: "" }, @@ -55,21 +55,21 @@ export const addClerk = async () => { ], rootPath ); - createFile( + await createFile( formatFilePath(middleware, { prefix: "rootPath", removeExtension: false }), generateMiddlewareTs() ); - createFile( + await createFile( formatFilePath(signInPage, { removeExtension: false, prefix: "rootPath" }), generateSignInPageTs() ); - createFile( + await createFile( formatFilePath(signUpPage, { removeExtension: false, prefix: "rootPath" }), generateSignUpPageTs() ); - replaceFile( + await replaceFile( formatFilePath(init.dashboardRoute, { removeExtension: false, prefix: "rootPath", @@ -77,7 +77,7 @@ export const addClerk = async () => { homePageWithUserButton(componentLib) ); - createFile( + await createFile( formatFilePath(authUtils, { prefix: "rootPath", removeExtension: false, @@ -86,15 +86,15 @@ export const addClerk = async () => { ); // If trpc installed, add protectedProcedure - updateTrpcWithSessionIfInstalled(); + await updateTrpcWithSessionIfInstalled(); addToInstallList({ regular: ["@clerk/nextjs"], dev: [] }); // await installPackages( // { regular: "@clerk/nextjs", dev: "" }, // preferredPackageManager, // ); - addPackageToConfig("clerk"); - updateConfigFile({ auth: "clerk" }); + await addPackageToConfig("clerk"); + await updateConfigFile({ auth: "clerk" }); // consola.success("Successfully added Clerk to your project!"); // consola.info( // "Head over to https://dashboard.clerk.com/apps/new to create a new Clerk app" diff --git a/src/commands/add/auth/clerk/utils.ts b/src/commands/add/auth/clerk/utils.ts index b28541b4..461d8240 100644 --- a/src/commands/add/auth/clerk/utils.ts +++ b/src/commands/add/auth/clerk/utils.ts @@ -1,5 +1,5 @@ import fs from "fs"; -import { createFile, replaceFile } from "../../../../utils.js"; +import { replaceFile } from "../../../../utils.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; // export const updateClerkMiddlewareForStripe = (rootPath: string) => { @@ -14,13 +14,13 @@ import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; // if (mwExists) { // const mwContent = fs.readFileSync(mwPath, "utf-8"); // const newUtilsContent = mwContent.replace(initMWContent, updatedMWContent); -// replaceFile(mwPath, newUtilsContent); +// await replaceFile(mwPath, newUtilsContent); // } else { // console.error("Middleware does not exist"); // } // }; -export const addToClerkIgnoredRoutes = (newPath: string) => { +export const addToClerkIgnoredRoutes = async (newPath: string) => { const { clerk } = getFilePaths(); const initMWContent = "ignoredRoutes: ["; const updatedMWContent = "ignoredRoutes: [" + ` "${newPath}", `; @@ -32,7 +32,7 @@ export const addToClerkIgnoredRoutes = (newPath: string) => { if (mwExists) { const mwContent = fs.readFileSync(mwPath, "utf-8"); const newUtilsContent = mwContent.replace(initMWContent, updatedMWContent); - replaceFile(mwPath, newUtilsContent); + await replaceFile(mwPath, newUtilsContent); } else { console.error("Middleware does not exist"); } diff --git a/src/commands/add/auth/kinde/index.ts b/src/commands/add/auth/kinde/index.ts index 0aabe0cc..ca421e19 100644 --- a/src/commands/add/auth/kinde/index.ts +++ b/src/commands/add/auth/kinde/index.ts @@ -1,8 +1,6 @@ -import { consola } from "consola"; import { addPackageToConfig, createFile, - installPackages, readConfigFile, updateConfigFile, } from "../../../../utils.js"; @@ -20,33 +18,36 @@ import { addToInstallList } from "../../utils.js"; export const addKinde = async () => { const { kinde, shared } = getFilePaths(); - const { preferredPackageManager } = readConfigFile(); + // add api route - createFile( + await createFile( formatFilePath(kinde.routeHandler, { prefix: "rootPath", removeExtension: false, }), generateKindeRouteHandler() ); + // create signin button component - createFile( + await createFile( formatFilePath(shared.auth.signInComponent, { prefix: "rootPath", removeExtension: false, }), generateSignInComponent() ); + // create auth/utils.ts - createFile( + await createFile( formatFilePath(shared.auth.authUtils, { prefix: "rootPath", removeExtension: false, }), generateAuthUtils() ); + // update root page - createFile( + await createFile( formatFilePath(shared.init.dashboardRoute, { prefix: "rootPath", removeExtension: false, @@ -55,7 +56,7 @@ export const addKinde = async () => { ); // generate sign in page - createFile( + await createFile( formatFilePath(kinde.signInPage, { prefix: "rootPath", removeExtension: false, @@ -64,10 +65,10 @@ export const addKinde = async () => { ); // If trpc installed, add protectedProcedure - updateTrpcWithSessionIfInstalled(); + await updateTrpcWithSessionIfInstalled(); // add env variables - addToDotEnv([ + await addToDotEnv([ { key: "KINDE_CLIENT_ID", value: "", @@ -91,7 +92,7 @@ export const addKinde = async () => { // ); addToInstallList({ regular: ["@kinde-oss/kinde-auth-nextjs"], dev: [] }); - addPackageToConfig("kinde"); - updateConfigFile({ auth: "kinde" }); + await addPackageToConfig("kinde"); + await updateConfigFile({ auth: "kinde" }); // consola.success("Successfully installed Kinde auth"); }; diff --git a/src/commands/add/auth/lucia/index.ts b/src/commands/add/auth/lucia/index.ts index 534cfb37..679e5105 100644 --- a/src/commands/add/auth/lucia/index.ts +++ b/src/commands/add/auth/lucia/index.ts @@ -1,12 +1,10 @@ import { addPackageToConfig, createFile, - installPackages, readConfigFile, replaceFile, updateConfigFile, } from "../../../../utils.js"; -import { consola } from "consola"; import { luciaGenerators } from "./generators.js"; import { generateDrizzleAdapterDriverMappings, @@ -30,13 +28,13 @@ export const addLucia = async () => { const { orm, provider, - preferredPackageManager, // packages, rootPath, driver, componentLib, t3, } = readConfigFile(); + // ask whether want to use shadcnui // consola.info( // "Kirimase generates views and components for authenticating using Lucia." @@ -73,55 +71,55 @@ export const addLucia = async () => { } else { viewsAndComponents = generateViewsAndComponents(false); } - createFile( + await createFile( formatFilePath(lucia.signInPage, { removeExtension: false, prefix: "rootPath", }), viewsAndComponents.signInPage ); - createFile( + await createFile( formatFilePath(lucia.signUpPage, { removeExtension: false, prefix: "rootPath", }), viewsAndComponents.signUpPage ); - createFile( + await createFile( formatFilePath(lucia.authFormComponent, { removeExtension: false, prefix: "rootPath", }), viewsAndComponents.authFormComponent ); - replaceFile( + await replaceFile( formatFilePath(shared.init.dashboardRoute, { removeExtension: false, prefix: "rootPath", }), viewsAndComponents.homePage ); - createFile( + await createFile( rootPath.concat("app/loading.tsx"), viewsAndComponents.loadingPage ); // add API routes const apiRoutes = generateApiRoutes(); - createFile( + await createFile( formatFilePath(lucia.signInApiRoute, { removeExtension: false, prefix: "rootPath", }), apiRoutes.signInRoute ); - createFile( + await createFile( formatFilePath(lucia.signUpApiRoute, { removeExtension: false, prefix: "rootPath", }), apiRoutes.signUpRoute ); - createFile( + await createFile( formatFilePath(lucia.signOutApiRoute, { removeExtension: false, prefix: "rootPath", @@ -131,7 +129,7 @@ export const addLucia = async () => { // add app.d.ts const appDTs = generateAppDTs(); - createFile( + await createFile( formatFilePath(lucia.appDTs, { removeExtension: false, prefix: "rootPath", @@ -141,7 +139,7 @@ export const addLucia = async () => { const authDirFiles = generateAuthDirFiles(orm, driver, provider); // create auth/utils.ts - createFile( + await createFile( formatFilePath(shared.auth.authUtils, { removeExtension: false, prefix: "rootPath", @@ -150,7 +148,7 @@ export const addLucia = async () => { ); // create auth/lucia.ts - createFile( + await createFile( formatFilePath(lucia.libAuthLucia, { removeExtension: false, prefix: "rootPath", @@ -167,7 +165,7 @@ export const addLucia = async () => { /\.references\(\(\) => user\.id\)/g, "" ); - createFile( + await createFile( formatFilePath(shared.auth.authSchema, { removeExtension: false, prefix: "rootPath", @@ -175,7 +173,7 @@ export const addLucia = async () => { schemaWithoutReferences ); } else { - createFile( + await createFile( formatFilePath(shared.auth.authSchema, { removeExtension: false, prefix: "rootPath", @@ -204,7 +202,7 @@ export const addLucia = async () => { const contentsWithPool = contentsImportsUpdated.concat( "\nexport const pool = new Pool({ connectionString: env.DATABASE_URL });" ); - replaceFile(dbTsPath, contentsWithPool); + await replaceFile(dbTsPath, contentsWithPool); } // install packages (lucia, and adapter) will have to pull in specific package @@ -217,12 +215,12 @@ export const addLucia = async () => { if (t3 && orm === "drizzle") { // replace server/db/index.ts to have connection exported - updateDrizzleDbIndex(provider); + await updateDrizzleDbIndex(provider); // updates to make sure shcmea is included in dbindex too } // If trpc installed, add protectedProcedure - updateTrpcWithSessionIfInstalled(); + await updateTrpcWithSessionIfInstalled(); // await installPackages( // { regular: `lucia ${adapterPackage}`, dev: "" }, @@ -231,7 +229,7 @@ export const addLucia = async () => { addToInstallList({ regular: ["lucia@2.7.7", adapterPackage], dev: [] }); // add package to config - addPackageToConfig("lucia"); - updateConfigFile({ auth: "lucia" }); + await addPackageToConfig("lucia"); + await updateConfigFile({ auth: "lucia" }); // consola.success("Successfully installed Lucia!"); }; diff --git a/src/commands/add/auth/lucia/utils.ts b/src/commands/add/auth/lucia/utils.ts index 96733f22..6f9c8876 100644 --- a/src/commands/add/auth/lucia/utils.ts +++ b/src/commands/add/auth/lucia/utils.ts @@ -320,14 +320,14 @@ export const addLuciaToPrismaSchema = async () => { // write logic to check if model already exists -> if so replace const newContent = schemaContents.concat("\n", PrismaLuciaSchema); - replaceFile(schemaPath, newContent); + await replaceFile(schemaPath, newContent); // consola.success(`Added auth to Prisma schema`); } else { consola.info(`Prisma schema file does not exist`); } }; -export const updateDrizzleDbIndex = (provider: DBProvider) => { +export const updateDrizzleDbIndex = async (provider: DBProvider) => { const { shared, drizzle } = getFilePaths(); // what is it like to type like this' // functions intended use if with t3 so assumed provider is pscale @@ -347,7 +347,7 @@ export const connection = connect({ export const db = drizzle(connection, { schema }); `; - replaceFile( + await replaceFile( formatFilePath(drizzle.dbIndex, { prefix: "rootPath", removeExtension: false, @@ -356,5 +356,5 @@ export const db = drizzle(connection, { schema }); ); } // TODO: NOW - updateRootSchema("auth", true, "lucia"); + await updateRootSchema("auth", true, "lucia"); }; diff --git a/src/commands/add/auth/next-auth/generators.ts b/src/commands/add/auth/next-auth/generators.ts index 72721a88..3835143a 100644 --- a/src/commands/add/auth/next-auth/generators.ts +++ b/src/commands/add/auth/next-auth/generators.ts @@ -13,6 +13,7 @@ import { getDbIndexPath, getFilePaths, } from "../../../filePaths/index.js"; +import { formatFileContentWithPrettier } from "../../../init/utils.js"; // 1. Create app/api/auth/[...nextauth].ts export const apiAuthNextAuthTsOld = ( @@ -510,7 +511,7 @@ export default function SignIn() { }; // 6. updateTrpcTs -export const updateTrpcTs = () => { +export const updateTrpcTs = async () => { const { trpc } = getFilePaths(); const filePath = formatFilePath(trpc.serverTrpc, { removeExtension: false, @@ -545,14 +546,17 @@ export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); `; const modifiedRouterContent = fileContent.concat(protectedProcedureContent); - fs.writeFileSync(filePath, modifiedRouterContent); + fs.writeFileSync( + filePath, + await formatFileContentWithPrettier(modifiedRouterContent,filePath) + ); // consola.success( // "TRPC Router updated successfully to add protectedProcedure." // ); }; -export const enableSessionInContext = () => { +export const enableSessionInContext = async () => { const { trpc } = getFilePaths(); const filePath = formatFilePath(trpc.trpcContext, { prefix: "rootPath", @@ -562,13 +566,16 @@ export const enableSessionInContext = () => { const fileContent = fs.readFileSync(filePath, "utf-8"); const updatedContent = fileContent.replace(/\/\//g, ""); - fs.writeFileSync(filePath, updatedContent); + fs.writeFileSync( + filePath, + await formatFileContentWithPrettier(updatedContent,filePath) + ); // consola.success("TRPC Context updated successfully to add Session data."); }; // no longer necessary -export const enableSessionInTRPCApi_DEPRECATED = () => { +export const enableSessionInTRPCApi_DEPRECATED = async () => { const { trpc } = getFilePaths(); const filePath = formatFilePath(trpc.trpcApiTs, { prefix: "rootPath", @@ -578,7 +585,10 @@ export const enableSessionInTRPCApi_DEPRECATED = () => { const fileContent = fs.readFileSync(filePath, "utf-8"); const updatedContent = fileContent.replace(/\/\//g, ""); - fs.writeFileSync(filePath, updatedContent); + fs.writeFileSync( + filePath, + await formatFileContentWithPrettier(updatedContent,filePath) + ); // consola.success("TRPC Server API updated successfully to add Session data."); }; diff --git a/src/commands/add/auth/next-auth/index.ts b/src/commands/add/auth/next-auth/index.ts index 8c6b3276..81cd43fc 100644 --- a/src/commands/add/auth/next-auth/index.ts +++ b/src/commands/add/auth/next-auth/index.ts @@ -1,8 +1,6 @@ -import { consola } from "consola"; import { addPackageToConfig, createFile, - installPackages, readConfigFile, replaceFile, updateConfigFile, @@ -17,7 +15,7 @@ import { libAuthProviderTsx, libAuthUtilsTs, } from "./generators.js"; -import { AuthDriver, AuthProvider, AuthProviders } from "./utils.js"; +import { AuthDriver, AuthProvider } from "./utils.js"; import { addContextProviderToAppLayout, // addContextProviderToAuthLayout, @@ -39,17 +37,15 @@ export const addNextAuth = async ( hasSrc, preferredPackageManager, driver, - packages, orm, componentLib, provider: dbProvider, t3, } = readConfigFile(); - const rootPath = `${hasSrc ? "src/" : ""}`; const { "next-auth": nextAuth, shared } = getFilePaths(); // 1. Create app/api/auth/[...nextauth].ts - createFile( + await createFile( formatFilePath(nextAuth.nextAuthApiRoute, { removeExtension: false, prefix: "rootPath", @@ -58,7 +54,7 @@ export const addNextAuth = async ( ); // 2. create lib/auth/Provider.tsx - createFile( + await createFile( formatFilePath(nextAuth.authProviderComponent, { removeExtension: false, prefix: "rootPath", @@ -67,7 +63,7 @@ export const addNextAuth = async ( ); // 3. create lib/auth/utils.ts - createFile( + await createFile( formatFilePath(shared.auth.authUtils, { removeExtension: false, prefix: "rootPath", @@ -78,7 +74,7 @@ export const addNextAuth = async ( // 4. create lib/db/schema/auth.ts if (orm !== null) { if (orm === "drizzle") { - createFile( + await createFile( formatFilePath(shared.auth.authSchema, { removeExtension: false, prefix: "rootPath", @@ -86,11 +82,11 @@ export const addNextAuth = async ( createDrizzleAuthSchema(driver) ); if (t3) { - updateRootSchema("auth", true, "next-auth"); + await updateRootSchema("auth", true, "next-auth"); } } if (orm === "prisma") { - addToPrismaSchema( + await addToPrismaSchema( createPrismaAuthSchema( driver, dbProvider === "planetscale", @@ -102,7 +98,7 @@ export const addNextAuth = async ( } // 5. create components/auth/SignIn.tsx - TODO - may be causing problems - createFile( + await createFile( formatFilePath(shared.auth.signInComponent, { removeExtension: false, prefix: "rootPath", @@ -111,9 +107,9 @@ export const addNextAuth = async ( ); // 6. If trpc installed, add protectedProcedure // this wont run because it is installed before trpc - updateTrpcWithSessionIfInstalled(); + await updateTrpcWithSessionIfInstalled(); - replaceFile( + await replaceFile( formatFilePath(shared.init.dashboardRoute, { removeExtension: false, prefix: "rootPath", @@ -122,7 +118,7 @@ export const addNextAuth = async ( ); // generate sign in page - createFile( + await createFile( formatFilePath(nextAuth.signInPage, { removeExtension: false, prefix: "rootPath", @@ -131,7 +127,7 @@ export const addNextAuth = async ( ); // add to env - addToDotEnv( + await addToDotEnv( [ { key: "NEXTAUTH_SECRET", @@ -182,11 +178,11 @@ export const addNextAuth = async ( if (orm !== null) addToInstallList({ regular: [AuthDriver[orm].package], dev: [] }); - addPackageToConfig("next-auth"); - updateConfigFile({ auth: "next-auth" }); + await addPackageToConfig("next-auth"); + await updateConfigFile({ auth: "next-auth" }); // 9. Instruct user to add the to their root layout. - // addContextProviderToAuthLayout("NextAuthProvider"); - addContextProviderToAppLayout("NextAuthProvider"); + // await addContextProviderToAuthLayout("NextAuthProvider"); + await addContextProviderToAppLayout("NextAuthProvider"); if (orm === "prisma") await prismaGenerate(preferredPackageManager); // consola.success("Successfully added Next Auth to your project!"); }; diff --git a/src/commands/add/auth/next-auth/utils.ts b/src/commands/add/auth/next-auth/utils.ts index 770100b5..1c0834c2 100644 --- a/src/commands/add/auth/next-auth/utils.ts +++ b/src/commands/add/auth/next-auth/utils.ts @@ -58,7 +58,7 @@ export const AuthDriver: { }, }; -export const checkAndAddAuthUtils = () => { +export const checkAndAddAuthUtils = async () => { const { shared } = getFilePaths(); const authUtilsPath = formatFilePath(shared.auth.authUtils, { removeExtension: false, @@ -93,5 +93,5 @@ export const checkAuth = async () => { if (!session) redirect("/api/auth/signin"); }; `; - createFile(authUtilsPath, t3AuthUtilsContent); + await createFile(authUtilsPath, t3AuthUtilsContent); }; diff --git a/src/commands/add/auth/shared/generators.ts b/src/commands/add/auth/shared/generators.ts index 400757a3..b13eace4 100644 --- a/src/commands/add/auth/shared/generators.ts +++ b/src/commands/add/auth/shared/generators.ts @@ -468,6 +468,7 @@ export const createNavbar = ( usingClerk = false, auth: AuthType ) => { + // TODO: Add supabase as auth provider const { shared, "next-auth": nextAuth } = getFilePaths(); const { alias } = readConfigFile(); let logOutRoute: string; diff --git a/src/commands/add/auth/shared/index.ts b/src/commands/add/auth/shared/index.ts index 9988461e..096f6ae3 100644 --- a/src/commands/add/auth/shared/index.ts +++ b/src/commands/add/auth/shared/index.ts @@ -1,9 +1,4 @@ -import { consola } from "consola"; -import { - createFile, - installShadcnUIComponents, - readConfigFile, -} from "../../../../utils.js"; +import { createFile, readConfigFile } from "../../../../utils.js"; import { addToShadcnComponentList } from "../../utils.js"; import { createAccountApiTs, @@ -12,7 +7,7 @@ import { createUserSettingsComponent, createUpdateNameCard, createUpdateEmailCard, - createNavbar, + // createNavbar, createSignOutBtn, } from "./generators.js"; import { AuthType, ORMType } from "../../../../types.js"; @@ -26,9 +21,10 @@ export const createAccountSettingsPage = async () => { const { orm, rootPath, componentLib, auth } = readConfigFile(); const { shared } = getFilePaths(); const withShadCn = componentLib === "shadcn-ui" ? true : false; + // create account api - clerk has managed component so no need - if (auth !== "clerk") { - createFile( + if (auth !== "supabase" && auth !== "clerk") { + await createFile( formatFilePath(shared.auth.accountApiRoute, { prefix: "rootPath", removeExtension: false, @@ -38,7 +34,7 @@ export const createAccountSettingsPage = async () => { } // create account page - createFile( + await createFile( formatFilePath(shared.auth.accountPage, { prefix: "rootPath", removeExtension: false, @@ -47,7 +43,7 @@ export const createAccountSettingsPage = async () => { ); // create usersettings component - createFile( + await createFile( formatFilePath(shared.auth.userSettingsComponent, { prefix: "rootPath", removeExtension: false, @@ -64,8 +60,9 @@ export const scaffoldAccountSettingsUI = async ( auth: AuthType ) => { const { shared, lucia } = getFilePaths(); + // create updatenamecard - createFile( + await createFile( formatFilePath(shared.auth.updateNameCardComponent, { prefix: "rootPath", removeExtension: false, @@ -74,7 +71,7 @@ export const scaffoldAccountSettingsUI = async ( ); // create updatenamecard - createFile( + await createFile( formatFilePath(shared.auth.updateEmailCardComponent, { prefix: "rootPath", removeExtension: false, @@ -83,7 +80,7 @@ export const scaffoldAccountSettingsUI = async ( ); // create accountcard components - createFile( + await createFile( formatFilePath(shared.auth.accountCardComponent, { prefix: "rootPath", removeExtension: false, @@ -92,7 +89,7 @@ export const scaffoldAccountSettingsUI = async ( ); // create navbar component - // createFile( + // await createFile( // formatFilePath(shared.init.navbarComponent, { // prefix: "rootPath", // removeExtension: false, @@ -100,8 +97,8 @@ export const scaffoldAccountSettingsUI = async ( // createNavbar(withShadCn, auth === "clerk", auth) // ); - if (withShadCn) { - createFile( + if (withShadCn && auth !== "supabase") { + await createFile( formatFilePath(lucia.signOutButtonComponent, { prefix: "rootPath", removeExtension: false, @@ -118,12 +115,12 @@ export const scaffoldAccountSettingsUI = async ( } }; -export const updateTrpcWithSessionIfInstalled = () => { +export const updateTrpcWithSessionIfInstalled = async () => { const { packages, t3 } = readConfigFile(); if (packages.includes("trpc")) { if (!t3) { - updateTrpcTs(); - enableSessionInContext(); + await updateTrpcTs(); + await enableSessionInContext(); } } }; diff --git a/src/commands/add/auth/supabase/generators.ts b/src/commands/add/auth/supabase/generators.ts new file mode 100644 index 00000000..a59aea79 --- /dev/null +++ b/src/commands/add/auth/supabase/generators.ts @@ -0,0 +1,717 @@ +import { ORMType } from "../../../../types.js"; +import { readConfigFile } from "../../../../utils.js"; +import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; + +const generateViewsAndComponents = (withShadCn: boolean) => { + const signUpPage = generateSignUpPage(withShadCn); + const signInPage = generateSignInPage(withShadCn); + const authFormComponent = generateAuthFormComponent(withShadCn); + const signOutButtonComponent = generateSignOutButtonComponent(withShadCn); + const homePage = generateHomePage(); + const loadingPage = generateLoadingPage(); + + return { + signUpPage, + signInPage, + signOutButtonComponent, + authFormComponent, + homePage, + loadingPage, + }; +}; + +export const generateSupabaseHelpers = () => { + const { shared } = getFilePaths(); + return ` + import { createBrowserClient, createServerClient, type CookieOptions } from '@supabase/ssr' + import { env } from "${formatFilePath(shared.init.envMjs, { + removeExtension: false, + prefix: "alias", + })}"; + import { cookies } from 'next/headers' + import { type NextRequest, NextResponse } from "next/server"; + + export const supabaseBrowserClient = createBrowserClient(env.NEXT_PUBLIC_SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY); + + export const createSupabaseServerClient = () => + createServerClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + get(name: string) { + return cookies().get(name)?.value; + }, + set(name: string, value: string, options: CookieOptions) { + cookies().set({ name, value, ...options }); + }, + remove(name: string, options: CookieOptions) { + cookies().set({ name, value: "", ...options }); + }, + }, + }, + ); + + export const createSupabaseMiddlewareClient = (request: NextRequest) => { + // Create an unmodified response + let response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + + const supabase = createServerClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + get(name: string) { + return request.cookies.get(name)?.value; + }, + set(name: string, value: string, options: CookieOptions) { + // If the cookie is updated, update the cookies for the request and response + request.cookies.set({ + name, + value, + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.set({ + name, + value, + ...options, + }); + }, + remove(name: string, options: CookieOptions) { + // If the cookie is removed, update the cookies for the request and response + request.cookies.set({ + name, + value: "", + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.set({ + name, + value: "", + ...options, + }); + }, + }, + } + ); + + return { supabase, response }; + }; + + export const updateSession = async (request: NextRequest) => { + try { + const { supabase, response } = createSupabaseMiddlewareClient(request); + // This will refresh session if expired - required for Server Components + // https://supabase.com/docs/guides/auth/server-side/nextjs + await supabase.auth.getUser(); + + return response; + } catch (e) { + // If you are here, a Supabase client could not be created! + // This is likely because you have not set up environment variables. + // Check out http://localhost:3000 for Next Steps. + return NextResponse.next({ + request: { + headers: request.headers, + }, + }); + } + }; +`; +}; + +export const generateSignInPage = (withShadCn: boolean) => { + const { supabase, shared } = getFilePaths(); + const { alias } = readConfigFile(); + if (withShadCn) { + return ` + import AuthForm from "${formatFilePath(supabase.authFormComponent, { + removeExtension: true, + prefix: "alias", + })}"; + import { signIn } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + import { Input } from "${alias}/components/ui/input"; + import { Label } from "${alias}/components/ui/label"; + import Link from "next/link"; + + const Page = async () => { + return ( +
+

+ Sign in to your account +

+ + + +
+ + +
+
+
+ Don't have an account yet?{" "} + + Create an account + +
+
+ ); + }; + export default Page; + `; + } else { + return ` + import AuthForm from "${formatFilePath(supabase.authFormComponent, { + removeExtension: true, + prefix: "alias", + })}"; + import { signUp } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + import Link from "next/link"; + + const Page = async () => { + return ( +
+

+ Sign in to your account +

+ + + +
+ + +
+
+
+ Don't have an account yet?{" "} + + Create an account + +
+
+ ); + }; + export default Page; + `; + } +}; + +export const generateSignUpPage = (withShadCn: boolean) => { + const { supabase, shared } = getFilePaths(); + const { alias } = readConfigFile(); + if (withShadCn) { + return ` + import AuthForm from "${formatFilePath(supabase.authFormComponent, { + removeExtension: true, + prefix: "alias", + })}"; + import { signUp } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + import { Input } from "${alias}/components/ui/input"; + import { Label } from "${alias}/components/ui/label"; + import Link from "next/link"; + + const Page = async () => { + return ( +
+

Create an account

+ + + +
+ + +
+
+
+ Already have an account?{" "} + + Sign in + +
+
+ ); + }; + export default Page; + `; + } else { + return ` + import AuthForm from "${formatFilePath(supabase.authFormComponent, { + removeExtension: true, + prefix: "alias", + })}"; + import { signUp } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + import Link from "next/link"; + + const Page = async () => { + return ( +
+

Create an account

+ + + +
+ + +
+
+
+ Already have an account?{" "} + + Sign in + +
+
+ ); + }; + export default Page; + `; + } +}; + +export const generateSignOutButtonComponent = (withShadCn: boolean) => { + const { alias } = readConfigFile(); + const { shared } = getFilePaths(); + if (withShadCn) { + return `"use client"; +import { Button } from "${alias}/components/ui/button"; +import { signOut } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + +export const SignOutButton = () => { + return ( + + ); +}; + `; + } else { + return ` +"use client"; + +import { signOut } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + +export const SignOutButton = () => { + return ( + + ); +}; +`; + } +}; + +export const generateAuthFormComponent = (withShadCn: boolean) => { + const { alias } = readConfigFile(); + const { shared } = getFilePaths(); + const authForm = ` + "use client"; + + import { useFormState, useFormStatus } from "react-dom"; + import { redirect } from 'next/navigation'; + import { Button } from "${alias}/components/ui/button"; + import { type State } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + + type AuthType = 'sign-in' | 'sign-up' + type ServerAction = (state: State, formData: FormData) => State | Promise + + function AuthForm({ + action, + authType, + children + }: { + action: ServerAction + authType: AuthType + children?: React.ReactNode + }) { + const [state, formAction] = useFormState(action, null) + + if (state?.redirectTo) redirect(state.redirectTo) + + return ( +
+
+ {children} + + {state?.error && {state.error.message}} + + {state?.message && {state.message}} + + + +
+ ) + } + + export default AuthForm + + const SubmitButton = ({ authType }: { authType: AuthType }) => { + const buttonSuffix = authType === 'sign-in' ? 'in' : 'up' + + const { pending } = useFormStatus() + `; + + if (withShadCn) { + return ( + authForm + + ` + return ( + + )}` + ); + } else { + return ( + authForm + + ` + return ( + + )}` + ); + } +}; + +export const generateHomePage = () => { + const { supabase, shared } = getFilePaths(); + const { componentLib } = readConfigFile(); + return ` + import { SignOutButton } from "${formatFilePath( + supabase.signOutButtonComponent, + { + removeExtension: true, + prefix: "alias", + } + )}"; + import { getUserAuth } from "${formatFilePath(shared.auth.authUtils, { + prefix: "alias", + removeExtension: true, + })}"; + + export default async function Home() { + const { session } = await getUserAuth(); + return ( +
+

Profile

+
+                {JSON.stringify(session, null, 2)}
+            
+ +
+ ); + } +`; +}; +export const generateLoadingPage = () => { + const { componentLib } = readConfigFile(); + const withShadCn = componentLib === "shadcn-ui"; + return ` + export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ); + } + `; +}; + +export const generateApiRoutes = () => { + const { supabase } = getFilePaths(); + return ` + import { createSupabaseServerClient } from "${formatFilePath( + supabase.libAuthSupabase, + { + removeExtension: true, + prefix: "alias", + } + )}"; + import { NextRequest, NextResponse } from "next/server"; + + export async function GET(request: NextRequest) { + const requestUrl = new URL(request.url); + const code = requestUrl.searchParams.get("code"); + + if (code) { + const supabase = createSupabaseServerClient(); + await supabase.auth.exchangeCodeForSession(code); + } + + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin); + } + `; +}; + +const generateAuthDirFiles = (orm: ORMType) => { + const { supabase } = getFilePaths(); + const utilsTs = ` + import { redirect } from "next/navigation"; + import { createSupabaseServerClient } from "${formatFilePath( + supabase.libAuthSupabase, + { + removeExtension: true, + prefix: "alias", + } + )}"; + + export const getServerSession = async () => { + const supabase = createSupabaseServerClient(); + const { + data: { session } + } = await supabase.auth.getSession() + + return { session }; + }; + + export const getServerUser = async () => { + const supabase = createSupabaseServerClient() + const { + data: { user } + } = await supabase.auth.getUser() + + return { user } + } + + export type AuthSession = { + session: { + user: { + id: string; + name?: string; + email?: string; + }; + } | null; + }; + + export const getUserAuth = async () => { + const { user } = await getServerUser(); + + if (user) { + return { + session: { + user: { + id: user.id, + name: "", + email: user.email, + }, + }, + } as AuthSession; + } else { + return { session: null }; + } + }; + + export const checkAuth = async () => { + const { session } = await getUserAuth(); + + if (!session) redirect("/sign-in"); + }; + + // --------------------------------------------------- + // You can move these functions to a separate file and/or directory if you want but make sure to update the import paths + import { type AuthError } from '@supabase/supabase-js' + + export type State = { + error?: AuthError + message?: string + redirectTo?: string + } | null + + export const signOut = async () => { + 'use server' + const supabase = createSupabaseServerClient() + await supabase.auth.signOut() + redirect('/') + } + + export const signIn = async (state: State, formData: FormData) => { + 'use server' + const supabase = createSupabaseServerClient() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + + if (error) return { error } + + return { redirectTo: '/' } + } + + export const signUp = async (state: State, formData: FormData) => { + 'use server' + const supabase = createSupabaseServerClient() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signUp({ email, password }) + + if (error) return { error } + + return { redirectTo: '/' } + } +`; + + return { utilsTs }; +}; + +const generateMiddleware = () => { + const { supabase } = getFilePaths(); + return ` + import { type NextRequest } from "next/server"; + import { updateSession } from "${formatFilePath(supabase.libAuthSupabase, { + removeExtension: true, + prefix: "alias", + })}"; + + export async function middleware(request: NextRequest) { + return await updateSession(request); + } + + export const config = { + matcher: [ + /* + * Match all request paths except: + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + * - images - .svg, .png, .jpg, .jpeg, .gif, .webp + * Feel free to modify this pattern to include more paths. + */ + "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", + ], + }; `; +}; + +export const supabaseGenerators = { + generateMiddleware, + generateViewsAndComponents, + generateSupabaseHelpers, + generateApiRoutes, + generateAuthDirFiles, +}; diff --git a/src/commands/add/auth/supabase/index.ts b/src/commands/add/auth/supabase/index.ts new file mode 100644 index 00000000..e89c4076 --- /dev/null +++ b/src/commands/add/auth/supabase/index.ts @@ -0,0 +1,158 @@ +/* + 1. Add Env keys in env.mjs too + 2. Add middleware + 3. Scaffold Signup and Signin pages + 4. Create Signout button + 5. Create AuthForm component + 6. Create Home page + 7. Create Loading page + 8. Add API routes + 9. Add Supabase helpers + 10. Add Supabase packages + 11. Add package to config + 12. Update config file +*/ + +import { + addPackageToConfig, + createFile, + readConfigFile, + updateConfigFile, +} from "../../../../utils.js"; +import { supabaseGenerators } from "./generators.js"; +import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; +import { addToInstallList } from "../../utils.js"; +import { addToDotEnv } from "../../orm/drizzle/generators.js"; +export const addSupabase = async () => { + const { orm, rootPath, componentLib } = readConfigFile(); + const { supabase, shared } = getFilePaths(); + + const { + generateMiddleware, + generateViewsAndComponents, + generateSupabaseHelpers, + generateApiRoutes, + generateAuthDirFiles, + } = supabaseGenerators; + + await addToDotEnv( + [ + { + key: "NEXT_PUBLIC_SUPABASE_URL", + value: "your-project-url", + public: true, + }, + { + key: "NEXT_PUBLIC_SUPABASE_ANON_KEY", + value: "your-anon-key", + public: true, + }, + ], + rootPath + ); + + await createFile( + formatFilePath(supabase.middleware, { + prefix: "rootPath", + removeExtension: false, + }), + generateMiddleware() + ); + + const useShadCnUI = componentLib === "shadcn-ui"; + + // create auth form component + // generate sign-in and sign-up pages + const viewsAndComponents = generateViewsAndComponents(useShadCnUI); + + // create signIn Page + await createFile( + formatFilePath(supabase.signInPage, { + prefix: "rootPath", + removeExtension: false, + }), + viewsAndComponents.signInPage + ); + + // create signUp Page + await createFile( + formatFilePath(supabase.signUpPage, { + prefix: "rootPath", + removeExtension: false, + }), + viewsAndComponents.signUpPage + ); + + // create signOut Button + await createFile( + formatFilePath(supabase.signOutButtonComponent, { + prefix: "rootPath", + removeExtension: false, + }), + viewsAndComponents.signOutButtonComponent + ); + + // create auth form component + await createFile( + formatFilePath(supabase.authFormComponent, { + prefix: "rootPath", + removeExtension: false, + }), + viewsAndComponents.authFormComponent + ); + + // create home Page + await createFile( + formatFilePath(shared.init.dashboardRoute, { + prefix: "rootPath", + removeExtension: false, + }), + viewsAndComponents.homePage + ); + + await createFile( + rootPath.concat("app/loading.tsx"), + viewsAndComponents.loadingPage + ); + + // add API routes + const apiRoute = generateApiRoutes(); + + await createFile( + formatFilePath(supabase.callbackApiRoute, { + removeExtension: false, + prefix: "rootPath", + }), + apiRoute + ); + + const authDirFiles = generateAuthDirFiles(orm); + + await createFile( + formatFilePath(shared.auth.authUtils, { + removeExtension: false, + prefix: "rootPath", + }), + authDirFiles.utilsTs + ); + + const helpers = generateSupabaseHelpers(); + + await createFile( + formatFilePath(supabase.libAuthSupabase, { + removeExtension: false, + prefix: "rootPath", + }), + helpers + ); + + const supabasePackages = ["@supabase/supabase-js", "@supabase/ssr"]; + const adapterPackage = orm === "drizzle" && ["postgres"]; + const packagesToInstall = [...supabasePackages, ...adapterPackage]; + + addToInstallList({ regular: packagesToInstall, dev: [] }); + + // add package to config + await addPackageToConfig("supabase"); + await updateConfigFile({ auth: "supabase" }); +}; diff --git a/src/commands/add/auth/supabase/utils.ts b/src/commands/add/auth/supabase/utils.ts new file mode 100644 index 00000000..f9d44f8a --- /dev/null +++ b/src/commands/add/auth/supabase/utils.ts @@ -0,0 +1,22 @@ +import { replaceFile } from "../../../../utils.js"; +import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; +import fs from "fs"; + +// TODO: Idk if this is necessary for Supabase +export const addToSupabasegnoredRoutes = async (newPath: string) => { + const { supabase } = getFilePaths(); + const initMWContent = "ignoredRoutes: ["; + const updatedMWContent = "ignoredRoutes: [" + ` "${newPath}", `; + const mwPath = formatFilePath(supabase.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); + await replaceFile(mwPath, newUtilsContent); + } else { + console.error("Middleware does not exist"); + } +}; diff --git a/src/commands/add/componentLib/shadcn-ui/index.ts b/src/commands/add/componentLib/shadcn-ui/index.ts index 4d705f5c..6e803d27 100644 --- a/src/commands/add/componentLib/shadcn-ui/index.ts +++ b/src/commands/add/componentLib/shadcn-ui/index.ts @@ -58,9 +58,9 @@ const manualInstallShadCn = async ( }); // add tailwind.config.ts - createFile("tailwind.config.ts", generateTailwindConfig(rootPath)); + await createFile("tailwind.config.ts", generateTailwindConfig(rootPath)); // update globals.css - replaceFile( + await replaceFile( formatFilePath(shared.init.globalCss, { prefix: "rootPath", removeExtension: false, @@ -68,25 +68,25 @@ const manualInstallShadCn = async ( generateGlobalsCss() ); // add cn helper (lib/utils.ts) - createFile(rootPath.concat("lib/utils.ts"), generateLibUtilsTs()); + await createFile(rootPath.concat("lib/utils.ts"), generateLibUtilsTs()); // create components.json - createFile("components.json", generateComponentsJson(rootPath)); + await createFile("components.json", generateComponentsJson(rootPath)); - createFile(rootPath.concat("app/loading.tsx"), generateLoadingPage()); + await createFile(rootPath.concat("app/loading.tsx"), generateLoadingPage()); // todo: install theme switcher // create theme provider - createFile( + await createFile( rootPath.concat("components/ThemeProvider.tsx"), generateThemeProvider() ); //generate theme toggler - createFile( + await createFile( rootPath.concat("components/ui/ThemeToggle.tsx"), generateThemeToggler() ); // add context provider to layout - addContextProviderToRootLayout("ThemeProvider"); + await addContextProviderToRootLayout("ThemeProvider"); }; export const installShadcnUI = async ( @@ -107,8 +107,8 @@ export const installShadcnUI = async ( if (existsSync(filePath)) { consola.info("Shadcn is already installed. Adding Shadcn UI to config..."); - addPackageToConfig("shadcn-ui"); - updateConfigFile({ componentLib: "shadcn-ui" }); + await addPackageToConfig("shadcn-ui"); + await updateConfigFile({ componentLib: "shadcn-ui" }); } else { try { // await execa(pmInstallCommand[preferredPackageManager], installArgs, { @@ -116,8 +116,8 @@ export const installShadcnUI = async ( // }); await manualInstallShadCn(preferredPackageManager, rootPath); // consola.success("Shadcn initialized successfully."); - addPackageToConfig("shadcn-ui"); - updateConfigFile({ componentLib: "shadcn-ui" }); + await addPackageToConfig("shadcn-ui"); + await updateConfigFile({ componentLib: "shadcn-ui" }); } catch (error) { consola.error(`Failed to initialize Shadcn: ${error.message}`); } @@ -139,12 +139,12 @@ export const installShadcnUI = async ( "dropdown-menu", ]); - addContextProviderToAppLayout("ShadcnToast"); + await addContextProviderToAppLayout("ShadcnToast"); // if (packages.includes("next-auth")) updateSignInComponentWithShadcnUI(); }; -export const updateSignInComponentWithShadcnUI = () => { +export const updateSignInComponentWithShadcnUI = async () => { const { hasSrc, alias } = readConfigFile(); const filepath = "components/auth/SignIn.tsx"; const updatedContent = `"use client"; @@ -171,5 +171,5 @@ export default function SignIn() { ); }`; - replaceFile(`${hasSrc ? "src/" : ""}${filepath}`, updatedContent); + await replaceFile(`${hasSrc ? "src/" : ""}${filepath}`, updatedContent); }; diff --git a/src/commands/add/index.ts b/src/commands/add/index.ts index 2d8fcca8..e8350b07 100644 --- a/src/commands/add/index.ts +++ b/src/commands/add/index.ts @@ -1,11 +1,5 @@ import { confirm } from "@inquirer/prompts"; -import { - createFile, - installPackages, - readConfigFile, - replaceFile, - updateConfigFile, -} from "../../utils.js"; +import { readConfigFile, replaceFile, updateConfigFile } from "../../utils.js"; import { addDrizzle } from "./orm/drizzle/index.js"; import { addNextAuth } from "./auth/next-auth/index.js"; import { addTrpc } from "./misc/trpc/index.js"; @@ -15,6 +9,7 @@ import { initProject } from "../init/index.js"; import { addPrisma } from "./orm/prisma/index.js"; import { ORMType, InitOptions } from "../../types.js"; import { addClerk } from "./auth/clerk/index.js"; +import { addSupabase } from "./auth/supabase/index.js"; import { addResend } from "./misc/resend/index.js"; import { addLucia } from "./auth/lucia/index.js"; import { createAccountSettingsPage } from "./auth/shared/index.js"; @@ -136,8 +131,8 @@ export const addPackage = async (options?: InitOptions) => { spinner.start(); spinner.text = "Beginning Configuration Process"; - createAppLayoutFile(); - createLandingPage(); + await createAppLayoutFile(); + await createLandingPage(); if (config.componentLib === undefined) { if (promptResponse.componentLib === "shadcn-ui") { @@ -154,20 +149,20 @@ export const addPackage = async (options?: InitOptions) => { // config.preferredPackageManager // ); // add to tailwindconfig - replaceFile("tailwind.config.ts", generateUpdatedTWConfig()); + await replaceFile("tailwind.config.ts", generateUpdatedTWConfig()); // add to globalcss colors - replaceFile( + await replaceFile( formatFilePath(shared.init.globalCss, { removeExtension: false, prefix: "rootPath", }), generateGlobalsCss() ); - updateConfigFile({ componentLib: null }); + await updateConfigFile({ componentLib: null }); } if (!config.t3) { - addContextProviderToAppLayout("Navbar"); + await addContextProviderToAppLayout("Navbar"); } } @@ -193,7 +188,7 @@ export const addPackage = async (options?: InitOptions) => { ); } if (promptResponse === null) - updateConfigFile({ orm: null, driver: null, provider: null }); + await updateConfigFile({ orm: null, driver: null, provider: null }); } // check if auth if (config.auth === undefined) { @@ -202,27 +197,28 @@ export const addPackage = async (options?: InitOptions) => { "Configuring " + promptResponse.auth[0].toUpperCase() + promptResponse.orm.slice(1); - if (promptResponse.auth) createAuthLayoutFile(); + if (promptResponse.auth) await createAuthLayoutFile(); if (promptResponse.auth === "next-auth") await addNextAuth(promptResponse.authProviders, options); if (promptResponse.auth === "clerk") await addClerk(); if (promptResponse.auth === "lucia") await addLucia(); if (promptResponse.auth === "kinde") await addKinde(); + if (promptResponse.auth === "supabase") await addSupabase(); if (!promptResponse.auth) { - replaceFile( + await replaceFile( formatFilePath(shared.init.dashboardRoute, { prefix: "rootPath", removeExtension: false, }), generateGenericHomepage() ); - updateConfigFile({ auth: null }); + await updateConfigFile({ auth: null }); } else { // add account page await createAccountSettingsPage(); } - addNavbarAndSettings(); - addAuthCheckToAppLayout(); + await addNavbarAndSettings(); + await addAuthCheckToAppLayout(); } // check if misc @@ -243,7 +239,7 @@ export const addPackage = async (options?: InitOptions) => { } if (config.t3 && config.auth === "next-auth") { - checkAndAddAuthUtils(); + await checkAndAddAuthUtils(); } spinner.succeed("Configuration complete"); diff --git a/src/commands/add/misc/defaultStyles/generators.ts b/src/commands/add/misc/defaultStyles/generators.ts index 665a2f1b..484f2f06 100644 --- a/src/commands/add/misc/defaultStyles/generators.ts +++ b/src/commands/add/misc/defaultStyles/generators.ts @@ -149,10 +149,10 @@ const defaultAppLayout = `export default async function AppLayout({ return (
{children}
) }`; -export const createAppLayoutFile = () => { +export const createAppLayoutFile = async () => { const { shared } = getFilePaths(); - createFile( + await createFile( formatFilePath(shared.init.appLayout, { prefix: "rootPath", removeExtension: false, @@ -176,10 +176,10 @@ export default async function AuthLayout({ } `; -export const createAuthLayoutFile = () => { +export const createAuthLayoutFile = async () => { const { shared } = getFilePaths(); - createFile( + await createFile( formatFilePath(shared.auth.layoutPage, { prefix: "rootPath", removeExtension: false, @@ -373,8 +373,8 @@ function MountainIcon(props: any) { } `; -export const createLandingPage = () => { - replaceFile( +export const createLandingPage = async () => { + await replaceFile( formatFilePath("app/page.tsx", { prefix: "rootPath", removeExtension: false, diff --git a/src/commands/add/misc/navbar/generators.ts b/src/commands/add/misc/navbar/generators.ts index e06ad365..aaf0ecd0 100644 --- a/src/commands/add/misc/navbar/generators.ts +++ b/src/commands/add/misc/navbar/generators.ts @@ -6,11 +6,11 @@ import { createFile, readConfigFile } from "../../../../utils.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; -export const addNavbarAndSettings = () => { +export const addNavbarAndSettings = async () => { const { componentLib } = readConfigFile(); // create navbar - createFile( + await createFile( formatFilePath("components/Navbar.tsx", { removeExtension: false, prefix: "rootPath", @@ -19,7 +19,7 @@ export const addNavbarAndSettings = () => { ); // create sidebar - createFile( + await createFile( formatFilePath("components/Sidebar.tsx", { removeExtension: false, prefix: "rootPath", @@ -28,7 +28,7 @@ export const addNavbarAndSettings = () => { ); // create sidebaritems - createFile( + await createFile( formatFilePath("components/SidebarItems.tsx", { removeExtension: false, prefix: "rootPath", @@ -37,7 +37,7 @@ export const addNavbarAndSettings = () => { ); // create sidebaritems - createFile( + await createFile( formatFilePath("config/nav.ts", { removeExtension: false, prefix: "rootPath", @@ -48,7 +48,7 @@ export const addNavbarAndSettings = () => { // create settings page if (componentLib === "shadcn-ui") - createFile( + await createFile( formatFilePath("app/(app)/settings/page.tsx", { removeExtension: false, prefix: "rootPath", diff --git a/src/commands/add/misc/resend/generators.ts b/src/commands/add/misc/resend/generators.ts index 1da91a9f..a850abac 100644 --- a/src/commands/add/misc/resend/generators.ts +++ b/src/commands/add/misc/resend/generators.ts @@ -173,7 +173,7 @@ const generateApiRoute = () => { const { resend } = getFilePaths(); return `import { EmailTemplate } from "${formatFilePath( resend.firstEmailComponent, - { prefix: "alias", removeExtension: true }, + { prefix: "alias", removeExtension: true } )}"; import { resend } from "${formatFilePath(resend.libEmailIndex, { prefix: "alias", @@ -212,7 +212,7 @@ const generateEmailIndexTs = () => { return `import { Resend } from "resend"; import { env } from "${formatFilePath(init.envMjs, { prefix: "alias", - removeExtension: true, + removeExtension: false, })}"; export const resend = new Resend(env.RESEND_API_KEY); diff --git a/src/commands/add/misc/resend/index.ts b/src/commands/add/misc/resend/index.ts index 0aad0fd9..70e30aad 100644 --- a/src/commands/add/misc/resend/index.ts +++ b/src/commands/add/misc/resend/index.ts @@ -1,8 +1,6 @@ -import { consola } from "consola"; import { addPackageToConfig, createFile, - installPackages, readConfigFile, } from "../../../../utils.js"; import { AvailablePackage } from "../../../../types.js"; @@ -19,6 +17,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { rootPath, } = readConfigFile(); const { resend } = getFilePaths(); + // const packages = packagesBeingInstalled.concat(installedPackages); // consola.start("Installing Resend..."); @@ -31,7 +30,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { } = resendGenerators; // 1. Add page at app/resend/page.tsx - createFile( + await createFile( formatFilePath(resend.resendPage, { prefix: "rootPath", removeExtension: false, @@ -40,7 +39,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { ); // 2. Add component at components/emails/FirstEmailTemplate.tsx - createFile( + await createFile( formatFilePath(resend.firstEmailComponent, { prefix: "rootPath", removeExtension: false, @@ -48,7 +47,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { generateEmailTemplateComponent() ); // 3. Add route handler at app/api/email/route.ts - createFile( + await createFile( formatFilePath(resend.emailApiRoute, { prefix: "rootPath", removeExtension: false, @@ -57,7 +56,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { ); // 4. Add email utils - createFile( + await createFile( formatFilePath(resend.emailUtils, { prefix: "rootPath", removeExtension: false, @@ -66,7 +65,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { ); // 5. add email index.ts - createFile( + await createFile( formatFilePath(resend.libEmailIndex, { prefix: "rootPath", removeExtension: false, @@ -75,7 +74,7 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { ); // 6. Add items to .env - addToDotEnv([{ key: "RESEND_API_KEY", value: "" }], rootPath, true); + await addToDotEnv([{ key: "RESEND_API_KEY", value: "" }], rootPath, true); // 7. Install packages (resend) // await installPackages( // { @@ -89,6 +88,6 @@ export const addResend = async (packagesBeingInstalled: AvailablePackage[]) => { if (orm === null) addToInstallList({ regular: ["zod", "@t3-oss/env-nextjs"], dev: [] }); - addPackageToConfig("resend"); + await addPackageToConfig("resend"); // consola.success("Resend successfully installed and configured."); }; diff --git a/src/commands/add/misc/stripe/index.ts b/src/commands/add/misc/stripe/index.ts index 5f1466a1..6c166911 100644 --- a/src/commands/add/misc/stripe/index.ts +++ b/src/commands/add/misc/stripe/index.ts @@ -16,8 +16,6 @@ import path from "path"; import { addPackageToConfig, createFile, - getFileLocations, - installPackages, readConfigFile, replaceFile, updateConfigFile, @@ -56,7 +54,6 @@ import { AuthSubTypeMapping, addToInstallList } from "../../utils.js"; export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { const { componentLib, - preferredPackageManager, rootPath, orm, driver, @@ -71,7 +68,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { if (orm === null || orm === undefined || driver === undefined) { consola.warn("You cannot install Stripe without an ORM installed."); - updateConfigFile({ orm: undefined }); + await updateConfigFile({ orm: undefined }); await addPackage(); return; } @@ -83,7 +80,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { const authUtilsExist = existsSync(authUtilsPath); if (!authUtilsExist) { - createFile(authUtilsPath, libAuthUtilsTsWithoutAuthOptions()); + await createFile(authUtilsPath, libAuthUtilsTsWithoutAuthOptions()); } } @@ -93,7 +90,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { // add attributes to usermodel if (orm === "prisma") { - addToPrismaSchema( + await addToPrismaSchema( `model Subscription { userId String @unique${ authSubtype !== "managed" @@ -115,7 +112,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { } } if (orm === "drizzle") { - createFile( + await createFile( formatFilePath(stripe.subscriptionSchema, { prefix: "rootPath", removeExtension: false, @@ -123,12 +120,12 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateSubscriptionsDrizzleSchema(driver, auth) ); if (t3) { - updateRootSchema("subscriptions"); + await updateRootSchema("subscriptions"); } } // create stripe/index file - createFile( + await createFile( formatFilePath(stripe.stripeIndex, { prefix: "rootPath", removeExtension: false, @@ -136,7 +133,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateStripeIndexTs() ); // create stripe/subscription file - createFile( + await createFile( formatFilePath(stripe.stripeSubscription, { prefix: "rootPath", removeExtension: false, @@ -144,7 +141,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateStripeSubscriptionTs() ); // create config/subscriptions.ts - createFile( + await createFile( formatFilePath(stripe.configSubscription, { prefix: "rootPath", removeExtension: false, @@ -152,7 +149,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateConfigSubscriptionsTs() ); // components: create billing card - createFile( + await createFile( formatFilePath(stripe.accountPlanSettingsComponent, { prefix: "rootPath", removeExtension: false, @@ -160,7 +157,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateBillingCard() ); // components: create manage subscription button - createFile( + await createFile( formatFilePath(stripe.billingManageSubscriptionComponent, { prefix: "rootPath", removeExtension: false, @@ -169,7 +166,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { ); // components: create success toast if (componentLib === "shadcn-ui") - createFile( + await createFile( formatFilePath(stripe.billingSuccessToast, { prefix: "rootPath", removeExtension: false, @@ -178,7 +175,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { ); // add billingcard to accountpage with billing card TODO - replaceFile( + await replaceFile( formatFilePath(shared.auth.accountPage, { prefix: "rootPath", removeExtension: false, @@ -186,7 +183,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { createAccountPage(true) ); // create account/billing/page.tsx - createFile( + await createFile( formatFilePath(stripe.accountBillingPage, { prefix: "rootPath", removeExtension: false, @@ -194,7 +191,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateBillingPage() ); // create api/webhooks/route.ts - createFile( + await createFile( formatFilePath(stripe.stripeWebhooksApiRoute, { prefix: "rootPath", removeExtension: false, @@ -202,7 +199,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { generateStripeWebhook() ); // create api/billing/manage-subscription/route.ts - createFile( + await createFile( formatFilePath(stripe.manageSubscriptionApiRoute, { prefix: "rootPath", removeExtension: false, @@ -213,7 +210,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { addUtilToUtilsTs(rootPath); // add to dotenv - addToDotEnv( + await addToDotEnv( [ { key: "STRIPE_SECRET_KEY", value: "" }, { key: "STRIPE_WEBHOOK_SECRET", value: "" }, @@ -226,7 +223,7 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { ); // misc script updates - addListenScriptToPackageJson(); + await addListenScriptToPackageJson(); // install packages // await installPackages( // { dev: "", regular: "stripe @stripe/stripe-js lucide-react" }, @@ -238,10 +235,10 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { dev: [], }); - addPackageToConfig("stripe"); + await addPackageToConfig("stripe"); if (packages.includes("trpc")) { - createFile( + await createFile( formatFilePath(stripe.accountRouterTrpc, { removeExtension: false, prefix: "rootPath", @@ -249,11 +246,11 @@ export const addStripe = async (packagesBeingInstalled: AvailablePackage[]) => { createAccountTRPCRouter() ); // add to main trpc router - updateTRPCRouter("account"); + await updateTRPCRouter("account"); } }; -const addListenScriptToPackageJson = () => { +const addListenScriptToPackageJson = async () => { // Define the path to package.json const packageJsonPath = path.resolve("package.json"); @@ -276,12 +273,12 @@ const addListenScriptToPackageJson = () => { const updatedPackageJsonData = JSON.stringify(packageJson, null, 2); // Write the updated content back to package.json - replaceFile(packageJsonPath, updatedPackageJsonData); + await replaceFile(packageJsonPath, updatedPackageJsonData); // consola.success("Stripe listen script added to package.json"); }; -const addUtilToUtilsTs = (rootPath: string) => { +const addUtilToUtilsTs = async (rootPath: string) => { const { shared } = getFilePaths(); const utilContentToAdd = `export function absoluteUrl(path: string) { return \`\${ @@ -297,9 +294,9 @@ const addUtilToUtilsTs = (rootPath: string) => { const utilsContent = fs.readFileSync(utilsPath, "utf-8"); if (!utilsContent.includes(utilContentToAdd)) { const newUtilsContent = utilsContent.concat(`\n${utilContentToAdd}`); - replaceFile(utilsPath, newUtilsContent); + await replaceFile(utilsPath, newUtilsContent); } else return; } else { - createFile(utilsPath, utilContentToAdd); + await createFile(utilsPath, utilContentToAdd); } }; diff --git a/src/commands/add/misc/trpc/index.ts b/src/commands/add/misc/trpc/index.ts index 7e025a57..8ff23ced 100644 --- a/src/commands/add/misc/trpc/index.ts +++ b/src/commands/add/misc/trpc/index.ts @@ -24,7 +24,7 @@ export const addTrpc = async () => { const { orm } = readConfigFile(); const { trpc } = getFilePaths(); // 1. Create lib/server/index.ts - createFile( + await createFile( formatFilePath(trpc.rootRouter, { prefix: "rootPath", removeExtension: false, @@ -33,7 +33,7 @@ export const addTrpc = async () => { ); // 2. create lib/server/trpc.ts - createFile( + await createFile( formatFilePath(trpc.serverTrpc, { prefix: "rootPath", removeExtension: false, @@ -42,7 +42,7 @@ export const addTrpc = async () => { ); // 3. create lib/server/router/ directory and maybe a users file // TODO : T3 COMPATABILITY - createFile( + await createFile( formatFilePath(`lib/server/routers/computers.ts`, { prefix: "rootPath", removeExtension: false, @@ -50,7 +50,7 @@ export const addTrpc = async () => { serverRouterComputersTs() ); // 4. create app/api/trpc/[trpc]/route.ts - createFile( + await createFile( formatFilePath(trpc.trpcApiRoute, { prefix: "rootPath", removeExtension: false, @@ -58,7 +58,7 @@ export const addTrpc = async () => { apiTrpcRouteTs() ); // 5. create lib/trpc/client.ts - createFile( + await createFile( formatFilePath(trpc.trpcClient, { prefix: "rootPath", removeExtension: false, @@ -66,7 +66,7 @@ export const addTrpc = async () => { libTrpcClientTs() ); // 6. create lib/trpc/Provider.tsx - createFile( + await createFile( formatFilePath(trpc.trpcProvider, { prefix: "rootPath", removeExtension: false, @@ -74,8 +74,8 @@ export const addTrpc = async () => { libTrpcProviderTsx() ); // 7. create lib/trpc/serverClient.ts -> updated to lib/trpc/api.ts using server invoker - // createFile(`${rootPath}/lib/trpc/serverClient.ts`, libTrpcServerClientTs()); - createFile( + // await createFile(`${rootPath}/lib/trpc/serverClient.ts`, libTrpcServerClientTs()); + await createFile( formatFilePath(trpc.trpcApiTs, { prefix: "rootPath", removeExtension: false, @@ -85,7 +85,7 @@ export const addTrpc = async () => { ); // 7.5. create context file and update to include context file above - createFile( + await createFile( formatFilePath(trpc.trpcContext, { prefix: "rootPath", removeExtension: false, @@ -94,7 +94,7 @@ export const addTrpc = async () => { ); // create trpc utils file lib/trpc/utils.ts - createFile( + await createFile( formatFilePath(trpc.trpcUtils, { prefix: "rootPath", removeExtension: false, @@ -127,10 +127,10 @@ export const addTrpc = async () => { }); if (orm === null) addToInstallList({ regular: ["zod"], dev: [] }); - addPackageToConfig("trpc"); + await addPackageToConfig("trpc"); // 9. Instruct user to add the to their root layout. - addContextProviderToAppLayout("TrpcProvider"); - // addToDotEnv( + await addContextProviderToAppLayout("TrpcProvider"); + // await addToDotEnv( // [ // { // key: "VERCEL_URL", diff --git a/src/commands/add/orm/drizzle/generators.ts b/src/commands/add/orm/drizzle/generators.ts index 21fa7146..e375a854 100644 --- a/src/commands/add/orm/drizzle/generators.ts +++ b/src/commands/add/orm/drizzle/generators.ts @@ -1,12 +1,6 @@ -import { consola } from "consola"; import fs from "fs"; import path from "path"; -import { - createFile, - installPackages, - readConfigFile, - replaceFile, -} from "../../../../utils.js"; +import { createFile, readConfigFile, replaceFile } from "../../../../utils.js"; import { DBProvider, DBType, @@ -18,10 +12,11 @@ import { formatFilePath, getDbIndexPath, getFilePaths, - removeFileExtension, } from "../../../filePaths/index.js"; import stripJsonComments from "strip-json-comments"; import { addToInstallList } from "../../utils.js"; +import { formatFileContentWithPrettier } from "../../../init/utils.js"; +// import consola from "consola"; type ConfigDriver = "pg" | "turso" | "libsql" | "mysql" | "better-sqlite"; @@ -38,13 +33,16 @@ const configDriverMappings = { turso: "turso", }; -export const createDrizzleConfig = (libPath: string, provider: DBProvider) => { +export const createDrizzleConfig = async ( + libPath: string, + provider: DBProvider +) => { const { shared: { init: { envMjs }, }, } = getFilePaths(); - createFile( + await createFile( "drizzle.config.ts", `import type { Config } from "drizzle-kit"; import { env } from "${formatFilePath(envMjs, { @@ -72,12 +70,11 @@ export default { ); }; -export const createIndexTs = (dbProvider: DBProvider) => { +export const createIndexTs = async (dbProvider: DBProvider) => { const { shared: { init: { envMjs }, }, - drizzle, } = getFilePaths(); const dbIndex = getDbIndexPath("drizzle"); let indexTS = ""; @@ -228,13 +225,13 @@ export const db = drizzle(sqlite); break; } - createFile( + await createFile( formatFilePath(dbIndex, { prefix: "rootPath", removeExtension: false }), indexTS ); }; -export const createMigrateTs = ( +export const createMigrateTs = async ( libPath: string, dbType: DBType, dbProvider: DBProvider @@ -424,13 +421,13 @@ runMigrate().catch((err) => { process.exit(1); });`; - createFile( + await createFile( formatFilePath(dbMigrate, { prefix: "rootPath", removeExtension: false }), template ); }; -export const createInitSchema = (libPath?: string, dbType?: DBType) => { +export const createInitSchema = async (libPath?: string, dbType?: DBType) => { const { packages, driver, rootPath } = readConfigFile(); const { shared: { @@ -518,10 +515,10 @@ export type NewComputer = z.infer; export type ComputerId = z.infer["id"];`; const finalDoc = `${sharedImports}\n${initModel}\n${sharedSchemas}`; - createFile(path, finalDoc); + await createFile(path, finalDoc); }; -export const addScriptsToPackageJson = ( +export const addScriptsToPackageJson = async ( libPath: string, driver: DBType, preferredPackageManager: PMType @@ -553,7 +550,10 @@ export const addScriptsToPackageJson = ( const updatedPackageJsonData = JSON.stringify(packageJson, null, 2); // Write the updated content back to package.json - fs.writeFileSync(packageJsonPath, updatedPackageJsonData); + fs.writeFileSync( + packageJsonPath, + await formatFileContentWithPrettier(updatedPackageJsonData, packageJsonPath) + ); // consola.success("Scripts added to package.json"); }; @@ -604,7 +604,7 @@ export const installDependencies = async ( } }; -export const createDotEnv = ( +export const createDotEnv = async ( orm: ORMType, preferredPackageManager: PMType, databaseUrl?: string, @@ -622,25 +622,32 @@ export const createDotEnv = ( const envPath = path.resolve(".env"); const envExists = fs.existsSync(envPath); if (!envExists) - createFile( + await createFile( ".env", `${ orm === "drizzle" && usingPlanetscale ? `# When using the PlanetScale driver with Drizzle, your connection string must end with ?ssl={"rejectUnauthorized":true} instead of ?sslaccept=strict.\n` : "" - }DATABASE_URL=${dburl}` + }DATABASE_URL=${dburl}`, + true ); const envmjsfilePath = formatFilePath(envMjs, { prefix: "rootPath", removeExtension: false, }); + const envMjsExists = fs.existsSync(envmjsfilePath); + if (!envMjsExists) - createFile(envmjsfilePath, generateEnvMjs(preferredPackageManager, orm)); + await createFile( + envmjsfilePath, + generateEnvMjs(preferredPackageManager, orm), + false + ); }; -export const addToDotEnv = ( +export const addToDotEnv = async ( items: DotEnvItem[], rootPathOld?: string, excludeDbUrlIfBlank = false @@ -651,17 +658,23 @@ export const addToDotEnv = ( init: { envMjs }, }, } = getFilePaths(); + // handling dotenv const envPath = path.resolve(".env"); const envExists = fs.existsSync(envPath); const newData = items.map((item) => `${item.key}=${item.value}`).join("\n"); + let content = newData; + if (envExists) { const envData = fs.readFileSync(envPath, "utf-8"); - const updatedEnvData = `${envData}\n${newData}`; - fs.writeFileSync(envPath, updatedEnvData); - } else { - fs.writeFileSync(envPath, newData); + content = `${envData}\n${newData}`; } + + fs.writeFileSync( + envPath, + await formatFileContentWithPrettier(content, envPath, true) + ); + // handling env.mjs const envmjsfilePath = formatFilePath(envMjs, { removeExtension: false, @@ -672,10 +685,11 @@ export const addToDotEnv = ( return; } if (!envMjsExists) - createFile( + await createFile( envmjsfilePath, generateEnvMjs(preferredPackageManager, orm, excludeDbUrlIfBlank) ); + let envmjsfileContents = fs.readFileSync(envmjsfilePath, "utf-8"); const formatItemForDotEnvMjs = (item: DotEnvItem) => @@ -700,20 +714,33 @@ export const addToDotEnv = ( .map(formatPublicItemForRuntimeEnv) .join("\n "); + const regex = /\s*},\n\s*client:\s*{\n/; + + const endofClientIndex = envmjsfileContents.match(regex); + const beforeClientBlockEnd = envmjsfileContents.lastIndexOf( + "\n", + endofClientIndex.index + ); + const endOfClientBlock = envmjsfileContents.slice(0, beforeClientBlockEnd); + const hasCommaBeforeClient = endOfClientBlock.endsWith(","); + // Construct the replacement string for both server and client sections - const replacementStr = ` ${serverItems}\n },\n client: {\n ${clientItems}`; + const replacementStr = `${hasCommaBeforeClient ? "" : ","}\n ${serverItems}\n },\n client: {\n ${clientItems}\n`; // Replace content using the known pattern - const regex = / },\n client: {\n/s; envmjsfileContents = envmjsfileContents.replace(regex, replacementStr); const runtimeEnvRegex = /experimental__runtimeEnv: {\n/s; envmjsfileContents = envmjsfileContents.replace( runtimeEnvRegex, - `experimental__runtimeEnv: {\n ${runtimeEnvItems}` + `experimental__runtimeEnv: {\n ${runtimeEnvItems}\n` ); + // Write the updated contents back to the file - fs.writeFileSync(envmjsfilePath, envmjsfileContents); + fs.writeFileSync( + envmjsfilePath, + await formatFileContentWithPrettier(envmjsfileContents, envmjsfilePath) + ); }; export async function updateTsConfigTarget() { @@ -721,7 +748,7 @@ export async function updateTsConfigTarget() { const tsConfigPath = path.join(process.cwd(), "tsconfig.json"); // Read the file - fs.readFile(tsConfigPath, "utf8", (err, data) => { + fs.readFile(tsConfigPath, "utf8", async (err, data) => { if (err) { console.error( `An error occurred while reading the tsconfig.json file: ${err}` @@ -740,14 +767,14 @@ export async function updateTsConfigTarget() { const updatedContent = JSON.stringify(tsConfig, null, 2); // 2 spaces indentation // Write the updated content back to the file - replaceFile(tsConfigPath, updatedContent); + await replaceFile(tsConfigPath, updatedContent); // consola.success( // "Updated tsconfig.json target to esnext to support Drizzle-Kit." // ); }); } -export function createQueriesAndMutationsFolders( +export async function createQueriesAndMutationsFolders( libPath: string, driver: DBType ) { @@ -837,14 +864,14 @@ export const deleteComputer = async (id: ComputerId) => { throw { error: message }; } };`; - createFile( + await createFile( formatFilePath("lib/api/computers/queries.ts", { removeExtension: false, prefix: "rootPath", }), query ); - createFile( + await createFile( formatFilePath("lib/api/computers/mutations.ts", { prefix: "rootPath", removeExtension: false, @@ -870,20 +897,19 @@ export const env = createEnv({ NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), - ${blank ? "// " : ""}DATABASE_URL: z.string().min(1), - + ${blank ? "// " : ""}DATABASE_URL: z.string().min(1) }, client: { - // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), + // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1) }, // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually // runtimeEnv: { // DATABASE_URL: process.env.DATABASE_URL, - // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY // }, // For Next.js >= 13.4.4, you only need to destructure client variables: experimental__runtimeEnv: { - // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY }, }); `; diff --git a/src/commands/add/orm/drizzle/index.ts b/src/commands/add/orm/drizzle/index.ts index fccfc7ad..8baa64ce 100644 --- a/src/commands/add/orm/drizzle/index.ts +++ b/src/commands/add/orm/drizzle/index.ts @@ -48,21 +48,21 @@ export const addDrizzle = async ( // create all the files here if (includeExampleModel) { - createInitSchema(libPath, dbType); - createQueriesAndMutationsFolders(libPath, dbType); + await createInitSchema(libPath, dbType); + await createQueriesAndMutationsFolders(libPath, dbType); } else { createFolder(`${hasSrc ? "src/" : ""}lib/db/schema`); createFolder(`${hasSrc ? "src/" : ""}lib/api`); } // dependent on dbtype and driver, create - createIndexTs(dbProvider); - createMigrateTs(libPath, dbType, dbProvider); - createDrizzleConfig(libPath, dbProvider); + await createIndexTs(dbProvider); + await createMigrateTs(libPath, dbType, dbProvider); + await createDrizzleConfig(libPath, dbProvider); // perhaps using push rather than migrate for sqlite? addScriptsToPackageJson(libPath, dbType, preferredPackageManager); - createDotEnv( + await createDotEnv( "drizzle", preferredPackageManager, databaseUrl, @@ -70,7 +70,7 @@ export const addDrizzle = async ( hasSrc ? "src/" : "" ); if (dbProvider === "vercel-pg") - addToDotEnv( + await addToDotEnv( [ { key: "POSTGRES_URL", value: "" }, { key: "POSTGRES_URL_NON_POOLING", value: "" }, @@ -82,13 +82,17 @@ export const addDrizzle = async ( rootPath ); if (dbProvider === "turso") - addToDotEnv([{ key: "DATABASE_AUTH_TOKEN", value: "" }], rootPath); + await addToDotEnv([{ key: "DATABASE_AUTH_TOKEN", value: "" }], rootPath); await updateTsConfigTarget(); - addNanoidToUtils(); + await addNanoidToUtils(); - updateConfigFile({ driver: dbType, provider: dbProvider, orm: "drizzle" }); + await updateConfigFile({ + driver: dbType, + provider: dbProvider, + orm: "drizzle", + }); await installDependencies(dbProvider, preferredPackageManager); - addPackageToConfig("drizzle"); + await addPackageToConfig("drizzle"); }; diff --git a/src/commands/add/orm/drizzle/utils.ts b/src/commands/add/orm/drizzle/utils.ts index ed9e3611..3efd70d2 100644 --- a/src/commands/add/orm/drizzle/utils.ts +++ b/src/commands/add/orm/drizzle/utils.ts @@ -3,7 +3,7 @@ import { createFile, readConfigFile, replaceFile } from "../../../../utils.js"; import { consola } from "consola"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; -export function addToDrizzleModel( +export async function addToDrizzleModel( modelName: string, attributesToAdd: string, additionalImports?: string[] @@ -31,7 +31,7 @@ export function addToDrizzleModel( '} from "drizzle-orm', `${additionalImports.map((i) => `, ${i}`)} } from "drizzle-orm` ); - replaceFile(pathToModel, newSchemaWithUpdatedImports); + await replaceFile(pathToModel, newSchemaWithUpdatedImports); consola.info("Updated Drizzle schema"); } } @@ -49,7 +49,7 @@ const getDrizzleModelStartAndEnd = (schema: string, modelName: string) => { return { modelStart, modelEnd, modelExists }; }; -export const addNanoidToUtils = () => { +export const addNanoidToUtils = async () => { const nanoidContent = `import { customAlphabet } from "nanoid"; export const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789");`; const { shared } = getFilePaths(); @@ -59,18 +59,18 @@ export const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789");`; }); const utilsExists = existsSync(utilsPath); if (!utilsExists) { - createFile(utilsPath, nanoidContent); + await createFile(utilsPath, nanoidContent); } else { const utilsContent = readFileSync(utilsPath, "utf-8"); const newContent = `${nanoidContent.split("\n")[0].trim()} ${utilsContent} ${nanoidContent.split("\n")[1].trim()} `; - replaceFile(utilsPath, newContent); + await replaceFile(utilsPath, newContent); } }; -export const checkTimestampsInUtils = () => { +export const checkTimestampsInUtils = async () => { const timestampsContent = `export const timestamps: { createdAt: true; updatedAt: true } = { createdAt: true, updatedAt: true, @@ -83,14 +83,14 @@ export const checkTimestampsInUtils = () => { }); const utilsExists = existsSync(utilsPath); if (!utilsExists) { - createFile(utilsPath, timestampsContent); + await createFile(utilsPath, timestampsContent); } else { const utilsContent = readFileSync(utilsPath, "utf-8"); if (utilsContent.indexOf(timestampsContent) === -1) { const newContent = `${utilsContent} -${timestampsContent} -`; - replaceFile(utilsPath, newContent); + ${timestampsContent} + `; + await replaceFile(utilsPath, newContent); } } }; diff --git a/src/commands/add/orm/prisma/index.ts b/src/commands/add/orm/prisma/index.ts index 0e7a54c0..f150a1ed 100644 --- a/src/commands/add/orm/prisma/index.ts +++ b/src/commands/add/orm/prisma/index.ts @@ -1,10 +1,8 @@ -import { confirm, select } from "@inquirer/prompts"; import { DBType, InitOptions } from "../../../../types.js"; import { addPackageToConfig, createFile, createFolder, - installPackages, readConfigFile, updateConfigFile, } from "../../../../utils.js"; @@ -39,12 +37,15 @@ export const addPrisma = async ( // if mysql, ask if planetscale if (dbType === "mysql") { // scaffold planetscale specific schema - createFile( + await createFile( `prisma/schema.prisma`, - generatePrismaSchema(dbType, initOptions.dbProvider === "planetscale") + await generatePrismaSchema( + dbType, + initOptions.dbProvider === "planetscale" + ) ); - updateConfigFile({ provider: "planetscale" }); - createDotEnv( + await updateConfigFile({ provider: "planetscale" }); + await createDotEnv( "prisma", preferredPackageManager, generateDbUrl(dbType), @@ -53,8 +54,11 @@ export const addPrisma = async ( ); } else { // create prisma/schema.prisma (with db type) - createFile(`prisma/schema.prisma`, generatePrismaSchema(dbType, false)); - createDotEnv( + await createFile( + `prisma/schema.prisma`, + await generatePrismaSchema(dbType, false) + ); + await createDotEnv( "prisma", preferredPackageManager, generateDbUrl(dbType), @@ -66,7 +70,7 @@ export const addPrisma = async ( // create .env with database_url // generate prisma global instance - createFile( + await createFile( formatFilePath(dbIndex, { prefix: "rootPath", removeExtension: false, @@ -80,7 +84,7 @@ export const addPrisma = async ( // create all the files here if (includeExampleModel) { - addToPrismaSchema( + await addToPrismaSchema( `model Computer { id String @id @default(cuid()) brand String @@ -89,17 +93,17 @@ export const addPrisma = async ( "Computer" ); // generate /lib/db/schema/computers.ts - createFile( + await createFile( `${rootPath}lib/db/schema/computers.ts`, generatePrismaComputerModel() ); // generate /lib/api/computers/queries.ts && /lib/api/computers/mutations.ts - createFile( + await createFile( `${rootPath}lib/api/computers/queries.ts`, generatePrismaComputerQueries() ); - createFile( + await createFile( `${rootPath}lib/api/computers/mutations.ts`, generatePrismaComputerMutations() ); @@ -108,7 +112,7 @@ export const addPrisma = async ( createFolder(`${hasSrc ? "src/" : ""}lib/api`); } - addScriptsToPackageJsonForPrisma(dbType); + await addScriptsToPackageJsonForPrisma(dbType); // install packages: regular: [] dev: [prisma, zod-prisma] // await installPackages( @@ -123,8 +127,8 @@ export const addPrisma = async ( // run prisma generate if (includeExampleModel) await prismaGenerate(preferredPackageManager); - addPackageToConfig("prisma"); - updateConfigFile({ orm: "prisma", driver: dbType }); + await addPackageToConfig("prisma"); + await updateConfigFile({ orm: "prisma", driver: dbType }); // consola.success("Prisma has been added to your project!"); }; diff --git a/src/commands/add/orm/prisma/utils.ts b/src/commands/add/orm/prisma/utils.ts index 18aeda3f..1eb78ed6 100644 --- a/src/commands/add/orm/prisma/utils.ts +++ b/src/commands/add/orm/prisma/utils.ts @@ -1,6 +1,7 @@ import path from "path"; import fs from "fs"; import { DBType } from "../../../../types.js"; +import { formatFileContentWithPrettier } from "../../../init/utils.js"; export const prismaDbTypeMappings: { [key in DBType]: string } = { pg: "postgresql", @@ -8,7 +9,7 @@ export const prismaDbTypeMappings: { [key in DBType]: string } = { sqlite: "sqlite", }; -export const addScriptsToPackageJsonForPrisma = (driver: DBType) => { +export const addScriptsToPackageJsonForPrisma = async (driver: DBType) => { // Define the path to package.json const packageJsonPath = path.resolve("package.json"); @@ -33,7 +34,10 @@ export const addScriptsToPackageJsonForPrisma = (driver: DBType) => { const updatedPackageJsonData = JSON.stringify(packageJson, null, 2); // Write the updated content back to package.json - fs.writeFileSync(packageJsonPath, updatedPackageJsonData); + fs.writeFileSync( + packageJsonPath, + await formatFileContentWithPrettier(updatedPackageJsonData, packageJsonPath) + ); // consola.success("Scripts added to package.json"); }; diff --git a/src/commands/add/orm/utils.ts b/src/commands/add/orm/utils.ts index d092612c..572fc2c1 100644 --- a/src/commands/add/orm/utils.ts +++ b/src/commands/add/orm/utils.ts @@ -72,6 +72,6 @@ export async function updateTsConfigPrismaTypeAlias() { const updatedContent = JSON.stringify(tsConfig, null, 2); // 2 spaces indentation // Write the updated content back to the file - replaceFile(tsConfigPath, updatedContent); + await replaceFile(tsConfigPath, updatedContent); // consola.success("Updated tsconfig.json to support zod-prisma type alias."); } diff --git a/src/commands/add/utils.ts b/src/commands/add/utils.ts index b60d360a..d128e6f1 100644 --- a/src/commands/add/utils.ts +++ b/src/commands/add/utils.ts @@ -35,6 +35,7 @@ export const Packages: { { name: "Clerk", value: "clerk" }, { name: "Lucia", value: "lucia" }, { name: "Kinde", value: "kinde" }, + { name: "Supabase", value: "supabase" }, ], misc: [ { name: "TRPC", value: "trpc" }, @@ -44,7 +45,9 @@ export const Packages: { componentLib: [{ name: "Shadcn UI (with next-themes)", value: "shadcn-ui" }], }; -export const addContextProviderToRootLayout = (provider: "ThemeProvider") => { +export const addContextProviderToRootLayout = async ( + provider: "ThemeProvider" +) => { const { hasSrc, alias } = readConfigFile(); const path = `${hasSrc ? "src/" : ""}app/layout.tsx`; @@ -87,10 +90,10 @@ export const addContextProviderToRootLayout = (provider: "ThemeProvider") => { searchValue, replacementText ); - replaceFile(path, newLayoutContent); + await replaceFile(path, newLayoutContent); }; -export const addAuthCheckToAppLayout = () => { +export const addAuthCheckToAppLayout = async () => { const { hasSrc } = readConfigFile(); const path = `${hasSrc ? "src/" : ""}app/(app)/layout.tsx`; const { shared } = getFilePaths(); @@ -109,9 +112,9 @@ export const addAuthCheckToAppLayout = () => { `; const newText = importStatement + fileContent.replace(searchText, replacementText); - replaceFile(path, newText); + await replaceFile(path, newText); }; -export const addContextProviderToAppLayout = ( +export const addContextProviderToAppLayout = async ( provider: | "NextAuthProvider" | "TrpcProvider" @@ -205,10 +208,10 @@ export const addContextProviderToAppLayout = ( searchValue, replacementText ); - replaceFile(path, newLayoutContent); + await replaceFile(path, newLayoutContent); }; -export const addContextProviderToAuthLayout = ( +export const addContextProviderToAuthLayout = async ( provider: | "NextAuthProvider" | "TrpcProvider" @@ -279,7 +282,7 @@ export const addContextProviderToAuthLayout = ( searchValue, replacementText ); - replaceFile(path, newLayoutContent); + await replaceFile(path, newLayoutContent); }; export const AuthSubTypeMapping: Record = { @@ -287,6 +290,7 @@ export const AuthSubTypeMapping: Record = { kinde: "managed", "next-auth": "self-hosted", lucia: "managed", + supabase: "managed", }; const installList: { regular: string[]; dev: string[] } = { @@ -368,6 +372,9 @@ export const printNextSteps = ( ...(promptResponses.auth === "clerk" ? [`${chalk.underline("Authentication")}: Clerk`] : []), + ...(promptResponses.auth === "supabase" + ? [`${chalk.underline("Authentication")}: Supabase`] + : []), ...(promptResponses.auth === "lucia" ? [`${chalk.underline("Authentication")}: Lucia`] : []), diff --git a/src/commands/filePaths/index.ts b/src/commands/filePaths/index.ts index 6e1ecede..de1fbe5c 100644 --- a/src/commands/filePaths/index.ts +++ b/src/commands/filePaths/index.ts @@ -65,6 +65,15 @@ export const paths: { t3: Paths; normal: Paths } = { authFormComponent: "components/auth/Form.tsx", signOutButtonComponent: "components/auth/SignOutBtn.tsx", }, + supabase: { + authFormComponent: "components/auth/Form.tsx", + signInPage: "app/(auth)/sign-in/page.tsx", + signUpPage: "app/(auth)/sign-up/page.tsx", + signOutButtonComponent: "components/auth/SignOutBtn.tsx", + callbackApiRoute: "app/api/auth/callback/route.ts", + libAuthSupabase: "lib/supabase/auth/index.ts", + middleware: "middleware.ts", + }, kinde: { routeHandler: "app/api/auth/[kindeAuth]/route.ts", signInPage: "app/(auth)/sign-in/page.tsx", @@ -162,6 +171,15 @@ export const paths: { t3: Paths; normal: Paths } = { authFormComponent: "components/auth/Form.tsx", signOutButtonComponent: "components/auth/SignOutBtn.tsx", }, + supabase: { + authFormComponent: "components/auth/Form.tsx", + signInPage: "app/(auth)/sign-in/page.tsx", + signUpPage: "app/(auth)/sign-up/page.tsx", + signOutButtonComponent: "components/auth/SignOutBtn.tsx", + callbackApiRoute: "app/api/auth/callback/route.ts", + libAuthSupabase: "lib/supabase/auth/index.ts", + middleware: "middleware.ts", + }, kinde: { routeHandler: "app/api/auth/[kindeAuth]/route.ts", signInPage: "app/(auth)/sign-in/page.tsx", diff --git a/src/commands/filePaths/types.d.ts b/src/commands/filePaths/types.d.ts index efb36d1f..ada3f795 100644 --- a/src/commands/filePaths/types.d.ts +++ b/src/commands/filePaths/types.d.ts @@ -57,6 +57,15 @@ export type Paths = { libAuthLucia: string; signOutButtonComponent: string; }; + supabase: { + signInPage: string; + signUpPage: string; + authFormComponent: string; + callbackApiRoute: string; + libAuthSupabase: string; + signOutButtonComponent: string; + middleware: string; + }; kinde: { routeHandler: string; signInPage: string; diff --git a/src/commands/generate/generators/apiRoute.ts b/src/commands/generate/generators/apiRoute.ts index 4c6cd21d..0db519ac 100644 --- a/src/commands/generate/generators/apiRoute.ts +++ b/src/commands/generate/generators/apiRoute.ts @@ -4,13 +4,13 @@ import { formatFilePath, getFilePaths } from "../../filePaths/index.js"; import { Schema } from "../types.js"; import { formatTableName, toCamelCase } from "../utils.js"; -export const scaffoldAPIRoute = (schema: Schema) => { +export const scaffoldAPIRoute = async (schema: Schema) => { const { hasSrc, driver } = readConfigFile(); const { tableName } = schema; const path = `${hasSrc ? "src/" : ""}app/api/${toCamelCase( tableName )}/route.ts`; - createFile(path, generateRouteContent(schema, driver)); + await createFile(path, generateRouteContent(schema, driver)); }; const generateRouteContent = (schema: Schema, driver: DBType) => { diff --git a/src/commands/generate/generators/model/index.ts b/src/commands/generate/generators/model/index.ts index 2d170296..b0eb9db1 100644 --- a/src/commands/generate/generators/model/index.ts +++ b/src/commands/generate/generators/model/index.ts @@ -1,8 +1,15 @@ -import { consola } from "consola"; +// import { consola } from "consola"; import { DBType } from "../../../../types.js"; -import { createFile, readConfigFile, replaceFile } from "../../../../utils.js"; +import { + createFile, + readConfigFile, + // replaceFile +} from "../../../../utils.js"; import { prismaFormat, prismaGenerate } from "../../../add/orm/utils.js"; -import { ExtendedSchema, Schema } from "../../types.js"; +import { + ExtendedSchema, + // Schema +} from "../../types.js"; import { toCamelCase } from "../../utils.js"; import { generateMutationContent } from "./mutations/index.js"; import { generateQueryContent } from "./queries/index.js"; @@ -12,7 +19,7 @@ import { generateServiceFileNames, getFilePaths, } from "../../../filePaths/index.js"; -import { existsSync, readFileSync } from "fs"; +// import { existsSync, readFileSync } from "fs"; import { updateRootSchema } from "./utils.js"; export async function scaffoldModel( @@ -22,17 +29,20 @@ export async function scaffoldModel( ) { const { tableName } = schema; const { orm, preferredPackageManager, driver, t3 } = readConfigFile(); - const { shared, drizzle } = getFilePaths(); + const { + shared, + // drizzle + } = getFilePaths(); const serviceFileNames = generateServiceFileNames(toCamelCase(tableName)); const modelPath = `${formatFilePath(shared.orm.schemaDir, { prefix: "rootPath", removeExtension: false, })}/${toCamelCase(tableName)}.ts`; - createFile(modelPath, generateModelContent(schema, dbType)); + await createFile(modelPath, await generateModelContent(schema, dbType)); if (t3 && orm === "drizzle") { - updateRootSchema(tableName); + await updateRootSchema(tableName); } if (orm === "prisma") { @@ -41,10 +51,13 @@ export async function scaffoldModel( } // create queryFile - createFile(serviceFileNames.queriesPath, generateQueryContent(schema, orm)); + await createFile( + serviceFileNames.queriesPath, + generateQueryContent(schema, orm) + ); // create mutationFile - createFile( + await createFile( serviceFileNames.mutationsPath, generateMutationContent(schema, driver, orm) ); diff --git a/src/commands/generate/generators/model/schema/index.ts b/src/commands/generate/generators/model/schema/index.ts index 53f03408..93441256 100644 --- a/src/commands/generate/generators/model/schema/index.ts +++ b/src/commands/generate/generators/model/schema/index.ts @@ -300,7 +300,7 @@ const generateIndexFields = ( .join("\n ")}`; }; -const generatePrismaSchema = ( +const generatePrismaSchema = async ( schema: Schema, mappings: TypeMap, zodSchemas: string, @@ -332,22 +332,23 @@ const generatePrismaSchema = ( schema.includeTimestamps ? generateTimestampFieldsPrisma() : "" } }`; - addToPrismaSchema(prismaSchemaContent, tableNameSingularCapitalised); + await addToPrismaSchema(prismaSchemaContent, tableNameSingularCapitalised); if (schema.belongsToUser && authSubtype === "self-hosted") - addToPrismaModel( + await addToPrismaModel( "User", `${tableNameCamelCase} ${tableNameSingularCapitalised}[]` ); - relations.forEach((relation) => { + relations.forEach(async (relation) => { const { references } = relation; const { tableNameSingularCapitalised: singularCapitalised } = formatTableName(references); - addToPrismaModel( + await addToPrismaModel( singularCapitalised, `${tableNameCamelCase} ${tableNameSingularCapitalised}[]` ); }); + const importStatement = generateImportStatement( "prisma", schema, @@ -358,11 +359,11 @@ const generatePrismaSchema = ( return `${importStatement}\n\n${zodSchemas}`; }; -export function generateModelContent(schema: Schema, dbType: DBType) { +export async function generateModelContent(schema: Schema, dbType: DBType) { const { provider, orm, auth } = readConfigFile(); const mappings = createOrmMappings()[orm][dbType]; const zodSchemas = createZodSchemas(schema, orm); - if (schema.includeTimestamps) checkTimestampsInUtils(); + if (schema.includeTimestamps) await checkTimestampsInUtils(); if (orm === "drizzle") { return generateDrizzleSchema( @@ -375,7 +376,7 @@ export function generateModelContent(schema: Schema, dbType: DBType) { ); } if (orm === "prisma") { - return generatePrismaSchema( + return await generatePrismaSchema( schema, mappings, zodSchemas, diff --git a/src/commands/generate/generators/model/utils.ts b/src/commands/generate/generators/model/utils.ts index 28157713..a40c13fd 100644 --- a/src/commands/generate/generators/model/utils.ts +++ b/src/commands/generate/generators/model/utils.ts @@ -142,7 +142,7 @@ export const authForWhereClausePrisma = (belongsToUser: boolean) => { return belongsToUser ? ", userId: session?.user.id!" : ""; }; -export const updateRootSchema = ( +export const updateRootSchema = async ( tableName: string, usingAuth?: boolean, auth?: AuthType @@ -163,6 +163,8 @@ export const updateRootSchema = ( break; case "kinde": break; + case "supabase": + break; case "lucia": tableNames = "keys, users, sessions"; break; @@ -192,10 +194,10 @@ export const updateRootSchema = ( const afterImport = rootSchemaWithNewExport.slice(nextLineAfterLastImport); const withNewImport = `${beforeImport}${newImportStatement}${afterImport}`; - replaceFile(rootSchemaPath, withNewImport); + await replaceFile(rootSchemaPath, withNewImport); } else { // if not create schema/_root.ts -> then do same import as above - createFile( + await createFile( rootSchemaPath, `${newImportStatement} @@ -216,7 +218,7 @@ import * as extended from "~/server/db/schema/_root";` `{ schema }`, `{ schema: { ...schema, ...extended } }` ); - replaceFile(indexDbPath, updatedContentsFinal); + await replaceFile(indexDbPath, updatedContentsFinal); // update drizzle config file to add all in server/db/* const drizzleConfigPath = "drizzle.config.ts"; @@ -225,6 +227,6 @@ import * as extended from "~/server/db/schema/_root";` `schema: "./src/server/db/schema.ts",`, `schema: "./src/server/db/*",` ); - replaceFile(drizzleConfigPath, updatedContents); + await replaceFile(drizzleConfigPath, updatedContents); } }; diff --git a/src/commands/generate/generators/model/views-shared.ts b/src/commands/generate/generators/model/views-shared.ts index df3d1038..f4d3313d 100644 --- a/src/commands/generate/generators/model/views-shared.ts +++ b/src/commands/generate/generators/model/views-shared.ts @@ -3,7 +3,7 @@ import { formatFilePath } from "../../../filePaths/index.js"; import { formatTableName } from "../../utils.js"; import { replaceFile } from "../../../../utils.js"; -export const addLinkToSidebar = (tableName: string) => { +export const addLinkToSidebar = async (tableName: string) => { const { tableNameKebabCase, tableNameNormalEnglishCapitalised } = formatTableName(tableName); const sidebarConfigPath = formatFilePath("config/nav.ts", { @@ -48,5 +48,5 @@ export const addLinkToSidebar = (tableName: string) => { `; newContent = configContents.replace(searchQuery, replacement); } - replaceFile(sidebarConfigPath, newContent); + await replaceFile(sidebarConfigPath, newContent); }; diff --git a/src/commands/generate/generators/serverActions.ts b/src/commands/generate/generators/serverActions.ts index 96d18702..b324010e 100644 --- a/src/commands/generate/generators/serverActions.ts +++ b/src/commands/generate/generators/serverActions.ts @@ -3,14 +3,14 @@ import { formatFilePath } from "../../filePaths/index.js"; import { Schema } from "../types.js"; import { formatTableName } from "../utils.js"; -export const scaffoldServerActions = (schema: Schema) => { +export const scaffoldServerActions = async (schema: Schema) => { const { tableName } = schema; const { tableNameCamelCase } = formatTableName(tableName); const path = formatFilePath(`lib/actions/${tableNameCamelCase}.ts`, { prefix: "rootPath", removeExtension: false, }); - createFile(path, generateRouteContent(schema)); + await createFile(path, generateRouteContent(schema)); }; const generateRouteContent = (schema: Schema) => { diff --git a/src/commands/generate/generators/trpcRoute.ts b/src/commands/generate/generators/trpcRoute.ts index 0e8a12cf..3a2fe0ac 100644 --- a/src/commands/generate/generators/trpcRoute.ts +++ b/src/commands/generate/generators/trpcRoute.ts @@ -20,9 +20,9 @@ export const scaffoldTRPCRoute = async (schema: Schema) => { prefix: "rootPath", removeExtension: false, })}/${tableNameCamelCase}.ts`; - createFile(path, generateRouteContent(schema)); + await createFile(path, generateRouteContent(schema)); - updateTRPCRouter(tableNameCamelCase); + await updateTRPCRouter(tableNameCamelCase); }; // function updateTRPCRouterOld(routerName: string): void { @@ -46,13 +46,13 @@ export const scaffoldTRPCRoute = async (schema: Schema) => { // const beforeRouter = modifiedImportContent.slice(0, beforeRouterBlockEnd); // const afterRouter = modifiedImportContent.slice(beforeRouterBlockEnd); // const modifiedRouterContent = `${beforeRouter}\n ${routerName}: ${routerName}Router,${afterRouter}`; -// replaceFile(filePath, modifiedRouterContent); +// await replaceFile(filePath, modifiedRouterContent); // // consola.success(`Added ${routerName} router to root router successfully.`); // } -export function updateTRPCRouter(routerName: string): void { - const { hasSrc, t3, rootPath } = readConfigFile(); +export async function updateTRPCRouter(routerName: string) { + const { t3, rootPath } = readConfigFile(); const { trpcRootDir, rootRouterRelativePath } = getFileLocations(); const filePath = rootPath.concat(`${trpcRootDir}${rootRouterRelativePath}`); @@ -98,18 +98,19 @@ export function updateTRPCRouter(routerName: string): void { modifiedRouterContent = withNewImport.replace(oldRouter, newRouter); } else { // Regular multi-line router - const routerBlockEnd = withNewImport.indexOf("});"); + const routerBlockEnd = withNewImport.indexOf("})"); const beforeRouterBlockEnd = withNewImport.lastIndexOf( "\n", routerBlockEnd ); const beforeRouter = withNewImport.slice(0, beforeRouterBlockEnd); + const hasCommaBefore = beforeRouter.endsWith(","); const afterRouter = withNewImport.slice(beforeRouterBlockEnd); const newRouterStatement = `\n ${routerName}: ${routerName}Router,`; - modifiedRouterContent = `${beforeRouter}${newRouterStatement}${afterRouter}`; + modifiedRouterContent = `${beforeRouter}${hasCommaBefore ? "" : ","}${newRouterStatement}${afterRouter}`; } - replaceFile(filePath, modifiedRouterContent); + await replaceFile(filePath, modifiedRouterContent); // consola.success( // `Added '${routerName}' router to the root tRPC router successfully.` diff --git a/src/commands/generate/generators/views-with-server-actions.ts b/src/commands/generate/generators/views-with-server-actions.ts index 56e69c40..493a7628 100644 --- a/src/commands/generate/generators/views-with-server-actions.ts +++ b/src/commands/generate/generators/views-with-server-actions.ts @@ -51,19 +51,19 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( // require trpc for these views if (packages.includes("shadcn-ui")) { // check if utils correct - checkUtils(); + await checkUtils(); // check if modal exists components/shared/Modal.tsx - checkModalExists(); + await checkModalExists(); // check if modal exists components/shared/BackButton.tsx - checkBackButtonExists(); + await checkBackButtonExists(); // check if vfh exists - checkValidatedForm(); + await checkValidatedForm(); // create view - tableName/page.tsx - createFile( + await createFile( formatFilePath(`app/(app)/${tableNameKebabCase}/page.tsx`, { prefix: "rootPath", removeExtension: false, @@ -72,7 +72,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( ); // create components/tableName/TableNameList.tsx - createFile( + await createFile( formatFilePath( `components/${tableNameCamelCase}/${tableNameSingularCapitalised}List.tsx`, { removeExtension: false, prefix: "rootPath" } @@ -81,7 +81,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( ); // create components/tableName/TableNameForm.tsx - createFile( + await createFile( formatFilePath( `components/${tableNameCamelCase}/${tableNameSingularCapitalised}Form.tsx`, { prefix: "rootPath", removeExtension: false } @@ -90,7 +90,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( ); // create optimisticEntity - createFile( + await createFile( formatFilePath( `app/(app)/${tableNameKebabCase}/useOptimistic${tableNameCapitalised}.tsx`, { @@ -102,7 +102,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( ); // create tableName/[id]/page.tsx - createFile( + await createFile( formatFilePath( `app/(app)/${tableNameKebabCase}/[${tableNameSingular}Id]/page.tsx`, { removeExtension: false, prefix: "rootPath" } @@ -118,7 +118,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( return `${parent.tableNameKebabCase}/[${parent.tableNameSingular}Id]/`; }) .join(""); - createFile( + await createFile( formatFilePath( `app/(app)/${baseUrl}${tableNameKebabCase}/[${tableNameSingular}Id]/page.tsx`, { removeExtension: false, prefix: "rootPath" } @@ -127,7 +127,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( ); } // create tableName/[id]/OptimisticEntity.tsx - createFile( + await createFile( formatFilePath( `app/(app)/${tableNameKebabCase}/[${tableNameSingular}Id]/Optimistic${tableNameSingularCapitalised}.tsx`, { removeExtension: false, prefix: "rootPath" } @@ -157,7 +157,7 @@ export const scaffoldViewsAndComponentsWithServerActions = async ( // await installShadcnUIComponents(baseComponents); addToShadcnComponentList(baseComponents); } else { - addPackage(); + await addPackage(); } }; @@ -1068,7 +1068,7 @@ const SaveButton = ({ `; }; -const checkUtils = () => { +const checkUtils = async () => { const utilTsPath = formatFilePath("lib/utils.ts", { removeExtension: false, prefix: "rootPath", @@ -1085,15 +1085,13 @@ export type OptimisticAction = { data: T; }; `; - if (utilTsContent.includes(contentToQuery)) { - return; - } else { - const newUtilTs = utilTsContent.concat("\n\n".concat(contentToQuery)); - replaceFile(utilTsPath, newUtilTs); - } + if (utilTsContent.includes(contentToQuery)) return; + + const newUtilTs = utilTsContent.concat("\n\n".concat(contentToQuery)); + await replaceFile(utilTsPath, newUtilTs); }; -const checkModalExists = () => { +const checkModalExists = async () => { const modalPath = formatFilePath("components/shared/Modal.tsx", { removeExtension: false, prefix: "rootPath", @@ -1134,11 +1132,11 @@ export default function Modal({ ); } `; - createFile(modalPath, modalContents); + await createFile(modalPath, modalContents); } }; -const checkBackButtonExists = () => { +const checkBackButtonExists = async () => { const bbPath = formatFilePath("components/shared/BackButton.tsx", { removeExtension: false, prefix: "rootPath", @@ -1184,7 +1182,7 @@ export function BackButton({ ); } `; - createFile(bbPath, bbContents); + await createFile(bbPath, bbContents); } }; @@ -1309,7 +1307,7 @@ export const useOptimistic${tableNamePluralCapitalised} = ( `; }; -const checkValidatedForm = () => { +const checkValidatedForm = async () => { const vfhPath = formatFilePath("lib/hooks/useValidatedForm.tsx", { prefix: "rootPath", removeExtension: false, @@ -1356,8 +1354,9 @@ export function useValidatedForm(insertEntityZodSchema: ZodSchema) { `; const vfhExists = existsSync(vfhPath); + if (!vfhExists) { - createFile(vfhPath, vfhContent); + await createFile(vfhPath, vfhContent); } }; diff --git a/src/commands/generate/generators/views.ts b/src/commands/generate/generators/views.ts index 6a356dcf..4afb135a 100644 --- a/src/commands/generate/generators/views.ts +++ b/src/commands/generate/generators/views.ts @@ -1,11 +1,6 @@ import { DBField } from "../../../types.js"; import pluralize from "pluralize"; -import { - createFile, - getFileContents, - installShadcnUIComponents, - readConfigFile, -} from "../../../utils.js"; +import { createFile, getFileContents, readConfigFile } from "../../../utils.js"; import { addPackage } from "../../add/index.js"; import { formatFilePath, getFilePaths } from "../../filePaths/index.js"; import { Schema } from "../types.js"; @@ -28,26 +23,26 @@ export const scaffoldViewsAndComponents = async (schema: Schema) => { if (packages.includes("trpc") && packages.includes("shadcn-ui")) { // create view - tableName/page.tsx const rootPath = hasSrc ? "src/" : ""; - createFile( + await createFile( rootPath.concat(`app/(app)/${tableNameKebabCase}/page.tsx`), generateView(schema) ); // create components/tableName/TableNameList.tsx - createFile( + await createFile( rootPath.concat( `components/${tableNameCamelCase}/${tableNameSingularCapitalised}List.tsx` ), createListComponent(schema) ); // create components/tableName/TableNameForm.tsx - createFile( + await createFile( rootPath.concat( `components/${tableNameCamelCase}/${tableNameSingularCapitalised}Form.tsx` ), createFormComponent(schema) ); // create components/tableName/TableNameModal.tsx - createFile( + await createFile( rootPath.concat( `components/${tableNameCamelCase}/${tableNameSingularCapitalised}Modal.tsx` ), diff --git a/src/commands/generate/index.ts b/src/commands/generate/index.ts index 01ad797e..41b144be 100644 --- a/src/commands/generate/index.ts +++ b/src/commands/generate/index.ts @@ -325,7 +325,7 @@ async function askForChildModel(parentModel: string) { }); } -export function preBuild() { +export async function preBuild() { const config = readConfigFile(); if (!config) { @@ -334,7 +334,7 @@ export function preBuild() { return false; } - if (config.orm === undefined) updateConfigFileAfterUpdate(); + if (config.orm === undefined) await updateConfigFileAfterUpdate(); return true; } @@ -449,23 +449,24 @@ async function generateResources( message: `Would you like to add a link to '${tnEnglish}' in your sidebar?`, default: true, }); - if (addToSidebar) addLinkToSidebar(schema.tableName); + if (addToSidebar) await addLinkToSidebar(schema.tableName); } if (resourceType.includes("model")) scaffoldModel(schema, config.driver, config.hasSrc); - if (resourceType.includes("api_route")) scaffoldAPIRoute(schema); - if (resourceType.includes("trpc_route")) scaffoldTRPCRoute(schema); + if (resourceType.includes("api_route")) await scaffoldAPIRoute(schema); + if (resourceType.includes("trpc_route")) await scaffoldTRPCRoute(schema); if (resourceType.includes("views_and_components_trpc")) - scaffoldViewsAndComponents(schema); - if (resourceType.includes("server_actions")) scaffoldServerActions(schema); + await scaffoldViewsAndComponents(schema); + if (resourceType.includes("server_actions")) + await scaffoldServerActions(schema); if (resourceType.includes("views_and_components_server_actions")) - scaffoldViewsAndComponentsWithServerActions(schema); + await scaffoldViewsAndComponentsWithServerActions(schema); await installShadcnComponentList(); } export async function buildSchema() { - const ready = preBuild(); + const ready = await preBuild(); if (!ready) return; const config = readConfigFile(); diff --git a/src/commands/generate/utils.ts b/src/commands/generate/utils.ts index a56ec023..80300424 100644 --- a/src/commands/generate/utils.ts +++ b/src/commands/generate/utils.ts @@ -327,7 +327,7 @@ export function getCurrentSchemas() { } } -export const addToPrismaSchema = (schema: string, modelName: string) => { +export const addToPrismaSchema = async (schema: string, modelName: string) => { const schemaPath = "prisma/schema.prisma"; const schemaExists = existsSync(schemaPath); if (schemaExists) { @@ -343,11 +343,11 @@ export const addToPrismaSchema = (schema: string, modelName: string) => { schemaContents.slice(0, modelStart) + schema + schemaContents.slice(modelEnd + 1); - replaceFile(schemaPath, newContent); + await replaceFile(schemaPath, newContent); // consola.success(`Replaced ${modelName} in Prisma schema`); } else { const newContent = schemaContents.concat("\n", schema); - replaceFile(schemaPath, newContent); + await replaceFile(schemaPath, newContent); // consola.success(`Added ${modelName} to Prisma schema`); } } else { @@ -382,7 +382,10 @@ const getPrismaModelStartAndEnd = (schema: string, modelName: string) => { return { modelStart, modelEnd, modelExists }; }; -export function addToPrismaModel(modelName: string, attributesToAdd: string) { +export async function addToPrismaModel( + modelName: string, + attributesToAdd: string +) { const hasSchema = existsSync("prisma/schema.prisma"); if (!hasSchema) { console.error("Prisma schema not found!"); @@ -401,12 +404,12 @@ export function addToPrismaModel(modelName: string, attributesToAdd: string) { const newSchema = beforeModelEnd + " " + attributesToAdd + "\n" + afterModelEnd; - replaceFile("prisma/schema.prisma", newSchema); + await replaceFile("prisma/schema.prisma", newSchema); consola.info("Updated Prisma schema"); } } -export function addToPrismaModelBulk( +export async function addToPrismaModelBulk( modelName: string, attributesToAdd: string ) { @@ -425,7 +428,7 @@ export function addToPrismaModelBulk( const newSchema = beforeModelEnd + " " + attributesToAdd + "\n" + afterModelEnd; - replaceFile("prisma/schema.prisma", newSchema); + await replaceFile("prisma/schema.prisma", newSchema); consola.info("Updated Prisma schema"); } } diff --git a/src/commands/init/index.ts b/src/commands/init/index.ts index 7383eba1..28f32af4 100644 --- a/src/commands/init/index.ts +++ b/src/commands/init/index.ts @@ -5,7 +5,11 @@ import { consola } from "consola"; import { addPackage } from "../add/index.js"; import { existsSync, readFileSync } from "fs"; import path from "path"; -import { checkForPackageManager } from "./utils.js"; +import { + // checkAndCreatIfNotExistPrettierConfig, + checkForPackageManager, + createPrettierConfigFileIfNotExist, +} from "./utils.js"; import figlet from "figlet"; import chalk from "chalk"; @@ -64,7 +68,9 @@ export async function initProject(options?: InitOptions) { if (tsConfigString.includes("@/*")) alias = "@"; if (tsConfigString.includes("~/*")) alias = "~"; - createConfigFile({ + await createPrettierConfigFileIfNotExist(); + + await createConfigFile({ driver: undefined, hasSrc: srcExists, provider: undefined, diff --git a/src/commands/init/utils.ts b/src/commands/init/utils.ts index 64211259..6c990bd6 100644 --- a/src/commands/init/utils.ts +++ b/src/commands/init/utils.ts @@ -1,4 +1,8 @@ -import { existsSync, readFileSync } from "fs"; +import { + existsSync, + readFileSync, + // writeFileSync +} from "fs"; import { AvailablePackage, Config, @@ -7,18 +11,16 @@ import { DBType, PMType, } from "../../types.js"; -import { - installPackages, - readConfigFile, - replaceFile, - updateConfigFile, - wrapInParenthesis, -} from "../../utils.js"; +import { createFile, replaceFile, updateConfigFile } from "../../utils.js"; import { consola } from "consola"; import { updateTsConfigPrismaTypeAlias } from "../add/orm/utils.js"; import { addToInstallList } from "../add/utils.js"; import { addNanoidToUtils } from "../add/orm/drizzle/utils.js"; -// test +import { + format as prettierFormat, + resolveConfigFile, + resolveConfig as resolvePrettierConfig, +} from "prettier"; export const DBProviders: DBProviderOptions = { pg: [ @@ -47,7 +49,7 @@ export const DBProviders: DBProviderOptions = { export const checkForExistingPackages = async (rootPath: string) => { consola.start("Checking project for existing packages..."); // get package json - const { preferredPackageManager } = readConfigFile(); + // const { preferredPackageManager } = readConfigFile(); const packageJsonInitText = readFileSync("package.json", "utf-8"); let configObj: Partial = { @@ -58,6 +60,7 @@ export const checkForExistingPackages = async (rootPath: string) => { trpc: ["@trpc/client", "@trpc/react-query", "@trpc/server", "@trpc/next"], clerk: ["@clerk/nextjs"], lucia: ["lucia"], + supabase: ["@supabase/supabase-js", "@supabase/ssr"], prisma: ["prisma"], resend: ["resend"], stripe: ["stripe", "@stripe/stripe-js"], @@ -74,6 +77,7 @@ export const checkForExistingPackages = async (rootPath: string) => { clerk: "auth", "next-auth": "auth", lucia: "auth", + supabase: "auth", drizzle: "orm", }; @@ -198,7 +202,7 @@ export const checkForExistingPackages = async (rootPath: string) => { // { regular: "", dev: "zod-prisma" }, // preferredPackageManager, // ); - addZodGeneratorToPrismaSchema(); + await addZodGeneratorToPrismaSchema(); // consola.success("Successfully installed!"); await updateTsConfigPrismaTypeAlias(); @@ -211,17 +215,17 @@ export const checkForExistingPackages = async (rootPath: string) => { // preferredPackageManager // ); addToInstallList({ regular: ["drizzle-zod", "nanoid"], dev: [] }); - addNanoidToUtils(); + await addNanoidToUtils(); // consola.success("Successfully installed!"); } } // if (drizzle), check if using one schema file or schema directory - perhaps just force users? // update config file - updateConfigFile(configObj); + await updateConfigFile(configObj); }; -const addZodGeneratorToPrismaSchema = () => { +const addZodGeneratorToPrismaSchema = async () => { const hasSchema = existsSync("prisma/schema.prisma"); if (!hasSchema) { console.error("Prisma schema not found!"); @@ -240,7 +244,7 @@ generator zod { } `); - replaceFile("prisma/schema.prisma", newSchema); + await replaceFile("prisma/schema.prisma", newSchema); consola.info("Updated Prisma schema"); }; @@ -255,3 +259,61 @@ export const checkForPackageManager = (): PMType | null => { return null; }; + +export const createPrettierConfigFileIfNotExist = async () => { + const userPrettierConfigPath = await resolveConfigFile(); + + // Throws an error if there is an error during parsing the users prettier config file + let prettierConfig = await resolvePrettierConfig(userPrettierConfigPath); + + if (prettierConfig != null) + consola.success( + "Prettier config found! Using prettier config for formatting..." + ); + + if (prettierConfig === null) { + consola.info( + "Prettier config not found! Create default prettier config for further usage..." + ); + + await createDefaultPrettierConfig(); + } +}; + +export const formatFileContentWithPrettier = async ( + content: string, + filePath: string, + skipPrettier?: boolean +) => { + if (skipPrettier) return content; + + const prettierConfigPath = await resolveConfigFile(); + + // Throws an error if there is an error during parsing the users prettier config file + let prettierConfig = await resolvePrettierConfig(prettierConfigPath); + + if ( + prettierConfig && + typeof prettierConfig === "object" && + !prettierConfig.filepath + ) + prettierConfig.filepath = filePath; + + return await prettierFormat(content, prettierConfig); +}; + +const createDefaultPrettierConfig = async () => { + /** @link https://prettier.io/docs/en/configuration#basic-configuration */ + const defaultPrettierConfig = { + trailingComma: "es5" as const, + tabWidth: 4, + semi: false, + singleQuote: true, + }; + + await createFile( + ".prettierrc", + JSON.stringify(defaultPrettierConfig, null, 2), + true + ); +}; diff --git a/src/index.ts b/src/index.ts index 2cd5bb40..a760307e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,10 +6,10 @@ 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.53"); +program.name("kirimase").description("Kirimase CLI").version("0.0.54"); addCommonOptions(program.command("init")) - .description("initialise and configure kirimase within directory") + .description("initialise and configure kirimase within a directory") .action(initProject); program @@ -37,7 +37,10 @@ function addCommonOptions(command: Command) { .option("-o, --orm ", "preferred orm (prisma, drizzle)") .option("-db, --db ", "preferred database (pg, mysql, sqlite)") .option("-dbp, --db-provider ", "database provider") - .option("-a, --auth ", "preferred auth (next-auth, clerk, lucia)") + .option( + "-a, --auth ", + "preferred auth (next-auth, clerk, lucia, supabase)" + ) .option( "-ap, --auth-providers ", "auth providers (if using next-auth - discord, google, github, apple)" diff --git a/src/types.d.ts b/src/types.d.ts index 6ca18ac0..6060b87a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -74,12 +74,13 @@ export type AvailablePackage = | "resend" | "lucia" | "kinde" - | "stripe"; + | "stripe" + | "supabase"; export type PackageType = "orm" | "auth" | "componentLib" | "misc"; export type ComponentLibType = "shadcn-ui"; export type ORMType = "drizzle" | "prisma"; -export type AuthType = "next-auth" | "clerk" | "lucia" | "kinde"; +export type AuthType = "next-auth" | "clerk" | "lucia" | "kinde" | "supabase"; export type MiscType = "trpc" | "stripe" | "resend"; export type AuthSubType = "self-hosted" | "managed"; diff --git a/src/utils.ts b/src/utils.ts index 672d67da..8f94b03d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,11 +4,16 @@ import { consola } from "consola"; import { AvailablePackage, Config, PMType, UpdateConfig } from "./types.js"; import { execa } from "execa"; import { spinner } from "./commands/add/index.js"; +import { formatFileContentWithPrettier } from "./commands/init/utils.js"; export const delay = (ms = 2000) => new Promise((resolve) => setTimeout(resolve, ms)); -export function createFile(filePath: string, content: string) { +export async function createFile( + filePath: string, + content: string, + skipPrettier = false +) { const resolvedPath = path.resolve(filePath); const dirName = path.dirname(resolvedPath); @@ -19,12 +24,20 @@ export function createFile(filePath: string, content: string) { // consola.success(`Directory ${dirName} created.`); } - fs.writeFileSync(resolvedPath, content); + fs.writeFileSync( + resolvedPath, + await formatFileContentWithPrettier(content, filePath, skipPrettier) + ); // TODO - add flag for verbose // consola.success(`File created at ${filePath}`); } -export function replaceFile(filePath: string, content: string, log = true) { +export async function replaceFile( + filePath: string, + content: string, + log = true, + skipPrettier = false +) { const resolvedPath = path.resolve(filePath); const dirName = path.dirname(resolvedPath); @@ -35,7 +48,10 @@ export function replaceFile(filePath: string, content: string, log = true) { // consola.success(`Directory ${dirName} created.`); } - fs.writeFileSync(resolvedPath, content); + fs.writeFileSync( + resolvedPath, + await formatFileContentWithPrettier(content, filePath, skipPrettier) + ); if (log === true) { // TODO as above // consola.success(`File replaced at ${filePath}`); @@ -103,14 +119,14 @@ export async function installPackages( } } -export const createConfigFile = (options: Config) => { - createFile("./kirimase.config.json", JSON.stringify(options, null, 2)); +export const createConfigFile = async (options: Config) => { + await createFile("./kirimase.config.json", JSON.stringify(options, null, 2)); }; -export const updateConfigFile = (options: UpdateConfig) => { +export const updateConfigFile = async (options: UpdateConfig) => { const config = readConfigFile(); const newConfig = { ...config, ...options }; - replaceFile( + await replaceFile( "./kirimase.config.json", JSON.stringify(newConfig, null, 2), false @@ -134,9 +150,9 @@ export const readConfigFile = (): (Config & { rootPath: string }) | null => { return { ...config, rootPath }; }; -export const addPackageToConfig = (packageName: AvailablePackage) => { +export const addPackageToConfig = async (packageName: AvailablePackage) => { const config = readConfigFile(); - updateConfigFile({ packages: [...config?.packages, packageName] }); + await updateConfigFile({ packages: [...config?.packages, packageName] }); }; export const wrapInParenthesis = (string: string) => { @@ -202,12 +218,12 @@ export const getFileContents = (filePath: string) => { return fileContents; }; -export const updateConfigFileAfterUpdate = () => { +export const updateConfigFileAfterUpdate = async () => { const { packages, orm, auth } = readConfigFile(); if (orm === undefined || auth === undefined) { const updatedOrm = packages.includes("drizzle") ? "drizzle" : null; const updatedAuth = packages.includes("next-auth") ? "next-auth" : null; - updateConfigFile({ orm: updatedOrm, auth: updatedAuth }); + await updateConfigFile({ orm: updatedOrm, auth: updatedAuth }); consola.info("Config file updated."); } else { consola.info("Config file already up to date."); From 5472de2281ab6c6f3d72beca3207538e2f1523ad Mon Sep 17 00:00:00 2001 From: OmegaHawkeye Date: Fri, 16 Feb 2024 22:43:00 +0100 Subject: [PATCH 2/5] Fixes that an error page is displayed due to wrong supabase client + custom server actions file for supabase. --- src/commands/add/auth/supabase/generators.ts | 381 +++++++++++-------- src/commands/add/auth/supabase/index.ts | 19 +- src/commands/add/auth/supabase/utils.ts | 4 +- src/commands/filePaths/index.ts | 6 +- src/commands/filePaths/types.d.ts | 3 +- 5 files changed, 243 insertions(+), 170 deletions(-) diff --git a/src/commands/add/auth/supabase/generators.ts b/src/commands/add/auth/supabase/generators.ts index a59aea79..bd65e10a 100644 --- a/src/commands/add/auth/supabase/generators.ts +++ b/src/commands/add/auth/supabase/generators.ts @@ -1,4 +1,3 @@ -import { ORMType } from "../../../../types.js"; import { readConfigFile } from "../../../../utils.js"; import { formatFilePath, getFilePaths } from "../../../filePaths/index.js"; @@ -31,105 +30,130 @@ export const generateSupabaseHelpers = () => { import { cookies } from 'next/headers' import { type NextRequest, NextResponse } from "next/server"; - export const supabaseBrowserClient = createBrowserClient(env.NEXT_PUBLIC_SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY); + /* Only use this for client components ("use client" at the top of the file) */ + export const createSupabaseBrowserClient = createBrowserClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY + ) - export const createSupabaseServerClient = () => - createServerClient( + /* Only use this for server only components */ + export const createSupabaseServerComponentClient = () => { + return createServerClient( env.NEXT_PUBLIC_SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY, { cookies: { get(name: string) { - return cookies().get(name)?.value; + return cookies().get(name)?.value + } + } + } + ) + } + + /* Use this inside Server Actions or Route Handlers bc only in those u can set cookies */ + export const createSupabaseServerActionClient = () => { + return createServerClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + get(name: string) { + return cookies().get(name)?.value }, set(name: string, value: string, options: CookieOptions) { - cookies().set({ name, value, ...options }); + cookies().set({ name, value, ...options }) }, remove(name: string, options: CookieOptions) { - cookies().set({ name, value: "", ...options }); - }, - }, - }, - ); + cookies().set({ name, value: '', ...options }) + } + } + } + ) + } + + /* Use this inside Server Actions or Route Handlers bc only in those u can set cookies */ + export const createSupabaseApiRouteClient = () => { + return createSupabaseServerActionClient() + } + /* Use this inside the middleware */ export const createSupabaseMiddlewareClient = (request: NextRequest) => { // Create an unmodified response let response = NextResponse.next({ request: { - headers: request.headers, - }, - }); + headers: request.headers + } + }) const supabase = createServerClient( env.NEXT_PUBLIC_SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY, { - cookies: { - get(name: string) { - return request.cookies.get(name)?.value; - }, - set(name: string, value: string, options: CookieOptions) { - // If the cookie is updated, update the cookies for the request and response - request.cookies.set({ - name, - value, - ...options, - }); - response = NextResponse.next({ - request: { - headers: request.headers, - }, - }); - response.cookies.set({ - name, - value, - ...options, - }); - }, - remove(name: string, options: CookieOptions) { - // If the cookie is removed, update the cookies for the request and response - request.cookies.set({ - name, - value: "", - ...options, - }); - response = NextResponse.next({ - request: { - headers: request.headers, - }, - }); - response.cookies.set({ - name, - value: "", - ...options, - }); - }, + cookies: { + get(name: string) { + return request.cookies.get(name)?.value }, - } - ); - - return { supabase, response }; - }; + set(name: string, value: string, options: CookieOptions) { + // If the cookie is updated, update the cookies for the request and response + request.cookies.set({ + name, + value, + ...options + }) + response = NextResponse.next({ + request: { + headers: request.headers + } + }) + response.cookies.set({ + name, + value, + ...options + }) + }, + remove(name: string, options: CookieOptions) { + // If the cookie is removed, update the cookies for the request and response + request.cookies.set({ + name, + value: '', + ...options + }) + response = NextResponse.next({ + request: { + headers: request.headers + } + }) + response.cookies.set({ + name, + value: '', + ...options + }) + } + }} + ) + return { supabase, response } + } export const updateSession = async (request: NextRequest) => { try { - const { supabase, response } = createSupabaseMiddlewareClient(request); + const { supabase, response } = createSupabaseMiddlewareClient(request) // This will refresh session if expired - required for Server Components // https://supabase.com/docs/guides/auth/server-side/nextjs - await supabase.auth.getUser(); + await supabase.auth.getUser() - return response; + return response } catch (e) { // If you are here, a Supabase client could not be created! // This is likely because you have not set up environment variables. // Check out http://localhost:3000 for Next Steps. return NextResponse.next({ request: { - headers: request.headers, - }, - }); + headers: request.headers + } + }) } - }; + } `; }; @@ -142,7 +166,7 @@ export const generateSignInPage = (withShadCn: boolean) => { removeExtension: true, prefix: "alias", })}"; - import { signIn } from "${formatFilePath(shared.auth.authUtils, { + import { signIn } from "${formatFilePath(shared.auth.authActions, { prefix: "alias", removeExtension: true, })}"; @@ -156,11 +180,11 @@ export const generateSignInPage = (withShadCn: boolean) => {

Sign in to your account

- -