<NME> utils.js
<BEF> ADDFILE
<MSG> feat(miniprogram-cropper): initial miniprogram-cropper package

<DFF> @@ -0,0 +1,213 @@
+const fs = require('fs')
+const path = require('path')
+
+// eslint-disable-next-line no-unused-vars
+const colors = require('colors')
+const through = require('through2')
+
+/**
+ * 异步函数封装
+ */
+function wrap(func, scope) {
+  return function (...args) {
+    if (args.length) {
+      const temp = args.pop()
+      if (typeof temp !== 'function') {
+        args.push(temp)
+      }
+    }
+
+    return new Promise(function (resolve, reject) {
+      args.push(function (err, data) {
+        if (err) reject(err)
+        else resolve(data)
+      })
+
+      func.apply((scope || null), args)
+    })
+  }
+}
+
+const accessSync = wrap(fs.access)
+const statSync = wrap(fs.stat)
+const renameSync = wrap(fs.rename)
+const mkdirSync = wrap(fs.mkdir)
+const readFileSync = wrap(fs.readFile)
+const writeFileSync = wrap(fs.writeFile)
+
+/**
+ * 调整路径分隔符
+ */
+function transformPath(filePath, sep = '/') {
+  return filePath.replace(/[\\/]/g, sep)
+}
+
+/**
+ * 检查文件是否存在
+ */
+async function checkFileExists(filePath) {
+  try {
+    await accessSync(filePath)
+    return true
+  } catch (err) {
+    return false
+  }
+}
+
+/**
+ * 递归创建目录
+ */
+async function recursiveMkdir(dirPath) {
+  const prevDirPath = path.dirname(dirPath)
+  try {
+    await accessSync(prevDirPath)
+  } catch (err) {
+    // 上一级目录不存在
+    await recursiveMkdir(prevDirPath)
+  }
+
+  try {
+    await accessSync(dirPath)
+
+    const stat = await statSync(dirPath)
+    if (stat && !stat.isDirectory()) {
+      // 目标路径存在，但不是目录
+      await renameSync(dirPath, `${dirPath}.bak`) // 将此文件重命名为 .bak 后缀
+      await mkdirSync(dirPath)
+    }
+  } catch (err) {
+    // 目标路径不存在
+    await mkdirSync(dirPath)
+  }
+}
+
+/**
+ * 读取 json
+ */
+function readJson(filePath) {
+  try {
+    // eslint-disable-next-line import/no-dynamic-require
+    const content = require(filePath)
+    delete require.cache[require.resolve(filePath)]
+    return content
+  } catch (err) {
+    return null
+  }
+}
+
+/**
+ * 读取文件
+ */
+async function readFile(filePath) {
+  try {
+    return await readFileSync(filePath, 'utf8')
+  } catch (err) {
+    // eslint-disable-next-line no-console
+    return console.error(err)
+  }
+}
+
+/**
+ * 写文件
+ */
+async function writeFile(filePath, data) {
+  try {
+    await recursiveMkdir(path.dirname(filePath))
+    return await writeFileSync(filePath, data, 'utf8')
+  } catch (err) {
+    // eslint-disable-next-line no-console
+    return console.error(err)
+  }
+}
+
+/**
+ * 时间格式化
+ */
+function format(time, reg) {
+  const date = typeof time === 'string' ? new Date(time) : time
+  const map = {}
+  map.yyyy = date.getFullYear()
+  map.yy = ('' + map.yyyy).substr(2)
+  map.M = date.getMonth() + 1
+  map.MM = (map.M < 10 ? '0' : '') + map.M
+  map.d = date.getDate()
+  map.dd = (map.d < 10 ? '0' : '') + map.d
+  map.H = date.getHours()
+  map.HH = (map.H < 10 ? '0' : '') + map.H
+  map.m = date.getMinutes()
+  map.mm = (map.m < 10 ? '0' : '') + map.m
+  map.s = date.getSeconds()
+  map.ss = (map.s < 10 ? '0' : '') + map.s
+
+  return reg.replace(/\byyyy|yy|MM|M|dd|d|HH|H|mm|m|ss|s\b/g, $1 => map[$1])
+}
+
+/**
+ * 日志插件
+ */
+function logger(action = 'copy') {
+  return through.obj(function (file, enc, cb) {
+    const type = path.extname(file.path).slice(1).toLowerCase()
+
+    // eslint-disable-next-line no-console
+    console.log(`[${format(new Date(), 'yyyy-MM-dd HH:mm:ss').grey}] [${action.green} ${type.green}] ${'=>'.cyan} ${file.path}`)
+
+    this.push(file)
+    cb()
+  })
+}
+
+/**
+ * 比较数组是否相等
+ */
+function compareArray(arr1, arr2) {
+  if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false
+  if (arr1.length !== arr2.length) return false
+
+  for (let i = 0, len = arr1.length; i < len; i++) {
+    if (arr1[i] !== arr2[i]) return false
+  }
+
+  return true
+}
+
+/**
+ * 合并两个对象
+ */
+function merge(obj1, obj2) {
+  Object.keys(obj2).forEach(key => {
+    if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) {
+      obj1[key] = obj1[key].concat(obj2[key])
+    } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
+      obj1[key] = Object.assign(obj1[key], obj2[key])
+    } else {
+      obj1[key] = obj2[key]
+    }
+  })
+
+  return obj1
+}
+
+/**
+ * 获取 id
+ */
+let seed = +new Date()
+function getId() {
+  return ++seed
+}
+
+module.exports = {
+  wrap,
+  transformPath,
+
+  checkFileExists,
+  readJson,
+  readFile,
+  writeFile,
+
+  logger,
+  format,
+  compareArray,
+  merge,
+  getId,
+}

feat(miniprogram-cropper): initial miniprogram-cropper package