From 7f8a177b017b28770629db00be183a320be43344 Mon Sep 17 00:00:00 2001 From: Apoorv Mishra Date: Wed, 31 May 2023 05:42:38 +0530 Subject: [PATCH] Use `umzug` to autorun migrations (#5281) --- package.json | 1 + server/database/sequelize.ts | 38 +++++++++++++++++++++++ server/utils/startup.ts | 47 +++------------------------- yarn.lock | 59 ++++++++++++++++++++++++++++++++++-- 4 files changed, 101 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 68f93725f..d07ed6d6e 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,7 @@ "tiny-cookie": "^2.4.1", "tmp": "^0.2.1", "turndown": "^7.1.2", + "umzug": "^3.2.1", "utf8": "^3.0.0", "utility-types": "^3.10.0", "uuid": "^8.3.2", diff --git a/server/database/sequelize.ts b/server/database/sequelize.ts index 636b57637..25dd4000a 100644 --- a/server/database/sequelize.ts +++ b/server/database/sequelize.ts @@ -1,4 +1,6 @@ +import path from "path"; import { Sequelize } from "sequelize-typescript"; +import { Umzug, SequelizeStorage, MigrationError } from "umzug"; import env from "@server/env"; import Logger from "../logging/Logger"; import * as models from "../models"; @@ -32,3 +34,39 @@ export const sequelize = new Sequelize(url, { idle: 10000, }, }); + +export const migrations = new Umzug({ + migrations: { + glob: ["migrations/*.js", { cwd: path.resolve("server") }], + resolve: ({ name, path, context }) => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const migration = require(path as string); + return { + name, + up: async () => migration.up(context, Sequelize), + down: async () => migration.down(context, Sequelize), + }; + }, + }, + context: sequelize.getQueryInterface(), + storage: new SequelizeStorage({ sequelize }), + logger: { + warn: (params) => Logger.warn("database", params), + error: (params: Record & MigrationError) => + Logger.error(params.message, params), + info: (params) => + Logger.info( + "database", + params.event === "migrating" + ? `Migrating ${params.name}…` + : `Migrated ${params.name} in ${params.durationSeconds}s` + ), + debug: (params) => + Logger.debug( + "database", + params.event === "migrating" + ? `Migrating ${params.name}…` + : `Migrated ${params.name} in ${params.durationSeconds}s` + ), + }, +}); diff --git a/server/utils/startup.ts b/server/utils/startup.ts index 444de475c..c8a609347 100644 --- a/server/utils/startup.ts +++ b/server/utils/startup.ts @@ -1,58 +1,21 @@ -import { execSync } from "child_process"; import chalk from "chalk"; import { isEmpty } from "lodash"; +import { migrations } from "@server/database/sequelize"; import env from "@server/env"; import Logger from "@server/logging/Logger"; import AuthenticationProvider from "@server/models/AuthenticationProvider"; import Team from "@server/models/Team"; -function getPendingMigrations() { - const commandResult = execSync( - `yarn sequelize db:migrate:status${ - env.PGSSLMODE === "disable" ? " --env=production-ssl-disabled" : "" - }` - ); - const commandResultArray = Buffer.from(commandResult) - .toString("utf-8") - .split("\n"); - - const pendingMigrations = commandResultArray.filter((line) => - line.startsWith("down") - ); - - return pendingMigrations; -} - -function runMigrations() { - Logger.info("database", "Running migrations..."); - const cmdResult = execSync( - `yarn db:migrate${ - env.PGSSLMODE === "disable" ? " --env=production-ssl-disabled" : "" - }` - ); - const cmdOutput = Buffer.from(cmdResult).toString("utf-8"); - Logger.info("database", cmdOutput); - Logger.info("database", "Done."); -} - -function logMigrations(migrations: string[]) { - Logger.warn("You have pending migrations"); - Logger.warn( - migrations - .map((line, i) => `${i + 1}. ${line.replace("down ", "")}`) - .join("\n") - ); -} - export async function checkPendingMigrations() { try { - const pending = getPendingMigrations(); + const pending = await migrations.pending(); if (!isEmpty(pending)) { if (env.isCloudHosted()) { - logMigrations(pending); + Logger.warn(chalk.red("Migrations are pending")); process.exit(1); } else { - runMigrations(); + Logger.info("database", "Running migrations…"); + await migrations.up(); } } await checkDataMigrations(); diff --git a/yarn.lock b/yarn.lock index c3faa194a..4ea40c7eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2471,6 +2471,16 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rushstack/ts-command-line@^4.12.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz#2dfdcf418d58256671433b1da4a3b67e1814cc7a" + integrity sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag== + dependencies: + "@types/argparse" "1.0.38" + argparse "~1.0.9" + colors "~1.2.1" + string-argv "~0.3.1" + "@sentry-internal/tracing@7.51.2": version "7.51.2" resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.51.2.tgz#17833047646426ca71445327018ffcb33506a699" @@ -2626,6 +2636,11 @@ dependencies: "@types/node" "*" +"@types/argparse@1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" + integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== + "@types/async-lock@^1.1.3": version "1.1.5" resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.5.tgz#a82f33e09aef451d6ded7bffae73f9d254723124" @@ -3767,7 +3782,7 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" -argparse@^1.0.7, argparse@~1.0.3: +argparse@^1.0.7, argparse@~1.0.3, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -4737,6 +4752,11 @@ colors@1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +colors@~1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== + colorspace@1.1.x: version "1.1.2" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" @@ -5837,6 +5857,11 @@ email-providers@^1.13.1: resolved "https://registry.yarnpkg.com/email-providers/-/email-providers-1.13.1.tgz#dfaea33a7744035510f0f64ed44098e7077f68c9" integrity sha512-+BPUngcWMy9piqS33yeOcqJXYhIxet94UbK1B/uDOGfjLav4YlDAf9/RhplRypSDBSKx92STNH0PcwgCJnNATw== +emittery@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.12.1.tgz#cb9a4a18745816f7a1fa03a8953e7eaededb45f2" + integrity sha512-pYyW59MIZo0HxPFf+Vb3+gacUu0gxVS3TZwB2ClwkEZywgF9f9OJDoVmNLojTn0vKX3tO9LC+pdQEcLP4Oz/bQ== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -6888,6 +6913,14 @@ fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-jetpack@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/fs-jetpack/-/fs-jetpack-4.3.1.tgz#cdfd4b64e6bfdec7c7dc55c76b39efaa7853bb20" + integrity sha512-dbeOK84F6BiQzk2yqqCVwCPWTxAvVGJ3fMQc6E2wuEohS28mR6yHngbrKuVCK1KHRx/ccByDylqu4H5PCP2urQ== + dependencies: + minimatch "^3.0.2" + rimraf "^2.6.3" + fs-merger@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/fs-merger/-/fs-merger-3.2.1.tgz#a225b11ae530426138294b8fbb19e82e3d4e0b3b" @@ -10392,6 +10425,11 @@ polished@^4.2.2: dependencies: "@babel/runtime" "^7.17.8" +pony-cause@^2.1.2: + version "2.1.9" + resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.9.tgz#39cd05418a94ee285d6f956f7df530449a497605" + integrity sha512-DIWdKGa0CSu5W1ooX1bcw4jQ+Fo++sgee0v1iczO7epT/suU/s2XBA8JDMl+8zkXZkjyfHfPaEngFwK5L3D9pg== + popmotion@9.3.6: version "9.3.6" resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1" @@ -12080,7 +12118,7 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= -string-argv@^0.3.1: +string-argv@^0.3.1, string-argv@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== @@ -12735,6 +12773,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^2.18.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-is@^1.6.16: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -12789,6 +12832,18 @@ umzug@^2.3.0: dependencies: bluebird "^3.7.2" +umzug@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/umzug/-/umzug-3.2.1.tgz#01c3a109efb037a10a317d4191be22810c37b195" + integrity sha512-XyWQowvP9CKZycKc/Zg9SYWrAWX/gJCE799AUTFqk8yC3tp44K1xWr3LoFF0MNEjClKOo1suCr5ASnoy+KltdA== + dependencies: + "@rushstack/ts-command-line" "^4.12.2" + emittery "^0.12.1" + fs-jetpack "^4.3.1" + glob "^8.0.3" + pony-cause "^2.1.2" + type-fest "^2.18.0" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"