Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve issues when installing the Prisma client in a PNPM workspace using Nuxt layers #30

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
29 changes: 21 additions & 8 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,6 +36,8 @@ interface ModuleOptions extends Prisma.PrismaClientOptions {
installStudio: boolean;
autoSetupPrisma: boolean;
skipPrompts: boolean;
packageManager?: PackageManager;
prismaRoot?: string;
}

export type PrismaExtendedModule = ModuleOptions;
Expand Down Expand Up @@ -61,11 +65,14 @@ export default defineNuxtModule<PrismaExtendedModule>({
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
Expand Down Expand Up @@ -118,15 +125,19 @@ export default defineNuxtModule<PrismaExtendedModule>({
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.
const prismaInstalled = await isPrismaCLIInstalled(PROJECT_PATH);

// if Prisma CLI is installed skip the following step.
if (!prismaInstalled) {
await installPrismaCLI(PROJECT_PATH);
await installPrismaCLI(PROJECT_PATH, options.packageManager);
}
}

Expand Down Expand Up @@ -182,8 +193,7 @@ export default defineNuxtModule<PrismaExtendedModule>({
});

// 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 () => {
Expand Down Expand Up @@ -219,14 +229,17 @@ export default defineNuxtModule<PrismaExtendedModule>({

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();
Expand Down
36 changes: 26 additions & 10 deletions src/package-utils/detect-pm.ts
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
14 changes: 14 additions & 0 deletions src/package-utils/get-project-root.ts
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 4 additions & 0 deletions src/package-utils/log-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`));
}
Expand Down
75 changes: 42 additions & 33 deletions src/package-utils/setup-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { execa } from "execa";
import {
installingPrismaClientWithPM,
installingPrismaCLIWithPM,
type PackageManager,
} from "./detect-pm";
import {
log,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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()
Expand All @@ -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);
}
Expand Down
Loading