Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): add migrations commands to cli
- Loading branch information
Showing
23 changed files
with
855 additions
and
304 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ services: | |
- postgresql | ||
|
||
cache: | ||
yarn: true | ||
directories: | ||
- 'node_modules' | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
--- | ||
--- | ||
|
||
# Migrations | ||
|
||
To generate entities from existing database schema, you can use `Migrator` helper. It uses | ||
[umzug](https://github.com/sequelize/umzug) under the hood. | ||
|
||
## Migration class | ||
|
||
Migrations are classes that extend Migration abstract class: | ||
|
||
```typescript | ||
export class Migration20191019195930 extends Migration { | ||
|
||
async up(): Promise<void> { | ||
this.addSql('select 1 + 1'); | ||
} | ||
|
||
} | ||
``` | ||
|
||
To support migrating back, you can implement `down` method, which by default throws an error. | ||
|
||
Transactions are by default wrapped in a transactions. You can override this behaviour on | ||
per transaction basis by implementing `isTransactional(): boolean` method. | ||
|
||
`Configuration` object and driver instance are available in the `Migration` class context. | ||
|
||
## Configuration | ||
|
||
```typescript | ||
await MikroORM.init({ | ||
// default values: | ||
migrations: { | ||
tableName: 'mikro_orm_migrations', | ||
path: './migrations', | ||
transactional: true, | ||
disableForeignKeys: true, | ||
}, | ||
}) | ||
``` | ||
|
||
## Using via CLI | ||
|
||
You can use it via CLI: | ||
|
||
```sh | ||
npx mikro-orm migration:create # Create new migration with current schema diff | ||
npx mikro-orm migration:up # Migrate up to the latest version | ||
npx mikro-orm migration:down # Migrate one step down | ||
npx mikro-orm migration:list # List all executed migrations | ||
npx mikro-orm migration:pending # List all pending migrations | ||
``` | ||
|
||
For `migration:up` and `migration:down` commands you can specify `--from` (`-f`), `--to` (`-t`) | ||
and `--only` (`-o`) options to run only a subset of migrations: | ||
|
||
```sh | ||
npx mikro-orm migration:up --from 2019101911 --to 2019102117 # the same as above | ||
npx mikro-orm migration:up --only 2019101923 # apply a single migration | ||
npx mikro-orm migration:down --to 0 # migratee down all migrations | ||
``` | ||
|
||
> To run TS migration files, you will need to [enable `useTsNode` flag](installation.md) | ||
> in your `package.json`. | ||
## Using the Migrator programmatically | ||
|
||
Or you can create simple script where you initialize MikroORM like this: | ||
|
||
**`./migrate.ts`** | ||
|
||
```typescript | ||
import { MikroORM } from 'mikro-orm'; | ||
|
||
(async () => { | ||
const orm = await MikroORM.init({ | ||
dbName: 'your-db-name', | ||
// ... | ||
}); | ||
|
||
const migrator = orm.getMigrator(); | ||
await migrator.createMigration(); // creates file Migration20191019195930.ts | ||
await migrator.up(); // runs migrations up to the latest | ||
await migrator.up('up-to-name'); // runs migrations up to given version | ||
await migrator.down('down-to-name'); // runs migrations down to given version | ||
await migrator.down(); // migrates one step down | ||
await migrator.down({ to: 0 }); // migrates down to the first version | ||
|
||
await orm.close(true); | ||
})(); | ||
``` | ||
|
||
Then run this script via `ts-node` (or compile it to plain JS and use `node`): | ||
|
||
```sh | ||
$ ts-node migrate | ||
``` | ||
|
||
[← Back to table of contents](index.md#table-of-contents) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { Arguments, Argv, CommandModule } from 'yargs'; | ||
import chalk from 'chalk'; | ||
import { CLIHelper } from './CLIHelper'; | ||
import { Utils } from '../utils'; | ||
|
||
type MigratorMethod = 'create' | 'up' | 'down' | 'list' | 'pending'; | ||
|
||
export class MigrationCommandFactory { | ||
|
||
static readonly DESCRIPTIONS = { | ||
create: 'Create new migration with current schema diff', | ||
up: 'Migrate up to the latest version', | ||
down: 'Migrate one step down', | ||
list: 'List all executed migrations', | ||
pending: 'List all pending migrations', | ||
}; | ||
|
||
static readonly SUCCESS_MESSAGES = { | ||
create: (name?: string) => `${name} successfully created`, | ||
up: (name?: string) => `Successfully migrated up to ${name ? `version ${name}` : 'the latest version'}`, | ||
down: (name?: string) => `Successfully migrated down to ${name ? `version ${name}` : 'the first version'}`, | ||
}; | ||
|
||
static create<U extends Options = Options>(command: MigratorMethod): CommandModule<{}, U> & { builder: (args: Argv) => Argv<U>; handler: (args: Arguments<U>) => Promise<void> } { | ||
return { | ||
command: `migration:${command}`, | ||
describe: MigrationCommandFactory.DESCRIPTIONS[command], | ||
builder: (args: Argv) => MigrationCommandFactory.configureMigrationCommand(args, command) as Argv<U>, | ||
handler: (args: Arguments<U>) => MigrationCommandFactory.handleMigrationCommand(args, command), | ||
}; | ||
} | ||
|
||
static configureMigrationCommand(args: Argv, method: MigratorMethod) { | ||
if (method === 'create') { | ||
args.option('b', { | ||
alias: 'blank', | ||
type: 'boolean', | ||
desc: 'Create blank migration', | ||
}); | ||
args.option('d', { | ||
alias: 'dump', | ||
type: 'boolean', | ||
desc: 'Dumps all queries to console', | ||
}); | ||
args.option('disable-fk-checks', { | ||
type: 'boolean', | ||
desc: 'Do not skip foreign key checks', | ||
}); | ||
args.option('p', { | ||
alias: 'path', | ||
type: 'string', | ||
desc: 'Sets path to directory where to save entities', | ||
}); | ||
} | ||
|
||
if (['up', 'down'].includes(method)) { | ||
args.option('t', { | ||
alias: 'to', | ||
type: 'string', | ||
desc: `Migrate ${method} to specific version`, | ||
}); | ||
args.option('f', { | ||
alias: 'from', | ||
type: 'string', | ||
desc: 'Start migration from specific version', | ||
}); | ||
args.option('o', { | ||
alias: 'only', | ||
type: 'string', | ||
desc: 'Migrate only specified versions', | ||
}); | ||
} | ||
|
||
return args; | ||
} | ||
|
||
static async handleMigrationCommand(args: Arguments<Options>, method: MigratorMethod) { | ||
const successMessage = MigrationCommandFactory.SUCCESS_MESSAGES[method]; | ||
const orm = await CLIHelper.getORM(); | ||
const migrator = orm.getMigrator(); | ||
|
||
switch (method) { | ||
case 'create': | ||
const ret = await migrator.createMigration(args.path, args.blank); | ||
|
||
if (args.dump) { | ||
CLIHelper.dump(chalk.green('Creating migration with following queries:')); | ||
CLIHelper.dump(ret[2].map(sql => ' ' + sql).join('\n'), orm.config, 'sql'); | ||
} | ||
|
||
CLIHelper.dump(chalk.green(successMessage(ret[1]))); | ||
break; | ||
case 'list': | ||
const executed = await migrator.getExecutedMigrations(); | ||
|
||
CLIHelper.dumpTable({ | ||
columns: ['Name', 'Executed at'], | ||
rows: executed.map(row => [row.name.replace(/\.[jt]s$/, ''), row.executed_at.toISOString()]), | ||
empty: 'No migrations executed yet', | ||
}); | ||
break; | ||
case 'pending': | ||
const pending = await migrator.getPendingMigrations(); | ||
CLIHelper.dumpTable({ | ||
columns: ['Name'], | ||
rows: pending.map(row => [row.file.replace(/\.[jt]s$/, '')]), | ||
empty: 'No pending migrations', | ||
}); | ||
break; | ||
case 'up': | ||
case 'down': | ||
await migrator[method as 'up' | 'down'](MigrationCommandFactory.getUpDownOptions(args) as string[]); | ||
CLIHelper.dump(chalk.green(successMessage(Utils.isString(args) ? args : args.to))); | ||
} | ||
|
||
await orm.close(true); | ||
} | ||
|
||
private static getUpDownOptions(flags: Arguments<Options>) { | ||
if (!flags.to && !flags.from && flags.only) { | ||
return flags.only.split(/[, ]+/); | ||
} | ||
|
||
['from', 'to'].forEach(k => flags[k] === '0' ? flags[k] = 0 : flags[k]); | ||
|
||
return flags; | ||
} | ||
|
||
} | ||
|
||
export type Options = { dump: boolean; blank: boolean; path: string; target: string; disableFkChecks: boolean; to: string | number; from: string | number; only: string }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.