UNPKG

@yuntools/ali-oss

Version:

阿里云 OSS 命令行工具 ossutil 封装,支持 ESM,CJS 导入,提供 TypeScript 类型定义

276 lines 9.12 kB
import https from 'https'; import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { createWriteStream } from 'node:fs'; import { stat, writeFile } from 'node:fs/promises'; import { homedir, platform, tmpdir } from 'node:os'; import { join } from 'node:path'; // eslint-disable-next-line import/no-extraneous-dependencies import { firstValueFrom, reduce, map } from 'rxjs'; import { run } from 'rxrunscript'; import { pickFuncMap, pickRegxMap } from './rule.js'; import { DataKey, MKey, PlaceholderKey, } from './types.js'; export async function processResp(input$, debug = false) { let exitCode; let exitSignal; const buf$ = input$.pipe(reduce((acc, curr) => { if (typeof curr.exitCode === 'undefined') { debug && console.log({ processResp: curr.data.toString('utf-8') }); acc.push(curr.data); } else { // last value exitCode = curr.exitCode; exitSignal = curr.exitSignal; } return acc; }, []), map(arr => Buffer.concat(arr))); if (typeof exitCode === 'undefined') { exitCode = 0; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (typeof exitSignal === 'undefined' || exitSignal === null) { exitSignal = ''; } const res = await firstValueFrom(buf$); const content = res.toString('utf-8').trim(); const ret = { exitCode, exitSignal, stdout: exitCode === 0 ? content : '', stderr: exitCode === 0 ? '' : content, }; return ret; } export function parseRespStdout(input, dataKeys = [DataKey.elapsed], debug = false, output) { if (input.exitCode !== 0) { return void 0; } const ret = output ?? {}; const keys = [...new Set(dataKeys)]; keys.forEach((key) => { const rule = pickRegxMap.get(key); /* c8 ignore next 4 */ if (!rule) { console.warn(`rule not found for ${key}`); return; } const func = pickFuncMap.get(key); /* c8 ignore next 4 */ if (!func) { console.warn(`func not found for ${key}`); return; } let value = func(input.stdout, rule, debug); value = convertUndefiedToZero(key, value); Object.defineProperty(ret, key, { enumerable: true, value, }); }); return ret; } function convertUndefiedToZero(key, value) { const keys = [ DataKey.uploadDirs, DataKey.uploadFiles, DataKey.copyObjects, ]; if (keys.includes(key)) { return value ? +value : 0; } return value; } export function combineProcessRet(resp, data) { const ret = { ...resp, data, }; return ret; } export function genParams(configPath, paramMap) { const ps = configPath ? ['-c', configPath] : []; /* c8 ignore next 3 */ if (!paramMap.size) { return ps; } const pp = preGenParams(paramMap); pp.forEach((value, key) => { if (key === PlaceholderKey.src) { return; } else if (key === PlaceholderKey.dest) { return; } switch (typeof value) { /* c8 ignore next 2 */ case 'undefined': return; case 'boolean': { if (value === true) { ps.push(`--${key}`); } break; } case 'number': ps.push(`--${key} ${value.toString()}`); break; case 'string': ps.push(`--${key} ${value}`); break; /* c8 ignore next 2 */ default: throw new TypeError(`unexpected typeof ${key}: ${typeof value}`); } }); const src = pp.get(PlaceholderKey.src); const dest = pp.get(PlaceholderKey.dest); if (src && typeof src === 'string') { ps.push(src); } if (dest && typeof dest === 'string') { ps.push(dest); } return ps; } export function preGenParams(paramMap) { const encodeSource = paramMap.get(PlaceholderKey.encodeSource); paramMap.delete(PlaceholderKey.encodeSource); const encodeTarget = paramMap.get(PlaceholderKey.encodeTarget); paramMap.delete(PlaceholderKey.encodeTarget); if (encodeTarget || encodeSource) { paramMap.set(MKey.encodingType, 'url'); } paramMap.delete(PlaceholderKey.bucket); // ensure bucket is not in the params paramMap.delete('src'); paramMap.delete('dest'); paramMap.delete('target'); return paramMap; } export function mergeParams(inputOptions, initOptions, config) { const ret = new Map(); const ps1 = inputOptions ?? {}; const ps2 = initOptions ?? {}; const ps3 = config ?? {}; [ps3, ps2, ps1].forEach((obj) => { Object.entries(obj).forEach(([key, value]) => { if (!Object.hasOwn(ps2, key) && !Object.hasOwn(ps3, key)) { return; } let kk = key; // 参数名转换 if (Object.hasOwn(MKey, key)) { // @ts-ignore const mkey = MKey[key]; if (typeof mkey === 'string' && mkey) { kk = mkey; } } const vv = value; if (typeof vv === 'undefined') { return; } if (typeof vv === 'number') { ret.set(kk, vv); } else if (typeof vv === 'string') { vv && ret.set(kk, vv); } else if (typeof vv === 'boolean') { ret.set(kk, vv); } // void else }); }); return ret; } export async function writeConfigFile(config, filePath) { const sha1 = createHash('sha1'); const hash = sha1.update(JSON.stringify(config)).digest('hex'); const path = filePath ?? join(tmpdir(), `${hash}.tmp`); try { const exists = (await stat(path)).isFile(); if (exists) { return { path, hash }; } } catch (ex) { void ex; } const arr = ['[Credentials]']; const { endpoint, accessKeyId, accessKeySecret, stsToken } = config; endpoint && arr.push(`endpoint = ${endpoint}`); accessKeyId && arr.push(`accessKeyID = ${accessKeyId}`); accessKeySecret && arr.push(`accessKeySecret = ${accessKeySecret}`); stsToken && arr.push(`stsToken = ${stsToken}`); await writeFile(path, arr.join('\n')); return { path, hash }; } export async function validateConfigPath(config) { assert(config, 'config file path is empty'); const exists = (await stat(config)).isFile(); assert(exists, `config file ${config} not exists`); } export async function downloadOssutil(srcLink, targetPath = join(homedir(), 'ossutil')) { assert(srcLink, 'srcLink is empty'); const file = createWriteStream(targetPath); await new Promise((done, reject) => { https.get(srcLink, (resp) => { resp.pipe(file); file.on('finish', () => { file.close(); console.log('Download Completed'); done(); }); }) .on('error', (err) => { /* c8 ignore next */ reject(err); }); }); if (platform() !== 'win32') { await setBinExecutable(targetPath); } return targetPath; } export async function setBinExecutable(file) { const ret = await firstValueFrom(run(`chmod +x ${file}`)); return ret; } export function encodeInputPath(input, encode = false) { const str = input.replace(/\\/ug, '/'); const ret = encode === true ? encodeURIComponent(str).replace(/'/ug, '%27') : input; return ret; } export function commonProcessInputMap(input, initOptions, globalConfig) { assert(input, 'input is required'); const ret = mergeParams(input, initOptions, globalConfig); const encodeSrc = ret.get(PlaceholderKey.encodeSource); const encodeTarget = ret.get(PlaceholderKey.encodeTarget); const src = processInputAsEncodedCloudUrl(input.src, input.bucket, encodeSrc); const dest = processInputAsEncodedCloudUrl(input.target, input.bucket, encodeTarget); ret.set(PlaceholderKey.src, src); ret.set(PlaceholderKey.dest, dest); return ret; } export function processInputAsEncodedCloudUrl(input, bucket, needEncode) { if (!input) { return ''; } assert(bucket, 'bucket is required'); const ossPrefix = 'oss://'; let ret = encodeInputPath(input, needEncode); if (needEncode && ret && !ret.startsWith(ossPrefix)) { const str = ret.replace(/^\/+/ug, ''); ret = `${ossPrefix}${bucket}/${str}`; } return ret; } /** start with `oss://` */ export function pathIsCloudUrl(path) { assert(path, 'path should not be empty'); assert(typeof path === 'string', 'path should be a string'); return path.trimStart().startsWith('oss://'); } //# sourceMappingURL=helper.js.map