UNPKG

@lark-project/cli

Version:

飞书项目插件开发工具

199 lines (198 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.permService = void 0; const local_plugin_config_1 = require("../../../local-plugin-config"); const logger_1 = require("../../../utils/logger"); const app_perm_1 = require("../../../api/app-perm"); const plugin_registry_1 = require("../../utils/plugin-registry"); const auth_config_1 = require("../../utils/auth-config"); function toScopeView(scopeKey, meta) { var _a; return { scope: scopeKey, name: meta === null || meta === void 0 ? void 0 : meta.name, openapis: (_a = meta === null || meta === void 0 ? void 0 : meta.resource) !== null && _a !== void 0 ? _a : [] }; } function deriveListView(result, appType, isAIPlugin) { var _a, _b, _c; const metaByKey = new Map(((_a = result.perm_meta) !== null && _a !== void 0 ? _a : []).filter(m => m.key).map(m => [m.key, m])); const grantedKeys = new Set(((_b = result.perm_info) !== null && _b !== void 0 ? _b : []).map(p => p.key).filter((k) => !!k)); const granted = [...grantedKeys].map(key => toScopeView(key, metaByKey.get(key))); const view = { appType, granted }; // AI 应用不能 apply:天花板 = granted 固定集,省略 applicable。 if (!isAIPlugin) { view.applicable = ((_c = result.perm_meta) !== null && _c !== void 0 ? _c : []) .filter(m => m.key && !grantedKeys.has(m.key)) .map(m => toScopeView(m.key, m)); } return view; } /** * 反查一个 OpenAPI token 归属哪些 scope。token 可为完整 resource key * (bytedance.bits.workitem:OAPIGetWorkItemsByIds)、短名(OAPIGetWorkItemsByIds)或中文名(获取工作项详情)。 */ function matchApiToScopes(token, perm_meta) { var _a, _b; const scopes = new Set(); for (const meta of perm_meta !== null && perm_meta !== void 0 ? perm_meta : []) { if (!meta.key) continue; for (const res of (_a = meta.resource) !== null && _a !== void 0 ? _a : []) { const shortKey = (_b = res.key) === null || _b === void 0 ? void 0 : _b.split(':').pop(); if (res.key === token || shortKey === token || res.name === token) { scopes.add(meta.key); } } } return { scopes: [...scopes] }; } function deriveCheckView(apis, result, appType, isAIPlugin) { var _a, _b, _c; const metaByKey = new Map(((_a = result.perm_meta) !== null && _a !== void 0 ? _a : []).filter(m => m.key).map(m => [m.key, m])); const grantedKeys = new Set(((_b = result.perm_info) !== null && _b !== void 0 ? _b : []).map(p => p.key).filter((k) => !!k)); const view = { appType, satisfied: [], needApply: [], infeasible: [], unknown: [], ambiguous: [] }; const needApplyByScope = new Map(); for (const api of apis) { const { scopes } = matchApiToScopes(api, result.perm_meta); if (scopes.length === 0) { view.unknown.push(api); } else if (scopes.length > 1) { view.ambiguous.push({ api, candidates: scopes }); } else if (grantedKeys.has(scopes[0])) { view.satisfied.push(api); } else if (isAIPlugin) { view.infeasible.push({ api, reason: 'ai-plugin-fixed-scope' }); } else { const arr = (_c = needApplyByScope.get(scopes[0])) !== null && _c !== void 0 ? _c : []; arr.push(api); needApplyByScope.set(scopes[0], arr); } } view.needApply = [...needApplyByScope.entries()].map(([scope, list]) => { var _a; return ({ scope, name: (_a = metaByKey.get(scope)) === null || _a === void 0 ? void 0 : _a.name, apis: list, }); }); return view; } /** * 定位目标插件,优先级: * 1. 显式 `--plugin/--site-domain`(工程目录外); * 2. 当前工程目录的 plugin.config.json(看得见的锚); * 3. 全局只读注册表(单登录态 + 该站点恰好记录过一个插件时自动定位,否则报错列出让用户加 flag)。 * 只读定位无 secret;写仍受 plugin.config.json(含 secret)约束,与本函数无关。 */ function resolveTarget(payload) { var _a; if (payload.pluginId) { if (!payload.siteDomain) { return { error: '`--plugin <id>` requires `--site-domain <url>` (the site that owns the plugin).' }; } const appType = (_a = (0, plugin_registry_1.listPlugins)(payload.siteDomain).find(p => p.pluginId === payload.pluginId)) === null || _a === void 0 ? void 0 : _a.app_type; (0, plugin_registry_1.recordPlugin)(payload.siteDomain, { pluginId: payload.pluginId }); return { pluginId: payload.pluginId, siteDomain: payload.siteDomain, appType, fromLocal: false }; } const local = (0, local_plugin_config_1.getLocalPluginConfig)(); if (local) { return { pluginId: local.pluginId, siteDomain: local.siteDomain, appType: local.app_type, fromLocal: true }; } const domains = Object.keys((0, auth_config_1.loadStore)()); const siteDomain = payload.siteDomain || (domains.length === 1 ? domains[0] : undefined); if (!siteDomain) { return { error: domains.length === 0 ? '`lpm perm` outside a plugin project needs `--plugin <id> --site-domain <url>` (no logged-in site found — run `lpm login` or pass both flags).' : `Multiple logged-in sites (${domains.join(', ')}). Re-run with \`--plugin <id> --site-domain <url>\`.`, }; } const plugins = (0, plugin_registry_1.listPlugins)(siteDomain); if (plugins.length === 1) { return { pluginId: plugins[0].pluginId, siteDomain, appType: plugins[0].app_type, fromLocal: false }; } if (plugins.length === 0) { return { error: `No plugins recorded for ${siteDomain}. Run \`lpm perm list --plugin <id> --site-domain ${siteDomain}\` once (it gets remembered), or run inside the plugin project.`, }; } return { error: `Multiple plugins recorded for ${siteDomain}:\n` + plugins.map(p => ` - ${p.pluginId}${p.app_type ? ` (${p.app_type})` : ''}`).join('\n') + `\nRe-run with \`--plugin <id> --site-domain ${siteDomain}\`.`, }; } async function permService(payload) { const resolved = resolveTarget(payload); if ('error' in resolved) { logger_1.logger.error(resolved.error); process.exit(1); } const { pluginId, siteDomain, appType, fromLocal } = resolved; // 工程目录外定位时回显目标,避免对着错插件操作而不自知(cwd 锚本就看得见,无需回显)。 if (!fromLocal) { process.stderr.write(`perm ${payload.action}: plugin ${pluginId} @ ${siteDomain}\n`); } const isAIPlugin = appType === 'ai_node' || appType === 'ai_field'; const effectiveAppType = appType !== null && appType !== void 0 ? appType : 'normal'; const { action, scopes, apis, raw } = payload; // AI plugins: OpenAPI scopes are auto-granted at create time; surface that up // front (on read actions) so AI agents stop hunting for an apply step that // doesn't exist. Derived views already encode this (no applicable / infeasible), // but the note keeps the human-facing reason explicit. const emitAINote = () => { if (isAIPlugin) { process.stderr.write(`info: this is an ${appType} plugin — OpenAPI scopes are auto-granted at create time. ` + 'Use `lpm perm list` (this command) to verify; `lpm perm apply` is rejected by the backend on AI plugins.\n'); } }; if (action === 'list') { emitAINote(); const result = await (0, app_perm_1.getAppPermList)(pluginId, siteDomain); // --raw: backend's original { perm_info, perm_meta } (debug / escape hatch). // Default: CLI-derived { granted, applicable } so callers don't re-implement // the perm_info ∩ perm_meta join themselves. console.log(JSON.stringify(raw ? result : deriveListView(result, effectiveAppType, isAIPlugin), null, 2)); return; } if (action === 'check') { if (!apis || apis.length === 0) { logger_1.logger.error('`lpm perm check` requires --apis <a,b,c> (OpenAPI keys / short names / Chinese names).'); process.exit(1); } emitAINote(); const result = await (0, app_perm_1.getAppPermList)(pluginId, siteDomain); console.log(JSON.stringify(deriveCheckView(apis, result, effectiveAppType, isAIPlugin), null, 2)); return; } if (action === 'apply') { // Hard-stop apply on AI plugins: backend rejects it anyway, and AI agents // routinely waste turns retrying. Exit 1 with the actionable next-step. if (isAIPlugin) { logger_1.logger.error(`\`lpm perm apply\` is not available on ${appType} plugins — OpenAPI scopes are auto-granted at create time. ` + 'Run `lpm perm list` to verify the already-granted scopes.'); process.exit(1); } if (!scopes || scopes.length === 0) { logger_1.logger.error('`lpm perm apply` requires --scopes <a,b,c>.'); process.exit(1); } // Validate against the catalog (perm_meta) before hitting the backend, so a // typo in a scope key fails fast with an actionable hint instead of a backend error. const { perm_meta } = await (0, app_perm_1.getAppPermList)(pluginId, siteDomain); const known = new Set((perm_meta !== null && perm_meta !== void 0 ? perm_meta : []).map(m => m.key)); const unknown = scopes.filter(s => !known.has(s)); if (unknown.length > 0) { logger_1.logger.error(`Unknown scope(s): ${unknown.join(', ')}. Run \`lpm perm list\` to see the scopes available for this plugin.`); process.exit(1); } const result = await (0, app_perm_1.applyAppPerm)(pluginId, siteDomain, scopes); console.log(JSON.stringify(result, null, 2)); return; } logger_1.logger.error(`Unknown perm action "${action}". Use "list", "check", or "apply".`); process.exit(1); } exports.permService = permService;