t-comm
Version:
专业、稳定、纯粹的工具库
565 lines (557 loc) • 20.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var _regeneratorRuntime = require('@babel/runtime/regenerator');
var tslib_es6 = require('../tslib.es6-0d92ef81.js');
var fs = require('fs');
var path = require('path');
var axios = require('axios');
var fs_collectDirFiles = require('../fs/collect-dir-files.js');
require('../nodejs/fs.js');
require('../nodejs/path.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty);
var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultLegacy(_regeneratorRuntime);
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
var path__namespace = /*#__PURE__*/_interopNamespace(path);
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
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__namespace.relative(baseDir, fullPath)
};
});
}
/**
* 读取文件内容,二进制文件自动 base64 编码
*/
function readFileContent(filePath) {
var ext = path__namespace.extname(filePath).toLowerCase();
if (BINARY_EXTS.has(ext)) {
return fs__namespace.readFileSync(filePath).toString('base64');
}
return fs__namespace.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__default["default"]({}, 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__default["default"]({}, 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 tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee() {
var res;
return _regeneratorRuntime__default["default"].wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 1;
return axios__default["default"]({
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 tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee2() {
var res;
return _regeneratorRuntime__default["default"].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 tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee3() {
var res, files;
return _regeneratorRuntime__default["default"].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 tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee4() {
var BATCH_FILE_COUNT, i, batch, res;
return _regeneratorRuntime__default["default"].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 tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].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__default["default"].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__namespace.resolve(directory);
if (fs__namespace.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__namespace.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 = fs_collectDirFiles.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]]);
}));
}
exports.deployPages = deployPages;