diff --git a/index.js b/index.js index f021d4d4..7539273d 100755 --- a/index.js +++ b/index.js @@ -1,14 +1,34 @@ #!/usr/bin/env node const { inspect } = require('util') -const files = require('./src/files') +const config = require('./src/files') +const analyse = require('./src/analyse') +const cli = require('./src/reporters/cli') const reporter = require('./src/reporter') const build = require('./src/build') -reporter(files) +const report = analyse(config) +cli.report(report) +// broke this +// reporter(report.files) process.on('unhandledRejection', function(reason) { console.log('Unhandled Promise') console.log(inspect(reason)) build.error() }) + +/* + This is the ideal structure to get to: + + - utilities function + - pipe results down + + start() + .then(getConfig) + .then(attachFiles) + .then(attachSize) + .then(attachComparison) + .then(reportOnCli) + .then(reportOnBuild) +*/ diff --git a/package.json b/package.json index 08ad538a..15518edf 100644 --- a/package.json +++ b/package.json @@ -42,10 +42,13 @@ "ci-env": "^1.4.0", "commander": "^2.20.0", "cosmiconfig": "^5.2.1", + "figures": "^3.0.0", "github-build": "^1.2.0", "glob": "^7.1.4", "gzip-size": "^4.0.0", - "prettycli": "^1.4.3" + "plur": "^3.1.1", + "prettycli": "^1.4.3", + "right-pad": "^1.0.1" }, "lint-staged": { "*.js": [ diff --git a/src/analyse.js b/src/analyse.js new file mode 100644 index 00000000..387b04e3 --- /dev/null +++ b/src/analyse.js @@ -0,0 +1,18 @@ +/* + loop through files and add + pass: true|false +*/ + +const bytes = require('bytes') + +function analyse(config) { + return config.files.map(function(row) { + row.filesMatched.map(function(file) { + if (bytes.parse(file.size) > bytes.parse(row.maxSize)) file.pass = false + else file.pass = true + }) + return row + }) +} + +module.exports = analyse diff --git a/src/config.js b/src/config.js index c1405a1b..2af1e13f 100644 --- a/src/config.js +++ b/src/config.js @@ -76,10 +76,10 @@ if (!configFromFile && !configFromCli) { ) } -const config = configFromCli || configFromFile +const files = configFromCli || configFromFile debug('cli config', configFromCli) debug('file config', configFromFile) -debug('selected config', config) +debug('selected config', files) -module.exports = config +module.exports = { files } diff --git a/src/files.js b/src/files.js index 586231fc..76f42464 100644 --- a/src/files.js +++ b/src/files.js @@ -5,24 +5,25 @@ const { error } = require('prettycli') const config = require('./config') const debug = require('./debug') const compressedSize = require('./compressed-size') -const files = [] -config.map(file => { - const paths = glob.sync(file.path) - if (!paths.length) { - error(`There is no matching file for ${file.path} in ${process.cwd()}`, { +config.files.map(row => { + row.filesMatched = [] + + const files = glob.sync(row.path) + + files.map(path => { + const compression = row.compression || 'gzip' + const size = compressedSize(fs.readFileSync(path, 'utf8'), compression) + row.filesMatched.push({ path, size }) + }) + + if (!row.filesMatched.length) { + error(`There is no matching file for ${row.path} in ${process.cwd()}`, { silent: true }) - } else { - paths.map(path => { - const maxSize = bytes(file.maxSize) || Infinity - const compression = file.compression || 'gzip' - const size = compressedSize(fs.readFileSync(path, 'utf8'), compression) - files.push({ maxSize, path, size, compression }) - }) } }) -debug('files', files) +debug('files', config) -module.exports = files +module.exports = config diff --git a/src/reporter.js b/src/reporter.js index 816c1a3a..90f95012 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -53,9 +53,13 @@ const getGlobalMessage = ({ const prettyChange = change === 0 ? 'no change' - : change > 0 ? `+${bytes(change)}` : `-${bytes(Math.abs(change))}` + : change > 0 + ? `+${bytes(change)}` + : `-${bytes(Math.abs(change))}` - globalMessage = `${failures} out of ${results.length} bundles are too big! (${prettyChange})` + globalMessage = `${failures} out of ${ + results.length + } bundles are too big! (${prettyChange})` } else { // multiple files, no failures const prettySize = bytes(totalSize) @@ -64,7 +68,9 @@ const getGlobalMessage = ({ const prettyChange = change === 0 ? 'no change' - : change > 0 ? `+${bytes(change)}` : `-${bytes(Math.abs(change))}` + : change > 0 + ? `+${bytes(change)}` + : `-${bytes(Math.abs(change))}` globalMessage = `Total bundle size is ${prettySize}/${prettyMaxSize} (${prettyChange})` } @@ -127,7 +133,7 @@ const analyse = ({ files, masterValues }) => { }) } -const report = ({ files, globalMessage, fail }) => { +const globalReport = ({ files, globalMessage, fail }) => { /* prepare the build page */ const params = encodeURIComponent( JSON.stringify({ files, repo, branch, commit_message, sha }) @@ -160,7 +166,7 @@ const compare = (files, masterValues = {}) => { }) let fail = results.filter(result => result.fail).length > 0 - report({ files, globalMessage, fail }) + globalReport({ files, globalMessage, fail }) } const reporter = files => { diff --git a/src/reporters/cli.js b/src/reporters/cli.js new file mode 100644 index 00000000..59fbf786 --- /dev/null +++ b/src/reporters/cli.js @@ -0,0 +1,85 @@ +const rightpad = require('right-pad') +const figures = require('figures') +const bytes = require('bytes') +const plur = require('plur') + +const colors = require('../utils/colors') + +function report(results) { + const maxFileLength = getMaxFileLenght(results) + + const counter = { pass: 0, fail: 0 } + + results.forEach(function(row) { + printBlockHeader(row) + + row.filesMatched.forEach(function(file) { + printRow(file, row, maxFileLength) + + if (file.pass) counter.pass++ + else counter.fail++ + }) + }) + + printSummary(counter) + + // exit with error code 1 if there are any failed checks + if (counter.fail) process.exit(1) +} + +module.exports = { report } + +function getMaxFileLenght(results) { + let maxFileLength = 0 + + results.forEach(function(row) { + row.filesMatched.forEach(function(file) { + if (file.path.length > maxFileLength) maxFileLength = file.path.length + }) + }) + + return maxFileLength +} + +function printBlockHeader(row) { + console.log() + console.log(colors.subtle(`${figures.line} ${row.path}`)) +} + +function printRow(file, row, maxFileLength) { + const symbol = getSymbol(file) + const operator = getOperator(file, row) + + console.log( + ' ', + symbol, + rightpad(file.path, maxFileLength), + ' ', + bytes(file.size), + operator, + row.maxSize, + colors.subtle(row.compression || 'gzip') + ) +} + +function printSummary({ pass, fail }) { + console.log() + + if (pass) console.log(colors.pass(' ', pass, plur('check', pass), 'passed')) + if (fail) console.log(colors.fail(' ', fail, plur('check', fail), 'failed')) + + console.log() +} + +function getSymbol(file) { + return file.pass ? colors.pass(figures.tick) : colors.fail(figures.cross) +} + +function getOperator(file, row) { + const fileSize = bytes.parse(file.size) + const maxSize = bytes.parse(row.maxSize) + + if (fileSize > maxSize) return colors.fail('>') + if (fileSize === maxSize) return colors.pass('=') + return colors.pass('<') +} diff --git a/src/utils/colors.js b/src/utils/colors.js new file mode 100644 index 00000000..026c6b18 --- /dev/null +++ b/src/utils/colors.js @@ -0,0 +1,9 @@ +const chalk = require('chalk') + +module.exports = { + subtle: chalk.gray, + pass: chalk.green, + fail: chalk.red, + title: chalk.bold, + info: chalk.magenta +} diff --git a/tests/snapshots/index.js.md b/tests/snapshots/index.js.md index a1cd5a60..2c999bbc 100644 --- a/tests/snapshots/index.js.md +++ b/tests/snapshots/index.js.md @@ -8,68 +8,102 @@ Generated by [AVA](https://ava.li). > Snapshot 1 - 'PASS file-1.js: 270B < maxSize 300B (gzip)' + `─ file-1.js␊ + ✔ file-1.js 270B < 300B gzip␊ + ␊ + 1 check passed` ## 10. pass: match by fuzzy name > Snapshot 1 - `PASS build/vendor-ha5h.js: 270B < maxSize 350B (gzip) ␊ + `─ build/vendor-*.js␊ + ✔ build/vendor-ha5h.js 270B < 350B gzip␊ + ␊ + ─ build/**/chunk-*.js␊ + ✔ build/chunks/chunk-ch0nk.js 270B < 300B gzip␊ ␊ - PASS build/chunks/chunk-ch0nk.js: 270B < maxSize 300B (gzip)` + 2 checks passed` ## 2. fail: single file larger than limit > Snapshot 1 - 'FAIL file-2.js: 270B > maxSize 250B (gzip)' + `─ file-2.js␊ + ✖ file-2.js 270B > 250B gzip␊ + ␊ + 1 check failed` ## 3. pass: use brotli > Snapshot 1 - 'PASS file-3.js: 245B < maxSize 250B (brotli)' + `─ file-3.js␊ + ✔ file-3.js 245B < 250B brotli␊ + ␊ + 1 check passed` ## 4. fail: dont use compression > Snapshot 1 - 'FAIL file-4.js: 437B > maxSize 300B (no compression)' + `─ file-4.js␊ + ✖ file-4.js 437B > 300B none␊ + ␊ + 1 check failed` ## 5. pass: custom config file > Snapshot 1 - 'PASS file-5.js: 270B < maxSize 300B (gzip)' + `─ file-5.js␊ + ✔ file-5.js 270B < 300B gzip␊ + ␊ + 1 check passed` ## 6. pass: multiple files, both smaller than limit > Snapshot 1 - `PASS file-61.js: 270B < maxSize 300B (gzip) ␊ + `─ file-61.js␊ + ✔ file-61.js 270B < 300B gzip␊ + ␊ + ─ file-62.js␊ + ✔ file-62.js 270B < 300B gzip␊ ␊ - PASS file-62.js: 270B < maxSize 300B (gzip)` + 2 checks passed` ## 7. fail: multiple files, both bigger than limit > Snapshot 1 - `FAIL file-61.js: 270B > maxSize 200B (gzip) ␊ + `─ file-61.js␊ + ✖ file-61.js 270B > 200B gzip␊ ␊ - FAIL file-62.js: 270B > maxSize 200B (gzip)` + ─ file-62.js␊ + ✖ file-62.js 270B > 200B gzip␊ + ␊ + 2 checks failed` ## 8. fail: multiple files, 1 smaller + 1 bigger than limit > Snapshot 1 - `PASS file-61.js: 270B < maxSize 300B (gzip) ␊ + `─ file-61.js␊ + ✔ file-61.js 270B < 300B gzip␊ + ␊ + ─ file-62.js␊ + ✖ file-62.js 270B > 200B gzip␊ ␊ - FAIL file-62.js: 270B > maxSize 200B (gzip)` + 1 check passed␊ + 1 check failed` ## 9. pass: catch all js files > Snapshot 1 - `PASS build/chunks/chunk-ch0nk.js: 270B < maxSize 300B (gzip) ␊ + `─ build/**/*.js␊ + ✔ build/chunks/chunk-ch0nk.js 270B < 300B gzip␊ + ✔ build/vendor-ha5h.js 270B < 300B gzip␊ ␊ - PASS build/vendor-ha5h.js: 270B < maxSize 300B (gzip)` + 2 checks passed` diff --git a/tests/snapshots/index.js.snap b/tests/snapshots/index.js.snap index ed124eb2..8b194e0d 100644 Binary files a/tests/snapshots/index.js.snap and b/tests/snapshots/index.js.snap differ