Skip to content

Commit 08df6c3

Browse files
committed
root commit
0 parents  commit 08df6c3

15 files changed

+695
-0
lines changed

.eslintrc.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
env:
2+
es6: true
3+
node: true
4+
extends: 'eslint:recommended'
5+
rules:
6+
indent:
7+
- error
8+
- 4
9+
linebreak-style:
10+
- error
11+
- unix
12+
quotes:
13+
- error
14+
- double
15+
semi:
16+
- error
17+
- always

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# dependencies
2+
node_modules
3+
package-lock.json

.npmignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
###
2+
### FROM .gitignore
3+
###
4+
5+
# dependencies
6+
node_modules
7+
package-lock.json
8+
9+
###
10+
.eslintrc.yml
11+
.gitignore
12+
test

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
This project adheres to [Semantic Versioning](http://semver.org/).
5+
6+
7+
## Unreleased
8+
9+
10+
11+
12+
## 0.0.0 - 2017-12-02
13+
14+
**Out in the Wild**

LICENSE.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Copyright (c) 2017 Forfuture, LLC <[email protected]>

README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# migrate
2+
3+
> Database-agnostic migration framework
4+
5+
* [Usage](#usage)
6+
* [Installation](#installation)
7+
* [License](#license)
8+
9+
10+
<a name="usage"></a>
11+
## usage
12+
13+
```bash
14+
# Intro: We have entered into a project that does NOT
15+
# manage database migrations, using this tool.
16+
17+
# Run migrations.
18+
# Assume we are in version 1.0.0 already (using the --current option).
19+
$ npx migrate --current 1.0.0
20+
21+
# ... new migrations added ...
22+
23+
# Run migrations.
24+
# No need to specify which version we are in! The tool
25+
# keeps history.
26+
$ npx migrate
27+
28+
# Migrate to a specific version (e.g. 1.4.0).
29+
$ npx migrate 1.4.0
30+
31+
# Undo last migration.
32+
$ npx migrate --undo
33+
34+
# List available migrations/versions.
35+
$ npx migrate --list
36+
37+
# Show current data version.
38+
$ npx migrate --which
39+
40+
# Show a brief history of migrations.
41+
$ npx migrate --history
42+
43+
# Show help information.
44+
$ npx migrate --help
45+
```
46+
47+
48+
<a name="installation"></a>
49+
## installation
50+
51+
```bash
52+
$ npm install gitlab:forfuture/migrate#semver:0.0.0
53+
```
54+
55+
56+
<a name="license"></a>
57+
## license
58+
59+
**The MIT License (MIT)**
60+
61+
Copyright (c) 2017 Forfuture LLC <[email protected]>

bin/migrate

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
require("../lib/cli").runAndExit();

integrations/sequelize.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* The MIT License (MIT)
3+
* Copyright (c) 2017 Forfuture, LLC <[email protected]>
4+
*/
5+
6+
7+
// installed modules
8+
const async = require("asyncawait/async");
9+
const await = require("asyncawait/await");
10+
const Sequelize = require("sequelize");
11+
12+
13+
const MigrationModel = {
14+
id: {
15+
field: "id",
16+
type: Sequelize.INTEGER,
17+
primaryKey: true,
18+
autoIncrement: true,
19+
},
20+
version: {
21+
field: "version",
22+
type: Sequelize.TEXT,
23+
},
24+
migratedAt: {
25+
field: "migrated_at",
26+
type: Sequelize.DATE,
27+
},
28+
};
29+
30+
31+
function toMigration(migration) {
32+
return {
33+
version: migration.version,
34+
migratedAt: migration.migratedAt,
35+
};
36+
}
37+
38+
39+
class SequelizeIntegration {
40+
constructor(sequelize, options={}) {
41+
this.sequelize = sequelize;
42+
this.options = Object.assign({
43+
tableName: "db_migrations",
44+
}, options);
45+
46+
this._Migration = this.sequelize.define("DatabaseMigration", MigrationModel, {
47+
tableName: this.options.tableName,
48+
});
49+
this._transaction = null;
50+
this._empty = false;
51+
52+
this.integrate = async (this.integrate);
53+
this.close = async (this.close);
54+
}
55+
56+
integrate() {
57+
// TODO: Document that all models should & will be synced!
58+
await (this.sequelize.sync());
59+
this._transaction = await (this.sequelize.transaction());
60+
61+
const migrations = await (this._Migration.findAll({
62+
order: [[MigrationModel.migratedAt.field, "DESC"]],
63+
limit: 2,
64+
}, {
65+
transaction: this._transaction,
66+
}));
67+
this._empty = !migrations.length;
68+
69+
const versions = {
70+
current: toMigration(migrations[0]),
71+
previous: toMigration(migrations[1]),
72+
};
73+
const context = {
74+
sequelize: this.sequelize,
75+
transaction: this._transaction,
76+
};
77+
78+
return [versions, context, this.close.bind(this)];
79+
}
80+
81+
close(error, output) {
82+
if (error || !output) {
83+
await (this._transaction.rollback());
84+
return;
85+
}
86+
87+
const migrations = [];
88+
if (this._empty) {
89+
migrations.push({
90+
version: output.previousVersion,
91+
migratedAt: new Date(0),
92+
});
93+
}
94+
migrations.push({
95+
version: output.currentVersion,
96+
migratedAt: new Date(),
97+
});
98+
99+
await (this._Migration.bulkCreate(migrations, {
100+
transaction: this._transaction,
101+
}));
102+
await (this._transaction.commit());
103+
}
104+
}
105+
106+
107+
exports = module.exports = SequelizeIntegration;

lib/cli.js

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* The MIT License (MIT)
3+
* Copyright (c) 2017 Forfuture, LLC <[email protected]>
4+
*/
5+
/* eslint-disable no-console */
6+
7+
8+
// built-in modules
9+
const async = require("asyncawait/async");
10+
const await = require("asyncawait/await");
11+
const path = require("path");
12+
13+
14+
// installed modules
15+
const Debug = require("debug");
16+
const program = require("commander");
17+
18+
19+
// own modules
20+
const migrations = require("./migrations");
21+
const pkg = require("../package");
22+
const utils = require("./utils");
23+
24+
25+
// module variables
26+
const debug = Debug("migrate:cli");
27+
28+
29+
program
30+
.version(pkg.version)
31+
.usage("[options] [version]")
32+
.option("-i, --init-path <path>", "path to initialization file", "script/migrate")
33+
.option("-m, --migrations-path <path>", "path to migrations directory", "script/migrations")
34+
.option("-c, --current <version>", "use as current version")
35+
.option("-u, --undo", "undo last migration")
36+
.option("-l, --list", "list available data versions")
37+
.option("-w, --which", "show current data version")
38+
.option("-t, --history", "show migration history")
39+
; // eslint-disable-line indent
40+
41+
42+
const run = async (function run() {
43+
let projectHandle;
44+
let output;
45+
let error;
46+
47+
try { await (async (function() {
48+
program.parse(process.argv);
49+
50+
const paths = {};
51+
["initPath", "migrationsPath"].forEach(function(key) {
52+
paths[key] = path.resolve(program[key]);
53+
});
54+
55+
const versions = await (utils.getVersions(paths.migrationsPath));
56+
if (program.list) {
57+
console.log(versions);
58+
return;
59+
}
60+
61+
projectHandle = await (utils.openProject(paths.initPath));
62+
63+
const { dbVersions, context } = projectHandle;
64+
const currentVersion = (dbVersions.current && dbVersions.current.version) || program.current;
65+
if (!currentVersion) {
66+
console.error("error: current version not found");
67+
return;
68+
}
69+
if (program.which) {
70+
console.log(currentVersion);
71+
return;
72+
}
73+
debug("current version: %s", currentVersion);
74+
if (program.history) {
75+
console.log(dbVersions);
76+
return;
77+
}
78+
debug("versions in db: %j", dbVersions);
79+
80+
let targetVersion = program.args.shift();
81+
if (targetVersion && versions.indexOf(targetVersion) === -1) {
82+
throw "target version not found";
83+
}
84+
if (!targetVersion && program.undo) {
85+
if (!dbVersions.previous) {
86+
return;
87+
}
88+
targetVersion = dbVersions.previous.version;
89+
}
90+
targetVersion = targetVersion || migrations.getLatestVersion(versions);
91+
if (!targetVersion) {
92+
throw "target version not found";
93+
}
94+
debug("target version: %s", targetVersion);
95+
96+
if (currentVersion === targetVersion) {
97+
debug("currentVersion(%s) -eq targetVersion(%s)", currentVersion, targetVersion);
98+
return;
99+
}
100+
101+
const targetMigrations = migrations.findVersions(versions, currentVersion, targetVersion).map(function(version) {
102+
return {
103+
version,
104+
module: require(path.join(paths.migrationsPath, version)),
105+
};
106+
});
107+
108+
if (!targetMigrations.length) {
109+
debug("no migrations to run");
110+
return;
111+
}
112+
113+
const up = migrations.goingUp(currentVersion, targetVersion);
114+
await (migrations.migrate(up, targetMigrations, context));
115+
116+
output = {
117+
currentVersion: targetVersion,
118+
previousVersion: currentVersion,
119+
};
120+
debug("migration output: %j", output);
121+
})()); } catch (ex) {
122+
error = ex;
123+
}
124+
125+
// Clean up.
126+
if (projectHandle) {
127+
await (utils.closeProject(projectHandle, error, output));
128+
}
129+
130+
// Error handling.
131+
// TODO:
132+
// Improve error logging!
133+
if (error) {
134+
console.error("error:", error.message || error);
135+
if (error.stack) {
136+
const padding = " ";
137+
console.error(padding + error.stack.split("\n").slice(1).map((l) => l.trim()).join(`\n${padding}`));
138+
}
139+
throw error;
140+
}
141+
142+
return output;
143+
});
144+
145+
146+
const runAndExit = async (function runAndExit() {
147+
try {
148+
await (run());
149+
} catch (ex) {
150+
process.exit(1);
151+
}
152+
process.exit();
153+
});
154+
155+
156+
if (require.main === module) {
157+
runAndExit();
158+
}
159+
160+
161+
exports = module.exports = {
162+
/**
163+
* Run the CLI.
164+
* @kind function
165+
* @return {Promise}
166+
*/
167+
run,
168+
/**
169+
* Run the CLI and exit immediately.
170+
* *Automatically invoked if this file is run as script.*
171+
* @kind function
172+
*/
173+
runAndExit,
174+
};

0 commit comments

Comments
 (0)