import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import get from 'lodash-es/get.js'
import isestr from './isestr.mjs'
import errToStr from './_errToStr.mjs'
import { isBinName, isBinRel, binUrlProblem } from './_binRules.mjs'
/**
* 後端nodejs依實檔產生外部執行檔清單之一項{ name, path或url, sha256, size }, 供各套件之toolg/genBinManifest.mjs產生bin/manifest.json(清單不手寫sha256與大小)
*
* 清單之格式與用法見fsEnsureBins(規格: 外部執行檔之存放下載校驗與離線, 規則帳本R-BIN-ENSURE-01)。name、path與url須合fsEnsureBins之規則(與其共用同一規則: url須https、不得帶帳密、不得為releases/latest或分支), path與url不可同給, 不合者reject。sha256為小寫64碼, size為位元組數; 計算對象為檔案之原始位元組, 自有檔須與GitHub上之內容(git blob)相同, 文字檔請以.gitattributes設為binary(不轉換換行)。
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/fsGetBinManifestEntry.test.mjs Github}
* @memberOf wsemi
* @param {String} fp 輸入檔案路徑字串
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {String} [opt.name] 輸入清單項之name,預設為fp之檔名
* @param {String} [opt.path] 輸入清單項之path(repo內之相對路徑, 以/分隔),給了才放入
* @param {String} [opt.url] 輸入清單項之url(廠商檔之下載網址),給了才放入
* @returns {Promise} 回傳Promise,resolve回傳清單項物件{ name, path, url, sha256, size }(path、url給了才有),reject回傳錯誤訊息字串
* @example
* //need test in nodejs
*
* import fs from 'fs'
*
* let test = async () => {
* let ms = []
*
* let fdt = './_test_fsGetBinManifestEntry'
* fsCreateFolder(fdt) //創建任務資料夾
*
* fs.writeFileSync(`${fdt}/tool.exe`, 'MZ-tool')
*
* let r1 = await fsGetBinManifestEntry(`${fdt}/tool.exe`, { path: 'bin/tool.exe' })
* console.log('r1', r1)
* ms.push(r1)
*
* let r2 = await fsGetBinManifestEntry(`${fdt}/tool.exe`)
* console.log('r2', r2)
* ms.push(r2)
*
* await fsGetBinManifestEntry(`${fdt}/none.exe`)
* .catch((err) => {
* ms.push({ err: typeof err })
* })
*
* fsDeleteFolder(fdt) //最終階段清除任務資料夾
*
* console.log('ms', ms)
* return ms
* }
* await test()
* .catch((err) => {
* console.log(err)
* })
* // r1 {
* // name: 'tool.exe',
* // path: 'bin/tool.exe',
* // sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
* // size: 7
* // }
* // r2 {
* // name: 'tool.exe',
* // sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
* // size: 7
* // }
* // ms [
* // {
* // name: 'tool.exe',
* // path: 'bin/tool.exe',
* // sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
* // size: 7
* // },
* // {
* // name: 'tool.exe',
* // sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
* // size: 7
* // },
* // { err: 'string' }
* // ]
*
*/
async function fsGetBinManifestEntry(fp, opt = {}) {
let r = null
try {
let name = get(opt, 'name')
let p = get(opt, 'path')
let u = get(opt, 'url')
if (!isestr(fp)) {
throw new Error(`fp is not an effective string`)
}
for (let [k, v] of [['name', name], ['path', p], ['url', u]]) {
if (v !== undefined && (typeof v !== 'string' || v === '')) {
throw new Error(`opt.${k} must be a non-empty primitive string when given`)
}
}
if (p !== undefined && u !== undefined) {
throw new Error(`opt.path and opt.url can not be given together (an entry has exactly one source)`)
}
if (u !== undefined && binUrlProblem(u) !== '') {
throw new Error(`opt.url[${u}] is not a url that fsEnsureBins accepts: ${binUrlProblem(u)}`)
}
if (p !== undefined && !isBinRel(p)) {
throw new Error(`opt.path[${p}] is not a relative path that fsEnsureBins accepts (/ separators, letters, digits, spaces and ._+-)`)
}
if (!fs.statSync(fp).isFile()) {
throw new Error(`fp[${fp}] is not a file`)
}
r = { name: name !== undefined ? name : path.basename(fp) }
if (!isBinName(r.name)) {
throw new Error(`name[${r.name}] is not a file name that fsEnsureBins accepts (letters, digits and ._+-, not a reserved device name), give opt.name`)
}
if (p !== undefined) {
r.path = p
}
if (u !== undefined) {
r.url = u
}
}
catch (err) {
return Promise.reject(errToStr(err))
}
let h = crypto.createHash('sha256')
let size = 0
let rs = null
try {
rs = fs.createReadStream(fp)
for await (let chunk of rs) {
size += chunk.length
h.update(chunk)
}
}
catch (err) {
return Promise.reject(errToStr(err))
}
finally {
if (rs !== null) {
rs.destroy()
}
}
r.sha256 = h.digest('hex')
r.size = size
return r
}
export default fsGetBinManifestEntry