UNPKG

@lark-project/cli

Version:

飞书项目插件开发工具

142 lines (141 loc) 7.95 kB
"use strict"; 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; } });