neo-cmp-cli 1.6.0-beta.9 → 1.6.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/README.md CHANGED
@@ -12,8 +12,8 @@ neo-cmp-cli 是 Neo 自定义组件开发工具,基于 [AKFun](https://github.
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,8 @@ 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,需自行添加授权配置)。
101
100
 
102
101
  ## 开发须知
103
102
  #### 1)默认自动识别自定义组件
@@ -153,11 +152,26 @@ neo linkDebug
153
152
  #### 6)发布自定义组件至 NeoCRM
154
153
  执行 `neo pushCmp` 即可构建并发布自定义组件至 NeoCRM 平台,其构建后资源也会上传到 NeoCRM 平台端提供的 CDN 中。
155
154
 
155
+ ##### 使用方式
156
+ ```bash
157
+ # 方式一:交互式选择要发布的自定义组件
158
+ neo pushCmp
159
+
160
+ # 方式二:直接指定要发布的自定义组件名称
161
+ neo pushCmp --name=xxCmp
162
+ # 或
163
+ neo pushCmp -n xxCmp
164
+ ```
165
+
156
166
  ##### 发布前请确保
157
- - **package.json 的 name 唯一**
158
- - **version 不重复**
167
+ - **package.json 的 name 唯一**:确保当前项目的 `package.json` 中的 `name` 字段在 NeoCRM 平台中唯一
168
+ - **version 不重复**:每次发布时,请更新 `package.json` 中的 `version` 字段,确保版本号不重复
169
+ - **已配置 NeoCRM 平台授权信息**:在 `neo.config.js` 中配置 `neoConfig.auth`
170
+ - **自定义组件已正确构建**:确保组件代码可以正常构建,且 `dist` 目录下有对应的构建产物
171
+ - **组件模型文件存在**:确保组件目录下有 `model.ts` 或 `model.js` 文件,且正确导出了模型类
159
172
 
160
173
  ##### 需自行添加授权配置
174
+ 在项目根目录的 `neo.config.js` 文件中添加 NeoCRM 平台授权配置:
161
175
  ```javascript
162
176
  module.exports = {
163
177
  neoConfig: {
@@ -181,13 +195,66 @@ module.exports = {
181
195
  }
182
196
  ```
183
197
 
