Skip to content

Commit

Permalink
feat: add multiple connections and also read .env config options
Browse files Browse the repository at this point in the history
  • Loading branch information
hirsch committed Apr 10, 2020
1 parent 990cc04 commit 69c9956
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 59 deletions.
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ yarn add typeorm-seeding
```

Optional, for `Faker` types

```bash
npm install -D @types/faker
```
Expand All @@ -60,11 +61,18 @@ To configure the path to your seeds and factories change the TypeORM config file
```JavaScript
module.exports = {
...
seeds: ['src/seeds/**/*.seed.ts'],
factories: ['src/factories/**/*.factory.ts'],
seeds: ['src/seeds/**/*{.ts,.js}'],
factories: ['src/factories/**/*{.ts,.js}'],
}
```

or add the following variables to your `.env` file.

```
TYPEORM_SEEDING_FACTORIES=src/seeds/**/*{.ts,.js}
TYPEORM_SEEDING_SEEDS=src/factories/**/*{.ts,.js}
```

![divider](./w3tec-divider.png)

## ❯ Writing Seeders
Expand All @@ -82,7 +90,10 @@ export default class CreateUsers implements Seeder {
.createQueryBuilder()
.insert()
.into(User)
.values([{ firstName: 'Timber', lastName: 'Saw' }, { firstName: 'Phantom', lastName: 'Lancer' }])
.values([
{ firstName: 'Timber', lastName: 'Saw' },
{ firstName: 'Phantom', lastName: 'Lancer' },
])
.execute()
}
}
Expand All @@ -99,7 +110,7 @@ Once you have written your seeder, you can add this script to your `package.json
}
```

And then
And then

```bash
npm run seed
Expand All @@ -112,8 +123,8 @@ For all entities we want to seed, we need to define a factory. To do so we give
Settings can be used to pass some static value into the factory.

```typescript
import Faker from 'faker';
import { define } from "typeorm-seeding";
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { User } from '../entities'

define(User, (faker: typeof Faker, settings: { roles: string[] }) => {
Expand All @@ -134,8 +145,8 @@ define(User, (faker: typeof Faker, settings: { roles: string[] }) => {
Handle relation in the entity factory like this.

```typescript
import Faker from 'faker';
import { define } from 'typeorm-seeding';
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { Pet } from '../entities'

