diff --git a/src/module.ts b/src/module.ts index 1f7f600..13b3281 100644 --- a/src/module.ts +++ b/src/module.ts @@ -23,6 +23,8 @@ import { } from "./package-utils/setup-helpers"; import { log, PREDEFINED_LOG_MESSAGES } from "./package-utils/log-helpers"; import type { Prisma } from "@prisma/client"; +import type { PackageManager } from "./package-utils/detect-pm"; +import getProjectRoot from "./package-utils/get-project-root"; interface ModuleOptions extends Prisma.PrismaClientOptions { writeToSchema: boolean; @@ -34,6 +36,8 @@ interface ModuleOptions extends Prisma.PrismaClientOptions { installStudio: boolean; autoSetupPrisma: boolean; skipPrompts: boolean; + packageManager?: PackageManager; + prismaRoot?: string; } export type PrismaExtendedModule = ModuleOptions; @@ -61,11 +65,14 @@ export default defineNuxtModule({ installStudio: true, autoSetupPrisma: false, skipPrompts: false, + packageManager: undefined, + prismaRoot: undefined, }, async setup(options, nuxt) { const { resolve: resolveProject } = createResolver(nuxt.options.rootDir); const { resolve: resolver } = createResolver(import.meta.url); + const { resolve: resolveRoot } = createResolver(getProjectRoot()); const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url)); // Identifies which script is running: posinstall, dev or prod @@ -118,7 +125,11 @@ export default defineNuxtModule({ return; } - const PROJECT_PATH = resolveProject(); + let projectPath = resolveProject(); + if (options.prismaRoot?.length) + projectPath = resolveRoot(options.prismaRoot); + + const PROJECT_PATH = projectPath; if (options.installCLI) { // Check if Prisma CLI is installed. @@ -126,7 +137,7 @@ export default defineNuxtModule({ // if Prisma CLI is installed skip the following step. if (!prismaInstalled) { - await installPrismaCLI(PROJECT_PATH); + await installPrismaCLI(PROJECT_PATH, options.packageManager); } } @@ -182,8 +193,7 @@ export default defineNuxtModule({ }); // Add dummy models to the Prisma schema - await writeToSchema(resolveProject("prisma", "schema.prisma")); - await prismaMigrateWorkflow(); + await writeToSchema(`${PROJECT_PATH}/prisma/schema.prisma`); }; const prismaStudioWorkflow = async () => { @@ -219,14 +229,17 @@ export default defineNuxtModule({ if (!prismaSchemaExists) { await prismaInitWorkflow(); - } else { - await prismaMigrateWorkflow(); } - await writeClientInLib(resolveProject("lib", "prisma.ts")); + await prismaMigrateWorkflow(); + await writeClientInLib(PROJECT_PATH); if (options.generateClient) { - await generateClient(PROJECT_PATH, options.installClient); + await generateClient( + PROJECT_PATH, + options.installClient, + options.packageManager, + ); } await prismaStudioWorkflow(); diff --git a/src/package-utils/detect-pm.ts b/src/package-utils/detect-pm.ts index cc7e57c..612e464 100644 --- a/src/package-utils/detect-pm.ts +++ b/src/package-utils/detect-pm.ts @@ -1,35 +1,49 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { existsSync } from "fs"; +import { logWarning } from "./log-helpers"; +import getProjectRoot from "./get-project-root"; -type PackageManager = "npm" | "yarn" | "pnpm" | "bun"; +export type PackageManager = "npm" | "yarn" | "pnpm" | "bun"; + +function detectPackageManager(packageManager?: PackageManager): PackageManager { + // If a package manager was explicitly defined, use that one. + if (packageManager) return packageManager; + + const projectRoot = getProjectRoot(); -function detectPackageManager(): PackageManager { // Check for package-lock.json - if (existsSync("package-lock.json")) { + if ( + existsSync("package-lock.json") || + existsSync(`${projectRoot}/package-lock.json`) + ) { return "npm"; } // Check for yarn.lock - if (existsSync("yarn.lock")) { + if (existsSync("yarn.lock") || existsSync(`${projectRoot}/yarn.lock`)) { return "yarn"; } // Check for pnpm-lock.yaml - if (existsSync("pnpm-lock.yaml")) { + if ( + existsSync("pnpm-lock.yaml") || + existsSync(`${projectRoot}/pnpm-lock.yaml`) + ) { return "pnpm"; } // bun.lockb - if (existsSync("bun.lockb")) { + if (existsSync("bun.lockb") || existsSync(`${projectRoot}/bun.lockb`)) { return "bun"; } // Default to npm if none of the above are found + logWarning("Could not find any package manager files. Defaulting to npm."); return "npm"; } -export const installingPrismaCLIWithPM = () => { - const pm = detectPackageManager(); +export const installingPrismaCLIWithPM = (packageManager?: PackageManager) => { + const pm = detectPackageManager(packageManager); switch (pm) { case "npm": { @@ -65,8 +79,10 @@ export const installingPrismaCLIWithPM = () => { } }; -export const installingPrismaClientWithPM = () => { - const pm = detectPackageManager(); +export const installingPrismaClientWithPM = ( + packageManager?: PackageManager, +) => { + const pm = detectPackageManager(packageManager); switch (pm) { case "npm": { diff --git a/src/package-utils/get-project-root.ts b/src/package-utils/get-project-root.ts new file mode 100644 index 0000000..bf8da81 --- /dev/null +++ b/src/package-utils/get-project-root.ts @@ -0,0 +1,14 @@ +import { existsSync } from "fs"; +import path from "path"; + +export default function getProjectRoot(): string { + let projectRoot = process.cwd(); + + // Find the project root, in case workspaces are being used. + // Please note that resolveProject will not work, since it picks the layer directory. + do { + projectRoot = path.resolve(projectRoot, ".."); + } while (projectRoot !== "/" && !existsSync(`${projectRoot}/package.json`)); + + return projectRoot; +} diff --git a/src/package-utils/log-helpers.ts b/src/package-utils/log-helpers.ts index 0cc97eb..d70e207 100644 --- a/src/package-utils/log-helpers.ts +++ b/src/package-utils/log-helpers.ts @@ -4,6 +4,10 @@ export function logSuccess(message: string) { console.log(chalk.green(`✔ ${message}`)); } +export function logWarning(message: string) { + console.warn(chalk.yellow(`⚠️ ${message}`)); +} + export function logError(message: string) { console.error(chalk.red(`✘ ${message}`)); } diff --git a/src/package-utils/setup-helpers.ts b/src/package-utils/setup-helpers.ts index ff8e7e2..cdef6f2 100644 --- a/src/package-utils/setup-helpers.ts +++ b/src/package-utils/setup-helpers.ts @@ -2,6 +2,7 @@ import { execa } from "execa"; import { installingPrismaClientWithPM, installingPrismaCLIWithPM, + type PackageManager, } from "./detect-pm"; import { log, @@ -39,9 +40,12 @@ export async function isPrismaCLIInstalled( } } -export async function installPrismaCLI(directory: string) { +export async function installPrismaCLI( + directory: string, + packageManager?: PackageManager, +) { try { - const installCmd = installingPrismaCLIWithPM(); + const installCmd = installingPrismaCLIWithPM(packageManager); await execa(installCmd.pm, installCmd.command, { cwd: directory, @@ -72,19 +76,19 @@ export async function initPrisma({ provider = "sqlite", datasourceUrl, }: PrismaInitOptions) { - const command = ["prisma", "init", "--datasource-provider"]; + const commandArgs = ["prisma", "init", "--datasource-provider"]; - command.push(provider); + commandArgs.push(provider); if (datasourceUrl) { - command.push("--url"); - command.push(datasourceUrl); + commandArgs.push("--url"); + commandArgs.push(datasourceUrl); } try { log(PREDEFINED_LOG_MESSAGES.initPrisma.action); - const { stdout: initializePrisma } = await execa("npx", command, { + const { stdout: initializePrisma } = await execa("npx", commandArgs, { cwd: directory, }); @@ -120,23 +124,26 @@ export async function writeToSchema(prismaSchemaPath: string) { return false; } - const addModel = ` - model User { - id Int @id @default(autoincrement()) - email String @unique - name String? - posts Post[] - } - - model Post { - id Int @id @default(autoincrement()) - title String - content String? - published Boolean @default(false) - author User @relation(fields: [authorId], references: [id]) - authorId Int - } - `; + const addModel = `\ +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int +} +`; + + // Don't bother adding the models if they already exist. + if (existingSchema.trim().includes(addModel.trim())) return; const updatedSchema = `${existingSchema.trim()}\n\n${addModel}`; writeFileSync(prismaSchemaPath, updatedSchema); @@ -174,12 +181,13 @@ export async function formatSchema(directory: string) { export async function generateClient( directory: string, installPrismaClient: boolean = true, + packageManager?: PackageManager, ) { log(PREDEFINED_LOG_MESSAGES.generatePrismaClient.action); if (installPrismaClient) { try { - const installCmd = installingPrismaClientWithPM(); + const installCmd = installingPrismaClientWithPM(packageManager); await execa(installCmd.pm, installCmd.command, { cwd: directory, @@ -214,9 +222,9 @@ export async function installStudio(directory: string) { log(PREDEFINED_LOG_MESSAGES.installStudio.action); const subprocess = execa("npx", ["prisma", "studio", "--browser", "none"], { - cwd: directory + cwd: directory, }); - + subprocess.unref(); logSuccess(PREDEFINED_LOG_MESSAGES.installStudio.success); @@ -230,11 +238,12 @@ export async function installStudio(directory: string) { } export async function writeClientInLib(path: string) { - const existingContent = existsSync(path); + const existingContent = existsSync(`${path}/lib/prisma.ts`); try { if (!existingContent) { - const prismaClient = `import { PrismaClient } from '@prisma/client' + const prismaClient = `\ +import { PrismaClient } from '@prisma/client' const prismaClientSingleton = () => { return new PrismaClient() @@ -251,16 +260,16 @@ export default prisma if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma `; - if (!existsSync("lib")) { - mkdirSync("lib"); + if (!existsSync(`${path}/lib`)) { + mkdirSync(`${path}/lib`); } - if (existsSync("lib/prisma.ts")) { + if (existsSync(`${path}/lib/prisma.ts`)) { log(PREDEFINED_LOG_MESSAGES.writeClientInLib.found); return; } - writeFileSync("lib/prisma.ts", prismaClient); + writeFileSync(`${path}/lib/prisma.ts`, prismaClient); logSuccess(PREDEFINED_LOG_MESSAGES.writeClientInLib.success); }