MigrateResolve.ts•5.71 kB
import type { PrismaConfigInternal } from '@prisma/config'
import {
  arg,
  checkUnsupportedDataProxy,
  Command,
  format,
  getCommandWithExecutor,
  HelpError,
  inferDirectoryConfig,
  isError,
  link,
  loadEnvFile,
  loadSchemaContext,
} from '@prisma/internals'
import { bold, dim, green, red } from 'kleur/colors'
import { Migrate } from '../Migrate'
import { ensureCanConnectToDatabase, parseDatasourceInfo } from '../utils/ensureDatabaseExists'
import { printDatasource } from '../utils/printDatasource'
export class MigrateResolve implements Command {
  public static new(): MigrateResolve {
    return new MigrateResolve()
  }
  private static help = format(`
Resolve issues with database migrations in deployment databases: 
- recover from failed migrations
- baseline databases when starting to use Prisma Migrate on existing databases
- reconcile hotfixes done manually on databases with your migration history
Run "prisma migrate status" to identify if you need to use resolve.
Read more about resolving migration history issues: ${link('https://pris.ly/d/migrate-resolve')}
 
${bold('Usage')}
  ${dim('$')} prisma migrate resolve [options]
  
${bold('Options')}
    -h, --help   Display this help message
      --config   Custom path to your Prisma config file
      --schema   Custom path to your Prisma schema
     --applied   Record a specific migration as applied
 --rolled-back   Record a specific migration as rolled back
${bold('Examples')}
  Update migrations table, recording a specific migration as applied 
  ${dim('$')} prisma migrate resolve --applied 20201231000000_add_users_table
  Update migrations table, recording a specific migration as rolled back
  ${dim('$')} prisma migrate resolve --rolled-back 20201231000000_add_users_table
  Specify a schema
  ${dim('$')} prisma migrate resolve --rolled-back 20201231000000_add_users_table --schema=./schema.prisma
`)
  public async parse(argv: string[], config: PrismaConfigInternal): Promise<string | Error> {
    const args = arg(
      argv,
      {
        '--help': Boolean,
        '-h': '--help',
        '--applied': String,
        '--rolled-back': String,
        '--schema': String,
        '--config': String,
        '--telemetry-information': String,
      },
      false,
    )
    if (isError(args)) {
      return this.help(args.message)
    }
    if (args['--help']) {
      return this.help()
    }
    await loadEnvFile({ schemaPath: args['--schema'], printMessage: true, config })
    const schemaContext = await loadSchemaContext({
      schemaPathFromArg: args['--schema'],
      schemaPathFromConfig: config.schema,
      schemaEngineConfig: config,
    })
    const { migrationsDirPath } = inferDirectoryConfig(schemaContext, config)
    const adapter = config.engine === 'js' ? await config.adapter() : undefined
    checkUnsupportedDataProxy({ cmd: 'migrate resolve', schemaContext })
    printDatasource({ datasourceInfo: parseDatasourceInfo(schemaContext.primaryDatasource), adapter })
    // if both are not defined
    if (!args['--applied'] && !args['--rolled-back']) {
      throw new Error(
        `--applied or --rolled-back must be part of the command like:
${bold(green(getCommandWithExecutor('prisma migrate resolve --applied 20201231000000_example')))}
${bold(green(getCommandWithExecutor('prisma migrate resolve --rolled-back 20201231000000_example')))}`,
      )
    }
    // if both are defined
    else if (args['--applied'] && args['--rolled-back']) {
      throw new Error('Pass either --applied or --rolled-back, not both.')
    }
    if (args['--applied']) {
      if (typeof args['--applied'] !== 'string' || args['--applied'].length === 0) {
        throw new Error(
          `--applied value must be a string like ${bold(
            green(getCommandWithExecutor('prisma migrate resolve --applied 20201231000000_example')),
          )}`,
        )
      }
      // `ensureCanConnectToDatabase` is not compatible with WebAssembly.
      // TODO: check why the output and error handling here is different than in `MigrateDeploy`.
      if (!adapter) {
        await ensureCanConnectToDatabase(schemaContext.primaryDatasource)
      }
      const migrate = await Migrate.setup({
        schemaEngineConfig: config,
        migrationsDirPath,
        schemaContext,
        extensions: config['extensions'],
      })
      try {
        await migrate.markMigrationApplied({
          migrationId: args['--applied'],
        })
      } finally {
        await migrate.stop()
      }
      process.stdout.write(`\nMigration ${args['--applied']} marked as applied.\n`)
      return ``
    } else {
      if (typeof args['--rolled-back'] !== 'string' || args['--rolled-back'].length === 0) {
        throw new Error(
          `--rolled-back value must be a string like ${bold(
            green(getCommandWithExecutor('prisma migrate resolve --rolled-back 20201231000000_example')),
          )}`,
        )
      }
      await ensureCanConnectToDatabase(schemaContext.primaryDatasource)
      const migrate = await Migrate.setup({
        schemaEngineConfig: config,
        migrationsDirPath,
        schemaContext,
        extensions: config['extensions'],
      })
      try {
        await migrate.markMigrationRolledBack({
          migrationId: args['--rolled-back'],
        })
      } finally {
        await migrate.stop()
      }
      process.stdout.write(`\nMigration ${args['--rolled-back']} marked as rolled back.\n`)
      return ``
    }
  }
  public help(error?: string): string | HelpError {
    if (error) {
      return new HelpError(`\n${bold(red(`!`))} ${error}\n${MigrateResolve.help}`)
    }
    return MigrateResolve.help
  }
}