From df103d6864b0aee3c5c402f0abf3e8bbbf9904d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Sun, 18 Aug 2019 17:17:31 +0200 Subject: [PATCH] feat(cli): add basic CLI tool Closes #101 --- ents/Author3.ts | 36 ++++++++++++++ ents/Book3.ts | 18 +++++++ ents/Book3ToBookTag3.ts | 15 ++++++ ents/BookTag3.ts | 15 ++++++ ents/Publisher3.ts | 15 ++++++ ents/Publisher3ToTest3.ts | 15 ++++++ ents/Test3.ts | 15 ++++++ foo_bar | 0 lib/MikroORM.ts | 2 +- lib/cache/CacheAdapter.ts | 2 + lib/cache/FileCacheAdapter.ts | 12 ++++- lib/cache/NullCacheAdapter.ts | 4 ++ lib/cli.ts | 14 ++++++ lib/cli/CLIHelper.ts | 58 +++++++++++++++++++++++ lib/cli/ClearCacheCommand.ts | 19 ++++++++ lib/cli/CreateSchemaCommand.ts | 38 +++++++++++++++ lib/cli/DropSchemaCommand.ts | 38 +++++++++++++++ lib/cli/GenerateEntitiesCommand.ts | 48 +++++++++++++++++++ lib/cli/UpdateSchemaCommand.ts | 38 +++++++++++++++ package.json | 11 ++++- tests/FileCacheAdapter.test.ts | 1 + tests/NullCacheAdapter.test.ts | 1 + tests/cli-config.ts | 13 +++++ tests/cli/CLIHelper.test.ts | 44 +++++++++++++++++ tests/cli/ClearCacheCommand.test.ts | 20 ++++++++ tests/cli/CreateSchemaCommand.test.ts | 43 +++++++++++++++++ tests/cli/DropSchemaCommand.test.ts | 43 +++++++++++++++++ tests/cli/GenerateEntitiesCommand.test.ts | 44 +++++++++++++++++ tests/cli/UpdateSchemaCommand.test.ts | 43 +++++++++++++++++ tsconfig.json | 3 +- yarn.lock | 36 ++++++++++---- 31 files changed, 691 insertions(+), 13 deletions(-) create mode 100644 ents/Author3.ts create mode 100644 ents/Book3.ts create mode 100644 ents/Book3ToBookTag3.ts create mode 100644 ents/BookTag3.ts create mode 100644 ents/Publisher3.ts create mode 100644 ents/Publisher3ToTest3.ts create mode 100644 ents/Test3.ts create mode 100644 foo_bar create mode 100644 lib/cli.ts create mode 100644 lib/cli/CLIHelper.ts create mode 100644 lib/cli/ClearCacheCommand.ts create mode 100644 lib/cli/CreateSchemaCommand.ts create mode 100644 lib/cli/DropSchemaCommand.ts create mode 100644 lib/cli/GenerateEntitiesCommand.ts create mode 100644 lib/cli/UpdateSchemaCommand.ts create mode 100644 tests/cli-config.ts create mode 100644 tests/cli/CLIHelper.test.ts create mode 100644 tests/cli/ClearCacheCommand.test.ts create mode 100644 tests/cli/CreateSchemaCommand.test.ts create mode 100644 tests/cli/DropSchemaCommand.test.ts create mode 100644 tests/cli/GenerateEntitiesCommand.test.ts create mode 100644 tests/cli/UpdateSchemaCommand.test.ts diff --git a/ents/Author3.ts b/ents/Author3.ts new file mode 100644 index 000000000000..699106ae9f96 --- /dev/null +++ b/ents/Author3.ts @@ -0,0 +1,36 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Author3 { + + @PrimaryKey() + id: number; + + @Property({ nullable: true }) + createdAt: Date; + + @Property({ nullable: true }) + updatedAt: Date; + + @Property() + name: string; + + @Property() + email: string; + + @Property({ nullable: true }) + age: number; + + @Property() + termsAccepted: number; + + @Property({ nullable: true }) + identities: string; + + @Property({ nullable: true }) + born: Date; + + @Property({ nullable: true }) + favouriteBookId: number; + +} diff --git a/ents/Book3.ts b/ents/Book3.ts new file mode 100644 index 000000000000..1b5fcaaa40a1 --- /dev/null +++ b/ents/Book3.ts @@ -0,0 +1,18 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Book3 { + + @PrimaryKey() + id: number; + + @Property() + title: string; + + @Property({ nullable: true }) + authorId: number; + + @Property({ nullable: true }) + publisherId: number; + +} diff --git a/ents/Book3ToBookTag3.ts b/ents/Book3ToBookTag3.ts new file mode 100644 index 000000000000..8aa2d4eea73b --- /dev/null +++ b/ents/Book3ToBookTag3.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Book3ToBookTag3 { + + @PrimaryKey() + id: number; + + @Property({ fieldName: 'book3_id', nullable: true }) + book3Id: number; + + @Property({ fieldName: 'book_tag3_id', nullable: true }) + bookTag3Id: number; + +} diff --git a/ents/BookTag3.ts b/ents/BookTag3.ts new file mode 100644 index 000000000000..8877b6a2baa0 --- /dev/null +++ b/ents/BookTag3.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class BookTag3 { + + @PrimaryKey() + id: number; + + @Property() + name: string; + + @Property({ default: `current_timestamp` }) + version: Date; + +} diff --git a/ents/Publisher3.ts b/ents/Publisher3.ts new file mode 100644 index 000000000000..36be8603c887 --- /dev/null +++ b/ents/Publisher3.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Publisher3 { + + @PrimaryKey() + id: number; + + @Property() + name: string; + + @Property() + type: string; + +} diff --git a/ents/Publisher3ToTest3.ts b/ents/Publisher3ToTest3.ts new file mode 100644 index 000000000000..f4c293e5221b --- /dev/null +++ b/ents/Publisher3ToTest3.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Publisher3ToTest3 { + + @PrimaryKey() + id: number; + + @Property({ fieldName: 'publisher3_id', nullable: true }) + publisher3Id: number; + + @Property({ fieldName: 'test3_id', nullable: true }) + test3Id: number; + +} diff --git a/ents/Test3.ts b/ents/Test3.ts new file mode 100644 index 000000000000..bfa6c9c297e5 --- /dev/null +++ b/ents/Test3.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryKey, Property } from 'mikro-orm'; + +@Entity() +export class Test3 { + + @PrimaryKey() + id: number; + + @Property({ nullable: true }) + name: string; + + @Property() + version: number = 1; + +} diff --git a/foo_bar b/foo_bar new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/MikroORM.ts b/lib/MikroORM.ts index 8eb2c3f67b26..25c3a22baac8 100644 --- a/lib/MikroORM.ts +++ b/lib/MikroORM.ts @@ -13,7 +13,7 @@ export class MikroORM { private readonly driver: IDatabaseDriver; private readonly logger: Logger; - static async init(options: Options): Promise { + static async init(options: Options | Configuration): Promise { const orm = new MikroORM(options); const driver = await orm.connect(); diff --git a/lib/cache/CacheAdapter.ts b/lib/cache/CacheAdapter.ts index 6f5f9ea461ed..d6e570d3a397 100644 --- a/lib/cache/CacheAdapter.ts +++ b/lib/cache/CacheAdapter.ts @@ -4,4 +4,6 @@ export interface CacheAdapter { set(name: string, data: any, origin: string): Promise; + clear(): Promise; + } diff --git a/lib/cache/FileCacheAdapter.ts b/lib/cache/FileCacheAdapter.ts index 69b23b2c9e81..345b525c25b2 100644 --- a/lib/cache/FileCacheAdapter.ts +++ b/lib/cache/FileCacheAdapter.ts @@ -1,4 +1,5 @@ -import { ensureDir, pathExists, readJSON, stat, writeJSON } from 'fs-extra'; +import globby from 'globby'; +import { ensureDir, pathExists, readJSON, stat, unlink, writeJSON } from 'fs-extra'; import { CacheAdapter } from './CacheAdapter'; export class FileCacheAdapter implements CacheAdapter { @@ -28,6 +29,15 @@ export class FileCacheAdapter implements CacheAdapter { await writeJSON(path, { modified, data, origin }); } + async clear(): Promise { + const path = await this.path('*'); + const files = await globby(path); + + for (const file of files) { + await unlink(file); + } + } + private async path(name: string): Promise { await ensureDir(this.options.cacheDir); return `${this.options.cacheDir}/${name}.json`; diff --git a/lib/cache/NullCacheAdapter.ts b/lib/cache/NullCacheAdapter.ts index 4308e391513d..9d60102add5d 100644 --- a/lib/cache/NullCacheAdapter.ts +++ b/lib/cache/NullCacheAdapter.ts @@ -10,4 +10,8 @@ export class NullCacheAdapter implements CacheAdapter { // ignore } + async clear(): Promise { + // ignore + } + } diff --git a/lib/cli.ts b/lib/cli.ts new file mode 100644 index 000000000000..052632e9fb11 --- /dev/null +++ b/lib/cli.ts @@ -0,0 +1,14 @@ +import yargs from 'yargs'; +import { CLIHelper } from './cli/CLIHelper'; + +require('yargonaut') + .style('blue') + .style('yellow', 'required') + .helpStyle('green') + .errorsStyle('red'); + +const args = CLIHelper.configure().parse(process.argv.slice(2)) as { _: string[] }; + +if (args._.length === 0) { + yargs.showHelp(); +} diff --git a/lib/cli/CLIHelper.ts b/lib/cli/CLIHelper.ts new file mode 100644 index 000000000000..1c32860a27a8 --- /dev/null +++ b/lib/cli/CLIHelper.ts @@ -0,0 +1,58 @@ +import yargs, { Argv } from 'yargs'; +import { ClearCacheCommand } from './ClearCacheCommand'; +import { GenerateEntitiesCommand } from './GenerateEntitiesCommand'; +import { MikroORM } from '../MikroORM'; +import { Configuration, Utils } from '../utils'; +import { CreateSchemaCommand } from './CreateSchemaCommand'; +import { UpdateSchemaCommand } from './UpdateSchemaCommand'; +import { DropSchemaCommand } from './DropSchemaCommand'; + +export class CLIHelper { + + static getConfiguration(): Configuration { + return new Configuration(require(Utils.normalizePath(process.env.MIKRO_ORM_CLI || './cli-config'))); + } + + static async getORM(): Promise { + const options = CLIHelper.getConfiguration(); + return MikroORM.init(options); + } + + static configure() { + return yargs + .scriptName('mikro-orm') + .version(require('../../package.json').version) + .usage('Usage: $0 [options]') + .example('$0 schema:update --run', 'Runs schema synchronization') + .alias('v', 'version') + .alias('h', 'help') + .command(new ClearCacheCommand()) + .command(new GenerateEntitiesCommand()) + .command(new CreateSchemaCommand()) + .command(new DropSchemaCommand()) + .command(new UpdateSchemaCommand()) + .recommendCommands() + .strict(); + } + + static configureSchemaCommand(args: Argv) { + args.option('r', { + alias: 'run', + type: 'boolean', + desc: 'Runs queries', + }); + args.option('d', { + alias: 'dump', + type: 'boolean', + desc: 'Dumps all queries to console', + }); + args.option('no-fk', { + type: 'boolean', + desc: 'Disable foreign key checks if possible', + default: true, + }); + + return args; + } + +} diff --git a/lib/cli/ClearCacheCommand.ts b/lib/cli/ClearCacheCommand.ts new file mode 100644 index 000000000000..4b6fcdf3c4c2 --- /dev/null +++ b/lib/cli/ClearCacheCommand.ts @@ -0,0 +1,19 @@ +import { Arguments, CommandModule } from 'yargs'; +import chalk from 'chalk'; +import { CLIHelper } from './CLIHelper'; + +export class ClearCacheCommand implements CommandModule { + + command = 'cache:clear'; + describe = 'Clear metadata cache'; + + async handler(args: Arguments) { + const config = CLIHelper.getConfiguration(); + const cache = config.getCacheAdapter(); + await cache.clear(); + + // tslint:disable-next-line:no-console + console.log(chalk.green('Metadata cache was successfully cleared') + '\n'); + } + +} diff --git a/lib/cli/CreateSchemaCommand.ts b/lib/cli/CreateSchemaCommand.ts new file mode 100644 index 000000000000..a96583d73e63 --- /dev/null +++ b/lib/cli/CreateSchemaCommand.ts @@ -0,0 +1,38 @@ +import yargs, { Arguments, Argv, CommandModule } from 'yargs'; +import chalk from 'chalk'; +import { CLIHelper } from './CLIHelper'; + +export type Options = { dump: boolean; run: boolean; noFk: boolean }; + +export class CreateSchemaCommand implements CommandModule<{}, U> { + + command = 'schema:create'; + describe = 'Create database schema based on current metadata'; + + builder(args: Argv) { + return CLIHelper.configureSchemaCommand(args) as Argv; + } + + async handler(args: Arguments) { + if (!args.run && !args.dump) { + yargs.showHelp(); + return; + } + + const orm = await CLIHelper.getORM(); + const generator = orm.getSchemaGenerator(); + + if (args.dump) { + const dump = await generator.getCreateSchemaSQL(args.noFk); + // tslint:disable-next-line:no-console + console.log(dump); + } else { + await generator.createSchema(args.noFk); + // tslint:disable-next-line:no-console + console.log(chalk.green('Schema successfully created')); + } + + await orm.close(true); + } + +} diff --git a/lib/cli/DropSchemaCommand.ts b/lib/cli/DropSchemaCommand.ts new file mode 100644 index 000000000000..81716e3c65fa --- /dev/null +++ b/lib/cli/DropSchemaCommand.ts @@ -0,0 +1,38 @@ +import yargs, { Arguments, Argv, CommandModule } from 'yargs'; +import chalk from 'chalk'; +import { CLIHelper } from './CLIHelper'; + +export type Options = { dump: boolean; run: boolean; noFk: boolean }; + +export class DropSchemaCommand implements CommandModule<{}, U> { + + command = 'schema:drop'; + describe = 'Drop all tables based on current metadata'; + + builder(args: Argv) { + return CLIHelper.configureSchemaCommand(args) as Argv; + } + + async handler(args: Arguments) { + if (!args.run && !args.dump) { + yargs.showHelp(); + return; + } + + const orm = await CLIHelper.getORM(); + const generator = orm.getSchemaGenerator(); + + if (args.dump) { + const dump = await generator.getDropSchemaSQL(args.noFk); + // tslint:disable-next-line:no-console + console.log(dump); + } else { + await generator.dropSchema(args.noFk); + // tslint:disable-next-line:no-console + console.log(chalk.green('Schema successfully dropped') + '\n'); + } + + await orm.close(true); + } + +} diff --git a/lib/cli/GenerateEntitiesCommand.ts b/lib/cli/GenerateEntitiesCommand.ts new file mode 100644 index 000000000000..9f710d6bab0f --- /dev/null +++ b/lib/cli/GenerateEntitiesCommand.ts @@ -0,0 +1,48 @@ +import yargs, { Arguments, Argv, CommandModule } from 'yargs'; +import { CLIHelper } from './CLIHelper'; + +export type Options = { dump: boolean; save: boolean; path: string }; + +export class GenerateEntitiesCommand implements CommandModule<{}, U> { + + command = 'generate-entities'; + describe = 'Generate entities based on current database schema'; + + builder(args: Argv) { + args.option('s', { + alias: 'save', + type: 'boolean', + desc: 'Saves entities to directory defined by --path', + }); + args.option('d', { + alias: 'dump', + type: 'boolean', + desc: 'Dumps all entities to console', + }); + args.option('p', { + alias: 'path', + type: 'string', + desc: 'Sets path to directory where to save entities', + }); + + return args as unknown as Argv; + } + + async handler(args: Arguments) { + if (!args.save && !args.dump) { + yargs.showHelp(); + return; + } + + const orm = await CLIHelper.getORM(); + const generator = orm.getEntityGenerator(); + const dump = await generator.generate({ save: args.save, baseDir: args.path }); + + if (args.dump) { + process.stdout.write(dump.join('\n\n')); + } + + await orm.close(true); + } + +} diff --git a/lib/cli/UpdateSchemaCommand.ts b/lib/cli/UpdateSchemaCommand.ts new file mode 100644 index 000000000000..e6a20f827f28 --- /dev/null +++ b/lib/cli/UpdateSchemaCommand.ts @@ -0,0 +1,38 @@ +import yargs, { Arguments, Argv, CommandModule } from 'yargs'; +import chalk from 'chalk'; +import { CLIHelper } from './CLIHelper'; + +export type Options = { dump: boolean; run: boolean; noFk: boolean }; + +export class UpdateSchemaCommand implements CommandModule<{}, U> { + + command = 'schema:update'; + describe = 'Update database schema based on current metadata'; + + builder(args: Argv) { + return CLIHelper.configureSchemaCommand(args) as Argv; + } + + async handler(args: Arguments) { + if (!args.run && !args.dump) { + yargs.showHelp(); + return; + } + + const orm = await CLIHelper.getORM(); + const generator = orm.getSchemaGenerator(); + + if (args.dump) { + const dump = await generator.getUpdateSchemaSQL(args.noFk); + // tslint:disable-next-line:no-console + console.log(dump + '\n'); + } else { + await generator.updateSchema(args.noFk); + // tslint:disable-next-line:no-console + console.log(chalk.green('Schema successfully updated') + '\n'); + } + + await orm.close(true); + } + +} diff --git a/package.json b/package.json index 937d4f6bbd8f..5f093d0b189f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,9 @@ "coveralls": "cat ./coverage/lcov.info | coveralls", "lint": "tslint -p ." }, + "bin": { + "mikro-orm": "./dist/cli.js" + }, "jest": { "transform": { "^.+\\.tsx?$": "ts-jest" @@ -53,6 +56,9 @@ "collectCoverage": false, "collectCoverageFrom": [ "lib/**/*.ts" + ], + "coveragePathIgnorePatterns": [ + "lib/cli.ts" ] }, "commitlint": { @@ -79,6 +85,7 @@ "pinVersions": false }, "dependencies": { + "chalk": "^2.4.2", "clone": "^2.1.0", "fast-deep-equal": "^2.0.0", "fs-extra": "^8.0.0", @@ -86,7 +93,9 @@ "knex": "^0.19.0", "ts-morph": "^3.0.0", "typescript": "^3.5.0", - "uuid": "^3.3.2" + "uuid": "^3.3.2", + "yargonaut": "^1.1.4", + "yargs": "^13.3.0" }, "peerDependencies": { "mongodb": "^3.2.0", diff --git a/tests/FileCacheAdapter.test.ts b/tests/FileCacheAdapter.test.ts index 25b7f3911948..47d70f121029 100644 --- a/tests/FileCacheAdapter.test.ts +++ b/tests/FileCacheAdapter.test.ts @@ -14,6 +14,7 @@ describe('FileCacheAdapter', () => { await new Promise(resolve => setTimeout(resolve, 10)); writeFileSync(origin, '321'); await expect(cache.get('cache-test-handle')).resolves.toBeNull(); + await cache.clear(); }); }); diff --git a/tests/NullCacheAdapter.test.ts b/tests/NullCacheAdapter.test.ts index abe4f27979ae..661aa0e5116e 100644 --- a/tests/NullCacheAdapter.test.ts +++ b/tests/NullCacheAdapter.test.ts @@ -8,6 +8,7 @@ describe('NullCacheAdapter', () => { const cache = new NullCacheAdapter(); await cache.set('cache-test-handle', 123, origin); await expect(cache.get('cache-test-handle')).resolves.toBeNull(); + await cache.clear(); }); }); diff --git a/tests/cli-config.ts b/tests/cli-config.ts new file mode 100644 index 000000000000..c7727bc14dfd --- /dev/null +++ b/tests/cli-config.ts @@ -0,0 +1,13 @@ +import { BASE_DIR } from './bootstrap'; +import { JavaScriptMetadataProvider } from '../lib'; +import { SqliteDriver } from '../lib/drivers/SqliteDriver'; + +const { BaseEntity4, Test3 } = require('./entities-js'); + +export = { + entities: [Test3, BaseEntity4], + dbName: './mikro_orm_test.db', + baseDir: BASE_DIR, + driver: SqliteDriver, + metadataProvider: JavaScriptMetadataProvider, +}; diff --git a/tests/cli/CLIHelper.test.ts b/tests/cli/CLIHelper.test.ts new file mode 100644 index 000000000000..f73a44e5d45d --- /dev/null +++ b/tests/cli/CLIHelper.test.ts @@ -0,0 +1,44 @@ +import { Configuration } from '../../lib/utils'; + +jest.mock(('../../tests/cli-config').replace(/\\/g, '/'), () => ({ dbName: 'foo_bar', entitiesDirs: ['.'] })); +(global as any).process.cwd = () => '../../tests'; + +import { CLIHelper } from '../../lib/cli/CLIHelper'; +import { MikroORM } from '../../lib'; +import { DropSchemaCommand } from '../../lib/cli/DropSchemaCommand'; + +describe('CLIHelper', () => { + + test('configures yargs instance', async () => { + const cli = CLIHelper.configure() as any; + expect(cli.$0).toBe('mikro-orm'); + expect(cli.getCommandInstance().getCommands()).toEqual(['cache:clear', 'generate-entities', 'schema:create', 'schema:drop', 'schema:update']); + expect(cli.getCommandInstance()); + }); + + test('gets ORM configuration', async () => { + const conf = CLIHelper.getConfiguration(); + expect(conf).toBeInstanceOf(Configuration); + expect(conf.get('dbName')).toBe('foo_bar'); + expect(conf.get('entitiesDirs')).toEqual(['.']); + }); + + test('gets ORM instance', async () => { + const orm = await CLIHelper.getORM(); + expect(orm).toBeInstanceOf(MikroORM); + await orm.close(true); + }); + + test('builder', async () => { + const args = { option: jest.fn() }; + CLIHelper.configureSchemaCommand(args as any); + expect(args.option.mock.calls.length).toBe(3); + expect(args.option.mock.calls[0][0]).toBe('r'); + expect(args.option.mock.calls[0][1]).toMatchObject({ alias: 'run', type: 'boolean' }); + expect(args.option.mock.calls[1][0]).toBe('d'); + expect(args.option.mock.calls[1][1]).toMatchObject({ alias: 'dump', type: 'boolean' }); + expect(args.option.mock.calls[2][0]).toBe('no-fk'); + expect(args.option.mock.calls[2][1]).toMatchObject({ type: 'boolean' }); + }); + +}); diff --git a/tests/cli/ClearCacheCommand.test.ts b/tests/cli/ClearCacheCommand.test.ts new file mode 100644 index 000000000000..fe978ee8537d --- /dev/null +++ b/tests/cli/ClearCacheCommand.test.ts @@ -0,0 +1,20 @@ +const close = jest.fn(); +const cacheAdapter = { clear: jest.fn() }; +const getConfiguration = () => ({ getCacheAdapter: () => cacheAdapter, close }); +jest.mock('../../lib/cli/CLIHelper', () => ({ CLIHelper: { getConfiguration } })); + +(global as any).console.log = jest.fn(); + +import { ClearCacheCommand } from '../../lib/cli/ClearCacheCommand'; + +describe('ClearCacheCommand', () => { + + test('handler', async () => { + const cmd = new ClearCacheCommand(); + + expect(cacheAdapter.clear.mock.calls.length).toBe(0); + await expect(cmd.handler({} as any)).resolves.toBeUndefined(); + expect(cacheAdapter.clear.mock.calls.length).toBe(1); + }); + +}); diff --git a/tests/cli/CreateSchemaCommand.test.ts b/tests/cli/CreateSchemaCommand.test.ts new file mode 100644 index 000000000000..cfc49d69ba30 --- /dev/null +++ b/tests/cli/CreateSchemaCommand.test.ts @@ -0,0 +1,43 @@ +const showHelp = jest.fn(); +const close = jest.fn(); +const configureSchemaCommand = jest.fn(); +const schemaGenerator = { createSchema: jest.fn(() => []), getCreateSchemaSQL: jest.fn(() => '') }; +const getORM = async () => ({ getSchemaGenerator: () => schemaGenerator, close }); +jest.mock('yargs', () => ({ showHelp })); +jest.mock('../../lib/cli/CLIHelper', () => ({ CLIHelper: { getORM, configureSchemaCommand } })); + +(global as any).console.log = jest.fn(); + +import { CreateSchemaCommand } from '../../lib/cli/CreateSchemaCommand'; + +describe('CreateSchemaCommand', () => { + + test('builder', async () => { + const cmd = new CreateSchemaCommand(); + + const args = { option: 123 }; + expect(configureSchemaCommand.mock.calls.length).toBe(0); + cmd.builder(args as any); + expect(configureSchemaCommand.mock.calls.length).toBe(1); + }); + + test('handler', async () => { + const cmd = new CreateSchemaCommand(); + + expect(showHelp.mock.calls.length).toBe(0); + await expect(cmd.handler({} as any)).resolves.toBeUndefined(); + expect(showHelp.mock.calls.length).toBe(1); + + expect(schemaGenerator.createSchema.mock.calls.length).toBe(0); + expect(close.mock.calls.length).toBe(0); + await expect(cmd.handler({ run: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.createSchema.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(1); + + expect(schemaGenerator.getCreateSchemaSQL.mock.calls.length).toBe(0); + await expect(cmd.handler({ dump: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.getCreateSchemaSQL.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(2); + }); + +}); diff --git a/tests/cli/DropSchemaCommand.test.ts b/tests/cli/DropSchemaCommand.test.ts new file mode 100644 index 000000000000..f31ceff0c92c --- /dev/null +++ b/tests/cli/DropSchemaCommand.test.ts @@ -0,0 +1,43 @@ +const showHelp = jest.fn(); +const close = jest.fn(); +const configureSchemaCommand = jest.fn(); +const schemaGenerator = { dropSchema: jest.fn(() => []), getDropSchemaSQL: jest.fn(() => '') }; +const getORM = async () => ({ getSchemaGenerator: () => schemaGenerator, close }); +jest.mock('yargs', () => ({ showHelp })); +jest.mock('../../lib/cli/CLIHelper', () => ({ CLIHelper: { getORM, configureSchemaCommand } })); + +(global as any).console.log = jest.fn(); + +import { DropSchemaCommand } from '../../lib/cli/DropSchemaCommand'; + +describe('DropSchemaCommand', () => { + + test('builder', async () => { + const cmd = new DropSchemaCommand(); + + const args = { option: 123 }; + expect(configureSchemaCommand.mock.calls.length).toBe(0); + cmd.builder(args as any); + expect(configureSchemaCommand.mock.calls.length).toBe(1); + }); + + test('handler', async () => { + const cmd = new DropSchemaCommand(); + + expect(showHelp.mock.calls.length).toBe(0); + await expect(cmd.handler({} as any)).resolves.toBeUndefined(); + expect(showHelp.mock.calls.length).toBe(1); + + expect(schemaGenerator.dropSchema.mock.calls.length).toBe(0); + expect(close.mock.calls.length).toBe(0); + await expect(cmd.handler({ run: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.dropSchema.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(1); + + expect(schemaGenerator.getDropSchemaSQL.mock.calls.length).toBe(0); + await expect(cmd.handler({ dump: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.getDropSchemaSQL.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(2); + }); + +}); diff --git a/tests/cli/GenerateEntitiesCommand.test.ts b/tests/cli/GenerateEntitiesCommand.test.ts new file mode 100644 index 000000000000..6f9a402a778e --- /dev/null +++ b/tests/cli/GenerateEntitiesCommand.test.ts @@ -0,0 +1,44 @@ +const showHelp = jest.fn(); +const close = jest.fn(); +const entityGenerator = { generate: jest.fn(() => []) }; +const getORM = async () => ({ getEntityGenerator: () => entityGenerator, close }); +jest.mock('yargs', () => ({ showHelp })); +jest.mock('../../lib/cli/CLIHelper', () => ({ CLIHelper: { getORM } })); + +import { GenerateEntitiesCommand } from '../../lib/cli/GenerateEntitiesCommand'; + +describe('GenerateEntitiesCommand', () => { + + test('builder', async () => { + const cmd = new GenerateEntitiesCommand(); + + const args = { option: jest.fn() }; + cmd.builder(args as any); + expect(args.option.mock.calls.length).toBe(3); + expect(args.option.mock.calls[0][0]).toBe('s'); + expect(args.option.mock.calls[0][1]).toMatchObject({ alias: 'save', type: 'boolean' }); + expect(args.option.mock.calls[1][0]).toBe('d'); + expect(args.option.mock.calls[1][1]).toMatchObject({ alias: 'dump', type: 'boolean' }); + expect(args.option.mock.calls[2][0]).toBe('p'); + expect(args.option.mock.calls[2][1]).toMatchObject({ alias: 'path', type: 'string' }); + }); + + test('handler', async () => { + const cmd = new GenerateEntitiesCommand(); + + expect(showHelp.mock.calls.length).toBe(0); + await expect(cmd.handler({} as any)).resolves.toBeUndefined(); + expect(showHelp.mock.calls.length).toBe(1); + + expect(entityGenerator.generate.mock.calls.length).toBe(0); + expect(close.mock.calls.length).toBe(0); + await expect(cmd.handler({ save: true } as any)).resolves.toBeUndefined(); + expect(entityGenerator.generate.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(1); + + await expect(cmd.handler({ dump: true } as any)).resolves.toBeUndefined(); + expect(entityGenerator.generate.mock.calls.length).toBe(2); + expect(close.mock.calls.length).toBe(2); + }); + +}); diff --git a/tests/cli/UpdateSchemaCommand.test.ts b/tests/cli/UpdateSchemaCommand.test.ts new file mode 100644 index 000000000000..057e56c22593 --- /dev/null +++ b/tests/cli/UpdateSchemaCommand.test.ts @@ -0,0 +1,43 @@ +const showHelp = jest.fn(); +const close = jest.fn(); +const configureSchemaCommand = jest.fn(); +const schemaGenerator = { updateSchema: jest.fn(() => []), getUpdateSchemaSQL: jest.fn(() => '') }; +const getORM = async () => ({ getSchemaGenerator: () => schemaGenerator, close }); +jest.mock('yargs', () => ({ showHelp })); +jest.mock('../../lib/cli/CLIHelper', () => ({ CLIHelper: { getORM, configureSchemaCommand } })); + +(global as any).console.log = jest.fn(); + +import { UpdateSchemaCommand } from '../../lib/cli/UpdateSchemaCommand'; + +describe('UpdateSchemaCommand', () => { + + test('builder', async () => { + const cmd = new UpdateSchemaCommand(); + + const args = { option: 123 }; + expect(configureSchemaCommand.mock.calls.length).toBe(0); + cmd.builder(args as any); + expect(configureSchemaCommand.mock.calls.length).toBe(1); + }); + + test('handler', async () => { + const cmd = new UpdateSchemaCommand(); + + expect(showHelp.mock.calls.length).toBe(0); + await expect(cmd.handler({} as any)).resolves.toBeUndefined(); + expect(showHelp.mock.calls.length).toBe(1); + + expect(schemaGenerator.updateSchema.mock.calls.length).toBe(0); + expect(close.mock.calls.length).toBe(0); + await expect(cmd.handler({ run: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.updateSchema.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(1); + + expect(schemaGenerator.getUpdateSchemaSQL.mock.calls.length).toBe(0); + await expect(cmd.handler({ dump: true } as any)).resolves.toBeUndefined(); + expect(schemaGenerator.getUpdateSchemaSQL.mock.calls.length).toBe(1); + expect(close.mock.calls.length).toBe(2); + }); + +}); diff --git a/tsconfig.json b/tsconfig.json index 01b2df39c637..bc379b48af8f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "lib/index.ts" ], "include": [ - "lib/drivers" + "lib/drivers", + "lib/cli" ], "exclude": [ "node_modules" diff --git a/yarn.lock b/yarn.lock index 2c5cace94889..7c1a9de760c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1426,7 +1426,7 @@ chalk@2.3.1: escape-string-regexp "^1.0.5" supports-color "^5.2.0" -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -2475,6 +2475,11 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== +figlet@^1.1.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.3.tgz#7d25df546f41fc411c2a8b88012d48d55de72129" + integrity sha512-+F5zdvZ66j77b8x2KCPvWUHC0UCKUMWrewxmewgPlagp3wmDpcrHMbyv/ygq/6xoxBPGQA+UJU3SMoBzKoROQQ== + figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -5599,7 +5604,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-locale@^3.0.0, os-locale@^3.1.0: +os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== @@ -5795,6 +5800,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parent-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" + integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc= + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -7992,6 +8002,15 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yargonaut@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c" + integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA== + dependencies: + chalk "^1.1.1" + figlet "^1.1.1" + parent-require "^1.0.0" + yargs-parser@10.x, yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" @@ -8007,7 +8026,7 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.0: +yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== @@ -8058,22 +8077,21 @@ yargs@^12.0.2: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@^13.1.0: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== +yargs@^13.1.0, yargs@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" + integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== dependencies: cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" - os-locale "^3.1.0" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.0" + yargs-parser "^13.1.1" yn@^3.0.0: version "3.1.0"