-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupdates.js
More file actions
182 lines (169 loc) · 5.59 KB
/
updates.js
File metadata and controls
182 lines (169 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
var _ = require('underscore');
var async = require('async');
var fs = require('fs');
var keystone = require('../');
var mongoose = keystone.mongoose;
var path = require('path');
var semver = require('semver');
var utils = require('keystone-utils');
var _dashes_ = '------------------------------------------------';
// Update Schema - automatically created and managed by Keystone when updates are used
var UpdateModel = new mongoose.Schema({
key: { type: String, index: true },
appliedOn: { type: Date, default: Date.now }
}, { collection: keystone.prefixModel('App_Update') });
mongoose.model('App_Update', UpdateModel);
// Apply method - loads the available updates and applies any that haven't been, in order
exports.apply = function(callback) {
var Update = mongoose.model('App_Update');
var updateCount = 0;
var deferCount = 0;
var skipCount = 0;
var updatesPath = keystone.getPath('updates', 'updates');
// logError is used to log errors before the process exits since it is more synchronous than console.error. Using
// console.error gets into race condition issues with process.exit, which has higher priority.
var logError = function() {
for (var i = 0, len = arguments.length; i < len; ++i) {
process.stderr.write(arguments[i] + '\n');
}
};
var applyUpdate = function(file, done) {
Update.findOne({ key: file }, function(err, updateRecord) {
if (err) {
console.error('Error searching database for update ' + file + ':');
console.dir(err);
done(err);
} else if (!updateRecord) {
var update = require(path.join(updatesPath, file));
// skip updates that export a falsy value
if (!update) {
skipCount++;
return done();
}
// auto-wrap create scripts for a friendlier shorthand syntax
if (_.isObject(update.create)) {
var items = update.create,
ops = update.options || {};
var background_mode = update.__background__ ? ' (background mode) ' : '';
update = function(done) {
keystone.createItems(items, ops, function(err, stats) {
if (!err) {
var statsMsg = stats ? stats.message : '';
console.log('\n' + _dashes_,
'\n' + keystone.get('name') + ': Successfully applied update ' + file + background_mode + '.',
'\n' + statsMsg,
'\n');
done(null);
}
else {
logError('\n' + _dashes_,
'\n' + keystone.get('name') + ': Update ' + file + background_mode + ' failed with errors:',
'\n' + err,
'\n');
// give the logging some time to finish
process.nextTick(function() {
done(err);
});
}
});
};
}
// ensure type
if (!_.isFunction(update)) {
console.log('\nError in update file ./updates/' + file + '.js\nUpdate files must export a function\n');
process.exit();
}
// if an update is deferred, don't process it
if (update.__defer__) {
deferCount++;
return done();
}
// if there are deferred updates, don't process any subsequent ones
if (deferCount) {
skipCount++;
return done();
}
console.log(_dashes_ + '\nApplying update ' + file + '...');
if (update.__background__) {
updateCount++;
update(function(err) {
if (!err) {
if (update.__commit__ !== false) {
new Update({ key: file }).save();
}
}
});
done();
} else {
update(function(err) {
if (!err) {
updateCount++;
if (update.__commit__ === false) {
done();
} else {
new Update({ key: file }).save(done);
}
}
});
}
} else {
done();
}
});
};
if (!fs.existsSync(updatesPath)) {
console.log('\nKeystoneJS Update Error:\n\n' +
'An updates folder must exist in your project root to use automatic updates.\n' +
'If you want to use a custom path for your updates, set the `updates` option.\n' +
'If you don\'t want to use updates, set the `auto update` option to `false`.\n' +
'See http://keystonejs.com/docs/configuration/#updates for more information.\n');
process.exit();
}
var updates = fs.readdirSync(updatesPath)
.map(function(i) {
// exclude non-javascript or coffee files in the updates folder
return (path.extname(i) !== '.js' && path.extname(i) !== '.coffee') ? false : path.basename(i, '.js');
}).filter(function(i) {
// exclude falsy values and filenames that without a valid semver
return i && semver.valid(i.split('-')[0]);
}).sort(function(a, b) {
// exclude anything after a hyphen from the version number
return semver.compare(a.split('-')[0], b.split('-')[0]);
});
async.eachSeries(updates, applyUpdate, function(err) {
if (updateCount || deferCount || skipCount) {
var status = '';
if (updateCount) {
status += 'Successfully applied ' + utils.plural(updateCount, '* update');
if (skipCount || deferCount) {
status += ', ';
}
}
if (deferCount) {
status += 'Deferred ' + utils.plural(deferCount, '* update');
if (skipCount) {
status += ', ';
}
}
if (skipCount) {
status += 'Skipped ' + utils.plural(skipCount, '* update');
}
status += '.';
console.log(_dashes_ + '\n' + status + '\n' + _dashes_);
}
if (err) {
var errmsg = 'An error occurred applying updates, bailing on Keystone init.\n\nError details:';
if (!(updateCount || deferCount || skipCount)) {
errmsg = _dashes_ + '\n' + errmsg;
}
logError(errmsg);
logError(err);
// wait till nextTick to exit so the trace completes.
process.nextTick(function() {
process.exit(1);
});
return;
}
callback && callback();// eslint-disable-line no-unused-expressions
});
};