@lark-project/cli
Version:
飞书项目插件开发工具
194 lines (193 loc) • 9.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.updatePluginDescription = exports.getPluginDescriptionInfo = void 0;
const request_1 = require("../request");
const get_tool_auth_headers_1 = require("../get-tool-auth-headers");
const list_categories_1 = require("./list-categories");
const logger_1 = require("../../utils/logger");
/**
* 将纯文本转为富文本编辑器所需的完整 RichText 结构(doc + doc_html + doc_text + is_empty)
* doc 格式为飞书编辑器的 Delta JSON,doc_html 为对应的 HTML
*/
function plainTextToRichText(text) {
if (!text) {
return { doc: '', doc_html: '', doc_text: '', is_empty: true };
}
const lines = text.split('\n');
// 构造 doc (Delta JSON 格式)
const ops = [];
lines.forEach(line => {
if (line) {
ops.push({ insert: line });
}
ops.push({ insert: '\n' });
});
const doc = JSON.stringify({
'0': { ops, zoneId: '0', zoneType: 'Z' },
});
// 构造 doc_html
const htmlLines = lines.map(line => `<div class="ace-line" data-node="true" dir="auto"><span data-string="true" data-leaf="true">${escapeHtml(line)}</span><span data-string="true" data-enter="true" data-leaf="true"></span></div>`);
const doc_html = htmlLines.join('');
return { doc, doc_html, doc_text: text, is_empty: false };
}
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
/**
* 获取插件描述信息(草稿态)。
* IDL: api_app_development.GetAppDescriptionInfoRequest / Response
* GET /goapi/v5/app/development/tool/description/info?app_key=XXX&data_status=draft
*/
async function getPluginDescriptionInfo({ siteDomain, appKey, dataStatus = 'draft', }) {
return (0, request_1.request)(`${siteDomain}/goapi/v5/app/development/tool/description/info`, {
method: 'GET',
params: {
app_key: appKey,
data_status: dataStatus,
},
headers: await (0, get_tool_auth_headers_1.getToolAuthHeaders)(siteDomain),
timeout: 15000,
});
}
exports.getPluginDescriptionInfo = getPluginDescriptionInfo;
/**
* 对齐前端 BaseInfoForm handleChange 的 update_type 分类:
* ICON = 1 — icon 单独更新,顶层字段
* I18N = 2 — name/short/description/document/carousel_list 走 i18n_info[lang]
* CATEGORY = 3 — category_ids 单独更新,顶层字段
* PROTOCOL = 4 — protocol 单独更新,顶层字段
*/
var PluginDescUpdateType;
(function (PluginDescUpdateType) {
PluginDescUpdateType[PluginDescUpdateType["ICON"] = 1] = "ICON";
PluginDescUpdateType[PluginDescUpdateType["I18N"] = 2] = "I18N";
PluginDescUpdateType[PluginDescUpdateType["CATEGORY"] = 3] = "CATEGORY";
PluginDescUpdateType[PluginDescUpdateType["PROTOCOL"] = 4] = "PROTOCOL";
})(PluginDescUpdateType || (PluginDescUpdateType = {}));
function inferUpdateType(req) {
if (req.updateType)
return req.updateType;
if (req.icon)
return PluginDescUpdateType.ICON;
if (req.categoryIds)
return PluginDescUpdateType.CATEGORY;
if (req.protocol)
return PluginDescUpdateType.PROTOCOL;
return PluginDescUpdateType.I18N;
}
async function updatePluginDescription(req) {
var _a, _b, _c, _d, _e;
const { siteDomain, appKey, name, short, detailDescription, document, carouselList, icon, protocol, categoryIds, i18nLang, lang, } = req;
const updateType = inferUpdateType(req);
// 语言不能写死 zh-cn:海外插件 default_lang 常为 en-us,后端强约束「i18n_lang 必须包含
// default_lang」,硬发 ['zh-cn'] 会触发 code=46「默认语言不匹配」。故优先取插件在后端的真实
// default_lang / i18n_lang 对齐;调用方显式传入的 lang / i18nLang 最高优先,拉取失败或均缺失
// 时退回 zh-cn,保持国内既有行为不变。
let resolvedLang = lang;
let resolvedI18nLang = (i18nLang === null || i18nLang === void 0 ? void 0 : i18nLang.length) ? i18nLang : undefined;
if (!resolvedLang || !resolvedI18nLang) {
try {
const info = await getPluginDescriptionInfo({ siteDomain, appKey, dataStatus: 'draft' });
if (!resolvedLang && info.default_lang)
resolvedLang = info.default_lang;
if (!resolvedI18nLang && ((_a = info.i18n_lang) === null || _a === void 0 ? void 0 : _a.length))
resolvedI18nLang = info.i18n_lang;
}
catch (e) {
logger_1.logger.debug('fetch plugin default_lang failed, fallback to zh-cn:', e);
}
}
resolvedLang = resolvedLang || 'zh-cn';
const langs = (resolvedI18nLang === null || resolvedI18nLang === void 0 ? void 0 : resolvedI18nLang.length) ? [...resolvedI18nLang] : [resolvedLang];
// 后端要求 i18n_lang 必须包含写入语言,缺则补上
if (!langs.includes(resolvedLang))
langs.push(resolvedLang);
// 按 update_type 组装不同的请求体,对齐前端 BaseInfoTabs handleBaseInfoChange 逻辑
const data = {
app_key: appKey,
update_type: updateType,
i18n_lang: langs,
};
switch (updateType) {
case PluginDescUpdateType.ICON:
// icon 是顶层字段(isOutter=true)
data.icon = icon;
break;
case PluginDescUpdateType.CATEGORY: {
// category_ids 是顶层字段(isOutter=true)
const categoriesRes = await (0, list_categories_1.listCategories)({ siteDomain });
const validIds = new Set((_b = categoriesRes.list) === null || _b === void 0 ? void 0 : _b.filter(i => i.status === 1).map(i => i.id));
let ids = categoryIds || [];
if (ids.length === 0) {
const fallback = (_c = categoriesRes.list) === null || _c === void 0 ? void 0 : _c.find(i => { var _a; return i.status === 1 && ((_a = i.name) === null || _a === void 0 ? void 0 : _a['zh-CN']) === '其他'; });
if (fallback === null || fallback === void 0 ? void 0 : fallback.id) {
ids = [fallback.id];
}
}
const invalidIds = ids.filter(id => !validIds.has(id));
if (invalidIds.length > 0) {
throw new Error(`Invalid category ids: ${invalidIds.join(', ')}. Valid ids: ${[...validIds].join(', ')}`);
}
data.category_ids = ids;
break;
}
case PluginDescUpdateType.PROTOCOL:
// protocol 是顶层字段(isOutter=true)
data.protocol = protocol;
break;
case PluginDescUpdateType.I18N:
default: {
// I18N 类字段包在 i18n_info[lang] 里(isOutter=false)
const i18nEntry = {};
if (name !== undefined)
i18nEntry.name = name;
if (short !== undefined)
i18nEntry.short = short;
if (detailDescription) {
i18nEntry.description = plainTextToRichText(detailDescription.trim());
}
if (document !== undefined)
i18nEntry.document = document;
if (carouselList !== undefined)
i18nEntry.carousel_list = carouselList;
data.i18n_info = { [resolvedLang]: i18nEntry };
break;
}
}
try {
return await (0, request_1.request)(`${siteDomain}/goapi/v5/app/development/tool/description/info`, {
method: 'POST',
data,
headers: await (0, get_tool_auth_headers_1.getToolAuthHeaders)(siteDomain),
});
}
catch (err) {
// code=46 (ErrAppCenterInvalidParam) 的错误信息会被 gateway 层吃掉,只剩通用"invalid parameter"。
// 为了方便 AI 和用户定位根因(最常见是 i18n_lang 未包含 default_lang),读一次当前配置并作为 hint 输出。
if ((err === null || err === void 0 ? void 0 : err.code) === 46) {
try {
const currentInfo = await getPluginDescriptionInfo({
siteDomain,
appKey,
dataStatus: 'draft',
});
const dflt = (_d = currentInfo === null || currentInfo === void 0 ? void 0 : currentInfo.default_lang) !== null && _d !== void 0 ? _d : '(unknown)';
const langs = ((_e = currentInfo === null || currentInfo === void 0 ? void 0 : currentInfo.i18n_lang) === null || _e === void 0 ? void 0 : _e.length) ? currentInfo.i18n_lang.join(', ') : '(none)';
logger_1.logger.warn('hint: current plugin language config:\n' +
` default_lang: ${dflt}\n` +
` i18n_lang: ${langs}\n` +
'note: i18n_lang must include the default_lang.\n' +
` if you passed i18n_lang without including '${dflt}', that is likely the cause.`);
}
catch (_f) {
/* ignore — failing to fetch hint should not mask the original error */
}
}
throw err;
}
}
exports.updatePluginDescription = updatePluginDescription;