diff --git a/index.js b/index.js index 0a9f397..3d3368f 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,12 @@ class Assets { this.config = Object.assign({}, { auto: false, + + // TODO: how to configure this level? + // Mode of processing targets: + // - 'parallel': targets are processed in parallel + // - 'sequence': targets are processed one by one + mode: 'parallel', targets: [], }, config); @@ -66,17 +72,68 @@ class Assets { } } + /** + * Creates promise that resolves then all promises returned by + * promiseFactories resolve. + * + * @param {Array.>} promiseFactories + * @return {Promise>} + */ + promiseParallel(promiseFactories) { + return Promise.all( + promiseFactories.map(factory => Promise.resolve(factory())) + ); + } + + /** + * Creates promise that resolves when a promise returned by last elelement of + * `promiseFactories` gets settled. Each element of promiseFactories is called + * only after promise returned by previous settled. + * + * @param {Array.>} promiseFactories + * @return {Promise>} + */ + promiseSequence(promiseFactories) { + const sequence = Promise.resolve(); + + return promiseFactories.reduce((sequence, promiseFactory) => { + const next = () => promiseFactory(); + + return sequence + .then(next) + .catch(error => { + this.log(error); + + return next(); + }); + }, sequence); + } + + /** + * @param {string} mode + */ + getModeMethodName(mode) { + return mode === 'sequence' ? + 'promiseSequence' : + 'promiseParallel'; + } + deployS3() { let assetSets = this.config.targets; - // glob - return new Promise(resolve => { - assetSets.forEach(assets => { - const bucket = assets.bucket; - const prefix = assets.prefix || ''; - assets.files.forEach(opt => { - this.log(`Bucket: ${bucket}:${prefix}`); + // Note: this.config.mode can't be expressed in config + // It would mean: process targets/assetSets in parallel or sequentially. + return this[this.getModeMethodName(this.config.mode)](assetSets.map(assets => { + const bucket = assets.bucket; + const prefix = assets.prefix || ''; + return () => { + this.log(`Bucket: ${bucket}:${prefix}`); + + // Note: assets.mode can be expressed in config by passing `mode` key + // next to `bucket`. + // It will mean: process file groups in parallel or sequentially + return this[this.getModeMethodName(assets.mode)](assets.files.map(opt => { if(this.options.bucket && this.options.bucket !== bucket) { this.log('Skipping'); return; @@ -85,8 +142,11 @@ class Assets { this.log(`Path: ${opt.source}`); const cfg = Object.assign({}, globOpts, {cwd: opt.source}); - glob.sync(opt.globs, cfg).forEach(filename => { + // Note: opt.mode can be expressed in config by passing `mode` key + // next to `source`/`globs`. + // It will mean: process files in group in parallel or sequentially + return () => this[this.getModeMethodName(opt.mode)](glob.sync(opt.globs, cfg).map(filename => { const body = fs.readFileSync(path.join(opt.source, filename)); const type = mime.lookup(filename) || opt.defaultContentType || 'application/octet-stream'; @@ -100,12 +160,16 @@ class Assets { ContentType: type }, opt.headers || {}); - this.provider.request('S3', 'putObject', details, this.options.stage, this.options.region); - }); - }); - }); - resolve(); - }); + return () => this + .provider + .request('S3', 'putObject', details, this.options.stage, this.options.region) + .then(() => { + this.log(`\tDONE: ${ filename } (${type})`); + }); + }, [])); + }, [])); + }; + }, [])); } }