sequelize-mig
Version:
Sequelize migration generator and es6 init tool
252 lines (203 loc) • 7.29 kB
JavaScript
import fs, { writeFileSync } from 'fs';
import path, { join } from 'path';
import prettier from 'prettier';
import { getYYYYMMDDHHMMSS, template, log } from './functions.js';
import { readAsset, sortActions } from './helpers.js';
import { getActions, reverseModels } from './models.js';
export const getPartialMigration = (actions) => {
const stringifyModel = (obj) => {
const vals = [];
Object.entries(obj).forEach(([key, val]) => {
const x = {};
if (key === 'seqType') {
vals.push(`"type": ${val}`);
} else if (key === 'defaultValue' && val.internal) {
vals.push(`"defaultValue": ${val.value}`);
} else if (key === 'defaultValue' && !val.notSupported) {
x[key] = val.value;
vals.push(JSON.stringify(x).slice(1, -1));
} else if (!val.notSupported) {
x[key] = val;
vals.push(JSON.stringify(x).slice(1, -1));
}
});
return `{ ${vals.reverse().join(', ')} }`;
};
const getAttributes = (attrs) => {
const ret = [];
Object.entries(attrs).forEach(([key, val]) => ret.push(`"${key}": ${stringifyModel(val)}`));
return `{ \n${ret.join(', \n')}\n}`;
};
const parseOptions = (options, addTransaction = false) => {
if (addTransaction) {
let ret = JSON.stringify({ ...options, transaction: '###TRANSACTION###' });
ret = ret.replace('"transaction":"###TRANSACTION###"', 'transaction');
return ret;
}
return '{ transaction }';
};
const writeCommand = (action, par, addProperty = false, addTransaction = false) => {
const pars = [`"${action.tableName}"`];
if (par) {
if (/^[^{[].*/.test(par)) par = `"${par}"`;
pars.push(par);
}
if (action.actionType === 'renameColumn') {
pars.push(`"${action.options}"`);
} else {
if (addProperty) pars.push(stringifyModel(action.options));
pars.push(parseOptions(action.options, addTransaction));
}
return `{
fn: "${action.actionType}",
params: [${pars.join(',')}]
}`;
};
const writeConsole = (action, par = '', showDeps = false) => {
let ret = `${action.actionType}(${par}) => "${action.tableName}"`;
if (showDeps) ret += `, deps: [${action.depends.join(', ')}]`;
return ret;
};
const commands = [];
const consoles = [];
actions.forEach((action) => {
let commandPar;
let consolePar;
let showDeps = false;
let addProperty = false;
let addTransaction = false;
if (!action.options) action.options = {};
switch (action.actionType) {
case 'createTable':
commandPar = getAttributes(action.attributes);
showDeps = true;
addTransaction = true;
break;
case 'dropTable':
showDeps = true;
break;
case 'addColumn':
case 'changeColumn':
commandPar = action.options.field ? action.options.field : action.attributeName;
addProperty = true;
consolePar = commandPar;
break;
case 'renameColumn':
case 'removeColumn':
commandPar = action.options.field ? action.options.field : action.columnName;
consolePar = commandPar;
break;
case 'addIndex':
commandPar = JSON.stringify(action.fields);
consolePar = action.options.indexName
? action.options.indexName
: JSON.stringify(action.fields);
addTransaction = true;
break;
case 'removeIndex':
commandPar = action.options.indexName
? action.options.indexName
: JSON.stringify(action.fields);
consolePar = commandPar;
break;
default:
action = null;
log(4, 'Error: action not specified');
}
if (action) {
commands.push(writeCommand(action, commandPar, addProperty, addTransaction));
consoles.push(writeConsole(action, consolePar, showDeps));
}
});
return { commands, consoles };
};
export const updateMigrationState = async (currentState, previousState) => {
// backup _current file
if (currentState.exists)
fs.writeFileSync(currentState.backupPath, JSON.stringify(previousState, null, 4));
// save current state
currentState.revision = previousState.revision + 1;
fs.writeFileSync(currentState.path, JSON.stringify(currentState, null, 4));
};
export const getMigration = (upActions, downActions) => {
const { commands: commandsUp, consoles } = getPartialMigration(upActions);
const { commands: commandsDown } = getPartialMigration(downActions);
return { commandsUp, commandsDown, consoles };
};
export const migrate = async (options) => {
const { modelsDir, migrationsDir, indexDir, stateDir } = options;
log(1, `migrate options:${JSON.stringify(options, null, 2)}`);
if (!fs.existsSync(modelsDir))
throw new Error("Can't find models directory. Use `sequelize init` to create it");
if (!fs.existsSync(migrationsDir))
throw new Error("Can't find migrations directory. Use `sequelize init` to create it");
if (!fs.existsSync(indexDir))
throw new Error("Can't find sequelize index file. Use `sequelize init` to create it");
if (!fs.existsSync(stateDir)) {
log(3, "Can't find State directory. Creating it...");
fs.mkdirSync(stateDir, { recursive: true });
}
// current state
const newState = {
tables: {},
path: path.join(stateDir, '_current.json'),
backupPath: path.join(stateDir, '_current_bak.json'),
};
newState.exists = fs.existsSync(newState.path);
let oldState = {
revision: 0,
tables: {},
};
if (newState.exists) {
try {
oldState = JSON.parse(fs.readFileSync(newState.path));
} catch (e) {
log(3, '_current.json syntax is not valid, overriding it');
}
} else {
log(3, '_current.json not found. first time running this tool');
}
try {
const { sequelize } = (await import(`file:\\\\${indexDir}`)).default;
const { models } = sequelize;
newState.tables = reverseModels(sequelize, models);
} catch (e) {
throw new Error(`sequelize index file is not valid probably. solve it and try again: ${e}`);
}
const upActions = await getActions(oldState.tables, newState.tables);
const downActions = await getActions(newState.tables, oldState.tables, upActions);
// sort actions
sortActions(upActions);
sortActions(downActions);
const migration = getMigration(upActions, downActions);
return {
previousState: oldState,
currentState: newState,
migration,
};
};
export const writeMigration = (
revision,
migration,
migrationsDir,
name = 'noname',
comment = '',
es6 = false,
) => {
const commandsUp = migration.commandsUp.join(', \n');
const commandsDown = migration.commandsDown.join(', \n');
const actions = migration.consoles.join('\n * ');
const infoObj = {
revision,
name,
created: new Date(),
comment,
};
const info = JSON.stringify(infoObj, null, 4);
const file = readAsset('migrations/skeleton.js');
const content = template(file, { actions, info, commandsUp, commandsDown });
name = name.replace(' ', '_');
const filename = join(migrationsDir, `${getYYYYMMDDHHMMSS()}_${name}.${es6 ? 'cjs' : 'js'}`);
writeFileSync(filename, prettier.format(content, { parser: 'babel' }));
return { filename, info };
};