UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

535 lines (532 loc) 18.9 kB
import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _regeneratorRuntime from '@babel/runtime/regenerator'; import { _ as __awaiter } from '../tslib.es6-848120af.js'; import * as fs from 'fs'; import * as path from 'path'; import axios from 'axios'; import { collectFilesSync } from '../fs/collect-dir-files.mjs'; import '../nodejs/fs.mjs'; import '../nodejs/path.mjs'; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var BASE_URL = 'https://pages.woa.com'; var MAX_BATCH_SIZE = 4 * 1024 * 1024; // 4MB,留 1MB 余量给 JSON 结构 // 二进制文件扩展名(需要 base64 编码) var BINARY_EXTS = new Set(['.ico', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.avif', '.woff', '.woff2', '.ttf', '.eot', '.otf', '.mp3', '.mp4', '.webm', '.ogg', '.wav', '.zip', '.gz', '.br', '.tar', '.rar', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.wasm']); /** * 补全域名后缀 */ function normalizeCname(cname) { if (!cname.includes('.')) { return "".concat(cname, ".pages.woa.com"); } if (!cname.endsWith('.woa.com')) { return "".concat(cname, ".pages.woa.com"); } return cname; } /** * 将 collectFilesSync 返回的绝对路径列表转换为 FileEntry 列表 */ function toFileEntries(filePaths, baseDir) { return filePaths.map(function (fullPath) { return { fullPath: fullPath, relativePath: path.relative(baseDir, fullPath) }; }); } /** * 读取文件内容,二进制文件自动 base64 编码 */ function readFileContent(filePath) { var ext = path.extname(filePath).toLowerCase(); if (BINARY_EXTS.has(ext)) { return fs.readFileSync(filePath).toString('base64'); } return fs.readFileSync(filePath, 'utf-8'); } /** * 将文件列表按大小分批 */ function splitIntoBatches(files) { var batches = []; var currentBatch = {}; var currentSize = 0; var _iterator = _createForOfIteratorHelper(files), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var file = _step.value; var content = readFileContent(file.fullPath); var entryJson = JSON.stringify(_defineProperty({}, file.relativePath, content)); var size = Buffer.byteLength(entryJson); // 单个文件超过限制,单独一批 if (size > MAX_BATCH_SIZE) { if (Object.keys(currentBatch).length > 0) { batches.push(currentBatch); currentBatch = {}; currentSize = 0; } batches.push(_defineProperty({}, file.relativePath, content)); continue; } if (currentSize + size > MAX_BATCH_SIZE && Object.keys(currentBatch).length > 0) { batches.push(currentBatch); currentBatch = {}; currentSize = 0; } currentBatch[file.relativePath] = content; currentSize += size; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } if (Object.keys(currentBatch).length > 0) { batches.push(currentBatch); } return batches; } /** * 发送请求到 OA Pages API */ function request(method, urlPath, data, apiKey) { return __awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { var res; return _regeneratorRuntime.wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: _context.next = 1; return axios({ method: method, url: "".concat(BASE_URL).concat(urlPath), data: data, headers: { 'X-Api-Key': apiKey }, validateStatus: function validateStatus() { return true; }, maxBodyLength: Infinity, maxContentLength: Infinity }); case 1: res = _context.sent; return _context.abrupt("return", { status: res.status, data: res.data }); case 2: case "end": return _context.stop(); } }, _callee); })); } /** * 检查网站是否已存在 */ function checkSiteExists(cname, apiKey) { return __awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { var res; return _regeneratorRuntime.wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: _context2.next = 1; return request('GET', "/api/repos/".concat(cname), {}, apiKey); case 1: res = _context2.sent; return _context2.abrupt("return", res.status === 200); case 2: case "end": return _context2.stop(); } }, _callee2); })); } /** * 获取远程网站的所有文件路径 */ function listRemoteFiles(cname, apiKey) { var _a; return __awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() { var res, files; return _regeneratorRuntime.wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: _context3.next = 1; return request('GET', "/api/sites/".concat(cname, "/files"), {}, apiKey); case 1: res = _context3.sent; if (!(res.status !== 200)) { _context3.next = 2; break; } throw new Error("\u83B7\u53D6\u8FDC\u7A0B\u6587\u4EF6\u5217\u8868\u5931\u8D25: ".concat(res.status, " ").concat(JSON.stringify(res.data))); case 2: files = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.files) || []; return _context3.abrupt("return", files.map(function (f) { return f.path; }).filter(Boolean)); case 3: case "end": return _context3.stop(); } }, _callee3); })); } /** * 判断文件路径是否匹配任一前缀 */ function matchPrefix(filePath, prefixes) { if (prefixes.length === 0) return true; return prefixes.some(function (prefix) { return filePath.startsWith(prefix); }); } /** * 将 cleanPrefix 选项归一化为数组 */ function normalizeCleanPrefix(cleanPrefix) { if (Array.isArray(cleanPrefix)) return cleanPrefix; if (cleanPrefix) return [cleanPrefix]; return []; } /** * 删除远程网站文件,自动按批次发送,避免单次请求过大 */ function deleteRemoteFiles(cname, apiKey, filesToDelete) { return __awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() { var BATCH_FILE_COUNT, i, batch, res; return _regeneratorRuntime.wrap(function (_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: if (!(filesToDelete.length === 0)) { _context4.next = 1; break; } return _context4.abrupt("return"); case 1: // 按数量分批,单批最多 500 个文件路径 BATCH_FILE_COUNT = 500; i = 0; case 2: if (!(i < filesToDelete.length)) { _context4.next = 5; break; } batch = filesToDelete.slice(i, i + BATCH_FILE_COUNT); _context4.next = 3; return request('DELETE', "/api/sites/".concat(cname, "/files"), { files: batch }, apiKey); case 3: res = _context4.sent; if (!(res.status !== 200)) { _context4.next = 4; break; } throw new Error("\u5220\u9664\u8FDC\u7A0B\u6587\u4EF6\u5931\u8D25: ".concat(res.status, " ").concat(JSON.stringify(res.data))); case 4: i += BATCH_FILE_COUNT; _context4.next = 2; break; case 5: case "end": return _context4.stop(); } }, _callee4); })); } /** * 部署本地目录到 OA Pages * * @param options - 部署配置 * @returns 部署结果 * * @example * ```ts * import { deployPages } from 't-comm'; * * // 基本用法 * const result = await deployPages({ * cname: 't-comm.pages.woa.com', * directory: 'docs/.vitepress/dist', * visibility: 'public', * }); * * // 传入 API Key(不依赖环境变量) * const result = await deployPages({ * cname: 't-comm', * directory: './dist', * apiKey: 'oa-pages-key-xxxxxxxx', * visibility: 'tof', * description: '我的网站', * }); * * // 部署前先清理远程历史文件(避免容量超限上传失败) * await deployPages({ * cname: 't-comm.pages.woa.com', * directory: './dist', * cleanBeforeDeploy: true, * }); * * // 仅清理 assets/ 前缀下的文件,再上传 * await deployPages({ * cname: 't-comm.pages.woa.com', * directory: './dist', * cleanBeforeDeploy: true, * cleanPrefix: 'assets/', * }); * ``` */ function deployPages(options) { return __awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() { var directory, visibility, description, dryRun, cleanBeforeDeploy, cleanPrefix, apiKey, cname, distDir, filePaths, files, batches, result, _exists, remoteFiles, prefixes, filesToDelete, exists, _remoteFiles, _prefixes, _filesToDelete, i, res, createBody, createRes, _i, _res, patchRes, _t2; return _regeneratorRuntime.wrap(function (_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: directory = options.directory, visibility = options.visibility, description = options.description, dryRun = options.dryRun, cleanBeforeDeploy = options.cleanBeforeDeploy, cleanPrefix = options.cleanPrefix; // 获取 API Key:优先使用传入参数,其次环境变量 apiKey = options.apiKey || process.env.OA_PAGES_API_KEY; if (apiKey) { _context5.next = 1; break; } return _context5.abrupt("return", { success: false, url: '', adminUrl: '', cname: '', totalFiles: 0, totalBatches: 0, error: '未提供 apiKey 参数,且环境变量 OA_PAGES_API_KEY 未配置。请访问 https://pages.woa.com/admin 申请 API Key' }); case 1: // 补全域名 cname = normalizeCname(options.cname); // 解析目录路径 distDir = path.resolve(directory); if (fs.existsSync(distDir)) { _context5.next = 2; break; } return _context5.abrupt("return", { success: false, url: '', adminUrl: '', cname: cname, totalFiles: 0, totalBatches: 0, error: "\u76EE\u5F55\u4E0D\u5B58\u5728: ".concat(distDir) }); case 2: if (fs.statSync(distDir).isDirectory()) { _context5.next = 3; break; } return _context5.abrupt("return", { success: false, url: '', adminUrl: '', cname: cname, totalFiles: 0, totalBatches: 0, error: "\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ".concat(distDir) }); case 3: // 使用 collectFilesSync 递归获取所有文件 filePaths = collectFilesSync(distDir); if (!(filePaths.length === 0)) { _context5.next = 4; break; } return _context5.abrupt("return", { success: false, url: '', adminUrl: '', cname: cname, totalFiles: 0, totalBatches: 0, error: '目录为空,无文件可部署' }); case 4: // 转换为 FileEntry 格式 files = toFileEntries(filePaths, distDir); // 分批 batches = splitIntoBatches(files); result = { success: true, url: "https://".concat(cname), adminUrl: "https://pages.woa.com/admin/".concat(cname), cname: cname, totalFiles: files.length, totalBatches: batches.length }; if (!dryRun) { _context5.next = 12; break; } if (!cleanBeforeDeploy) { _context5.next = 11; break; } _context5.prev = 5; _context5.next = 6; return checkSiteExists(cname, apiKey); case 6: _exists = _context5.sent; if (!_exists) { _context5.next = 8; break; } _context5.next = 7; return listRemoteFiles(cname, apiKey); case 7: remoteFiles = _context5.sent; prefixes = normalizeCleanPrefix(cleanPrefix); filesToDelete = remoteFiles.filter(function (p) { return matchPrefix(p, prefixes); }); result.deletedFiles = filesToDelete.length; _context5.next = 9; break; case 8: result.deletedFiles = 0; case 9: _context5.next = 11; break; case 10: _context5.prev = 10; _context5["catch"](5); case 11: return _context5.abrupt("return", result); case 12: _context5.next = 13; return checkSiteExists(cname, apiKey); case 13: exists = _context5.sent; if (!(cleanBeforeDeploy && exists)) { _context5.next = 18; break; } _context5.prev = 14; _context5.next = 15; return listRemoteFiles(cname, apiKey); case 15: _remoteFiles = _context5.sent; _prefixes = normalizeCleanPrefix(cleanPrefix); _filesToDelete = _remoteFiles.filter(function (p) { return matchPrefix(p, _prefixes); }); _context5.next = 16; return deleteRemoteFiles(cname, apiKey, _filesToDelete); case 16: result.deletedFiles = _filesToDelete.length; _context5.next = 18; break; case 17: _context5.prev = 17; _t2 = _context5["catch"](14); result.success = false; result.error = (_t2 === null || _t2 === void 0 ? void 0 : _t2.message) ? _t2.message : String(_t2); return _context5.abrupt("return", result); case 18: if (!exists) { _context5.next = 23; break; } i = 0; case 19: if (!(i < batches.length)) { _context5.next = 22; break; } _context5.next = 20; return request('PUT', "/api/sites/".concat(cname), { files: batches[i] }, apiKey); case 20: res = _context5.sent; if (!(res.status !== 200)) { _context5.next = 21; break; } result.success = false; result.error = "\u66F4\u65B0\u7B2C ".concat(i + 1, " \u6279\u6587\u4EF6\u5931\u8D25: ").concat(res.status, " ").concat(JSON.stringify(res.data)); return _context5.abrupt("return", result); case 21: i++; _context5.next = 19; break; case 22: _context5.next = 29; break; case 23: // 网站不存在,使用 POST 创建 createBody = { cname: cname, files: batches[0] }; if (description) { createBody.description = description; } _context5.next = 24; return request('POST', '/api/sites', createBody, apiKey); case 24: createRes = _context5.sent; if (!(createRes.status !== 200 && createRes.status !== 201)) { _context5.next = 25; break; } result.success = false; result.error = "\u521B\u5EFA\u7F51\u7AD9\u5931\u8D25: ".concat(createRes.status, " ").concat(JSON.stringify(createRes.data)); return _context5.abrupt("return", result); case 25: _i = 1; case 26: if (!(_i < batches.length)) { _context5.next = 29; break; } _context5.next = 27; return request('PUT', "/api/sites/".concat(cname), { files: batches[_i] }, apiKey); case 27: _res = _context5.sent; if (!(_res.status !== 200)) { _context5.next = 28; break; } result.success = false; result.error = "\u66F4\u65B0\u7B2C ".concat(_i + 1, " \u6279\u6587\u4EF6\u5931\u8D25: ").concat(_res.status, " ").concat(JSON.stringify(_res.data)); return _context5.abrupt("return", result); case 28: _i++; _context5.next = 26; break; case 29: if (!visibility) { _context5.next = 31; break; } _context5.next = 30; return request('PATCH', "/api/repos/".concat(cname), { visibility: visibility }, apiKey); case 30: patchRes = _context5.sent; if (patchRes.status !== 200) { result.error = "\u6743\u9650\u8BBE\u7F6E\u5931\u8D25\uFF08\u4F46\u6587\u4EF6\u5DF2\u4E0A\u4F20\u6210\u529F\uFF09: ".concat(patchRes.status, " ").concat(JSON.stringify(patchRes.data)); } case 31: return _context5.abrupt("return", result); case 32: case "end": return _context5.stop(); } }, _callee5, null, [[5, 10], [14, 17]]); })); } export { deployPages };