@lark-project/cli
Version:
飞书项目插件开发工具
142 lines (141 loc) • 7.95 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initConfigOnlyProject = exports.initProject = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = require("fs-extra");
const execa_1 = __importDefault(require("execa"));
const logger_1 = require("../../../utils/logger");
const use_yarn_1 = require("../../../utils/use-yarn");
const with_loading_spinner_1 = require("../../../utils/with-loading-spinner");
const download_codebase_1 = require("./download-codebase");
const utils_1 = require("../../../v1/utils");
const get_plugin_info_1 = require("../../../api/get-plugin-info");
const types_1 = require("../../../types");
const file_1 = require("../utils/file");
const workspace_1 = require("../../utils/workspace");
const is_empty_dir_1 = require("../../../utils/is-empty-dir");
const persist_app_type_1 = require("../utils/persist-app-type");
const resolve_backend_app_type_1 = require("../utils/resolve-backend-app-type");
function transformTemplateType(type) {
switch (type) {
case types_1.EFeatureType.page:
return 'page';
case types_1.EFeatureType.tab:
return 'tab';
case types_1.EFeatureType.configuration:
case types_1.EFeatureType.button:
case types_1.EFeatureType.control:
case types_1.EFeatureType.intercept:
case types_1.EFeatureType.view:
default:
return type;
}
}
// 通过模版id 拉取模版代码
async function loaderProjectWithTemplateId(options, templateId, templateTargetDir, targetDir) {
const isDefault = ['react-empty', 'react-initialized'].includes(templateId);
const { pluginId, pluginSecret, siteDomain } = options;
// 拉取插件信息
const remotePluginInfo = await (0, get_plugin_info_1.getPluginInfo)(pluginId, pluginSecret, siteDomain);
// 整理插件信息
// 用户配置的插件构成信息
const pluginType2IdMap = {};
for (const feature of remotePluginInfo.features) {
const { type, web, mobile } = feature;
const transformedType = transformTemplateType(type);
const webResourceId = web.resourceId.resource;
const mobileResourceId = mobile.resourceId.resource;
if (!pluginType2IdMap[`${transformedType}_web`]) {
pluginType2IdMap[`${transformedType}_web`] = [];
}
if (!pluginType2IdMap[`${transformedType}_mobile`]) {
pluginType2IdMap[`${transformedType}_mobile`] = [];
}
if (webResourceId) {
pluginType2IdMap[`${transformedType}_web`].push(webResourceId);
}
if (mobileResourceId) {
pluginType2IdMap[`${transformedType}_mobile`].push(mobileResourceId);
}
}
const renderCtx = Object.assign(Object.assign({}, options), { pluginSecret: (0, utils_1.encrypt)(pluginSecret), pluginType2IdMap });
logger_1.logger.info(`Download from the path of project/${templateId}`);
await (0, with_loading_spinner_1.withLoadingSpinner)(async () => {
await (0, file_1.copyProjectWithTemplate)({
templateTargetDir,
templateId,
targetDir,
renderCtx,
// 这里需要过滤掉 ejs 文件,因为这里的 renderCtx 是插件维度的,渲染不了点位维度的 ejs 文件
filter: entry => entry.includes('tsx.ejs'),
});
if (isDefault) {
await (0, fs_extra_1.remove)(`${targetDir}/src/features/web`);
await (0, fs_extra_1.remove)(`${targetDir}/src/features/mobile`);
const { list } = await (0, file_1.generateResourceCode)({
remoteConfig: remotePluginInfo,
localConfig: {},
targetDir,
siteDomain,
});
await Promise.all(list);
}
await (0, fs_extra_1.remove)(templateTargetDir);
}, 'Code template processing...');
}
async function initProject(options) {
const { projectName, templateId } = options;
const targetDir = path_1.default.resolve(process.cwd(), projectName);
logger_1.logger.info('Start init project at ', targetDir);
// 硬暂停:targetDir 已存在且不为空 → 拒绝覆盖。
// 防止 lpm create 误删用户工程(典型场景:name 被 slugify 后撞同名兄弟目录,
// 例如 `--name "需求AI摘要字段"` 会被 strip 成 `ai`,撞已存在的 ai/ 工程目录)。
// 续跑场景由 caller(createProject 续跑分支)在调本函数前显式清理 targetDir。
if ((0, fs_extra_1.existsSync)(targetDir) && !(0, is_empty_dir_1.isEmptyDir)(targetDir)) {
process.stderr.write(`Target directory "${targetDir}" already exists and is not empty.\n` +
'Refusing to overwrite — `lpm create` will not delete files inside this directory.\n\n' +
'If this directory contains an existing plugin project you want to keep:\n' +
` → cd into "${targetDir}" and use \`lpm local-config\` / \`lpm update\`\n` +
' (the existing plugin\'s app_type and pluginId stay locked at creation time;\n' +
' cross-app_type point types will be rejected at config write time)\n\n' +
'If you want to start fresh:\n' +
` → manually remove "${targetDir}" first, or rerun \`lpm create\` with a different --name\n` +
' (note: `--name` is slugified to a directory name; non-ASCII names can\n' +
' collapse to very short slugs that collide with sibling directories)\n');
process.exit(1);
}
const templateTargetDir = path_1.default.join(targetDir, '.template');
await (0, with_loading_spinner_1.withLoadingSpinner)(download_codebase_1.downloadCodeBase, 'Code template downloading...', templateTargetDir, templateId);
await loaderProjectWithTemplateId(options, templateId, templateTargetDir, targetDir);
// app_type 以**后端为准**落盘(不以传入的 --app-type 为准):此处已过 runInit 的同步等待、
// getPluginInfo 刚成功,可向 GetAppDescriptionInfo 确认后端实际建成的形态。requested 仅作
// 后端不可达时的兜底。必须在 `yarn install` **之前**写——install 耗时长易被中断,写在它之后
// 会让中断时留下一份缺 app_type 的 config(被下游当 normal)。详见 resolve-backend-app-type.ts
// 与 persist-app-type.ts 的时序说明。
const resolvedAppType = await (0, resolve_backend_app_type_1.resolveBackendAppType)({
siteDomain: options.siteDomain,
appKey: options.pluginId,
requested: options.appType,
});
(0, persist_app_type_1.persistAppTypeToConfig)(targetDir, resolvedAppType);
// install packages
logger_1.logger.info('Installing packages. This might take a couple of minutes.');
const payload = {
stdio: 'inherit',
cwd: targetDir,
shell: true,
};
await (0, execa_1.default)((0, use_yarn_1.useYarn)() ? 'yarn' : 'npm', ['install'], payload);
// 预置 .lpm-cache/ 工作区并登记 .gitignore,后续 schema / local-config / mcp 缓存全部落在此。
(0, workspace_1.ensureWorkspace)(targetDir);
(0, workspace_1.ensureGitignore)(targetDir);
logger_1.logger.success(`🍻🍻🍻 Init successfully! The project is under ${targetDir} . All resourceIds that don't match will follow the default path. Enter the following commands at the terminal to begin development.`);
console.log(`Step 1: cd ${options.projectName}`);
console.log('Step 2: lpm start');
}
exports.initProject = initProject;
var config_only_1 = require("./config-only");
Object.defineProperty(exports, "initConfigOnlyProject", { enumerable: true, get: function () { return config_only_1.initConfigOnlyProject; } });