UNPKG

@yuntools/ali-oss

Version:

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

1,117 lines (1,094 loc) 40.6 kB
/** * @yuntools/ali-oss * 阿里云 OSS 命令行工具 ossutil 封装,支持 ESM,CJS 导入,提供 TypeScript 类型定义 * * @version 16.1.4 * @author waiting * @license MIT * @link https://github.com/waitingsong/yuntools#readme */ 'use strict'; var assert = require('node:assert/strict'); var node_fs = require('node:fs'); var promises = require('node:fs/promises'); var node_os = require('node:os'); var node_path = require('node:path'); var rxrunscript = require('rxrunscript'); var https = require('https'); var node_crypto = require('node:crypto'); var rxjs = require('rxjs'); var assert$1 = require('node:assert'); exports.PlaceholderKey = void 0; (function (PlaceholderKey) { PlaceholderKey["src"] = "__src__"; PlaceholderKey["dest"] = "__dest__"; PlaceholderKey["target"] = "__target__"; PlaceholderKey["bucket"] = "bucket"; PlaceholderKey["bucketName"] = "bucketname"; /** * 对于远程目录进行编码,并且添加 `oss://` 前缀 * 不适用于本地目录 */ PlaceholderKey["encodeSource"] = "encodeSource"; /** * 对于远程目录进行编码,并且添加 `oss://` 前缀 */ PlaceholderKey["encodeTarget"] = "encodeTarget"; })(exports.PlaceholderKey || (exports.PlaceholderKey = {})); exports.ACLKey = void 0; (function (ACLKey) { /** 继承Bucket的读写权限 */ ACLKey["default"] = "default"; /** 有该Bucket的拥有者可以对该Bucket内的文件进行读写操作,其他人无法访问该Bucket内的文件 */ ACLKey["private"] = "private"; /** * 只有Bucket拥有者可以对该Bucket内的文件进行写操作,其他用户(包括匿名访问者)都可以对该Bucket中的文件进行读操作。 * 这有可能造成您数据的外泄以及费用激增,如果被人恶意写入违法信息还可能会侵害您的合法权益。 * 除特殊场景外,不建议您配置此权限 */ ACLKey["publicRead"] = "public-read"; /** * 任何人(包括匿名访问者)都可以对该Bucket内文件进行读写操作。 * 这有可能造成您数据的外泄以及费用激增, * \*\*请谨慎操作\*\* */ ACLKey["publicReadWrite"] = "public-read-write"; })(exports.ACLKey || (exports.ACLKey = {})); exports.DataKey = void 0; (function (DataKey) { DataKey["elapsed"] = "elapsed"; DataKey["averageSpeed"] = "averageSpeed"; DataKey["acl"] = "ACL"; DataKey["acceptRanges"] = "Accept-Ranges"; DataKey["contentLength"] = "Content-Length"; DataKey["contentMd5"] = "Content-Md5"; DataKey["contentType"] = "Content-Type"; DataKey["etag"] = "Etag"; DataKey["lastModified"] = "Last-Modified"; DataKey["owner"] = "Owner"; DataKey["xOssHashCrc64ecma"] = "X-Oss-Hash-Crc64ecma"; DataKey["xOssObjectType"] = "X-Oss-Object-Type"; DataKey["xOssStorageClass"] = "X-Oss-Storage-Class"; DataKey["link"] = "link"; DataKey["httpUrl"] = "httpUrl"; DataKey["httpShareUrl"] = "httpShareUrl"; DataKey["succeedTotalNumber"] = "succeedTotalNumber"; DataKey["succeedTotalSize"] = "succeedTotalSize"; DataKey["uploadDirs"] = "uploadDirs"; DataKey["uploadFiles"] = "uploadFiles"; /** sync between cloud */ DataKey["copyObjects"] = "copyObjects"; DataKey["downloadObjects"] = "downloadObjects"; })(exports.DataKey || (exports.DataKey = {})); exports.FnKey = void 0; (function (FnKey) { FnKey["cp"] = "cp"; FnKey["download"] = "download"; FnKey["link"] = "createSymlink"; FnKey["mkdir"] = "mkdir"; FnKey["mv"] = "mv"; FnKey["pathExists"] = "pathExists"; FnKey["probeUpload"] = "probeUpload"; FnKey["rm"] = "rm"; FnKey["rmrf"] = "rmrf"; FnKey["sign"] = "sign"; FnKey["stat"] = "stat"; FnKey["syncCloud"] = "syncCloud"; FnKey["syncLocal"] = "syncLocal"; FnKey["syncRemote"] = "syncRemote"; FnKey["upload"] = "upload"; })(exports.FnKey || (exports.FnKey = {})); exports.CmdKey = void 0; (function (CmdKey) { CmdKey["cp"] = "cp"; CmdKey["download"] = "cp"; CmdKey["link"] = "create-symlink"; CmdKey["createSymlink"] = "create-symlink"; CmdKey["mkdir"] = "mkdir"; CmdKey["mv"] = "mv"; CmdKey["probeUpload"] = "probe"; CmdKey["rm"] = "rm"; CmdKey["rmrf"] = "rm"; CmdKey["sign"] = "sign"; CmdKey["stat"] = "stat"; CmdKey["syncCloud"] = "sync"; CmdKey["syncLocal"] = "sync"; CmdKey["syncRemote"] = "sync"; CmdKey["upload"] = "cp"; })(exports.CmdKey || (exports.CmdKey = {})); /** 扁担参数名映射 */ exports.MKey = void 0; (function (MKey) { MKey["accessKeyId"] = "access-key-id"; MKey["accessKeySecret"] = "access-key-secret"; MKey["stsToken"] = "sts-token"; /** 设置分片大小,单位为字节 */ MKey["partSize"] = "part-size"; /** 文件名称的编码方式。取值为url。如果不指定该选项,则表示文件名称未经过编码 */ MKey["encodingType"] = "encoding-type"; /** 上传链接子目录,默认不上传 */ MKey["enableSymlinkDir"] = "enable-symlink-dir"; /** 批量操作时不忽略错误 */ MKey["disableIgnoreError"] = "disable-ignore-error"; /** 仅上传当前目录下的文件,忽略子目录及子目录下的文件 */ MKey["onlyCurrentDir"] = "only-current-dir"; /** 设置断点续传文件的大小阈值,单位为字节 */ MKey["bigfileThreshold"] = "bigfile-threshold"; /** 指定断点续传记录信息所在的目录 */ MKey["checkpointDir"] = "checkpoint-dir"; /** 指定保存上传文件时的快照信息所在的目录。在下一次上传文件时,ossutil会读取指定目录下的快照信息进行增量上传 */ MKey["snapshotPath"] = "snapshot-path"; /** 表示上传文件时不为目录生成Object */ MKey["disableCrc64"] = "disable-crc64"; /** Object 的指定版本。仅适用于已开启或暂停版本控制状态 Bucket下的 Object */ MKey["versionId"] = "version-id"; /** * Object 的所有版本。 * 仅适用于已开启或暂停版本控制状态 Bucket 下的 Object, * 且同一个删除示例中仅允许选择--version-id或--all-versions其中一个选项 */ MKey["allVersions"] = "all-versions"; /** 客户端读超时的时间,单位为秒,默认值为1200 */ MKey["readTimeoutSec"] = "read-timeout"; /** 客户端连接超时的时间,单位为秒,默认值为120 */ MKey["connectTimeoutSec"] = "connect-timeout"; /** 超时秒 */ MKey["timeoutSec"] = "timeout"; /** 参数名typo */ MKey["trafficLimit"] = "trafic-limit"; /* 不对cloud_url中携带的正斜线(/)进行编码 */ MKey["disableEncodeSlash"] = "disable-encode-slash"; })(exports.MKey || (exports.MKey = {})); exports.Msg = void 0; (function (Msg) { Msg["accessDenied"] = "AccessDenied"; Msg["cloudFileAlreadyExists"] = "Cloud file already exists"; Msg["cloudConfigFileNotExists"] = "Cloud config file not exists"; Msg["noSuchBucket"] = "NoSuchBucket"; })(exports.Msg || (exports.Msg = {})); // eslint-disable-next-line no-shadow-restricted-names const undefined$1 = void 0; /** * @link https://help.aliyun.com/document_detail/120075.html */ const configDownLinks = { darwin: 'https://gosspublic.alicdn.com/ossutil/1.7.12/ossutilmac64', linux: 'https://gosspublic.alicdn.com/ossutil/1.7.12/ossutil64', win32: 'https://gosspublic.alicdn.com/ossutil/1.7.12/ossutil64.zip', }; const cpKeys = [ exports.DataKey.elapsed, exports.DataKey.averageSpeed, exports.DataKey.succeedTotalNumber, exports.DataKey.succeedTotalSize, exports.DataKey.uploadDirs, exports.DataKey.uploadFiles, exports.DataKey.copyObjects, ]; const downloadKeys = [ exports.DataKey.elapsed, exports.DataKey.averageSpeed, exports.DataKey.succeedTotalNumber, exports.DataKey.succeedTotalSize, // DataKey.uploadDirs, // DataKey.uploadFiles, exports.DataKey.downloadObjects, ]; const regxStat = new Map([ [exports.DataKey.acl, new RegExp(`${exports.DataKey.acl}\\s+:\\s+(\\w+)`, 'u')], [exports.DataKey.acceptRanges, new RegExp(`${exports.DataKey.acceptRanges}\\s+:\\s+(\\d+)`, 'u')], [exports.DataKey.contentLength, new RegExp(`${exports.DataKey.contentLength}\\s+:\\s+(\\d+)`, 'u')], [exports.DataKey.contentMd5, new RegExp(`${exports.DataKey.contentMd5}\\s+:\\s+(\\S+)`, 'u')], [exports.DataKey.contentType, new RegExp(`${exports.DataKey.contentType}\\s+:\\s+(\\S+)`, 'u')], [exports.DataKey.etag, new RegExp(`${exports.DataKey.etag}\\s+:\\s+(\\S+)`, 'u')], [exports.DataKey.lastModified, new RegExp(`${exports.DataKey.lastModified}\\s+:\\s+(\\d+.+)`, 'u')], [exports.DataKey.owner, new RegExp(`${exports.DataKey.owner}\\s+:\\s+(\\d+)`, 'u')], [exports.DataKey.xOssHashCrc64ecma, new RegExp(`${exports.DataKey.xOssHashCrc64ecma}\\s+:\\s+(\\S+)`, 'u')], [exports.DataKey.xOssObjectType, new RegExp(`${exports.DataKey.xOssObjectType}\\s+:\\s+(\\w+)`, 'u')], [exports.DataKey.xOssStorageClass, new RegExp(`${exports.DataKey.xOssStorageClass}\\s+:\\s+(\\w+)`, 'u')], [exports.DataKey.link, new RegExp('https://[^?\\s\\n]+', 'u')], [exports.DataKey.httpUrl, new RegExp('https://[^?\\s\\n]+', 'u')], [exports.DataKey.httpShareUrl, new RegExp('https?://\\S+', 'u')], ]); const regxCommon = new Map([ [exports.DataKey.elapsed, /(\d+\.\d+)\(s\)\s+elapsed(?=\s|\n|\r\n|$)/u], /** average speed 1628000(byte/s)' */ [exports.DataKey.averageSpeed, /average\s+speed\s+(\d+)/u], // Succeed: Total num: 10, size: 2,125. OK num: 10(upload 9 files, 1 directories). [exports.DataKey.succeedTotalNumber, new RegExp('Succeed: Total num:\\s+(\\d+)', 'u')], [exports.DataKey.succeedTotalSize, new RegExp('Succeed: Total.+? size:\\s+([\\d,]+)', 'u')], [exports.DataKey.uploadDirs, new RegExp('OK num:.+?upload.+?(\\d+)\\s+directories', 'u')], [exports.DataKey.uploadFiles, new RegExp('OK num:.+?upload.+?(\\d+)\\s+files', 'u')], // Succeed: Total num: 10, size: 2,125. OK num: 10(copy 9 objects). [exports.DataKey.copyObjects, new RegExp('OK num:.+?copy\\s+?(\\d+)\\s+objects', 'u')], // "Succeed: Total num: 1, size: 493. OK num: 1(download 1 objects).\n\naverage speed 1000(byte/s)\n\n0.259723(s) elapsed" [exports.DataKey.downloadObjects, new RegExp('OK num:.+?download\\s+?(\\d+)\\s+objects', 'u')], ]); const pickRegxMap = new Map([ ...regxCommon, ...regxStat, ]); const pickFuncMap = new Map([ [exports.DataKey.elapsed, pickString], [exports.DataKey.averageSpeed, pickNumber], [exports.DataKey.acl, pickString], [exports.DataKey.acceptRanges, pickNumber], [exports.DataKey.contentLength, pickNumber], [exports.DataKey.contentMd5, pickString], [exports.DataKey.contentType, pickString], [exports.DataKey.etag, pickString], [exports.DataKey.lastModified, pickString], [exports.DataKey.owner, pickString], [exports.DataKey.xOssHashCrc64ecma, pickString], [exports.DataKey.xOssObjectType, pickString], [exports.DataKey.xOssStorageClass, pickString], [exports.DataKey.link, pickString], [exports.DataKey.httpUrl, pickString], [exports.DataKey.httpShareUrl, pickString], [exports.DataKey.succeedTotalNumber, pickNumber], [exports.DataKey.succeedTotalSize, pickNumberStr], [exports.DataKey.uploadDirs, pickNumber], [exports.DataKey.uploadFiles, pickNumber], [exports.DataKey.copyObjects, pickNumber], [exports.DataKey.downloadObjects, pickNumber], ]); function pickString(input, rule, debug = false) { return pick(input, rule, debug); } function pickNumber(input, rule, debug = false) { const found = pick(input, rule, debug); return found ? parseInt(found, 10) : void 0; } function pickNumberStr(input, rule, debug = false) { const found = pick(input, rule, debug); return found ? found.replace(/,/ug, '').trim() : void 0; } function pick(input, rule, debug = false) { debug && console.log({ pickInput: input }); const found = input.match(rule); if (!found) { return; } debug && console.log({ pickFound: found }); if (found.length === 1) { return found[0]; } else if (found.length >= 1) { return found[1]; } } async function processResp(input$, debug = false) { let exitCode; let exitSignal; const buf$ = input$.pipe(rxjs.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; }, []), rxjs.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 rxjs.firstValueFrom(buf$); const content = res.toString('utf-8').trim(); const ret = { exitCode, exitSignal, stdout: exitCode === 0 ? content : '', stderr: exitCode === 0 ? '' : content, }; return ret; } function parseRespStdout(input, dataKeys = [exports.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 = [ exports.DataKey.uploadDirs, exports.DataKey.uploadFiles, exports.DataKey.copyObjects, ]; if (keys.includes(key)) { return value ? +value : 0; } return value; } function combineProcessRet(resp, data) { const ret = { ...resp, data, }; return ret; } 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 === exports.PlaceholderKey.src) { return; } else if (key === exports.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(exports.PlaceholderKey.src); const dest = pp.get(exports.PlaceholderKey.dest); if (src && typeof src === 'string') { ps.push(src); } if (dest && typeof dest === 'string') { ps.push(dest); } return ps; } function preGenParams(paramMap) { const encodeSource = paramMap.get(exports.PlaceholderKey.encodeSource); paramMap.delete(exports.PlaceholderKey.encodeSource); const encodeTarget = paramMap.get(exports.PlaceholderKey.encodeTarget); paramMap.delete(exports.PlaceholderKey.encodeTarget); if (encodeTarget || encodeSource) { paramMap.set(exports.MKey.encodingType, 'url'); } paramMap.delete(exports.PlaceholderKey.bucket); // ensure bucket is not in the params paramMap.delete('src'); paramMap.delete('dest'); paramMap.delete('target'); return paramMap; } 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(exports.MKey, key)) { // @ts-ignore const mkey = exports.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; } async function writeConfigFile(config, filePath) { const sha1 = node_crypto.createHash('sha1'); const hash = sha1.update(JSON.stringify(config)).digest('hex'); const path = filePath ?? node_path.join(node_os.tmpdir(), `${hash}.tmp`); try { const exists = (await promises.stat(path)).isFile(); if (exists) { return { path, hash }; } } catch (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 promises.writeFile(path, arr.join('\n')); return { path, hash }; } async function validateConfigPath(config) { assert(config, 'config file path is empty'); const exists = (await promises.stat(config)).isFile(); assert(exists, `config file ${config} not exists`); } async function downloadOssutil(srcLink, targetPath = node_path.join(node_os.homedir(), 'ossutil')) { assert(srcLink, 'srcLink is empty'); const file = node_fs.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 (node_os.platform() !== 'win32') { await setBinExecutable(targetPath); } return targetPath; } async function setBinExecutable(file) { const ret = await rxjs.firstValueFrom(rxrunscript.run(`chmod +x ${file}`)); return ret; } function encodeInputPath(input, encode = false) { const str = input.replace(/\\/ug, '/'); const ret = encode === true ? encodeURIComponent(str).replace(/'/ug, '%27') : input; return ret; } function commonProcessInputMap(input, initOptions, globalConfig) { assert(input, 'input is required'); const ret = mergeParams(input, initOptions, globalConfig); const encodeSrc = ret.get(exports.PlaceholderKey.encodeSource); const encodeTarget = ret.get(exports.PlaceholderKey.encodeTarget); const src = processInputAsEncodedCloudUrl(input.src, input.bucket, encodeSrc); const dest = processInputAsEncodedCloudUrl(input.target, input.bucket, encodeTarget); ret.set(exports.PlaceholderKey.src, src); ret.set(exports.PlaceholderKey.dest, dest); return ret; } 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://` */ function pathIsCloudUrl(path) { assert(path, 'path should not be empty'); assert(typeof path === 'string', 'path should be a string'); return path.trimStart().startsWith('oss://'); } const initBaseOptions = { accessKeyId: '', accessKeySecret: '', bucket: '', connectTimeoutSec: undefined$1, encodeSource: false, encodeTarget: true, readTimeoutSec: undefined$1, stsToken: '', endpoint: '', loglevel: undefined$1, }; const initOptions$c = { ...initBaseOptions, target: '', }; /** * src will be delete */ async function processInputWoSrc(input, initOptionsInput, globalConfig) { const map = commonProcessInputMap(input, initOptionsInput, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); map.delete(exports.PlaceholderKey.src); return map; } const initOptions$b = { ...initBaseOptions, force: false, recursive: false, target: '', src: '', }; const initOptions$a = { ...initOptions$b, bigfileThreshold: undefined$1, checkpointDir: undefined$1, disableCrc64: undefined$1, disableIgnoreError: undefined$1, enableSymlinkDir: undefined$1, onlyCurrentDir: undefined$1, partSize: undefined$1, snapshotPath: undefined$1, acl: undefined$1, exclude: undefined$1, force: false, jobs: undefined$1, include: undefined$1, maxupspeed: 0, meta: undefined$1, parallel: undefined$1, payer: undefined$1, recursive: false, tagging: undefined$1, update: false, }; async function processInput$9(input, globalConfig) { const map = commonProcessInputMap(input, initOptions$a, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); const src = map.get(exports.PlaceholderKey.src); assert$1(src, 'src is required'); assert$1(typeof src === 'string'); return map; } const initOptions$9 = { ...initOptions$a, encodeSource: true, encodeTarget: false, }; async function processInput$8(input, globalConfig) { const { src, target, encodeTarget } = input; assert$1(src, 'src is required'); assert$1(target, 'target is required'); // const pathSrc = typeof encodeSource === 'undefined' || encodeSource === true // ? encodeInputPath(src, true) // : src const path = typeof encodeTarget === 'undefined' || encodeTarget === true ? encodeInputPath(target, true) : target; const opts = { ...input, // src: pathSrc, target: path, // encodeSource: false, encodeTarget: false, }; const map = commonProcessInputMap(opts, initOptions$9, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); return map; } const initOptions$8 = { ...initOptions$b, target: '', src: '', encodeSource: true, }; async function processInput$7(input, globalConfig) { const map = commonProcessInputMap(input, initOptions$8, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); const src = map.get(exports.PlaceholderKey.src); assert$1(src, 'src is required'); assert$1(typeof src === 'string'); assert$1(src.startsWith('oss://'), 'src must start with oss://'); const dest = map.get(exports.PlaceholderKey.dest); assert$1(dest, 'dest is required'); // 命令行参数是目标在前,源在后!! map.set(exports.PlaceholderKey.src, dest); map.set(exports.PlaceholderKey.dest, src); return map; } const initOptions$7 = { ...initBaseOptions, target: '', }; async function processInput$6(input, globalConfig) { const map = await processInputWoSrc(input, initOptions$7, globalConfig); return map; } const initOptions$6 = { ...initBaseOptions, }; async function processInput$5(input, globalConfig) { const map = commonProcessInputMap(input, initOptions$6, globalConfig); map.delete(exports.PlaceholderKey.dest); map.delete(exports.PlaceholderKey.src); map.delete(exports.PlaceholderKey.encodeSource); map.delete(exports.PlaceholderKey.encodeTarget); const bucketName = map.get(exports.PlaceholderKey.bucket); assert$1(bucketName, 'bucket is required'); map.set(exports.PlaceholderKey.bucketName, bucketName); map.set('upload', true); return map; } const initOptions$5 = { ...initOptions$b, target: '', allVersions: undefined$1, versionId: undefined$1, exclude: undefined$1, include: undefined$1, multipart: undefined$1, }; async function processInput$4(input, globalConfig) { const map = await processInputWoSrc(input, initOptions$5, globalConfig); map.set('force', true); return map; } const initOptions$4 = { ...initOptions$5, target: '', }; async function processInput$3(input, globalConfig) { const map = commonProcessInputMap(input, initOptions$4, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); map.delete(exports.PlaceholderKey.src); map.set('force', true); map.set('recursive', true); return map; } const initOptions$3 = { ...initBaseOptions, src: '', disableEncodeSlash: false, timeoutSec: 60, trafficLimit: undefined$1, versionId: undefined$1, }; async function processInput$2(input, globalConfig) { const opts = { ...input, encodeSource: true, }; const map = commonProcessInputMap(opts, initOptions$3, globalConfig); assert$1(map.get(exports.PlaceholderKey.src), 'dest is required'); map.delete(exports.PlaceholderKey.dest); return map; } const initOptions$2 = { ...initBaseOptions, target: '', versionId: undefined$1, payer: undefined$1, }; async function processInput$1(input, globalConfig) { const map = await processInputWoSrc(input, initOptions$2, globalConfig); return map; } const initOptions$1 = { ...initOptions$a, encodeSource: true, }; async function processInput(input, globalConfig) { const { src, encodeSource } = input; assert$1(src, 'src is required'); const path = typeof encodeSource === 'undefined' || encodeSource === true ? encodeInputPath(src, true) : src; const opts = { ...input, src: path, encodeSource: false, }; const map = commonProcessInputMap(opts, initOptions$1, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); return map; } const initOptions = { ...initOptions$1, delete: false, force: true, }; async function processInputCloud(input, globalConfig) { const { encodeSource, encodeTarget } = input; const opts = { ...input, encodeSource: encodeSource ?? true, encodeTarget: encodeTarget ?? true, }; const map = commonProcessInputMap(opts, initOptions, globalConfig); assert$1(pathIsCloudUrl(map.get(exports.PlaceholderKey.dest)), 'dest should be a cloud url'); assert$1(pathIsCloudUrl(map.get(exports.PlaceholderKey.src)), 'src should be a cloud url'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); return map; } async function processInputRemote(input, globalConfig) { const { src, encodeSource } = input; assert$1(src, 'src is required'); const srcNew = typeof encodeSource === 'undefined' || encodeSource === true ? encodeInputPath(src, true) : src; const opts = { ...input, src: srcNew, encodeSource: false, }; const map = commonProcessInputMap(opts, initOptions, globalConfig); assert$1(pathIsCloudUrl(map.get(exports.PlaceholderKey.dest)), 'dest should be a cloud url'); assert$1(!pathIsCloudUrl(map.get(exports.PlaceholderKey.src)), 'src should not be a cloud url'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); return map; } async function processInputLocal(input, globalConfig) { const { target: dst, encodeSource, encodeTarget } = input; assert$1(dst, 'target is required'); const encode2 = !!(typeof encodeTarget === 'undefined' || encodeTarget === true); const dstNew = encode2 ? encodeInputPath(dst, true) : dst; const opts = { ...input, target: dstNew, encodeSource: encodeSource ?? true, encodeTarget: false, }; const map = commonProcessInputMap(opts, initOptions, globalConfig); assert$1(map.get(exports.PlaceholderKey.dest), 'dest is required'); assert$1(map.get(exports.PlaceholderKey.src), 'src is required'); assert$1(!pathIsCloudUrl(map.get(exports.PlaceholderKey.dest)), 'dest should not be a cloud url'); assert$1(pathIsCloudUrl(map.get(exports.PlaceholderKey.src)), 'src should be a cloud url'); const bucket = map.get(exports.PlaceholderKey.bucket); assert$1(bucket, 'bucket is required'); assert$1(typeof bucket === 'string', 'bucket must be string'); map.set('encodeTarget', encode2); return map; } const processInputFnMap = new Map([ [exports.FnKey.cp, processInput$9], [exports.FnKey.download, processInput$8], [exports.FnKey.link, processInput$7], [exports.FnKey.mkdir, processInput$6], [exports.FnKey.probeUpload, processInput$5], [exports.FnKey.rm, processInput$4], [exports.FnKey.rmrf, processInput$3], [exports.FnKey.sign, processInput$2], [exports.FnKey.stat, processInput$1], [exports.FnKey.syncCloud, processInputCloud], [exports.FnKey.syncLocal, processInputLocal], [exports.FnKey.syncRemote, processInputRemote], [exports.FnKey.upload, processInput], ]); /** * 阿里云 OSS 服务接口, * 基于命令行工具 ossutil 封装 */ class OssClient { configInput; cmd; debug = false; configPath = ''; config = void 0; constructor( /** * 配置参数或者配置文件路径 * @default ~/.ossutilconfig */ configInput, cmd = 'ossutil') { this.configInput = configInput; this.cmd = cmd; if (typeof configInput === 'string') { this.configPath = configInput; const pathExists = node_fs.statSync(this.configPath).isFile(); assert(pathExists, `${exports.Msg.cloudConfigFileNotExists}: ${this.configPath}`); } else if (typeof configInput === 'object') { this.config = configInput; } else { this.configPath = node_path.join(node_os.homedir(), '.ossutilconfig'); const pathExists = node_fs.statSync(this.configPath).isFile(); assert(pathExists, `${exports.Msg.cloudConfigFileNotExists}: ${this.configPath}`); } } /** * 删除 OSS 配置文件 */ async destroy() { const { configPath } = this; await promises.rm(configPath); } /** * 在远程之间拷贝文件。 * 若 force 为空或者 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @note 下载文件使用 `download()` * @link https://help.aliyun.com/document_detail/120057.html */ async cp(options) { if (!options.force) { const statRet = await this.stat(options); if (!statRet.exitCode) { const ret = { exitCode: 1, exitSignal: '', stdout: '', stderr: `${exports.Msg.cloudFileAlreadyExists}: "${options.target}"`, data: void 0, }; return ret; } } const ret = await this.runner(options, exports.FnKey.cp, cpKeys); return ret; } /** * 创建软链接 * @link https://help.aliyun.com/document_detail/120059.html */ async createSymlink(options) { const keys = [exports.DataKey.elapsed]; const ret = await this.runner(options, exports.FnKey.link, keys); return ret; } /** * 下载远程文件到本地 * 若 force 为空或者 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @link https://help.aliyun.com/document_detail/120057.html */ async download(options) { const ret = await this.runner(options, exports.FnKey.download, downloadKeys); return ret; } /** * 创建目录 * @link https://help.aliyun.com/document_detail/120062.html */ async mkdir(options) { const keys = [exports.DataKey.elapsed]; const ret = await this.runner(options, exports.FnKey.mkdir, keys); return ret; } /** * 移动云端的 OSS 对象 * 流程为先 `cp()` 然后 `rm()` */ async mv(options) { const opts = { ...options, encodeSource: true, }; const cp = await this.cp(opts); if (cp.exitCode) { return cp; } const opts2 = { ...options, target: options.src, }; const remove = await this.rm(opts2); if (remove.exitCode) { return remove; } const statRet = await this.stat(opts); return statRet; } /** * OSS 远程路径是否存在 */ async pathExists(options) { const statRet = await this.stat(options); const exists = !!(statRet.exitCode === 0 && statRet.data); return exists; } /** * 探测上传状态 * @link https://help.aliyun.com/document_detail/120061.html */ async probeUpload(options) { const keys = [exports.DataKey.elapsed]; const ret = await this.runner(options, exports.FnKey.probeUpload, keys); return ret; } /** * 删除云对象,不支持删除 bucket 本身 * 如果在 recusive 为 false 时删除目录,则目录参数值必须以 '/' 结尾,否则不会删除成功 * @link https://help.aliyun.com/document_detail/120053.html */ async rm(options) { const keys = [exports.DataKey.elapsed, exports.DataKey.averageSpeed]; const ret = await this.runner(options, exports.FnKey.rm, keys); return ret; } /** * 递归删除,相当于 `rm -rf` * @link https://help.aliyun.com/document_detail/120053.html */ async rmrf(options) { const keys = [exports.DataKey.elapsed, exports.DataKey.averageSpeed]; const ret = await this.runner(options, exports.FnKey.rmrf, keys); return ret; } /** * sign(生成签名URL) * @link https://help.aliyun.com/document_detail/120064.html */ async sign(options) { const keys = [exports.DataKey.elapsed, exports.DataKey.httpUrl, exports.DataKey.httpShareUrl]; const ret = await this.runner(options, exports.FnKey.sign, keys); if (ret.data?.httpUrl) { ret.data.link = options.disableEncodeSlash ? ret.data.httpUrl : decodeURIComponent(ret.data.httpUrl); } return ret; } /** * 查看 Bucket 和 Object 信息 * @link https://help.aliyun.com/document_detail/120054.html */ async stat(options) { const keys = [exports.DataKey.elapsed].concat(Array.from(regxStat.keys())); const ret = await this.runner(options, exports.FnKey.stat, keys); return ret; } /** * 在 OSS 之间同步文件 * - force 参数默认 true * - 若 force 为 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @link https://help.aliyun.com/document_detail/256354.html */ async syncCloud(options) { const ret = await this.runner(options, exports.FnKey.syncCloud, cpKeys); return ret; } /** * 同步 OSS 文件到本地 * - force 参数默认 true * - 若 force 为 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @link https://help.aliyun.com/document_detail/256352.html */ async syncLocal(options) { const ret = await this.runner(options, exports.FnKey.syncLocal, cpKeys); return ret; } /** * 同步本地文件到 OSS * - force 参数默认 true * - 若 force 为 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @link https://help.aliyun.com/document_detail/193394.html */ async syncRemote(options) { const ret = await this.runner(options, exports.FnKey.syncRemote, cpKeys); return ret; } /** * 上传本地文件到 OSS * 若 force 为空或者 false,且目标文件存在时会卡在命令行提示输入阶段(无显示)最后导致超时异常 * @link https://help.aliyun.com/document_detail/120057.html */ async upload(options) { if (!options.force) { const statRet = await this.stat(options); if (!statRet.exitCode) { const ret = { exitCode: 1, exitSignal: '', stdout: '', stderr: `${exports.Msg.cloudFileAlreadyExists}: "${options.target}"`, data: void 0, }; return ret; } } const ret = await this.runner(options, exports.FnKey.upload, cpKeys); return ret; } async runner(options, fnKey, retKeys) { assert(fnKey, 'fnKey is required'); assert(retKeys, 'retKeys is required'); assert(retKeys.length, 'retKeys must be an array'); // @ts-ignore const cmdKey = exports.CmdKey[fnKey]; assert(cmdKey, 'cmdKey is required'); const ps = await this.genCliParams(fnKey, options); const resp$ = rxrunscript.run(`${this.cmd} ${cmdKey} ${ps.join(' ')} `); const res = await processResp(resp$, this.debug); const data = parseRespStdout(res, retKeys, this.debug); const ret = combineProcessRet(res, data); return ret; } async genCliParams(fnKey, options) { const func = processInputFnMap.get(fnKey); assert(typeof func === 'function', `${fnKey} is not a function`); const map = await func(options, this.config); assert(map); const ret = genParams(this.configPath, map); return ret; } } exports.OssClient = OssClient; exports.configDownLinks = configDownLinks; exports.downloadOssutil = downloadOssutil; exports.encodeInputPath = encodeInputPath; exports.initBaseOptions = initBaseOptions; exports.initOptions = initOptions$c; exports.processInputWoSrc = processInputWoSrc; exports.setBinExecutable = setBinExecutable; exports.validateConfigPath = validateConfigPath; exports.writeConfigFile = writeConfigFile; //# sourceMappingURL=index.cjs.map