Skip to content

Commit

Permalink
feat: add check-structure command
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge committed Feb 28, 2018
1 parent b62c39e commit cc2c27b
Show file tree
Hide file tree
Showing 13 changed files with 1,268 additions and 59 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
lib/
db/
!test/__fixtures__/structure.sql
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"build": "babel -d lib src",
"lint": "eslint .",
"prebuild": "rm -rf lib",
"test": "yarn lint && yarn build && bin/knex-scripts create && bin/knex-scripts dump && bin/knex-scripts load && bin/knex-scripts drop",
"release": "yarn build && standard-version && conventional-github-releaser -p angular"
"test":
"yarn lint && yarn build && jest --ci && bin/knex-scripts create && bin/knex-scripts dump && bin/knex-scripts load && bin/knex-scripts drop && bin/knex-scripts check-structure",
"release":
"yarn build && standard-version && conventional-github-releaser -p angular"
},
"bin": {
"knex-scripts": "bin/knex-scripts"
Expand All @@ -29,6 +31,7 @@
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-jest": "^22.4.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
Expand All @@ -37,6 +40,7 @@
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0",
"jest": "^22.4.2",
"knex": "^0.14.2",
"pg": "^7.4.1",
"prettier": "^1.10.2",
Expand Down
19 changes: 19 additions & 0 deletions src/checkStructure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
requireEnv,
getInsertsFromMigrations,
getInsertsFromStructure,
} from './utils'

async function checkStructure(options) {
const { structurePath, migrationsPath } = options
requireEnv('development', options.env)

const migrationsInFolder = await getInsertsFromMigrations(migrationsPath)
const migrationsInStructure = await getInsertsFromStructure(structurePath)

return migrationsInFolder.every(
(insert, index) => migrationsInStructure[index] === insert,
)
}

export default checkStructure
33 changes: 27 additions & 6 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import drop from './drop'
import dump from './dump'
import load from './load'
import truncate from './truncate'
import checkStructure from './checkStructure'

const argv = minimist(process.argv.slice(2))