184
- ##### 支持发布指定自定义组件
185
- 执行 `neo pushCmp --name=xxCmp`
198
+ ##### 授权配置获取方式
199
+ 1. **客户端 ID 和客户端秘钥**:需通过创建连接器获取
200
+ - 访问 [销售易文档中心](https://doc.xiaoshouyi.com) / 创建连接器
201
+ - 创建连接器后,从客户端信息中获取 `Client_Id` 和 `Client_Secret`
202
+ 2. **安全令牌**:如何获取安全令牌
203
+ - 访问 [销售易文档中心](https://doc.xiaoshouyi.com) / OAuth安全认证 / 密码模式 / 获取令牌
204
+ - 按照文档说明获取 8 位安全令牌
205
+ - `password` 字段 = 用户账户密码 + 8 位安全令牌(直接拼接,无空格或分隔符)
206
+
207
+ ##### 注意事项
208
+ - **版本管理**:每次发布前务必更新 `package.json` 中的 `version` 字段,避免版本冲突
209
+ - **组件名称唯一性**:确保 `package.json` 中的 `name` 字段在平台中唯一
210
+ - **模型文件要求**:组件必须包含有效的模型文件(`model.ts` 或 `model.js`),且正确导出模型类
211
+ - **技术栈一致性**:确保组件使用的技术栈与项目配置一致(React、React+TS、Vue2 等)
212
+ - **构建产物完整性**:确保 `dist` 目录下包含组件的 JS 文件和模型文件(`xxCmp.js` 和 `xxCmpModel.js`)
213
+ - **网络连接**:发布过程需要网络连接,确保可以访问 NeoCRM 平台和 OSS 服务
214
+
215
+ ##### 常见问题
216
+
217
+ **Q: 发布失败,提示"未找到 NeoCRM 平台授权配置"**
218
+ A: 请检查 `neo.config.js` 文件中是否配置了 `neoConfig.auth`,确保所有授权字段都已正确填写。
219
+
220
+ **Q: 发布失败,提示"未找到自定义组件模型文件"**
221
+ A: 请确保组件目录下有 `model.ts` 或 `model.js` 文件,且构建后在 `dist` 目录下生成了对应的 `xxCmpModel.js` 文件。
222
+
223
+ **Q: 发布失败,提示"version 不重复"**
224
+ A: 请更新 `package.json` 中的 `version` 字段,使用新的版本号(如从 `1.0.0` 更新为 `1.0.1`)。
225
+
226
+ **Q: 发布成功但平台端看不到组件**
227
+ A: 请检查组件模型文件中的 `exposedToDesigner` 属性是否为 `true`,确保组件已暴露给设计器使用。
186
228
 
187
229
  ##### 线上 NeoCRM 端使用
188
230
  发布成功后,即可在对应租户环境下的页面设计器和表单设计器中使用此自定义组件。
189
231
 
190
- #### 7)发布自定义组件至CDN
232
+ #### 7)拉取线上自定义组件至本地项目
233
+ 执行 `neo pullCmp` 即可从 NeoCRM 平台拉取自定义组件源码到当前项目。
234
+ 该命令会将线上自定义组件的源码下载并解析到当前项目的 `src/components` 目录下。
235
+
236
+ ##### 使用方式
237
+ ```bash
238
+ # 方式一:交互式选择要拉取的自定义组件
239
+ neo pullCmp
240
+
241
+ # 方式二:直接指定要拉取的自定义组件名称
242
+ neo pullCmp --name=xxCmp
243
+ # 或
244
+ neo pullCmp -n xxCmp
245
+ ```
246
+
247
+ ##### 拉取前请确保
248
+ - **已配置 NeoCRM 平台授权信息**(neo.config.js 中的 neoConfig.auth)
249
+ - **当前项目目录中不存在同名自定义组件**(`./src/components` 目录下)
250
+ - **拉取的组件与当前项目技术栈一致**(React、React+TS、Vue2 等)
251
+
252
+ ##### 注意事项
253
+ - 拉取的自定义组件会覆盖本地同名组件,请谨慎操作
254
+ - 如果组件源码中包含新增的依赖包,拉取完成后会提示执行 `npm install` 或 `yarn install`
255
+ - zip 源文件会保留在 `.neo-cli/zip-source` 目录下,可手动删除
256
+
257
+ #### 8)发布自定义组件至CDN
191
258
  执行 `neo publish2oss` 即可构建对应自定义组件,并自动将构建后资源上传到对象存储(OSS)中。
192
259
  备注:请优先使用 neo pushCmp。
193
260
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.6.0-beta.9",
3
+ "version": "1.6.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 = [
@@ -157,9 +157,11 @@ class NeoService {
157
157
  token: access_token,
158
158
  expiresAt: Date.now() + (expiresIn - 60) * 1000
159
159
  };
160
- spinner.succeed('token 获取成功。');
160
+ spinner.clear();
161
+ spinner.stop();
161
162
  return access_token;
162
163
  } catch (error) {
164
+ spinner.fail('获取 token 失败');
163
165
  console.error('\n获取 token 失败:', error.message);
164
166
  console.error('\ntoken 授权地址:', tokenUrl);
165
167
  console.error('\ntoken 请求参数:', formData);
@@ -193,9 +195,14 @@ class NeoService {
193
195
  return await this.getToken();
194
196
  } else if (this.isTokenExpired()) {
195
197
  const spinner = ora('token 已过期,正在刷新...').start();
196
- const token = await this.refreshToken();
197
- spinner.succeed('token 刷新成功。');
198
- return token;
198
+ try {
199
+ const token = await this.refreshToken();
200
+ spinner.succeed('token 刷新成功。');
201
+ return token;
202
+ } catch (error) {
203
+ spinner.fail('token 刷新失败。');
204
+ throw error;
205
+ }
199
206
  }
200
207
  return this.tokenCache.token;
201
208
  }
@@ -458,7 +465,8 @@ class NeoService {
458
465
  throw new Error(`更新组件失败: ${response.data.message || '未知错误'}`);
459
466
  }
460
467
 
461
- spinner.succeed('组件更新成功。');
468
+ spinner.clear();
469
+ spinner.stop();
462
470
  } catch (error) {
463
471
  spinner.fail('更新组件失败。');
464
472
  if (error.message) {
@@ -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,6 +3,7 @@ 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');
@@ -14,7 +15,7 @@ const hasNeoProject = require('../projectUtils/hasNeoProject');
14
15
  * @param {*} componentBaseDir 自定义组件目录
15
16
  */
16
17
  module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/components') {
17
- const finalCmpName = cmpName || 'neoCustomCmp';
18
+ const finalCmpName = cmpName;
18
19
 
19
20
  if (!hasNeoProject()) {
20
21
  console.error(
@@ -31,36 +32,75 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
31
32
  // 创建临时目录
32
33
  const tempDir = path.join(process.cwd(), '.neo-cli', 'zip-source');
33
34
 
35
+ const spinner = ora(`${consoleTag}正在下载组件源码...`).start();
36
+
34
37
  // 下载源码文件并解析到 src/components 目录下
35
38
  try {
36
39
  await fs.ensureDir(tempDir); // 如果目录不存在,会自动创建(包括所有必要的父目录)
37
40
 
38
- // 下载 zip 文件
39
- const spinner = ora(`${consoleTag}正在下载组件源码...`).start();
40
41
  const zipFilePath = path.join(tempDir, `${finalCmpName}.zip`);
41
42
 
42
- const response = await axios({
43
- url: cmpZipUrl,
44
- method: 'GET',
45
- responseType: 'arraybuffer'
46
- });
43
+ // 检查是否存在同名 zip 包,如果存在则先删除
44
+ if (await fs.pathExists(zipFilePath)) {
45
+ await fs.remove(zipFilePath);
46
+ }
47
+
48
+ // 下载 zip 文件
49
+ let response;
50
+ try {
51
+ response = await axios({
52
+ url: cmpZipUrl,
53
+ method: 'GET',
54
+ responseType: 'arraybuffer',
55
+ timeout: 60000, // 60秒超时
56
+ maxContentLength: Infinity,
57
+ maxBodyLength: Infinity
58
+ });
59
+ } catch (axiosError) {
60
+ spinner.fail(`${consoleTag}下载文件失败:`, axiosError.message);
61
+ throw axiosError;
62
+ }
47
63
 
48
64
  // 保存 zip 文件到临时目录
49
- await fs.writeFile(zipFilePath, response.data);
65
+ try {
66
+ let buffer;
67
+ // 处理不同的数据类型
68
+ if (Buffer.isBuffer(response.data)) {
69
+ // 如果已经是 Buffer,直接使用
70
+ buffer = response.data;
71
+ } else if (response.data instanceof ArrayBuffer) {
72
+ // 如果是 ArrayBuffer,转换为 Buffer
73
+ buffer = Buffer.from(response.data);
74
+ } else if (response.data.buffer instanceof ArrayBuffer) {
75
+ // 如果是 TypedArray (如 Uint8Array),使用其 buffer
76
+ buffer = Buffer.from(response.data.buffer);
77
+ } else {
78
+ // 尝试直接转换
79
+ buffer = Buffer.from(response.data);
80
+ }
81
+
82
+ await fs.writeFile(zipFilePath, buffer);
83
+ } catch (writeError) {
84
+ spinner.fail(`${consoleTag}读取组件源码文件失败:`, writeError.message);
85
+ throw writeError;
86
+ }
50
87
 
51
88
  // 解压 zip 文件
52
- spinner.text(`${consoleTag}正在解压组件源码...`);
89
+ spinner.info(`${consoleTag}正在解压组件源码...`);
90
+
53
91
  const zip = new AdmZip(zipFilePath);
92
+ // 解压后的目录名称为组件名称:.neo-cli/zip-source/xxCmp
54
93
  const extractPath = path.join(tempDir, finalCmpName);
55
- zip.extractAllTo(extractPath, true);
94
+ zip.extractAllTo(extractPath, true); // 解压到临时目录
56
95
 
57
96
  // 查找解压后的 src/components 目录
58
97
  const cmpSourcePath = path.join(extractPath, componentBaseDir, finalCmpName);
59
98
 
60
99
  if (!fs.existsSync(cmpSourcePath)) {
61
100
  spinner.fail(`${consoleTag}解压后的 zip 包中未找到 ${finalCmpName} 组件源码目录。`);
62
- await fs.remove(tempDir);
63
- process.exit(1);
101
+ // 只删除解压目录,保留 zip 源文件
102
+ await fs.remove(extractPath);
103
+ return false;
64
104
  }
65
105
 
66
106
  // 确保目标目录存在
@@ -68,22 +108,105 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
68
108
  await fs.ensureDir(targetComponentsDir);
69
109
 
70
110
  // 复制 src/components 目录下的所有内容到目标目录
71
- await fs
72
- .copy(cmpSourcePath, targetComponentsDir)
73
- .catch((err) => spinner.fail(`${consoleTag}自定义组件模板下载失败:${err.message || err}`));
111
+ try {
112
+ await fs.copy(cmpSourcePath, targetComponentsDir);
113
+ } catch (err) {
114
+ spinner.fail(`${consoleTag}自定义组件模板下载失败:${err.message || err}`);
115
+ // 只删除解压目录,保留 zip 源文件
116
+ await fs.remove(extractPath);
117
+ return false;
118
+ }
119
+
120
+ // 处理源码中其他文件
121
+ try {
122
+ // 标准化 componentBaseDir,去掉开头的 ./ 并统一路径分隔符
123
+ const normalizedComponentBaseDir = componentBaseDir.replace(/^\.\//, '').replace(/\\/g, '/');
124
+
125
+ /**
126
+ * 递归复制文件,排除 componentBaseDir 目录
127
+ * @param {string} sourceDir 源目录
128
+ * @param {string} targetRoot 目标根目录(项目根目录)
129
+ * @param {string} excludeDir 要排除的目录(相对于源目录)
130
+ */
131
+ const copyOtherFiles = async (sourceDir, targetRoot, excludeDir) => {
132
+ const items = await fs.readdir(sourceDir);
133
+
134
+ for (const item of items) {
135
+ const sourcePath = path.join(sourceDir, item); // 当前源文件路径
136
+ const stat = await fs.stat(sourcePath); // 当前源文件状态
137
+ const fileName = path.basename(sourcePath); // 当前源文件名称
138
+
139
+ // 计算相对于解压根目录的路径
140
+ const relativePath = path.relative(extractPath, sourcePath);
141
+ const normalizedRelativePath = relativePath.replace(/\\/g, '/');
142
+
143
+ // 跳过 componentBaseDir 目录及其子目录
144
+ if (
145
+ normalizedRelativePath.startsWith(excludeDir + '/') ||
146
+ normalizedRelativePath === excludeDir
147
+ ) {
148
+ continue;
149
+ }
150
+
151
+ const targetPath = path.join(targetRoot, relativePath); // 目标文件路径
152
+
153
+ if (stat.isDirectory()) {
154
+ // 如果是目录,递归处理
155
+ await copyOtherFiles(sourcePath, targetRoot, excludeDir);
156
+ } else if (stat.isFile()) {
157
+ // 如果是文件,检查目标文件是否存在
158
+ const targetExists = await fs.pathExists(targetPath);
159
+ if (!targetExists) {
160
+ // 确保目标目录存在
161
+ await fs.ensureDir(path.dirname(targetPath));
162
+ // 复制文件
163
+ await fs.copy(sourcePath, targetPath);
164
+ } else if (fileName === 'package.json') {
165
+ // 处理 package.json 文件(当目标文件已存在时)
166
+ const sourcePackageJson = await fs.readJson(sourcePath);
167
+ const targetPackageJson = await fs.readJson(targetPath);
168
+
169
+ const newPackageJsonDeps = _.omit(
170
+ sourcePackageJson.dependencies,
171
+ targetPackageJson.dependencies
172
+ );
173
+ const newDeps = Object.keys(newPackageJsonDeps);
174
+ if (newDeps.length > 0) {
175
+ console.warn(
176
+ `${consoleTag}检测到 package.json 中新增了 ${
177
+ newDeps.length
178
+ } 个依赖包:${newDeps.join(', ')}`
179
+ );
180
+ console.warn(
181
+ `${consoleTag}为确保组件正常运行,请执行以下命令安装依赖:npm install 或 yarn install`
182
+ );
183
+ }
184
+ // 合并 package.json:将源文件的配置合并到目标文件中
185
+ // 注意:_.merge 会修改第一个参数,所以先克隆目标对象
186
+ const mergedPackageJson = _.merge({}, sourcePackageJson, targetPackageJson);
187
+ await fs.writeJson(targetPath, mergedPackageJson, { spaces: 2 });
188
+ }
189
+ }
190
+ }
191
+ };
192
+ // 开始处理源码中其他文件
193
+ await copyOtherFiles(extractPath, process.cwd(), normalizedComponentBaseDir);
194
+ } catch (err) {
195
+ spinner.warn(`${consoleTag}处理源码文件出现警告:${err.message || err}`);
196
+ }
74
197
 
75
- // 清理临时目录
76
- await fs.remove(tempDir);
198
+ // 清理解压目录,保留 zip 源文件
199
+ await fs.remove(extractPath);
77
200
  spinner.succeed(`${consoleTag}已成功从 zip 包解析自定义组件(${finalCmpName})!`);
78
201
 
79
202
  return true;
80
203
  } catch (error) {
81
- spinner.fail(
82
- `${consoleTag}从 zip 包创建自定义组件失败(${finalCmpName}):`,
83
- error.message
84
- );
85
- // 清理临时目录
86
- await fs.remove(tempDir);
204
+ spinner.fail(`${consoleTag}从 zip 包创建自定义组件失败(${finalCmpName}):`, error);
205
+ // 清理解压目录,保留 zip 源文件
206
+ const extractPath = path.join(tempDir, finalCmpName);
207
+ if (await fs.pathExists(extractPath)) {
208
+ await fs.remove(extractPath);
209
+ }
87
210
 
88
211
  return false;
89
212
  }
@@ -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) {
@@ -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');