Report strapi-generate-migration updates

This commit is contained in:
Aurélien Georget 2016-03-23 14:57:20 +01:00
parent 9261e67c5d
commit 239e53193c
9 changed files with 281 additions and 67 deletions

View File

@ -5,12 +5,17 @@
*/
// Node.js core.
const _ = require('lodash');
const async = require('async');
const fs = require('fs');
const path = require('path');
// Public node modules.
const beautify = require('js-beautify').js_beautify;
// Local utilities.
const dictionary = require('strapi/util/dictionary');
/**
* Runs after this generator has finished
*
@ -19,26 +24,73 @@ const beautify = require('js-beautify').js_beautify;
*/
module.exports = function afterGenerate(scope, cb) {
const migrationFile = path.resolve(scope.rootPath, 'data', 'migrations', scope.connection, scope.filename);
async.parallel({
migrationFile: function (cb) {
const migrationFile = path.resolve(scope.rootPath, 'data', 'migrations', scope.connection, scope.filename);
// Read the migration file.
fs.readFile(migrationFile, 'utf8', function (err, data) {
// Read the migration file.
fs.readFile(migrationFile, 'utf8', function (err, data) {
if (err) {
return cb.invalid(err);
}
// And rewrite it with the beautify node module.
fs.writeFile(migrationFile, beautify(data, {
indent_size: 2,
keep_function_indentation: true,
space_before_conditional: true,
end_with_newline: true
}), 'utf8', function (err) {
if (err) {
return cb(err, null);
} else {
return cb(null, null);
}
});
});
},
settings: function (cb) {
dictionary.aggregate({
dirname: path.resolve(scope.rootPath, 'api'),
filter: /(.+)\.settings.json$/,
depth: 4
}, cb);
},
functions: function (cb) {
dictionary.aggregate({
dirname: path.resolve(scope.rootPath, 'api'),
filter: /(.+)\.js$/,
depth: 4
}, cb);
}
}, function (err, data) {
if (err) {
return cb.invalid(err);
}
// And rewrite it with the beautify node module.
fs.writeFile(migrationFile, beautify(data, {
indent_size: 2,
keep_function_indentation: true,
space_before_conditional: true,
end_with_newline: true
}), 'utf8', function (err) {
if (err) {
return cb.invalid(err);
} else {
return cb.success();
}
});
// Fetch all models
const models = _.get(_.merge(data.settings, data.functions), 'models');
if (!_.isUndefined(models)) {
_.mapValues(models, function (model) {
return _.omitBy(model, _.isFunction);
});
const historyFile = path.resolve(scope.rootPath, 'data', 'migrations', '.history');
// And rewrite it with the beautify node module.
fs.writeFile(historyFile, beautify(JSON.stringify(models), {
indent_size: 2,
keep_function_indentation: true,
space_before_conditional: true,
end_with_newline: true
}), 'utf8', function (err) {
if (err) {
return cb.invalid(err);
} else {
return cb.success();
}
});
}
});
};

View File

@ -76,6 +76,14 @@ module.exports = function (scope, cb) {
}
});
let history;
try {
history = JSON.parse(fs.readFileSync(path.resolve(scope.rootPath, 'data', 'migrations', '.history'), 'utf8'));
} catch (err) {
// File not existing
history = {};
}
// Register every model.
const migrations = glob.sync(path.resolve(scope.rootPath, 'api', '**', 'models', '*.json')).map((file) => {
let modelName;
@ -87,6 +95,12 @@ module.exports = function (scope, cb) {
modelName = JSON.parse(fs.readFileSync(path.resolve(file))).tableName;
scope.models[modelName] = JSON.parse(fs.readFileSync(path.resolve(file)));
if (!_.isEmpty(history) && history.hasOwnProperty(_.capitalize(modelName))) {
_.set(scope.models, modelName + '.oldAttributes', _.get(history, _.capitalize(modelName) + '.attributes'));
} else {
_.set(scope.models, modelName + '.oldAttributes', {});
}
// First, we need to know if the table already exists.
scope.db.schema.hasTable(modelName).then(function (exists) {
@ -121,40 +135,82 @@ module.exports = function (scope, cb) {
// Parse every attribute.
_.forEach(scope.models[modelName].attributes, function (details, attribute) {
// TODO:
// - Column is existing ?
// -- YES:
// --- Compare current type with last one (nullable, maxLenght, type, defaultValue).
// ---- Updated ? Drop column, and create a new one.
// ---- Not updated ? Do nothing.
// -- NO:
// --- Add the new column
// scope.db.schema.hasColumn(modelName, attribute).then(function (exists) {
//
// }).catch(function (err) {
// console.log(err);
// });
// Verify if a column already exists for the attribute.
scope.db.schema.hasColumn(modelName, attribute).then(function (exists) {
scope.models[modelName].newAttributes = {};
scope.models[modelName].newAttributes = {};
// If it's a new attribute.
if (!exists) {
// If it's a new attribute.
if (!scope.models[modelName].oldAttributes.hasOwnProperty(attribute)) {
// Save the attribute as a new attribute.
scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details);
// Builder: create template for each attribute-- either with a column type
// or with a relationship.
if (details.type && _.isString(details.type)) {
builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute);
} else if (_.isString(details.collection) || _.isString(details.model)) {
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute);
}
// Builder: select the table.
builder.selectTable(scope.models, modelName);
} else {
// If the column already exists.
let toDrop = false;
// Try to identify relation attribute update
if (details.hasOwnProperty('collection') && details.hasOwnProperty('via') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'collection') !== details.collection || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) {
toDrop = true;
} else if (details.hasOwnProperty('model') && details.hasOwnProperty('via') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) {
toDrop = true;
} else if (details.hasOwnProperty('model') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model)) {
toDrop = true;
} else if (!_.isUndefined(details.type) && _.get(scope.models[modelName].oldAttributes[attribute], 'type') !== _.get(details, 'type')) {
toDrop = true;
} else if (!_.isUndefined(details.defaultValue) && _.get(scope.models[modelName].oldAttributes[attribute], 'defaultValue') === _.get(details, 'defaultValue')) {
toDrop = true;
} else if (!_.isUndefined(details.maxLength) && _.get(scope.models[modelName].oldAttributes[attribute], 'maxLength') === _.get(details, 'maxLength')) {
toDrop = true;
} else if (!_.isUndefined(details.nullable) && _.get(scope.models[modelName].oldAttributes[attribute], 'nullable') === _.get(details, 'nullable')) {
toDrop = true;
}
// The attribute has been updated.
// We will drop it then create it again with the new options.
if (toDrop) {
// Save the attribute as a new attribute.
scope.models[modelName].newAttributes[attribute] = details;
scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details);
// Builder: create template for each attribute-- either with a column type
// or with a relationship.
if (details.type && _.isString(details.type)) {
builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute);
builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop);
} else if (_.isString(details.collection) || _.isString(details.model)) {
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute);
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop);
}
// Builder: select the table.
builder.selectTable(scope.models, modelName);
}
// If the column already exists.
else {
// TODO: Verify columns info are the same.
// scope.db(modelName).columnInfo(attribute).then(function (info) {
//
// });
}
}).catch(function (err) {
console.log(err);
});
}
});
}
});

