Skip to content

Commit

Permalink
feat: how do we create and run migrations (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
noxecane authored Jun 9, 2020
1 parent 759549d commit c958ccc
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 6 deletions.
6 changes: 5 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ workflows:
ignore:
- master
- release
- /release-.*/
- test:
context: library
requires:
Expand All @@ -94,8 +95,11 @@ workflows:
ignore:
- master
- release
- /release-.*/
- deploy:
context: library
filters:
branches:
only: release
only:
- release
- /release-.*/
41 changes: 41 additions & 0 deletions bin/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#! /usr/bin/env node

const program = require("commander");
const migrate = require("../dist/migration");
const pkgjson = require("../package.json");

program.version(pkgjson.version);

program
.command("init")
.description("initialize a new migration project")
.action(() =>
migrate
.init()
.then(() => console.log(`Initialization successful. Check out \`${migrate.DEFAULT_CONFIG_FILE_NAME}\` file`))
.catch(err => {
console.error(`ERROR: ${err.message}`);
process.exit(1);
})
);

program
.command("create [description]")
.description("create a new database migration with the provided description")
.option("-f --file <file>", "use a custom config file")
.action((description, options) => {
global.options = options;
migrate
.create(description)
.then(destination => console.log(`Created: ${destination}`))
.catch(err => {
console.error(`ERROR: ${err.message}`);
process.exit(1);
});
});

program.parse(process.argv);

if (!program.rawArgs || program.rawArgs.length === 0) {
program.outputHelp();
}
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "@random-guys/bucket",
"version": "0.8.1",
"version": "0.9.0-rc.4",
"description": "Wrapper around axios for interservice communication",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"bucket-migrate": "bin/migrate.js"
},
"scripts": {
"build": "tsc -p ./tsconfig.json",
"watch": "tsc -w -p ./tsconfig.json",
Expand All @@ -20,6 +23,7 @@
"homepage": "https://github.com/random-guys/bucket#readme",
"devDependencies": {
"@types/jest": "^25.2.3",
"@types/migrate-mongo": "^7.0.1",
"@types/mongoose": "^5.5.18",
"@types/supertest": "^2.0.9",
"@types/uuid": "^3.4.4",
Expand All @@ -29,10 +33,14 @@
"typescript": "^3.5.2"
},
"files": [
"dist/"
"dist/",
"samples/"
],
"dependencies": {
"commander": "^5.1.0",
"date-fns": "^2.14.0",
"http-status-codes": "^1.3.2",
"migrate-mongo": "^7.2.1",
"mongoose": "^5.7.1",
"uuid": "^3.3.2"
}
Expand Down
15 changes: 15 additions & 0 deletions samples/migrate-mongo-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// In this file you can configure migrate-mongo

const config = {
// The migrations dir, can be an relative or absolute path. Only edit this when really necessary.
migrationsDir: "dist/migrations",

// The mongodb collection where the applied changes are stored. Only edit this when really necessary.
changelogCollectionName: "changelog",

// The file extension to create migrations and search for in migration dir
migrationFileExtension: ".js"
};

// Return the config as a promise
module.exports = config;
13 changes: 13 additions & 0 deletions samples/sample-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Db, MongoClient } from "mongodb";

export async function up(db: Db, conn: MongoClient) {
// TODO write your migration here.
// Example:
// await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: true}});
}

export async function down(db: Db, conn: MongoClient) {
// TODO write the statements to rollback your migration (if possible)
// Example:
// await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: false}});
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from "./base.repo";
export * from "./base.schema";
export * from "./connect";
export * from "./errors";
export * from "./migration";
export * from "./query";
export * from "./utils.schema";
68 changes: 68 additions & 0 deletions src/migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import fs from "fs";
import { down, up, config } from "migrate-mongo";
import { Connection } from "mongoose";
import path from "path";
import format from "date-fns/format";

const PREFIX_FORMAT = "yyyyMMddHHmmss";
export const DEFAULT_MIGRATION_DIR = "src/migrations";
export const DEFAULT_CONFIG_FILE_NAME = "migrate-mongo-config.js";

/**
* Applies all unapplied migrations to the DB. Note that it passes the mongoose
* connection in place of `MongoClient` due to being unable to access
* that from mongoose.
* @param connection mongoose connection to be used for migration
*/
export function migrateUp(connection: Connection) {
// @ts-ignore wrong typing
return up(connection.db, connection.client);
}

/**
* Undo the last applied migration. Note that it passes the mongoose
* connection in place of `MongoClient` due to being unable to access
* that from mongoose.
* @param connection mongoose connection to be used for migration
*/
export function migrateDown(connection: Connection) {
// @ts-ignore wrong typing
return down(connection.db, connection.client);
}

export async function create(description: string) {
if (!description) {
throw new Error("Missing parameter: description");
}

// where should we store the new migration
await config.shouldExist();
const migrationConfig = await config.read();
const configuredDir = migrationConfig.migrationsDir?.replace("dist", "src") ?? DEFAULT_MIGRATION_DIR;
const migrationsDir = path.isAbsolute(configuredDir) ? configuredDir : path.join(process.cwd(), configuredDir);

try {
await fs.promises.access(migrationsDir);
} catch (err) {
throw new Error(`migrations directory does not exist: ${migrationsDir}`);
}

// construct the file name
const source = path.join(__dirname, "../samples/sample-migration.ts");
const prefix = format(new Date(), PREFIX_FORMAT);
const filename = `${prefix}-${description.split(" ").join("_")}.ts`;

// copy sample file to new file
const destination = path.join(migrationsDir, filename);
await fs.promises.copyFile(source, destination);

return destination;
}

export async function init() {
const source = path.join(__dirname, `../samples/${DEFAULT_CONFIG_FILE_NAME}`);
const destination = path.join(process.cwd(), DEFAULT_CONFIG_FILE_NAME);

await fs.promises.copyFile(source, destination);
return fs.promises.mkdir(path.join(process.cwd(), "src/migrations"));
}
Loading

0 comments on commit c958ccc

Please sign in to comment.