import fs from 'fs' ; import { promisify } from 'util' ; import path from 'path' ; import { getDMMF , getConfig } from '@prisma/sdk' ; import { deserializeEnums , deserializeDatasources , deserializeModels , deserializeGenerators } from './deserializer' ; import { DataSource , DMMF , GeneratorConfig } from '@prisma/generator-helper/dist' ; import glob from 'glob' ; const readFile = promisify ( fs . readFile ) ; const writeFile = promisify ( fs . writeFile ) ; export interface MixerOptions { input : string [ ] ; output : string ; } export interface PrismixOptions { mixers : MixerOptions [ ] ; } type UnPromisify < T > = T extends Promise < infer U > ? U : T ; type Schema = NonNullable < UnPromisify < ReturnType < typeof getSchema > > > ; async function getSchema ( schemaPath ) { try { const schema = await readFile ( path . join ( process . cwd ( ) , schemaPath ) , { encoding : 'utf-8' } ) ; const dmmf = await getDMMF ( { datamodel : schema } ) ; const config = await getConfig ( { datamodel : schema } ) ; return { models : dmmf . datamodel . models as DMMF . Model [ ] , enums : dmmf . datamodel . enums , datasources : config . datasources , generators : config . generators } ; } catch ( e ) { console . error ( `Prismix failed to parse schema located at "${ schemaPath }". Did you attempt to reference to a model without creating an alias? Remember you must define a "blank" alias model with only the "@id" field in your extended schemas otherwise we can't parse your schema.` , e ) ; } } function mixModels ( inputModels ) { const models = { } ; for ( const newModel of inputModels ) { const existingModel = models [ newModel . name ] ; if ( existingModel ) { const existingFieldNames = existingModel . fields . map ( ( f ) => f . name ) ; for ( const newField of newModel . fields ) { if ( existingFieldNames . includes ( newField . name ) ) { const existingFieldIndex = existingFieldNames . indexOf ( newField . name ) ; existingModel . fields [ existingFieldIndex ] = newField ; } else { existingModel . fields . push ( newField ) ; } } } else { models [ newModel . name ] = newModel ; } } return Object . values ( models ) ; } export async function prismix ( options ) { for ( const mixer of options . mixers ) { const schemasToMix = [ ] ; for ( const input of mixer . input ) { for ( const file of glob . sync ( input ) ) { const parsedSchema = await getSchema ( file ) ; if ( parsedSchema ) schemasToMix . push ( parsedSchema ) ; } } let models = [ ] ; for ( const schema of schemasToMix ) models = [ ... models , ... schema . models ] ; models = mixModels ( models ) ; let enums = [ ] ; schemasToMix . forEach ( ( schema ) => ! ! schema . enums && ( enums = [ ... enums , ... schema . enums ] ) ) ; let datasources = [ ] ; schemasToMix . forEach ( ( schema ) => schema . datasources . length > 0 && schema . datasources . filter ( ( d ) => d . url . value ) . length > 0 && ( datasources = schema . datasources ) ) ; let generators = [ ] ; schemasToMix . forEach ( ( schema ) => schema . generators . length > 0 && ( generators = schema . generators ) ) ; let outputSchema = [ '// *** GENERATED BY PRISMIX :: DO NOT EDIT ***' , await deserializeDatasources ( datasources ) , await deserializeGenerators ( generators ) , await deserializeModels ( models ) , await deserializeEnums ( enums ) ] . filter ( ( e ) => e ) . join ( '\n' ) ; await writeFile ( path . join ( process . cwd ( ) , mixer . output ) , outputSchema ) ; } }