Add support for ES6 style async boot scripts

This commit is contained in:
Jürg Lehni 2017-07-29 13:48:28 +02:00 committed by Miroslav Bajtoš
parent 39b885f9fb
commit 51326091b2
No known key found for this signature in database
GPG Key ID: 6F2304BA9361C7E3
7 changed files with 161 additions and 41 deletions

View File

@ -305,17 +305,24 @@ function runScripts(app, list, callback) {
async.eachSeries(functions, function(f, done) { async.eachSeries(functions, function(f, done) {
debug('Running script %s', f.path); debug('Running script %s', f.path);
if (f.func.length >= 2) { var cb = function(err) {
debug('Starting async function %s', f.path); debug('Async function finished %s', f.path);
f.func(app, function(err) { done(err);
debug('Async function finished %s', f.path); // Make sure done() isn't called twice, e.g. if a script returns a
done(err); // thenable object and also calls the passed callback.
}); cb = null;
} else { };
debug('Starting sync function %s', f.path); try {
f.func(app); var result = f.func(app, cb);
debug('Sync function finished %s', f.path); if (result && typeof result.then === 'function') {
done(); result.then(function() { cb(); }, cb);
} else if (f.func.length < 2) {
debug('Sync function finished %s', f.path);
done();
}
} catch (err) {
debug('Sync function failed %s', f.path, err);
done(err);
} }
}, callback); }, callback);
} }

View File

@ -31,6 +31,7 @@
}, },
"devDependencies": { "devDependencies": {
"browserify": "^4.1.8", "browserify": "^4.1.8",
"bluebird": "^3.1.1",
"chai": "^1.10.0", "chai": "^1.10.0",
"coffee-script": "^1.8.0", "coffee-script": "^1.8.0",
"coffeeify": "^0.7.0", "coffeeify": "^0.7.0",

View File

@ -265,6 +265,8 @@ describe('executor', function() {
'barLoaded', 'barLoaded',
'barSyncLoaded', 'barSyncLoaded',
'fooLoaded', 'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted', 'barStarted',
]); ]);
@ -275,9 +277,15 @@ describe('executor', function() {
'barLoaded', 'barLoaded',
'barSyncLoaded', 'barSyncLoaded',
'fooLoaded', 'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted', 'barStarted',
'barFinished', 'barFinished',
'barSyncExecuted', 'barSyncExecuted',
'promiseStarted',
'promiseFinished',
'thenableStarted',
'thenableFinished',
'umdLoaded', 'umdLoaded',
]); ]);
done(); done();
@ -292,9 +300,15 @@ describe('executor', function() {
'barLoaded', 'barLoaded',
'barSyncLoaded', 'barSyncLoaded',
'fooLoaded', 'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted', 'barStarted',
'barFinished', 'barFinished',
'barSyncExecuted', 'barSyncExecuted',
'promiseStarted',
'promiseFinished',
'thenableStarted',
'thenableFinished',
'umdLoaded', 'umdLoaded',
]); ]);
done(); done();
@ -305,7 +319,7 @@ describe('executor', function() {
function(done) { function(done) {
var options = { var options = {
app: app, app: app,
appRootDir: path.join(__dirname, './fixtures/simple-app'), appRootDir: SIMPLE_APP,
scriptExtensions: ['.customjs', '.customjs2'], scriptExtensions: ['.customjs', '.customjs2'],
}; };
boot.execute(app, boot.compile(options), function(err) { boot.execute(app, boot.compile(options), function(err) {
@ -317,44 +331,84 @@ describe('executor', function() {
done(); done();
}); });
}); });
});
describe('for mixins', function() { describe('with boot script returning a rejected promise', function() {
var options; before(function() {
beforeEach(function() { // Tell simple-app/boot/reject.js to return a rejected promise
appdir.writeFileSync('custom-mixins/example.js', process.rejectPromise = true;
'module.exports = ' + });
'function(Model, options) {}');
appdir.writeFileSync('custom-mixins/time-stamps.js', after(function() {
'module.exports = ' + delete process.rejectPromise;
'function(Model, options) {}'); });
appdir.writeConfigFileSync('custom-mixins/time-stamps.json', { it('receives rejected promise as callback error',
name: 'Timestamping', function(done) {
}); boot.execute(app, simpleAppInstructions(), function(err) {
expect(err).to.exist.and.be.an.instanceOf(Error)
.with.property('message', 'reject');
done();
});
});
});
options = { describe('with boot script throwing an error', function() {
appRootDir: appdir.PATH, before(function() {
}; // Tell simple-app/boot/throw.js to throw an error
process.throwError = true;
});
after(function() {
delete process.throwError;
});
it('receives thrown error as callback errors',
function(done) {
boot.execute(app, simpleAppInstructions(), function(err) {
expect(err).to.exist.and.be.an.instanceOf(Error)
.with.property('message', 'throw');
done();
});
});
});
describe('for mixins', function() {
var options;
beforeEach(function() {
appdir.writeFileSync('custom-mixins/example.js',
'module.exports = ' +
'function(Model, options) {}');
appdir.writeFileSync('custom-mixins/time-stamps.js',
'module.exports = ' +
'function(Model, options) {}');
appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
name: 'Timestamping',
}); });
it('defines mixins from instructions - using `mixinDirs`', function() { options = {
options.mixinDirs = ['./custom-mixins']; appRootDir: appdir.PATH,
boot(app, options); };
});
var modelBuilder = app.registry.modelBuilder; it('defines mixins from instructions - using `mixinDirs`', function() {
var registry = modelBuilder.mixins.mixins; options.mixinDirs = ['./custom-mixins'];
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']); boot(app, options);
});
it('defines mixins from instructions - using `mixinSources`', function() { var modelBuilder = app.registry.modelBuilder;
options.mixinSources = ['./custom-mixins']; var registry = modelBuilder.mixins.mixins;
boot(app, options); expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
});
var modelBuilder = app.registry.modelBuilder; it('defines mixins from instructions - using `mixinSources`', function() {
var registry = modelBuilder.mixins.mixins; options.mixinSources = ['./custom-mixins'];
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']); boot(app, options);
});
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
}); });
}); });

View File

@ -0,0 +1,19 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var Promise = require('bluebird');
process.bootFlags.push('promiseLoaded');
module.exports = function(app) {
process.bootFlags.push('promiseStarted');
return Promise.resolve({
then: function(onFulfill, onReject) {
process.nextTick(function() {
process.bootFlags.push('promiseFinished');
onFulfill();
});
},
});
};

12
test/fixtures/simple-app/boot/reject.js vendored Normal file
View File

@ -0,0 +1,12 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var Promise = require('bluebird');
module.exports = function(app) {
if (process.rejectPromise) {
return Promise.reject(new Error('reject'));
}
};

View File

@ -0,0 +1,17 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
process.bootFlags.push('thenableLoaded');
module.exports = function(app) {
process.bootFlags.push('thenableStarted');
return {
then: function(onFulfill, onReject) {
process.nextTick(function() {
process.bootFlags.push('thenableFinished');
onFulfill();
});
},
};
};

10
test/fixtures/simple-app/boot/throw.js vendored Normal file
View File

@ -0,0 +1,10 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = function(app) {
if (process.throwError) {
throw new Error('throw');
}
};