UNPKG

@lark-project/cli

Version:

飞书项目插件开发工具

142 lines (141 loc) 8.01 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.addInitCommand = void 0; // ⚠️ 改命令名 / flag / alias 时,同步 grep `lpm` skills/ .claude/skills/ 修文档引用 const path_1 = __importDefault(require("path")); const inquirer_1 = __importDefault(require("inquirer")); const types_1 = require("../../types"); const logger_1 = require("../../utils/logger"); const validate_tools_1 = require("../../utils/validate-tools"); const run_script_1 = __importDefault(require("../../utils/run-script")); const is_empty_dir_1 = require("../../utils/is-empty-dir"); const resolve_workspace_context_1 = require("../../utils/resolve-workspace-context"); const env_1 = require("../../utils/env"); const DEFAULT_PLUGIN_NAME = 'project-name'; const inquirerProjectName = async (defaultName, force) => { const { action } = await inquirer_1.default.prompt([ { name: 'action', type: 'input', message: 'What is your project named?', default: defaultName, validate: (input) => { const targetDir = path_1.default.resolve(process.cwd(), input); if (!(0, validate_tools_1.isValidDirectoryName)(input)) { return `The projectName "${input}" is not valid, the name can contain only a-zA-Z,0-9,_,-. please check and retry.`; } if (!(0, is_empty_dir_1.isEmptyDir)(targetDir) && !force) { return `The target directory ${targetDir} already exists, please check and retry.`; } return true; }, }, ]); return action; }; function addInitCommand(program) { program .command('init') .description('Create a new plugin project.') .argument('<project-name>', 'Specify the name of a new directory in the current working directory. The name can contain only alpha、number、underscore、hyphens.') .argument('<plugin-id>', 'Specify the plugin id from the developer site. The plugin id starts with MII.') .argument('[plugin-secret]', 'Specify the plugin secret from the developer site. Required (including with --config-only) — the CLI no longer fetches the secret for you.') // 原先 arguments 有三个 <project-name> <plugin-id> <plugin-secret> // 考虑 plugin-secret 后续要下线,argument 是依赖顺序的,所以 site-domain 作为 option 不作为 argument .requiredOption('--site-domain <site-domain>', 'Specify the origin of the developer site, e.g. https://[example].com .') .option('--template-id [template-id]', 'The IDs used to identify different template codes.') .option('-f, --force', 'Overwrite the target directory if it exist.') .option('--config-only', 'Scaffold only a config-only workspace (just plugin.config.json, no frontend code / no install) — for backend-only repos. Forces the fixed directory name "meegle-plugin-config".') .action(async (projectName, pluginId, pluginSecret, options) => { const { siteDomain, templateId = 'react-initialized', force, configOnly } = options; if (!(0, validate_tools_1.isValidPluginId)(pluginId)) { logger_1.logger.error(`The pluginId "${pluginId}" is not valid. Expected format: MII_ + 16 uppercase alphanumeric chars (e.g. MII_69F2F73D121E0BCC). A pluginId is issued by the Meego developer site and cannot be made up locally; to obtain one, run \`lpm create --site-domain <your-site>\` which registers a new plugin on the server via device code OAuth.`); process.exit(1); } if (!(0, validate_tools_1.isValidURL)(siteDomain) || !siteDomain.startsWith('http')) { logger_1.logger.error(`The siteDomain "${siteDomain}" is not valid (expected an absolute URL like https://project.feishu.cn (the developer-site origin)).`); process.exit(1); } // config-only:固定目录名 meegle-plugin-config(忽略传入的 project-name,保证 skill // 的 `lpm check context` 能稳定识别),不铺前端、不跑目录/覆盖交互(幂等由服务层管)。 if (configOnly) { (0, run_script_1.default)(path_1.default.join(__dirname, '../dispatcher'), [ '--command', types_1.ECommandName.init, '--payload', JSON.stringify({ projectName: resolve_workspace_context_1.BACKEND_HANDLE_DIRNAME, pluginId, pluginSecret, siteDomain, templateId, configOnly: true, }), ]); return; } let newProjectName = projectName; // 默认值走重新输入逻辑 if (projectName === DEFAULT_PLUGIN_NAME) { newProjectName = await inquirerProjectName(DEFAULT_PLUGIN_NAME); } else { // 可能用户直接更改了命令行取名的,那就直接校验 if (!(0, validate_tools_1.isValidDirectoryName)(projectName)) { logger_1.logger.error(`The projectName "${projectName}" is not valid, the name can contain only alpha、number、underscore、hyphens, please check and retry.`); newProjectName = await inquirerProjectName(projectName); } } const targetDir = path_1.default.resolve(process.cwd(), newProjectName); // 目录不为空:`lpm init` 按模板 + 远端点位重建骨架,会用模板代码盖掉目录里已有的点位实现, // 且拉不回你在原仓迭代过的前端源码——所以这是「覆盖现有代码」的硬确认点。 // 非交互(agent / 非 TTY)下不静默覆盖:fail-closed 退 1 并给下一步;交互下让用户二次确认。--force 显式放行。 if (!(0, is_empty_dir_1.isEmptyDir)(targetDir) && !force) { const warning = `The target directory ${targetDir} already exists. \`lpm init\` scaffolds template code from the plugin's remote point config — ` + 'it will OVERWRITE the existing point code implementations in this directory and does NOT recover your iterated frontend source. ' + 'If you have the original plugin repo, work there instead.'; if (!env_1.env.isTTY) { logger_1.logger.error(`${warning}\nTo overwrite anyway, re-run \`lpm init\` with --force.`); process.exit(1); } const { action } = await inquirer_1.default.prompt([ { name: 'action', type: 'list', message: `${warning}\nPick an action:`, choices: [ { name: 'Overwrite (deletes files here + replaces point code with templates)', value: 'overwrite', }, { name: 'Cancel', value: false, }, ], default: false, }, ]); if (action !== 'overwrite') { logger_1.logger.warn('The `lpm init` is canceled, please check and retry.'); process.exit(1); } } (0, run_script_1.default)(path_1.default.join(__dirname, '../dispatcher'), [ '--command', types_1.ECommandName.init, '--payload', JSON.stringify({ projectName: newProjectName, pluginId, pluginSecret, siteDomain, templateId, }), ]); }); } exports.addInitCommand = addInitCommand;