diff --git a/package-lock.json b/package-lock.json index a6f5d15..8a91f98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "chalk": "^5.3.0", "defu": "^6.1.4", "execa": "^8.0.1", + "nypm": "^0.4.1", + "pathe": "^1.1.2", "prompts": "^2.4.2" }, "devDependencies": { @@ -2446,6 +2448,77 @@ "node": ">=14.18.0" } }, + "node_modules/@nuxt/devtools/node_modules/nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/devtools/node_modules/nypm/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@nuxt/devtools/node_modules/nypm/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/devtools/node_modules/nypm/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@nuxt/devtools/node_modules/nypm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nuxt/devtools/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -7660,9 +7733,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==" + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" }, "node_modules/config-chain": { "version": "1.1.13", @@ -10021,6 +10094,25 @@ "giget": "dist/cli.mjs" } }, + "node_modules/giget/node_modules/nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/git-config-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-2.0.0.tgz", @@ -12773,14 +12865,25 @@ } }, "node_modules/mlly": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz", - "integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", + "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.14.0", "pathe": "^1.1.2", - "pkg-types": "^1.1.0", - "ufo": "^1.5.3" + "pkg-types": "^1.2.1", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/mri": { @@ -16588,16 +16691,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nypm": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz", - "integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==", + "node_modules/nuxt/node_modules/nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", "dependencies": { "citty": "^0.1.6", "consola": "^3.2.3", "execa": "^8.0.1", "pathe": "^1.1.2", - "ufo": "^1.4.0" + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/nypm": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.4.1.tgz", + "integrity": "sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "tinyexec": "^0.3.1", + "ufo": "^1.5.4" }, "bin": { "nypm": "dist/cli.mjs" @@ -17317,12 +17440,12 @@ } }, "node_modules/pkg-types": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", - "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.0", + "confbox": "^0.1.8", + "mlly": "^1.7.2", "pathe": "^1.1.2" } }, @@ -20325,8 +20448,7 @@ "node_modules/tinyexec": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", - "dev": true + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==" }, "node_modules/tinypool": { "version": "0.8.4", @@ -20511,9 +20633,9 @@ } }, "node_modules/ufo": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", - "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==" + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==" }, "node_modules/uglify-js": { "version": "3.19.3", diff --git a/package.json b/package.json index 1052bc0..b872c5b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "chalk": "^5.3.0", "defu": "^6.1.4", "execa": "^8.0.1", + "nypm": "^0.4.1", + "pathe": "^1.1.2", "prompts": "^2.4.2" }, "devDependencies": { diff --git a/playground/database/.gitignore b/playground/database/.gitignore new file mode 100644 index 0000000..11ddd8d --- /dev/null +++ b/playground/database/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +.env diff --git a/playground/database/nuxt.config.ts b/playground/database/nuxt.config.ts new file mode 100644 index 0000000..68093d8 --- /dev/null +++ b/playground/database/nuxt.config.ts @@ -0,0 +1 @@ +export default defineNuxtConfig({}); diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 6951b92..915fea3 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -1,6 +1,10 @@ export default defineNuxtConfig({ modules: ["../src/module"], - prisma: {}, + extends: ["./database"], + prisma: { + prismaRoot: "./database", + prismaSchemaPath: "./database/prisma/schema.prisma", + }, experimental: { componentIslands: true, }, diff --git a/src/module.ts b/src/module.ts index 8bbb6e2..4f00d0a 100644 --- a/src/module.ts +++ b/src/module.ts @@ -7,12 +7,13 @@ import { } from "@nuxt/kit"; import { fileURLToPath } from "url"; import defu from "defu"; -import { executeRequiredPrompts } from "./package-utils/prompts"; + +// Import utility functions import { checkIfMigrationsFolderExists, checkIfPrismaSchemaExists, formatSchema, - generateClient, + installPrismaClient, initPrisma, installPrismaCLI, installStudio, @@ -20,10 +21,13 @@ import { runMigration, writeClientInLib, writeToSchema, + generatePrismaClient, } from "./package-utils/setup-helpers"; import { log, PREDEFINED_LOG_MESSAGES } from "./package-utils/log-helpers"; import type { Prisma } from "@prisma/client"; +import { executeRequiredPrompts } from "./package-utils/prompts"; +// Module configuration interface interface ModuleOptions extends Prisma.PrismaClientOptions { writeToSchema: boolean; formatSchema: boolean; @@ -34,6 +38,8 @@ interface ModuleOptions extends Prisma.PrismaClientOptions { installStudio: boolean; autoSetupPrisma: boolean; skipPrompts: boolean; + prismaRoot?: string; + prismaSchemaPath?: string; } export type PrismaExtendedModule = ModuleOptions; @@ -43,11 +49,12 @@ export default defineNuxtModule({ name: "@prisma/nuxt", configKey: "prisma", }, - // Default configuration options of the Nuxt module + + // Default configuration options for the module defaults: { datasources: { db: { - url: process.env.DATABASE_URL, + url: process.env.DATABASE_URL, // Security: Ensure DATABASE_URL is correctly set and secure }, }, log: [], @@ -61,6 +68,8 @@ export default defineNuxtModule({ installStudio: true, autoSetupPrisma: false, skipPrompts: false, + prismaRoot: undefined, + prismaSchemaPath: undefined, }, async setup(options, nuxt) { @@ -68,26 +77,29 @@ export default defineNuxtModule({ const { resolve: resolver } = createResolver(import.meta.url); const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url)); - // Identifies which script is running: posinstall, dev or prod - const npm_lifecycle_event = process.env?.npm_lifecycle_event; + const npmLifecycleEvent = process.env?.npm_lifecycle_event; + const skipAllPrompts = + options.skipPrompts || npmLifecycleEvent === "dev:build"; - const skip_all_prompts = - options.skipPrompts || npm_lifecycle_event === "dev:build"; + const PRISMA_SCHEMA_CMD = options.prismaSchemaPath + ? ["--schema", options.prismaSchemaPath] + : []; + /** + * Helper function to prepare the module configuration + */ const prepareModule = () => { // Enable server components for Nuxt nuxt.options.experimental.componentIslands ||= {}; nuxt.options.experimental.componentIslands = true; - // Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack` - + // Add plugins and import directories addPlugin(resolver("./runtime/plugin")); addImportsDir(resolver(runtimeDir, "composables")); - - // Auto-import from runtime/server/utils addServerImportsDir(resolver(runtimeDir, "utils")); // addServerImportsDir(resolver(runtimeDir, "server/utils")); + // Optimize dependencies for Vite nuxt.options.vite.optimizeDeps = defu( nuxt.options.vite.optimizeDeps || {}, { @@ -96,12 +108,13 @@ export default defineNuxtModule({ ); }; - const force_skip_prisma_setup = + // Skip Prisma setup logic if flagged + const forceSkipPrismaSetup = import.meta.env?.SKIP_PRISMA_SETUP ?? process.env?.SKIP_PRISMA_SETUP ?? false; - // exposing module options to application runtime + // Expose module options to the runtime configuration nuxt.options.runtimeConfig.public.prisma = defu( nuxt.options.runtimeConfig.public.prisma || {}, { @@ -110,8 +123,8 @@ export default defineNuxtModule({ }, ); - if (force_skip_prisma_setup || npm_lifecycle_event === "postinstall") { - if (npm_lifecycle_event !== "postinstall") { + if (forceSkipPrismaSetup || npmLifecycleEvent === "postinstall") { + if (npmLifecycleEvent !== "postinstall") { log(PREDEFINED_LOG_MESSAGES.PRISMA_SETUP_SKIPPED_WARNING); } prepareModule(); @@ -120,42 +133,48 @@ export default defineNuxtModule({ const PROJECT_PATH = resolveProject(); + // Concatenate PROJECT_PATH and prismaRoot manually + const LAYER_PATH = options.prismaRoot + ? resolveProject(options.prismaRoot) // Combines paths safely + : PROJECT_PATH; + + // Ensure Prisma CLI is installed if required 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 generatePrismaClient( + PROJECT_PATH, + PRISMA_SCHEMA_CMD, + options.log?.includes("error"), + ); } } - // Check if Prisma Schema exists + // Check if Prisma schema exists const prismaSchemaExists = checkIfPrismaSchemaExists([ - resolveProject("prisma", "schema.prisma"), - resolveProject("prisma", "schema"), + resolveProject(LAYER_PATH, "prisma", "schema.prisma"), + resolveProject(LAYER_PATH, "prisma", "schema"), ]); + /** + * Handle Prisma migrations workflow + */ const prismaMigrateWorkflow = async () => { - // Check if Prisma migrations folder exists - const doesMigrationFolderExist = checkIfMigrationsFolderExists( - resolveProject("prisma", "migrations"), + const migrationFolderExists = checkIfMigrationsFolderExists( + resolveProject(LAYER_PATH, "prisma", "migrations"), ); - if (doesMigrationFolderExist || !options.runMigration) { - // Skip migration as the migration folder exists + if (migrationFolderExists || !options.runMigration) { log(PREDEFINED_LOG_MESSAGES.skipMigrations); return; } const migrateAndFormatSchema = async () => { - await runMigration(PROJECT_PATH); - - if (!options.formatSchema) { - return; + await runMigration(PROJECT_PATH, PRISMA_SCHEMA_CMD); + if (options.formatSchema) { + await formatSchema(PROJECT_PATH, PRISMA_SCHEMA_CMD); } - - await formatSchema(PROJECT_PATH); }; if (options.autoSetupPrisma && options.runMigration) { @@ -164,36 +183,39 @@ export default defineNuxtModule({ } const promptResult = await executeRequiredPrompts({ - promptForMigrate: true && !skip_all_prompts, - promptForPrismaStudio: false && !skip_all_prompts, + promptForMigrate: true && !skipAllPrompts, + promptForPrismaStudio: false && !skipAllPrompts, }); if (promptResult?.promptForPrismaMigrate && options.runMigration) { await migrateAndFormatSchema(); } - - return; }; + /** + * Handle Prisma initialization workflow + */ const prismaInitWorkflow = async () => { await initPrisma({ - directory: PROJECT_PATH, + directory: LAYER_PATH, + rootDir: PROJECT_PATH, provider: "sqlite", }); - - // Add dummy models to the Prisma schema - await writeToSchema(resolveProject("prisma", "schema.prisma")); - await prismaMigrateWorkflow(); + await writeToSchema(`${LAYER_PATH}/prisma/schema.prisma`); }; + /** + * Handle Prisma Studio setup workflow + */ const prismaStudioWorkflow = async () => { - if (!options.installStudio || npm_lifecycle_event !== "dev") { + if (!options.installStudio || npmLifecycleEvent !== "dev") { log(PREDEFINED_LOG_MESSAGES.skipInstallingPrismaStudio); return; } const installAndStartPrismaStudio = async () => { - await installStudio(PROJECT_PATH); + await installStudio(PROJECT_PATH, PRISMA_SCHEMA_CMD); + nuxt.hooks.hook("devtools:customTabs", (tab) => { tab.push({ name: "nuxt-prisma", @@ -209,29 +231,26 @@ export default defineNuxtModule({ }); }; - if (options.autoSetupPrisma) { - await installAndStartPrismaStudio(); - return; - } - await installAndStartPrismaStudio(); }; + // Execute workflows sequentially if (!prismaSchemaExists) { await prismaInitWorkflow(); - } else { - await prismaMigrateWorkflow(); } - - await writeClientInLib(resolveProject("lib", "prisma.ts")); + await prismaMigrateWorkflow(); + await writeClientInLib(LAYER_PATH); if (options.generateClient) { - await generateClient(PROJECT_PATH, options.installClient); + await installPrismaClient(PROJECT_PATH, options.installClient); + await generatePrismaClient( + PROJECT_PATH, + PRISMA_SCHEMA_CMD, + options.log?.includes("error"), + ); } await prismaStudioWorkflow(); - prepareModule(); - return; }, }); diff --git a/src/package-utils/detect-pm.ts b/src/package-utils/detect-pm.ts deleted file mode 100644 index cc7e57c..0000000 --- a/src/package-utils/detect-pm.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { existsSync } from "fs"; - -type PackageManager = "npm" | "yarn" | "pnpm" | "bun"; - -function detectPackageManager(): PackageManager { - // Check for package-lock.json - if (existsSync("package-lock.json")) { - return "npm"; - } - - // Check for yarn.lock - if (existsSync("yarn.lock")) { - return "yarn"; - } - - // Check for pnpm-lock.yaml - if (existsSync("pnpm-lock.yaml")) { - return "pnpm"; - } - - // bun.lockb - if (existsSync("bun.lockb")) { - return "bun"; - } - - // Default to npm if none of the above are found - return "npm"; -} - -export const installingPrismaCLIWithPM = () => { - const pm = detectPackageManager(); - - switch (pm) { - case "npm": { - return { - pm, - command: ["install", "prisma", "--save-dev"], - }; - } - case "pnpm": { - return { - pm, - command: ["add", "-D", "prisma"], - }; - } - case "yarn": { - return { - pm, - command: ["add", "-D", "prisma"], - }; - } - case "bun": { - return { - pm, - command: ["add", "prisma", "--dev"], - }; - } - default: { - return { - pm: "npm", - command: ["install", "prisma", "--save-dev"], - }; - } - } -}; - -export const installingPrismaClientWithPM = () => { - const pm = detectPackageManager(); - - switch (pm) { - case "npm": { - return { - pm, - command: ["install", "@prisma/client", "--save-dev"], - }; - } - case "pnpm": { - return { - pm, - command: ["add", "-D", "@prisma/client"], - }; - } - case "yarn": { - return { - pm, - command: ["add", "-D", "@prisma/client"], - }; - } - case "bun": { - return { - pm, - command: ["add", "@prisma/client", "--dev"], - }; - } - default: { - return { - pm: "npm", - command: ["install", "@prisma/client", "--save-dev"], - }; - } - } -}; 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..b71ec91 100644 --- a/src/package-utils/setup-helpers.ts +++ b/src/package-utils/setup-helpers.ts @@ -1,8 +1,4 @@ import { execa } from "execa"; -import { - installingPrismaClientWithPM, - installingPrismaCLIWithPM, -} from "./detect-pm"; import { log, logError, @@ -10,6 +6,8 @@ import { PREDEFINED_LOG_MESSAGES, } from "./log-helpers"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { join } from "pathe"; +import { addDependency, addDevDependency } from "nypm"; export type DatabaseProviderType = | "sqlite" @@ -23,6 +21,7 @@ export type PrismaInitOptions = { directory: string; datasourceUrl?: string; provider: DatabaseProviderType; + rootDir: string; }; export async function isPrismaCLIInstalled( @@ -41,11 +40,10 @@ export async function isPrismaCLIInstalled( export async function installPrismaCLI(directory: string) { try { - const installCmd = installingPrismaCLIWithPM(); - - await execa(installCmd.pm, installCmd.command, { + await addDevDependency("prisma", { cwd: directory, }); + logSuccess(PREDEFINED_LOG_MESSAGES.installPrismaCLI.yes); } catch (err) { logError(PREDEFINED_LOG_MESSAGES.installPrismaCLI.no); @@ -67,29 +65,66 @@ export function checkIfPrismaSchemaExists(paths: string[]) { return false; } +function moveEnvFileContent(dirA: string, dirB: string) { + if (dirA === dirB) { + return; + } + + const envFileA = join(dirA, ".env"); + const envFileB = join(dirB, ".env"); + + try { + if (!existsSync(envFileB)) { + console.error(`Source .env file does not exist in directory: ${dirB}`); + return; + } + + const envContentB = readFileSync(envFileB, "utf8"); + + if (existsSync(envFileA)) { + const envContentA = readFileSync(envFileA, "utf8"); + const combinedContent = `${envContentA}\n${envContentB}`; + writeFileSync(envFileA, combinedContent, "utf8"); + } else { + writeFileSync(envFileA, envContentB, "utf8"); + } + + console.log(`Successfully moved content from ${envFileB} to ${envFileA}`); + } catch (error) { + console.error(`Failed to move .env file content: ${error}`); + } +} + export async function initPrisma({ directory, + rootDir, 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, }); log(initializePrisma?.split("Next steps")?.[0]); + try { + moveEnvFileContent(directory, rootDir); + } catch (error) { + console.log(); + } + return true; } catch (err) { logError(PREDEFINED_LOG_MESSAGES.initPrisma.error); @@ -120,23 +155,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); @@ -145,13 +183,17 @@ export async function writeToSchema(prismaSchemaPath: string) { } } -export async function runMigration(directory: string) { +export async function runMigration(directory: string, schemaPath: string[]) { try { log(PREDEFINED_LOG_MESSAGES.runMigration.action); - await execa("npx", ["prisma", "migrate", "dev", "--name", "init"], { - cwd: directory, - }); + await execa( + "npx", + ["prisma", "migrate", "dev", "--name", "init"].concat(schemaPath), + { + cwd: directory, + }, + ); logSuccess(PREDEFINED_LOG_MESSAGES.runMigration.success); return true; } catch (err) { @@ -162,16 +204,18 @@ export async function runMigration(directory: string) { } } -export async function formatSchema(directory: string) { +export async function formatSchema(directory: string, schemaPath: string[]) { try { log(PREDEFINED_LOG_MESSAGES.formatSchema.action); - await execa("npx", ["prisma", "format"], { cwd: directory }); + await execa("npx", ["prisma", "format"].concat(schemaPath), { + cwd: directory, + }); } catch { logError(PREDEFINED_LOG_MESSAGES.formatSchema.error); } } -export async function generateClient( +export async function installPrismaClient( directory: string, installPrismaClient: boolean = true, ) { @@ -179,9 +223,7 @@ export async function generateClient( if (installPrismaClient) { try { - const installCmd = installingPrismaClientWithPM(); - - await execa(installCmd.pm, installCmd.command, { + await addDependency("@prisma/client", { cwd: directory, }); } catch (error) { @@ -192,31 +234,44 @@ export async function generateClient( // log(error); } } +} +export async function generatePrismaClient( + directory: string, + prismaSchemaPath: string[], + verboseLog: boolean = false, +) { try { const { stdout: generateClient } = await execa( "npx", - ["prisma", "generate"], + ["prisma", "generate"].concat(prismaSchemaPath), { cwd: directory }, ); log("\n" + generateClient.split("\n").slice(0, 4).join("\n") + "\n"); - - // log(generateClient); } catch (err) { logError(PREDEFINED_LOG_MESSAGES.generatePrismaClient.error); - // log(err); + if (verboseLog) { + log(err); + } } } -export async function installStudio(directory: string) { +export async function installStudio( + directory: string, + schemaLocation: string[], +) { try { log(PREDEFINED_LOG_MESSAGES.installStudio.action); - const subprocess = execa("npx", ["prisma", "studio", "--browser", "none"], { - cwd: directory - }); - + const subprocess = execa( + "npx", + ["prisma", "studio", "--browser", "none"].concat(schemaLocation), + { + cwd: directory, + }, + ); + subprocess.unref(); logSuccess(PREDEFINED_LOG_MESSAGES.installStudio.success); @@ -230,11 +285,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 +307,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); }