Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,192
  • Linux
  • Apple
getGenerators.ts17.5 kB
import Debug from '@prisma/debug' import { enginesVersion, getCliQueryEngineBinaryType } from '@prisma/engines' import type { BinaryTargetsEnvValue, EngineType, Generator as IGenerator, GeneratorConfig, GeneratorOptions, SqlQueryOutput, } from '@prisma/generator' import type { BinaryTarget } from '@prisma/get-platform' import { binaryTargets, getBinaryTargetForCurrentPlatform } from '@prisma/get-platform' import { bold, gray, green, red, underline, yellow } from 'kleur/colors' import pMap from 'p-map' import path from 'path' import { match } from 'ts-pattern' import { getDMMF, getEnvPaths, loadSchemaContext, mergeSchemas, SchemaContext } from '..' import { Generator, InProcessGenerator, JsonRpcGenerator } from '../Generator' import { resolveOutput } from '../resolveOutput' import { extractPreviewFeatures } from '../utils/extractPreviewFeatures' import { missingDatasource } from '../utils/missingDatasource' import { missingModelMessage, missingModelMessageMongoDB } from '../utils/missingGeneratorMessage' import { parseBinaryTargetsEnvValue, parseEnvValue } from '../utils/parseEnvValue' import { pick } from '../utils/pick' import { printConfigWarnings } from '../utils/printConfigWarnings' import { binaryTypeToEngineType } from './utils/binaryTypeToEngineType' import { fixBinaryTargets } from './utils/fixBinaryTargets' import { getBinaryPathsByVersion } from './utils/getBinaryPathsByVersion' import { getEngineVersionForGenerator } from './utils/getEngineVersionForGenerator' import { getOriginalBinaryTargetsValue, printGeneratorConfig } from './utils/printGeneratorConfig' const debug = Debug('prisma:getGenerators') type BinaryPathsOverride = { [P in EngineType]?: string } export type GeneratorRegistryEntry = | { type: 'rpc' generatorPath: string isNode?: boolean } | { type: 'in-process' generator: IGenerator } export type GeneratorRegistry = Record<string, GeneratorRegistryEntry> export type ProviderAliases = { [alias: string]: { generatorPath: string isNode?: boolean } } // From https://github.com/prisma/prisma/blob/eb4563aea6fb6e593ae48106a74f716ce3dc6752/packages/cli/src/Generate.ts#L167-L172 // versions are set like: // version: enginesVersion, // cliVersion: pkg.version, export type GetGeneratorOptions = { /** @deprecated Pass a schemaContext instead. Kept for compatibility with prisma studio. */ schemaPath?: string schemaContext?: SchemaContext registry: GeneratorRegistry /** @deprecated Use `registry` instead. Kept for compatibility with Prisma Studio. */ providerAliases?: ProviderAliases version?: string printDownloadProgress?: boolean overrideGenerators?: GeneratorConfig[] skipDownload?: boolean binaryPathsOverride?: BinaryPathsOverride generatorNames?: string[] postinstall?: boolean noEngine?: boolean allowNoModels?: boolean typedSql?: SqlQueryOutput[] extensions?: {} } /** * Makes sure that all generators have the binaries they deserve and returns a * `Generator` class per generator defined in the schema.prisma file. * In other words, this is basically a generator factory function. */ export async function getGenerators(options: GetGeneratorOptions): Promise<Generator[]> { // A hack for backward compatibility with Prisma Studio. Remove in Prisma 7. if (options.registry === undefined && options.providerAliases !== undefined) { options.registry = Object.fromEntries( Object.entries(options.providerAliases).map(([name, definition]) => [ name, { type: 'rpc', generatorPath: definition.generatorPath, isNode: definition.isNode, } satisfies GeneratorRegistryEntry, ]), ) } const { schemaPath, registry, version, printDownloadProgress, overrideGenerators, skipDownload, binaryPathsOverride, generatorNames = [], postinstall, noEngine, allowNoModels = true, typedSql, } = options // Fallback logic for prisma studio which still only passes a schema path const schemaContext = !options.schemaContext && schemaPath ? await loadSchemaContext({ schemaPathFromArg: schemaPath, ignoreEnvVarErrors: true }) : options.schemaContext if (!schemaContext) { throw new Error(`no schema provided for getGenerators`) } if (!schemaContext.primaryDatasource) { throw new Error(missingDatasource) } printConfigWarnings(schemaContext.warnings) const previewFeatures = extractPreviewFeatures(schemaContext.generators) const dmmf = await getDMMF({ datamodel: schemaContext.schemaFiles, previewFeatures, }) if (dmmf.datamodel.models.length === 0 && !allowNoModels) { // MongoDB needs extras for @id: @map("_id") @db.ObjectId if (schemaContext.primaryDatasource.provider === 'mongodb') { throw new Error(missingModelMessageMongoDB) } throw new Error(missingModelMessage) } const generatorConfigs = filterGenerators(overrideGenerators || schemaContext.generators, generatorNames) await validateGenerators(generatorConfigs) const runningGenerators: Generator[] = [] try { // 1. Get all generators const generators = await pMap( generatorConfigs, async (generatorConfig, index) => { const baseDir = path.dirname(generatorConfig.sourceFilePath ?? schemaContext.schemaRootDir) // as of now mostly used by studio const providerValue = parseEnvValue(generatorConfig.provider) const generatorDefinition = registry[providerValue] ?? { type: 'rpc', generatorPath: providerValue, } const generatorInstance = match(generatorDefinition) .with({ type: 'in-process' }, ({ generator }) => new InProcessGenerator(generatorConfig, generator)) .with( { type: 'rpc' }, ({ generatorPath, isNode }) => new JsonRpcGenerator(generatorPath, generatorConfig, isNode), ) .exhaustive() await generatorInstance.init() // resolve output path if (generatorConfig.output) { generatorConfig.output = { value: path.resolve(baseDir, parseEnvValue(generatorConfig.output)), fromEnvVar: null, } generatorConfig.isCustomOutput = true } else { if (!generatorInstance.manifest?.defaultOutput) { throw new Error( `Can't resolve output dir for generator ${bold(generatorConfig.name)} with provider ${bold( generatorConfig.provider.value!, )}. You need to define \`output\` in the generator block in the schema file.`, ) } generatorConfig.output = { value: await resolveOutput({ defaultOutput: generatorInstance.manifest.defaultOutput, baseDir, }), fromEnvVar: null, } } const datamodel = mergeSchemas({ schemas: schemaContext.schemaFiles }) const envPaths = await getEnvPaths(schemaContext.schemaPath, { cwd: generatorConfig.output.value! }) const options: GeneratorOptions = { datamodel, datasources: schemaContext.datasources, generator: generatorConfig, dmmf, otherGenerators: skipIndex(generatorConfigs, index), schemaPath: schemaContext.schemaPath, // TODO:(schemaPath) can we get rid of schema path passing here? version: version || enginesVersion, // this version makes no sense anymore and should be ignored postinstall, noEngine, allowNoModels, envPaths, typedSql, } // we set the options here a bit later after instantiating the Generator, // as we need the generator manifest to resolve the `output` dir generatorInstance.setOptions(options) runningGenerators.push(generatorInstance) return generatorInstance }, { stopOnError: false, // needed so we can first make sure all generators are created properly, then cleaned up properly }, ) // 2. Check, if all required generators are there. // Generators can say in their "requiresGenerators" property in the manifest, which other generators they depend on // This has mostly been introduced for 3rd party generators, which rely on `prisma-client-js`. const generatorProviders: string[] = generatorConfigs.map((g) => parseEnvValue(g.provider)) for (const g of generators) { if (g.manifest && g.manifest.requiresGenerators && g.manifest.requiresGenerators.length > 0) { for (const neededGenerator of g.manifest.requiresGenerators) { if (!generatorProviders.includes(neededGenerator)) { throw new Error( `Generator "${g.manifest.prettyName}" requires generator "${neededGenerator}", but it is missing in your schema.prisma. Please add it to your schema.prisma: generator gen { provider = "${neededGenerator}" } `, ) } } } } // 3. Download all binaries and binary targets needed const neededVersions = Object.create(null) for (const g of generators) { if ( g.manifest && g.manifest.requiresEngines && Array.isArray(g.manifest.requiresEngines) && g.manifest.requiresEngines.length > 0 ) { const neededVersion = getEngineVersionForGenerator(g.manifest, version) if (!neededVersions[neededVersion]) { neededVersions[neededVersion] = { engines: [], binaryTargets: [] } } for (const engine of g.manifest.requiresEngines) { if (!neededVersions[neededVersion].engines.includes(engine)) { neededVersions[neededVersion].engines.push(engine) } } const generatorBinaryTargets = g.options?.generator?.binaryTargets if (generatorBinaryTargets && generatorBinaryTargets.length > 0) { for (const binaryTarget of generatorBinaryTargets) { if (!neededVersions[neededVersion].binaryTargets.find((object) => object.value === binaryTarget.value)) { neededVersions[neededVersion].binaryTargets.push(binaryTarget) } } } } } const queryEngineBinaryType = getCliQueryEngineBinaryType() const queryEngineType = binaryTypeToEngineType(queryEngineBinaryType) debug('neededVersions', JSON.stringify(neededVersions, null, 2)) const { binaryPathsByVersion, binaryTarget } = await getBinaryPathsByVersion({ neededVersions, // We're lazily computing the binary target here, to avoid printing the // `Prisma failed to detect the libssl/openssl version to use` warning // on StackBlitz, where the binary target is not detected. // // On other platforms, it's safe and fast to call this function again, // as its result is memoized anyway. detectBinaryTarget: getBinaryTargetForCurrentPlatform, version, printDownloadProgress, skipDownload, binaryPathsOverride, }) for (const generator of generators) { if (generator.manifest && generator.manifest.requiresEngines) { const engineVersion = getEngineVersionForGenerator(generator.manifest, version) const binaryPaths = binaryPathsByVersion[engineVersion] // pick only the engines that we need for this generator const generatorBinaryPaths = pick(binaryPaths ?? {}, generator.manifest.requiresEngines) debug({ generatorBinaryPaths }) generator.setBinaryPaths(generatorBinaryPaths) // in case cli engine version !== client engine version // we need to re-generate the dmmf and pass it into the generator if ( engineVersion !== version && generator.options && generator.manifest.requiresEngines.includes(queryEngineType) && generatorBinaryPaths[queryEngineType] && generatorBinaryPaths[queryEngineType]?.[binaryTarget] ) { const customDmmf = await getDMMF({ datamodel: schemaContext.schemaFiles, previewFeatures, }) const options = { ...generator.options, dmmf: customDmmf } debug('generator.manifest.prettyName', generator.manifest.prettyName) debug('options', options) debug('options.generator.binaryTargets', options.generator.binaryTargets) generator.setOptions(options) } } } return generators } catch (e) { // make sure all generators that are already running are being stopped runningGenerators.forEach((g) => g.stop()) throw e } } type NeededVersions = { [key: string]: { engines: EngineType[] binaryTargets: BinaryTargetsEnvValue[] } } export type GetBinaryPathsByVersionInput = { neededVersions: NeededVersions detectBinaryTarget: () => Promise<BinaryTarget> version?: string printDownloadProgress?: boolean skipDownload?: boolean binaryPathsOverride?: BinaryPathsOverride } /** * Shortcut for getGenerators, if there is only one generator defined. Useful for testing. * @param schemaPath path to schema.prisma * @param aliases Aliases like `photonjs` -> `node_modules/photonjs/gen.js` * @param version Version of the binary, commit hash of https://github.com/prisma/prisma-engine/commits/master * @param printDownloadProgress `boolean` to print download progress or not */ export async function getGenerator(options: GetGeneratorOptions): Promise<Generator> { const generators = await getGenerators(options) return generators[0] } export function skipIndex<T = any>(arr: T[], index: number): T[] { return [...arr.slice(0, index), ...arr.slice(index + 1)] } export const knownBinaryTargets: BinaryTarget[] = [...binaryTargets, 'native'] const oldToNewBinaryTargetsMapping = { 'linux-glibc-libssl1.0.1': 'debian-openssl-1.0.x', 'linux-glibc-libssl1.0.2': 'debian-openssl-1.0.x', 'linux-glibc-libssl1.1.0': 'debian-openssl1.1.x', } async function validateGenerators(generators: GeneratorConfig[]): Promise<void> { const binaryTarget = await getBinaryTargetForCurrentPlatform() for (const generator of generators) { if (generator.config.platforms) { throw new Error( `The \`platforms\` field on the generator definition is deprecated. Please rename it to \`binaryTargets\`.`, ) } if (generator.config.pinnedBinaryTargets) { throw new Error( `The \`pinnedBinaryTargets\` field on the generator definition is deprecated. Please use the PRISMA_QUERY_ENGINE_BINARY env var instead to pin the binary target.`, ) } if (generator.binaryTargets) { const binaryTargets = generator.binaryTargets && generator.binaryTargets.length > 0 ? generator.binaryTargets : [{ fromEnvVar: null, value: 'native' }] const resolvedBinaryTargets: string[] = binaryTargets .flatMap((object) => parseBinaryTargetsEnvValue(object)) .map((p) => (p === 'native' ? binaryTarget : p)) for (const resolvedBinaryTarget of resolvedBinaryTargets) { if (oldToNewBinaryTargetsMapping[resolvedBinaryTarget]) { throw new Error( `Binary target ${red(bold(resolvedBinaryTarget))} is deprecated. Please use ${green( bold(oldToNewBinaryTargetsMapping[resolvedBinaryTarget]), )} instead.`, ) } if (!knownBinaryTargets.includes(resolvedBinaryTarget as BinaryTarget)) { throw new Error( `Unknown binary target ${red(resolvedBinaryTarget)} in generator ${bold(generator.name)}. Possible binaryTargets: ${green(knownBinaryTargets.join(', '))}`, ) } } // Only show warning if resolvedBinaryTargets // is missing current platform if (!resolvedBinaryTargets.includes(binaryTarget)) { const originalBinaryTargetsConfig = getOriginalBinaryTargetsValue(generator.binaryTargets) console.log(`${yellow('Warning:')} Your current platform \`${bold( binaryTarget, )}\` is not included in your generator's \`binaryTargets\` configuration ${JSON.stringify( originalBinaryTargetsConfig, )}. To fix it, use this generator config in your ${bold('schema.prisma')}: ${green( printGeneratorConfig({ ...generator, binaryTargets: fixBinaryTargets(generator.binaryTargets, binaryTarget), }), )} ${gray( `Note, that by providing \`native\`, Prisma Client automatically resolves \`${binaryTarget}\`. Read more about deploying Prisma Client: ${underline( 'https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/generators', )}`, )}\n`) } } } } function filterGenerators(generators: GeneratorConfig[], generatorNames: string[]) { if (generatorNames.length < 1) { return generators } const filtered = generators.filter((generator) => generatorNames.includes(generator.name)) if (filtered.length !== generatorNames.length) { const missings = generatorNames.filter((name) => filtered.find((generator) => generator.name === name) == null) const isSingular = missings.length <= 1 throw new Error( `The ${isSingular ? 'generator' : 'generators'} ${bold(missings.join(', '))} specified via ${bold( '--generator', )} ${isSingular ? 'does' : 'do'} not exist in your Prisma schema`, ) } return filtered }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server