Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gzipper

Package Overview
Dependencies
Maintainers
1
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gzipper - npm Package Compare versions

Comparing version 2.2.0 to 2.3.0

test/resources/folder_to_compress/a/b/index_b.css

271

Gzipper.js
const zlib = require('zlib')
const fs = require('fs')
const path = require('path')
const util = require('util')

@@ -12,3 +13,11 @@ const Logger = require('./Logger')

const getCompressionType = Symbol('getCompressionType')
const createFolders = Symbol('createFolders')
const getBrotliOptionName = Symbol('getBrotliOptionName')
const statExists = Symbol('statExists')
const stat = util.promisify(fs.stat)
const lstat = util.promisify(fs.lstat)
const readdir = util.promisify(fs.readdir)
const mkdir = util.promisify(fs.mkdir)
/**

@@ -28,18 +37,17 @@ * Compressing files.

constructor(target, outputPath, options = {}) {
this.logger = new Logger(options.verbose)
if (!target) {
throw new Error(`Can't find a path.`)
const message = `Can't find a path.`
this.logger.error(message, true)
throw new Error(message)
}
this.options = options
this.logger = new Logger(this.options.verbose)
if (outputPath) {
this.outputPath = path.resolve(process.cwd(), outputPath)
if (!fs.existsSync(this.outputPath)) {
fs.mkdirSync(this.outputPath, { recursive: true })
}
}
this.target = path.resolve(process.cwd(), target)
const [compression, compressionOptions] = this[selectCompression]()
this.compression = compression
const [createCompression, compressionOptions] = this[selectCompression]()
this.createCompression = createCompression
this.compressionOptions = compressionOptions
this[compressionTypeLog]()
this.compressionType = this[getCompressionType]()
}

@@ -51,46 +59,47 @@

* @param {string} target path to target directory
* @param {number} [globalCount=0] global files count
* @param {number} [successGlobalCount=0] success files count
* @memberof Gzipper
*/
[compileFolderRecursively](target, pending = [], files = []) {
async [compileFolderRecursively](target) {
try {
const filesList = fs.readdirSync(target)
const compressedFiles = []
const filesList = await readdir(target)
filesList.forEach(file => {
for (const file of filesList) {
const filePath = path.resolve(target, file)
const isFile = (await lstat(filePath)).isFile()
const isDirectory = (await lstat(filePath)).isDirectory()
if (
fs.lstatSync(filePath).isFile() &&
(path.extname(filePath) === '.js' ||
path.extname(filePath) === '.css')
) {
files.push(file)
pending.push(file)
this[compressFile](
file,
target,
this.outputPath,
(beforeSize, afterSize) => {
pending.pop()
this.logger.info(
`File ${file} has been compressed ${beforeSize}Kb -> ${afterSize}Kb.`
if (isDirectory) {
compressedFiles.push(
...(await this[compileFolderRecursively](filePath))
)
} else if (isFile) {
try {
if (
path.extname(filePath) === '.js' ||
path.extname(filePath) === '.css'
) {
compressedFiles.push(filePath)
const fileInfo = await this[compressFile](
file,
target,
this.outputPath
)
if (!pending.length) {
this.logger.success(
`${files.length} ${
files.length > 1 ? 'files have' : 'file has'
} been compressed.`,
true
if (fileInfo) {
this.logger.info(
`File ${file} has been compressed ${
fileInfo.beforeSize
}Kb -> ${fileInfo.afterSize}Kb.`
)
}
}
)
} else if (fs.lstatSync(filePath).isDirectory()) {
this[compileFolderRecursively](filePath, pending, files)
} catch (error) {
throw error
}
}
})
} catch (err) {
this.logger.error(err, true)
}
return compressedFiles
} catch (error) {
throw error
}

@@ -105,10 +114,13 @@ }

* @param {string} outputDir path to output directory (default {target})
* @param {() => void} callback finish callback
* @returns {Promise<{ beforeSize: number, afterSize: number }>} finish promise
* @memberof Gzipper
*/
[compressFile](filename, target, outputDir, callback) {
const compressionType = this[getCompressionType]()
async [compressFile](filename, target, outputDir) {
const inputPath = path.join(target, filename)
const outputPath = `${path.join(outputDir || target, filename)}.${
compressionType.ext
if (outputDir) {
target = path.join(outputDir, path.relative(this.target, target))
await this[createFolders](target)
}
const outputPath = `${path.join(target, filename)}.${
this.compressionType.ext
}`

@@ -118,13 +130,21 @@ const input = fs.createReadStream(inputPath)

input.pipe(this.compression).pipe(output)
output.once('open', () => input.pipe(this.createCompression()).pipe(output))
if (callback) {
output.once('finish', () => {
callback(
fs.statSync(inputPath).size / 1024,
fs.statSync(outputPath).size / 1024
)
const compressPromise = new Promise((resolve, reject) => {
output.once('finish', async () => {
if (this.options.verbose) {
const beforeSize = (await stat(inputPath)).size / 1024
const afterSize = (await stat(outputPath)).size / 1024
resolve({ beforeSize, afterSize })
} else {
resolve()
}
})
output.once('error', error => this.logger.error(error, true))
}
output.once('error', error => {
this.logger.error(error, true)
reject(error)
})
})
return compressPromise
}

@@ -137,4 +157,29 @@

*/
compress() {
this[compileFolderRecursively](this.target)
async compress() {
let files
try {
if (this.outputPath) {
await this[createFolders](this.outputPath)
}
this[compressionTypeLog]()
files = await this[compileFolderRecursively](this.target)
} catch (error) {
this.logger.error(error, true)
throw new Error(error.message)
}
const filesCount = files.length
if (filesCount) {
this.logger.success(
`${filesCount} ${
filesCount > 1 ? 'files have' : 'file has'
} been compressed.`,
true
)
} else {
this.logger.warn(
`we couldn't find any appropriate files (.css, .js).`,
true
)
}
}

@@ -148,10 +193,18 @@

[compressionTypeLog]() {
let compressionType = this[getCompressionType](),
options = ''
let options = ''
for (const [key, value] of Object.entries(this.compressionOptions)) {
options += `${key}: ${value}, `
switch (this.compressionType.name) {
case 'BROTLI':
options += `${this[getBrotliOptionName](key)}: ${value}, `
break
default:
options += `${key}: ${value}, `
}
}
this.logger.warn(`${compressionType.name} -> ${options.slice(0, -2)}`, true)
this.logger.warn(
`${this.compressionType.name} -> ${options.slice(0, -2)}`,
true
)
}

@@ -162,3 +215,3 @@

*
* @returns {[object, object]} compression instance (Gzip or BrotliCompress) and options
* @returns {[() => object, object]} compression instance (Gzip or BrotliCompress) and options
* @memberof Gzipper

@@ -181,4 +234,2 @@ */

let compression = zlib.createGzip(options)
if (

@@ -188,5 +239,5 @@ this.options.brotli &&

) {
throw new Error(
`Can't use brotli compression, Node.js >= v11.7.0 required.`
)
const message = `Can't use brotli compression, Node.js >= v11.7.0 required.`
this.logger.error(message, true)
throw new Error(message)
}

@@ -232,9 +283,17 @@

}
}
compression = zlib.createBrotliCompress({
params: options,
})
const createCompression = () => {
let compression = zlib.createGzip(options)
if (this.options.brotli) {
compression = zlib.createBrotliCompress({
params: options,
})
}
return compression
}
return [compression, options]
return [createCompression, options]
}

@@ -253,3 +312,4 @@

[getCompressionType]() {
if (this.compression instanceof zlib.Gzip) {
const compression = this.createCompression()
if (compression instanceof zlib.Gzip) {
return {

@@ -259,3 +319,3 @@ name: 'GZIP',

}
} else if (this.compression instanceof zlib.BrotliCompress) {
} else if (compression instanceof zlib.BrotliCompress) {
return {

@@ -267,4 +327,71 @@ name: 'BROTLI',

}
/**
* Create folders by path.
*
* @todo when Node.js >= 8 support will be removed, rewrite this to mkdir(path, { recursive: true })
*
* @param {string} target where folders will be created
* @memberof Gzipper
*/
async [createFolders](target) {
const initDir = path.isAbsolute(target) ? path.sep : ''
await target.split(path.sep).reduce(async (parentDir, childDir) => {
parentDir = await parentDir
childDir = await childDir
const folderPath = path.resolve(parentDir, childDir)
if (!(await this[statExists](folderPath))) {
await mkdir(folderPath)
}
return folderPath
}, initDir)
}
/**
* Returns human-readable brotli option name.
*
* @param {number} index
* @returns {string}
* @memberof Gzipper
*/
[getBrotliOptionName](index) {
switch (+index) {
case zlib.constants.BROTLI_PARAM_MODE:
return 'brotliParamMode'
case zlib.constants.BROTLI_PARAM_QUALITY:
return 'brotliQuality'
case zlib.constants.BROTLI_PARAM_SIZE_HINT:
return 'brotliSizeHint'
default:
return 'unknown'
}
}
/**
* Returns if the file or folder exists.
*
* @param {string} target
* @returns {Promise<boolean>}
* @memberof Gzipper
*/
[statExists](target) {
return new Promise(async (resolve, reject) => {
try {
await stat(target)
resolve(true)
} catch (error) {
if (error && error.code === 'ENOENT') {
resolve(false)
} else {
reject(error)
}
}
})
}
}
module.exports = Gzipper

@@ -67,2 +67,3 @@ #!/usr/bin/env node

new Gzipper(target, outputPath, options).compress()
const gzipper = new Gzipper(target, outputPath, options)
gzipper.compress().catch(err => console.error(err))

@@ -27,18 +27,19 @@ const logger = Symbol('logger')

[logger](level) {
let colorfulMessage
let colorfulMessage,
prefix = 'gzipper: '
switch (level) {
case 'info':
colorfulMessage = `\x1b[36m%s\x1b[0m`
colorfulMessage = `\x1b[36m${prefix}%s\x1b[0m`
break
case 'error':
colorfulMessage = `\x1b[31m%s\x1b[0m`
colorfulMessage = `\x1b[31m${prefix}%s\x1b[0m`
break
case 'warning':
colorfulMessage = `\x1b[33m%s\x1b[0m`
colorfulMessage = `\x1b[33m${prefix}%s\x1b[0m`
break
case 'success':
colorfulMessage = `\x1b[32m%s\x1b[0m`
colorfulMessage = `\x1b[32m${prefix}%s\x1b[0m`
break

@@ -45,0 +46,0 @@ }

{
"name": "gzipper",
"version": "2.2.0",
"version": "2.3.0",
"description": "CLI for compressing files.",

@@ -39,2 +39,3 @@ "main": "index.js",

"@types/node": "^11.11.2",
"@types/sinon": "^7.0.10",
"eslint": "^5.15.1",

@@ -44,3 +45,4 @@ "eslint-config-prettier": "^4.1.0",

"mocha": "^6.0.2",
"prettier": "^1.16.4"
"prettier": "^1.16.4",
"sinon": "^7.2.7"
},

@@ -47,0 +49,0 @@ "engines": {

# Gzipper
[![Build Status](https://travis-ci.org/gios/gzipper.svg?branch=master)](https://travis-ci.org/gios/gzipper)
CLI for compressing files.
- [Gzipper](#gzipper)

@@ -6,0 +6,0 @@ - [Install](#install)

@@ -1,32 +0,238 @@

// const assert = require('assert')
// const fs = require('fs')
// const path = require('path')
const assert = require('assert')
const sinon = require('sinon')
const zlib = require('zlib')
const path = require('path')
// const Gzipper = require('../Gzipper')
const Gzipper = require('../Gzipper')
const {
EMPTY_FOLDER_PATH,
COMPRESS_PATH,
NO_FILES_COMPRESS_PATH,
COMPRESS_PATH_TARGET,
getFiles,
createFolder,
clear,
getPrivateSymbol,
} = require('./utils')
// const RESOURCES_PATH = path.resolve(__dirname, './resources')
const VERBOSE_REGEXP = /File [^\s]+ has been compressed [^\s]+Kb -> [^\s]+Kb./
const MESSAGE_REGEXP = /[^\s]+ files have been compressed./
// describe('Gzipper', () => {
// describe('verbose', () => {
// beforeEach(() => removeGzippedFiles())
describe('Gzipper', () => {
beforeEach(async () => {
await createFolder(EMPTY_FOLDER_PATH)
await createFolder(COMPRESS_PATH_TARGET)
await clear(COMPRESS_PATH, ['.gz', '.br'])
})
// it('should print logs to console about every file', () => {
// const options = { verbose: true }
// new Gzipper(RESOURCES_PATH, null, options).compress()
// this.timeout(100)
// assert.equal()
// })
// })
// })
it('should throw an error if no path found', () => {
try {
new Gzipper(null, null)
} catch (err) {
assert.ok(err instanceof Error)
assert.strictEqual(err.message, `Can't find a path.`)
}
})
// function removeGzippedFiles() {
// fs.readdir(RESOURCES_PATH, (err, files) => {
// if (err) throw err
it('should throw on compress error', async () => {
const gzipper = new Gzipper(COMPRESS_PATH, null)
const compileFolderRecursivelySpy = sinon.spy(
gzipper,
getPrivateSymbol(gzipper, 'compileFolderRecursively')
)
const errorSpy = sinon.spy(gzipper.logger, 'error')
sinon
.stub(gzipper, getPrivateSymbol(gzipper, 'compressFile'))
.rejects('UNKNOWN_ERROR', 'Compressing error.')
// for (const file of files) {
// fs.unlink(path.join(RESOURCES_PATH, file), err => {
// if (err) throw err
// })
// }
// })
// }
try {
await gzipper.compress()
} catch (err) {
assert.ok(err instanceof Error)
assert.strictEqual(err.message, 'Compressing error.')
assert.ok(compileFolderRecursivelySpy.calledWithExactly(COMPRESS_PATH))
assert.ok(
errorSpy.calledOnceWithExactly(sinon.match.instanceOf(Error), true)
)
}
})
it('should print message about appropriate files', async () => {
const gzipper = new Gzipper(NO_FILES_COMPRESS_PATH, null)
const noFilesWarnSpy = sinon.spy(gzipper.logger, 'warn')
await gzipper.compress()
const responseMessage = `we couldn't find any appropriate files (.css, .js).`
assert.ok(noFilesWarnSpy.calledWithExactly(responseMessage, true))
assert.ok(gzipper.createCompression() instanceof zlib.Gzip)
assert.strictEqual(gzipper.compressionType.name, 'GZIP')
assert.strictEqual(gzipper.compressionType.ext, 'gz')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 0)
assert.strictEqual(Object.keys(gzipper.options).length, 0)
})
it('should print message about empty folder', async () => {
const gzipper = new Gzipper(EMPTY_FOLDER_PATH, null)
const noFilesWarnSpy = sinon.spy(gzipper.logger, 'warn')
await gzipper.compress()
const responseMessage = `we couldn't find any appropriate files (.css, .js).`
assert.ok(noFilesWarnSpy.calledWithExactly(responseMessage, true))
assert.ok(gzipper.createCompression() instanceof zlib.Gzip)
assert.strictEqual(gzipper.compressionType.name, 'GZIP')
assert.strictEqual(gzipper.compressionType.ext, 'gz')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 0)
assert.strictEqual(Object.keys(gzipper.options).length, 0)
})
it('--verbose should print logs to console with and use default configuration', async () => {
const options = { verbose: true }
const gzipper = new Gzipper(COMPRESS_PATH, null, options)
const loggerSuccessSpy = sinon.spy(gzipper.logger, 'success')
const loggerInfoSpy = sinon.spy(gzipper.logger, 'info')
await gzipper.compress()
const files = await getFiles(COMPRESS_PATH, ['.gz'])
assert.ok(
loggerSuccessSpy.calledOnceWithExactly(
`${files.length} files have been compressed.`,
true
)
)
assert.ok(MESSAGE_REGEXP.test(loggerSuccessSpy.args[0][0]))
assert.strictEqual(loggerInfoSpy.callCount, files.length)
assert.ok(gzipper.createCompression() instanceof zlib.Gzip)
assert.strictEqual(gzipper.compressionType.name, 'GZIP')
assert.strictEqual(gzipper.compressionType.ext, 'gz')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 0)
assert.strictEqual(Object.keys(gzipper.options).length, 1)
for (const [arg] of loggerInfoSpy.args) {
assert.ok(VERBOSE_REGEXP.test(arg))
}
})
it('--gzip-level, --gzip-memory-level, --gzip-strategy should change gzip configuration', async () => {
const options = { gzipLevel: 6, gzipMemoryLevel: 4, gzipStrategy: 2 }
const gzipper = new Gzipper(COMPRESS_PATH, null, options)
const loggerSuccessSpy = sinon.spy(gzipper.logger, 'success')
await gzipper.compress()
const files = await getFiles(COMPRESS_PATH, ['.gz'])
assert.ok(
loggerSuccessSpy.calledOnceWithExactly(
`${files.length} files have been compressed.`,
true
)
)
assert.ok(MESSAGE_REGEXP.test(loggerSuccessSpy.args[0][0]))
assert.ok(gzipper.createCompression() instanceof zlib.Gzip)
assert.strictEqual(gzipper.compressionType.name, 'GZIP')
assert.strictEqual(gzipper.compressionType.ext, 'gz')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 3)
assert.strictEqual(Object.keys(gzipper.options).length, 3)
assert.strictEqual(gzipper.compressionOptions.gzipLevel, 6)
assert.strictEqual(gzipper.compressionOptions.gzipMemoryLevel, 4)
assert.strictEqual(gzipper.compressionOptions.gzipStrategy, 2)
})
it('--brotli should emit compress-error on compress error', () => {
const createBrotliCompress =
zlib.createBrotliCompress && zlib.createBrotliCompress.bind({})
try {
delete zlib.createBrotliCompress
new Gzipper(COMPRESS_PATH, null, { brotli: true })
} catch (err) {
assert.ok(err instanceof Error)
assert.strictEqual(
err.message,
`Can't use brotli compression, Node.js >= v11.7.0 required.`
)
}
zlib.createBrotliCompress = createBrotliCompress
})
it('--brotli-param-mode, --brotli-quality, --brotli-size-hint should change brotli configuration', async () => {
const options = {
brotli: true,
brotliParamMode: 'text',
brotliQuality: 10,
brotliSizeHint: 5,
}
if (zlib.createBrotliCompress !== 'function') {
return
}
const gzipper = new Gzipper(COMPRESS_PATH, null, options)
const loggerSuccessSpy = sinon.spy(gzipper.logger, 'success')
await gzipper.compress()
const files = await getFiles(COMPRESS_PATH, ['.br'])
assert.ok(
loggerSuccessSpy.calledOnceWithExactly(
`${files.length} files have been compressed.`,
true
)
)
assert.ok(MESSAGE_REGEXP.test(loggerSuccessSpy.args[0][0]))
assert.ok(gzipper.createCompression() instanceof zlib.BrotliCompress)
assert.strictEqual(gzipper.compressionType.name, 'BROTLI')
assert.strictEqual(gzipper.compressionType.ext, 'br')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 3)
assert.strictEqual(Object.keys(gzipper.options).length, 4)
assert.strictEqual(
gzipper.compressionOptions[zlib.constants.BROTLI_PARAM_MODE],
zlib.constants.BROTLI_MODE_TEXT
)
assert.strictEqual(
gzipper.compressionOptions[zlib.constants.BROTLI_PARAM_QUALITY],
10
)
assert.strictEqual(
gzipper.compressionOptions[zlib.constants.BROTLI_PARAM_SIZE_HINT],
5
)
})
it('should compress files to a certain folder with existing folder structure', async () => {
const gzipper = new Gzipper(COMPRESS_PATH, COMPRESS_PATH_TARGET)
const loggerSuccessSpy = sinon.spy(gzipper.logger, 'success')
await gzipper.compress()
const files = await getFiles(COMPRESS_PATH)
const compressedFiles = await getFiles(COMPRESS_PATH_TARGET, ['.gz'])
const filesRelative = files.map(file => path.relative(COMPRESS_PATH, file))
const compressedRelative = compressedFiles.map(file =>
path.relative(COMPRESS_PATH_TARGET, file)
)
assert.ok(
loggerSuccessSpy.calledOnceWithExactly(
`${files.length} files have been compressed.`,
true
)
)
assert.strictEqual(files.length, compressedFiles.length)
for (const file of filesRelative) {
assert.ok(
compressedRelative.some(compressedFile => {
const withoutExtFile = compressedFile.replace(
path.basename(compressedFile),
path.parse(path.basename(compressedFile)).name
)
return withoutExtFile === file
})
)
}
assert.ok(MESSAGE_REGEXP.test(loggerSuccessSpy.args[0][0]))
assert.ok(gzipper.createCompression() instanceof zlib.Gzip)
assert.strictEqual(gzipper.compressionType.name, 'GZIP')
assert.strictEqual(gzipper.compressionType.ext, 'gz')
assert.strictEqual(Object.keys(gzipper.compressionOptions).length, 0)
assert.strictEqual(Object.keys(gzipper.options).length, 0)
})
afterEach(async () => {
await clear(EMPTY_FOLDER_PATH, true)
await clear(COMPRESS_PATH_TARGET, true)
await clear(COMPRESS_PATH, ['.gz', '.br'])
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc