├── .gitignore ├── package.json ├── README.md ├── main.js └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imgopt", 3 | "version": "1.2.0", 4 | "description": "A simple cli script for optimizing images in bulk.", 5 | "main": "main.js", 6 | "bin": { 7 | "imgopt": "./main.js" 8 | }, 9 | "type": "module", 10 | "keywords": [ 11 | "image", 12 | "optimization" 13 | ], 14 | "homepage": "https://github.com/jiftoo/imgopt", 15 | "bugs": { 16 | "url": "https://github.com/jiftoo/imgopt/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/jiftoo/imgopt" 21 | }, 22 | "author": "jiftoo", 23 | "license": "Apache-2.0", 24 | "dependencies": { 25 | "chalk": "^5.0.1", 26 | "commander": "^9.3.0", 27 | "prompt-sync": "^4.2.0", 28 | "sharp": "^0.30.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Repo stars](https://img.shields.io/github/stars/jiftoo/imgopt?style=social)](https://github.com/jiftoo/imgopt) 2 | 3 | # Welcome to `imgopt`! 4 | This cli tool is inspired by the awesome [expo-optimize](https://www.npmjs.com/package/expo-optimize) tool. Unlike the former, imgopt allows the user to convert images to other formats, or limit their width outside of an npm project and without the need to install the `sharp-cli` package. 5 | # Usage :rocket: 6 | ``` 7 | # Simply run this 8 | npx imgopt [options] 9 | ``` 10 | See `npx imgopt --help` for more. 11 | # Examples :pencil: 12 | ``` 13 | # Convert all files in the current directory to JPEG with quality=80, save to ./optimized 14 | npx imgopt . --format jpg --quality 80 -o optimized 15 | 16 | #Convert all files to PNG, limit width to 1000px, also copy non-image files, save to ./output (default) 17 | npx imgopt . --format png --max-width 1000 --copy-all 18 | 19 | # Keep format, quality=90, clear the output folder before optimizing 20 | npx imgopt . --quality 90 --clear 21 | ``` 22 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import sharp from "sharp"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import {exit} from "process"; 7 | import _prompt_sync from "prompt-sync"; 8 | import {Command} from "commander"; 9 | import chalk from "chalk"; 10 | const prompt_sync = _prompt_sync({sigint: true}); 11 | 12 | const abort = (msg) => { 13 | console.error("error: " + msg); 14 | exit(-1); 15 | }; 16 | const prompt = (msg) => { 17 | const resp = prompt_sync(msg + " [y/N] "); 18 | return resp.toLowerCase() === "y"; 19 | }; 20 | 21 | const supportedExtensions = [".jpg", ".jpeg", ".webp", ".png"]; 22 | 23 | let totalDirSizeAfter = 0; // updated in loop 24 | 25 | const program = new Command(); 26 | program.showHelpAfterError(true); 27 | program 28 | .name("imgopt") 29 | .version("1.0.3") 30 | .description("optimise all images in a folder. created by jiftoo") 31 | .argument("path", "path to folder") 32 | .option("-o --output ", "Output directory", "./output") 33 | .option("--format ", "Output format ", "preserve") 34 | .option("--quality <0-100>", "Output quality (default: 85)") 35 | .option("--max-width ", "Limit output width") 36 | .option("--dry-run", "Perform a dry run (no file system changes)", false) 37 | .option("--clear", "Clear the output directory") 38 | .option("--copy-all", "Copy all files into the output directory") 39 | .option("-y --yes", "Bypass [y/n] prompts"); 40 | program.action(async (dir, options) => { 41 | dir = path.resolve(dir); 42 | if (options.quality !== undefined && (isNaN(+options.quality) || options.quality < 0 || options.quality > 100)) { 43 | abort("--quality has to be a number in range 0-100"); 44 | } 45 | if (options.quality !== undefined) { 46 | options.quality = +options.quality; 47 | } 48 | if (options.maxWidth !== undefined && (isNaN(+options.maxWidth) || options.maxWidth < 1)) { 49 | abort("--max-width has to be a number greater than 0"); 50 | } 51 | if (options.maxWidth !== undefined) { 52 | options.maxWidth = +options.maxWidth; 53 | } 54 | 55 | if (!fs.existsSync(dir)) { 56 | abort(`"${dir}" does not exist.`); 57 | } 58 | if (fs.lstatSync(dir).isFile()) { 59 | abort(`"${dir}" is a file`); 60 | } 61 | if (!fs.readdirSync(dir).some((filename) => supportedExtensions.includes(path.parse(filename).ext.toLowerCase()))) { 62 | abort(`${dir} contains no images.`); 63 | } 64 | 65 | let outputDir = "none"; 66 | if (!options.dryRun) { 67 | if (path.isAbsolute(options.output)) { 68 | outputDir = path.normalize(options.output); 69 | } else { 70 | outputDir = path.resolve(path.join(process.cwd(), path.normalize(options.output))); 71 | } 72 | if (fs.existsSync(outputDir) && fs.readdirSync(outputDir).length !== 0) { 73 | console.log(`${outputDir} is not empty`); 74 | if (options.clear) { 75 | fs.readdirSync(outputDir).forEach((p) => fs.unlinkSync(path.join(outputDir, p))); 76 | console.log("Cleared", outputDir); 77 | } else { 78 | if (!(options.y || options.yes) && !prompt("Continue?")) { 79 | exit(0); 80 | } 81 | } 82 | } 83 | try { 84 | fs.mkdirSync(outputDir, {recursive: true}); 85 | } catch (_) {} 86 | console.log("Output directory:", outputDir); 87 | console.log(); 88 | } 89 | 90 | const totalDirSizeBefore = fs 91 | .readdirSync(dir) 92 | .filter((p) => supportedExtensions.includes(path.parse(p).ext)) 93 | .map((p) => fs.statSync(path.join(dir, p)).size) 94 | .reduce((acc, v) => acc + v, 0); 95 | 96 | const files = fs.readdirSync(dir); 97 | for (let i = 0; i < files.length; i++) { 98 | try { 99 | await transform(path.parse(path.join(dir, files[i])), outputDir, options); 100 | } catch (err) { 101 | console.log(chalk.redBright("copying (error)".padEnd(53, " ")), chalk.white(files[i]), chalk.redBright(err.message)); 102 | } 103 | } 104 | 105 | console.log(chalk.whiteBright("")); 106 | console.log( 107 | "Total size before:".padEnd(27, " "), 108 | chalk.yellowBright(formatSize(totalDirSizeBefore)) + "----->", 109 | chalk.greenBright(formatSize(totalDirSizeAfter)), 110 | `(${+((1 - totalDirSizeAfter / totalDirSizeBefore) * 100).toFixed(3)}% reduction)` 111 | ); 112 | }); 113 | 114 | const formatSize = (bytes, pad = false) => { 115 | let f; 116 | if (bytes < 1000) { 117 | f = bytes.toString(); 118 | return f + " b " + (pad ? "-".repeat(9 - f.length) : ""); 119 | } 120 | const kb = bytes / 1000; 121 | if (kb < 1000) { 122 | f = kb.toFixed(2); 123 | return f + " Kb " + (pad ? "-".repeat(9 - f.length) : ""); 124 | } 125 | f = (kb / 1000).toFixed(2); 126 | return f + " Mb " + (pad ? "-".repeat(9 - f.length) : ""); 127 | }; 128 | 129 | const transform = async (filePath, outputDir, options) => { 130 | if (!fs.existsSync(path.format(filePath))) { 131 | console.log(chalk.white("skipping (missing)".padEnd(53, " ")), chalk.white(filePath.name + filePath.ext)); 132 | return; 133 | } 134 | if (!supportedExtensions.includes(filePath.ext?.toLowerCase())) { 135 | if (options.copyAll && fs.statSync(path.format(filePath)).isFile()) { 136 | console.log(chalk.whiteBright("copying (not supported)".padEnd(53, " ")), chalk.whiteBright(filePath.name + filePath.ext)); 137 | if (!options.dryRun) fs.copyFileSync(path.format(filePath), path.join(outputDir, filePath.name + filePath.ext)); 138 | } else { 139 | console.log(chalk.white("skipping (not supported)".padEnd(53, " ")), chalk.white(filePath.name + filePath.ext)); 140 | } 141 | return; 142 | } 143 | const outputExtension = options.format === "preserve" ? filePath.ext : "." + options.format; 144 | 145 | const size = fs.statSync(path.format(filePath)).size; 146 | // console.log("path:", path.format(filePath)); 147 | const shr = sharp(path.format(filePath)); 148 | const metadata = await shr.metadata(); 149 | const notFormatChange = options.format === "preserve"; 150 | shr.toFormat(options.format === "preserve" ? filePath.ext.replace(".", "") : options.format, {quality: options.quality ?? 85}); 151 | const widthChange = options.maxWidth && metadata.width > options.maxWidth; 152 | if (widthChange) { 153 | shr.resize(options.maxWidth, null, {fit: "inside"}); 154 | } 155 | 156 | return new Promise((res, rej) => { 157 | shr.toBuffer((err, buffer, info) => { 158 | if (err !== null) { 159 | console.log(`error ${err}`, filePath.name + filePath.ext); 160 | rej(err); 161 | } else { 162 | if (options.dryRun) { 163 | totalDirSizeAfter += buffer.byteLength; 164 | console.log(chalk.magenta("transformed (dry run)".padEnd(27, " ")), formatSize(size, true) + ">", formatSize(info.size, false), filePath.name + filePath.ext); 165 | } else if (size < buffer.byteLength) { 166 | totalDirSizeAfter += size; 167 | fs.copyFileSync(path.format(filePath), path.join(outputDir, filePath.name + filePath.ext)); 168 | console.log( 169 | chalk.magenta("transformed (copy)".padEnd(27, " ")), 170 | formatSize(size, true) + ">", 171 | formatSize(info.size, false).padEnd(10, " "), 172 | filePath.name + filePath.ext 173 | ); 174 | } else { 175 | totalDirSizeAfter += buffer.byteLength; 176 | fs.writeFileSync(path.join(outputDir, filePath.name + outputExtension), buffer); 177 | console.log( 178 | chalk.green("transformed".padEnd(27, " ")), 179 | formatSize(size, true) + ">", 180 | formatSize(info.size, false).padEnd(10, " "), 181 | filePath.name + filePath.ext 182 | ); 183 | } 184 | res(); 185 | } 186 | }); 187 | }); 188 | }; 189 | 190 | program.parse(); 191 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | chalk: ^5.0.1 5 | commander: ^9.3.0 6 | prompt-sync: ^4.2.0 7 | sharp: ^0.30.7 8 | 9 | dependencies: 10 | chalk: 5.0.1 11 | commander: 9.3.0 12 | prompt-sync: 4.2.0 13 | sharp: 0.30.7 14 | 15 | packages: 16 | 17 | /ansi-regex/4.1.1: 18 | resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} 19 | engines: {node: '>=6'} 20 | dev: false 21 | 22 | /base64-js/1.5.1: 23 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 24 | dev: false 25 | 26 | /bl/4.1.0: 27 | resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} 28 | dependencies: 29 | buffer: 5.7.1 30 | inherits: 2.0.4 31 | readable-stream: 3.6.0 32 | dev: false 33 | 34 | /buffer/5.7.1: 35 | resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 36 | dependencies: 37 | base64-js: 1.5.1 38 | ieee754: 1.2.1 39 | dev: false 40 | 41 | /chalk/5.0.1: 42 | resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} 43 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 44 | dev: false 45 | 46 | /chownr/1.1.4: 47 | resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} 48 | dev: false 49 | 50 | /color-convert/2.0.1: 51 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 52 | engines: {node: '>=7.0.0'} 53 | dependencies: 54 | color-name: 1.1.4 55 | dev: false 56 | 57 | /color-name/1.1.4: 58 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 59 | dev: false 60 | 61 | /color-string/1.9.1: 62 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 63 | dependencies: 64 | color-name: 1.1.4 65 | simple-swizzle: 0.2.2 66 | dev: false 67 | 68 | /color/4.2.3: 69 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 70 | engines: {node: '>=12.5.0'} 71 | dependencies: 72 | color-convert: 2.0.1 73 | color-string: 1.9.1 74 | dev: false 75 | 76 | /commander/9.3.0: 77 | resolution: {integrity: sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==} 78 | engines: {node: ^12.20.0 || >=14} 79 | dev: false 80 | 81 | /decompress-response/6.0.0: 82 | resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} 83 | engines: {node: '>=10'} 84 | dependencies: 85 | mimic-response: 3.1.0 86 | dev: false 87 | 88 | /deep-extend/0.6.0: 89 | resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 90 | engines: {node: '>=4.0.0'} 91 | dev: false 92 | 93 | /detect-libc/2.0.1: 94 | resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} 95 | engines: {node: '>=8'} 96 | dev: false 97 | 98 | /end-of-stream/1.4.4: 99 | resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 100 | dependencies: 101 | once: 1.4.0 102 | dev: false 103 | 104 | /expand-template/2.0.3: 105 | resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} 106 | engines: {node: '>=6'} 107 | dev: false 108 | 109 | /fs-constants/1.0.0: 110 | resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} 111 | dev: false 112 | 113 | /github-from-package/0.0.0: 114 | resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} 115 | dev: false 116 | 117 | /ieee754/1.2.1: 118 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 119 | dev: false 120 | 121 | /inherits/2.0.4: 122 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 123 | dev: false 124 | 125 | /ini/1.3.8: 126 | resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 127 | dev: false 128 | 129 | /is-arrayish/0.3.2: 130 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 131 | dev: false 132 | 133 | /lru-cache/6.0.0: 134 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 135 | engines: {node: '>=10'} 136 | dependencies: 137 | yallist: 4.0.0 138 | dev: false 139 | 140 | /mimic-response/3.1.0: 141 | resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} 142 | engines: {node: '>=10'} 143 | dev: false 144 | 145 | /minimist/1.2.6: 146 | resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} 147 | dev: false 148 | 149 | /mkdirp-classic/0.5.3: 150 | resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} 151 | dev: false 152 | 153 | /napi-build-utils/1.0.2: 154 | resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} 155 | dev: false 156 | 157 | /node-abi/3.22.0: 158 | resolution: {integrity: sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==} 159 | engines: {node: '>=10'} 160 | dependencies: 161 | semver: 7.3.7 162 | dev: false 163 | 164 | /node-addon-api/5.0.0: 165 | resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==} 166 | dev: false 167 | 168 | /once/1.4.0: 169 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 170 | dependencies: 171 | wrappy: 1.0.2 172 | dev: false 173 | 174 | /prebuild-install/7.1.1: 175 | resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} 176 | engines: {node: '>=10'} 177 | hasBin: true 178 | dependencies: 179 | detect-libc: 2.0.1 180 | expand-template: 2.0.3 181 | github-from-package: 0.0.0 182 | minimist: 1.2.6 183 | mkdirp-classic: 0.5.3 184 | napi-build-utils: 1.0.2 185 | node-abi: 3.22.0 186 | pump: 3.0.0 187 | rc: 1.2.8 188 | simple-get: 4.0.1 189 | tar-fs: 2.1.1 190 | tunnel-agent: 0.6.0 191 | dev: false 192 | 193 | /prompt-sync/4.2.0: 194 | resolution: {integrity: sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==} 195 | dependencies: 196 | strip-ansi: 5.2.0 197 | dev: false 198 | 199 | /pump/3.0.0: 200 | resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} 201 | dependencies: 202 | end-of-stream: 1.4.4 203 | once: 1.4.0 204 | dev: false 205 | 206 | /rc/1.2.8: 207 | resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 208 | hasBin: true 209 | dependencies: 210 | deep-extend: 0.6.0 211 | ini: 1.3.8 212 | minimist: 1.2.6 213 | strip-json-comments: 2.0.1 214 | dev: false 215 | 216 | /readable-stream/3.6.0: 217 | resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} 218 | engines: {node: '>= 6'} 219 | dependencies: 220 | inherits: 2.0.4 221 | string_decoder: 1.3.0 222 | util-deprecate: 1.0.2 223 | dev: false 224 | 225 | /safe-buffer/5.2.1: 226 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 227 | dev: false 228 | 229 | /semver/7.3.7: 230 | resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} 231 | engines: {node: '>=10'} 232 | hasBin: true 233 | dependencies: 234 | lru-cache: 6.0.0 235 | dev: false 236 | 237 | /sharp/0.30.7: 238 | resolution: {integrity: sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==} 239 | engines: {node: '>=12.13.0'} 240 | requiresBuild: true 241 | dependencies: 242 | color: 4.2.3 243 | detect-libc: 2.0.1 244 | node-addon-api: 5.0.0 245 | prebuild-install: 7.1.1 246 | semver: 7.3.7 247 | simple-get: 4.0.1 248 | tar-fs: 2.1.1 249 | tunnel-agent: 0.6.0 250 | dev: false 251 | 252 | /simple-concat/1.0.1: 253 | resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} 254 | dev: false 255 | 256 | /simple-get/4.0.1: 257 | resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} 258 | dependencies: 259 | decompress-response: 6.0.0 260 | once: 1.4.0 261 | simple-concat: 1.0.1 262 | dev: false 263 | 264 | /simple-swizzle/0.2.2: 265 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 266 | dependencies: 267 | is-arrayish: 0.3.2 268 | dev: false 269 | 270 | /string_decoder/1.3.0: 271 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 272 | dependencies: 273 | safe-buffer: 5.2.1 274 | dev: false 275 | 276 | /strip-ansi/5.2.0: 277 | resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} 278 | engines: {node: '>=6'} 279 | dependencies: 280 | ansi-regex: 4.1.1 281 | dev: false 282 | 283 | /strip-json-comments/2.0.1: 284 | resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 285 | engines: {node: '>=0.10.0'} 286 | dev: false 287 | 288 | /tar-fs/2.1.1: 289 | resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} 290 | dependencies: 291 | chownr: 1.1.4 292 | mkdirp-classic: 0.5.3 293 | pump: 3.0.0 294 | tar-stream: 2.2.0 295 | dev: false 296 | 297 | /tar-stream/2.2.0: 298 | resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} 299 | engines: {node: '>=6'} 300 | dependencies: 301 | bl: 4.1.0 302 | end-of-stream: 1.4.4 303 | fs-constants: 1.0.0 304 | inherits: 2.0.4 305 | readable-stream: 3.6.0 306 | dev: false 307 | 308 | /tunnel-agent/0.6.0: 309 | resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} 310 | dependencies: 311 | safe-buffer: 5.2.1 312 | dev: false 313 | 314 | /util-deprecate/1.0.2: 315 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 316 | dev: false 317 | 318 | /wrappy/1.0.2: 319 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 320 | dev: false 321 | 322 | /yallist/4.0.0: 323 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 324 | dev: false 325 | --------------------------------------------------------------------------------