Skip to content

Commit d74449b

Browse files
author
hirsch
committedSep 27, 2018
🎉 Initial commit
1 parent 20a5fa6 commit d74449b

9 files changed

+1453
-23
lines changed
 

‎package.json

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typeorm-seeding",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"description": "TypeORM seeding",
55
"bin": {
66
"seed": "./dist/cli.js"
@@ -26,12 +26,26 @@
2626
}
2727
],
2828
"devDependencies": {
29+
"@types/faker": "^4.1.4",
2930
"rimraf": "^2.6.2",
3031
"rollup": "^0.66.2",
31-
"rollup-plugin-typescript2": "^0.17.0",
3232
"rollup-plugin-cli": "^0.1.5",
33+
"rollup-plugin-commonjs": "^9.1.8",
34+
"rollup-plugin-node-builtins": "^2.1.2",
35+
"rollup-plugin-node-globals": "^1.4.0",
36+
"rollup-plugin-node-resolve": "^3.4.0",
37+
"rollup-plugin-typescript2": "^0.17.0",
3338
"tslint": "^5.11.0",
3439
"typescript": "^3.0.3"
3540
},
36-
"dependencies": {}
41+
"dependencies": {
42+
"@types/node": "^10.11.0",
43+
"chalk": "^2.4.1",
44+
"commander": "^2.18.0",
45+
"faker": "^4.1.0",
46+
"glob": "^7.1.3",
47+
"reflect-metadata": "^0.1.12",
48+
"typeorm": "^0.2.7"
49+
},
50+
"peerDependencies": {}
3751
}

‎rollup.config.js

+35-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import typescript from 'rollup-plugin-typescript2'
22
import cli from 'rollup-plugin-cli';
3+
import commonjs from 'rollup-plugin-commonjs';
4+
import nodeResolve from 'rollup-plugin-node-resolve';
5+
import globals from 'rollup-plugin-node-globals';
6+
import builtins from 'rollup-plugin-node-builtins';
37
import pkg from './package.json'
48

