neo-cmp-cli 1.5.0-beta.3 → 1.5.0-beta.5

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
@@ -1,14 +1,14 @@
1
1
  ## Neo 自定义组件开发工具
2
- neo-cmp-cli 是 Neo 自定义组件开发工具,基于 [AKFun](https://github.com/wibetter/akfun) 的工程能力,提供 初始化、编译构建、预览调试、热更新、多技术栈支持和一键发布等功能。
2
+ neo-cmp-cli 是 Neo 自定义组件开发工具,基于 [AKFun](https://github.com/wibetter/akfun) 的工程能力,提供 初始化、编译构建、预览调试、热更新、多技术栈支持和发布等功能。
3
3
 
4
4
  ### 主要特性
5
5
  - **零配置**: 内置默认配置,开箱可用;
6
6
  - **多技术栈**: 支持 Vue2、React、React+TypeScript 自定义组件的调试、构建与发布;
7
- - **多构建场景**: 本地预览(含热更新/代理)、外链调试、库构建(UMD/ESM);
7
+ - **多构建场景**: 本地预览(含热更新/代理)、外链调试、库构建(UMD/ESM)、部署&发布;
8
8
  - **灵活可配**: 支持 构建入口、别名、代理、SASS 注入、ESLint/StyleLint、Babel/Loader/Plugin 扩展等配置;
9
9
  - **样式与规范**: 内置 Autoprefixer、Sass、PostCSS、ESLint、StyleLint;
10
- - **参数替换**: 支持基于 [params-replace-loader](https://www.npmjs.com/package/params-replace-loader) 的环境变量批量替换;
11
- - **一键发布**: 内置发布到 OSS 的能力,并支持自定义对象存储配置。
10
+ - **发布至 CDN**: 内置发布到对象存储(OSS)的能力,支持自定义对象存储配置;
11
+ - **发布至 NeoCRM 平台**: 支持一键发布到NeoCRM 平台的能力,需自行补充授权配置;
12
12
 
13
13
  ### 内置的自定义组件模板
14
14
  创建自定义组件时(执行初始化命令 neo init)可选用。
@@ -40,8 +40,8 @@ neo preview
40
40
  # 外链调试(在平台线上预览与调试)
41
41
  neo linkDebug
42
42
 
43
- # 构建并发布到 OSS(确保 package.json 的 name 唯一、version 不重复)
44
- neo publish2oss
43
+ # 构建并发布到 NeoCRM(需自行添加授权配置,并确保 package.json 的 name 唯一、version 不重复)
44
+ neo publish2neo
45
45
  ```
46
46
 
47
47
  ### 方法二:在现有业务项目中使用自定义组件开发工具
@@ -58,7 +58,7 @@ npm i neo-cmp-cli --save-dev
58
58
  ```bash
59
59
  "preview": "neo preview",
60
60
  "linkDebug": "neo linkDebug",
61
- "publish2oss": "neo publish2oss"
61
+ "publish2neo": "neo publish2neo"
62
62
  ```
63
63
  ##### 3) 初始化配置文件
64
64
  ```bash
@@ -76,6 +76,7 @@ npm run publish2oss
76
76
  - **neo preview**: 本地预览自定义组件内容,默认支持热更新与接口代理。
77
77
  - **neo linkDebug**: 外链调试模式,在平台端页面设计器中调试自定义组件。
78
78
  - **neo publish2oss**: 构建并上传到对象存储(可自定义配置对象存储)。
79
+ - **neo publish2neo**: 构建并发布到NeoCRM平台(需自行添加授权配置)。
79
80
 
80
81
  ## 开发须知
81
82
  #### 1)默认自动识别自定义组件
@@ -128,17 +129,50 @@ neo linkDebug
128
129
  ##### 3. 页面设计器开启 debug 模式后,左侧会展示 外部链接 管理面板
129
130
  将第 1 步生成的「外链脚本地址」添加进来,即可在此页面设计器 / 组件物料面板中看到对应自定义组件。
130
131
 
131
- #### 6)发布自定义组件
132
- 执行 `neo publish2oss` 即可构建对应自定义组件,并自动将构建后资源上传到对象存储(OSS)中。
132
+ #### 6)发布自定义组件至 NeoCRM
133
+ 执行 `neo publish2neo` 即可构建并发布自定义组件至 NeoCRM 平台,其构建后资源也会上传到 NeoCRM 平台端提供的 CDN 中。
133
134
 
134
135
  ##### 发布前请确保
135
136
  - **package.json 的 name 唯一**
136
137
  - **version 不重复**
137
- - 已按需配置对象存储参数(支持自定义)
138
+
139
+ ##### 需自行添加授权配置
140
+ ```javascript
141
+ module.exports = {
142
+ publishCmp: {
143
+ neoBaseURL: 'https://crm-cd.xiaoshouyi.com', // 平台根地址(默认:https://crm.xiaoshouyi.com)
144
+ tokenAPI: 'https://login-cd.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址(默认:https://login.xiaoshouyi.com/auc/oauth2/token)
145
+ // NeoCRM 授权配置
146
+ authorization: {
147
+ /**
148
+ * 客户端 ID 和 客户端秘钥 需通过 创建连接器 获取,
149
+ * 详细见:https://doc.xiaoshouyi.com / 创建连接器。
150
+ */
151
+ client_id: 'xx', // 客户端 ID,从创建连接器的客户端信息中获取(Client_Id)
152
+ client_secret: 'xxx', // 客户端秘钥,从创建连接器的客户端信息中获取(Client_Secret)
153
+ username: 'xx', // 用户在销售易系统中的用户名
154
+ /**
155
+ * password 为 用户在销售易系统中的账号密码加上 8 位安全令牌。
156
+ * 例如,用户密码为 123456,安全令牌为 ABCDEFGH,则 password 的值应为 123456ABCDEFGH。
157
+ * 如何获取 安全令牌请见:https://doc.xiaoshouyi.com / OAuth安全认证 / 密码模式 / 获取令牌。
158
+ */
159
+ password: 'xx xx' // 用户账户密码 + 8 位安全令牌
160
+ },
161
+ },
162
+ }
163
+ ```
138
164
 
139
165
  ##### 支持发布指定自定义组件
140
166
  执行 `neo publish2oss --cmpType=xxCmp`
141
167
 
168
+ #### 7)发布自定义组件至CDN
169
+ 执行 `neo publish2oss` 即可构建对应自定义组件,并自动将构建后资源上传到对象存储(OSS)中。
170
+
171
+ ##### 发布前请确保
172
+ - **package.json 的 name 唯一**
173
+ - **version 不重复**
174
+ - 可按需配置对象存储参数(支持自定义),默认使用内置对象存储配置。
175
+
142
176
  ##### 支持自定义对象存储配置
143
177
  ```javascript
144
178
  module.exports = {
@@ -159,6 +193,9 @@ module.exports = {
159
193
  }
160
194
  ```
161
195
 
196
+ ##### 支持发布指定自定义组件
197
+ 执行 `neo publish2oss --cmpType=xxCmp`
198
+
162
199
  ## 项目工程配置说明(neo.config.js)
163
200
  neo-cmp-cli 默认提供完整配置;
164
201
  如需自定义,使用 `neo config init` 生成 `neo.config.js` 并按需修改。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.5.0-beta.3",
3
+ "version": "1.5.0-beta.5",
4
4
  "description": "前端脚手架:自定义组件开发工具,支持react 和 vue2.0技术栈。",
5
5
  "keywords": [
6
6
  "neo-cli",
@@ -0,0 +1,41 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { resolveToCurrentRoot } = require('../utils/pathUtils');
4
+ /**
5
+ * 根据当前组件目录,获取所有组件类型
6
+ * @param {*} componentsBaseDir 自定义组件目录
7
+ * @returns 组件类型列表
8
+ */
9
+ const getCmpTypeByDir = (componentsBaseDir = './src/components') => {
10
+ const componentsDir = resolveToCurrentRoot(componentsBaseDir);
11
+ if (!fs.existsSync(componentsDir)) {
12
+ console.error(`未找到自定义组件目录,请检查 ${componentsDir} 目录是否存在。`);
13
+ // 退出进程
14
+ process.exit(1);
15
+ }
16
+
17
+ try {
18
+ // 读取组件目录下的所有子目录
19
+ const dirs = fs.readdirSync(componentsDir);
20
+ const cmpTypes = [];
21
+
22
+ // 遍历所有目录,过滤出有效的组件类型
23
+ dirs.forEach((dir) => {
24
+ const dirPath = path.join(componentsDir, dir);
25
+ const stat = fs.statSync(dirPath);
26
+
27
+ // 只处理目录,过滤掉隐藏目录和 node_modules
28
+ if (stat.isDirectory() && !dir.startsWith('.') && dir !== 'node_modules') {
29
+ cmpTypes.push(dir);
30
+ }
31
+ });
32
+
33
+ return cmpTypes;
34
+ } catch (error) {
35
+ console.error('获取组件类型失败(getCmpTypeByDir):', error);
36
+ // 退出进程
37
+ process.exit(1);
38
+ }
39
+ };
40
+
41
+ module.exports = getCmpTypeByDir;
@@ -99,8 +99,6 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
99
99
  }
100
100
  const widgetName = _.camelCase(cmpType);
101
101
  const modelFile = path.join(assetsRoot, `${widgetName}Model.js`);
102
-
103
- let CatchCustomCmpModelClass = null;
104
102
 
105
103
  // 为 Node.js 环境设置全局 window 对象(模型文件可能需要)
106
104
  // 使用 globalThis 以确保在 Node.js 和浏览器环境中都能工作
@@ -109,6 +107,7 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
109
107
  globalThis.window = {
110
108
  console: console,
111
109
  neoRequire: () => {},
110
+ postMessage: () => {},
112
111
  // 可以添加其他常用的 window 属性
113
112
  };
114
113
  }
@@ -124,35 +123,38 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
124
123
  console.error(`未找到自定义组件模型文件,请检查以下路径是否存在:`, modelFile);
125
124
  return null;
126
125
  }
127
-
128
- if (!CatchCustomCmpModelClass || typeof CatchCustomCmpModelClass !== 'function') {
126
+ if (!window.NEOEditorCustomModels) {
129
127
  console.error(`模型文件未导出有效模型方法(CatchCustomCmpModelClass),模型文件地址: ${modelFile} `);
130
128
  return null;
131
129
  }
132
130
 
133
- // 先获取对应的模型类
134
- const ModelClass = new CatchCustomCmpModelClass(cmpType);
131
+ const ModelClass = window.NEOEditorCustomModels[cmpType];
132
+ if (!ModelClass) {
133
+ console.error(`未找到自定义组件模型类(${cmpType}),模型文件地址: ${modelFile} `);
134
+ return null;
135
+ }
135
136
  // 实例化模型类
136
137
  const modelInstance = new ModelClass();
137
138
 
138
139
  if (!modelInstance) {
139
- console.error(`模型文件未导出有效模型实例(${cmpType}),模型文件地址: ${modelFile} `);
140
+ console.error(`未找到自定义组件模型信息(${cmpType}),模型文件地址: ${modelFile} `);
140
141
  return null;
141
142
  }
142
143
 
143
144
  // 构建组件数据,合并模型实例的信息
144
145
  const curCmpInfo = {
145
146
  ...cmpInfo,
147
+ plugin: cmpInfo.modelAsset,
146
148
  version: currentPackageJson.version || '1.0.0',
147
149
  framework: currentPackageJson.framework ? getFramework(currentPackageJson.framework) : '0', // 0: React, 1: vue2, 2: jQuery, 3: vue3
148
150
  // 从模型实例中提取并设置组件信息
149
151
  label: modelInstance.label || cmpType,
150
152
  description: modelInstance.description || '',
151
- componentCategory: modelInstance.tags || [],
153
+ componentCategory: (modelInstance.tags || []).join(','),
152
154
  icon: modelInstance.iconSrc,
153
- defaultProps: modelInstance.defaultComProps || {},
154
- previewProps: modelInstance.previewComProps || {},
155
- propsSchema: modelInstance.propsSchema || [],
155
+ defaultProps: JSON.stringify(modelInstance.defaultComProps || {}),
156
+ previewProps: JSON.stringify(modelInstance.previewComProps || {}),
157
+ propsSchema: JSON.stringify(modelInstance.propsSchema || []),
156
158
  events: modelInstance.events || [],
157
159
  actions: modelInstance.actions || [],
158
160
  // 如果模型实例中有其他属性,也可以添加
@@ -161,7 +163,7 @@ const buildComponentData = async (assetsRoot, cmpInfo) => {
161
163
  enableDuplicate: modelInstance.enableDuplicate !== undefined ? modelInstance.enableDuplicate : true
162
164
  };
163
165
 
164
- console.log('自定义组件模型信息(${cmpType}):', curCmpInfo);
166
+ console.log(`自定义组件模型信息(${cmpType}):`, curCmpInfo);
165
167
  return curCmpInfo;
166
168
  } catch (error) {
167
169
  console.error(`自定义组件模型文件解析失败 (${modelFile || '未知路径'}):`, error.message);
@@ -9,6 +9,7 @@ const inspect = require('./inspect.js'); // 输出当前项目配置文件
9
9
  const neoConfigInit = require('../utils/neoConfigInit.js');
10
10
  const { validateProjectName } = require('../utils/projectNameValidator.js');
11
11
  const mainAction = require('./main.js'); // 入口文件
12
+ const getCmpTypeByDir = require('../cmpUtils/getCmpTypeByDir.js');
12
13
 
13
14
  // neo 的 package 文件
14
15
  const neoPackage = require('../../package.json');
@@ -168,16 +169,26 @@ yargs
168
169
  if (argv.cmpType) {
169
170
  mainAction.previewCmp(argv.cmpType);
170
171
  } else {
172
+ const cmpTypes = getCmpTypeByDir();
173
+ if (!cmpTypes.lenth || cmpTypes.length === 0) {
174
+ console.error('当前自定义组件目录中未找到自定义组件。(./src/components 目录下)');
175
+ process.exit(1);
176
+ }
177
+ const cmpTypeChoices = cmpTypes.map((cmpType) => ({
178
+ name: cmpType,
179
+ value: cmpType
180
+ }));
171
181
  const questions = [
172
182
  {
173
183
  name: 'cmpType',
174
- type: 'input',
175
- message: '请输入要预览的自定义组件名称:',
184
+ type: 'list',
185
+ message: '请选择要预览的自定义组件:',
186
+ choices: cmpTypeChoices
176
187
  }
177
188
  ];
178
189
  inquirer.prompt(questions).then((ans) => {
179
190
  if (!ans.cmpType) {
180
- console.error('自定义组件名称不能为空。');
191
+ console.error('未选择要预览的自定义组件。');
181
192
  process.exit(1);
182
193
  }
183
194
  mainAction.previewCmp(ans.cmpType);
@@ -251,7 +262,34 @@ yargs
251
262
  .alias('h', 'help');
252
263
  },
253
264
  (argv) => {
254
- mainAction.publish2oss(argv.cmpType); // 构建并发布脚本到oss
265
+ if (argv.cmpType) {
266
+ mainAction.publish2oss(argv.cmpType); // 构建并发布脚本到oss
267
+ } else {
268
+ const cmpTypes = getCmpTypeByDir();
269
+ if (!cmpTypes.lenth || cmpTypes.length === 0) {
270
+ console.error('当前自定义组件目录中未找到自定义组件。(./src/components 目录下)');
271
+ process.exit(1);
272
+ }
273
+ const cmpTypeChoices = cmpTypes.map((cmpType) => ({
274
+ name: cmpType,
275
+ value: cmpType
276
+ }));
277
+ const questions = [
278
+ {
279
+ name: 'cmpType',
280
+ type: 'list',
281
+ message: '请选择要发布的自定义组件:',
282
+ choices: cmpTypeChoices
283
+ }
284
+ ];
285
+ inquirer.prompt(questions).then((ans) => {
286
+ if (!ans.cmpType) {
287
+ console.error('未选择要发布的自定义组件。');
288
+ process.exit(1);
289
+ }
290
+ mainAction.publish2oss(ans.cmpType);
291
+ });
292
+ }
255
293
  }
256
294
  )
257
295
  .command(
@@ -271,16 +309,26 @@ yargs
271
309
  if (argv.cmpType) {
272
310
  mainAction.publishCmp(argv.cmpType); // 构建并发布组件到 NeoCRM
273
311
  } else {
312
+ const cmpTypes = getCmpTypeByDir();
313
+ if (!cmpTypes.lenth || cmpTypes.length === 0) {
314
+ console.error('当前自定义组件目录中未找到自定义组件。(./src/components 目录下)');
315
+ process.exit(1);
316
+ }
317
+ const cmpTypeChoices = cmpTypes.map((cmpType) => ({
318
+ name: cmpType,
319
+ value: cmpType
320
+ }));
274
321
  const questions = [
275
322
  {
276
323
  name: 'cmpType',
277
- type: 'input',
278
- message: '请输入要发布的自定义组件名称:'
324
+ type: 'list',
325
+ message: '请选择要发布的自定义组件:',
326
+ choices: cmpTypeChoices
279
327
  }
280
328
  ];
281
329
  inquirer.prompt(questions).then((ans) => {
282
330
  if (!ans.cmpType) {
283
- console.error('自定义组件名称不能为空。');
331
+ console.error('未选择要发布的自定义组件。');
284
332
  process.exit(1);
285
333
  }
286
334
  mainAction.publishCmp(ans.cmpType);
@@ -123,7 +123,7 @@ class NeoService {
123
123
  const { access_token, expires_in } = response.data || {};
124
124
 
125
125
  if (!access_token) {
126
- console.error('获取 token 失败:响应中未包含 access_token', response.data);
126
+ console.error('\n获取 token 失败:响应中未包含 access_token', response.data);
127
127
  process.exit(1);
128
128
  }
129
129
 
@@ -135,7 +135,7 @@ class NeoService {
135
135
  };
136
136
  return access_token;
137
137
  } catch (error) {
138
- console.error('获取 token 失败:', error.message);
138
+ console.error('\n获取 token 失败:', error.message);
139
139
  console.error('\ntoken 授权地址:', tokenUrl);
140
140
  console.error('\ntoken 请求参数:', formData);
141
141
  if (error.response) {
@@ -296,11 +296,11 @@ class NeoService {
296
296
  } else if (resultData && typeof resultData === 'object' && resultData.url) {
297
297
  fileUrl = resultData.url;
298
298
  }
299
- console.info(`文件上传成功: ${fileName} -> ${fileUrl}`);
299
+ console.info(`\n文件上传成功: ${fileName} -> ${fileUrl}`);
300
300
  return fileUrl;
301
301
  } catch (error) {
302
- console.error(`上传文件失败: ${error.message}`);
303
- console.error(`文件路径: ${filePath}`);
302
+ console.error(`\n上传文件失败: ${error.message},`);
303
+ console.error(`文件路径: ${filePath}。\n`);
304
304
 
305
305
  // 输出详细的错误信息
306
306
  if (error.response) {
@@ -412,7 +412,6 @@ class NeoService {
412
412
 
413
413
  try {
414
414
  const fullUpdateAPI = this.saveAPI();
415
- console.info('更新组件 API 地址:', fullUpdateAPI);
416
415
  const response = await axios.post(fullUpdateAPI, componentData, {
417
416
  headers: {
418
417
  Authorization: `Bearer ${token}`,
@@ -420,17 +419,19 @@ class NeoService {
420
419
  'Content-Type': 'application/json'
421
420
  }
422
421
  });
422
+ const {code, message} = response.data || {};
423
423
 
424
- if (response.data && response.data.code && response.data.code !== 200) {
424
+ if (code && code !== 200) {
425
425
  throw new Error(`更新组件失败: ${response.data.message || '未知错误'}`);
426
426
  }
427
+
427
428
 
428
- console.info('组件更新成功:', response.data);
429
- return response.data;
429
+ console.info(message ? `组件更新成功: ${message}。` : '组件更新成功。');
430
430
  } catch (error) {
431
- console.error('更新组件失败:', error.message);
432
- if (error.response) {
433
- console.error('响应数据:', error.response.data);
431
+ if (error.message) {
432
+ console.error('更新组件失败:', error.message);
433
+ } else {
434
+ console.error('响应数据:', error);
434
435
  }
435
436
  process.exit(1);
436
437
  }
@@ -1,4 +1,6 @@
1
1
  const { ConcatSource } = require('webpack-sources');
2
+ const fs = require('fs');
3
+ const path = require('path');
2
4
  /**
3
5
  * 注入 neoRequire 函数
4
6
  * 备注:用于实现和 Neo 平台共享依赖
@@ -162,9 +164,9 @@ class AddNeoRequirePlugin {
162
164
  })(function(require) {
163
165
  `;
164
166
 
165
- const NeoUMDContent = fs.readFileSync(path.join(__dirname, '../neo/NeoUMDContent.js'), 'utf8');
166
-
167
- const Footer = `}); \n${NeoUMDContent}`;
167
+ // const NeoUMDContent = fs.readFileSync(path.join(__dirname, '../neo/NeoUMDContent.js'), 'utf8');
168
+ // const Footer = `}); \n${NeoUMDContent}`;
169
+ const Footer = `});`;
168
170
 
169
171
  // 创建新的资源
170
172
  const newSource = new ConcatSource(Header, content, Footer);
@@ -127,7 +127,6 @@ module.exports = {
127
127
  */
128
128
  },
129
129
  publishCmp: {
130
- libraryName: 'NeoCustomCmpModel', // 构建第三方功能包时最后导出的引用变量名
131
130
  // 用于构建并发布至 NeoCRM 的相关配置
132
131
  neoBaseURL: 'https://crm-cd.xiaoshouyi.com', // 平台根地址(默认:https://crm.xiaoshouyi.com)
133
132
  tokenAPI: 'https://login-cd.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址(默认:https://login.xiaoshouyi.com/auc/oauth2/token)
@@ -53,7 +53,7 @@
53
53
  "@types/react": "^16.9.11",
54
54
  "@types/react-dom": "^16.9.15",
55
55
  "@types/axios": "^0.14.0",
56
- "neo-cmp-cli": "^1.5.0-beta.2",
56
+ "neo-cmp-cli": "^1.5.0-beta.3",
57
57
  "husky": "^4.2.5",
58
58
  "lint-staged": "^10.2.9",
59
59
  "prettier": "^2.0.5"
@@ -278,8 +278,14 @@ export default class EntityDetail extends React.PureComponent<
278
278
 
279
279
  return (
280
280
  <div className="entity-detail-container">
281
- <img src="https://custom-widget2.oss-cn-beijing.aliyuncs.com/img.png" alt="logo" />
282
- <img src="https://publicfront-1253467224.cos.ap-beijing.myqcloud.com/customComponent/crm-cd/tenant/3392331536784858/1761882606360/img.png" alt="logo" />
281
+ <img
282
+ src="https://custom-widget2.oss-cn-beijing.aliyuncs.com/img.png"
283
+ alt="logo"
284
+ />
285
+ <img
286
+ src="https://publicfront-1253467224.cos.ap-beijing.myqcloud.com/customComponent/crm-cd/tenant/3392331536784858/1761882606360/img.png"
287
+ alt="logo"
288
+ />
283
289
  {showTitle && (
284
290
  <div className="detail-header">
285
291
  <div className="header-content">