From 2f5a19519edaff595e2d7877c6ec283176d2b87f Mon Sep 17 00:00:00 2001 From: Thomas Kugi Date: Tue, 12 Jan 2021 19:08:34 +0100 Subject: [PATCH 1/5] saving state to migration, so that every dev can start using this tool without setup fixed a bug with integers in mysql removed validate from attributes to save added supported JSONTYPE, DOUBLE added correct escape of postfix for blob added usage description --- README.md | 12 + package-lock.json | 2 +- package.json | 14 +- src/index.ts | 2 +- src/utils/getLastMigrationState.ts | 2 +- src/utils/getTablesFromModels.ts | 2 +- src/utils/reverseSequelizeColType.ts | 345 ++++++++++++++------------- src/utils/writeMigration.ts | 123 +++++++--- 8 files changed, 291 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index 427aed8..008f3d8 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,18 @@ module.exports = { then you can apply this `npx sequelize db:migrate --to 00000001-noname.js` +## Possible Usage Scenario +Make sure to have writeMigration in your System under development and that sequelize is all set up + +If you change a model and re-run the backend there should be a new file under `db/migrations`, but the database +won't update automatically. There are easy but important steps: +1) Rename the file's name as well as the content (Info: name), so that everyone knows what this migration is about +2) Migrate your database `sequelize db:migrate` +3) Re-Serve the backend. You Should see 'No changes found'. +4) Test the automatically created file's down function `sequelize db:migrate:undo` +5) If there are any troubles, fix the auto-generated file (ordering!) +6) Run `sequelize db:migrate:undo` and continue your amazing work + ## Documentation not ready yet. diff --git a/package-lock.json b/package-lock.json index 349c190..c4472ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sequelize-typescript-migration", - "version": "0.0.1-beta.2", + "version": "0.0.1-beta.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6664be5..6e1ddb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "sequelize-typescript-migration", - "version": "0.0.1-beta.3", + "name": "sequelize-typescript-migration-v2", + "version": "0.0.2-beta.3", "description": "migration tool for sequelize & typescript users", "keywords": [ "sequelize", @@ -9,10 +9,10 @@ "migration", "makemigration" ], - "homepage": "https://github.com/kimjbstar/sequelize-typescript-migration", + "homepage": "https://github.com/syon-development/sequelize-typescript-migration", "bugs": { - "url": "https://github.com/kimjbstar/sequelize-typescript-migration/issues", - "email": "kimjbstar@gmail.com" + "url": "https://github.com/syon-development/sequelize-typescript-migrationissues", + "email": "thomas.kugi@syon.at" }, "main": "dist/index.js", "scripts": { @@ -25,8 +25,8 @@ "url": "git://github.com/kimjbstar/sequelize-typescript-migration.git" }, "author": { - "name": "kimjbstar", - "email": "kimjbstar@gmail.com" + "name": "Thomas Kugi", + "email": "thomas.kugi@syon.at" }, "license": "MIT", "dependencies": { diff --git a/src/index.ts b/src/index.ts index 3b4b0f9..19701e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,7 +105,7 @@ export class SequelizeTypescriptMigration { } const info = await writeMigration( - currentState.revision, + currentState, migration, options ); diff --git a/src/utils/getLastMigrationState.ts b/src/utils/getLastMigrationState.ts index 74692a5..03ea85d 100644 --- a/src/utils/getLastMigrationState.ts +++ b/src/utils/getLastMigrationState.ts @@ -9,7 +9,7 @@ export default async function getLastMigrationState(sequelize: Sequelize) { const lastRevision: number = lastExecutedMigration !== undefined - ? lastExecutedMigration["name"].split("-")[0] + ? parseInt(lastExecutedMigration["name"].split("-")[0]) : -1; const [ diff --git a/src/utils/getTablesFromModels.ts b/src/utils/getTablesFromModels.ts index 94f2926..2dc12e8 100644 --- a/src/utils/getTablesFromModels.ts +++ b/src/utils/getTablesFromModels.ts @@ -64,7 +64,7 @@ export default function reverseModels( "references", "onUpdate", "onDelete", - "validate", + //"validate", ].forEach(key => { if (attribute[key] !== undefined) { rowAttribute[key] = attribute[key]; diff --git a/src/utils/reverseSequelizeColType.ts b/src/utils/reverseSequelizeColType.ts index 74927b9..bc56bdf 100644 --- a/src/utils/reverseSequelizeColType.ts +++ b/src/utils/reverseSequelizeColType.ts @@ -1,8 +1,5 @@ -import { - DataType as SequelizeTypescriptDataType, - Sequelize, -} from "sequelize-typescript"; -import { DataType, NUMBER, STRING, DATE, INTEGER } from "sequelize"; +import {Sequelize,} from "sequelize-typescript"; +import {DataType} from "sequelize"; // interface IDataType { // key: string; @@ -26,182 +23,188 @@ import { DataType, NUMBER, STRING, DATE, INTEGER } from "sequelize"; // } export default function reverseSequelizeColType( - sequelize: Sequelize, - attrType: any, - prefix = "Sequelize." + sequelize: Sequelize, + attrType: any, + prefix = "Sequelize." ) { - if (attrType.constructor.name === "VIRTUAL") { - return `${prefix}VIRTUAL`; - } + if (attrType.constructor.name === "VIRTUAL") { + return `${prefix}VIRTUAL`; + } + + if (attrType.constructor.name === "CHAR") { + if (!attrType.options) { + return `${prefix}CHAR`; + } + const postfix = attrType.options.binary ? ".BINARY" : ""; + return `${prefix}CHAR${postfix}`; + } + + if (attrType.constructor.name === "STRING") { + if (attrType.options === undefined) { + return `${prefix}STRING`; + } + + if (attrType.options.binary !== undefined) { + return `${prefix}STRING.BINARY`; + } + const length = + attrType.options.length !== undefined + ? `(${attrType.options.length})` + : ""; + return `${prefix}STRING${length}`; + } - if (attrType.constructor.name === "CHAR") { - if (!attrType.options) { - return `${prefix}CHAR`; + if (attrType.constructor.name === "TEXT") { + if (!attrType.options.length) { + return `${prefix}TEXT`; + } + const postfix = `('${attrType.options.length.toLowerCase()}')`; + return `${prefix}TEXT(${postfix})`; } - const postfix = attrType.options.binary ? ".BINARY" : ""; - return `${prefix}CHAR${postfix}`; - } - if (attrType.constructor.name === "STRING") { - if (attrType.options === undefined) { - return `${prefix}STRING`; + if (attrType.constructor.name === "DECIMAL") { + const params = []; + + if (attrType.options.precision) { + params.push(attrType.options.precision); + } + if (attrType.options.scale) { + params.push(attrType.options.scale); + } + const postfix = params.length > 0 ? `(${params.join(",")})` : ""; + return `${prefix}DECIMAL${postfix}`; } - if (attrType.options.binary !== undefined) { - return `${prefix}STRING.BINARY`; + if ( + ["TINYINT", "SMALLINT", "MEDIUMINT", "INTEGER", "BIGINT"].indexOf( + attrType.constructor.name + ) >= 0 + ) { + const params = []; + + if (attrType.options.length) { + params.push(attrType.options.length); + } + if (attrType.options.decimals) { + params.push(attrType.options.decimals); + } + let postfix = params.length > 0 ? `(${params.join(",")})` : ""; + + if (attrType.options.zerofill) { + postfix += ".ZEROFILL"; + } + + if (attrType.options.unsigned) { + postfix += ".UNSIGNED"; + } + + return `${prefix}${attrType.key}${postfix}`; } - const length = - attrType.options.length !== undefined - ? `(${attrType.options.length})` - : ""; - return `${prefix}STRING${length}`; - } - if (attrType.constructor.name === "TEXT") { - if (!attrType.options.length) { - return `${prefix}TEXT`; + if (attrType.constructor.name === "DATE") { + const length = attrType.options.length + ? `(${attrType.options.length})` + : ""; + return `${prefix}DATE${length}`; + } + + if (attrType.constructor.name === "DATEONLY") { + return `${prefix}DATEONLY`; + } + if (attrType.constructor.name === "JSONTYPE") { + return `${prefix}JSON`; } - const postfix = `('${attrType.options.length.toLowerCase()}')`; - return `${prefix}TEXT(${postfix})`; - } - if (attrType.constructor.name === "DECIMAL") { - const params = []; - if (attrType.options.precision) { - params.push(attrType.options.precision); + if (attrType.constructor.name === "BLOB") { + const postfix = `'${attrType.options.length.toLowerCase()}'`; + return `${prefix}BLOB(${postfix})`; } - if (attrType.options.scale) { - params.push(attrType.options.scale); - } - const postfix = params.length > 0 ? `(${params.join(",")})` : ""; - return `${prefix}DECIMAL${postfix}`; - } - - if ( - ["TINYINT", "SMALLINT", "MEDIUMINT", "INTEGER", "BIGINT"].indexOf( - attrType.constructor.name - ) >= 0 - ) { - const params = []; - - if (attrType.options.length) { - params.push(attrType.options.length); - } - if (attrType.options.decimals) { - params.push(attrType.options.decimals); - } - let postfix = params.length > 0 ? `(${params.join(",")})` : ""; - - if (attrType.options.zerofill) { - postfix += ".ZEROFILL"; - } - - if (attrType.options.unsigned) { - postfix += ".UNSIGNED"; - } - - return `${prefix}${attrType.key}${postfix}`; - } - - if (attrType.constructor.name === "DATE") { - const length = attrType.options.length - ? `(${attrType.options.length})` - : ""; - return `${prefix}DATE${length}`; - } - - if (attrType.constructor.name === "DATEONLY") { - return `${prefix}DATEONLY`; - } - - if (attrType.constructor.name === "BLOB") { - const postfix = `(${attrType.options.length.toLowerCase()})`; - return `${prefix}BLOB(${postfix})`; - } - - if (attrType.constructor.name === "ENUM") { - return `${prefix}ENUM('${attrType.options.values.join("', '")}')`; - } - - if (attrType.constructor.name === "GEOMETRY") { - if (attrType.options.type == undefined) { - return `${prefix}GEOMETRY`; - } - const type = attrType.options.type.toUpperCase(); - const srid = attrType.options.srid; - const postfixItems = [`'${type}'`]; - if (srid !== undefined) { - postfixItems.push(attrType.options.srid.toString()); - } - return `${prefix}GEOMETRY(${postfixItems.join(",")})`; - } - - if (attrType.constructor.name === "GEOGRAPHY") { - if (attrType.options.type == undefined) { - return `${prefix}GEOGRAPHY`; - } - const type = attrType.options.type.toUpperCase(); - const srid = attrType.options.srid; - const postfixItems = [`'${type}'`]; - if (srid !== undefined) { - postfixItems.push(attrType.options.srid.toString()); - } - return `${prefix}GEOGRAPHY(${postfixItems.join(",")})`; - } - - // ARRAY ( PostgreSQL only ) - if (attrType.constructor.name === "ARRAY") { - const type: DataType = attrType.options.type; - const innerType = reverseSequelizeColType(sequelize, attrType); - return `${prefix}ARRAY(${innerType})`; - } - - // RANGE ( PostgreSQL only ) - if (attrType.constructor.name === "RANGE") { - const type: DataType = attrType.options.subtype; - const innerType = reverseSequelizeColType(sequelize, attrType); - return `${prefix}RANGE(${innerType})`; - } - - let seqType; - [ - "BOOLEAN", - "TIME", - "HSTORE", - "JSON", - "JSONB", - "NOW", - "UUID", - "UUIDV1", - "UUIDV4", - "CIDR", - "INET", - "MACADDR", - "CITEXT", - ].forEach(typeName => { - if (attrType.constructor.name === typeName) { - seqType = `${prefix}${typeName}`; - } - }); - if (seqType) { - return seqType; - } - - // not supported - console.log("not supported ..." + attrType.constructor.name); - return `${prefix}VIRTUAL`; - // handle function - // if ( - // attrType.options !== undefined && - // typeof attrType.options.toString === "function" - // ) { - // seqType = attrType.options.toString(sequelize); - // } - - // if (typeof type.toString === "function") { - // seqType = type.toString(sequelize); - // } - - // return seqType; + + if (attrType.constructor.name === "ENUM") { + return `${prefix}ENUM('${attrType.options.values.join("', '")}')`; + } + + if (attrType.constructor.name === "GEOMETRY") { + if (attrType.options.type == undefined) { + return `${prefix}GEOMETRY`; + } + const type = attrType.options.type.toUpperCase(); + const srid = attrType.options.srid; + const postfixItems = [`'${type}'`]; + if (srid !== undefined) { + postfixItems.push(attrType.options.srid.toString()); + } + return `${prefix}GEOMETRY(${postfixItems.join(",")})`; + } + + if (attrType.constructor.name === "GEOGRAPHY") { + if (attrType.options.type == undefined) { + return `${prefix}GEOGRAPHY`; + } + const type = attrType.options.type.toUpperCase(); + const srid = attrType.options.srid; + const postfixItems = [`'${type}'`]; + if (srid !== undefined) { + postfixItems.push(attrType.options.srid.toString()); + } + return `${prefix}GEOGRAPHY(${postfixItems.join(",")})`; + } + + // ARRAY ( PostgreSQL only ) + if (attrType.constructor.name === "ARRAY") { + const type: DataType = attrType.options.type; + const innerType = reverseSequelizeColType(sequelize, attrType); + return `${prefix}ARRAY(${innerType})`; + } + + // RANGE ( PostgreSQL only ) + if (attrType.constructor.name === "RANGE") { + const type: DataType = attrType.options.subtype; + const innerType = reverseSequelizeColType(sequelize, attrType); + return `${prefix}RANGE(${innerType})`; + } + + let seqType; + [ + "BOOLEAN", + "TIME", + "HSTORE", + "JSON", + "JSONB", + "NOW", + "UUID", + "UUIDV1", + "UUIDV4", + "JSONTYPE", + "DOUBLE", + "CIDR", + "INET", + "MACADDR", + "CITEXT", + ].forEach(typeName => { + if (attrType.constructor.name === typeName) { + seqType = `${prefix}${typeName}`; + } + }); + if (seqType) { + return seqType; + } + + // not supported + console.log("not supported ..." + attrType.constructor.name); + return `${prefix}VIRTUAL`; + // handle function + // if ( + // attrType.options !== undefined && + // typeof attrType.options.toString === "function" + // ) { + // seqType = attrType.options.toString(sequelize); + // } + + // if (typeof type.toString === "function") { + // seqType = type.toString(sequelize); + // } + + // return seqType; } diff --git a/src/utils/writeMigration.ts b/src/utils/writeMigration.ts index 45cd494..ab8024b 100644 --- a/src/utils/writeMigration.ts +++ b/src/utils/writeMigration.ts @@ -2,31 +2,96 @@ import * as beautify from "js-beautify"; import * as fs from "fs"; import * as path from "path"; import removeCurrentRevisionMigrations from "./removeCurrentRevisionMigrations"; -export default async function writeMigration(revision, migration, options) { - await removeCurrentRevisionMigrations(revision, options.outDir, options); - const name = options.migrationName || "noname"; - const comment = options.comment || ""; - let commands = `var migrationCommands = [ \n${migration.commandsUp.join( - ", \n" - )} \n];\n`; - let commandsDown = `var rollbackCommands = [ \n${migration.commandsDown.join( - ", \n" - )} \n];\n`; +export default async function writeMigration(currentState, migration, options) { + await removeCurrentRevisionMigrations(currentState.revision, options.outDir, options); - const actions = ` * ${migration.consoleOut.join("\n * ")}`; + const name = options.migrationName || "noname"; + const comment = options.comment || ""; - commands = beautify(commands); - commandsDown = beautify(commandsDown); - const info = { - revision, - name, - created: new Date(), - comment, - }; + let myState = JSON.stringify(currentState); + const searchRegExp = /'/g; + const replaceWith = "\\'"; + myState = myState.replace(searchRegExp, replaceWith); - const template = `'use strict'; + const versionCommands = ` + { + fn: "createTable", + params: [ + "SequelizeMetaMigrations", + { + "revision": { + "primaryKey": true, + "type": Sequelize.UUID + }, + "name": { + "allowNull": false, + "type": Sequelize.STRING + }, + "state": { + "allowNull": false, + "type": Sequelize.JSON + }, + }, + {} + ] + }, + { + fn: "bulkDelete", + params: [ + "SequelizeMetaMigrations", + [{ + revision: info.revision + }], + {} + ] + }, + { + fn: "bulkInsert", + params: [ + "SequelizeMetaMigrations", + [{ + revision: info.revision, + name: info.name, + state: '${myState}' + }], + {} + ] + }, + ` + + const versionDownCommands = ` + { + fn: "bulkDelete", + params: [ + "SequelizeMetaMigrations", + [{ + revision: info.revision, + }], + {} + ] + }, + `; + + + let commands = `var migrationCommands = [\n${versionCommands}\n\n \n${migration.commandsUp.join(", \n")} \n];\n`; + let commandsDown = `var rollbackCommands = [\n${versionDownCommands}\n\n \n${migration.commandsDown.join(", \n")} \n];\n`; + + + const actions = ` * ${migration.consoleOut.join("\n * ")}`; + + commands = beautify(commands); + commandsDown = beautify(commandsDown); + + const info = { + revision: currentState.revision, + name, + created: new Date(), + comment, + }; + + const template = `'use strict'; var Sequelize = require('sequelize'); @@ -85,16 +150,16 @@ module.exports = { }; `; - const revisionNumber = revision.toString().padStart(8, "0"); + const revisionNumber = currentState.revision.toString().padStart(8, "0"); - const filename = path.join( - options.outDir, - `${ - revisionNumber + (name !== "" ? `-${name.replace(/[\s-]/g, "_")}` : "") - }.js` - ); + const filename = path.join( + options.outDir, + `${ + revisionNumber + (name !== "" ? `-${name.replace(/[\s-]/g, "_")}` : "") + }.js` + ); - fs.writeFileSync(filename, template); + fs.writeFileSync(filename, template); - return { filename, info, revisionNumber }; + return {filename, info, revisionNumber}; } From 21c93d348ae9d8c2482d202d287e3d527d99938a Mon Sep 17 00:00:00 2001 From: Thomas Kugi Date: Tue, 12 Jan 2021 19:51:53 +0100 Subject: [PATCH 2/5] fixed package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6e1ddb3..2418b1e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ ], "homepage": "https://github.com/syon-development/sequelize-typescript-migration", "bugs": { - "url": "https://github.com/syon-development/sequelize-typescript-migrationissues", + "url": "https://github.com/syon-development/sequelize-typescript-migration/issues", "email": "thomas.kugi@syon.at" }, "main": "dist/index.js", @@ -22,7 +22,7 @@ "bin": "./dist/index.js", "repository": { "type": "git", - "url": "git://github.com/kimjbstar/sequelize-typescript-migration.git" + "url": "git://github.com/syon-development/sequelize-typescript-migration" }, "author": { "name": "Thomas Kugi", From 36e663c6df2123505c982fa9e619c18350d8053f Mon Sep 17 00:00:00 2001 From: Thomas Kugi Date: Wed, 13 Jan 2021 19:03:34 +0100 Subject: [PATCH 3/5] return success if no migrations are generated in order to allow something like: await writeMigration(); in dev codebase --- package.json | 3 +- src/index.ts | 253 +++++++++++++++++++++++++-------------------------- 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/package.json b/package.json index 2418b1e..02ec3b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-typescript-migration-v2", - "version": "0.0.2-beta.3", + "version": "0.0.2-beta.4", "description": "migration tool for sequelize & typescript users", "keywords": [ "sequelize", @@ -17,6 +17,7 @@ "main": "dist/index.js", "scripts": { "build": "tsc", + "prepare": "tsc", "start": "node dist/index.js" }, "bin": "./dist/index.js", diff --git a/src/index.ts b/src/index.ts index 19701e0..1df3a6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import { Sequelize } from "sequelize-typescript"; +import {Sequelize} from "sequelize-typescript"; import * as beautify from "js-beautify"; import * as fs from "fs"; -import { IMigrationState } from "./constants"; -import { ModelCtor, Model, QueryInterface } from "sequelize/types"; +import {IMigrationState} from "./constants"; +import {Model, ModelCtor, QueryInterface} from "sequelize/types"; import getTablesFromModels from "./utils/getTablesFromModels"; import getDiffActionsFromTables from "./utils/getDiffActionsFromTables"; import getMigration from "./utils/getMigration"; @@ -11,134 +11,133 @@ import getLastMigrationState from "./utils/getLastMigrationState"; import writeMigration from "./utils/writeMigration"; export interface IMigrationOptions { - /** - * directory where migration file saved. We recommend that you specify this path to sequelize migration path. - */ - outDir: string; - /** - * if true, it doesn't generate files but just prints result action. - */ - preview?: boolean; - /** - * migration file name, default is "noname" - */ - migrationName?: string; - /** - * comment of migration. - */ - comment?: string; - debug?: boolean; + /** + * directory where migration file saved. We recommend that you specify this path to sequelize migration path. + */ + outDir: string; + /** + * if true, it doesn't generate files but just prints result action. + */ + preview?: boolean; + /** + * migration file name, default is "noname" + */ + migrationName?: string; + /** + * comment of migration. + */ + comment?: string; + debug?: boolean; } export class SequelizeTypescriptMigration { - /** - * generates migration file including up, down code - * after this, run 'npx sequelize-cli db:migrate'. - * @param sequelize sequelize-typescript instance - * @param options options - */ - public static makeMigration = async ( - sequelize: Sequelize, - options: IMigrationOptions - ) => { - options.preview = options.preview || false; - if (fs.existsSync(options.outDir) === false) { - return Promise.reject({ - msg: `${options.outDir} not exists. check path and if you did 'npx sequelize init' you must use path used in sequelize migration path`, - }); - } - await sequelize.authenticate(); - - const models: { - [key: string]: ModelCtor; - } = sequelize.models; - - const queryInterface: QueryInterface = sequelize.getQueryInterface(); - - await createMigrationTable(sequelize); - const lastMigrationState = await getLastMigrationState(sequelize); - - const previousState: IMigrationState = { - revision: - lastMigrationState !== undefined ? lastMigrationState["revision"] : 0, - version: - lastMigrationState !== undefined ? lastMigrationState["version"] : 1, - tables: - lastMigrationState !== undefined ? lastMigrationState["tables"] : {}, - }; - const currentState: IMigrationState = { - revision: previousState.revision + 1, - tables: getTablesFromModels(sequelize, models), - }; - - const upActions = getDiffActionsFromTables( - previousState.tables, - currentState.tables - ); - const downActions = getDiffActionsFromTables( - currentState.tables, - previousState.tables - ); - - const migration = getMigration(upActions); - const tmp = getMigration(downActions); - - migration.commandsDown = tmp.commandsUp; - - if (migration.commandsUp.length === 0) { - console.log("No changes found"); - process.exit(0); - } - - // log - migration.consoleOut.forEach(v => { - console.log(`[Actions] ${v}`); - }); - if (options.preview) { - console.log("Migration result:"); - console.log(beautify(`[ \n${migration.commandsUp.join(", \n")} \n];\n`)); - console.log("Undo commands:"); - console.log( - beautify(`[ \n${migration.commandsDown.join(", \n")} \n];\n`) - ); - return Promise.resolve({ msg: "success without save" }); - } - - const info = await writeMigration( - currentState, - migration, - options - ); - - console.log( - `New migration to revision ${currentState.revision} has been saved to file '${info.filename}'` - ); - - // save current state, Ugly hack, see https://github.com/sequelize/sequelize/issues/8310 - const rows = [ - { - revision: currentState.revision, - name: info.info.name, - state: JSON.stringify(currentState), - }, - ]; - - try { - await queryInterface.bulkDelete("SequelizeMetaMigrations", { - revision: currentState.revision, - }); - await queryInterface.bulkInsert("SequelizeMetaMigrations", rows); - - console.log(`Use sequelize CLI: + /** + * generates migration file including up, down code + * after this, run 'npx sequelize-cli db:migrate'. + * @param sequelize sequelize-typescript instance + * @param options options + */ + public static makeMigration = async ( + sequelize: Sequelize, + options: IMigrationOptions + ) => { + options.preview = options.preview || false; + if (fs.existsSync(options.outDir) === false) { + return Promise.reject({ + msg: `${options.outDir} not exists. check path and if you did 'npx sequelize init' you must use path used in sequelize migration path`, + }); + } + await sequelize.authenticate(); + + const models: { + [key: string]: ModelCtor; + } = sequelize.models; + + const queryInterface: QueryInterface = sequelize.getQueryInterface(); + + await createMigrationTable(sequelize); + const lastMigrationState = await getLastMigrationState(sequelize); + + const previousState: IMigrationState = { + revision: + lastMigrationState !== undefined ? lastMigrationState["revision"] : 0, + version: + lastMigrationState !== undefined ? lastMigrationState["version"] : 1, + tables: + lastMigrationState !== undefined ? lastMigrationState["tables"] : {}, + }; + const currentState: IMigrationState = { + revision: previousState.revision + 1, + tables: getTablesFromModels(sequelize, models), + }; + + const upActions = getDiffActionsFromTables( + previousState.tables, + currentState.tables + ); + const downActions = getDiffActionsFromTables( + currentState.tables, + previousState.tables + ); + + const migration = getMigration(upActions); + const tmp = getMigration(downActions); + + migration.commandsDown = tmp.commandsUp; + + if (migration.commandsUp.length === 0) { + return Promise.resolve({msg: "success: no changes found"}); + } + + // log + migration.consoleOut.forEach(v => { + console.log(`[Actions] ${v}`); + }); + if (options.preview) { + console.log("Migration result:"); + console.log(beautify(`[ \n${migration.commandsUp.join(", \n")} \n];\n`)); + console.log("Undo commands:"); + console.log( + beautify(`[ \n${migration.commandsDown.join(", \n")} \n];\n`) + ); + return Promise.resolve({msg: "success without save"}); + } + + const info = await writeMigration( + currentState, + migration, + options + ); + + console.log( + `New migration to revision ${currentState.revision} has been saved to file '${info.filename}'` + ); + + // save current state, Ugly hack, see https://github.com/sequelize/sequelize/issues/8310 + const rows = [ + { + revision: currentState.revision, + name: info.info.name, + state: JSON.stringify(currentState), + }, + ]; + + try { + await queryInterface.bulkDelete("SequelizeMetaMigrations", { + revision: currentState.revision, + }); + await queryInterface.bulkInsert("SequelizeMetaMigrations", rows); + + console.log(`Use sequelize CLI: npx sequelize db:migrate --to ${info.revisionNumber}-${ - info.info.name - }.js ${`--migrations-path=${options.outDir}`} `); + info.info.name + }.js ${`--migrations-path=${options.outDir}`} `); - return Promise.resolve({ msg: "success" }); - } catch (err) { - if (options.debug) console.error(err); - } + return Promise.resolve({msg: "success"}); + } catch (err) { + if (options.debug) console.error(err); + } - return Promise.resolve({ msg: "success anyway.." }); - }; + return Promise.resolve({msg: "success anyway.."}); + }; } From 126c0221b375d35e9d9a8a86984e87623ac45e25 Mon Sep 17 00:00:00 2001 From: lou2013 <49943020+lou2013@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:15:25 +0330 Subject: [PATCH 4/5] Update parseIndex.ts change indicesType to type in parseindex so that the indexes works with newer versions of the code please notice that the old migration keys are not compatible with this so if there is a migration with indicesType as a index key in it it will crash all indicesType keys in old migration files and data in database must be changes to type --- src/utils/parseIndex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/parseIndex.ts b/src/utils/parseIndex.ts index eaff077..a430971 100644 --- a/src/utils/parseIndex.ts +++ b/src/utils/parseIndex.ts @@ -27,7 +27,7 @@ export default function parseIndex(idx: IndexesOptions) { // @todo: UNIQUE|FULLTEXT|SPATIAL if (idx.unique) { - options["indicesType"] = "UNIQUE"; + options["type"] = "UNIQUE"; } // Set a type for the index, e.g. BTREE. See the documentation of the used dialect From 9a53aec5611e1ed6c50684b83e80911262914e87 Mon Sep 17 00:00:00 2001 From: lou2013 <49943020+lou2013@users.noreply.github.com> Date: Sat, 8 Jan 2022 11:27:54 +0330 Subject: [PATCH 5/5] Update getDiffActionsFromTables.ts handling index diffrences --- src/utils/getDiffActionsFromTables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/getDiffActionsFromTables.ts b/src/utils/getDiffActionsFromTables.ts index 8903c79..b2ddd70 100644 --- a/src/utils/getDiffActionsFromTables.ts +++ b/src/utils/getDiffActionsFromTables.ts @@ -118,7 +118,7 @@ export default function getDiffActionsFromTables( } // new index - if (df.path[1] === "indexes") { + if (df.path[1] === "indexes" && df.rhs) { const tableName = df.path[0]; const copied = df.rhs ? JSON.parse(JSON.stringify(df.rhs)) @@ -189,7 +189,7 @@ export default function getDiffActionsFromTables( } } - if (df.path[1] === "indexes") { + if (df.path[1] === "indexes" && df.lhs) { actions.push({ actionType: "removeIndex", tableName,