Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,210
  • Linux
  • Apple
run.ts9.68 kB
import os from 'node:os' import { finished } from 'node:stream/promises' import { arg } from '@prisma/internals' import { createReadStream, existsSync } from 'fs' import fs from 'fs/promises' import glob from 'globby' import path from 'path' import { $, ProcessOutput, sleep } from 'zx' const monorepoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..') const e2eRoot = path.join(monorepoRoot, 'packages', 'client', 'tests', 'e2e') const args = arg( process.argv.slice(2), { // see which commands are run and the outputs of the failures '--verbose': Boolean, // like run jest in band, useful for debugging and CI '--runInBand': Boolean, '--run-in-band': '--runInBand', // do not fully pack cli and client packages before packing '--skipPack': Boolean, '--skip-pack': '--skipPack', // a way to cleanup created files that also works on linux '--clean': Boolean, // number of workers to use for parallel tests '--maxWorkers': Number, '--max-workers': '--maxWorkers', }, true, true, ) async function main() { if (args instanceof Error) { console.log(args.message) process.exit(1) } args['--maxWorkers'] = args['--maxWorkers'] ?? os.cpus().length args['--runInBand'] = args['--runInBand'] ?? false args['--skipPack'] = args['--skipPack'] ?? false args['--verbose'] = args['--verbose'] ?? false args['--clean'] = args['--clean'] ?? false $.verbose = args['--verbose'] if (args['--verbose'] === true) { await $`docker -v` } console.log('🧹 Cleaning up old files') if (args['--clean'] === true) { await $`docker compose -f ${__dirname}/docker-compose-clean.yml down --remove-orphans` await $`docker compose -f ${__dirname}/docker-compose-clean.yml up clean` } console.log('🎠 Preparing e2e tests') let allPackageFolderNames = await fs.readdir(path.join(monorepoRoot, 'packages')) allPackageFolderNames = allPackageFolderNames.filter((p) => !p.includes('DS_Store')) const prismaTmpDir = path.join(os.homedir(), '.local', 'share', 'prisma-tmp') if (args['--skipPack'] === false) { // this process will need to modify some package.json, we save copies await $`pnpm -r exec cp package.json package.copy.json` // we prepare to replace references to local packages with their tarballs names const localPackageNames = [...allPackageFolderNames.map((p) => `@prisma/${p}`), 'prisma'] const allPackageFolders = allPackageFolderNames.map((p) => path.join(monorepoRoot, 'packages', p)) const allPkgJsonPaths = allPackageFolders.map((p) => path.join(p, 'package.json')) const allPkgJson = allPkgJsonPaths.map((p) => require(p)) // replace references to unbundled local packages with built and packaged tarballs for (let i = 0; i < allPkgJson.length; i++) { for (const key of Object.keys(allPkgJson[i].dependencies ?? {})) { if (localPackageNames.includes(key)) { allPkgJson[i].dependencies[key] = `/tmp/${key.replace('@prisma/', 'prisma-')}-0.0.0.tgz` } } await fs.writeFile(allPkgJsonPaths[i], JSON.stringify(allPkgJson[i], null, 2)) } await $`pnpm -r --parallel exec pnpm pack --pack-destination ${prismaTmpDir}/` await restoreOriginalState() } console.log('🐳 Starting tests in docker') // tarball was created, ready to send it to docker and begin e2e tests const testStepFiles = await glob(['../**/_steps.ts', '../**/_steps.cts'], { cwd: __dirname }) let e2eTestNames = testStepFiles.map((p) => path.relative('..', path.dirname(p))) if (args._.length > 0) { e2eTestNames = e2eTestNames.filter((p) => args._.some((a) => p.includes(a))) } const dockerVolumes = [ `${prismaTmpDir}/prisma-0.0.0.tgz:/tmp/prisma-0.0.0.tgz`, // hardcoded because folder doesn't match name ...allPackageFolderNames.map((p) => `${prismaTmpDir}/prisma-${p}-0.0.0.tgz:/tmp/prisma-${p}-0.0.0.tgz`), `${path.join(monorepoRoot, 'packages', 'engines')}:/engines`, `${path.join(monorepoRoot, 'packages', 'client')}:/client`, `${e2eRoot}:/e2e`, `${path.join(e2eRoot, '.cache')}:/root/.cache`, `${(await $`pnpm store path`.quiet()).stdout.trim()}:/root/.local/share/pnpm/store/v3`, ] const dockerVolumeArgs = dockerVolumes.flatMap((v) => ['-v', v]) await $`docker compose -f ${__dirname}/docker-compose.yaml build test-e2e` const dockerJobs = e2eTestNames.map((testPath) => { const composeFileArgs = ['-f', `${__dirname}/docker-compose.yaml`] const localComposePath = path.join(e2eRoot, testPath, 'docker-compose.yaml') if (existsSync(localComposePath)) { composeFileArgs.push('-f', localComposePath) } const projectName = testPath.toLocaleLowerCase().replace(/[^0-9a-z_-]/g, '-') const networkName = `${projectName}_default` return async () => { const result = await $`docker compose ${composeFileArgs} -p ${projectName} run --rm ${dockerVolumeArgs} -e "NAME=${testPath}" test-e2e`.nothrow() await $`docker compose ${composeFileArgs} -p ${projectName} logs > ${path.join( e2eRoot, testPath, 'LOGS.docker.txt', )}` await $`docker compose ${composeFileArgs} -p ${projectName} stop` await $`docker compose ${composeFileArgs} -p ${projectName} rm -f` await $`docker network rm -f ${networkName}` return result } }) const jobResults: (ProcessOutput & { name: string })[] = [] if (args['--runInBand'] === true) { console.log('🏃 Running tests in band') for (const [i, job] of dockerJobs.entries()) { console.log(`💡 Running test ${i + 1}/${dockerJobs.length}`) jobResults.push(Object.assign(await job(), { name: e2eTestNames[i] })) } } else { console.log('🏃 Running tests in parallel') const pendingJobResults = [] as Promise<void>[] const semaphore = new Semaphore(args['--maxWorkers']) for (const [i, job] of dockerJobs.entries()) { await semaphore.acquire() const pendingJob = (async () => { console.log(`💡 Running test ${i + 1}/${dockerJobs.length}`) jobResults.push(Object.assign(await job(), { name: e2eTestNames[i] })) })() pendingJobResults.push(pendingJob.finally(() => semaphore.release())) } await Promise.allSettled(pendingJobResults) } const failedJobResults = jobResults.filter((r) => r.exitCode !== 0) const passedJobResults = jobResults.filter((r) => r.exitCode === 0) if (args['--verbose'] === true) { for (const result of failedJobResults) { console.log(`-----------------------------------------------------------------------`) console.log(`🛑🛑🛑 Test "${result.name}" failed with exit code ${result.exitCode} 🛑🛑🛑`) console.log(`\t\t⬇️ Container Log output below ⬇️\n\n`) console.log(`-----------------------------------------------------------------------`) const logsPath = path.resolve(__dirname, '..', result.name, 'LOGS.txt') const dockerLogsPath = path.resolve(__dirname, '..', result.name, 'LOGS.docker.txt') if (await isFile(logsPath)) { await printFile(logsPath) } else if (await isFile(dockerLogsPath)) { await printFile(dockerLogsPath) } await sleep(50) // give some time for the logs to be printed (CI issue) console.log(`-----------------------------------------------------------------------`) console.log(`🛑 ⬆️ Container Log output of test failure "${result.name}" above ⬆️ 🛑`) console.log(`-----------------------------------------------------------------------`) } } // let the tests run and gather a list of logs for containers that have failed if (failedJobResults.length > 0) { const failedJobLogPaths = failedJobResults.map((result) => path.resolve(__dirname, '..', result.name, 'LOGS.txt')) console.log(`-----------------------------------------------------------------------`) console.log(`✅ ${passedJobResults.length}/${jobResults.length} tests passed`) console.log(`🛑 ${failedJobResults.length}/${jobResults.length} tests failed`, failedJobLogPaths) throw new Error('Some tests exited with a non-zero exit code') } else { console.log(`-----------------------------------------------------------------------`) console.log(`✅ All ${passedJobResults.length}/${jobResults.length} tests passed`) } } async function restoreOriginalState() { if (args['--skipPack'] === false) { await $`pnpm -r exec cp package.copy.json package.json` } } async function printFile(filePath: string) { try { const fileStream = createReadStream(filePath) fileStream.pipe(process.stdout, { end: false }) await finished(fileStream) } catch (err) { console.error(`Error trying to print log file "${filePath}":`, err) } } async function isFile(filePath: string) { try { const stat = await fs.stat(filePath) return stat.isFile() } catch (e) { if (e.code === 'ENOENT') { return false } throw e } } class Semaphore { #permits: number #waiting: Array<() => void> = [] constructor(permits: number) { this.#permits = permits } async acquire(): Promise<void> { if (this.#permits > 0) { this.#permits-- } else { await new Promise<void>((resolve) => this.#waiting.push(resolve)) } } release(): void { if (this.#waiting.length > 0) { this.#waiting.shift()!() } else { this.#permits++ } } } process.on('SIGINT', async () => { await restoreOriginalState() process.exit(0) }) void main().catch((e) => { console.log(e) void restoreOriginalState() process.exit(1) })

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