UNPKG

resource-uploader

Version:
352 lines (285 loc) 8.35 kB
import fs from 'fs'; import path from 'path'; import through2 from 'through2-concurrent'; import PluginError from 'plugin-error'; import chalk from 'chalk'; import log from 'fancy-log'; import ALY from 'aliyun-sdk'; import crypto from 'crypto'; import _ from 'lodash'; import zlib from 'zlib'; import sizeOf from 'image-size'; import os from 'os'; import mime from 'mime'; import url from 'url'; import pkg from '../package.json' with { type: 'json' }; const userInfo = os.userInfo(); const sha1 = str => crypto.createHash('md5').update(str).digest('hex').slice(16); const getConfigFileHash = (file) => { let content = ''; try { content = fs.readFileSync(file).toString(); } catch (error) { content = ''; } return sha1(`${userInfo.username}${content}`); }; let configFile = path.join(os.homedir(), '.config', 'resource-uploader', 'config.json'); let cacheFile = path.join(os.tmpdir(), `resource-uploader-manifest.${getConfigFileHash(configFile)}.json`); let aliossOptions = pkg.alioss; let cdnCache = {}; const encodeUrl = str => str.split('/').map(item => encodeURIComponent(item)).join('/'); const getHashName = (name, hash, encodeUrl = false) => { return `${hash}/${encodeUrl ? encodeURIComponent(name) : name}`; }; const getHashCdnName = (hashName) => { return '-/' + hashName; }; const putObject = (instance, options) => new Promise((resolve) => { instance.putObject(options, (err) => { if (err) { err.isSuccess = false; resolve(err); } else { resolve({ isSuccess: true }); } }); }); const refreshObject = (instance, options) => new Promise((resolve) => { instance.refreshObjectCaches(options, (err) => { if (err) { err.isSuccess = false; resolve(err); } else { resolve({ isSuccess: true }); } }); }); const mergeAliossOptions = () => { if (fs.existsSync(configFile)) { aliossOptions = Object.assign({}, aliossOptions, JSON.parse(fs.readFileSync(configFile)).alioss); } }; const refresh = async(options, url, isQuiet = false) => { mergeAliossOptions(); options = Object.assign({}, aliossOptions, options); const cdnClient = new ALY.CDN({ accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey, endpoint: options.endpointCdn, apiVersion: options.apiVersionCdn }); const result = await refreshObject(cdnClient, { ObjectType: 'File', ObjectPath: url }); if (result.isSuccess) { if (isQuiet) { console.log(chalk.green(url)); } else { log('OK:', chalk.green(url)); } } else { log('Refresh CDN ERR:', chalk.red(url)); } }; const getBucketLocation = (options) => new Promise((resolve) => { options = Object.assign({}, aliossOptions, options); const ossClient = new ALY.OSS({ accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey, endpoint: options.endpoint, apiVersion: options.apiVersion }); ossClient.getBucketLocation({ Bucket: options.bucket }, (err, data) => { if (err) { resolve({ isSuccess: false, data: err }); } else { resolve({ isSuccess: true, data }); } }); }); const upload = (options, isQuiet = false) => { mergeAliossOptions(); options = Object.assign({}, aliossOptions, options); let urlPrefix = options.urlPrefix; if (urlPrefix.indexOf('//') === 0) { urlPrefix = `http:${urlPrefix}`; } const urlObject = url.parse(urlPrefix); const keyPrefix = urlObject.pathname; const ossClient = new ALY.OSS({ accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey, endpoint: options.endpoint, apiVersion: options.apiVersion }); try { cdnCache = _(cdnCache).merge(JSON.parse(fs.readFileSync(cacheFile))).value(); } catch (e) { } return through2.obj({ maxConcurrency: 8 }, async function(file, enc, cb) { if (file.isDirectory()) { return cb(); } if (file.isStream()) { this.emit('error', new PluginError('alioss', 'Streams are not supported!')); return cb(); } if (file.noCdn) { return cb(null, file); } if (file.contents.length >= 1024 * 1024 * 100) { log('file size too big:', chalk.yellow(file.path + "\t" + file.contents.length)); return cb(null, file); } const ext = path.extname(file.path); let name; let filename; let hash; let hashName; let cdnKey; let dimensions; try { dimensions = sizeOf(file.path); } catch (e) { } if (options.uriPrefix) { name = options.uriName || path.relative(file.base, file.path); filename = `!/${options.uriPrefix}/${name}`; const encodeUri = encodeUrl(options.uriPrefix); file.cdn = `${options.urlPrefix}!/${encodeUri}/${encodeUrl(name)}`; } else { name = path.basename(file.path); hash = sha1(file.contents); hashName = getHashName(name, hash); filename = getHashCdnName(hashName); cdnKey = hashName; file.cdn = `${options.urlPrefix}${getHashCdnName(getHashName(name, hash, true))}`; if (cdnCache[cdnKey]) { if (isQuiet) { console.log(chalk.green(file.cdn)); } else { log('file:', name); if (dimensions) { log('width:', chalk.green(dimensions.width) + 'px', 'height:', chalk.green(dimensions.height) + 'px'); } log('OK:', chalk.green(file.cdn)); } return cb(null, file); } } if (file.sourceMap) { if (options.uriPrefix && options.uriName) { file.sourceMap.sources = [name]; file.sourceMap.file = name; } file.contents = Buffer.concat([ file.contents, Buffer.from('\n' + (ext === '.css' ? '/*# sourceMappingURL=' + name + '.map */' : '//# sourceMappingURL=' + name + '.map')) ]); } let contentType = ''; const gzipMimes = { '.plist': 6, '.html': 6, '.htm': 6, '.js': 6, '.css': 6, '.svg': 6 }; const charsetMimes = { '.js': 'utf-8', '.css': 'utf-8', '.html': 'utf-8', '.htm': 'utf-8', '.svg': 'utf-8' }; contentType = mime.getType(ext) || 'application/octet-stream'; if (charsetMimes[ext]) { contentType += '; charset=' + charsetMimes[ext]; } // console.log(filename); const key = `${keyPrefix}${filename}`.replace(/^\/+/, ''); const opt = { Bucket: options.bucket, Key: key, Body: file.contents, AccessControlAllowOrigin: '*', ContentType: contentType, CacheControl: 'max-age=315360000, immutable' }; if (gzipMimes[ext]) { opt.ContentEncoding = 'gzip'; opt.Body = zlib.gzipSync(file.contents, {level: gzipMimes[ext]}); } const { isSuccess, code } = await putObject(ossClient, opt); if (isSuccess) { if (isQuiet) { console.log(chalk.green(file.cdn)); } else { log('file:', name); if (dimensions) { log('width:', chalk.green(dimensions.width) + 'px', 'height:', chalk.green(dimensions.height) + 'px'); } log('OK:', chalk.green(file.cdn)); } if (file.sourceMap) { await putObject(ossClient, { Bucket: options.bucket, Key: key + '.map', Body: JSON.stringify(file.sourceMap), AccessControlAllowOrigin: '*', ContentType: 'application/json; charset=utf-8', CacheControl: 'max-age=315360000, immutable' }); if (options.uriPrefix) { cb(); } else { cdnCache[cdnKey] = true; cb(null, file); } } else { if (options.uriPrefix) { cb(); } else { cdnCache[cdnKey] = true; cb(null, file); } } } else { log('ERR:', chalk.red(filename + "\t" + code)); cb(); } }, (cb) => { fs.writeFileSync(cacheFile, JSON.stringify(cdnCache, null, ' ')); cb(); }); }; const setConfigFile = (filename) => { if (filename) { configFile = filename; cacheFile = path.join(os.tmpdir(), `resource-uploader-manifest.${getConfigFileHash(configFile)}.json`); } }; export default { upload, refresh, getBucketLocation, setConfigFile };