Skip to content

Commit

Permalink
feat: add support for seeders
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jun 20, 2020
1 parent e755e2e commit a213661
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 8 deletions.
6 changes: 3 additions & 3 deletions adonis-typings/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ declare module '@ioc:Adonis/Lucid/Migrator' {
* Migration node returned by the migration source
* implementation
*/
export type MigrationNode = {
export type FileNode<T extends any> = {
absPath: string,
name: string,
source: SchemaConstructorContract,
source: T,
}

/**
Expand All @@ -41,7 +41,7 @@ declare module '@ioc:Adonis/Lucid/Migrator' {
export type MigratedFileNode = {
status: 'completed' | 'error' | 'pending',
queries: string[],
migration: MigrationNode,
migration: FileNode<SchemaConstructorContract>,
batch: number,
}

Expand Down
34 changes: 34 additions & 0 deletions adonis-typings/seeds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare module '@ioc:Adonis/Lucid/Seeder' {
import { QueryClientContract } from '@ioc:Adonis/Lucid/Database'

/**
* Shape of seeder class
*/
export type SeederConstructorContract = {
developmentOnly: boolean,
client: QueryClientContract,
new (client: QueryClientContract): {
run (): Promise<void>
}
}

/**
* Shape of file node returned by the run method
*/
export type SeederFileNode = {
absPath: string,
name: string,
source: SeederConstructorContract,
status: 'pending' | 'completed' | 'failed' | 'ignored',
error?: any,
}
}
155 changes: 155 additions & 0 deletions commands/DbSeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { inject } from '@adonisjs/fold'
import { SeederFileNode } from '@ioc:Adonis/Lucid/Seeder'
import { BaseCommand, Kernel, flags } from '@adonisjs/ace'
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

@inject([null, null, 'Adonis/Lucid/Database'])
export default class DbSeed extends BaseCommand {
public static commandName = 'db:seed'
public static description = 'Execute database seeder files'

/**
* Choose a custom pre-defined connection. Otherwise, we use the
* default connection
*/
@flags.string({ description: 'Define a custom database connection for the seeders', alias: 'c' })
public connection: string

/**
* Interactive mode allows selecting seeder files
*/
@flags.boolean({ description: 'Run seeders in interactive mode', alias: 'i' })
public interactive: boolean

/**
* Define a custom set of seeder files. Interactive and files together ignores
* the interactive mode.
*/
@flags.array({ description: 'Define a custom set of seeders files names to run', alias: 'f' })
public files: string[]

/**
* This command loads the application, since we need the runtime
* to find the migration directories for a given connection
*/
public static settings = {
loadApp: true,
}

constructor (app: ApplicationContract, kernel: Kernel, private db: DatabaseContract) {
super(app, kernel)
}

/**
* Print log message to the console
*/
private printLogMessage (file: SeederFileNode) {
const colors = this['colors']

let color: keyof typeof colors = 'gray'
let message: string = ''
let prefix: string = ''

switch (file.status) {
case 'pending':
message = 'pending '
color = 'gray'
break
case 'failed':
message = 'error '
prefix = file.error!.message
color = 'red'
break
case 'ignored':
message = 'ignored '
prefix = 'Enabled only in development environment'
color = 'dim'
break
case 'completed':
message = 'completed'
color = 'green'
break
}

console.log(`${colors[color]('❯')} ${colors[color](message)} ${file.name}`)
if (prefix) {
console.log(` ${colors[color](prefix)}`)
}
}

/**
* Execute command
*/
public async handle (): Promise<void> {
const client = this.db.connection(this.connection || this.db.primaryConnectionName)

/**
* Ensure the define connection name does exists in the
* config file
*/
if (!client) {
this.logger.error(
`${this.connection} is not a valid connection name. Double check config/database file`,
)
return
}

const { SeedsRunner } = await import('../src/SeedsRunner')
const runner = new SeedsRunner(this.application.seedsPath(), process.env.NODE_ENV === 'development')

/**
* List of available files
*/
const files = await runner.listSeeders()

/**
* List of selected files. Initially, all files are selected and one can
* define cherry pick using the `--interactive` or `--files` flag.
*/
let selectedFileNames: string[] = files.map(({ name }) => name)

if (this.files.length) {
selectedFileNames = this.files
if (this.interactive) {
this.logger.warn('Cannot use "--interactive" and "--files" together. Ignoring "--interactive"')
}
} else if (this.interactive) {
selectedFileNames = await this.prompt.multiple('Select files to run', files.map((file) => {
return {
disabled: file.status === 'ignored',
name: file.name,
hint: file.status === 'ignored' ? '(Enabled only in development environment)' : '',
}
}))
}

/**
* Execute selected seeders
*/
for (let fileName of selectedFileNames) {
const sourceFile = files.find(({ name }) => fileName === name)
if (!sourceFile) {
this.printLogMessage({
name: fileName,
status: 'failed',
error: new Error('Invalid file path. Pass relative path from the "database/seeds" directory'),
source: {} as any,
absPath: fileName,
})
} else {
await runner.run(sourceFile, client)
this.printLogMessage(sourceFile)
}
}
}
}
46 changes: 46 additions & 0 deletions commands/MakeSeeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { join } from 'path'
import { BaseCommand, args } from '@adonisjs/ace'

export default class MakeSeeder extends BaseCommand {
public static commandName = 'make:seeder'
public static description = 'Make a new Seeder file'

/**
* The name of the seeder file.
*/
@args.string({ description: 'Name of the seeder class' })
public name: string

/**
* Execute command
*/
public async handle (): Promise<void> {
const stub = join(
__dirname,
'..',
'templates',
'seeder.txt',
)

const path = this.application.seedsPath()

this
.generator
.addFile(this.name, { pattern: 'pascalcase', form: 'singular' })
.stub(stub)
.destinationDir(path || 'database/Seeders')
.useMustache()
.appRoot(this.application.cliCwd || this.application.appRoot)

await this.generator.run()
}
}
2 changes: 2 additions & 0 deletions commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
*/

export default [
'@adonisjs/lucid/build/commands/DbSeed',
'@adonisjs/lucid/build/commands/MakeModel',
'@adonisjs/lucid/build/commands/MakeMigration',
'@adonisjs/lucid/build/commands/MakeSeeder',
'@adonisjs/lucid/build/commands/Migration/Run',
'@adonisjs/lucid/build/commands/Migration/Rollback',
'@adonisjs/lucid/build/commands/Migration/Status',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
"@poppinss/utils": "^2.2.6",
"@types/faker": "^4.1.12",
"cli-table3": "^0.6.0",
"fast-deep-equal": "^3.1.1",
"faker": "^4.1.0",
"fast-deep-equal": "^3.1.1",
"kleur": "^3.0.3",
"knex": "^0.21.1",
"knex-dynamic-connection": "^1.0.5",
Expand Down
5 changes: 3 additions & 2 deletions src/Migrator/MigrationSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
/// <reference path="../../adonis-typings/index.ts" />

import { join, isAbsolute, extname } from 'path'
import { FileNode } from '@ioc:Adonis/Lucid/Migrator'
import { esmRequire, fsReadAll } from '@poppinss/utils'
import { MigrationNode } from '@ioc:Adonis/Lucid/Migrator'
import { ConnectionConfig } from '@ioc:Adonis/Lucid/Database'
import { SchemaConstructorContract } from '@ioc:Adonis/Lucid/Schema'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

/**
Expand All @@ -29,7 +30,7 @@ export class MigrationSource {
* Returns an array of files inside a given directory. Relative
* paths are resolved from the project root
*/
private getDirectoryFiles (directoryPath: string): Promise<MigrationNode[]> {
private getDirectoryFiles (directoryPath: string): Promise<FileNode<SchemaConstructorContract>[]> {
const basePath = this.app.appRoot

return new Promise((resolve, reject) => {
Expand Down
5 changes: 3 additions & 2 deletions src/Migrator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Exception } from '@poppinss/utils'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

import {
MigrationNode,
FileNode,
MigratorOptions,
MigratedFileNode,
MigratorContract,
Expand All @@ -26,6 +26,7 @@ import {
QueryClientContract,
TransactionClientContract,
} from '@ioc:Adonis/Lucid/Database'
import { SchemaConstructorContract } from '@ioc:Adonis/Lucid/Schema'

import { MigrationSource } from './MigrationSource'

Expand Down Expand Up @@ -182,7 +183,7 @@ export class Migrator extends EventEmitter implements MigratorContract {
* Executes a given migration node and cleans up any created transactions
* in case of failure
*/
private async executeMigration (migration: MigrationNode) {
private async executeMigration (migration: FileNode<SchemaConstructorContract>) {
const client = await this.getClient(migration.source.disableTransactions)

try {
Expand Down
Loading

0 comments on commit a213661

Please sign in to comment.