Skip to content

Commit 534bfd8

Browse files
Update CLI output format
1 parent 4d4030f commit 534bfd8

File tree

6 files changed

+115
-34
lines changed

6 files changed

+115
-34
lines changed

compress.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Context, ItemConfig, Compression } from './validation/Condition';
17+
import { Context, ItemConfig, Compression, OrderedCompressionMap, CompressionMap } from './validation/Condition';
1818
import { cpus } from 'os';
19-
import bytes from 'bytes';
2019
import { constants as brotliConstants, brotliCompress, gzip, ZlibOptions } from 'zlib';
2120
import { readFile } from './fs';
22-
import { LogError, LogPassingOutput, LogFailingOutput } from './log';
21+
import { LogError, LogReport } from './log';
2322

2423
const COMPRESSION_CONCURRENCY = cpus().length;
2524
const BROTLI_OPTIONS = {
@@ -32,26 +31,37 @@ const BROTLI_OPTIONS = {
3231
const GZIP_OPTIONS: ZlibOptions = {
3332
level: 9,
3433
};
34+
const reported: Map<ItemConfig['path'], CompressionMap> = new Map();
35+
36+
/**
37+
* Pre-populate the report map
38+
* @param configurations
39+
*/
40+
function initReport(configurations: Context['config']) {
41+
for (const config of configurations) {
42+
if (!reported.get(config.originalPath)) {
43+
reported.set(config.originalPath, new Map(OrderedCompressionMap));
44+
}
45+
}
46+
}
3547

3648
/**
3749
* Use the given configuration and actual size to report item filesize.
3850
* @param item Configuration for an Item
3951
* @param error Error from compressing an Item
4052
* @param size actual size for this comparison
4153
*/
42-
function report(item: ItemConfig, error: Error | null, size: number): boolean {
54+
function store(item: ItemConfig, error: Error | null, size: number): boolean {
4355
if (error !== null) {
4456
LogError(`Could not compress '${item.path}' with '${item.compression}'.`);
4557
return false;
4658
}
4759

48-
if (item.maxSize >= size) {
49-
LogPassingOutput(`${item.path} ${bytes(size)} <= ${bytes(item.maxSize)}`);
50-
return true;
60+
let compressionMap: CompressionMap | undefined;
61+
if ((compressionMap = reported.get(item.originalPath))) {
62+
compressionMap.set(item.compression, [size, item.maxSize]);
5163
}
52-
53-
LogFailingOutput(`${item.path} ${bytes(size)} > ${bytes(item.maxSize)}`);
54-
return false;
64+
return size < item.maxSize;
5565
}
5666

5767
/**
@@ -66,15 +76,15 @@ async function compressor(item: ItemConfig): Promise<boolean> {
6676
switch (item.compression) {
6777
case Compression.BROTLI:
6878
return new Promise(resolve =>
69-
brotliCompress(buffer, BROTLI_OPTIONS, (error: Error | null, result: Buffer) => resolve(report(item, error, result.byteLength))),
79+
brotliCompress(buffer, BROTLI_OPTIONS, (error: Error | null, result: Buffer) => resolve(store(item, error, result.byteLength))),
7080
);
7181
case Compression.GZIP:
7282
return new Promise(resolve =>
73-
gzip(buffer, GZIP_OPTIONS, (error: Error | null, result: Buffer) => resolve(report(item, error, result.byteLength))),
83+
gzip(buffer, GZIP_OPTIONS, (error: Error | null, result: Buffer) => resolve(store(item, error, result.byteLength))),
7484
);
7585
case Compression.NONE:
7686
default:
77-
return report(item, null, buffer.byteLength);
87+
return store(item, null, buffer.byteLength);
7888
}
7989
}
8090

@@ -86,6 +96,8 @@ async function compressor(item: ItemConfig): Promise<boolean> {
8696
* @param context Finalized Valid Context from Configuration
8797
*/
8898
export default async function compress(context: Context): Promise<boolean> {
99+
initReport(context.config);
100+
89101
let success: boolean = true;
90102
for (let iterator: number = 0; iterator < context.config.length; iterator += COMPRESSION_CONCURRENCY) {
91103
let itemsSuccessful = await Promise.all(context.config.slice(iterator, iterator + COMPRESSION_CONCURRENCY).map(compressor));
@@ -94,5 +106,6 @@ export default async function compress(context: Context): Promise<boolean> {
94106
}
95107
}
96108

109+
LogReport(reported);
97110
return success;
98111
}

log.ts

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@
1515
*/
1616

1717
const kleur = require('kleur');
18+
import bytes from 'bytes';
19+
import { ItemConfig, CompressionMap } from './validation/Condition';
1820

1921
// Disable output colors for test runs.
2022
kleur.enabled = !('AVA_PATH' in process.env);
2123

24+
// Aliases to colors used.
25+
const { red, grey, green, bold } = kleur;
26+
2227
/**
2328
* Format output as an error message.
2429
* @param output
@@ -36,17 +41,56 @@ export function LogError(output: string): void {
3641
}
3742

3843
/**
39-
* Display output as a success message on the console.
40-
* @param output
44+
* Format size into more human readable string.
45+
* @param size
4146
*/
42-
export function LogPassingOutput(output: string): void {
43-
console.log(`${kleur.green('success')} ${kleur.white(output)}`);
47+
function prettyBytes(size: number): string {
48+
return bytes(size, { unit: 'kb', fixedDecimals: true, unitSeparator: ' ' });
4449
}
4550

4651
/**
47-
* Display output as a failure message on the console.
48-
* @param output
52+
* Given a compression type, format it to a human readable file extension.
53+
* @param compression
54+
*/
55+
function compressedExtension(compression: string): string {
56+
if (compression === 'none') {
57+
return ''.padEnd(3);
58+
}
59+
return '.' + compression.substring(0, 2);
60+
}
61+
62+
/**
63+
* Display report to the console.
64+
* @param report
4965
*/
50-
export function LogFailingOutput(output: string): void {
51-
console.log(`${kleur.red('failure')} ${kleur.white(output)}`);
66+
export function LogReport(report: Map<ItemConfig['path'], CompressionMap>) {
67+
let success: number = 0;
68+
let failure: number = 0;
69+
70+
if ([...report.keys()].length > 0) {
71+
console.log(bold('\nFilesize Report'));
72+
for (const [originalPath, values] of report) {
73+
console.log(grey(`\npath: ${originalPath}`));
74+
for (const [compression, compressionResults] of values) {
75+
const [size, maxSize] = compressionResults;
76+
const compressedPath = originalPath + grey(compressedExtension(compression));
77+
if (size === null || maxSize === null) {
78+
continue;
79+
} else if (size < maxSize) {
80+
success++;
81+
console.log(` ✔️ ${compressedPath} ${prettyBytes(size)} ${green('<')} ${prettyBytes(maxSize)}`);
82+
} else {
83+
failure++;
84+
console.log(` ❌ ${compressedPath} ${prettyBytes(size)} ${red('>')} ${prettyBytes(maxSize)}`);
85+
}
86+
}
87+
}
88+
if (success > 0 || failure > 0) {
89+
console.log('\n ' + green(success + ` ${success === 1 ? 'check' : 'checks'} passed`));
90+
console.log(' ' + red(failure + ` ${failure === 1 ? 'check' : 'checks'} failed`) + (failure === 0 ? ' 🎉' : ''));
91+
}
92+
console.log();
93+
} else {
94+
MakeError('No report available.');
95+
}
5296
}

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"mri": "1.1.4"
3131
},
3232
"devDependencies": {
33+
"@ampproject/rollup-plugin-closure-compiler": "0.20.0",
3334
"@rollup/plugin-commonjs": "11.0.1",
3435
"@rollup/plugin-node-resolve": "7.0.0",
3536
"@rollup/plugin-typescript": "2.1.0",
@@ -57,17 +58,22 @@
5758
{
5859
"path": "./dist/filesize",
5960
"compression": "brotli",
60-
"maxSize": "4.5 kB"
61+
"maxSize": "3.5 kB"
6162
},
6263
{
6364
"path": "./dist/filesize",
6465
"compression": "gzip",
65-
"maxSize": "6 kB"
66+
"maxSize": "3.6 kB"
6667
},
6768
{
6869
"path": "./dist/filesize",
6970
"compression": "none",
7071
"maxSize": "20 kB"
72+
},
73+
{
74+
"path": "./dist/index.js",
75+
"compression": "brotli",
76+
"maxSize": "4 kB"
7177
}
7278
],
7379
"ava": {

rollup.config.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import resolve from '@rollup/plugin-node-resolve';
1818
import typescript from '@rollup/plugin-typescript';
1919
import commonjs from '@rollup/plugin-commonjs';
20+
import compiler from '@ampproject/rollup-plugin-closure-compiler';
2021
import MagicString from 'magic-string';
2122

2223
function makeExecutable() {
@@ -32,6 +33,15 @@ function makeExecutable() {
3233
};
3334
}
3435

36+
const external = ['os', 'zlib', 'path', 'fs'];
37+
const plugins = executable => [
38+
resolve({ preferBuiltins: true }),
39+
commonjs({ include: 'node_modules/**' }),
40+
typescript({ include: '**/*.ts' }),
41+
compiler(),
42+
executable ? makeExecutable() : null,
43+
];
44+
3545
export default [
3646
{
3747
input: 'index.ts',
@@ -40,8 +50,8 @@ export default [
4050
format: 'cjs',
4151
sourcemap: true,
4252
},
43-
external: ['os', 'zlib', 'path', 'fs'],
44-
plugins: [resolve({ preferBuiltins: true }), commonjs({ include: 'node_modules/**' }), typescript({ include: '**/*.ts' }), makeExecutable()],
53+
external,
54+
plugins: plugins(true),
4555
},
4656
{
4757
input: 'index.ts',
@@ -50,7 +60,7 @@ export default [
5060
format: 'cjs',
5161
sourcemap: true,
5262
},
53-
external: ['os', 'zlib', 'path', 'fs'],
54-
plugins: [resolve({ preferBuiltins: true }), commonjs({ include: 'node_modules/**' }), typescript({ include: '**/*.ts' })],
63+
external,
64+
plugins: plugins(false),
5565
},
5666
];

validation/Condition.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@
1717
export type ValidationResponse = [boolean, string | null];
1818

1919
export enum Compression {
20-
NONE = '',
20+
NONE = 'none',
2121
GZIP = 'gzip',
2222
BROTLI = 'brotli',
2323
}
24+
const OrderedCompressionValues = [Compression.BROTLI, Compression.GZIP, Compression.NONE];
25+
26+
export type CompressionMapValue = [number | null, number | null];
27+
export type CompressionMap = Map<string, CompressionMapValue>;
28+
export const OrderedCompressionMap: CompressionMap = new Map(OrderedCompressionValues.map(value => [value, [null, null]]));
29+
export const CompressionDisplayLength = OrderedCompressionValues.sort((a, b) => b.length - a.length)[0].length;
2430

2531
export interface ItemConfig {
32+
originalPath: string;
2633
path: string;
2734
compression: string;
2835
maxSize: number;

validation/File.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const READABLE_KEY_NAMES = ['path', 'compression', 'maxSize'];
2424

2525
/**
2626
* Format input string to a known Compression Enum Value.
27-
* @param fsValue
27+
* @param fsValue
2828
*/
2929
function compressionValue(fsValue: string): { compression: Compression; error: string | null } {
3030
switch (fsValue.toLowerCase()) {
@@ -42,7 +42,7 @@ function compressionValue(fsValue: string): { compression: Compression; error: s
4242

4343
/**
4444
* Ensure a File config entry contains all the necessary keys.
45-
* @param bundle
45+
* @param bundle
4646
*/
4747
export function FileConfigContainsKeys(fileConfig: { [key: string]: string }): { success: boolean; invalid: number | null } {
4848
const mandatoryValues = READABLE_KEY_NAMES.map(key => fileConfig[key]);
@@ -53,8 +53,8 @@ export function FileConfigContainsKeys(fileConfig: { [key: string]: string }): {
5353

5454
/**
5555
* Validate a File config contains necessary keys and valid values.
56-
* @param fileConfig
57-
* @param index
56+
* @param fileConfig
57+
* @param index
5858
*/
5959
export default async function ValidateFileConfig(
6060
fileConfig: { path: string; compression: string; maxSize: string },
@@ -71,13 +71,14 @@ export default async function ValidateFileConfig(
7171
return {
7272
success: false,
7373
config: null,
74-
error:
75-
MakeError(`${fileConfig.path ? `'${fileConfig.path}'` : `#${index}`} configuration is invalid. ${valueErrorMapping[invalidValue]}`), };
74+
error: MakeError(`${fileConfig.path ? `'${fileConfig.path}'` : `#${index}`} configuration is invalid. ${valueErrorMapping[invalidValue]}`),
75+
};
7676
}
7777

7878
return {
7979
success: true,
8080
config: {
81+
originalPath: fileConfig.path,
8182
path,
8283
compression,
8384
maxSize,

0 commit comments

Comments
 (0)