Expand Down Expand Up @@ -65,7 +66,7 @@ function initConfig(env) {

if (environment) {
console.log('Using environment:', chalk.magenta(environment))
config = config[environment] || config
config = { ...config, ...config[environment] }
}

if (!config) {
Expand All @@ -80,7 +81,10 @@ function initConfig(env) {
knexConfig: config,
env: environment,
structurePath: knexScriptsConfig.structurePath || 'db/structure.sql',
migrationsPath: join(process.cwd(), 'migrations'),
migrationsPath:
config.migrations && config.migrations.directory
? config.migrations.directory
: join(process.cwd(), 'migrations'),
docker:
(commander.docker !== undefined
? commander.docker
Expand Down Expand Up @@ -116,7 +120,7 @@ function invoke(env) {
.action(() => {
const config = initConfig(env)
return create(config)
.then(() => console.log(chalk.green(`Database created.`)))
.then(() => console.log(chalk.green('Database created.')))
.catch(exit)
})

Expand All @@ -126,7 +130,7 @@ function invoke(env) {
.action(() => {
const config = initConfig(env)
return drop(config)
.then(() => console.log(chalk.green(`Database dropped.`)))
.then(() => console.log(chalk.green('Database dropped.')))
.catch(exit)
})

Expand All @@ -136,7 +140,7 @@ function invoke(env) {
.action(() => {
const config = initConfig(env)
return dump(config)
.then(() => console.log(chalk.green(`Dump created.`)))
.then(() => console.log(chalk.green('Dump created.')))
.catch(exit)
})

Expand All @@ -146,7 +150,24 @@ function invoke(env) {
.action(() => {
const config = initConfig(env)
return load(config)
.then(() => console.log(chalk.green(`Database loaded.`)))
.then(() => console.log(chalk.green('Database loaded.')))
.catch(exit)
})

commander
.command('check-structure')
.description('Check structure.')
.action(() => {
const config = initConfig(env)
return checkStructure(config)
.then(upToDate => {
if (upToDate) {
console.log(chalk.green('Structure is up to date.'))
} else {
console.log(chalk.red('Structure is not up to date.'))
process.exit(1)
}
})
.catch(exit)
})

Expand Down
21 changes: 7 additions & 14 deletions src/dump.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import { exec } from 'mz/child_process'
import { appendFile, readdir, exists } from 'mz/fs'
import { appendFile } from 'mz/fs'
import { dirname } from 'path'
import mkdirp from 'mkdirp'
import {
requireEnv,
wrapDockerCommand,
getCommand,
getCommandEnv,
getInsertsFromMigrations,
} from './utils'

async function getMigrationInserts({ migrationsPath }) {
if (!await exists(migrationsPath)) return ''
const migrations = await readdir(migrationsPath)
return migrations
.map(
migration =>
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('${migration}', 1, NOW());\n`,
)
.join('')
}

async function dump(options) {
const { structurePath, migrationsPath } = options
requireEnv('development', options.env)
Expand All @@ -34,8 +24,11 @@ async function dump(options) {

await exec(wrapDockerCommand(options, command), { env })

const migrationInserts = await getMigrationInserts({ migrationsPath })
return appendFile(structurePath, `-- Knex migrations\n\n${migrationInserts}`)
const migrationInserts = await getInsertsFromMigrations(migrationsPath)
return appendFile(
structurePath,
`-- Knex migrations\n\n${migrationInserts.join('\n')}`,
)
}

export default dump
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as drop } from './drop'
export { default as dump } from './dump'
export { default as load } from './load'
export { default as truncate } from './truncate'
export { default as checkStructure } from './checkStructure'
28 changes: 28 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
import { readFile, readdir, exists } from 'mz/fs'

export async function getInsertsFromMigrations(migrationsPath) {
if (!await exists(migrationsPath)) return []
const migrations = await readdir(migrationsPath)
return migrations.map(
migration =>
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('${migration}', 1, NOW());`,
)
}

export async function getInsertsFromStructure(structurePath) {
if (!await exists(structurePath)) return []
const structure = await readFile(structurePath, 'utf-8')
const regExp = /INSERT INTO knex_migrations\(name, batch, migration_time\) VALUES \('.*', 1, NOW\(\)\);/g

const inserts = []

let match
/* eslint-disable no-cond-assign */
while ((match = regExp.exec(structure))) {
inserts.push(match[0])
}
/* eslint-enable no-cond-assign */

return inserts
}

export function preventEnv(preventedEnv, env) {
if (env === preventedEnv) {
throw new Error(`Not in ${preventedEnv} please!`)
Expand Down
5 changes: 5 additions & 0 deletions test/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
env: {
jest: true,
},
}
Empty file.
Empty file.
31 changes: 31 additions & 0 deletions test/__fixtures__/structure.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--
-- PostgreSQL database dump
--

-- Dumped from database version 10.2
-- Dumped by pg_dump version 10.2

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;

--
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
--

CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;

--
-- PostgreSQL database dump complete
--

-- Knex migrations

INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());
INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());
INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153050_migration_3.js', 1, NOW());
41 changes: 41 additions & 0 deletions test/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import path from 'path'
import { getInsertsFromMigrations, getInsertsFromStructure } from '../src/utils'

describe('#getInsertsFromMigrations', () => {
it('without an existing directory, it should return an empty array', async () => {
const inserts = await getInsertsFromMigrations(
'/something-that-does-not-exist',
)
expect(inserts).toEqual([])
})

it('should read migrations from folder', async () => {
const inserts = await getInsertsFromMigrations(
path.join(__dirname, '__fixtures__/migrations'),
)
expect(inserts).toEqual([
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());`,
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());`,
])
})
})

describe('#getInsertsFromStructure', () => {
it('without an existing file, it should return an empty array', async () => {
const inserts = await getInsertsFromStructure(
'/something-that-does-not-exist',
)
expect(inserts).toEqual([])
})

it('should read migrations from structure', async () => {
const inserts = await getInsertsFromStructure(
path.join(__dirname, '__fixtures__/structure.sql'),
)
expect(inserts).toEqual([
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());`,
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());`,
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153050_migration_3.js', 1, NOW());`,
])
})
})
Loading

0 comments on commit cc2c27b

Please sign in to comment.