You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

tmi

Package Overview
Dependencies
Maintainers
2
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tmi - npm Package Compare versions

Comparing version
0.2.2
to
1.0.0
+43
cli.js
#!/usr/bin/env node
'use strict';
var meow = require('meow');
var updateNotifier = require('update-notifier');
var psi = require('psi');
var tmi = require('./');
var cli = meow({
help: [
'Usage',
' tmi <url> [options]',
'',
'Example',
' tmi todomvc.com --strategy=desktop',
'',
'Options',
' --verbose Detailed summary.',
' --key Google API Key. By default the free tier is used.',
' --strategy Strategy to use when analyzing the page: mobile|desktop',
' --locale Locale results should be generated in.',
' --threshold Threshold score to pass the PageSpeed test.'
].join('\n')
});
updateNotifier({pkg: cli.pkg}).notify();
if (!cli.input[0]) {
console.error('Please supply an URL');
process.exit(1);
}
psi(cli.input[0], cli.flags, function (err, res) {
if (err) {
if (err.noStack) {
console.error(err.message);
process.exit(1);
} else {
throw err;
}
}
tmi().process(cli.flags, res);
});
+195
-27

@@ -1,35 +0,203 @@

/*
* tmi (based on psi)
* http://github.com/addyosmani/tmi
*
* Copyright (c) 2014 Google Inc.
* Licensed under an Apache 2 license.
*/
'use strict';
var pagespeed = require('gpagespeed');
var prependHttp = require('prepend-http');
var output = require('./output');
var fs = require('fs');
var path = require('path');
var prettyBytes = require('pretty-bytes');
var chalk = require('chalk');
var logSymbols = require('log-symbols');
module.exports = function (opts, cb) {
opts = opts || {};
cb = cb || function () {
var THRESHOLD = 70;
function Output() {
if (!(this instanceof Output)) {
return new Output();
}
/**
* What indices from BigQuery do we want to summarize scores
* for in the CLI output?
* @type {number[]}
*/
this.indicesOfInterest = [3, 6, 9]; // 25th, 50th, 75th percentile
/**
* Is the weight higher than one of the BigQuery
* percentiles?
* @type {boolean}
*/
this.fasterThanAPercentile = false;
this.bigQueryData = {
desktop: [],
mobile: [],
titles: []
};
opts.strategy = opts.strategy || 'desktop';
opts.nokey = opts.key === undefined;
opts.verbose = opts.verbose || false;
opts.url = prependHttp(opts.url);
var Output = new output();
pagespeed(opts, function (err, data) {
if (err) {
cb(err);
return;
/**
* A subset of BigQuery data that we actually use for output
* This should represent the indices of interest from the
* bigQueryData.
* @type {{desktop: Array, mobile: Array, titles: Array}}
*/
this.outputData = {
desktop: [],
mobile: [],
titles: []
};
/**
* Average image weights per site, based on BigQuery data
* @type {{desktop: number, mobile: number}}
*/
this.medians = {};
var data = fs.readFileSync(path.join(__dirname, 'data', 'bigquery.csv'), 'utf8').split('\n');
// Complete full set of available BigQuery Data
this.bigQueryData.titles = data[0].split(',');
this.bigQueryData.desktop = data[1].split(',').slice(1).map(function (el) {
return parseInt(el, 10);
});
this.bigQueryData.mobile = data[2].split(',').slice(1).map(function (el) {
return parseInt(el, 10);
});
// Sub-slice portions of data we're interested
this.indicesOfInterest.forEach(function (item) {
this.outputData.desktop.push(this.bigQueryData.desktop[item]);
this.outputData.mobile.push(this.bigQueryData.mobile[item]);
this.outputData.titles.push(this.bigQueryData.titles[item]);
}, this);
this.medians.mobile = parseInt(this.bigQueryData.mobile[1], 10);
this.medians.desktop = parseInt(this.bigQueryData.desktop[1], 10);
}
/**
* Compare a supplied image weight with the weight in a supplied percentile.
* @param siteImageWeight
* @param percentileImageWeight
* @param percentile
* @returns {string} Summary message of comparisons to the percentile
*/
Output.prototype.compareWeightPercentile = function (siteImageWeight, percentileImageWeight, percentile) {
if (siteImageWeight === undefined) {
return;
}
var diff = (siteImageWeight - parseInt(percentileImageWeight, 10)) * 1000;
if (diff > 0) {
diff = chalk.red('+' + prettyBytes(diff));
} else {
diff = diff * -1;
diff = chalk.green('-' + prettyBytes(diff));
this.fasterThanAPercentile = true;
}
return diff + (' compared to sites in the ') + chalk.yellow(percentile.replace('p', '') + 'th') + ' percentile';
};
Output.prototype.compareWeights = function (siteImageWeight, sizes, percentiles) {
if (siteImageWeight === undefined) {
return;
}
var comparisons = '';
siteImageWeight = parseInt(siteImageWeight, 10);
for (var i = 0; i < percentiles.length; i++) {
comparisons += this.compareWeightPercentile(siteImageWeight, sizes[i], percentiles[i]) + '\n';
}
return comparisons;
};
/**
* Check if image weight is higher than one of the available percentile sizes
* @param sizeImageWeight
* @param sizes
* @param percentiles
* @returns {*}
*/
Output.prototype.getHighestPercentile = function (sizeImageWeight, sizes, percentiles) {
if (sizeImageWeight === undefined) {
return;
}
var highestPercentileMatch = -1;
var result;
// Begin with index 2 to avoid catching unnecessary labels
// like `desktop` and `mobile` included in this row of data
for (var i = 2; i < percentiles.length; i++) {
sizes[i] = parseInt(sizes[i], 10);
if (sizeImageWeight > sizes[i]) {
highestPercentileMatch = i;
}
}
var response = data;
Output.process(opts, response, function (processErr) {
cb(processErr || err, response);
if (highestPercentileMatch === -1) {
result = '0';
} else {
result = percentiles[highestPercentileMatch];
}
return result;
};
Output.prototype.process = function (opts, res) {
var threshold = opts.threshold || THRESHOLD;
var yourImageWeight = parseInt(res.pageStats.imageResponseBytes || 0, 10);
var unoptimizedImages = res.formattedResults.ruleResults.OptimizeImages.urlBlocks;
var desktopWeights = this.compareWeights(yourImageWeight / 1000, this.outputData.desktop, this.outputData.titles);
var mobileWeights = this.compareWeights(yourImageWeight / 1000, this.outputData.mobile, this.outputData.titles);
var imagesToOptimize = '';
if (opts.verbose && unoptimizedImages[1] !== undefined) {
unoptimizedImages[1].urls.forEach(function (url) {
url.result.args.forEach(function (x) {
var result = '';
switch (x.type) {
case 'URL':
result += chalk.green(x.value);
break;
case 'BYTES':
result += 'Size: ' + chalk.red(x.value);
break;
case 'PERCENTAGE':
result += 'Can be improved by ' + chalk.yellow(x.value) + '\n';
break;
}
imagesToOptimize += result + '\n';
});
});
});
}
console.log([
chalk.cyan('\nYour image weight'), prettyBytes(yourImageWeight),
chalk.gray('Median mobile site image weight'), prettyBytes(this.medians.mobile * 1000),
chalk.gray('Median desktop site image weight'), prettyBytes(this.medians.desktop * 1000),
chalk.cyan('\nOn Mobile'),
mobileWeights,
chalk.cyan('On Desktop'),
desktopWeights
].join('\n'));
if (this.fasterThanAPercentile) {
console.log(chalk.cyan('Thanks for keeping the web fast <3'));
}
if (imagesToOptimize.length) {
console.log(chalk.underline('\nImages to optimize\n') + imagesToOptimize + chalk.cyan('This list does not include images which cannot be optimized further.\nYou may consider removing those images if possible.\n'));
}
if (res.score < threshold) {
console.error(chalk.bold(logSymbols.error + ' ' + 'Threshold of ' + threshold + ' not met with score of ' + res.score));
process.exit(1);
}
};
module.exports = Output;
+23
-21
{
"name": "tmi",
"version": "0.2.2",
"main": "index.js",
"version": "1.0.0",
"description": "Discover your image weight on the web",
"homepage": "http://github.com/addyosmani/tmi",
"bugs": "https://github.com/addyosmani/tmi/issues",
"author": {

@@ -14,3 +11,3 @@ "name": "Addy Osmani",

"bin": {
"tmi": "./bin/cli.js"
"tmi": "cli.js"
},

@@ -20,35 +17,40 @@ "engines": {

},
"repository": {
"type": "git",
"url": "https://github.com/addyosmani/tmi"
},
"license": "Apache-2",
"repository": "addyosmani/tmi",
"license": "Apache-2.0",
"scripts": {
"test": "mocha test"
"test": "mocha"
},
"files": [
"index.js",
"output.js",
"bin/cli.js",
"lib/utils.js",
"cli.js",
"data/bigquery.csv"
],
"keywords": [
"pagespeed",
"cli-app",
"cli",
"bin",
"image",
"images",
"weight",
"image-weight",
"size"
"size",
"pagespeed",
"insights",
"speed",
"page",
"website",
"measure",
"optimize"
],
"dependencies": {
"chalk": "^0.5.1",
"csv-parse": "0.0.6",
"gpagespeed": "0.0.8",
"minimist": "^1.1.0",
"log-symbols": "^1.0.1",
"meow": "^2.1.0",
"pretty-bytes": "^1.0.1",
"prepend-http": "^1.0.0"
"psi": "^1.0.3",
"update-notifier": "^0.3.0"
},
"devDependencies": {
"mocha": "^2.0.1"
"mocha": "*"
}
}
+31
-140

@@ -1,161 +0,46 @@

# tmi [![Dependency Status](https://david-dm.org/addyosmani/tmi.svg)](https://david-dm.org/addyosmani/tmi) [![devDependency Status](https://david-dm.org/addyosmani/tmi/dev-status.svg)](https://david-dm.org/addyosmani/tmi#info=devDependencies) [![Build Status](https://travis-ci.org/addyosmani/tmi.svg?branch=master)](https://travis-ci.org/addyosmani/tmi)
# tmi - too many images [![Build Status](https://travis-ci.org/addyosmani/tmi.svg?branch=master)](https://travis-ci.org/addyosmani/tmi)
> TMI (Too Many Images)
> Discover your image weight on the web
Discover your image weight on the web. Find out the image weight in your pages, compare to the BigQuery medians and discover what images you can optimize further.
![](screenshot.png)
Find out the image weight in your pages, compare to the BigQuery quantiles and discover what images you can optimize further.
## Install
```sh
$ npm install -g tmi
```
## Quick start
Summary:
```sh
$ tmi <url>
$ npm install --global tmi
```
Examples:
![](http://i.imgur.com/v9kMjQS.png)
![](http://i.imgur.com/CglUZ8N.png)
Switch between desktop and mobile strategies:
```sh
$ tmi <url> --strategy=mobile
```
![](http://i.imgur.com/DEI2wWG.png)
Detailed summary with URLs you can optimize:
```sh
$ tmi <url> --verbose
```
![](http://i.imgur.com/Z3K6kIN.png)
## Usage
This module is modelled on [psi](http://github.com/addyosmani/psi) and follows a very similar API.
When using this module for a production-level build process, registering for an API key from the [Google Developer Console](https://developers.google.com/speed/docs/insights/v1/getting_started#auth) is recommended.
```js
var tmi = require('tmi');
tmi({
// key: '...', optional
url: 'http://html5rocks.com',
paths: '', // optional
locale: 'en_GB', // optional
strategy: 'mobile', // optional
threshold: 80 // optional
});
```
$ tmi --help
Optionally, a callback is also available with access to the response:
Usage
tmi <url> [options]
```js
tmi(options, function (err, data) {
console.log(data.score);
console.log(data.pageStats);
});
```
Example
tmi todomvc.com --strategy=desktop
### Options
#### url
*Required*
Type: `string`
URL of the page for which the PageSpeed Insights API should generate results.
#### locale
Type: `string`
Default: `en_US`
The locale that results should be generated in (e.g 'en_GB').
#### strategy
Type: `string`
Default: `desktop`
The strategy to use when analyzing the page. Valid values are desktop and mobile.
#### threshold
Type: `number`
Default: `70`
Threshold score that is needed to pass the pagespeed test
#### paths
Type: `array`
An array of URL paths that are appended to the URL
#### key
Type: `string`
Default: `nokey`
[Google API Key](https://code.google.com/apis/console/)
Unless Specified defaults to use the free tier on PageSpeed Insights. Good for getting a feel for how well this tool works for you.
## CLI support
You will probably want to globally install tmi if using as a CLI. This can be done as follows:
```sh
$ npm install --global tmi
Options
--verbose Detailed summary.
--key Google API Key. By default the free tier is used.
--strategy Strategy to use when analyzing the page: mobile|desktop
--locale Locale results should be generated in.
--threshold Threshold score to pass the PageSpeed test.
```
You can then casually use it with or without your key:
```sh
$ tmi http://www.google.com
```
## Verbose mode
```sh
$ tmi http://www.google.com --key 'YOUR_KEY_GOES_HERE'
```
Verbose mode will show you a detailed summary of which images needs improving.
With or without http:// for URLs:
![](screenshot-verbose.png)
```sh
$ tmi chrome.com
```
Or ask for a more detailed report including image URLs that can be optimized:
## Good test URLs
```sh
$ tmi http://www.google.com --verbose
```
Similar to gpagespeed, the following optional flags are also supported:
```sh
$ tmi <url> --key=<key> --prettyprint=<true> --userIp=<userIp> --locale=<locale> --strategy=<desktop|mobile>
```
```sh
$ tmi http://www.html5rocks.com --strategy=mobile
```
### Good test URLs
* [LG Watch](http://www.lg.com/global/gwatch/index.html)

@@ -167,10 +52,16 @@ * [MySpace homepage](http://myspace.com)

## Local testing
We plan on adding support for testing localhost and local files in the
very near future. Until then, [ngrok](https://ngrok.com/) should be able
to help get you mostly there.
We plan on adding support for testing localhost and local files in the very near future. Until then, [ngrok](https://ngrok.com/) should be able to help get you mostly there.
## API
See [`psi`](https://github.com/addyosmani/psi) if you need a programmatic API for PageSpeed Insights.
## License
Apache 2.
Apache-2.0
Copyright 2015 Google Inc
#!/usr/bin/env node
'use strict';
// Adapted from cli.js in gpagespeed
var insights = require('../index')
, pkg = require('../package.json')
, query = process.argv[2]
, argv = require('minimist')((process.argv.slice(2)))
, opts = {}
;
function printHelp() {
console.log(pkg.description);
console.log('');
console.log('Usage:');
console.log(' $ tmi <url>');
console.log(' $ tmi <url> --key=<key>');
console.log(' $ tmi <url> --verbose');
console.log('');
console.log('Optional, supply other arguments.');
console.log('See https://developers.google.com/speed/docs/insights/v1/getting_started for description');
console.log(' $ tmi <url> --key=<key> --prettyprint=<true> --userIp=<userIp> --locale=<locale> --strategy=<desktop|mobile>');
}
if (!query || process.argv.indexOf('-h') !== -1 || process.argv.indexOf('--help') !== -1) {
printHelp();
return;
}
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--version') !== -1) {
console.log(pkg.version);
return;
}
opts.url = argv._[0];
if(!opts.url){
printHelp();
return;
}
if(argv.url){
opts.url = argv.url;
}
if(argv.key){
opts.key = argv.key;
}
if (process.argv.indexOf('-r') !== -1 || process.argv.indexOf('--verbose') !== -1) {
opts.verbose = true;
}
if(argv.callback){
opts.callback = argv.callback;
}
if(argv.prettyprint){
opts.prettyprint = argv.prettyprint;
}
if(argv.userIp){
opts.userIp = argv.userIp;
}
if(argv.locale){
opts.locale = argv.locale;
}
if(argv.strategy){
opts.strategy = argv.strategy;
}
insights(opts);
/*
* utils
*
* Collection of functions for report output
*/
'use strict';
var chalk = require('chalk');
var divider = '\n' + chalk.grey(new Array(65).join('-')) + '\n';
var buffer = function (msg, length) {
var buffer = '';
if (length === undefined) {
length = 50;
}
if (length - msg.length > 0) {
buffer = new Array(length - msg.length).join(' ');
}
return buffer;
};
var addSpacesToWords = function (msg) {
return msg.replace(/([A-Z]+)/g, ' $1').replace(/([A-Z][a-z])/g, '$1');
};
var firstToUpperCaseAndAddSpace = function (msg) {
msg = msg.replace('Bytes', '');
return msg.charAt(0).toUpperCase() + addSpacesToWords(msg.slice(1));
};
var scoreColor = function(score) {
var color = chalk.yellow;
color = score < 21 ? chalk.red : color;
color = score > 79 ? chalk.green : color;
return color;
};
var labelize = function(msg) {
var label = firstToUpperCaseAndAddSpace(msg);
return label + buffer(label) + chalk.grey('| ');
};
module.exports = {
scoreColor: scoreColor,
buffer: buffer,
divider: divider,
labelize: labelize,
firstToUpperCaseAndAddSpace: firstToUpperCaseAndAddSpace
};
'use strict';
var prettyBytes = require('pretty-bytes');
var chalk = require('chalk');
var utils = require('./lib/utils');
var csvparse = require('csv-parse');
var fs = require('fs');
var path = require('path');
function Output() {
if (!(this instanceof Output)) {
return new Output;
}
this.constants = {
YOUR_IMAGE_WEIGHT : '\nYour image weight: ',
MEDIAN_MOBILE_WEIGHT : 'Median mobile site image weight: ',
MEDIAN_DESKTOP_WEIGHT : 'Median desktop site image weight: ',
ON_MOBILE : '\nOn Mobile:',
ON_DESKTOP : '\nOn Desktop:',
MORE_BYTES_THAN : 'You have more image bytes than ',
IMAGES_TO_OPTIMISE : '\nImages to optimize:\n',
PERCENTAGE_OF : '% of sites'
};
/**
* PageSpeed Insights threshold
* @type {number}
*/
this.threshold = 70;
/**
* What indices from BigQuery do we want to summarize scores
* for in the CLI output?
* @type {number[]}
*/
this.indicesOfInterest = [3, 6, 9]; // 25th, 50th, 75th percentile
/**
* Include suggestions for URLs that could be improved score-wise
* This will not include a complete list of possible optimizations
* and relies on PageSpeed Insights API data
* @type {boolean}
*/
this.verbose = false;
/**
* Is the weight higher than one of the BigQuery
* percentiles?
* @type {boolean}
*/
this.fasterThanAPercentile = false;
this.bigQueryData = {
desktop: [], mobile: [], titles: []
};
/**
* A subset of BigQuery data that we actually use for output
* This should represent the indices of interest from the
* bigQueryData.
* @type {{desktop: Array, mobile: Array, titles: Array}}
*/
this.outputData = {
desktop: [], mobile: [], titles: []
};
/**
* Average image weights per site, based on BigQuery data
* @type {{desktop: number, mobile: number}}
*/
this.medians = {};
/**
* Message shown when image weight is lower than one of the percentiles
* we care about.
*/
this.keepingWebFast = chalk.cyan('Thanks for keeping the web fast <3');
var parser = csvparse({
delimiter: ';'
}, function (err, data) {
// TODO: Refactor all of this to be less boilerplate-y.
// Complete full set of available BigQuery Data
this.bigQueryData.titles = data[0][0].split(',');
this.bigQueryData.desktop = data[1][0].split(',');
this.bigQueryData.mobile = data[2][0].split(',');
// Sub-slice portions of data we're interested
this.indicesOfInterest.forEach(function (item) {
this.outputData.desktop.push(this.bigQueryData.desktop[item]);
this.outputData.mobile.push(this.bigQueryData.mobile[item]);
this.outputData.titles.push(this.bigQueryData.titles[item]);
}.bind(this));
this.medians.mobile = parseInt(this.bigQueryData.mobile[1], 10);
this.medians.desktop = parseInt(this.bigQueryData.desktop[1], 10);
}.bind(this));
fs.createReadStream(path.resolve(__dirname + '/data/bigquery.csv')).pipe(parser);
};
/**
* Compare a supplied image weight with the weight in a supplied percentile.
* @param siteImageWeight
* @param percentileImageWeight
* @param percentile
* @returns {string} Summary message of comparisons to the percentile
*/
Output.prototype.compareWeightPercentile = function (siteImageWeight, percentileImageWeight, percentile) {
if (siteImageWeight === undefined) {
return;
}
var diff = (siteImageWeight - parseInt(percentileImageWeight, 10)) * 1000;
if (diff > 0) {
diff = chalk.red('+' + prettyBytes(diff));
} else {
diff = diff * -1;
diff = chalk.green('-' + prettyBytes(diff));
this.fasterThanAPercentile = true;
}
return diff + (' compared to sites in the ') + chalk.yellow(percentile.replace('p', '') + 'th') + ' percentile';
};
Output.prototype.compareWeights = function (siteImageWeight, sizes, percentiles) {
if (siteImageWeight === undefined) {
return;
}
var comparisons = '';
siteImageWeight = parseInt(siteImageWeight, 10);
for (var i = 0; i < percentiles.length; i++) {
comparisons += this.compareWeightPercentile(siteImageWeight, sizes[i], percentiles[i]) + '\n';
}
return comparisons;
};
/**
* Check if image weight is higher than one of the available percentile sizes
* @param sizeImageWeight
* @param sizes
* @param percentiles
* @returns {*}
*/
Output.prototype.getHighestPercentile = function (sizeImageWeight, sizes, percentiles) {
if (sizeImageWeight === undefined) {
return;
}
var highestPercentileMatch = -1;
var result;
// Begin with index 2 to avoid catching unnecessary labels
// like `desktop` and `mobile` included in this row of data
for (var i = 2; i < percentiles.length; i++) {
sizes[i] = parseInt(sizes[i], 10);
if (sizeImageWeight > sizes[i]) {
highestPercentileMatch = i;
}
}
if (highestPercentileMatch === -1){
result = '0';
} else {
result = percentiles[highestPercentileMatch];
}
return result;
};
Output.prototype.process = function (parameters, response, done) {
done = done || function () {};
var logger = console.log;
var error = null;
this.threshold = parameters.threshold || this.threshold;
this.verbose = parameters.verbose;
var yourImageWeight = parseInt(response.pageStats.imageResponseBytes || 0, 10);
var unoptimizedImages = response.formattedResults.ruleResults.OptimizeImages.urlBlocks;
var desktopWeights = this.compareWeights(yourImageWeight / 1000, this.outputData.desktop, this.outputData.titles);
var mobileWeights = this.compareWeights(yourImageWeight / 1000, this.outputData.mobile, this.outputData.titles);
var highestPercentileDesktop = this.getHighestPercentile(yourImageWeight / 1000, this.bigQueryData.desktop, this.bigQueryData.titles);
var highestPercentileMobile = this.getHighestPercentile(yourImageWeight / 1000, this.bigQueryData.mobile, this.bigQueryData.titles);
var imagesToOptimize = '';
if (this.verbose) {
if (unoptimizedImages[1] !== undefined) {
unoptimizedImages[1].urls.forEach(function (url) {
url.result.args.forEach(function (x) {
var result = '';
switch (x.type) {
case 'URL':
result += chalk.green(x.value);
break;
case 'BYTES':
result += 'Size: ' + chalk.red(x.value);
break;
case 'PERCENTAGE':
result += 'Can be improved by ' + chalk.yellow(x.value) + '\n';
break;
}
imagesToOptimize += result + '\n';
});
});
}
}
logger([
chalk.cyan(this.constants.YOUR_IMAGE_WEIGHT) + prettyBytes(yourImageWeight),
chalk.gray(this.constants.MEDIAN_MOBILE_WEIGHT) + prettyBytes(this.medians.mobile * 1000),
chalk.gray(this.constants.MEDIAN_DESKTOP_WEIGHT) + prettyBytes(this.medians.desktop * 1000),
chalk.cyan(this.constants.ON_MOBILE),
// chalk.magenta(this.constants.MORE_BYTES_THAN + highestPercentileDesktop.replace('p', '') + this.constants.PERCENTAGE_OF),
mobileWeights,
chalk.cyan(this.constants.ON_DESKTOP),
// chalk.magenta(this.constants.MORE_BYTES_THAN + highestPercentileMobile.replace('p', '') + this.constants.PERCENTAGE_OF),
desktopWeights
].join('\n'));
if (this.fasterThanAPercentile) {
logger(this.keepingWebFast);
}
if (imagesToOptimize.length) {
logger(chalk.underline(this.constants.IMAGES_TO_OPTIMISE) + imagesToOptimize + chalk.cyan('\nThis list does not include images which cannot be optimized further.\nYou may consider removing those images if possible.'));
}
if (response.score < this.threshold) {
error = new Error('Threshold of ' + this.threshold + ' not met with score of ' + response.score);
}
return done(error);
};
module.exports = Output;