import type { Command } from '@prisma/sdk' ; import { arg , format , HelpError , isError , getSchemaPath , getCommandWithExecutor , isCi , logger , getConfig , getDMMF , } from '@prisma/sdk' ; import Debug from '@prisma/debug' ; import chalk from 'chalk' ; import prompt from 'prompts' ; import fs from 'fs' ; import path from 'path' ; import { Migrate } from '../Migrate' ; import type { DbType } from '../utils/ensureDatabaseExists' ; import { ensureDatabaseExists , getDbInfo } from '../utils/ensureDatabaseExists' ; import { ExperimentalFlagWithNewMigrateError , EarlyAccessFeatureFlagWithNewMigrateError } from '../utils/flagErrors' ; import { NoSchemaFoundError , MigrateDevEnvNonInteractiveError } from '../utils/errors' ; import { printMigrationId } from '../utils/printMigrationId' ; import { printFilesFromMigrationIds } from '../utils/printFiles' ; import { handleUnexecutableSteps } from '../utils/handleEvaluateDataloss' ; import { getMigrationName } from '../utils/promptForMigrationName' ; import { throwUpgradeErrorIfOldMigrate } from '../utils/detectOldMigrate' ; import { printDatasource } from '../utils/printDatasource' ; import { executeSeedCommand , verifySeedConfigAndReturnMessage , getSeedCommandFromPackageJson } from '../utils/seed' ; import type { EngineResults } from '../types' ; const debug = Debug ( 'prisma:migrate:dev' ) ; export class MigrateDev implements Command { public static new ( ) { return new MigrateDev ( ) ; } private static help = format ( `
${ process . platform === 'win32' ? '' : chalk . bold ( '🏋️  ' ) }Create a migration from changes in Prisma schema, apply it to the database, trigger generators (e.g. Prisma Client)
 
${ chalk . bold ( 'Usage' ) }

  ${ chalk . dim ( '$' ) } prisma migrate dev [options]

${ chalk . bold ( 'Options' ) }

       -h, --help   Display this help message
         --schema   Custom path to your Prisma schema
       -n, --name   Name the migration
    --create-only   Create a new migration but do not apply it
                    The migration will be empty if there are no changes in Prisma schema
  --skip-generate   Skip triggering generators (e.g. Prisma Client)
      --skip-seed   Skip triggering seed

${ chalk . bold ( 'Examples' ) }

  Create a migration from changes in Prisma schema, apply it to the database, trigger generators (e.g. Prisma Client)
  ${ chalk . dim ( '$' ) } prisma migrate dev

  Specify a schema
  ${ chalk . dim ( '$' ) } prisma migrate dev --schema=./schema.prisma

  Create a migration without applying it
  ${ chalk . dim ( '$' ) } prisma migrate dev --create-only
  ` ) ; public async parse ( argv ) { const args = arg ( argv , { '--help' : Boolean , '-h' : '--help' , '--name' : String , '-n' : '--name' , '--create-only' : Boolean , '--schema' : String , '--skip-generate' : Boolean , '--skip-seed' : Boolean , '--experimental' : Boolean , '--early-access-feature' : Boolean , '--telemetry-information' : String , } ) ; if ( isError ( args ) ) { return this . help ( args . message ) ; } if ( args [ '--help' ] ) { return this . help ( ) ; } if ( args [ '--experimental' ] ) { throw new ExperimentalFlagWithNewMigrateError ( ) ; } if ( args [ '--early-access-feature' ] ) { throw new EarlyAccessFeatureFlagWithNewMigrateError ( ) ; } const schemaPath = await getSchemaPath ( args [ '--schema' ] ) ; if ( ! schemaPath ) { throw new NoSchemaFoundError ( ) ; } console . info ( chalk . dim ( `Prisma schema loaded from ${ path . relative ( process . cwd ( ) , schemaPath ) }` ) ) ; await printDatasource ( schemaPath ) ; console . info ( ) ; throwUpgradeErrorIfOldMigrate ( schemaPath ) ; const schema = fs . readFileSync ( schemaPath , 'utf-8' ) ; await getDMMF ( { datamodel : schema , } ) ; await getConfig ( { datamodel : schema , } ) ; const wasDbCreated = await ensureDatabaseExists ( 'create' , true , schemaPath ) ; if ( wasDbCreated ) { console . info ( wasDbCreated ) ; console . info ( ) ; } const migrate = new Migrate ( schemaPath ) ; let devDiagnostic ; try { devDiagnostic = await migrate . devDiagnostic ( ) ; debug ( { devDiagnostic : JSON . stringify ( devDiagnostic , null , 2 ) } ) ; } catch ( e ) { migrate . stop ( ) ; throw e ; } const migrationIdsApplied = [ ] ; if ( devDiagnostic . action . tag === 'reset' ) { if ( ! args [ '--force' ] ) { if ( isCi ( ) && Boolean ( ( prompt as any ) . _injected ?. length ) === false ) { migrate . stop ( ) ; throw new MigrateDevEnvNonInteractiveError ( ) ; } const dbInfo = await getDbInfo ( schemaPath ) ; const confirmedReset = await this . confirmReset ( dbInfo , devDiagnostic . action . reason ) ; console . info ( ) ; if ( ! confirmedReset ) { console . info ( 'Reset cancelled.' ) ; migrate . stop ( ) ; process . exit ( 0 ) ; return `` ; } } try { await migrate . reset ( ) ; } catch ( e ) { migrate . stop ( ) ; throw e ; } } try { const { appliedMigrationNames } = await migrate . applyMigrations ( ) ; migrationIdsApplied . push ( ... appliedMigrationNames ) ; if ( appliedMigrationNames . length > 0 ) { console . info ( ) ; console . info ( `The following migration(s) have been applied:\n\n${ chalk ( printFilesFromMigrationIds ( 'migrations' , appliedMigrationNames , { 'migration.sql' : '' , } ) ) }` ) ; } } catch ( e ) { migrate . stop ( ) ; throw e ; } let evaluateDataLossResult ; try { evaluateDataLossResult = await migrate . evaluateDataLoss ( ) ; debug ( { evaluateDataLossResult } ) ; } catch ( e ) { migrate . stop ( ) ; throw e ; } const unexecutableStepsError = handleUnexecutableSteps ( evaluateDataLossResult . unexecutableSteps , args [ '--create-only' ] ) ; if ( unexecutableStepsError ) { migrate . stop ( ) ; throw new Error ( unexecutableStepsError ) ; } if ( evaluateDataLossResult . warnings && evaluateDataLossResult . warnings . length > 0 ) { console . log ( chalk . bold ( `\n⚠️  Warnings for the current datasource:\n` ) ) ; for ( const warning of evaluateDataLossResult . warnings ) { console . log ( chalk ( `  • ${ warning . message }` ) ) ; } console . info ( ) ; if ( ! args [ '--force' ] ) { if ( isCi ( ) && Boolean ( ( prompt as any ) . _injected ?. length ) === false ) { migrate . stop ( ) ; throw new MigrateDevEnvNonInteractiveError ( ) ; } const message = args [ '--create-only' ] ? 'Are you sure you want create this migration?' : 'Are you sure you want create and apply this migration?' ; const confirmation = await prompt ( { type : 'confirm' , name : 'value' , message , } ) ; if ( ! confirmation . value ) { migrate . stop ( ) ; return `Migration cancelled.` ; } } } let migrationName = undefined ; if ( evaluateDataLossResult . migrationSteps > 0 || args [ '--create-only' ] ) { const getMigrationNameResult = await getMigrationName ( args [ '--name' ] ) ; if ( getMigrationNameResult . userCancelled ) { migrate . stop ( ) ; return getMigrationNameResult . userCancelled ; } else { migrationName = getMigrationNameResult . name ; } } let migrationIds ; try { const createMigrationResult = await migrate . createMigration ( { migrationsDirectoryPath : migrate . migrationsDirectoryPath , migrationName : migrationName || '' , draft : args [ '--create-only' ] ? true : false , prismaSchema : migrate . getDatamodel ( ) , } ) ; debug ( { createMigrationResult } ) ; if ( args [ '--create-only' ] ) { migrate . stop ( ) ; return `Prisma Migrate created the following migration without applying it ${ printMigrationId ( createMigrationResult . generatedMigrationName ! ) }\n\nYou can now edit it and apply it by running ${ chalk . greenBright ( getCommandWithExecutor ( 'prisma migrate dev' ) ) }.` ; } const { appliedMigrationNames } = await migrate . applyMigrations ( ) ; migrationIds = appliedMigrationNames ; } finally { migrate . stop ( ) ; } migrationIdsApplied . length > 0 && console . info ( ) ; if ( migrationIds . length === 0 ) { if ( migrationIdsApplied . length > 0 ) { console . info ( `${ chalk . green ( 'Your database is now in sync with your schema.' ) }` ) ; } else { console . info ( `Already in sync, no schema change or pending migration was found.` ) ; } } else { console . info ( ) ; console . info ( `The following migration(s) have been created and applied from new schema changes:\n\n${ chalk ( printFilesFromMigrationIds ( 'migrations' , migrationIds , { 'migration.sql' : '' , } ) ) }

${ chalk . green ( 'Your database is now in sync with your schema.' ) }` ) ; } if ( ! process . env . PRISMA_MIGRATE_SKIP_GENERATE && ! args [ '--skip-generate' ] ) { await migrate . tryToRunGenerate ( ) ; console . info ( ) ; } if ( ( wasDbCreated || devDiagnostic . action . tag === 'reset' ) && ! process . env . PRISMA_MIGRATE_SKIP_SEED && ! args [ '--skip-seed' ] ) { try { const seedCommandFromPkgJson = await getSeedCommandFromPackageJson ( process . cwd ( ) ) ; if ( seedCommandFromPkgJson ) { console . info ( ) ; const successfulSeeding = await executeSeedCommand ( seedCommandFromPkgJson ) ; if ( successfulSeeding ) { console . info ( `\n${ process . platform === 'win32' ? '' : '🌱  ' }The seed command has been executed.\n` ) ; } else { console . info ( ) ; } } else { const schemaPath = await getSchemaPath ( args [ '--schema' ] ) ; await verifySeedConfigAndReturnMessage ( schemaPath ) ; } } catch ( e ) { console . error ( e ) ; } } return '' ; } private async confirmReset ( { schemaWord , dbType , dbName , dbLocation , } , reason ) { const mssqlMessage = `We need to reset the database.
Do you want to continue? ${ chalk . red ( 'All data will be lost' ) }.` ; const message = `We need to reset the ${ dbType } ${ schemaWord } "${ dbName }" at "${ dbLocation }".
Do you want to continue? ${ chalk . red ( 'All data will be lost' ) }.` ; console . info ( reason ) ; const confirmation = await prompt ( { type : 'confirm' , name : 'value' , message : dbType === 'SQL Server' ? mssqlMessage : message , } ) ; return confirmation . value ; } public help ( error ? ) { if ( error ) { return new HelpError ( `\n${ chalk . bold . red ( `!` ) } ${ error }\n${ MigrateDev . help }` ) ; } return MigrateDev . help ; } }