Skip to content

Commit 2a1f300

Browse files
authored
Merge pull request #3 from smooth-code/add-check-structure-command
feat: add check-structure command
2 parents b62c39e + 680bf51 commit 2a1f300

14 files changed

+1282
-71
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
lib/
33
db/
4+
!test/__fixtures__/structure.sql

README.md

+14-12
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,28 @@ npm install knex-scripts
1111
## Command Line Usage
1212

1313
```
14-
Usage: knex-scripts [options] [command]
14+
Usage: cli [options] [command]
1515
1616
1717
Options:
1818
19-
-V, --version output the version number
20-
--docker Use docker.
21-
--knexfile [path] Specify the knexfile path.
22-
--cwd [path] Specify the working directory.
23-
--env [name] environment, default: process.env.NODE_ENV || development
24-
-h, --help output usage information
19+
-V, --version output the version number
20+
--docker Use docker.
21+
--docker-service [service] Docker service name, default: "postgres".
22+
--knexfile [path] Specify the knexfile path.
23+
--cwd [path] Specify the working directory.
24+
--env [name] environment, default: process.env.NODE_ENV || development
25+
-h, --help output usage information
2526
2627
2728
Commands:
2829
29-
create Create database.
30-
drop Drop database.
31-
dump Dump database.
32-
load Load database.
33-
truncate Truncate all tables.
30+
create Create database.
31+
drop Drop database.
32+
dump Dump database.
33+
load Load database.
34+
check-structure Check structure.
35+
truncate Truncate all tables.
3436
```
3537

