@lark-project/cli
Version:
飞书项目插件开发工具
142 lines (141 loc) • 8.01 kB
JavaScript
;
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;