efficient-tools
Version:
Provide some efficient tools to improve your efficiency.
214 lines (197 loc) • 5.84 kB
JavaScript
/*global process */
/**
* @Description: A tool for image compression
* @Author: luhaifeng
* @Date: 2022/5/23
*/
import tinify from 'tinify'
import path from 'path'
import { readdir, mkdir } from 'fs/promises'
import { program } from '../src/utils/programInit.js'
import { promptCreator } from '../src/utils/etl.js'
import { dotenvInit } from '../src/utils/dotenvConfig.js'
import { successHandler, errorHandler, handleDotenv, handleDotenvCheck, notEmpty, promisify, __dirname } from '../src/utils/common.js'
// dotenv configuration
const dotenvPath = path.join(__dirname, '../../.env')
dotenvInit()
// exts
const IMAGE_SUPPORT_EXTS = [
'.webp', '.jpeg', '.jpg', '.png'
]
// PWD
const BASE_URL = process.env.PWD
program
.option('-s, --set', 'set your API key')
.option('-c, --compress', 'image compression')
program.parse(process.argv)
const { set, compress } = program.opts()
// init tinify key
function init (key = process.env.TINIFY_API_KEY ) {
tinify.key = key
}
// get all images' name
async function getAllImagesName (isDeep, dir = BASE_URL) {
const res = {}
try {
const { name } = path.parse(dir)
const hiddenFileRule = /(^|\/)\.[^\/\.]/g
const files = (await readdir(dir)).filter(file => !hiddenFileRule.test(file))
if (files.length) {
res[name] = {
imageNames: files.filter(file => IMAGE_SUPPORT_EXTS.includes(path.extname(`${dir}/${file}`).toLowerCase()))
}
if (isDeep) {
const dirs = files.filter(file => !path.extname(`${dir}/${file}`))
if (dirs.length) {
const handlers = dirs.map(dirname => getAllImagesName(isDeep, `${dir}/${dirname}`))
const data = await Promise.allSettled(handlers)
res[name].children = data.reduce((obj, { value }) => ({...obj, ...value}), {})
}
}
}
} catch (e) {
errorHandler(e.message)
}
return res
}
// compress handler
async function compressHandler(baseUrl, images, originBaseUrl) {
const dirs = Object.keys(images)
await Promise.allSettled(dirs.map(dir => mkdir(`${baseUrl}/${dir}`)))
dirs.forEach(async dir => {
const { imageNames, children } = images[dir]
const dirname =`${baseUrl}/${dir}`
imageNames.forEach(name => {
const imagePath = `${originBaseUrl}/${dir}/${name}`
promisify((resolve, reject) => {
const source = tinify.fromFile(imagePath)
resolve(source.toFile(`${dirname}/${name}`))
}).then(() => {
successHandler(`${imagePath} compression completed!`)
}).catch(e => {
errorHandler(e.message)
})
})
if (children) {
compressHandler(dirname, children, `${originBaseUrl}/${dir}`)
}
})
}
// initialization
init()
// set API key
if (set) {
promptCreator([
{
type: 'input',
name: 'TINIFY_API_KEY',
message: 'Please set your tinify apiKey'
}
], answer => {
handleDotenv(Object.keys(answer).reduce((obj, key) => ({
...obj,
[key]: answer[key]
}), {}), 'Config successfully!', dotenvPath)
// set TINIFY_API_KEY
init(answer.TINIFY_API_KEY)
})
}
// compress images
if (compress) {
handleDotenvCheck(['TINIFY_API_KEY'], res => {
if (res) {
promptCreator([
{
type: 'list',
name: 'type',
message: 'Do you wanna compress local images or online images?',
choices: ['local-images', 'online-images'],
default: 'local-images'
},
{
type: 'input',
name: 'target',
message: 'Please type the output file path:',
default: path.resolve(BASE_URL, '..')
}
], answer => {
const { target } = answer
// processording to selection
switch (answer.type) {
case 'local-images':
promptCreator([
{
type: 'confirm',
name: 'isDeep',
message: 'Shallow or depth(default shallow)?',
default: false
},
{
type: 'input',
name: 'pathname',
message: 'Please type the target directory name(absolute path):',
default: BASE_URL
},
{
type: 'input',
name: 'dName',
message: 'Please type the directory name used to store compressed images:',
default: new Date().getTime()
}
], async answer => {
try {
const { isDeep, dName, pathname } = answer
const images = await getAllImagesName(isDeep, pathname)
if (Object.keys(images).length) {
// create a new directory to store compressed images
const targetDir = `${target.trim()}/${dName.trim()}`
const message = `* Compression start! The output file path is ${targetDir} *`
const startLine = new Array(message.length).fill('*').join('')
await mkdir(targetDir)
successHandler(startLine)
successHandler(message)
successHandler(startLine)
// compress images
compressHandler(targetDir, images, path.resolve(pathname, '..'))
}
} catch (e) {
errorHandler(e)
}
})
break
case 'online-images':
promptCreator([
{
type: 'input',
name: 'imageLink',
message: 'Please type the image\'s link:',
validate: notEmpty('The image\'s link can not be empty!')
},
{
type: 'input',
name: 'imageName',
message: 'Please type the image\'s name:',
default: new Date().getTime().toString()
}
], answer => {
const { imageLink, imageName } = answer
successHandler('Compressing...')
promisify((resolve, reject) => {
const source = tinify.fromUrl(imageLink)
resolve(source.toFile(`${target}/${imageName}`))
}).then(() => {
successHandler(`Compression completed! The compressed image is in ${target}.`)
}).catch(e => {
errorHandler(e.message)
})
})
break
default: break
}
})
} else {
errorHandler('The API key shouldn\'t be empty! Please config them first by "etp -s/--set" command.')
}
}, dotenvPath)
}