diff --git a/package.json b/package.json index 8e38cc70..a904b4fc 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/jest": "^24.0.13", "@types/yargs": "^13.0.0", "jest": "^24.8.0", + "prettier": "^1.17.1", "rimraf": "^2.6.2", "rollup": "^0.66.2", "rollup-plugin-cli": "^0.1.5", @@ -48,6 +49,7 @@ "typescript": "^3.0.3" }, "dependencies": { + "@types/glob": "^7.1.1", "@types/node": "^12.0.4", "chalk": "^2.4.2", "faker": "^4.1.0", diff --git a/src/cli.ts b/src/cli.ts index cbc0099c..8bf16ec0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' import * as yargs from 'yargs' import { SeedCommand } from './commands/seed.command' -import { ConfigCommand } from './commands/config.command'; +import { ConfigCommand } from './commands/config.command' yargs .usage('Usage: $0 [options]') diff --git a/src/commands/config.command.ts b/src/commands/config.command.ts index bb298f6c..56d7c33e 100644 --- a/src/commands/config.command.ts +++ b/src/commands/config.command.ts @@ -9,12 +9,11 @@ export class ConfigCommand implements yargs.CommandModule { describe = 'Show the TypeORM config' builder(args: yargs.Argv) { - return args - .option('c', { - alias: 'config', - default: 'ormconfig.js', - describe: 'Path to the typeorm config file (json or js).', - }) + return args.option('c', { + alias: 'config', + default: 'ormconfig.js', + describe: 'Path to the typeorm config file (json or js).', + }) } async handler(args: yargs.Arguments) { diff --git a/src/commands/seed.command.ts b/src/commands/seed.command.ts index 3aff9b5e..8ae6571c 100644 --- a/src/commands/seed.command.ts +++ b/src/commands/seed.command.ts @@ -1,10 +1,11 @@ import * as yargs from 'yargs' import chalk from 'chalk' import { createConnection } from 'typeorm' -import { setConnection, loadEntityFactories, loadSeeds, runSeed, getConnectionOptions } from '../typeorm-seeding' +import { setConnection, runSeed, getConnectionOptions } from '../typeorm-seeding' import * as pkg from '../../package.json' import { printError } from '../utils/log.util' import { importSeed } from '../importer' +import { loadFiles, importFiles } from '../utils/file.util' export class SeedCommand implements yargs.CommandModule { command = 'seed' @@ -12,20 +13,12 @@ export class SeedCommand implements yargs.CommandModule { builder(args: yargs.Argv) { return args - .option('c', { - alias: 'config', + .option('config', { default: 'ormconfig.js', describe: 'Path to the typeorm config file (json or js).', }) - .option('s', { - alias: 'seeds', - default: 'database/seeds', - describe: 'Directory where seeds are.', - }) - .option('f', { - alias: 'factories', - default: 'database/factories', - describe: 'Directory where enity factories are.', + .option('class', { + describe: 'Specific seed class to run.', }) } @@ -33,14 +26,22 @@ export class SeedCommand implements yargs.CommandModule { const log = console.log log(chalk.bold(`typeorm-seeding v${(pkg as any).version}`)) + // Get TypeORM config file + let options + try { + options = await getConnectionOptions(args.config as string) + } catch (error) { + printError('Could not load the config file!', error) + process.exit(1) + } + // Find all factories and seed with help of the config - let factoryFiles - let seedFiles + const factoryFiles = loadFiles(options.factories || ['src/database/factories/**/*.ts']) + const seedFiles = loadFiles(options.seeds || ['src/database/seeds/**/*.ts']) try { - factoryFiles = await loadEntityFactories(args.factories as string) - seedFiles = await loadSeeds(args.seeds as string) + importFiles(factoryFiles) } catch (error) { - printError('Could not load factories and seeds!', error) + printError('Could not load factories!', error) process.exit(1) } @@ -57,7 +58,6 @@ export class SeedCommand implements yargs.CommandModule { // Get database connection and pass it to the seeder try { - const options = await getConnectionOptions(args.config as string) const connection = await createConnection(options) setConnection(connection) } catch (error) { @@ -68,7 +68,7 @@ export class SeedCommand implements yargs.CommandModule { // Show seeds in the console for (const seedFile of seedFiles) { try { - const seedFileObject: any = importSeed(seedFile) + const seedFileObject = importSeed(seedFile) log(chalk.gray.underline(`executing seed:`), chalk.green.bold(`${seedFileObject.name}`)) await runSeed(seedFileObject) } catch (error) { diff --git a/src/connection.ts b/src/connection.ts index aec40fb4..47b94a72 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1,12 +1,8 @@ import * as path from 'path' -import { - Connection, - createConnection as createTypeORMConnection, - ConnectionOptions, -} from 'typeorm' +import { Connection, createConnection as createTypeORMConnection, ConnectionOptions } from 'typeorm' export const getConnectionOptions = async (configPath: string): Promise => { - return require(path.join(process.cwd(), configPath)) + return require(path.join(process.cwd(), configPath)) } export const createConnection = async (configPath: string): Promise => { diff --git a/src/entity-factory.ts b/src/entity-factory.ts index fdca1af4..4026b55d 100644 --- a/src/entity-factory.ts +++ b/src/entity-factory.ts @@ -2,7 +2,7 @@ import * as Faker from 'faker' import { Connection, ObjectType } from 'typeorm' import { FactoryFunction, EntityProperty } from './types' import { isPromiseLike } from './utils/factory.util' -import { printError } from './utils/log.util'; +import { printError } from './utils/log.util' export class EntityFactory { private mapFunction: (entity: Entity) => Promise @@ -22,9 +22,7 @@ export class EntityFactory { * This function is used to alter the generated values of entity, before it * is persist into the database */ - public map( - mapFunction: (entity: Entity) => Promise, - ): EntityFactory { + public map(mapFunction: (entity: Entity) => Promise): EntityFactory { this.mapFunction = mapFunction return this } @@ -32,9 +30,7 @@ export class EntityFactory { /** * Make a new entity, but does not persist it */ - public async make( - overrideParams: EntityProperty = {}, - ): Promise { + public async make(overrideParams: EntityProperty = {}): Promise { if (this.factory) { let entity = await this.resolveEntity(this.factory(Faker, this.settings)) if (this.mapFunction) { @@ -53,9 +49,7 @@ export class EntityFactory { /** * Seed makes a new entity and does persist it */ - public async seed( - overrideParams: EntityProperty = {}, - ): Promise { + public async seed(overrideParams: EntityProperty = {}): Promise { const connection: Connection = (global as any).seeder.connection if (connection) { const em = connection.createEntityManager() @@ -74,10 +68,7 @@ export class EntityFactory { } } - public async makeMany( - amount: number, - overrideParams: EntityProperty = {}, - ): Promise { + public async makeMany(amount: number, overrideParams: EntityProperty = {}): Promise { const list = [] for (let index = 0; index < amount; index++) { list[index] = await this.make(overrideParams) @@ -85,10 +76,7 @@ export class EntityFactory { return list } - public async seedMany( - amount: number, - overrideParams: EntityProperty = {}, - ): Promise { + public async seedMany(amount: number, overrideParams: EntityProperty = {}): Promise { const list = [] for (let index = 0; index < amount; index++) { list[index] = await this.seed(overrideParams) @@ -107,10 +95,7 @@ export class EntityFactory { entity[attribute] = await entity[attribute] } - if ( - typeof entity[attribute] === 'object' && - !(entity[attribute] instanceof Date) - ) { + if (typeof entity[attribute] === 'object' && !(entity[attribute] instanceof Date)) { const subEntityFactory = entity[attribute] try { if (typeof (subEntityFactory as any).make === 'function') { diff --git a/src/helpers.ts b/src/helpers.ts index b8620936..8fdac838 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,10 +1,7 @@ /** * Times repeats a function n times */ -export const times = async ( - n: number, - iteratee: (index: number) => Promise, -): Promise => { +export const times = async (n: number, iteratee: (index: number) => Promise): Promise => { const rs = [] as TResult[] for (let i = 0; i < n; i++) { const r = await iteratee(i) diff --git a/src/importer.ts b/src/importer.ts index 72315afd..4b12926c 100644 --- a/src/importer.ts +++ b/src/importer.ts @@ -1,36 +1,10 @@ -import { loadFiles, importFiles } from './utils/file.util' +import { SeederConstructor } from './types' -// ------------------------------------------------------------------------- -// Util functions -// ------------------------------------------------------------------------- - -const loadFactoryFiles = loadFiles('**/*actory{.js,.ts}') -const loadSeedFiles = loadFiles('**/*{.js,.ts}') - -// ------------------------------------------------------------------------- -// Facade functions -// ------------------------------------------------------------------------- - -export const loadEntityFactories = (pathToFolder: string): Promise => { - return new Promise((resolve, reject) => { - loadFactoryFiles(pathToFolder)(files => { - importFiles(files) - resolve(files) - })(reject) - }) -} - -export const loadSeeds = (pathToFolder: string): Promise => { - return new Promise((resolve, reject) => { - loadSeedFiles(pathToFolder + '/')(resolve)(reject) - }) -} - -export const importSeed = (filePath: string): any => { +export const importSeed = (filePath: string): SeederConstructor => { let className = filePath.split('/')[filePath.split('/').length - 1] className = className.replace('.ts', '').replace('.js', '') className = className.split('-')[className.split('-').length - 1] - + // TODO: without the default const seedFileObject: any = require(filePath) return seedFileObject.default } diff --git a/src/typeorm-seeding.ts b/src/typeorm-seeding.ts index 339aefb2..467c4eeb 100644 --- a/src/typeorm-seeding.ts +++ b/src/typeorm-seeding.ts @@ -2,12 +2,7 @@ import 'reflect-metadata' import { Connection, ObjectType } from 'typeorm' import { EntityFactory } from './entity-factory' -import { - EntityFactoryDefinition, - Factory, - FactoryFunction, - SeedConstructor, -} from './types' +import { EntityFactoryDefinition, Factory, FactoryFunction, SeederConstructor, Seeder } from './types' import { getNameOfEntity } from './utils/factory.util' // ------------------------------------------------------------------------- @@ -16,7 +11,7 @@ import { getNameOfEntity } from './utils/factory.util' export * from './importer' export * from './connection' -export { Factory, Seed } from './types' +export { Factory, Seeder } from './types' export { times } from './helpers' // ------------------------------------------------------------------------- @@ -34,8 +29,7 @@ export { times } from './helpers' /** * Adds the typorm connection to the seed options */ -export const setConnection = (connection: Connection) => - ((global as any).seeder.connection = connection) +export const setConnection = (connection: Connection) => ((global as any).seeder.connection = connection) /** * Returns the typorm connection from our seed options @@ -45,10 +39,7 @@ export const getConnection = () => (global as any).seeder.connection /** * Defines a new entity factory */ -export const define = ( - entity: ObjectType, - factoryFn: FactoryFunction, -) => { +export const define = (entity: ObjectType, factoryFn: FactoryFunction) => { ;(global as any).seeder.entityFactories.set(getNameOfEntity(entity), { entity, factory: factoryFn, @@ -58,25 +49,16 @@ export const define = ( /** * Gets a defined entity factory and pass the settigns along to the entity factory function */ -export const factory: Factory = ( - entity: ObjectType, -) => (settings?: Settings) => { +export const factory: Factory = (entity: ObjectType) => (settings?: Settings) => { const name = getNameOfEntity(entity) const entityFactoryObject = (global as any).seeder.entityFactories.get(name) - return new EntityFactory( - name, - entity, - entityFactoryObject.factory, - settings, - ) + return new EntityFactory(name, entity, entityFactoryObject.factory, settings) } /** * Runs a seed class */ -export const runSeed = async ( - seederConstructor: SeedConstructor, -): Promise => { - const seeder = new seederConstructor() - return seeder.seed(factory, getConnection()) +export const runSeed = async (clazz: SeederConstructor): Promise => { + const seeder: Seeder = new clazz() + return seeder.run(factory, getConnection()) } diff --git a/src/types.ts b/src/types.ts index a0ebd020..b626a876 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,15 +6,12 @@ import { EntityFactory } from './entity-factory' /** * FactoryFunction is the fucntion, which generate a new filled entity */ -export type FactoryFunction = ( - faker: typeof Faker, - settings?: Settings, -) => Entity +export type FactoryFunction = (faker: typeof Faker, settings?: Settings) => Entity /** * EntityProperty defines an object whose keys and values must be properties of the given Entity. */ -export type EntityProperty = { [Property in keyof Entity]?: Entity[Property] }; +export type EntityProperty = { [Property in keyof Entity]?: Entity[Property] } /** * Factory gets the EntityFactory to the given Entity and pass the settings along @@ -26,15 +23,15 @@ export type Factory = ( /** * Seed are the class to create some data. Those seed are run by the cli. */ -export interface Seed { - seed(factory: Factory, connection: Connection): Promise +export interface Seeder { + run(factory: Factory, connection: Connection): Promise } /** * Constructor of the seed class */ -export interface SeedConstructor { - new (): Seed +export interface SeederConstructor { + new (): Seeder } /** diff --git a/src/utils/factory.test.ts b/src/utils/factory.test.ts index f0d000d3..2444ff67 100644 --- a/src/utils/factory.test.ts +++ b/src/utils/factory.test.ts @@ -1,41 +1,39 @@ import { getNameOfEntity, isPromiseLike } from './factory.util' -describe('utils/factory', () => { - describe('getNameOfClass', () => { - test('Passing UserEnity class should return the name of the class', () => { - class UserEntity {} - expect(getNameOfEntity(UserEntity)).toBe('UserEntity') - }) - test('Passing UserEnity function should return the name of the function', () => { - function UserEntity() {} - expect(getNameOfEntity(UserEntity)).toBe('UserEntity') - }) - test('Passing undefinde as a enity-class should throw an error', () => { - try { - getNameOfEntity(undefined) - } catch (error) { - expect(error.message).toBe('Enity is not defined') - } - }) +describe('getNameOfClass', () => { + test('Passing UserEnity class should return the name of the class', () => { + class UserEntity {} + expect(getNameOfEntity(UserEntity)).toBe('UserEntity') }) - describe('isPromiseLike', () => { - test('Passing a promise should return true', () => { - const promise = new Promise(() => {}) - expect(isPromiseLike(promise)).toBeTruthy() - }) - test('Passing no promise should return false', () => { - expect(isPromiseLike(undefined)).toBeFalsy() - expect(isPromiseLike(null)).toBeFalsy() - expect(isPromiseLike('')).toBeFalsy() - expect(isPromiseLike(42)).toBeFalsy() - expect(isPromiseLike(true)).toBeFalsy() - expect(isPromiseLike(false)).toBeFalsy() - expect(isPromiseLike([])).toBeFalsy() - expect(isPromiseLike({})).toBeFalsy() - expect(isPromiseLike(() => {})).toBeFalsy() - class UserEntity {} - expect(isPromiseLike(new UserEntity())).toBeFalsy() - expect(isPromiseLike(new Date())).toBeFalsy() - }) + test('Passing UserEnity function should return the name of the function', () => { + function UserEntity() {} + expect(getNameOfEntity(UserEntity)).toBe('UserEntity') + }) + test('Passing undefinde as a enity-class should throw an error', () => { + try { + getNameOfEntity(undefined) + } catch (error) { + expect(error.message).toBe('Enity is not defined') + } + }) +}) +describe('isPromiseLike', () => { + test('Passing a promise should return true', () => { + const promise = new Promise(() => {}) + expect(isPromiseLike(promise)).toBeTruthy() + }) + test('Passing no promise should return false', () => { + expect(isPromiseLike(undefined)).toBeFalsy() + expect(isPromiseLike(null)).toBeFalsy() + expect(isPromiseLike('')).toBeFalsy() + expect(isPromiseLike(42)).toBeFalsy() + expect(isPromiseLike(true)).toBeFalsy() + expect(isPromiseLike(false)).toBeFalsy() + expect(isPromiseLike([])).toBeFalsy() + expect(isPromiseLike({})).toBeFalsy() + expect(isPromiseLike(() => {})).toBeFalsy() + class UserEntity {} + expect(isPromiseLike(new UserEntity())).toBeFalsy() + expect(isPromiseLike(new Date())).toBeFalsy() }) }) diff --git a/src/utils/file.util.ts b/src/utils/file.util.ts index df3680c6..8b96a466 100644 --- a/src/utils/file.util.ts +++ b/src/utils/file.util.ts @@ -1,14 +1,11 @@ import glob from 'glob' import * as path from 'path' -export const importFiles = (files: string[]) => files.forEach(require) +export const importFiles = (filePaths: string[]) => filePaths.forEach(require) -export const loadFiles = (filePattern: string) => (pathToFolder: string) => ( - successFn: (files: string[]) => void, -) => (failedFn: (error: any) => void) => { - glob( - path.join(process.cwd(), pathToFolder, filePattern), - (error: any, files: string[]) => - error ? failedFn(error) : successFn(files), - ) +export const loadFiles = (filePattern: string[]): string[] => { + const filePaths: string[] = filePattern + .map(pattern => glob.sync(path.join(process.cwd(), pattern))) + .reduce((acc, filePath) => acc.concat(filePath), []) + return filePaths } diff --git a/yarn.lock b/yarn.lock index c720e65b..0e5f3d42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -323,11 +323,25 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/faker@^4.1.4": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/faker/-/faker-4.1.5.tgz#8f620f9c9a67150aa0a32b4e8a407da43fca61d4" integrity sha512-YSDqoBEWYGdNk53xSkkb6REaUaVSlIjxIAGjj/nbLzlZOit7kUU+nA2zC2qQkIVO4MQ+3zl4Sz7aw+kbpHHHUQ== +"@types/glob@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -360,6 +374,11 @@ dependencies: "@types/jest-diff" "*" +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + "@types/node@*", "@types/node@^12.0.4": version "12.0.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.4.tgz#46832183115c904410c275e34cf9403992999c32" @@ -3500,6 +3519,11 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +prettier@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db" + integrity sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg== + pretty-format@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"