View File

@ -23,19 +23,19 @@ module.exports = function (models, modelName) {
// Then, every `up` logic of every model call the
// `./builder/tables/createTableIfNotExists` template.
const tplTableCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'createTableIfNotExists.template'), 'utf8');
models[modelName].up = _.unescape(_.template(tplTableCreate)({
_.set(models[modelName], 'up.others', _.unescape(_.template(tplTableCreate)({
models: models,
tableName: modelName,
attributes: models[modelName].attributes,
options: models[modelName].options
}));
})));
// Template: drop the table for the `down` export.
// This adds a `down` logic for the current model.
// Then, every `down` logic of every model call the
// `./builder/tables/dropTable` template.
const tplTableDrop = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8');
models[modelName].down = _.unescape(_.template(tplTableDrop)({
_.set(models[modelName], 'down.others', _.unescape(_.template(tplTableDrop)({
tableName: modelName
}));
})));
};

View File

@ -17,21 +17,41 @@ const _ = require('lodash');
module.exports = function (models, modelName) {
models[modelName].up = {};
// Template: select the table for the `up` export.
// Every attribute with `create` key will be added in this template.
const tplSelectTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'up.template'), 'utf8');
models[modelName].up = _.unescape(_.template(tplSelectTableUp)({
models[modelName].up.drop = _.unescape(_.template(tplSelectTableUp)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes
attributes: models[modelName].newAttributes,
toDrop: true
}));
models[modelName].up.others = _.unescape(_.template(tplSelectTableUp)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes,
toDrop: false
}));
models[modelName].down = {};
// Template: select the table for the `down` export.
// Every attribute with `delete` key will be added in this template.
const tplSelectTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'down.template'), 'utf8');
models[modelName].down = _.unescape(_.template(tplSelectTableDown)({
models[modelName].down.drop = _.unescape(_.template(tplSelectTableDown)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes
attributes: models[modelName].newAttributes,
toDrop: true
}));
models[modelName].down.others = _.unescape(_.template(tplSelectTableDown)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes,
toDrop: false
}));
};

