UNPKG

prismaql

Version:

A powerful tool for managing and editing Prisma schema files using a SQL-like DSL.

162 lines 6.75 kB
import fs from "fs"; import fsx from "fs-extra"; import path from "path"; import { getSchema, createPrismaSchemaBuilder } from "@mrleebo/prisma-ast"; import { validatePrismaSchema } from "./utils/prisma-validation.js"; import { loadPrismaSchema } from "./utils/load-prisma-schema.js"; import pkg from '@prisma/internals'; const { getDMMF } = pkg; import { PrismaHighlighter } from "prismalux"; import chalk from "chalk"; const HighlightPrismaSchema = new PrismaHighlighter(); export class PrismaQlSchemaLoader { relationCollector; options; lastValidatedSchema = null; prismaState = null; backupPath = null; cwd; constructor(relationCollector, options = {}) { this.relationCollector = relationCollector; this.options = options; if (options.backupPath) { this.backupPath = options.backupPath; } this.cwd = options?.cwd || process.cwd(); } async rebase() { const schema = this.prismaState?.builder.print({ sort: true }); const parsedSchema = getSchema(schema); const builder = createPrismaSchemaBuilder(schema); this.setPrismaState({ schemaPath: this.prismaState?.schemaPath, schema: schema, ast: parsedSchema, builder, relations: this.relationCollector.getRelations() }); await this.collectRelations(); } getSchemaPath() { return this.prismaState?.schemaPath; } setPrismaState(newState) { this.prismaState = newState; } async loadFromFile(filePath, forceReload = false) { if (this.prismaState && !forceReload) { return this.prismaState; } const { schema, path } = await loadPrismaSchema(this.cwd, filePath); return this.prepareSchema(schema, path); } async collectRelations() { const prismaSchema = this.prismaState?.schema || ''; const dmmf = await getDMMF({ datamodel: prismaSchema }); const models = dmmf.datamodel.models; return this.relationCollector.setModels(models); } async prepareSchema(sourcePrismaSchema, schemaPath) { const isValid = await this.isValid(sourcePrismaSchema); if (isValid instanceof Error) { throw isValid; } const parsedSchema = getSchema(sourcePrismaSchema); const builder = createPrismaSchemaBuilder(sourcePrismaSchema); this.setPrismaState({ schemaPath: schemaPath || '', schema: sourcePrismaSchema, ast: parsedSchema, builder, relations: this.relationCollector.getRelations() }); await this.collectRelations(); return this.prismaState; } loadFromText(sourcePrismaSchema) { return this.prepareSchema(sourcePrismaSchema); } async getState() { if (!this.prismaState) { await this.loadFromFile(); } const relations = this.relationCollector.getRelations(); return { ...this.prismaState || {}, relations }; } clonePrismaState() { if (!this.prismaState) { throw new Error("No schema loaded."); } const { schema: sourcePrismaSchema, schemaPath } = this.prismaState; const cloneBuilder = createPrismaSchemaBuilder(sourcePrismaSchema); const parsedSchema = getSchema(sourcePrismaSchema); return { schemaPath, schema: sourcePrismaSchema, ast: parsedSchema, builder: cloneBuilder, relations: this.relationCollector.getRelations() }; } async save(commits, sourcePath) { if (!this.prismaState) { throw new Error('No schema loaded to save. Please load a schema first.'); } const messages = Array.isArray(commits) ? commits : [commits]; console.log(`🔄 Saving schema with ${messages.length} commit(s):`); for (let i = 0; i < messages.length; i++) { const message = messages[i]; const commitNumber = i + 1; console.log(chalk.grey(`Commit ${commitNumber}:`), chalk.cyanBright(message)); } let outputPath = sourcePath; if (sourcePath && !path.isAbsolute(sourcePath)) { outputPath = path.join(this.cwd, sourcePath); } if (!this.prismaState?.schemaPath && !outputPath) { throw new Error('Cannot save schema without a path, please provide a path!'); } try { this.check(); } catch (e) { throw new Error('Cannot save invalid schema. Please fix the schema before saving.'); } const finalPath = outputPath || this.prismaState.schemaPath; if (!finalPath) { throw new Error('Cannot save schema without a path, please provide a path!'); } // ✅ Generate timestamped backup if (fs.existsSync(finalPath)) { const backupDir = this.backupPath ? this.backupPath : path.join(path.dirname(finalPath), ".prisma", "backups"); fsx.ensureDirSync(backupDir); const backupPath = path.join(backupDir, `${path.basename(finalPath)}_${new Date().toISOString().replace(/[:.]/g, "-")}.bak.prisma`); fsx.copyFileSync(finalPath, backupPath); } // ✅ Print the schema using builder (formatted output) const updatedsourcePrismaSchema = this.prismaState.builder.print({ sort: true }); // ✅ Save schema fs.writeFileSync(finalPath, updatedsourcePrismaSchema, "utf-8"); console.log(chalk.greenBright(`✅ Schema saved successfully to ${finalPath}`)); console.log(chalk.grey(`📅 Timestamp: ${new Date().toLocaleString()}`)); } print() { if (!this.prismaState) { return "No schema loaded."; } return HighlightPrismaSchema.highlight(this.prismaState.builder.print({ sort: true })); } async isValid(sourceSchema) { if (!sourceSchema && !this.prismaState) { throw new Error("No schema loaded."); } const sourcePrismaSchema = sourceSchema || this.prismaState?.builder.print({ sort: true }); if (!sourcePrismaSchema) { return new Error("No schema content provided."); } if (this.lastValidatedSchema === sourcePrismaSchema) { return true; } const validation = await validatePrismaSchema(sourcePrismaSchema); if (validation === true) { this.lastValidatedSchema = sourcePrismaSchema; } else { this.lastValidatedSchema = null; } return validation; } check() { if (!this.prismaState) { throw new Error("No schema loaded."); } this.isValid(); } } export default PrismaQlSchemaLoader; //# sourceMappingURL=prisma-schema-loader.js.map