3638
## Node API Usage

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"build": "babel -d lib src",
1010
"lint": "eslint .",
1111
"prebuild": "rm -rf lib",
12-
"test": "yarn lint && yarn build && bin/knex-scripts create && bin/knex-scripts dump && bin/knex-scripts load && bin/knex-scripts drop",
13-
"release": "yarn build && standard-version && conventional-github-releaser -p angular"
12+
"test":
13+
"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",
14+
"release":
15+
"yarn build && standard-version && conventional-github-releaser -p angular"
1416
},
1517
"bin": {
1618
"knex-scripts": "bin/knex-scripts"
@@ -29,6 +31,7 @@
2931
"devDependencies": {
3032
"babel-cli": "^6.26.0",
3133
"babel-eslint": "^8.2.1",
34+
"babel-jest": "^22.4.1",
3235
"babel-plugin-transform-class-properties": "^6.24.1",
3336
"babel-plugin-transform-object-rest-spread": "^6.26.0",
3437
"babel-preset-env": "^1.6.1",
@@ -37,6 +40,7 @@
3740
"eslint-config-airbnb-base": "^12.1.0",
3841
"eslint-config-prettier": "^2.9.0",
3942
"eslint-plugin-import": "^2.8.0",
43+
"jest": "^22.4.2",
4044
"knex": "^0.14.2",
4145
"pg": "^7.4.1",
4246
"prettier": "^1.10.2",

src/checkStructure.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
requireEnv,
3+
getInsertsFromMigrations,
4+
getInsertsFromStructure,
5+
} from './utils'
6+
7+
async function checkStructure(options) {
8+
const { structurePath, migrationsPath } = options
9+
requireEnv('development', options.env)
10+
11+
const migrationsInFolder = await getInsertsFromMigrations(migrationsPath)
12+
const migrationsInStructure = await getInsertsFromStructure(structurePath)
13+
14+
return migrationsInFolder.every(
15+
(insert, index) => migrationsInStructure[index] === insert,
16+
)
17+
}
18+
19+
export default checkStructure

src/cli.js

+27-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import drop from './drop'
1313
import dump from './dump'
1414
import load from './load'
1515
import truncate from './truncate'
16+
import checkStructure from './checkStructure'
1617

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

@@ -65,7 +66,7 @@ function initConfig(env) {
6566

6667
if (environment) {
6768
console.log('Using environment:', chalk.magenta(environment))
68-
config = config[environment] || config
69+
config = { ...config, ...config[environment] }
6970
}
7071

7172
if (!config) {
@@ -80,7 +81,10 @@ function initConfig(env) {
8081
knexConfig: config,
8182
env: environment,
8283
structurePath: knexScriptsConfig.structurePath || 'db/structure.sql',
83-
migrationsPath: join(process.cwd(), 'migrations'),
84+
migrationsPath:
85+
config.migrations && config.migrations.directory
86+
? config.migrations.directory
87+
: join(process.cwd(), 'migrations'),
8488
docker:
8589
(commander.docker !== undefined
8690
? commander.docker
@@ -116,7 +120,7 @@ function invoke(env) {
116120
.action(() => {
117121
const config = initConfig(env)
118122
return create(config)
119-
.then(() => console.log(chalk.green(`Database created.`)))
123+
.then(() => console.log(chalk.green('Database created.')))
120124
.catch(exit)
121125
})
122126

@@ -126,7 +130,7 @@ function invoke(env) {
126130
.action(() => {
127131
const config = initConfig(env)
128132
return drop(config)
129-
.then(() => console.log(chalk.green(`Database dropped.`)))
133+
.then(() => console.log(chalk.green('Database dropped.')))
130134
.catch(exit)
131135
})
132136

@@ -136,7 +140,7 @@ function invoke(env) {
136140
.action(() => {
137141
const config = initConfig(env)
138142
return dump(config)
139-
.then(() => console.log(chalk.green(`Dump created.`)))
143+
.then(() => console.log(chalk.green('Dump created.')))
140144
.catch(exit)
141145
})
142146

@@ -146,7 +150,24 @@ function invoke(env) {
146150
.action(() => {
147151
const config = initConfig(env)
148152
return load(config)
149-
.then(() => console.log(chalk.green(`Database loaded.`)))
153+
.then(() => console.log(chalk.green('Database loaded.')))
154+
.catch(exit)
155+
})
156+
157+
commander
158+
.command('check-structure')
159+
.description('Check structure.')
160+
.action(() => {
161+
const config = initConfig(env)
162+
return checkStructure(config)
163+
.then(upToDate => {
164+
if (upToDate) {
165+
console.log(chalk.green('Structure is up to date.'))
166+
} else {
167+
console.log(chalk.red('Structure is not up to date.'))
168+
process.exit(1)
169+
}
170+
})
150171
.catch(exit)
151172
})
152173

src/dump.js

+7-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { exec } from 'mz/child_process'
2-
import { appendFile, readdir, exists } from 'mz/fs'
2+
import { appendFile } from 'mz/fs'
33
import { dirname } from 'path'
44
import mkdirp from 'mkdirp'
55
import {
66
requireEnv,
77
wrapDockerCommand,
88
getCommand,
99
getCommandEnv,
10+
getInsertsFromMigrations,
1011
} from './utils'
1112

12-
async function getMigrationInserts({ migrationsPath }) {
13-
if (!await exists(migrationsPath)) return ''
14-
const migrations = await readdir(migrationsPath)
15-
return migrations
16-
.map(
17-
migration =>
18-
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('${migration}', 1, NOW());\n`,
19-
)
20-
.join('')
21-
}
22-
2313
async function dump(options) {
2414
const { structurePath, migrationsPath } = options
2515
requireEnv('development', options.env)
@@ -34,8 +24,11 @@ async function dump(options) {
3424

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

37-
const migrationInserts = await getMigrationInserts({ migrationsPath })
38-
return appendFile(structurePath, `-- Knex migrations\n\n${migrationInserts}`)
27+
const migrationInserts = await getInsertsFromMigrations(migrationsPath)
28+
return appendFile(
29+
structurePath,
30+
`-- Knex migrations\n\n${migrationInserts.join('\n')}`,
31+
)
3932
}
4033

4134
export default dump

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { default as drop } from './drop'
33
export { default as dump } from './dump'
44
export { default as load } from './load'
55
export { default as truncate } from './truncate'
6+
export { default as checkStructure } from './checkStructure'

src/utils.js

+28
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
import { readFile, readdir, exists } from 'mz/fs'
2+
3+
export async function getInsertsFromMigrations(migrationsPath) {
4+
if (!await exists(migrationsPath)) return []
5+
const migrations = await readdir(migrationsPath)
6+
return migrations.map(
7+
migration =>
8+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('${migration}', 1, NOW());`,
9+
)
10+
}
11+
12+
export async function getInsertsFromStructure(structurePath) {
13+
if (!await exists(structurePath)) return []
14+
const structure = await readFile(structurePath, 'utf-8')
15+
const regExp = /INSERT INTO knex_migrations\(name, batch, migration_time\) VALUES \('.*', 1, NOW\(\)\);/g
16+
17+
const inserts = []
18+
19+
let match
20+
/* eslint-disable no-cond-assign */
21+
while ((match = regExp.exec(structure))) {
22+
inserts.push(match[0])
23+
}
24+
/* eslint-enable no-cond-assign */
25+
26+
return inserts
27+
}
28+
129
export function preventEnv(preventedEnv, env) {
230
if (env === preventedEnv) {
331
throw new Error(`Not in ${preventedEnv} please!`)

test/.eslintrc.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
env: {
3+
jest: true,
4+
},
5+
}

test/__fixtures__/migrations/20180207153033_migration_1.js

Whitespace-only changes.

test/__fixtures__/migrations/20180207153040_migration_2.js

Whitespace-only changes.

test/__fixtures__/structure.sql

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--
2+
-- PostgreSQL database dump
3+
--
4+
5+
-- Dumped from database version 10.2
6+
-- Dumped by pg_dump version 10.2
7+
8+
SET statement_timeout = 0;
9+
SET lock_timeout = 0;
10+
SET idle_in_transaction_session_timeout = 0;
11+
SET client_encoding = 'UTF8';
12+
SET standard_conforming_strings = on;
13+
SET check_function_bodies = false;
14+
SET client_min_messages = warning;
15+
SET row_security = off;
16+
17+
--
18+
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
19+
--
20+
21+
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
22+
23+
--
24+
-- PostgreSQL database dump complete
25+
--
26+
27+
-- Knex migrations
28+
29+
INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());
30+
INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());
31+
INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153050_migration_3.js', 1, NOW());

test/utils.test.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import path from 'path'
2+
import { getInsertsFromMigrations, getInsertsFromStructure } from '../src/utils'
3+
4+
describe('#getInsertsFromMigrations', () => {
5+
it('without an existing directory, it should return an empty array', async () => {
6+
const inserts = await getInsertsFromMigrations(
7+
'/something-that-does-not-exist',
8+
)
9+
expect(inserts).toEqual([])
10+
})
11+
12+
it('should read migrations from folder', async () => {
13+
const inserts = await getInsertsFromMigrations(
14+
path.join(__dirname, '__fixtures__/migrations'),
15+
)
16+
expect(inserts).toEqual([
17+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());`,
18+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());`,
19+
])
20+
})
21+
})
22+
23+
describe('#getInsertsFromStructure', () => {
24+
it('without an existing file, it should return an empty array', async () => {
25+
const inserts = await getInsertsFromStructure(
26+
'/something-that-does-not-exist',
27+
)
28+
expect(inserts).toEqual([])
29+
})
30+
31+
it('should read migrations from structure', async () => {
32+
const inserts = await getInsertsFromStructure(
33+
path.join(__dirname, '__fixtures__/structure.sql'),
34+
)
35+
expect(inserts).toEqual([
36+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153033_migration_1.js', 1, NOW());`,
37+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153040_migration_2.js', 1, NOW());`,
38+
`INSERT INTO knex_migrations(name, batch, migration_time) VALUES ('20180207153050_migration_3.js', 1, NOW());`,
39+
])
40+
})
41+
})

0 commit comments

Comments
 (0)