diff --git a/src/commands/make-publish.ts b/src/commands/make-publish.ts index 707a3e12..604c3ef2 100644 --- a/src/commands/make-publish.ts +++ b/src/commands/make-publish.ts @@ -1,5 +1,5 @@ import { grey, cyan } from 'chalk'; -import { Command } from '@oclif/command'; +import { Command, flags } from '@oclif/command'; import { loadConfig } from '../config'; import { printInfo, printLine } from '../util/io'; @@ -7,6 +7,12 @@ import * as fileMakerService from '../service/fileMaker'; class MakePublish extends Command { static description = 'Publish migration templates files.'; + static flags = { + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' + }) + }; /** * CLI command execution handler. @@ -14,7 +20,8 @@ class MakePublish extends Command { * @returns {Promise} */ async run(): Promise { - const config = await loadConfig(); + const { flags: parsedFlags } = this.parse(MakePublish); + const config = await loadConfig(parsedFlags.config); await printLine(); diff --git a/src/commands/make.ts b/src/commands/make.ts index c7034bc6..ad33d45a 100644 --- a/src/commands/make.ts +++ b/src/commands/make.ts @@ -17,6 +17,10 @@ class Make extends Command { default: false, description: 'Generate create table stub.' }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' + }), type: flags.string({ char: 't', helpValue: 'TYPE', @@ -33,7 +37,7 @@ class Make extends Command { */ async run(): Promise { const { args, flags: parsedFlags } = this.parse(Make); - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const list = await this.makeFiles(config, args.name, parsedFlags.type, { create: parsedFlags.create, objectName: parsedFlags['object-name'] diff --git a/src/commands/migrate-latest.ts b/src/commands/migrate-latest.ts index 128c5746..1bf63452 100644 --- a/src/commands/migrate-latest.ts +++ b/src/commands/migrate-latest.ts @@ -19,6 +19,10 @@ class MigrateLatest extends Command { 'connection-resolver': flags.string({ helpValue: 'PATH', description: 'Path to the connection resolver.' + }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' }) }; @@ -72,7 +76,7 @@ class MigrateLatest extends Command { async run(): Promise { const { flags: parsedFlags } = this.parse(MigrateLatest); const isDryRun = parsedFlags['dry-run']; - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const connections = await resolveConnections(config, parsedFlags['connection-resolver']); if (isDryRun) await printLine(magenta('\n• DRY RUN STARTED\n')); diff --git a/src/commands/migrate-list.ts b/src/commands/migrate-list.ts index c067b0e2..79cf4ead 100644 --- a/src/commands/migrate-list.ts +++ b/src/commands/migrate-list.ts @@ -17,6 +17,10 @@ class MigrateList extends Command { 'connection-resolver': flags.string({ helpValue: 'PATH', description: 'Path to the connection resolver.' + }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' }) }; @@ -67,7 +71,7 @@ class MigrateList extends Command { */ async run(): Promise { const { flags: parsedFlags } = this.parse(MigrateList); - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const connections = await resolveConnections(config, parsedFlags['connection-resolver']); const results = await migrateList(config, connections, { diff --git a/src/commands/migrate-rollback.ts b/src/commands/migrate-rollback.ts index 91569c78..19a015ed 100644 --- a/src/commands/migrate-rollback.ts +++ b/src/commands/migrate-rollback.ts @@ -19,6 +19,10 @@ class MigrateRollback extends Command { 'connection-resolver': flags.string({ helpValue: 'PATH', description: 'Path to the connection resolver.' + }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' }) }; @@ -71,7 +75,7 @@ class MigrateRollback extends Command { async run(): Promise { const { flags: parsedFlags } = this.parse(MigrateRollback); const isDryRun = parsedFlags['dry-run']; - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const connections = await resolveConnections(config, parsedFlags['connection-resolver']); if (isDryRun) await printLine(magenta('\n• DRY RUN STARTED\n')); diff --git a/src/commands/prune.ts b/src/commands/prune.ts index 97d96b54..5de2caf2 100644 --- a/src/commands/prune.ts +++ b/src/commands/prune.ts @@ -18,6 +18,10 @@ class Prune extends Command { 'connection-resolver': flags.string({ helpValue: 'PATH', description: 'Path to the connection resolver.' + }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' }) }; @@ -51,7 +55,7 @@ class Prune extends Command { async run(): Promise { const { flags: parsedFlags } = this.parse(Prune); const isDryRun = parsedFlags['dry-run']; - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const connections = await resolveConnections(config, parsedFlags['connection-resolver']); if (isDryRun) await printLine(magenta('\n• DRY RUN STARTED\n')); diff --git a/src/commands/synchronize.ts b/src/commands/synchronize.ts index 78f92075..eb527e14 100644 --- a/src/commands/synchronize.ts +++ b/src/commands/synchronize.ts @@ -25,6 +25,10 @@ class Synchronize extends Command { 'connection-resolver': flags.string({ helpValue: 'PATH', description: 'Path to the connection resolver.' + }), + config: flags.string({ + char: 'c', + description: 'Custom configuration file.' }) }; @@ -131,7 +135,7 @@ class Synchronize extends Command { const isDryRun = parsedFlags['dry-run']; try { - const config = await loadConfig(); + const config = await loadConfig(parsedFlags.config); const connections = await resolveConnections(config, parsedFlags['connection-resolver']); const timeStart = process.hrtime(); diff --git a/src/config.ts b/src/config.ts index 423c5c0b..e1e670e1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -11,6 +11,8 @@ import ConnectionsFileSchema from './domain/ConnectionsFileSchema'; import { prepareInjectionConfigVars } from './service/configInjection'; import { DEFAULT_CONFIG, CONFIG_FILENAME, CONNECTIONS_FILENAME, REQUIRED_ENV_KEYS } from './constants'; +const CONFIG_FILE_CONVENTION = /^sync-db-\w+\.yml$/; + interface ConnectionResolver { resolve: (config: Configuration) => Promise; } @@ -41,11 +43,18 @@ export function getSqlBasePath(config: Configuration): string { * * @returns {Promise} */ -export async function loadConfig(): Promise { +export async function loadConfig(configFilename: string = CONFIG_FILENAME): Promise { log('Resolving config file.'); + const isAbsolutePath = path.isAbsolute(configFilename); + const filename = isAbsolutePath ? path.parse(configFilename).base : configFilename; + const match = filename.match(CONFIG_FILE_CONVENTION) || filename === CONFIG_FILENAME; + + if (!match) { + throw new Error(`The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml`); + } - const filename = path.resolve(process.cwd(), CONFIG_FILENAME); - const loadedConfig = (await yaml.load(filename)) as Configuration; + const filepath = isAbsolutePath ? configFilename : path.join(process.cwd(), configFilename); + const loadedConfig = (await yaml.load(filepath)) as Configuration; log('Resolved config file.'); diff --git a/test/cli/commands/make-publish.test.ts b/test/cli/commands/make-publish.test.ts index 03e7047b..e46cd265 100644 --- a/test/cli/commands/make-publish.test.ts +++ b/test/cli/commands/make-publish.test.ts @@ -143,4 +143,63 @@ describe('CLI: make-publish', () => { expect(createUp).to.equal('CREATE TABLE {{table}} (id INT PRIMARY KEY)'); expect(createDown).to.equal('DROP TABLE {{table}}'); }); + + it('should create respective stub files based on configuration from --config flag.', async () => { + // Write sync-db.yml file. + const cwd = await mkdtemp(); + const stubPath = path.join(cwd, 'src/stub'); + await mkdir(stubPath, { recursive: true }); + + const migrationPath1 = path.join(cwd, 'src/migration1'); + await mkdir(migrationPath1, { recursive: true }); + + await write( + path.join(cwd, 'sync-db.yml'), + yaml.stringify({ + migration: { + directory: 'migration', + sourceType: 'typescript' + } + } as Configuration) + ); + + await write( + path.join(cwd, 'sync-db-test.yml'), + yaml.stringify({ + migration: { + directory: 'migration1', + sourceType: 'javascript' + } + } as Configuration) + ); + + const { stdout } = await runCli(['make-publish'], { cwd }); + + // Check the output. + expect(stdout).to.match(/src\/stub\/create_ts\.stub/); + expect(stdout).to.match(/src\/stub\/update_ts\.stub/); + + // Check files are created. + const files = await glob(stubPath); + const createFileExists = await exists(path.join(stubPath, queryByPattern(files, /create_ts\.stub/))); + const updateFileExists = await exists(path.join(stubPath, queryByPattern(files, /update_ts\.stub/))); + + expect(files.length).to.equal(2); + expect(createFileExists).to.equal(true); + expect(updateFileExists).to.equal(true); + + const { stdout: stdout1 } = await runCli(['make-publish', '--config=sync-db-test.yml'], { cwd }); + + // Check the output. + expect(stdout1).to.match(/src\/stub\/create_js\.stub/); + expect(stdout1).to.match(/src\/stub\/update_js\.stub/); + + // Check files are created. + const files1 = await glob(stubPath); + const createFileExists1 = await exists(path.join(stubPath, queryByPattern(files1, /create_ts\.stub/))); + const updateFileExists1 = await exists(path.join(stubPath, queryByPattern(files1, /update_ts\.stub/))); + + expect(createFileExists1).to.equal(true); + expect(updateFileExists1).to.equal(true); + }); }); diff --git a/test/cli/commands/make.test.ts b/test/cli/commands/make.test.ts index 4808460c..41fd0863 100644 --- a/test/cli/commands/make.test.ts +++ b/test/cli/commands/make.test.ts @@ -198,39 +198,6 @@ describe('CLI: make', () => { expect(migrationFile).to.equal(interpolate(fileOutput, { table: 'demo_users' })); }); - it('should create a migration file with template when name matches filename convention for typescript.', async () => { - // Write sync-db.yml file. - const cwd = await mkdtemp(); - const migrationPath = path.join(cwd, 'src/migration'); - await mkdir(migrationPath, { recursive: true }); - await write( - path.join(cwd, 'sync-db.yml'), - yaml.stringify({ - migration: { - directory: 'migration', - sourceType: 'typescript' - } - } as Configuration) - ); - - const { stdout } = await runCli(['make', 'create_demo_users_table'], { cwd }); - - // Check the output. - expect(stdout).to.match(/Created.+\d{13}_create_demo_users_table\.ts/); - - // Check files are created. - const files = await glob(migrationPath); - - expect(files.length).to.equal(1); - - const migrationFile = await read( - path.join(migrationPath, queryByPattern(files, /\d{13}_create_demo_users_table\.ts/)) - ); - const fileOutput = await read(path.join(MIGRATION_TEMPLATE_PATH, 'create_ts.stub')); - - expect(migrationFile).to.equal(interpolate(fileOutput, { table: 'demo_users' })); - }); - it('should create a migration file with custom template for typescript.', async () => { // Write sync-db.yml file. const cwd = await mkdtemp(); @@ -334,4 +301,55 @@ describe('CLI: make', () => { expect(upFile).to.equal(interpolate(upSQL, { table: 'settings' })); expect(downFile).to.equal(interpolate(downSQL, { table: 'settings' })); }); + + it('should make migration based on custom configurations with --config flag.', async () => { + // Write sync-db.yml file. + const cwd = await mkdtemp(); + + const migrationPath = path.join(cwd, 'src/migration'); + await mkdir(migrationPath, { recursive: true }); + + const migrationPath1 = path.join(cwd, 'src/migration1'); + await mkdir(migrationPath1, { recursive: true }); + + await write( + path.join(cwd, 'sync-db.yml'), + yaml.stringify({ + migration: { + directory: 'migration' + } + } as Configuration) + ); + + await write( + path.join(cwd, 'sync-db-test.yml'), + yaml.stringify({ + migration: { + directory: 'migration1', + sourceType: 'typescript' + } + } as Configuration) + ); + + const { stdout } = await runCli(['make', 'settings'], { cwd }); + + // Check the output. + expect(stdout).to.match(/Created.+\d{13}_settings\.up\.sql/); + expect(stdout).to.match(/Created.+\d{13}_settings\.down\.sql/); + + // Check files are created. + const files = await glob(migrationPath); + + expect(files.length).to.equal(2); + + const { stdout: stdout1 } = await runCli(['make', 'settings', '--config=sync-db-test.yml'], { cwd }); + + // Check the output. + expect(stdout1).to.match(/Created.+\d{13}_settings\.ts/); + + // Check files are created. + const files1 = await glob(migrationPath1); + + expect(files1.length).to.equal(1); + }); }); diff --git a/test/unit/util/config.test.ts b/test/unit/util/config.test.ts index ae23462c..13fb9025 100644 --- a/test/unit/util/config.test.ts +++ b/test/unit/util/config.test.ts @@ -1,8 +1,12 @@ +import * as path from 'path'; import { expect } from 'chai'; +import * as yaml from 'yamljs'; import { it, describe } from 'mocha'; +import { mkdtemp, write } from '../../../src/util/fs'; +import Configuration from '../../../src/domain/Configuration'; import ConnectionConfig from '../../../src/domain/ConnectionConfig'; -import { validate, getConnectionId, resolveConnectionsFromEnv, isCLI } from '../../../src/config'; +import { validate, getConnectionId, resolveConnectionsFromEnv, isCLI, loadConfig } from '../../../src/config'; describe('CONFIG:', () => { describe('isCLI', () => { @@ -109,4 +113,105 @@ describe('CONFIG:', () => { expect(getConnectionId({ connection: 'someconnectionstring' } as ConnectionConfig)).to.equal(''); }); }); + + describe('loadConfig', () => { + it('should load the config file that is provided.', async () => { + const cwd = await mkdtemp(); + await write( + path.join(cwd, 'sync-db.yml'), + yaml.stringify({ + migration: { + directory: 'migration', + sourceType: 'javascript' + } + } as Configuration) + ); + + await write( + path.join(cwd, 'sync-db-test.yml'), + yaml.stringify({ + migration: { + directory: 'migration', + sourceType: 'typescript' + } + } as Configuration) + ); + process.chdir(cwd); + const config = await loadConfig(); + expect(config.migration.sourceType).to.equal('javascript'); + + process.chdir(cwd); + const config1 = await loadConfig('sync-db-test.yml'); + expect(config1.migration.sourceType).to.equal('typescript'); + }); + + it('should load the config file only if it matches the naming convention.', async () => { + const cwd = await mkdtemp(); + + await write( + path.join(cwd, 'sync-db-test.yml'), + yaml.stringify({ + migration: { + directory: 'migration', + sourceType: 'typescript' + } + } as Configuration) + ); + + process.chdir(cwd); + + await expect(loadConfig('sync-db-test.yml')).not.to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + + const config = await loadConfig('sync-db-test.yml'); + expect(config).to.have.property('migration'); + }); + + it(`should throw an error if the config file doesn't match the naming convention.`, async () => { + await expect(loadConfig('sync-db.txt')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + await expect(loadConfig('sync-db-test.js')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + await expect(loadConfig('sync.yml')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + await expect(loadConfig('sync-db.yml.txt')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + await expect(loadConfig('sync-db-.yml')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + await expect(loadConfig('sync-dbasdfghjkl.yml')).to.be.rejectedWith( + Error, + `The config filename doesn't match the pattern sync-db.yml or sync-db-*.yml` + ); + }); + + it(`should load config file with given absolute path.`, async () => { + const cwd = await mkdtemp(); + + await write( + path.join(cwd, 'sync-db-test.yml'), + yaml.stringify({ + migration: { + directory: 'migration', + sourceType: 'typescript' + } + } as Configuration) + ); + + process.chdir(cwd); + const config = await loadConfig(path.join(cwd, 'sync-db-test.yml')); + expect(config).to.have.property('migration'); + }); + }); });