From 87cfe800da56802ec6d865397fdcb679bd49af67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Mon, 19 Aug 2019 13:18:05 +0200 Subject: [PATCH] feat(cli): add basic CLI tool (#102) Adds `mikro-orm` npm command that allows to clear cache and work with SchemaGenerator and EntityGenerator. First one needs to provide `cli-config.[jt]s` file in the root directory that will export the ORM configuration. Closes #101 --- lib/MikroORM.ts | 2 +- lib/cache/CacheAdapter.ts | 2 + lib/cache/FileCacheAdapter.ts | 12 +- lib/cache/NullCacheAdapter.ts | 4 + lib/cli.ts | 18 +++ lib/cli/CLIHelper.ts | 108 ++++++++++++++++++ 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 ++++++ lib/connections/AbstractSqlConnection.ts | 7 +- lib/connections/Connection.ts | 2 +- lib/metadata/MetadataDiscovery.ts | 2 +- lib/query/QueryBuilder.ts | 6 +- lib/schema/EntityGenerator.ts | 10 +- lib/schema/MySqlSchemaHelper.ts | 11 +- lib/schema/PostgreSqlSchemaHelper.ts | 5 +- lib/schema/SchemaHelper.ts | 2 +- lib/utils/Configuration.ts | 10 +- lib/utils/Utils.ts | 2 +- package.json | 14 ++- tests/EntityGenerator.test.ts | 6 + tests/FileCacheAdapter.test.ts | 1 + tests/NullCacheAdapter.test.ts | 1 + tests/SchemaHelper.test.ts | 6 + .../EntityGenerator.test.ts.snap | 10 +- tests/cli-config.ts | 13 +++ tests/cli/CLIHelper.test.ts | 90 +++++++++++++++ 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 +- tslint.json | 4 +- yarn.lock | 54 +++++---- 37 files changed, 722 insertions(+), 57 deletions(-) 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/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..d4e62ae855c6 --- /dev/null +++ b/lib/cli.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +require('yargonaut') + .style('blue') + .style('yellow', 'required') + .helpStyle('green') + .errorsStyle('red'); + +import yargs from 'yargs'; +import { CLIHelper } from './cli/CLIHelper'; + +(async () => { + const args = (await 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..17fa7522bc2b --- /dev/null +++ b/lib/cli/CLIHelper.ts @@ -0,0 +1,108 @@ +import yargs, { Argv } from 'yargs'; +import { pathExists } from 'fs-extra'; + +import { MikroORM } from '../MikroORM'; +import { Configuration, Utils } from '../utils'; +import { ClearCacheCommand } from './ClearCacheCommand'; +import { GenerateEntitiesCommand } from './GenerateEntitiesCommand'; +import { CreateSchemaCommand } from './CreateSchemaCommand'; +import { UpdateSchemaCommand } from './UpdateSchemaCommand'; +import { DropSchemaCommand } from './DropSchemaCommand'; + +export class CLIHelper { + + static async getConfiguration(): Promise { + const paths = await CLIHelper.getConfigPaths(); + + for (let path of paths) { + path = Utils.normalizePath(path); + + if (await pathExists(path)) { + return new Configuration(require(path)); + } + } + + throw new Error(`cli-config not found in ['${paths.join(`', '`)}']`); + } + + static async getORM(): Promise { + const options = await CLIHelper.getConfiguration(); + const settings = await CLIHelper.getSettings(); + + if (settings.useTsNode) { + options.set('tsNode', true); + } + + return MikroORM.init(options); + } + + static async configure(): Promise { + const settings = await CLIHelper.getSettings(); + + if (settings.useTsNode) { + require('ts-node').register(); + } + + 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(); + } + + private static async getSettings(): Promise { + if (await pathExists(process.cwd() + '/package.json')) { + const config = require(process.cwd() + '/package.json'); + return config['mikro-orm']; + } + + return {}; + } + + 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; + } + + private static async getConfigPaths(): Promise { + const paths: string[] = []; + + if (await pathExists(process.cwd() + '/package.json')) { + const config = require(process.cwd() + '/package.json'); + const settings = config['mikro-orm'] as Settings; + paths.push(...(settings.configPaths || [])); + } + + return [...paths, process.env.MIKRO_ORM_CLI || './cli-config']; + } + +} + +export interface Settings { + useTsNode?: boolean; + configPaths?: string[]; +} diff --git a/lib/cli/ClearCacheCommand.ts b/lib/cli/ClearCacheCommand.ts new file mode 100644 index 000000000000..0ae97ebd92f6 --- /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 = await 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/lib/connections/AbstractSqlConnection.ts b/lib/connections/AbstractSqlConnection.ts index db6996abc374..20c23e7e98aa 100644 --- a/lib/connections/AbstractSqlConnection.ts +++ b/lib/connections/AbstractSqlConnection.ts @@ -40,12 +40,13 @@ export abstract class AbstractSqlConnection extends Connection { }); } - async execute | EntityData[]>(queryOrKnex: string | QueryBuilder | Raw, params: any[] = [], method: 'all' | 'get' | 'run' = 'all'): Promise { + async execute | EntityData[] = EntityData[]>(queryOrKnex: string | QueryBuilder | Raw, params: any[] = [], method: 'all' | 'get' | 'run' = 'all'): Promise { if (Utils.isObject(queryOrKnex)) { return await this.executeKnex(queryOrKnex, method); } - const res = await this.executeQuery(queryOrKnex, params, () => this.client.raw(queryOrKnex, params)); + const sql = this.client.client.positionBindings(queryOrKnex); + const res = await this.executeQuery(sql, () => this.client.raw(queryOrKnex, params)); return this.transformRawResult(res, method); } @@ -74,7 +75,7 @@ export abstract class AbstractSqlConnection extends Connection { protected async executeKnex(qb: QueryBuilder | Raw, method: 'all' | 'get' | 'run'): Promise { const q = qb.toSQL(); const query = q.toNative ? q.toNative() : q; - const res = await this.executeQuery(query.sql, query.bindings, () => qb); + const res = await this.executeQuery(query.sql, () => qb); return this.transformKnexResult(res, method); } diff --git a/lib/connections/Connection.ts b/lib/connections/Connection.ts index a40157363db3..1c14a627002f 100644 --- a/lib/connections/Connection.ts +++ b/lib/connections/Connection.ts @@ -60,7 +60,7 @@ export abstract class Connection { this.metadata = metadata; } - protected async executeQuery(query: string, params: any[], cb: () => Promise): Promise { + protected async executeQuery(query: string, cb: () => Promise): Promise { const now = Date.now(); const res = await cb(); this.logQuery(query, Date.now() - now); diff --git a/lib/metadata/MetadataDiscovery.ts b/lib/metadata/MetadataDiscovery.ts index bf88734b8019..580aab38ae3e 100644 --- a/lib/metadata/MetadataDiscovery.ts +++ b/lib/metadata/MetadataDiscovery.ts @@ -26,7 +26,7 @@ export class MetadataDiscovery { const startTime = Date.now(); this.logger.debug(`ORM entity discovery started`); this.discovered.length = 0; - const tsNode = process.argv[0].endsWith('ts-node') || process.argv.slice(1).some(arg => arg.includes('ts-node')); + const tsNode = this.config.get('tsNode') || process.argv[0].endsWith('ts-node') || process.argv.slice(1).some(arg => arg.includes('ts-node')); if (this.config.get('entities').length > 0) { await Utils.runSerial(this.config.get('entities'), entity => this.discoverEntity(entity)); diff --git a/lib/query/QueryBuilder.ts b/lib/query/QueryBuilder.ts index f6541931a2f8..5324ff397097 100644 --- a/lib/query/QueryBuilder.ts +++ b/lib/query/QueryBuilder.ts @@ -2,7 +2,7 @@ import { QueryBuilder as KnexQueryBuilder, Raw, Transaction } from 'knex'; import { Utils, ValidationError } from '../utils'; import { QueryBuilderHelper } from './QueryBuilderHelper'; import { SmartQueryHelper } from './SmartQueryHelper'; -import { EntityProperty } from '../decorators'; +import { EntityProperty, IEntity } from '../decorators'; import { ReferenceType } from '../entity'; import { QueryFlag, QueryOrderMap, QueryType } from './enums'; import { LockMode } from '../unit-of-work'; @@ -244,10 +244,10 @@ export class QueryBuilder { } if (method === 'all' && Array.isArray(res)) { - return res.map((r: any) => this.driver.mapResult(r, meta)); + return res.map(r => this.driver.mapResult(r, meta)); } - return this.driver.mapResult(res, meta); + return this.driver.mapResult(res, meta); } clone(): QueryBuilder { diff --git a/lib/schema/EntityGenerator.ts b/lib/schema/EntityGenerator.ts index 605fb388ca07..63288e9532d4 100644 --- a/lib/schema/EntityGenerator.ts +++ b/lib/schema/EntityGenerator.ts @@ -55,7 +55,7 @@ export class EntityGenerator { this.sources.push(entity); } - private createProperty(writer: CodeBlockWriter, column: Column): void { + createProperty(writer: CodeBlockWriter, column: Column): void { const prop = this.getPropertyName(column); const type = this.getPropertyType(column); const defaultValue = this.getPropertyDefaultValue(column, type); @@ -187,11 +187,13 @@ export class EntityGenerator { } private getPropertyDefaultValue(column: any, propType: string): any { - if (!column.nullable && column.defaultValue === null) { + if (!column.defaultValue) { return; } - if (!column.defaultValue) { + const val = this.helper.normalizeDefaultValue(column.defaultValue, column.maxLength); + + if (column.nullable && val === 'null') { return; } @@ -203,7 +205,7 @@ export class EntityGenerator { return +column.defaultValue; } - return '' + this.helper.normalizeDefaultValue(column.defaultValue, column.maxLength); + return '' + val; } } diff --git a/lib/schema/MySqlSchemaHelper.ts b/lib/schema/MySqlSchemaHelper.ts index fd853cca7c5b..ec21bb525de7 100644 --- a/lib/schema/MySqlSchemaHelper.ts +++ b/lib/schema/MySqlSchemaHelper.ts @@ -25,6 +25,11 @@ export class MySqlSchemaHelper extends SchemaHelper { date: 0, }; + static readonly DEFAULT_VALUES = { + 'now()': ['now()', 'current_timestamp'], + 'current_timestamp(?)': ['current_timestamp(?)'], + }; + getSchemaBeginning(): string { return 'set names utf8;\nset foreign_key_checks = 0;\n\n'; } @@ -96,7 +101,11 @@ export class MySqlSchemaHelper extends SchemaHelper { } isSame(prop: EntityProperty, column: Column): IsSame { - return super.isSame(prop, column, MySqlSchemaHelper.TYPES); + return super.isSame(prop, column, MySqlSchemaHelper.TYPES, MySqlSchemaHelper.DEFAULT_VALUES); + } + + normalizeDefaultValue(defaultValue: string, length: number) { + return super.normalizeDefaultValue(defaultValue, length, MySqlSchemaHelper.DEFAULT_VALUES); } } diff --git a/lib/schema/PostgreSqlSchemaHelper.ts b/lib/schema/PostgreSqlSchemaHelper.ts index 8ade49d67c61..71dedfe854cd 100644 --- a/lib/schema/PostgreSqlSchemaHelper.ts +++ b/lib/schema/PostgreSqlSchemaHelper.ts @@ -25,7 +25,10 @@ export class PostgreSqlSchemaHelper extends SchemaHelper { static readonly DEFAULT_VALUES = { 'now()': ['now()', 'current_timestamp'], + 'current_timestamp(?)': ['current_timestamp(?)'], "('now'::text)::timestamp(?) with time zone": ['current_timestamp(?)'], + 'null::character varying': ['null'], + 'null::timestamp without time zone': ['null'], }; getSchemaBeginning(): string { @@ -53,7 +56,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper { } getListTablesSQL(): string { - return 'select quote_ident(table_name) as table_name, table_schema as schema_name ' + return 'select table_name, table_schema as schema_name ' + `from information_schema.tables where table_schema not like 'pg\_%' and table_schema != 'information_schema' ` + `and table_name != 'geometry_columns' and table_name != 'spatial_ref_sys' and table_type != 'VIEW' order by table_name`; } diff --git a/lib/schema/SchemaHelper.ts b/lib/schema/SchemaHelper.ts index 380cb2468dbe..8beb30f5ba73 100644 --- a/lib/schema/SchemaHelper.ts +++ b/lib/schema/SchemaHelper.ts @@ -156,7 +156,7 @@ export abstract class SchemaHelper { if (info.defaultValue && prop.default) { const defaultValue = info.defaultValue.toString().replace(/\([?\d]+\)/, '').toLowerCase(); const propDefault = prop.default.toString().toLowerCase(); - const same = prop.default.toString() === info.defaultValue.toString().toLowerCase(); + const same = propDefault === info.defaultValue.toString().toLowerCase(); const equal = same || propDefault === defaultValue; return equal || Object.keys(defaultValues).map(t => t.replace(/\([?\d]+\)/, '').toLowerCase()).includes(defaultValue); diff --git a/lib/utils/Configuration.ts b/lib/utils/Configuration.ts index 40d8a67fe793..3f3d5318c4e8 100644 --- a/lib/utils/Configuration.ts +++ b/lib/utils/Configuration.ts @@ -25,6 +25,7 @@ export class Configuration { baseDir: process.cwd(), entityRepository: EntityRepository, hydrator: ObjectHydrator, + tsNode: false, debug: false, cache: { enabled: true, @@ -60,8 +61,12 @@ export class Configuration { this.init(); } - get(key: T, defaultValue?: U): MikroORMOptions[T] { - return (this.options[key] || defaultValue) as MikroORMOptions[T]; + get(key: T, defaultValue?: U): MikroORMOptions[T] { + return (Utils.isDefined(this.options[key]) ? this.options[key] : defaultValue) as MikroORMOptions[T]; + } + + set(key: T, value: U): void { + this.options[key] = value; } getLogger(): Logger { @@ -171,6 +176,7 @@ export interface MikroORMOptions { strict: boolean; logger: (message: string) => void; debug: boolean; + tsNode: boolean; baseDir: string; cache: { enabled?: boolean; diff --git a/lib/utils/Utils.ts b/lib/utils/Utils.ts index 0cf6e929cb93..49b55c695af9 100644 --- a/lib/utils/Utils.ts +++ b/lib/utils/Utils.ts @@ -42,7 +42,7 @@ export class Utils { if (Utils.isObject(target) && Utils.isObject(source)) { Object.entries(source).forEach(([key, value]) => { if (Utils.isObject(value)) { - if (!target[key]) { + if (!(key in target)) { Object.assign(target, { [key]: {} }); } diff --git a/package.json b/package.json index 937d4f6bbd8f..32beee1e4ca6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mikro-orm", - "version": "2.7.6", + "version": "3.0.0-alpha.1", "description": "Simple typescript ORM for node.js based on data-mapper, unit-of-work and identity-map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JS.", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -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", @@ -133,7 +142,6 @@ "ts-node": "^8.1.0", "tslint": "^5.18.0", "tslint-eslint-rules": "^5.4.0", - "tslint-import-rules": "^0.3.0", "tslint-lines-between-class-members": "^1.3.6" } } diff --git a/tests/EntityGenerator.test.ts b/tests/EntityGenerator.test.ts index b1734eb594d4..93dbf130d886 100644 --- a/tests/EntityGenerator.test.ts +++ b/tests/EntityGenerator.test.ts @@ -1,5 +1,6 @@ import { pathExists, remove } from 'fs-extra'; import { initORMMySql, initORMPostgreSql, initORMSqlite } from './bootstrap'; +import { EntityGenerator } from '../lib/schema/EntityGenerator'; describe('EntityGenerator', () => { @@ -31,6 +32,11 @@ describe('EntityGenerator', () => { const dump = await generator.generate(); expect(dump).toMatchSnapshot('postgres-entity-dump'); + const writer = { writeLine: jest.fn(), blankLineIfLastNot: jest.fn(), blankLine: jest.fn(), block: jest.fn(), write: jest.fn() }; + generator.createProperty(writer as any, { name: 'test', type: 'varchar(50)', defaultValue: 'null::character varying', nullable: true } as any); + expect(writer.writeLine.mock.calls.length).toBe(2); + expect(writer.writeLine.mock.calls[0][0]).toBe(`@Property({ type: 'varchar(50)', nullable: true })`); + await orm.close(true); }); 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/SchemaHelper.test.ts b/tests/SchemaHelper.test.ts index f568cc64b3d0..090315700a8c 100644 --- a/tests/SchemaHelper.test.ts +++ b/tests/SchemaHelper.test.ts @@ -1,6 +1,7 @@ import { SchemaHelper } from '../lib/schema'; import { SqliteSchemaHelper } from '../lib/schema/SqliteSchemaHelper'; import { MySqlSchemaHelper } from '../lib/schema/MySqlSchemaHelper'; +import { PostgreSqlSchemaHelper } from '../lib/schema/PostgreSqlSchemaHelper'; class SchemaHelperTest extends SchemaHelper { } @@ -33,4 +34,9 @@ describe('SchemaHelper', () => { expect(helper.getRenameColumnSQL('table', { name: 'test1' } as any, { fieldName: 'test_123' } as any)).toBe('alter table `table` rename column `test1` to `test_123`'); }); + test('postgres schema helper', async () => { + const helper = new PostgreSqlSchemaHelper(); + expect(helper.isSame({ reference: 'scalar', type: 'Date', nullable: false, columnType: 'timestamp(3)', default: 'current_timestamp(3)' } as any, { type: 'timestamp(3)', nullable: false, defaultValue: `('now'::text)::timestamp(3) with time zone` } as any).all).toBe(true); + }); + }); diff --git a/tests/__snapshots__/EntityGenerator.test.ts.snap b/tests/__snapshots__/EntityGenerator.test.ts.snap index 7d38343c7689..baf1d109de01 100644 --- a/tests/__snapshots__/EntityGenerator.test.ts.snap +++ b/tests/__snapshots__/EntityGenerator.test.ts.snap @@ -11,10 +11,10 @@ export class Author2 { @PrimaryKey() id: number; - @Property({ length: 3, default: \`CURRENT_TIMESTAMP(3)\` }) + @Property({ length: 3, default: \`current_timestamp(3)\` }) createdAt: Date; - @Property({ length: 3, default: \`CURRENT_TIMESTAMP(3)\` }) + @Property({ length: 3, default: \`current_timestamp(3)\` }) updatedAt: Date; @Property({ length: 255 }) @@ -53,7 +53,7 @@ export class Book2 { @PrimaryKey({ length: 36 }) uuidPk: string; - @Property({ length: 3, default: \`CURRENT_TIMESTAMP(3)\` }) + @Property({ length: 3, default: \`current_timestamp(3)\` }) createdAt: Date; @Property({ length: 255, nullable: true }) @@ -131,7 +131,7 @@ export class FooBar2 { @OneToOne({ entity: () => FooBar2, nullable: true }) fooBar: FooBar2; - @Property({ length: 3, default: \`CURRENT_TIMESTAMP(3)\` }) + @Property({ length: 3, default: \`current_timestamp(3)\` }) version: Date; } @@ -147,7 +147,7 @@ export class FooBaz2 { @Property({ length: 255 }) name: string; - @Property({ length: 3, default: \`CURRENT_TIMESTAMP(3)\` }) + @Property({ length: 3, default: \`current_timestamp(3)\` }) version: Date; } 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..22e8d791828a --- /dev/null +++ b/tests/cli/CLIHelper.test.ts @@ -0,0 +1,90 @@ +import { Configuration } from '../../lib/utils'; + +jest.mock(('../../tests/cli-config').replace(/\\/g, '/'), () => ({ dbName: 'foo_bar', entitiesDirs: ['.'] })); +const pkg = { 'mikro-orm': {} } as any; +jest.mock('../../tests/package.json', () => pkg, { virtual: true }); +(global as any).process.cwd = () => '../../tests'; + +import { CLIHelper } from '../../lib/cli/CLIHelper'; +import { MikroORM } from '../../lib'; + +describe('CLIHelper', () => { + + test('configures yargs instance', async () => { + const cli = await 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']); + }); + + test('configures yargs instance [ts-node]', async () => { + const pathExistsMock = jest.spyOn(require('fs-extra'), 'pathExists'); + pathExistsMock.mockReturnValue(Promise.resolve(true)); + pkg['mikro-orm'].useTsNode = true; + const tsNodeMock = jest.spyOn(require('ts-node'), 'register'); + const cli = await CLIHelper.configure() as any; + expect(cli.$0).toBe('mikro-orm'); + expect(tsNodeMock).toHaveBeenCalled(); + expect(cli.getCommandInstance().getCommands()).toEqual(['cache:clear', 'generate-entities', 'schema:create', 'schema:drop', 'schema:update']); + pathExistsMock.mockRestore(); + }); + + test('gets ORM configuration [no cli-config]', async () => { + await expect(CLIHelper.getConfiguration()).rejects.toThrowError(`cli-config not found in ['./cli-config']`); + }); + + test('gets ORM configuration [no package.json]', async () => { + const pathExistsMock = jest.spyOn(require('fs-extra'), 'pathExists'); + pathExistsMock.mockImplementation(async path => path === '../../tests/cli-config'); + const conf = await CLIHelper.getConfiguration(); + expect(conf).toBeInstanceOf(Configuration); + expect(conf.get('dbName')).toBe('foo_bar'); + expect(conf.get('entitiesDirs')).toEqual(['.']); + pathExistsMock.mockRestore(); + }); + + test('gets ORM configuration [from package.json]', async () => { + const pathExistsMock = jest.spyOn(require('fs-extra'), 'pathExists'); + pathExistsMock.mockReturnValue(Promise.resolve(true)); + pkg['mikro-orm'].useTsNode = true; + const conf = await CLIHelper.getConfiguration(); + expect(conf).toBeInstanceOf(Configuration); + expect(conf.get('dbName')).toBe('foo_bar'); + expect(conf.get('entitiesDirs')).toEqual(['.']); + pathExistsMock.mockRestore(); + }); + + test('gets ORM instance', async () => { + const pathExistsMock = jest.spyOn(require('fs-extra'), 'pathExists'); + pathExistsMock.mockReturnValue(Promise.resolve(true)); + delete pkg['mikro-orm'].useTsNode; + const orm = await CLIHelper.getORM(); + expect(orm).toBeInstanceOf(MikroORM); + expect(orm.config.get('tsNode')).toBe(false); + await orm.close(true); + pathExistsMock.mockRestore(); + }); + + test('gets ORM instance [ts-node]', async () => { + const pathExistsMock = jest.spyOn(require('fs-extra'), 'pathExists'); + pathExistsMock.mockReturnValue(Promise.resolve(true)); + pkg['mikro-orm'].useTsNode = true; + const orm = await CLIHelper.getORM(); + expect(orm).toBeInstanceOf(MikroORM); + expect(orm.config.get('tsNode')).toBe(true); + await orm.close(true); + pathExistsMock.mockRestore(); + }); + + 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..6bec17469537 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "experimentalDecorators": true }, "files": [ - "lib/index.ts" + "lib/index.ts", + "lib/cli.ts" ], "include": [ "lib/drivers" diff --git a/tslint.json b/tslint.json index d9c3d3f7e0c1..43e05ede9919 100644 --- a/tslint.json +++ b/tslint.json @@ -91,12 +91,10 @@ "prefer-const": true, "jsdoc-format": true, "lines-between-class-members": [true, 1, {"exceptAfterSingleLine": true}], - "ter-padded-blocks": [true, {"classes": "always"}], - "tir-newline-after-import": [true] + "ter-padded-blocks": [true, {"classes": "always"}] }, "rulesDirectory": [ "node_modules/tslint-eslint-rules/dist/rules", - "node_modules/tslint-import-rules/dist/rules", "node_modules/tslint-lines-between-class-members" ] } diff --git a/yarn.lock b/yarn.lock index 2c5cace94889..2723b09a6cbb 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" @@ -7491,7 +7501,7 @@ tslib@1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ== -tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2: +tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== @@ -7505,21 +7515,12 @@ tslint-eslint-rules@^5.4.0: tslib "1.9.0" tsutils "^3.0.0" -tslint-import-rules@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/tslint-import-rules/-/tslint-import-rules-0.3.0.tgz#337c083b9682b61893831643efa8e608614e41d6" - integrity sha512-znYLRFzdm6O/aNTY2YdHDNvfw+3hzRotZ5s5T+aUAWTxyjnA5n4IFDjw69ebn6htvIAmo6Zf5rakSlT8n8/bKA== - dependencies: - tslib "^1.9.2" - tslint "^5.10.0" - typescript "^2.9.1" - tslint-lines-between-class-members@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/tslint-lines-between-class-members/-/tslint-lines-between-class-members-1.3.6.tgz#edaa48dbdf9f7df446299d0ff2eb0e68d8fa7028" integrity sha512-g9bxloPZVrkZLxLl+auqxWpZ4QJPTQLZocQN1zeD01pZOiH1m1OjDKwhDRjJD17IVsdTXd2M/Wp1+A9tfLr1Iw== -tslint@^5.10.0, tslint@^5.18.0: +tslint@^5.18.0: version "5.18.0" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.18.0.tgz#f61a6ddcf372344ac5e41708095bbf043a147ac6" integrity sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w== @@ -7591,11 +7592,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" - integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== - typescript@^3.0.1, typescript@^3.5.0: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" @@ -7992,6 +7988,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 +8012,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 +8063,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"