define(Pet, (faker: typeof Faker, settings: undefined) => {
Expand All @@ -145,7 +156,7 @@ define(Pet, (faker: typeof Faker, settings: undefined) => {
const pet = new Pet()
pet.name = name
pet.age = faker.random.number()
pet.user = factory(User)({ roles: ['admin'] })
pet.user = factory(User)({ roles: ['admin'] }) // not yet implemented
return pet
})
```
Expand Down Expand Up @@ -198,7 +209,7 @@ export default class CreatePets implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
const em = connection.createEntityManager()

await times(10, async n => {
await times(10, async (n) => {
// This creates a pet in the database
const pet = await factory(Pet)().seed()
// This only returns a entity with fake data
Expand All @@ -216,11 +227,12 @@ Now you are able to execute your seeds with this command `npm run seed`.

### CLI Options

| Option | Default | Description |
| ------------------ | -------------- | ------------------------------------------------------------- |
| `--class` or `--c` | null | Option to specify a specific seeder class to run individually |
| `--config` | `ormconfig.js` | Path to the typeorm config file (json or js). |

| Option | Default | Description |
| ---------------------- | --------------- | --------------------------------------------------------------------------- |
| `--seed` or `-s` | null | Option to specify a specific seeder class to run individually. |
| `--name` or `-n` | null | Name of the typeorm connection. Required if there are multiple connections. |
| `--configName` or `-c` | `ormconfig.js` | Name to the typeorm config file. |
| `--root` or `-r` | `process.cwd()` | Path to the typeorm config file. |

## ❯ License

Expand Down
25 changes: 18 additions & 7 deletions ormconfig.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
const path = require('path')

module.exports = {
type: 'sqlite',
database: './test.db',
entities: ['sample/entities/**/*{.ts,.js}'],
factories: ['sample/factories/**/*{.ts,.js}'],
seeds: ['sample/seeds/**/*{.ts,.js}'],
}
module.exports = [
{
name: 'sample',
type: 'sqlite',
database: 'test.db',
entities: ['sample/entities/**/*{.ts,.js}'],
factories: ['sample/factories/**/*{.ts,.js}'],
seeds: ['sample/seeds/**/*{.ts,.js}'],
},
{
name: 'other',
type: 'sqlite',
database: 'test.db',
entities: ['sample/entities/**/*{.ts,.js}'],
factories: ['sample/factories/**/*{.ts,.js}'],
seeds: ['sample/seeds/**/*{.ts,.js}'],
}
]
33 changes: 25 additions & 8 deletions src/commands/config.command.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
import * as yargs from 'yargs'
import * as chalk from 'chalk'
import { getConnectionOptions } from '../typeorm-seeding'
import { getConnectionOption } from '../typeorm-seeding'
import { printError } from '../utils/log.util'

export class ConfigCommand implements yargs.CommandModule {
command = 'config'
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: 'configName',
default: '',
describe: 'Name of the typeorm config file (json or js).',
})
.option('n', {
alias: 'name',
default: '',
describe: 'Name of the typeorm connection',
})
.option('r', {
alias: 'root',
default: process.cwd(),
describe: 'Path to your typeorm config file',
})
}

async handler(args: yargs.Arguments) {
const log = console.log
const pkg = require('../../package.json')
log(chalk.bold(`typeorm-seeding v${(pkg as any).version}`))
try {
const options = await getConnectionOptions(args.config as string)
log(options)
const option = await getConnectionOption(
{
root: args.root as string,
configName: args.configName as string,
},
args.name as string,
)
log(option)
} catch (error) {
printError('Could not find the orm config file', error)
process.exit(1)
Expand Down
42 changes: 29 additions & 13 deletions src/commands/seed.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import * as yargs from 'yargs'
import * as ora from 'ora'
import * as chalk from 'chalk'
import { createConnection } from 'typeorm'
import { setConnection, runSeeder, getConnectionOptions, getConnection } from '../typeorm-seeding'
import { printError } from '../utils/log.util'
import { setConnection, runSeeder, getConnectionOption, getConnection } from '../typeorm-seeding'
import { importSeed } from '../importer'
import { loadFiles, importFiles } from '../utils/file.util'
import { ConnectionOptions } from '../connection'
Expand All @@ -14,12 +13,23 @@ export class SeedCommand implements yargs.CommandModule {

builder(args: yargs.Argv) {
return args
.option('config', {
default: 'ormconfig.js',
describe: 'Path to the typeorm config file (json or js).',
.option('c', {
alias: 'configName',
default: '',
describe: 'Name of the typeorm config file (json or js).',
})
.option('class', {
alias: 'c',
.option('n', {
alias: 'name',
default: '',
describe: 'Name of the typeorm connection',
})
.option('r', {
alias: 'root',
default: process.cwd(),
describe: 'Path to your typeorm config file',
})
.option('seed', {
alias: 's',
describe: 'Specific seed class to run.',
})
}
Expand All @@ -36,17 +46,23 @@ export class SeedCommand implements yargs.CommandModule {
const spinner = ora('Loading ormconfig').start()

// Get TypeORM config file
let options: ConnectionOptions
let option: ConnectionOptions
try {
options = await getConnectionOptions(args.config as string)
option = await getConnectionOption(
{
root: args.root as string,
configName: args.configName as string,
},
args.name as string,
)
spinner.succeed('ORM Config loaded')
} catch (error) {
panic(spinner, error, 'Could not load the config file!')
}

// Find all factories and seed with help of the config
spinner.start('Import Factories')
const factoryFiles = loadFiles(options.factories || ['src/database/factories/**/*{.js,.ts}'])
const factoryFiles = loadFiles(option.factories || ['src/database/factories/**/*{.js,.ts}'])
try {
importFiles(factoryFiles)
spinner.succeed('Factories are imported')
Expand All @@ -56,12 +72,12 @@ export class SeedCommand implements yargs.CommandModule {

// Show seeds in the console
spinner.start('Importing Seeders')
const seedFiles = loadFiles(options.seeds || ['src/database/seeds/**/*{.js,.ts}'])
const seedFiles = loadFiles(option.seeds || ['src/database/seeds/**/*{.js,.ts}'])
let seedFileObjects: any[] = []
try {
seedFileObjects = seedFiles
.map((seedFile) => importSeed(seedFile))
.filter((seedFileObject) => args.class === undefined || args.class === seedFileObject.name)
.filter((seedFileObject) => args.seed === undefined || args.seed === seedFileObject.name)
spinner.succeed('Seeders are imported')
} catch (error) {
panic(spinner, error, 'Could not import seeders!')
Expand All @@ -70,7 +86,7 @@ export class SeedCommand implements yargs.CommandModule {
// Get database connection and pass it to the seeder
spinner.start('Connecting to the database')
try {
const connection = await createConnection(options)
const connection = await createConnection(option)
setConnection(connection)
spinner.succeed('Database connected')
} catch (error) {
Expand Down
53 changes: 39 additions & 14 deletions src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,54 @@
import * as path from 'path'
import {
Connection,
ConnectionOptionsReader,
createConnection as createTypeORMConnection,
ConnectionOptions as TypeORMConnectionOptions,
} from 'typeorm'
import { printError } from './utils/log.util'

interface SeedingOptions {
readonly factories: string[]
readonly seeds: string[]
factories: string[]
seeds: string[]
}

export interface ConnectionOptionArguments {
root: string
configName: string
}

export declare type ConnectionOptions = TypeORMConnectionOptions & SeedingOptions

export const getConnectionOptions = async (configPath = 'ormconfig.js'): Promise<ConnectionOptions> => {
return require(path.join(process.cwd(), configPath))
const attachSeedingOptions = (option: ConnectionOptions): ConnectionOptions => {
if (!option.factories) {
option.factories = [process.env.TYPEORM_SEEDING_FACTORIES as string]
}
if (!option.seeds) {
option.seeds = [process.env.TYPEORM_SEEDING_SEEDS as string]
}
return option
}

export const createConnection = async (configPath: string): Promise<Connection> => {
let options = await getConnectionOptions(configPath)
if (Array.isArray(options)) {
options.forEach((item) => {
if (item.name === 'default') {
options = item
}
})
export const getConnectionOption = async (
option: ConnectionOptionArguments,
name: string,
): Promise<ConnectionOptions> => {
const reader = new ConnectionOptionsReader(option)
const options = (await reader.all()) as any[]
if (options.length === 1) {
return attachSeedingOptions(options[0])
}
return createTypeORMConnection(options as TypeORMConnectionOptions)
if (name !== undefined && name !== '') {
const filteredOptions = options.filter((o) => o.name === name)
if (filteredOptions.length === 1) {
return attachSeedingOptions(options[0])
} else {
printError('Could not find any connection with the name=', name)
}
}
printError('There are multiple connections please provide a connection name')
}

export const createConnection = async (option: ConnectionOptionArguments, name: string): Promise<Connection> => {
const connectionOption = await getConnectionOption(option, name)
return createTypeORMConnection(connectionOption)
}
4 changes: 2 additions & 2 deletions src/utils/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('getNameOfClass', () => {
expect(getNameOfEntity(UserEntity)).toBe('UserEntity')
})
test('Passing UserEnity function should return the name of the function', () => {
const UserEntity = () => void 0
const UserEntity = (): any => void 0
expect(getNameOfEntity(UserEntity)).toBe('UserEntity')
})
test('Passing undefinde as a enity-class should throw an error', () => {
Expand All @@ -31,7 +31,7 @@ describe('isPromiseLike', () => {
expect(isPromiseLike(false)).toBeFalsy()
expect(isPromiseLike([])).toBeFalsy()
expect(isPromiseLike({})).toBeFalsy()
expect(isPromiseLike(() => void 0)).toBeFalsy()
expect(isPromiseLike((): any => void 0)).toBeFalsy()
// tslint:disable-next-line
class UserEntity {}
expect(isPromiseLike(new UserEntity())).toBeFalsy()
Expand Down

0 comments on commit 69c9956

Please sign in to comment.