View File

@ -15,7 +15,7 @@ const _ = require('lodash');
* Template types
*/
module.exports = function (models, modelName, details, attribute) {
module.exports = function (models, modelName, details, attribute, toDrop) {
// Template: create a new column thanks to the attribute's type.
// Firt, make sure we know the attribute type. If not, just do it
@ -26,7 +26,21 @@ module.exports = function (models, modelName, details, attribute) {
} catch (err) {
tplTypeCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', 'specificType.template'), 'utf8');
}
models[modelName].attributes[attribute].create = _.unescape(_.template(tplTypeCreate)({
const tplTypeDelete = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8');
// UP
models[modelName].attributes[attribute].create = {};
if (!_.isUndefined(toDrop) && toDrop) {
// Template: delete a specific column.
models[modelName].attributes[attribute].create.drop = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
}
models[modelName].attributes[attribute].create.others = _.unescape(_.template(tplTypeCreate)({
tableName: modelName,
attribute: attribute,
details: details
@ -36,7 +50,7 @@ module.exports = function (models, modelName, details, attribute) {
// if a default value is needed.
if (!_.isUndefined(details.defaultTo)) {
const tplDefaultTo = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'defaultTo.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplDefaultTo)({
models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplDefaultTo)({
details: details
}));
}
@ -45,20 +59,43 @@ module.exports = function (models, modelName, details, attribute) {
// if the column respect uniqueness rule.
if (details.unique === true) {
const tplUnique = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'unique.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplUnique)({}));
models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplUnique)({}));
}
// Template: make the column chainable with the `primary` template
// if the column needs the rule.
if (details.primary === true) {
const tplPrimary = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'primary.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplPrimary)({}));
models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplPrimary)({}));
}
// Template: delete a specific column.
const tplTypeDelete = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8');
models[modelName].attributes[attribute].delete = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
// DOWN
models[modelName].attributes[attribute].delete = {};
if (!_.isUndefined(toDrop) && toDrop) {
let tplTypeDeleteCreate;
try {
tplTypeDeleteCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', models[modelName].oldAttributes[attribute].type + '.template'), 'utf8');
} catch (err) {
tplTypeDeleteCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', 'specificType.template'), 'utf8');
}
// Template: delete a specific column.
models[modelName].attributes[attribute].delete.drop = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
models[modelName].attributes[attribute].delete.others = _.unescape(_.template(tplTypeDeleteCreate)({
tableName: modelName,
attribute: attribute,
details: models[modelName].oldAttributes[attribute]
}));
} else {
// Template: delete a specific column.
models[modelName].attributes[attribute].delete.others = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
}
};

View File

@ -5,7 +5,7 @@
connection.schema.createTableIfNotExists('<%= tableName %>', function (table) {<% if (_.isObject(options)) { _.forEach(options, function(value, option) { %><% if (models[tableName].options[option] !== false) { %>
<%= models[tableName][option] %>;<% } %><% }); } %>
<% _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create %>;
<% _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.others %>;
<% }); %>
}).catch(function (err) {
console.log('Impossible to create the `<%= tableName %>` table.');

View File

@ -2,11 +2,18 @@
/**
* Select the `<%= tableName %>` table.
*/
<% if (toDrop === true) { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete %>;
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete.drop %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),
}), <% } else { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete.others %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),<% } %>

View File

@ -2,11 +2,18 @@
/**
* Select the `<%= tableName %>` table.
*/
<% if (toDrop === true) { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create %>;
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.drop %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),
}), <% } else { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.others %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),<% } %>

View File

@ -8,9 +8,26 @@
*/
exports.up = function(connection, Promise) {
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= models[model].up %><% }); %>
]);
<% var dropped = false;
_.forEach(models, function (definition, model) {
if (!_.isUndefined(_.get(models[model], 'up.drop'))) {
dropped = true;
}
});
if (dropped) { %> return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.drop') %><% }); %>
]).then(function() {
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.others') %><% }); %>
]);
});
<% } else { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.others') %><% }); %>
]);
<% } %>
};
/**
@ -21,9 +38,27 @@ exports.up = function(connection, Promise) {
*/
exports.down = function(connection, Promise) {
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= models[model].down %><% }); %>
]);
<% var dropped = false;
_.forEach(models, function (definition, model) {
if (!_.isUndefined(_.get(models[model], 'down.drop'))) {
dropped = true;
}
});
if (dropped) { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.drop') %><% }); %>
]).then(function() {
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.others') %><% }); %>
]);
});
<% } else { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.others') %><% }); %>
]);
<% } %>
};
/**