59
export default [{
@@ -8,12 +12,14 @@ export default [{
812
{
913
file: pkg.main,
1014
format: 'cjs',
11-
},
12-
{
13-
file: pkg.module,
14-
format: 'es',
15-
},
15+
}
1616
],
17+
onwarn: function (warning, warn) {
18+
if (warning.code === 'THIS_IS_UNDEFINED') {
19+
return;
20+
}
21+
warn(warning);
22+
},
1723
external: [
1824
...Object.keys(pkg.dependencies || {}),
1925
...Object.keys(pkg.peerDependencies || {}),
@@ -22,15 +28,29 @@ export default [{
2228
typescript({
2329
typescript: require('typescript'),
2430
}),
31+
nodeResolve({
32+
module: true,
33+
jsnext: true,
34+
main: true,
35+
}),
36+
commonjs(),
37+
globals(),
38+
builtins(),
2539
],
26-
},{
40+
}, {
2741
input: 'src/cli.ts',
2842
output: [
2943
{
3044
file: pkg.bin.seed,
31-
format: 'umd',
45+
format: 'cjs',
3246
}
3347
],
48+
onwarn: function (warning, warn) {
49+
if (warning.code === 'THIS_IS_UNDEFINED') {
50+
return;
51+
}
52+
warn(warning);
53+
},
3454
external: [
3555
...Object.keys(pkg.dependencies || {}),
3656
...Object.keys(pkg.peerDependencies || {}),
@@ -39,6 +59,14 @@ export default [{
3959
typescript({
4060
typescript: require('typescript'),
4161
}),
62+
nodeResolve({
63+
module: true,
64+
jsnext: true,
65+
main: true,
66+
}),
67+
commonjs(),
68+
globals(),
69+
builtins(),
4270
cli(),
4371
],
4472
}]

‎src/cli.ts

+91-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,92 @@
1+
import chalk from 'chalk';
2+
import commander from 'commander';
3+
import * as path from 'path';
4+
import { createConnection, getConnectionOptions } from 'typeorm';
15

2-
console.log('Hello');
6+
import { loadEntityFactories, runSeed, setConnection } from './typeorm-seeding';
7+
import { loadSeeds } from './importer';
8+
9+
// Cli helper
10+
commander
11+
.version('1.0.0')
12+
.description('Run database seeds of your project')
13+
.option('-L, --logging', 'enable sql query logging')
14+
.option('--factories <path>', 'add filepath for your factories')
15+
.option('--seeds <path>', 'add filepath for your seeds')
16+
.option('--run <seeds>', 'run specific seeds (file names without extension)', (val) => val.split(','))
17+
.option('--config <file>', 'path to your ormconfig.json file (must be a json)')
18+
.parse(process.argv);
19+
20+
// Get cli parameter for a different factory path
21+
const factoryPath = (commander.factories)
22+
? commander.factories
23+
: 'src/database/';
24+
25+
// Get cli parameter for a different seeds path
26+
const seedsPath = (commander.seeds)
27+
? commander.seeds
28+
: 'src/database/seeds/';
29+
30+
// Get a list of seeds
31+
const listOfSeeds = (commander.run)
32+
? commander.run.map(l => l.trim()).filter(l => l.length > 0)
33+
: [];
34+
35+
// Search for seeds and factories
36+
const run = async () => {
37+
const log = console.log;
38+
39+
let factoryFiles;
40+
let seedFiles;
41+
try {
42+
factoryFiles = await loadEntityFactories(factoryPath);
43+
seedFiles = await loadSeeds(seedsPath);
44+
} catch (error) {
45+
return handleError(error);
46+
}
47+
48+
// Filter seeds
49+
if (listOfSeeds.length > 0) {
50+
seedFiles = seedFiles.filter(sf => listOfSeeds.indexOf(path.basename(sf).replace('.ts', '')) >= 0);
51+
}
52+
53+
// Status logging to print out the amount of factories and seeds.
54+
log(chalk.bold('seeds'));
55+
log('🔎 ', chalk.gray.underline(`found:`),
56+
chalk.blue.bold(`${factoryFiles.length} factories`, chalk.gray('&'), chalk.blue.bold(`${seedFiles.length} seeds`)));
57+
58+
// Get database connection and pass it to the seeder
59+
let connection;
60+
try {
61+
const connectionOptions = await getConnectionOptions();
62+
connection = await createConnection(connectionOptions);
63+
setConnection(connection);
64+
} catch (error) {
65+
return handleError(error);
66+
}
67+
68+
// Show seeds in the console
69+
for (const seedFile of seedFiles) {
70+
try {
71+
let className = seedFile.split('/')[seedFile.split('/').length - 1];
72+
className = className.replace('.ts', '').replace('.js', '');
73+
className = className.split('-')[className.split('-').length - 1];
74+
log('\n' + chalk.gray.underline(`executing seed: `), chalk.green.bold(`${className}`));
75+
const seedFileObject: any = require(seedFile);
76+
await runSeed(seedFileObject[className]);
77+
} catch (error) {
78+
console.error('Could not run seed ', error);
79+
process.exit(1);
80+
}
81+
}
82+
83+
log('\n👍 ', chalk.gray.underline(`finished seeding`));
84+
process.exit(0);
85+
};
86+
87+
const handleError = (error) => {
88+
console.error(error);
89+
process.exit(1);
90+
};
91+
92+
run();

‎src/entity-factory.ts

+100
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,103 @@
1+
import * as Faker from 'faker';
2+
import { Connection, ObjectType } from 'typeorm';
3+
4+
import { FactoryFunction } from './types';
5+
import { isPromiseLike } from './utils';
6+
17
export class EntityFactory<Entity, Settings> {
28

9+
private mapFunction: (entity: Entity) => Promise<Entity>;
10+
11+
constructor(
12+
public name: string,
13+
public entity: ObjectType<Entity>,
14+
private factory: FactoryFunction<Entity, Settings>,
15+
private settings?: Settings
16+
) { }
17+
18+
// -------------------------------------------------------------------------
19+
// Public API
20+
// -------------------------------------------------------------------------
21+
22+
/**
23+
* This function is used to alter the generated values of entity, before it
24+
* is persist into the database
25+
*/
26+
public map(mapFunction: (entity: Entity) => Promise<Entity>): EntityFactory<Entity, Settings> {
27+
this.mapFunction = mapFunction;
28+
return this;
29+
}
30+
31+
/**
32+
* Make a new entity, but does not persist it
33+
*/
34+
public async make(): Promise<Entity> {
35+
if (this.factory) {
36+
let entity = await this.resolveEntity(this.factory(Faker, this.settings));
37+
if (this.mapFunction) {
38+
entity = await this.mapFunction(entity);
39+
}
40+
return entity;
41+
}
42+
throw new Error('Could not found entity');
43+
}
44+
45+
/**
46+
* Seed makes a new entity and does persist it
47+
*/
48+
public async seed(): Promise<Entity> {
49+
const connection: Connection = (global as any).seeder.connection;
50+
if (connection) {
51+
const em = connection.createEntityManager();
52+
try {
53+
const entity = await this.make();
54+
return await em.save<Entity>(entity);
55+
} catch (error) {
56+
throw new Error('Could not save entity');
57+
}
58+
} else {
59+
throw new Error('No db connection is given');
60+
}
61+
}
62+
63+
public async makeMany(amount: number): Promise<Entity[]> {
64+
const list = [];
65+
for (let index = 0; index < amount; index++) {
66+
list[index] = await this.make();
67+
}
68+
return list;
69+
}
70+
71+
public async seedMany(amount: number): Promise<Entity[]> {
72+
const list = [];
73+
for (let index = 0; index < amount; index++) {
74+
list[index] = await this.seed();
75+
}
76+
return list;
77+
}
78+
79+
// -------------------------------------------------------------------------
80+
// Prrivat Helpers
81+
// -------------------------------------------------------------------------
82+
83+
private async resolveEntity(entity: Entity): Promise<Entity> {
84+
for (const attribute in entity) {
85+
if (entity.hasOwnProperty(attribute)) {
86+
if (isPromiseLike(entity[attribute])) {
87+
entity[attribute] = await entity[attribute];
88+
}
89+
90+
if (typeof entity[attribute] === 'object') {
91+
const subEntityFactory = entity[attribute];
92+
try {
93+
entity[attribute] = await (subEntityFactory as any).make();
94+
} catch (e) {
95+
throw new Error(`Could not make ${(subEntityFactory as any).name}`);
96+
}
97+
}
98+
}
99+
}
100+
return entity;
101+
}
102+
3103
}

‎src/importer.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import glob from 'glob';
2+
import * as path from 'path';
3+
4+
// -------------------------------------------------------------------------
5+
// Util functions
6+
// -------------------------------------------------------------------------
7+
8+
const importFactories = (files: string[]) => files.forEach(require);
9+
10+
const loadFiles =
11+
(filePattern: string) =>
12+
(pathToFolder: string) =>
13+
(successFn: (files: string[]) => void) =>
14+
(failedFn: (error: any) => void) => {
15+
glob(path.join(process.cwd(), pathToFolder, filePattern), (error: any, files: string[]) => error
16+
? failedFn(error)
17+
: successFn(files));
18+
};
19+
20+
const loadFactoryFiles = loadFiles('**/*Factory{.js,.ts}');
21+
22+
// -------------------------------------------------------------------------
23+
// Facade functions
24+
// -------------------------------------------------------------------------
25+
26+
export const loadEntityFactories = (pathToFolder: string): Promise<string[]> => {
27+
return new Promise((resolve, reject) => {
28+
loadFactoryFiles(pathToFolder)(files => {
29+
importFactories(files);
30+
resolve(files);
31+
})(reject);
32+
});
33+
};
34+
35+
export const loadSeeds = (pathToFolder: string): Promise<string[]> => {
36+
return new Promise((resolve, reject) => {
37+
loadFiles('**/*{.js,.ts}')(pathToFolder)(resolve)(reject);
38+
});
39+
};

‎src/typeorm-seeding.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,66 @@
11
import 'reflect-metadata';
2+
import { Connection, ObjectType } from 'typeorm';
23

34
import { EntityFactory } from './entity-factory';
5+
import { EntityFactoryDefinition, Factory, FactoryFunction, SeedConstructor } from './types';
6+
import { getNameOfClass } from './utils';
47

5-
export const greet = () => console.log('Hello, world?!', EntityFactory);
8+
// -------------------------------------------------------------------------
9+
// Handy Exports
10+
// -------------------------------------------------------------------------
11+
12+
export * from './importer';
13+
export { Factory, Seed } from './types';
14+
export { times } from './utils';
15+
16+
// -------------------------------------------------------------------------
17+
// Types & Variables
18+
// -------------------------------------------------------------------------
19+
20+
(global as any).seeder = {
21+
connection: undefined,
22+
entityFactories: new Map<string, EntityFactoryDefinition<any, any>>(),
23+
};
24+
25+
// -------------------------------------------------------------------------
26+
// Facade functions
27+
// -------------------------------------------------------------------------
28+
29+
/**
30+
* Adds the typorm connection to the seed options
31+
*/
32+
export const setConnection = (connection: Connection) => (global as any).seeder.connection = connection;
33+
34+
/**
35+
* Returns the typorm connection from our seed options
36+
*/
37+
export const getConnection = () => (global as any).seeder.connection;
38+
39+
/**
40+
* Defines a new entity factory
41+
*/
42+
export const define = <Entity, Settings>(entity: ObjectType<Entity>, factoryFn: FactoryFunction<Entity, Settings>) => {
43+
(global as any).seeder.entityFactories.set(getNameOfClass(entity), { entity, factory: factoryFn });
44+
};
45+
46+
/**
47+
* Gets a defined entity factory and pass the settigns along to the entity factory function
48+
*/
49+
export const factory: Factory = <Entity, Settings>(entity: ObjectType<Entity>) => (settings?: Settings) => {
50+
const name = getNameOfClass(entity);
51+
const entityFactoryObject = (global as any).seeder.entityFactories.get(name);
52+
return new EntityFactory<Entity, Settings>(
53+
name,
54+
entity,
55+
entityFactoryObject.factory,
56+
settings
57+
);
58+
};
59+
60+
/**
61+
* Runs a seed class
62+
*/
63+
export const runSeed = async <T>(seederConstructor: SeedConstructor): Promise<T> => {
64+
const seeder = new seederConstructor();
65+
return seeder.seed(factory, getConnection());
66+
};

‎src/types.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as Faker from 'faker';
2+
import { Connection, ObjectType } from 'typeorm';
3+
4+
import { EntityFactory } from './entity-factory';
5+
6+
/**
7+
* FactoryFunction is the fucntion, which generate a new filled entity
8+
*/
9+
export type FactoryFunction<Entity, Settings> = (faker: typeof Faker, settings?: Settings) => Entity;
10+
11+
/**
12+
* Factory gets the EntityFactory to the given Entity and pass the settings along
13+
*/
14+
export type Factory = <Entity, Settings>(entity: ObjectType<Entity>) => (settings?: Settings) => EntityFactory<Entity, Settings>;
15+
16+
/**
17+
* Seed are the class to create some data. Those seed are run by the cli.
18+
*/
19+
export interface Seed {
20+
seed(factory: Factory, connection: Connection): Promise<any>;
21+
}
22+
23+
/**
24+
* Constructor of the seed class
25+
*/
26+
export interface SeedConstructor {
27+
new(): Seed;
28+
}
29+
30+
/**
31+
* Value of our EntityFactory state
32+
*/
33+
export interface EntityFactoryDefinition<Entity, Settings> {
34+
entity: ObjectType<Entity>;
35+
factory: FactoryFunction<Entity, Settings>;
36+
}

‎src/utils.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Returns the name of a class
3+
*/
4+
export const getNameOfClass = (c: any): string => new c().constructor.name;
5+
6+
/**
7+
* Checks if the given argument is a promise
8+
*/
9+
export const isPromiseLike = (o: any): boolean => !!o && (typeof o === 'object' || typeof o === 'function') && typeof o.then === 'function';
10+
11+
/**
12+
* Times repeats a function n times
13+
*/
14+
export const times = async <TResult>(n: number, iteratee: (index: number) => Promise<TResult>): Promise<TResult[]> => {
15+
const rs = [] as TResult[];
16+
for (let i = 0; i < n; i++) {
17+
const r = await iteratee(i);
18+
rs.push(r);
19+
}
20+
return rs;
21+
};

‎yarn.lock

+1,052-11
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.