Skip to content

feat: add check-structure command #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,28 @@ npm install knex-scripts
## Command Line Usage

```
Usage: knex-scripts [options] [command]
Usage: cli [options] [command]


Options:

-V, --version output the version number
--docker Use docker.
--knexfile [path] Specify the knexfile path.
--cwd [path] Specify the working directory.
--env [name] environment, default: process.env.NODE_ENV || development
-h, --help output usage information
-V, --version output the version number
--docker Use docker.
--docker-service [service] Docker service name, default: "postgres".
--knexfile [path] Specify the knexfile path.
--cwd [path] Specify the working directory.
--env [name] environment, default: process.env.NODE_ENV || development
-h, --help output usage information


Commands:

create Create database.
drop Drop database.
dump Dump database.
load Load database.
truncate Truncate all tables.
create Create database.
drop Drop database.
dump Dump database.
load Load database.
check-structure Check structure.
truncate Truncate all tables.
```

## Node API Usage
Expand Down
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