neo-cmp-cli 1.6.0-beta.8 → 1.6.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 wibetter
4
+
package/README.md CHANGED
@@ -8,12 +8,12 @@ neo-cmp-cli 是 Neo 自定义组件开发工具,基于 [AKFun](https://github.
8
8
  - **灵活可配**: 支持 构建入口、别名、代理、SASS 注入、ESLint/StyleLint、Babel/Loader/Plugin 扩展等配置;
9
9
  - **样式与规范**: 内置 Autoprefixer、Sass、PostCSS、ESLint、StyleLint;
10
10
  - **发布至 CDN**: 内置发布到对象存储(OSS)的能力,支持自定义对象存储配置;
11
- - **发布至 NeoCRM 平台**: 支持一键发布到NeoCRM 平台的能力,需自行补充授权配置;
11
+ - **发布至 NeoCRM 平台**: 支持一键发布到NeoCRM 平台的能力,需自行补充授权配置。
12
12
 
13
13
  ### 内置的自定义组件模板
14
14
  创建自定义组件时(执行初始化命令 neo init)可选用。
15
- - **React 模板**: [react-custom-cmp-template](https://github.com/wibetter/react-custom-cmp-template)
16
15
  - **React + TS 模板**: [react-ts-custom-cmp-template](https://github.com/wibetter/react-ts-custom-cmp-template) 内置信息卡片列表展示组件示例
16
+ - **React 模板(非 TS)**: [react-custom-cmp-template](https://github.com/wibetter/react-custom-cmp-template)
17
17
  - **Antd 模板**: [antd-custom-cmp-template](https://github.com/wibetter/antd-custom-cmp-template) 内置 Antd UI 组件实现的数据仪表板展示组件示例
18
18
  - **Neo 模板**: [neo-custom-cmp-template](https://github.com/wibetter/neo-custom-cmp-template) 支持使用平台实体数据源(内置平台实体数据对象的增删改查示例组件)
19
19
  - **Echarts 模板**: [echarts-custom-cmp-template](https://github.com/wibetter/echarts-custom-cmp-template) Echarts 图表自定义组件(含 高德地图展示组件示例)
@@ -63,10 +63,10 @@ neo linkDebug
63
63
  需在NeoCRM / 页面设计器端启动 debug 模式,并添加控制台输出的外链脚本地址,方可在页面设计器端使用当前自定义组件。
64
64
 
65
65
  #### 7) 构建并发布到 NeoCRM
66
- ```bash
67
- neo pushCmp
68
- ```
69
- ##### 需自行添加授权配置,并确保 package.json 的 name 唯一、version 不重复。
66
+ 执行 `neo pushCmp` 即可构建并发布自定义组件至 NeoCRM 平台。
67
+ **发布前请确保**:`package.json` 的 `name` 唯一、`version` 不重复,并已配置 NeoCRM 平台授权信息。
68
+
69
+ ##### 需自行添加授权配置
70
70
  ```javascript
71
71
  module.exports = {
72
72
  neoConfig: {
@@ -86,9 +86,7 @@ module.exports = {
86
86
  },
87
87
  }
88
88
  ```
89
- 1、客户端 ID 和 客户端秘钥 需通过 创建连接器 获取,获取方式见:[https://doc.xiaoshouyi.com](https://doc.xiaoshouyi.com) / 创建连接器;
90
- 2、如何获取 安全令牌 见:[https://doc.xiaoshouyi.com](https://doc.xiaoshouyi.com) / OAuth安全认证 / 密码模式 / 获取令牌;
91
- 3、发布成功后即可在 NeoCRM 对应租户环境的页面设计器和表单设计器中使用此自定义组件。
89
+ **详细使用说明**:请参考下方「[发布自定义组件至 NeoCRM](#6发布自定义组件至-neocrm)」章节。
92
90
 
93
91
  ## 常用命令说明
94
92
  - **neo init**: 交互式创建自定义组件(支持 -t、--name);
@@ -97,7 +95,9 @@ module.exports = {
97
95
  - **neo preview**: 本地预览自定义组件内容(支持 --name),默认支持热更新与接口代理;
98
96
  - **neo linkDebug**: 外链调试模式,在平台端页面设计器中调试自定义组件;
99
97
  - **neo publish2oss**: 构建并上传到对象存储(支持 --name,可自定义配置对象存储);
100
- - **neo pushCmp**: 构建并发布到NeoCRM平台(支持 --name,需自行添加授权配置)。
98
+ - **neo pushCmp**: 构建并发布到NeoCRM平台(支持 --name,需自行添加授权配置);
99
+ - **neo pullCmp**: 拉取线上自定义组件(NeoCRM平台)至当前项目(支持 --name,需自行添加授权配置);
100
+ - **neo deleteCmp**: 删除线上自定义组件(NeoCRM平台)(支持 --name,需自行添加授权配置)。
101
101
 
102
102
  ## 开发须知
103
103
  #### 1)默认自动识别自定义组件
@@ -153,11 +153,26 @@ neo linkDebug
153
153
  #### 6)发布自定义组件至 NeoCRM
154
154
  执行 `neo pushCmp` 即可构建并发布自定义组件至 NeoCRM 平台,其构建后资源也会上传到 NeoCRM 平台端提供的 CDN 中。
155
155
 
156
+ ##### 使用方式
157
+ ```bash
158
+ # 方式一:交互式选择要发布的自定义组件
159
+ neo pushCmp
160
+
161
+ # 方式二:直接指定要发布的自定义组件名称
162
+ neo pushCmp --name=xxCmp
163
+ # 或
164
+ neo pushCmp -n xxCmp
165
+ ```
166
+
156
167
  ##### 发布前请确保
157
- - **package.json 的 name 唯一**
158
- - **version 不重复**
168
+ - **package.json 的 name 唯一**:确保当前项目的 `package.json` 中的 `name` 字段在 NeoCRM 平台中唯一
169
+ - **version 不重复**:每次发布时,请更新 `package.json` 中的 `version` 字段,确保版本号不重复
170
+ - **已配置 NeoCRM 平台授权信息**:在 `neo.config.js` 中配置 `neoConfig.auth`
171
+ - **自定义组件已正确构建**:确保组件代码可以正常构建,且 `dist` 目录下有对应的构建产物
172
+ - **组件模型文件存在**:确保组件目录下有 `model.ts` 或 `model.js` 文件,且正确导出了模型类
159
173
 
160
174
  ##### 需自行添加授权配置
175
+ 在项目根目录的 `neo.config.js` 文件中添加 NeoCRM 平台授权配置:
161
176
  ```javascript
162
177
  module.exports = {
163
178
  neoConfig: {
@@ -181,13 +196,87 @@ module.exports = {
181
196
  }
182
197
  ```
183
198
 
184
- ##### 支持发布指定自定义组件
185
- 执行 `neo pushCmp --name=xxCmp`
199
+ ##### 授权配置获取方式
200
+ 1. **客户端 ID 和客户端秘钥**:需通过创建连接器获取
201
+ - 访问 [销售易文档中心](https://doc.xiaoshouyi.com) / 创建连接器
202
+ - 创建连接器后,从客户端信息中获取 `Client_Id` 和 `Client_Secret`
203
+ 2. **安全令牌**:如何获取安全令牌
204
+ - 访问 [销售易文档中心](https://doc.xiaoshouyi.com) / OAuth安全认证 / 密码模式 / 获取令牌
205
+ - 按照文档说明获取 8 位安全令牌
206
+ - `password` 字段 = 用户账户密码 + 8 位安全令牌(直接拼接,无空格或分隔符)
207
+
208
+ ##### 注意事项
209
+ - **版本管理**:每次发布前务必更新 `package.json` 中的 `version` 字段,避免版本冲突
210
+ - **组件名称唯一性**:确保 `package.json` 中的 `name` 字段在平台中唯一
211
+ - **模型文件要求**:组件必须包含有效的模型文件(`model.ts` 或 `model.js`),且正确导出模型类
212
+ - **技术栈一致性**:确保组件使用的技术栈与项目配置一致(React、React+TS、Vue2 等)
213
+ - **构建产物完整性**:确保 `dist` 目录下包含组件的 JS 文件和模型文件(`xxCmp.js` 和 `xxCmpModel.js`)
214
+ - **网络连接**:发布过程需要网络连接,确保可以访问 NeoCRM 平台和 OSS 服务
215
+
216
+ ##### 常见问题
217
+
218
+ **Q: 发布失败,提示"未找到 NeoCRM 平台授权配置"**
219
+ A: 请检查 `neo.config.js` 文件中是否配置了 `neoConfig.auth`,确保所有授权字段都已正确填写。
220
+
221
+ **Q: 发布失败,提示"未找到自定义组件模型文件"**
222
+ A: 请确保组件目录下有 `model.ts` 或 `model.js` 文件,且构建后在 `dist` 目录下生成了对应的 `xxCmpModel.js` 文件。
223
+
224
+ **Q: 发布失败,提示"获取 token 失败(授权配置错误)"**
225
+ A: 请检查 `neo.config.js` 文件中是否配置了 `neoConfig.auth`,确保所有授权字段都已正确填写。
186
226
 
187
227
  ##### 线上 NeoCRM 端使用
188
228
  发布成功后,即可在对应租户环境下的页面设计器和表单设计器中使用此自定义组件。
189
229
 
190
- #### 7)发布自定义组件至CDN
230
+ #### 7)拉取线上自定义组件至本地项目
231
+ 执行 `neo pullCmp` 即可从 NeoCRM 平台拉取自定义组件源码到当前项目。
232
+ 该命令会将线上自定义组件的源码下载并解析到当前项目的 `src/components` 目录下。
233
+
234
+ ##### 使用方式
235
+ ```bash
236
+ # 方式一:交互式选择要拉取的自定义组件
237
+ neo pullCmp
238
+
239
+ # 方式二:直接指定要拉取的自定义组件名称
240
+ neo pullCmp --name=xxCmp
241
+ # 或
242
+ neo pullCmp -n xxCmp
243
+ ```
244
+
245
+ ##### 拉取前请确保
246
+ - **已配置 NeoCRM 平台授权信息**(neo.config.js 中的 neoConfig.auth)
247
+ - **当前项目目录中不存在同名自定义组件**(`./src/components` 目录下)
248
+ - **拉取的组件与当前项目技术栈一致**(React、React+TS、Vue2 等)
249
+
250
+ ##### 注意事项
251
+ - 拉取的自定义组件会覆盖本地同名组件,请谨慎操作
252
+ - 如果组件源码中包含新增的依赖包,拉取完成后会提示执行 `npm install` 或 `yarn install`
253
+ - zip 源文件会保留在 `.neo-cli/zip-source` 目录下,可手动删除
254
+
255
+ #### 8)删除线上自定义组件
256
+ 执行 `neo deleteCmp` 即可从 NeoCRM 平台删除指定的自定义组件。
257
+ 该命令会删除平台上的自定义组件,删除后该组件将无法在页面设计器和表单设计器中使用。
258
+
259
+ ##### 使用方式
260
+ ```bash
261
+ # 方式一:交互式选择要删除的自定义组件
262
+ neo deleteCmp
263
+
264
+ # 方式二:直接指定要删除的自定义组件名称
265
+ neo deleteCmp --name=xxCmp
266
+ # 或
267
+ neo deleteCmp -n xxCmp
268
+ ```
269
+
270
+ ##### 删除前请确保
271
+ - **已配置 NeoCRM 平台授权信息**(neo.config.js 中的 neoConfig.auth)
272
+ - **确认要删除的组件名称正确**,删除操作不可恢复
273
+
274
+ ##### 注意事项
275
+ - **删除操作不可恢复**:删除后的自定义组件将无法恢复,请谨慎操作
276
+ - **影响范围**:删除组件后,所有使用该组件的页面和表单将受到影响,请确保没有正在使用的场景
277
+ - **建议先备份**:删除前建议先使用 `neo pullCmp` 拉取组件源码到本地进行备份
278
+
279
+ #### 9)发布自定义组件至CDN
191
280
  执行 `neo publish2oss` 即可构建对应自定义组件,并自动将构建后资源上传到对象存储(OSS)中。
192
281
  备注:请优先使用 neo pushCmp。
193
282
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.6.0-beta.8",
3
+ "version": "1.6.1-beta.1",
4
4
  "description": "前端脚手架:自定义组件开发工具,支持react 和 vue2.0技术栈。",
5
5
  "keywords": [
6
6
  "neo-cli",
@@ -252,15 +252,15 @@ yargs
252
252
  } else {
253
253
  // 创建 neoService 实例
254
254
  const neoService = new NeoService(curConfig.neoConfig);
255
- const spinner = ora('正在获取自定义组件列表...').start();
255
+ const spinner = ora('正在拉取线上自定义组件列表...').start();
256
256
  const cmpList = await neoService.getCustomCmpList();
257
257
  if (cmpList.length === 0) {
258
258
  console.error('当前租户暂无任何自定义组件。');
259
259
  process.exit(1);
260
260
  }
261
- spinner.succeed('自定义组件列表获取成功。');
261
+ spinner.stop('线上自定义组件列表拉取成功。');
262
262
  const cmpTypeChoices = cmpList.map((cmpItem) => ({
263
- name: cmpItem.label,
263
+ name: `${cmpItem.label}(${cmpItem.cmpType})`,
264
264
  value: cmpItem.cmpType
265
265
  }));
266
266
  const questions = [
@@ -282,6 +282,55 @@ yargs
282
282
  }
283
283
  }
284
284
  )
285
+ .command(
286
+ 'deleteCmp [options]',
287
+ '删除线上自定义组件',
288
+ (yargs) => {
289
+ yargs
290
+ .reset()
291
+ .usage(titleTip('Usage') + ': $0 deleteCmp [options]')
292
+ .option('name', {
293
+ alias: 'n',
294
+ describe: '自定义组件名称'
295
+ })
296
+ .alias('h', 'help');
297
+ },
298
+ async (argv) => {
299
+ if (argv.name) {
300
+ mainAction.deleteCmp(argv.name);
301
+ } else {
302
+ // 创建 neoService 实例
303
+ const neoService = new NeoService(curConfig.neoConfig);
304
+ const spinner = ora('正在获取线上自定义组件列表...').start();
305
+ const cmpList = await neoService.getCustomCmpList();
306
+ if (cmpList.length === 0) {
307
+ console.error('当前租户暂无任何自定义组件。');
308
+ process.exit(1);
309
+ }
310
+ spinner.stop('线上自定义组件列表获取成功。');
311
+ const cmpTypeChoices = cmpList.map((cmpItem) => ({
312
+ name: `${cmpItem.label}(${cmpItem.cmpType})`,
313
+ value: cmpItem.cmpType
314
+ }));
315
+ const questions = [
316
+ {
317
+ name: 'cmpType',
318
+ type: 'list',
319
+ message: '请选择要删除的自定义组件:',
320
+ choices: cmpTypeChoices
321
+ }
322
+ ];
323
+ inquirer.prompt(questions).then((ans) => {
324
+ if (!ans.cmpType) {
325
+ console.error('自定义组件名称不能为空。');
326
+ process.exit(1);
327
+ } else {
328
+ mainAction.deleteCmp(ans.cmpType, neoService);
329
+ }
330
+ });
331
+ }
332
+ }
333
+ )
285
334
  .command(
286
335
  'preview [options]',
287
336
  '预览指定自定义组件(仅预览组件本身内容)',
@@ -17,6 +17,7 @@ const createCommonModulesCode = require('../utils/cmpUtils/createCommonModulesCo
17
17
  const createCmpByTemplate = require('../utils/cmpUtils/createCmpByTemplate');
18
18
  const createCmpProjectByTemplate = require('../utils/projectUtils/createCmpProjectByTemplate');
19
19
  const pullCmp = require('../utils/cmpUtils/pullCmp');
20
+ const deleteCmp = require('../utils/cmpUtils/deleteCmp');
20
21
 
21
22
  const getValue = (originValue, defaultValue) => {
22
23
  return originValue !== undefined ? originValue : defaultValue;
@@ -263,5 +264,9 @@ module.exports = {
263
264
  pullCmp: (cmpType, neoService) => {
264
265
  pullCmp(cmpType, curConfig.neoConfig, neoService);
265
266
  },
267
+ // 从 NeoCRM 平台删除组件
268
+ deleteCmp: (cmpType, neoService) => {
269
+ deleteCmp(cmpType, curConfig.neoConfig, neoService);
270
+ },
266
271
  build2esm: (fileName) => akfun.build2esm(fileName, curConfig, consoleTag) // 构建esm输出模块
267
272
  };
@@ -16,7 +16,8 @@ const NeoCrmAPI = {
16
16
  delete: '/rest/metadata/v3.0/ui/customComponents',
17
17
  query: '/rest/metadata/v3.0/ui/customComponents/actions/queryCustomComponents', // 带分页
18
18
  queryAll: '/rest/metadata/v3.0/ui/customComponents/actions/queryAllCustomComponents', // 不带分页
19
- saveAPI: '/rest/metadata/v3.0/ui/customComponents/actions/saveOrUpdateComponent' // 创建或者保存接口地址
19
+ saveAPI: '/rest/metadata/v3.0/ui/customComponents/actions/saveOrUpdateComponent', // 创建或者保存接口地址
20
+ deleteAPI: '/rest/metadata/v3.0/ui/customComponents'
20
21
  };
21
22
 
22
23
  const cmpFields = [
@@ -157,9 +158,11 @@ class NeoService {
157
158
  token: access_token,
158
159
  expiresAt: Date.now() + (expiresIn - 60) * 1000
159
160
  };
160
- spinner.succeed('token 获取成功。');
161
+ spinner.clear();
162
+ spinner.stop();
161
163
  return access_token;
162
164
  } catch (error) {
165
+ spinner.fail('获取 token 失败');
163
166
  console.error('\n获取 token 失败:', error.message);
164
167
  console.error('\ntoken 授权地址:', tokenUrl);
165
168
  console.error('\ntoken 请求参数:', formData);
@@ -193,9 +196,14 @@ class NeoService {
193
196
  return await this.getToken();
194
197
  } else if (this.isTokenExpired()) {
195
198
  const spinner = ora('token 已过期,正在刷新...').start();
196
- const token = await this.refreshToken();
197
- spinner.succeed('token 刷新成功。');
198
- return token;
199
+ try {
200
+ const token = await this.refreshToken();
201
+ spinner.succeed('token 刷新成功。');
202
+ return token;
203
+ } catch (error) {
204
+ spinner.fail('token 刷新失败。');
205
+ throw error;
206
+ }
199
207
  }
200
208
  return this.tokenCache.token;
201
209
  }
@@ -458,7 +466,8 @@ class NeoService {
458
466
  throw new Error(`更新组件失败: ${response.data.message || '未知错误'}`);
459
467
  }
460
468
 
461
- spinner.succeed('组件更新成功。');
469
+ spinner.clear();
470
+ spinner.stop();
462
471
  } catch (error) {
463
472
  spinner.fail('更新组件失败。');
464
473
  if (error.message) {
@@ -519,6 +528,38 @@ class NeoService {
519
528
 
520
529
  return this.cmpList || [];
521
530
  }
531
+
532
+ /**
533
+ * 删除自定义组件
534
+ * @param {*} cmpType 自定义组件类型
535
+ * @returns {Promise<object>} 删除结果
536
+ */
537
+ async deleteCustomComponent(cmpType) {
538
+ // 确保 token 有效
539
+ const token = await this.ensureValidToken();
540
+
541
+ if (!cmpType) {
542
+ throw new Error('自定义组件名称不能为空。');
543
+ }
544
+
545
+ let fullDeleteAPI = this.buildFullUrl(NeoCrmAPI.deleteAPI);
546
+ fullDeleteAPI += `?cmpType=${cmpType}`;
547
+
548
+ const response = await axios.delete(fullDeleteAPI, {
549
+ headers: {
550
+ Authorization: `Bearer ${token}`,
551
+ 'xsy-inner-source': 'bff',
552
+ 'Content-Type': 'application/json'
553
+ }
554
+ });
555
+ const { code, message } = response.data || {};
556
+
557
+ if (code && code !== 200) {
558
+ throw new Error(`删除自定义组件失败: ${message || '未知错误'}`);
559
+ }
560
+
561
+ return response.data;
562
+ }
522
563
 
523
564
  // 获取指定框架的自定义组件列表
524
565
  getCmpListByFramework(framework) {
@@ -48,7 +48,7 @@
48
48
  "@commitlint/config-conventional": "^9.1.1",
49
49
  "@types/react": "^16.9.11",
50
50
  "@types/react-dom": "^16.9.15",
51
- "neo-cmp-cli": "^1.5.6",
51
+ "neo-cmp-cli": "^1.6.1",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5"
@@ -48,7 +48,7 @@
48
48
  "@commitlint/config-conventional": "^9.1.1",
49
49
  "@types/react": "^16.9.11",
50
50
  "@types/react-dom": "^16.9.15",
51
- "neo-cmp-cli": "^1.5.6",
51
+ "neo-cmp-cli": "^1.6.1",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5",
@@ -46,7 +46,7 @@
46
46
  "@commitlint/config-conventional": "^9.1.1",
47
47
  "@types/react": "^16.9.11",
48
48
  "@types/react-dom": "^16.9.15",
49
- "neo-cmp-cli": "^1.5.6",
49
+ "neo-cmp-cli": "^1.6.1",
50
50
  "husky": "^4.2.5",
51
51
  "lint-staged": "^10.2.9",
52
52
  "prettier": "^2.0.5"
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@commitlint/cli": "^8.3.5",
47
47
  "@commitlint/config-conventional": "^9.1.1",
48
- "neo-cmp-cli": "^1.5.6",
48
+ "neo-cmp-cli": "^1.6.1",
49
49
  "husky": "^4.2.5",
50
50
  "lint-staged": "^10.2.9",
51
51
  "prettier": "^2.0.5"
@@ -47,7 +47,7 @@
47
47
  "@commitlint/config-conventional": "^9.1.1",
48
48
  "@types/react": "^16.9.11",
49
49
  "@types/react-dom": "^16.9.15",
50
- "neo-cmp-cli": "^1.5.6",
50
+ "neo-cmp-cli": "^1.6.1",
51
51
  "husky": "^4.2.5",
52
52
  "lint-staged": "^10.2.9",
53
53
  "prettier": "^2.0.5"
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@commitlint/cli": "^8.3.5",
47
47
  "@commitlint/config-conventional": "^9.1.1",
48
- "neo-cmp-cli": "^1.5.6",
48
+ "neo-cmp-cli": "^1.6.1",
49
49
  "husky": "^4.2.5",
50
50
  "lint-staged": "^10.2.9",
51
51
  "prettier": "^2.0.5",
@@ -3,10 +3,86 @@ const path = require('path');
3
3
  const axios = require('axios');
4
4
  const ora = require('ora');
5
5
  const AdmZip = require('adm-zip');
6
+ const _ = require('lodash');
6
7
  const { consoleTag } = require('../neoParams'); // 输出标记
7
8
  const hasCmpTypeByDir = require('./hasCmpTypeByDir');
8
9
  const hasNeoProject = require('../projectUtils/hasNeoProject');
9
10
 
11
+ /**
12
+ * 将配置对象格式化为 JavaScript 代码字符串
13
+ * @param {*} obj 配置对象
14
+ * @param {number} indent 缩进级别
15
+ * @param {string} fileDir 配置文件所在目录路径,用于将绝对路径转换回相对路径(resolve() 基于 __dirname)
16
+ * @returns {string} 格式化后的字符串
17
+ */
18
+ function formatConfigObject(obj, indent = 0, fileDir = '') {
19
+ const indentStr = ' '.repeat(indent);
20
+ const nextIndentStr = ' '.repeat(indent + 1);
21
+
22
+ if (obj === null) {
23
+ return 'null';
24
+ }
25
+
26
+ if (obj === undefined) {
27
+ return 'undefined';
28
+ }
29
+
30
+ if (typeof obj === 'string') {
31
+ // 尝试将绝对路径转换回 resolve() 调用
32
+ if (fileDir && path.isAbsolute(obj)) {
33
+ try {
34
+ const relativePath = path.relative(fileDir, obj).replace(/\\/g, '/');
35
+ // 如果路径看起来像是通过 resolve() 生成的,转换回 resolve() 调用
36
+ // 排除包含 .. 的路径(这些可能是外部路径)
37
+ if (relativePath && !relativePath.startsWith('..') && relativePath !== '') {
38
+ // 确保路径使用正斜杠,并添加 ./ 前缀(如果需要)
39
+ const normalizedPath = relativePath.startsWith('.') ? relativePath : './' + relativePath;
40
+ return `resolve(${JSON.stringify(normalizedPath)})`;
41
+ }
42
+ } catch (e) {
43
+ // 如果路径转换失败,继续使用原始字符串
44
+ }
45
+ }
46
+ // 检查是否包含 resolve() 或 auth. 调用(从原始文件中提取的)
47
+ if (obj.includes('resolve(') || obj.includes('auth.')) {
48
+ return obj;
49
+ }
50
+ // 转义字符串中的特殊字符
51
+ return JSON.stringify(obj);
52
+ }
53
+
54
+ if (typeof obj === 'number' || typeof obj === 'boolean') {
55
+ return String(obj);
56
+ }
57
+
58
+ if (Array.isArray(obj)) {
59
+ if (obj.length === 0) {
60
+ return '[]';
61
+ }
62
+ const items = obj.map((item) => {
63
+ const formatted = formatConfigObject(item, indent + 1, fileDir);
64
+ return nextIndentStr + formatted;
65
+ });
66
+ return '[\n' + items.join(',\n') + '\n' + indentStr + ']';
67
+ }
68
+
69
+ if (typeof obj === 'object') {
70
+ const keys = Object.keys(obj);
71
+ if (keys.length === 0) {
72
+ return '{}';
73
+ }
74
+ const items = keys.map((key) => {
75
+ const value = obj[key];
76
+ const formattedValue = formatConfigObject(value, indent + 1, fileDir);
77
+ return nextIndentStr + key + ': ' + formattedValue;
78
+ });
79
+ return '{\n' + items.join(',\n') + '\n' + indentStr + '}';
80
+ }
81
+
82
+ // 对于函数或其他类型,尝试转换为字符串
83
+ return String(obj);
84
+ }
85
+
10
86
  /**
11
87
  * 从 zip 包中创建自定义组件
12
88
  * @param {*} cmpZipUrl 自定义组件源码文件地址(zip包地址)
@@ -14,7 +90,7 @@ const hasNeoProject = require('../projectUtils/hasNeoProject');
14
90
  * @param {*} componentBaseDir 自定义组件目录
15
91
  */
16
92
  module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/components') {
17
- const finalCmpName = cmpName || 'neoCustomCmp';
93
+ const finalCmpName = cmpName;
18
94
 
19
95
  if (!hasNeoProject()) {
20
96
  console.error(
@@ -31,36 +107,75 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
31
107
  // 创建临时目录
32
108
  const tempDir = path.join(process.cwd(), '.neo-cli', 'zip-source');
33
109
 
110
+ const spinner = ora(`${consoleTag}正在下载组件源码...`).start();
111
+
34
112
  // 下载源码文件并解析到 src/components 目录下
35
113
  try {
36
114
  await fs.ensureDir(tempDir); // 如果目录不存在,会自动创建(包括所有必要的父目录)
37
115
 
38
- // 下载 zip 文件
39
- const spinner = ora(`${consoleTag}正在下载组件源码...`).start();
40
116
  const zipFilePath = path.join(tempDir, `${finalCmpName}.zip`);
41
117
 
42
- const response = await axios({
43
- url: cmpZipUrl,
44
- method: 'GET',
45
- responseType: 'arraybuffer'
46
- });
118
+ // 检查是否存在同名 zip 包,如果存在则先删除
119
+ if (await fs.pathExists(zipFilePath)) {
120
+ await fs.remove(zipFilePath);
121
+ }
122
+
123
+ // 下载 zip 文件
124
+ let response;
125
+ try {
126
+ response = await axios({
127
+ url: cmpZipUrl,
128
+ method: 'GET',
129
+ responseType: 'arraybuffer',
130
+ timeout: 60000, // 60秒超时
131
+ maxContentLength: Infinity,
132
+ maxBodyLength: Infinity
133
+ });
134
+ } catch (axiosError) {
135
+ spinner.fail(`${consoleTag}下载文件失败:`, axiosError.message);
136
+ throw axiosError;
137
+ }
47
138
 
48
139
  // 保存 zip 文件到临时目录
49
- await fs.writeFile(zipFilePath, response.data);
140
+ try {
141
+ let buffer;
142
+ // 处理不同的数据类型
143
+ if (Buffer.isBuffer(response.data)) {
144
+ // 如果已经是 Buffer,直接使用
145
+ buffer = response.data;
146
+ } else if (response.data instanceof ArrayBuffer) {
147
+ // 如果是 ArrayBuffer,转换为 Buffer
148
+ buffer = Buffer.from(response.data);
149
+ } else if (response.data.buffer instanceof ArrayBuffer) {
150
+ // 如果是 TypedArray (如 Uint8Array),使用其 buffer
151
+ buffer = Buffer.from(response.data.buffer);
152
+ } else {
153
+ // 尝试直接转换
154
+ buffer = Buffer.from(response.data);
155
+ }
156
+
157
+ await fs.writeFile(zipFilePath, buffer);
158
+ } catch (writeError) {
159
+ spinner.fail(`${consoleTag}读取组件源码文件失败:`, writeError.message);
160
+ throw writeError;
161
+ }
50
162
 
51
163
  // 解压 zip 文件
52
- spinner.text(`${consoleTag}正在解压组件源码...`);
164
+ spinner.info(`${consoleTag}正在解压组件源码...`);
165
+
53
166
  const zip = new AdmZip(zipFilePath);
167
+ // 解压后的目录名称为组件名称:.neo-cli/zip-source/xxCmp
54
168
  const extractPath = path.join(tempDir, finalCmpName);
55
- zip.extractAllTo(extractPath, true);
169
+ zip.extractAllTo(extractPath, true); // 解压到临时目录
56
170
 
57
171
  // 查找解压后的 src/components 目录
58
172
  const cmpSourcePath = path.join(extractPath, componentBaseDir, finalCmpName);
59
173
 
60
174
  if (!fs.existsSync(cmpSourcePath)) {
61
- spinner.stopAndFail(`${consoleTag}解压后的 zip 包中未找到 ${finalCmpName} 组件源码目录。`);
62
- await fs.remove(tempDir);
63
- process.exit(1);
175
+ spinner.fail(`${consoleTag}解压后的 zip 包中未找到 ${finalCmpName} 组件源码目录。`);
176
+ // 只删除解压目录,保留 zip 源文件
177
+ await fs.remove(extractPath);
178
+ return false;
64
179
  }
65
180
 
66
181
  // 确保目标目录存在
@@ -68,22 +183,167 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
68
183
  await fs.ensureDir(targetComponentsDir);
69
184
 
70
185
  // 复制 src/components 目录下的所有内容到目标目录
71
- await fs
72
- .copy(cmpSourcePath, targetComponentsDir)
73
- .catch((err) => spinner.stopAndFail(`${consoleTag}自定义组件模板下载失败:`, err));
186
+ try {
187
+ await fs.copy(cmpSourcePath, targetComponentsDir);
188
+ } catch (err) {
189
+ spinner.fail(`${consoleTag}自定义组件模板下载失败:${err.message || err}`);
190
+ // 只删除解压目录,保留 zip 源文件
191
+ await fs.remove(extractPath);
192
+ return false;
193
+ }
194
+
195
+ // 处理源码中其他文件
196
+ try {
197
+ // 标准化 componentBaseDir,去掉开头的 ./ 并统一路径分隔符
198
+ const normalizedComponentBaseDir = componentBaseDir.replace(/^\.\//, '').replace(/\\/g, '/');
199
+
200
+ /**
201
+ * 递归复制文件,排除 componentBaseDir 目录
202
+ * @param {string} sourceDir 源目录
203
+ * @param {string} targetRoot 目标根目录(项目根目录)
204
+ * @param {string} excludeDir 要排除的目录(相对于源目录)
205
+ */
206
+ const copyOtherFiles = async (sourceDir, targetRoot, excludeDir) => {
207
+ const items = await fs.readdir(sourceDir);
208
+
209
+ for (const item of items) {
210
+ const sourcePath = path.join(sourceDir, item); // 当前源文件路径
211
+ const stat = await fs.stat(sourcePath); // 当前源文件状态
212
+ const fileName = path.basename(sourcePath); // 当前源文件名称
213
+
214
+ // 计算相对于解压根目录的路径
215
+ const relativePath = path.relative(extractPath, sourcePath);
216
+ const normalizedRelativePath = relativePath.replace(/\\/g, '/');
217
+
218
+ // 跳过 componentBaseDir 目录及其子目录
219
+ if (
220
+ normalizedRelativePath.startsWith(excludeDir + '/') ||
221
+ normalizedRelativePath === excludeDir
222
+ ) {
223
+ continue;
224
+ }
225
+
226
+ const targetPath = path.join(targetRoot, relativePath); // 目标文件路径
227
+
228
+ if (stat.isDirectory()) {
229
+ // 如果是目录,递归处理
230
+ await copyOtherFiles(sourcePath, targetRoot, excludeDir);
231
+ } else if (stat.isFile()) {
232
+ // 如果是文件,检查目标文件是否存在
233
+ const targetExists = await fs.pathExists(targetPath);
234
+ if (!targetExists) {
235
+ // 确保目标目录存在
236
+ await fs.ensureDir(path.dirname(targetPath));
237
+ // 复制文件
238
+ await fs.copy(sourcePath, targetPath);
239
+ } else if (fileName === 'package.json') {
240
+ // 处理 package.json 文件(当目标文件已存在时)
241
+ const sourcePackageJson = await fs.readJson(sourcePath);
242
+ const targetPackageJson = await fs.readJson(targetPath);
74
243
 
75
- // 清理临时目录
76
- await fs.remove(tempDir);
244
+ const newPackageJsonDeps = _.omit(
245
+ sourcePackageJson.dependencies,
246
+ targetPackageJson.dependencies
247
+ );
248
+ const newDeps = Object.keys(newPackageJsonDeps);
249
+ if (newDeps.length > 0) {
250
+ console.warn(
251
+ `${consoleTag}检测到 package.json 中新增了 ${
252
+ newDeps.length
253
+ } 个依赖包:${newDeps.join(', ')}`
254
+ );
255
+ console.warn(
256
+ `${consoleTag}为确保组件正常运行,请执行以下命令安装依赖:npm install 或 yarn install`
257
+ );
258
+ }
259
+ // 合并 package.json:将源文件的配置合并到目标文件中
260
+ // 注意:_.merge 会修改第一个参数,所以先克隆目标对象
261
+ const mergedPackageJson = _.merge({}, sourcePackageJson, targetPackageJson);
262
+ await fs.writeJson(targetPath, mergedPackageJson, { spaces: 2 });
263
+ } else if (fileName === 'neo.config.js') {
264
+ // 处理 neo.config.js 文件(当目标文件已存在时)
265
+ try {
266
+ // 清除 require 缓存,确保读取最新内容
267
+ const sourceResolvedPath = require.resolve(sourcePath);
268
+ const targetResolvedPath = require.resolve(targetPath);
269
+ delete require.cache[sourceResolvedPath];
270
+ delete require.cache[targetResolvedPath];
271
+
272
+ // 读取源文件和目标文件的原始内容
273
+ const sourceContent = await fs.readFile(sourcePath, 'utf8');
274
+ const targetContent = await fs.readFile(targetPath, 'utf8');
275
+
276
+ // 读取源文件和目标文件的配置对象(函数调用会被执行)
277
+ const sourceConfig = require(sourcePath);
278
+ const targetConfig = require(targetPath);
279
+
280
+ // 合并配置:源配置合并到目标配置(目标配置优先级更高)
281
+ const mergedConfig = _.merge({}, sourceConfig, targetConfig);
282
+
283
+ // 提取目标文件的头部(module.exports 之前的内容)
284
+ const moduleExportMatch = targetContent.match(/^([\s\S]*?)(module\.exports\s*=\s*\{[\s\S]*\};?\s*)$/);
285
+ let fileHeader = '';
286
+ if (moduleExportMatch) {
287
+ fileHeader = moduleExportMatch[1];
288
+ } else {
289
+ // 如果没有找到 module.exports,尝试提取到最后一个大括号之前的内容
290
+ const lastBraceIndex = targetContent.lastIndexOf('module.exports');
291
+ if (lastBraceIndex > 0) {
292
+ fileHeader = targetContent.substring(0, lastBraceIndex);
293
+ } else {
294
+ // 如果都没有找到,使用默认头部
295
+ fileHeader = "'use strict';\nconst path = require('path');\n\n// 统一路径解析\nfunction resolve(dir) {\n return path.resolve(__dirname, dir);\n}\n\n";
296
+ }
297
+ }
298
+
299
+ // 获取目标文件的目录,用于路径转换(resolve() 基于 __dirname)
300
+ const targetFileDir = path.dirname(targetPath);
301
+
302
+ // 将合并后的配置对象转换为格式化的字符串
303
+ const configString = formatConfigObject(mergedConfig, 0, targetFileDir);
304
+
305
+ // 组合完整的文件内容
306
+ const mergedContent = fileHeader + 'module.exports = ' + configString + ';\n';
307
+
308
+ // 写回文件
309
+ await fs.writeFile(targetPath, mergedContent, 'utf8');
310
+ } catch (configError) {
311
+ spinner.warn(
312
+ `${consoleTag}合并 neo.config.js 配置文件时出现警告:${configError.message || configError}`
313
+ );
314
+ }
315
+ } else if (fileName === 'tsconfig.json') {
316
+ // 处理 tsconfig.json 文件(当目标文件已存在时)
317
+ const sourceTsconfigJson = await fs.readJson(sourcePath);
318
+ // 先判断 targetPath 是否存在,如果存在则合并,否则直接写入
319
+ if (await fs.pathExists(targetPath)) {
320
+ const targetTsconfigJson = _.merge({}, sourceTsconfigJson, targetTsconfigJson);
321
+ await fs.writeJson(targetPath, targetTsconfigJson, { spaces: 2 });
322
+ } else {
323
+ await fs.writeJson(targetPath, sourceTsconfigJson, { spaces: 2 });
324
+ }
325
+ }
326
+ }
327
+ }
328
+ };
329
+ // 开始处理源码中其他文件
330
+ await copyOtherFiles(extractPath, process.cwd(), normalizedComponentBaseDir);
331
+ } catch (err) {
332
+ spinner.warn(`${consoleTag}处理源码文件出现警告:${err.message || err}`);
333
+ }
334
+
335
+ // 清理解压目录,保留 zip 源文件
336
+ await fs.remove(extractPath);
77
337
  spinner.succeed(`${consoleTag}已成功从 zip 包解析自定义组件(${finalCmpName})!`);
78
338
 
79
339
  return true;
80
340
  } catch (error) {
81
- spinner.stopAndFail(
82
- `${consoleTag}从 zip 包创建自定义组件失败(${finalCmpName}):`,
83
- error.message
84
- );
85
- // 清理临时目录
86
- await fs.remove(tempDir);
341
+ spinner.fail(`${consoleTag}从 zip 包创建自定义组件失败(${finalCmpName}):`, error);
342
+ // 清理解压目录,保留 zip 源文件
343
+ const extractPath = path.join(tempDir, finalCmpName);
344
+ if (await fs.pathExists(extractPath)) {
345
+ await fs.remove(extractPath);
346
+ }
87
347
 
88
348
  return false;
89
349
  }
@@ -0,0 +1,63 @@
1
+ const ora = require('ora');
2
+ const NeoService = require('../../neo/neoService');
3
+
4
+ /**
5
+ * 从 NeoCRM 删除自定义组件
6
+ * @param {string} cmpType 自定义组件类型
7
+ * @param {object} authConfig 授权配置
8
+ * @param {object} _neoService 可选的 NeoService 实例(用于复用已获取的组件列表)
9
+ *
10
+ * 删除流程说明
11
+ * 1. 获取当前租户下的自定义组件列表
12
+ * 2. 验证组件是否存在
13
+ * 3. 调用删除 API 删除组件
14
+ */
15
+ const deleteCmp = async (cmpType, authConfig, _neoService) => {
16
+ if (!authConfig) {
17
+ console.error('未找到 NeoCRM 平台授权配置(neo.config.js / authConfig)。');
18
+ return;
19
+ }
20
+
21
+ const spinner = ora('正在删除组件...').start();
22
+
23
+ try {
24
+ let neoService = _neoService;
25
+ let cmpList = [];
26
+ let cmpInfo = null;
27
+
28
+ if (_neoService) {
29
+ // 使用传入的 neoService 实例
30
+ cmpList = _neoService.cmpList || [];
31
+ } else {
32
+ // 创建新的 neoService 实例
33
+ neoService = new NeoService(authConfig);
34
+
35
+ // 获取自定义组件列表
36
+ spinner.info('正在获取自定义组件列表...');
37
+ cmpList = await neoService.getCustomCmpList();
38
+ }
39
+
40
+ if (cmpList.length === 0) {
41
+ spinner.fail('删除失败,当前租户暂无任何自定义组件。');
42
+ process.exit(1);
43
+ }
44
+
45
+ // 获取自定义组件信息
46
+ cmpInfo = neoService.getCmpInfoByCmpType(cmpType);
47
+ if (!cmpInfo) {
48
+ spinner.fail(`删除失败,当前租户不存在${cmpType}自定义组件。`);
49
+ process.exit(1);
50
+ }
51
+
52
+ // 调用删除 API
53
+ spinner.info(`正在删除${cmpType}自定义组件...`);
54
+ await neoService.deleteCustomComponent(cmpType);
55
+ spinner.succeed(`已成功删除${cmpType}自定义组件!\n`);
56
+ } catch (error) {
57
+ spinner.fail(`删除自定义组件失败: ${error.message}`);
58
+ process.exit(1);
59
+ }
60
+ };
61
+
62
+ module.exports = deleteCmp;
63
+
@@ -16,6 +16,14 @@ const framework = getFramework(currentPackageJson.framework);
16
16
  * 从 NeoCRM 拉取自定义组件
17
17
  * @param {string} cmpType 自定义组件类型
18
18
  * @param {object} authConfig 授权配置
19
+ *
20
+ * 拉取流程说明
21
+ * 1. 获取当前租户下的自定义组件列表
22
+ * 2. 验证组件是否存在、是否已存在本地、技术栈是否一致
23
+ * 3. 下载组件源码 zip 包到临时目录(`.neo-cli/zip-source`)
24
+ * 4. 解压并解析组件源码到 `src/components` 目录
25
+ * 5. 合并 `package.json` 中的依赖配置(如有新增依赖会提示安装)
26
+ * 6. 清理解压目录,保留 zip 源文件(便于后续问题排查)
19
27
  */
20
28
  const pullCmp = async (cmpType, authConfig, _neoService) => {
21
29
  if (!authConfig) {
@@ -52,26 +60,26 @@ const pullCmp = async (cmpType, authConfig, _neoService) => {
52
60
  }
53
61
 
54
62
  if (cmpList.length === 0) {
55
- spinner.stopAndFail('拉取失败,当前租户暂无任何自定义组件。');
63
+ spinner.fail('拉取失败,当前租户暂无任何自定义组件。');
56
64
  process.exit(1);
57
65
  }
58
66
 
59
67
  // 获取自定义组件信息
60
68
  cmpInfo = neoService.getCmpInfoByCmpType(cmpType);
61
69
  if (!cmpInfo) {
62
- spinner.stopAndFail(`拉取失败,当前租户不存在${cmpType}自定义组件。`);
70
+ spinner.fail(`拉取失败,当前租户不存在${cmpType}自定义组件。`);
63
71
  process.exit(1);
64
72
  }
65
73
 
66
74
  // 判断拉取的组件和当前项目是否为同一技术栈
67
75
  if (cmpInfo.framework !== framework) {
68
- spinner.stopAndFail(`拉取失败,${cmpType}自定义组件与当前项目技术栈不一致。`);
76
+ spinner.fail(`拉取失败,${cmpType}自定义组件与当前项目技术栈不一致。`);
69
77
  process.exit(1);
70
78
  }
71
79
 
72
80
  // 获取当前源码
73
81
  if (!cmpInfo.codeLib) {
74
- spinner.stopAndFail(`拉取失败,${cmpType}自定义组件未记录对应的源码文件。`);
82
+ spinner.fail(`拉取失败,${cmpType}自定义组件未记录对应的源码文件。`);
75
83
  process.exit(1);
76
84
  }
77
85
 
@@ -81,7 +89,7 @@ const pullCmp = async (cmpType, authConfig, _neoService) => {
81
89
  // 将zip 包里面的自定义组件源码解析到 src/components 目录下
82
90
  const cmpResult = await createCmpByZip(codeLib, cmpType, './src/components');
83
91
  if (!cmpResult) {
84
- spinner.stopAndFail(`拉取失败,${cmpType}自定义组件源码解析失败,请检查源码文件是否正确。`);
92
+ spinner.fail(`拉取失败,${cmpType}自定义组件源码解析失败,请检查源码文件是否正确。`);
85
93
  process.exit(1);
86
94
  }
87
95
 
@@ -82,7 +82,7 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
82
82
  plugin: cmpInfo.modelAsset,
83
83
  version: currentPackageJson.version || '1.0.0',
84
84
  // 技术栈标识: 从 package.json / framework 字段获取,没有则默认 为 react ts 技术栈
85
- framework: currentPackageJson.framework ? getFramework(currentPackageJson.framework) : '0', // 0: React, 1: vue2, 2: jQuery, 3: vue3
85
+ framework: currentPackageJson.framework ? getFramework(currentPackageJson.framework) : 0, // 0: React, 1: vue2, 2: jQuery, 3: vue3
86
86
  // 从模型实例中提取并设置组件信息
87
87
  label: modelInstance.label || cmpType,
88
88
  description: modelInstance.description || '',
@@ -121,6 +121,12 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
121
121
  * 发布组件到 NeoCRM
122
122
  * @param {object} config 配置信息
123
123
  * @param {string} cmpType 自定义组件类型
124
+ *
125
+ * 发布流程说明
126
+ * 1. 打包源码文件:将自定义组件源码打包成 zip 文件
127
+ * 2. 上传构建产物到 OSS:将构建后的资源文件上传到 NeoCRM 平台提供的 CDN
128
+ * 3. 构建组件数据:从组件模型文件中提取组件信息
129
+ * 4. 保存组件信息到 NeoCRM 平台:将组件信息保存到平台数据库
124
130
  */
125
131
  const pushCmp = async (config, cmpType) => {
126
132
  const { auth: credentials } = config;
@@ -5,7 +5,7 @@ const { consoleTag } = require('./neoParams');
5
5
  * 0: React, 1: vue2, 2: jQuery, 3: vue3
6
6
  */
7
7
  function getFramework(_framework) {
8
- let defaultFramework = '0'; // 默认 React 技术栈
8
+ let defaultFramework = 0; // 默认 React 技术栈
9
9
  if (!_framework) {
10
10
  return defaultFramework;
11
11
  }
@@ -13,32 +13,32 @@ function getFramework(_framework) {
13
13
  switch (curFramework) {
14
14
  case 'jquery':
15
15
  case 'jq':
16
- curFramework = '2';
16
+ curFramework = 2;
17
17
  break;
18
18
  case 'vue2':
19
19
  case 'vue 2':
20
20
  case 'vue2.0':
21
21
  case 'vue 2.0':
22
- curFramework = '1';
22
+ curFramework = 1;
23
23
  break;
24
24
  case 'vue':
25
25
  case 'vue3':
26
26
  case 'vue 3':
27
27
  case 'vue3.0':
28
28
  case 'vue 3.0':
29
- curFramework = '3';
29
+ curFramework = 3;
30
30
  console.error(`${consoleTag} 暂不支持 vue3.0 技术栈。`);
31
31
  break;
32
32
  case 'react-js':
33
33
  console.error(`${consoleTag} 暂不支持 react-js 技术栈。`);
34
- curFramework = '0';
34
+ curFramework = 0;
35
35
  break;
36
36
  case 'react':
37
37
  case 'react-ts':
38
- curFramework = '0';
38
+ curFramework = 0;
39
39
  break;
40
40
  default:
41
- curFramework = '0';
41
+ curFramework = 0;
42
42
  }
43
43
  return curFramework;
44
44
  }
package/test/demo2.js ADDED
@@ -0,0 +1,3 @@
1
+ const createCmpByZip = require('../src/utils/cmpUtils/createCmpByZip');
2
+
3
+ createCmpByZip('https://publicfront-1253467224.cos.ap-beijing.myqcloud.com/customComponent/crm-cd/tenant/3256219432312412/1762331900697/entityDetailSource.zip', 'entity-detail');