neo-cmp-cli 1.6.1 → 1.6.2

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,7 +8,7 @@ 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)可选用。
@@ -96,7 +96,8 @@ module.exports = {
96
96
  - **neo linkDebug**: 外链调试模式,在平台端页面设计器中调试自定义组件;
97
97
  - **neo publish2oss**: 构建并上传到对象存储(支持 --name,可自定义配置对象存储);
98
98
  - **neo pushCmp**: 构建并发布到NeoCRM平台(支持 --name,需自行添加授权配置);
99
- - **neo pullCmp**: 拉取线上自定义组件(NeoCRM平台)至当前项目(支持 --name,需自行添加授权配置)。
99
+ - **neo pullCmp**: 拉取线上自定义组件(NeoCRM平台)至当前项目(支持 --name,需自行添加授权配置);
100
+ - **neo deleteCmp**: 删除线上自定义组件(NeoCRM平台)(支持 --name,需自行添加授权配置)。
100
101
 
101
102
  ## 开发须知
102
103
  #### 1)默认自动识别自定义组件
@@ -220,11 +221,8 @@ A: 请检查 `neo.config.js` 文件中是否配置了 `neoConfig.auth`,确保
220
221
  **Q: 发布失败,提示"未找到自定义组件模型文件"**
221
222
  A: 请确保组件目录下有 `model.ts` 或 `model.js` 文件,且构建后在 `dist` 目录下生成了对应的 `xxCmpModel.js` 文件。
222
223
 
223
- **Q: 发布失败,提示"version 不重复"**
224
- A: 请更新 `package.json` 中的 `version` 字段,使用新的版本号(如从 `1.0.0` 更新为 `1.0.1`)。
225
-
226
- **Q: 发布成功但平台端看不到组件**
227
- A: 请检查组件模型文件中的 `exposedToDesigner` 属性是否为 `true`,确保组件已暴露给设计器使用。
224
+ **Q: 发布失败,提示"获取 token 失败(授权配置错误)"**
225
+ A: 请检查 `neo.config.js` 文件中是否配置了 `neoConfig.auth`,确保所有授权字段都已正确填写。
228
226
 
229
227
  ##### 线上 NeoCRM 端使用
230
228
  发布成功后,即可在对应租户环境下的页面设计器和表单设计器中使用此自定义组件。
@@ -254,7 +252,31 @@ neo pullCmp -n xxCmp
254
252
  - 如果组件源码中包含新增的依赖包,拉取完成后会提示执行 `npm install` 或 `yarn install`
255
253
  - zip 源文件会保留在 `.neo-cli/zip-source` 目录下,可手动删除
256
254
 
257
- #### 8)发布自定义组件至CDN
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
258
280
  执行 `neo publish2oss` 即可构建对应自定义组件,并自动将构建后资源上传到对象存储(OSS)中。
259
281
  备注:请优先使用 neo pushCmp。
260
282
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "前端脚手架:自定义组件开发工具,支持react 和 vue2.0技术栈。",
5
5
  "keywords": [
6
6
  "neo-cli",
@@ -5,7 +5,7 @@
5
5
  <meta name="format-detection" content="telephone=no"/>
6
6
  <meta name="viewport" content="initial-scale=1.0,user-scalable=no,width=device-width,viewport-fit=cover">
7
7
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
- <title>neo-widget Demo</title>
8
+ <title>自定义组件预览页</title>
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -25,19 +25,30 @@ module.exports = {
25
25
  $public: resolve('public')
26
26
  }
27
27
  },
28
- // sassResources中的sass文件会自动注入每一个sass文件中
28
+ // sassResources 中的 sass 文件会自动注入项目中每一个 sass 文件中
29
29
  sassResources: [
30
30
  resolve('./src/assets/css/common.scss'),
31
31
  resolve('./src/assets/css/mixin.scss')
32
32
  ],
33
33
  // createDeclaration: true, // 打包时是否创建ts声明文件
34
- ignoreNodeModules: false, // 打包时是否忽略 node_modules
35
- allowList: [], // ignoreNodeModules为true时生效
36
- projectDir: ['src']
34
+ ignoreNodeModules: false // 打包时是否忽略 node_modules
35
+ // allowList: [], // ignoreNodeModules true 时生效
36
+ // projectDir: ['src'],
37
37
  // template: resolve('./public/template.html'), // 自定义html模板
38
38
  // plugins: [],
39
39
  // babelPlugins: [],
40
40
  },
41
+ // 用于添加 Neo 共享依赖模块的配置信息
42
+ /*
43
+ neoCommonModule: {
44
+ // neoExports: ['xxModule'], // 自定义组件 共享出来的模块,支持数组和对象形式
45
+ neoExports: { // 对象写法
46
+ 'xx-module': path.resolve('./src/components/xx-module'), // 导出 xx组件 或 xx模块
47
+ },
48
+ // remoteDeps: ['xxCmpType'], // 远程依赖组件,表示当前自定义组件会用到的依赖组件,需要和 neoExternals 配合使用
49
+ // neoExternals: ['xxModule'], // 自定义组件中需要剔除的模块,仅支持数组写法
50
+ },
51
+ */
41
52
  preview: {
42
53
  // 用于开启本地预览模式的相关配置信息
43
54
  /*
@@ -67,12 +78,13 @@ module.exports = {
67
78
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
68
79
  // 外链调试(在线上页面设计器端预览自定义组件)
69
80
  index: [
70
- './src/components/info-card/register.ts',
71
- './src/components/info-card/model.ts',
81
+ './src/components/xxCmp/register.ts',
82
+ './src/components/xxCmp/model.ts',
72
83
  ],
73
84
  },
74
85
  NODE_ENV: 'development',
75
86
  port: 80, // 设置基础端口,如果被占用则自动寻找可用端口
87
+ closeHotReload: true, // 是否关闭热更新
76
88
  assetsPublicPath: '/', // 设置静态资源的引用路径(根域名+路径)
77
89
  assetsSubDirectory: '',
78
90
  hostname: 'localhost',
@@ -85,23 +97,32 @@ module.exports = {
85
97
  }
86
98
  */
87
99
  },
88
- publish2oss: {
89
- // 用于构建并发布至 OSS 的相关配置
100
+ // NeoCRM 平台配置
101
+ neoConfig: {
102
+ neoBaseURL: 'https://crm-cd.xiaoshouyi.com', // 平台根地址(默认:https://crm.xiaoshouyi.com)
103
+ tokenAPI: 'https://login-cd.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址(默认:https://login.xiaoshouyi.com/auc/oauth2/token)
104
+ // NeoCRM 授权配置
105
+ auth: {
106
+ client_id: 'xx', // 客户端 ID,从创建连接器的客户端信息中获取(Client_Id)
107
+ client_secret: 'xxx', // 客户端秘钥,从创建连接器的客户端信息中获取(Client_Secret)
108
+ username: 'xx', // 用户在销售易系统中的用户名
109
+ /**
110
+ * password 为 用户在销售易系统中的账号密码加上 8 位安全令牌。
111
+ * 例如,用户密码为 123456,安全令牌为 ABCDEFGH,则 password 的值应为 123456ABCDEFGH。
112
+ */
113
+ password: 'xx xx' // 用户账户密码 + 8 位安全令牌
114
+ }
115
+ },
116
+ pushCmp: {
117
+ // 用于构建并发布至 NeoCRM 的相关配置
90
118
  /*
91
119
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
92
120
  NODE_ENV: 'production',
93
121
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
94
- InfoCardModel: './src/components/info-card/model.ts',
95
- infoCard: './src/components/info-card/register.ts'
122
+ InfoCardModel: './src/components/xxCmp/model.ts',
123
+ infoCard: './src/components/xxCmp/register.ts'
96
124
  },
97
125
  cssExtract: false, // 不额外提取css文件
98
- ossType: 'ali', // oss类型:ali、baidu
99
- ossConfig: {
100
- endpoint: 'https://oss-cn-beijing.aliyuncs.com',
101
- AccessKeyId: 'xxx',
102
- AccessKeySecret: 'xx',
103
- bucket: 'neo-widgets' // 存储桶名称
104
- },
105
126
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
106
127
  */
107
128
  }
@@ -12,7 +12,8 @@ const mainAction = require('./main.js'); // 入口文件
12
12
  const getCmpTypeByDir = require('../utils/cmpUtils/getCmpTypeByDir.js');
13
13
  const NeoService = require('../neo/neoService.js');
14
14
  const curConfig = require('../config/index'); // 获取当前项目根目录下的配置文件
15
-
15
+ const hasNeoProject = require('../utils/hasNeoProject.js');
16
+ const consoleTag = require('../utils/neoParams').consoleTag;
16
17
  // neo 的 package 文件
17
18
  const neoPackage = require('../../package.json');
18
19
 
@@ -30,7 +31,7 @@ console.log(chalk.green(`当前版本:v${neoPackage.version}.\n`));
30
31
  yargs
31
32
  .command(
32
33
  'init [options]',
33
- '创建一个自定义组件',
34
+ '根据模板创建一个自定义组件',
34
35
  (yargs) => {
35
36
  yargs
36
37
  .reset()
@@ -55,6 +56,12 @@ yargs
55
56
  },
56
57
  (argv) => {
57
58
  if (argv.type && argv.name) {
59
+ if (hasNeoProject()) {
60
+ console.error(
61
+ `${consoleTag}创建自定义组件失败,当前目录(${process.cwd()})已经是一个自定义组件项目,请勿重复创建。`
62
+ );
63
+ process.exit(1);
64
+ }
58
65
  if (argv.mode === 'github') {
59
66
  neoInit(argv.type, argv.name);
60
67
  } else {
@@ -143,7 +150,7 @@ yargs
143
150
  )
144
151
  .command(
145
152
  'config init',
146
- '初始化 neo 配置文件',
153
+ '创建 neo.config.js 配置文件',
147
154
  (yargs) => {
148
155
  yargs
149
156
  .reset()
@@ -282,6 +289,55 @@ yargs
282
289
  }
283
290
  }
284
291
  )
292
+ .command(
293
+ 'deleteCmp [options]',
294
+ '删除线上自定义组件',
295
+ (yargs) => {
296
+ yargs
297
+ .reset()
298
+ .usage(titleTip('Usage') + ': $0 deleteCmp [options]')
299
+ .option('name', {
300
+ alias: 'n',
301
+ describe: '自定义组件名称'
302
+ })
303
+ .alias('h', 'help');
304
+ },
305
+ async (argv) => {
306
+ if (argv.name) {
307
+ mainAction.deleteCmp(argv.name);
308
+ } else {
309
+ // 创建 neoService 实例
310
+ const neoService = new NeoService(curConfig.neoConfig);
311
+ const spinner = ora('正在获取线上自定义组件列表...').start();
312
+ const cmpList = await neoService.getCustomCmpList();
313
+ if (cmpList.length === 0) {
314
+ console.error('当前租户暂无任何自定义组件。');
315
+ process.exit(1);
316
+ }
317
+ spinner.stop('线上自定义组件列表获取成功。');
318
+ const cmpTypeChoices = cmpList.map((cmpItem) => ({
319
+ name: `${cmpItem.label}(${cmpItem.cmpType})`,
320
+ value: cmpItem.cmpType
321
+ }));
322
+ const questions = [
323
+ {
324
+ name: 'cmpType',
325
+ type: 'list',
326
+ message: '请选择要删除的自定义组件:',
327
+ choices: cmpTypeChoices
328
+ }
329
+ ];
330
+ inquirer.prompt(questions).then((ans) => {
331
+ if (!ans.cmpType) {
332
+ console.error('自定义组件名称不能为空。');
333
+ process.exit(1);
334
+ } else {
335
+ mainAction.deleteCmp(ans.cmpType, neoService);
336
+ }
337
+ });
338
+ }
339
+ }
340
+ )
285
341
  .command(
286
342
  'preview [options]',
287
343
  '预览指定自定义组件(仅预览组件本身内容)',
@@ -328,7 +384,7 @@ yargs
328
384
  )
329
385
  .command(
330
386
  'dev',
331
- '开启组件预览模式',
387
+ '开启本地调试模式',
332
388
  (yargs) => {
333
389
  yargs
334
390
  .reset()
@@ -367,7 +423,7 @@ yargs
367
423
  )
368
424
  .command(
369
425
  'build2lib',
370
- '构建lib库',
426
+ '构建 UMD 模块',
371
427
  (yargs) => {
372
428
  yargs
373
429
  .reset()
@@ -424,7 +480,7 @@ yargs
424
480
  )
425
481
  .command(
426
482
  'pushCmp [options]',
427
- '发布组件到 NeoCRM 平台',
483
+ '构建并发布自定义组件到 NeoCRM 平台',
428
484
  (yargs) => {
429
485
  yargs
430
486
  .reset()
@@ -468,7 +524,7 @@ yargs
468
524
  )
469
525
  .command(
470
526
  'build2esm',
471
- '构建esm模块',
527
+ '构建 ESM 模块',
472
528
  (yargs) => {
473
529
  yargs
474
530
  .reset()
@@ -502,6 +558,29 @@ yargs
502
558
  inspect(argv.type);
503
559
  }
504
560
  )
561
+ .command(
562
+ 'open [options]',
563
+ '使用 Cursor 或 VSCode 打开项目',
564
+ (yargs) => {
565
+ yargs
566
+ .reset()
567
+ .usage(titleTip('Usage') + ': $0 open [options]')
568
+ .option('editor', {
569
+ alias: 'e',
570
+ describe: '编辑器类型(cursor/vscode/auto),默认为 auto(自动检测)',
571
+ default: 'auto',
572
+ choices: ['cursor', 'vscode', 'code', 'auto']
573
+ })
574
+ .option('path', {
575
+ alias: 'p',
576
+ describe: '要打开的项目路径,默认为当前目录'
577
+ })
578
+ .alias('h', 'help');
579
+ },
580
+ (argv) => {
581
+ mainAction.openEditor(argv.editor, argv.path);
582
+ }
583
+ )
505
584
  .alias('h', 'help')
506
585
  .alias('v', 'version')
507
586
  .help()
@@ -17,6 +17,8 @@ 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');
21
+ const openNeoProject = require('../utils/projectUtils/openNeoProject');
20
22
 
21
23
  const getValue = (originValue, defaultValue) => {
22
24
  return originValue !== undefined ? originValue : defaultValue;
@@ -263,5 +265,11 @@ module.exports = {
263
265
  pullCmp: (cmpType, neoService) => {
264
266
  pullCmp(cmpType, curConfig.neoConfig, neoService);
265
267
  },
266
- build2esm: (fileName) => akfun.build2esm(fileName, curConfig, consoleTag) // 构建esm输出模块
268
+ // NeoCRM 平台删除组件
269
+ deleteCmp: (cmpType, neoService) => {
270
+ deleteCmp(cmpType, curConfig.neoConfig, neoService);
271
+ },
272
+ build2esm: (fileName) => akfun.build2esm(fileName, curConfig, consoleTag), // 构建esm输出模块
273
+ // 打开编辑器(Cursor 或 VSCode)
274
+ openEditor: openNeoProject
267
275
  };
@@ -528,6 +528,38 @@ class NeoService {
528
528
  return this.cmpList || [];
529
529
  }
530
530
 
531
+ /**
532
+ * 删除自定义组件
533
+ * @param {*} cmpType 自定义组件类型
534
+ * @returns {Promise<object>} 删除结果
535
+ */
536
+ async deleteCmp(cmpType) {
537
+ // 确保 token 有效
538
+ const token = await this.ensureValidToken();
539
+
540
+ if (!cmpType) {
541
+ throw new Error('自定义组件名称不能为空。');
542
+ }
543
+
544
+ let fullDeleteAPI = this.buildFullUrl(NeoCrmAPI.delete);
545
+ fullDeleteAPI += `/${cmpType}`;
546
+
547
+ const response = await axios.delete(fullDeleteAPI, {
548
+ headers: {
549
+ Authorization: `Bearer ${token}`,
550
+ 'xsy-inner-source': 'bff',
551
+ 'Content-Type': 'application/json'
552
+ }
553
+ });
554
+ const { code, message } = response.data || {};
555
+
556
+ if (code && code !== 200) {
557
+ throw new Error(`删除自定义组件失败: ${message || '未知错误'}`);
558
+ }
559
+
560
+ return response.data;
561
+ }
562
+
531
563
  // 获取指定框架的自定义组件列表
532
564
  getCmpListByFramework(framework) {
533
565
  if (!framework) {
@@ -78,8 +78,8 @@ module.exports = {
78
78
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
79
79
  // 外链调试(在线上页面设计器端预览自定义组件)
80
80
  index: [
81
- './src/components/info-card/register.ts',
82
- './src/components/info-card/model.ts',
81
+ './src/components/xxCmp/register.ts',
82
+ './src/components/xxCmp/model.ts',
83
83
  ],
84
84
  },
85
85
  NODE_ENV: 'development',
@@ -119,8 +119,8 @@ module.exports = {
119
119
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
120
120
  NODE_ENV: 'production',
121
121
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
122
- InfoCardModel: './src/components/entity-form/model.ts',
123
- infoCard: './src/components/entity-form/register.ts'
122
+ InfoCardModel: './src/components/xxCmp/model.ts',
123
+ infoCard: './src/components/xxCmp/register.ts'
124
124
  },
125
125
  cssExtract: false, // 不额外提取css文件
126
126
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
51
+ "neo-cmp-cli": "^1.6.2",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5"
@@ -80,8 +80,8 @@ module.exports = {
80
80
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
81
81
  // 外链调试(在线上页面设计器端预览自定义组件)
82
82
  index: [
83
- './src/components/info-card/register.ts',
84
- './src/components/info-card/model.ts',
83
+ './src/components/xxCmp/register.ts',
84
+ './src/components/xxCmp/model.ts',
85
85
  ],
86
86
  },
87
87
  NODE_ENV: 'development',
@@ -121,8 +121,8 @@ module.exports = {
121
121
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
122
122
  NODE_ENV: 'production',
123
123
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
124
- InfoCardModel: './src/components/entity-form/model.ts',
125
- infoCard: './src/components/entity-form/register.ts'
124
+ InfoCardModel: './src/components/xxCmp/model.ts',
125
+ infoCard: './src/components/xxCmp/register.ts'
126
126
  },
127
127
  cssExtract: false, // 不额外提取css文件
128
128
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
51
+ "neo-cmp-cli": "^1.6.2",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5",
@@ -119,8 +119,8 @@ module.exports = {
119
119
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
120
120
  NODE_ENV: 'production',
121
121
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
122
- InfoCardModel: './src/components/entity-form/model.ts',
123
- infoCard: './src/components/entity-form/register.ts'
122
+ InfoCardModel: './src/components/xxCmp/model.ts',
123
+ infoCard: './src/components/xxCmp/register.ts'
124
124
  },
125
125
  cssExtract: false, // 不额外提取css文件
126
126
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
49
+ "neo-cmp-cli": "^1.6.2",
50
50
  "husky": "^4.2.5",
51
51
  "lint-staged": "^10.2.9",
52
52
  "prettier": "^2.0.5"
@@ -86,8 +86,8 @@ module.exports = {
86
86
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
87
87
  // 外链调试(在线上页面设计器端预览自定义组件)
88
88
  index: [
89
- './src/components/entity-form/register.ts',
90
- './src/components/entity-form/model.ts',
89
+ './src/components/xxCmp/register.ts',
90
+ './src/components/xxCmp/model.ts',
91
91
  ],
92
92
  },
93
93
  NODE_ENV: 'development',
@@ -127,8 +127,8 @@ module.exports = {
127
127
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
128
128
  NODE_ENV: 'production',
129
129
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
130
- InfoCardModel: './src/components/entity-form/model.ts',
131
- infoCard: './src/components/entity-form/register.ts'
130
+ InfoCardModel: './src/components/xxCmp/model.ts',
131
+ infoCard: './src/components/xxCmp/register.ts'
132
132
  },
133
133
  cssExtract: false, // 不额外提取css文件
134
134
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -52,7 +52,7 @@
52
52
  "@types/react": "^16.9.11",
53
53
  "@types/react-dom": "^16.9.15",
54
54
  "@types/axios": "^0.14.0",
55
- "neo-cmp-cli": "^1.5.0-beta.5",
55
+ "neo-cmp-cli": "^1.6.2",
56
56
  "husky": "^4.2.5",
57
57
  "lint-staged": "^10.2.9",
58
58
  "prettier": "^2.0.5"
@@ -78,8 +78,8 @@ module.exports = {
78
78
  entry: { // entry 会根据 src/components 目录下的文件自动生成
79
79
  // 外链调试(在线上页面设计器端预览自定义组件)
80
80
  index: [
81
- './src/components/info-card/register.js',
82
- './src/components/info-card/model.js',
81
+ './src/components/xxCmp/register.js',
82
+ './src/components/xxCmp/model.js',
83
83
  ],
84
84
  },
85
85
  NODE_ENV: 'development',
@@ -118,8 +118,8 @@ module.exports = {
118
118
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
119
119
  NODE_ENV: 'production',
120
120
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
121
- InfoCardModel: './src/components/entity-form/model.ts',
122
- infoCard: './src/components/entity-form/register.ts'
121
+ InfoCardModel: './src/components/xxCmp/model.ts',
122
+ infoCard: './src/components/xxCmp/register.ts'
123
123
  },
124
124
  cssExtract: false, // 不额外提取css文件
125
125
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
48
+ "neo-cmp-cli": "^1.6.2",
49
49
  "husky": "^4.2.5",
50
50
  "lint-staged": "^10.2.9",
51
51
  "prettier": "^2.0.5"
@@ -78,8 +78,8 @@ module.exports = {
78
78
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
79
79
  // 外链调试(在线上页面设计器端预览自定义组件)
80
80
  index: [
81
- './src/components/list-widget/register.ts',
82
- './src/components/list-widget/model.ts',
81
+ './src/components/xxCmp/register.ts',
82
+ './src/components/xxCmp/model.ts',
83
83
  ],
84
84
  },
85
85
  NODE_ENV: 'development',
@@ -119,8 +119,8 @@ module.exports = {
119
119
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
120
120
  NODE_ENV: 'production',
121
121
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
122
- InfoCardModel: './src/components/entity-form/model.ts',
123
- infoCard: './src/components/entity-form/register.ts'
122
+ InfoCardModel: './src/components/xxCmp/model.ts',
123
+ infoCard: './src/components/xxCmp/register.ts'
124
124
  },
125
125
  cssExtract: false, // 不额外提取css文件
126
126
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
50
+ "neo-cmp-cli": "^1.6.2",
51
51
  "husky": "^4.2.5",
52
52
  "lint-staged": "^10.2.9",
53
53
  "prettier": "^2.0.5"
@@ -84,8 +84,8 @@ module.exports = {
84
84
  entry: { // entry 未配置时,cli 会根据 src/components 目录下的文件自动生成对应的 entry
85
85
  // 外链调试(在线上页面设计器端预览自定义组件)
86
86
  index: [
87
- './src/components/vue-info-card/register.js',
88
- './src/components/vue-info-card/model.js',
87
+ './src/components/xxCmp/register.js',
88
+ './src/components/xxCmp/model.js',
89
89
  ],
90
90
  },
91
91
  NODE_ENV: 'development',
@@ -127,8 +127,8 @@ module.exports = {
127
127
  【特别说明】以下配置项都自带默认值,非必填。如需自定义请自行配置。
128
128
  NODE_ENV: 'production',
129
129
  entry: { // 根据 src/components 目录下的文件自动生成 entry 相关配置
130
- InfoCardModel: './src/components/entity-form/model.ts',
131
- infoCard: './src/components/entity-form/register.ts'
130
+ InfoCardModel: './src/components/xxCmp/model.ts',
131
+ infoCard: './src/components/xxCmp/register.ts'
132
132
  },
133
133
  cssExtract: false, // 不额外提取css文件
134
134
  assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
@@ -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.6.1",
48
+ "neo-cmp-cli": "^1.6.2",
49
49
  "husky": "^4.2.5",
50
50
  "lint-staged": "^10.2.9",
51
51
  "prettier": "^2.0.5",
@@ -69,6 +69,9 @@ module.exports = function (projectPath, options = {}) {
69
69
  console.log('='.repeat(60));
70
70
  console.log('\n📝 说明:组件开发工具(neo-cmp-cli)无法直接改变当前命令窗口工作目录,');
71
71
  console.log(` 您需要手动执行上述命令才能进入刚创建的自定义组件项目(${projectName})。\n`);
72
+
73
+ // 自动打开 IDE编辑器
74
+ openNeoProject('auto', projectName);
72
75
  }
73
76
 
74
77
  return success;
@@ -8,6 +8,105 @@ const { consoleTag } = require('../neoParams'); // 输出标记
8
8
  const hasCmpTypeByDir = require('./hasCmpTypeByDir');
9
9
  const hasNeoProject = require('../projectUtils/hasNeoProject');
10
10
 
11
+ /**
12
+ * 检查对象键名是否需要加引号
13
+ * @param {string} key 键名
14
+ * @returns {boolean} 是否需要加引号
15
+ */
16
+ function needsQuotes(key) {
17
+ // 如果键名包含特殊字符(如 @、-、空格等)或不是有效的 JavaScript 标识符,需要加引号
18
+ // 有效的 JavaScript 标识符:以字母、$、_ 开头,后续可以是字母、数字、$、_
19
+ const validIdentifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
20
+ return !validIdentifierRegex.test(key);
21
+ }
22
+
23
+ /**
24
+ * 格式化对象键名,如果需要则加引号
25
+ * @param {string} key 键名
26
+ * @returns {string} 格式化后的键名
27
+ */
28
+ function formatKey(key) {
29
+ if (needsQuotes(key)) {
30
+ return JSON.stringify(key);
31
+ }
32
+ return key;
33
+ }
34
+
35
+ /**
36
+ * 将配置对象格式化为 JavaScript 代码字符串
37
+ * @param {*} obj 配置对象
38
+ * @param {number} indent 缩进级别
39
+ * @param {string} fileDir 配置文件所在目录路径,用于将绝对路径转换回相对路径(resolve() 基于 __dirname)
40
+ * @returns {string} 格式化后的字符串
41
+ */
42
+ function formatConfigObject(obj, indent = 0, fileDir = '') {
43
+ const indentStr = ' '.repeat(indent);
44
+ const nextIndentStr = ' '.repeat(indent + 1);
45
+
46
+ if (obj === null) {
47
+ return 'null';
48
+ }
49
+
50
+ if (obj === undefined) {
51
+ return 'undefined';
52
+ }
53
+
54
+ if (typeof obj === 'string') {
55
+ // 尝试将绝对路径转换回 resolve() 调用
56
+ if (fileDir && path.isAbsolute(obj)) {
57
+ try {
58
+ const relativePath = path.relative(fileDir, obj).replace(/\\/g, '/');
59
+ // 如果路径看起来像是通过 resolve() 生成的,转换回 resolve() 调用
60
+ // 排除包含 .. 的路径(这些可能是外部路径)
61
+ if (relativePath && !relativePath.startsWith('..') && relativePath !== '') {
62
+ // 确保路径使用正斜杠,并添加 ./ 前缀(如果需要)
63
+ const normalizedPath = relativePath.startsWith('.') ? relativePath : './' + relativePath;
64
+ return `resolve(${JSON.stringify(normalizedPath)})`;
65
+ }
66
+ } catch (e) {
67
+ // 如果路径转换失败,继续使用原始字符串
68
+ }
69
+ }
70
+ // 检查是否包含 resolve() 或 auth. 调用(从原始文件中提取的)
71
+ if (obj.includes('resolve(') || obj.includes('auth.')) {
72
+ return obj;
73
+ }
74
+ // 转义字符串中的特殊字符
75
+ return JSON.stringify(obj);
76
+ }
77
+
78
+ if (typeof obj === 'number' || typeof obj === 'boolean') {
79
+ return String(obj);
80
+ }
81
+
82
+ if (Array.isArray(obj)) {
83
+ if (obj.length === 0) {
84
+ return '[]';
85
+ }
86
+ const items = obj.map((item) => {
87
+ const formatted = formatConfigObject(item, indent + 1, fileDir);
88
+ return nextIndentStr + formatted;
89
+ });
90
+ return '[\n' + items.join(',\n') + '\n' + indentStr + ']';
91
+ }
92
+
93
+ if (typeof obj === 'object') {
94
+ const keys = Object.keys(obj);
95
+ if (keys.length === 0) {
96
+ return '{}';
97
+ }
98
+ const items = keys.map((key) => {
99
+ const value = obj[key];
100
+ const formattedValue = formatConfigObject(value, indent + 1, fileDir);
101
+ return nextIndentStr + formatKey(key) + ': ' + formattedValue;
102
+ });
103
+ return '{\n' + items.join(',\n') + '\n' + indentStr + '}';
104
+ }
105
+
106
+ // 对于函数或其他类型,尝试转换为字符串
107
+ return String(obj);
108
+ }
109
+
11
110
  /**
12
111
  * 从 zip 包中创建自定义组件
13
112
  * @param {*} cmpZipUrl 自定义组件源码文件地址(zip包地址)
@@ -168,7 +267,7 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
168
267
 
169
268
  const newPackageJsonDeps = _.omit(
170
269
  sourcePackageJson.dependencies,
171
- targetPackageJson.dependencies
270
+ Object.keys(targetPackageJson.dependencies)
172
271
  );
173
272
  const newDeps = Object.keys(newPackageJsonDeps);
174
273
  if (newDeps.length > 0) {
@@ -185,6 +284,84 @@ module.exports = async function (cmpZipUrl, cmpName, componentBaseDir = './src/c
185
284
  // 注意:_.merge 会修改第一个参数,所以先克隆目标对象
186
285
  const mergedPackageJson = _.merge({}, sourcePackageJson, targetPackageJson);
187
286
  await fs.writeJson(targetPath, mergedPackageJson, { spaces: 2 });
287
+ } else if (fileName === 'neo.config.js') {
288
+ // 处理 neo.config.js 文件(当目标文件已存在时)
289
+ try {
290
+ // 清除 require 缓存,确保读取最新内容
291
+ const sourceResolvedPath = require.resolve(sourcePath);
292
+ const targetResolvedPath = require.resolve(targetPath);
293
+ delete require.cache[sourceResolvedPath];
294
+ delete require.cache[targetResolvedPath];
295
+
296
+ // 读取源文件和目标文件的原始内容
297
+ const sourceContent = await fs.readFile(sourcePath, 'utf8');
298
+ const targetContent = await fs.readFile(targetPath, 'utf8');
299
+
300
+ // 读取源文件和目标文件的配置对象(函数调用会被执行)
301
+ const sourceConfig = require(sourcePath);
302
+ const targetConfig = require(targetPath);
303
+
304
+ // 合并配置:源配置合并到目标配置(目标配置优先级更高)
305
+ const mergedConfig = _.merge({}, sourceConfig, targetConfig);
306
+
307
+ // 提取目标文件的头部(module.exports 之前的内容)
308
+ const moduleExportMatch = targetContent.match(
309
+ /^([\s\S]*?)(module\.exports\s*=\s*\{[\s\S]*\};?\s*)$/
310
+ );
311
+ let fileHeader = '';
312
+ if (moduleExportMatch) {
313
+ fileHeader = moduleExportMatch[1];
314
+ } else {
315
+ // 如果没有找到 module.exports,尝试提取到最后一个大括号之前的内容
316
+ const lastBraceIndex = targetContent.lastIndexOf('module.exports');
317
+ if (lastBraceIndex > 0) {
318
+ fileHeader = targetContent.substring(0, lastBraceIndex);
319
+ } else {
320
+ // 如果都没有找到,使用默认头部
321
+ fileHeader =
322
+ "'use strict';\nconst path = require('path');\n\n// 统一路径解析\nfunction resolve(dir) {\n return path.resolve(__dirname, dir);\n}\n\n";
323
+ }
324
+ }
325
+
326
+ // 获取目标文件的目录,用于路径转换(resolve() 基于 __dirname)
327
+ const targetFileDir = path.dirname(targetPath);
328
+
329
+ // 将合并后的配置对象转换为格式化的字符串
330
+ const configString = formatConfigObject(mergedConfig, 0, targetFileDir);
331
+
332
+ // 组合完整的文件内容
333
+ const mergedContent = fileHeader + 'module.exports = ' + configString + ';\n';
334
+
335
+ // 写回文件
336
+ await fs.writeFile(targetPath, mergedContent, 'utf8');
337
+ } catch (configError) {
338
+ spinner.warn(
339
+ `${consoleTag}合并 neo.config.js 配置文件时出现警告:${
340
+ configError.message || configError
341
+ }`
342
+ );
343
+ }
344
+ } else if (fileName === 'tsconfig.json') {
345
+ // 处理 tsconfig.json 文件(当目标文件已存在时)
346
+ try {
347
+ const sourceTsconfigJson = await fs.readJson(sourcePath);
348
+ // 先判断 targetPath 是否存在,如果存在则合并,否则直接写入
349
+ if (await fs.pathExists(targetPath)) {
350
+ const targetTsconfigJson = await fs.readJson(targetPath);
351
+ // 合并配置:源配置合并到目标配置(目标配置优先级更高)
352
+ const mergedTsconfigJson = _.merge({}, sourceTsconfigJson, targetTsconfigJson);
353
+ // 使用 writeJson 写入,确保 JSON 格式正确(所有键名都会被正确序列化)
354
+ await fs.writeJson(targetPath, mergedTsconfigJson, { spaces: 2 });
355
+ } else {
356
+ await fs.writeJson(targetPath, sourceTsconfigJson, { spaces: 2 });
357
+ }
358
+ } catch (tsconfigError) {
359
+ spinner.warn(
360
+ `${consoleTag}合并 tsconfig.json 配置文件时出现警告:${
361
+ tsconfigError.message || tsconfigError
362
+ }`
363
+ );
364
+ }
188
365
  }
189
366
  }
190
367
  }
@@ -0,0 +1,62 @@
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.deleteCmp(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;
@@ -0,0 +1,83 @@
1
+ const path = require('path');
2
+ const { exec } = require('child_process');
3
+ const ora = require('ora');
4
+
5
+ /**
6
+ * 打开自定义组件项目
7
+ */
8
+ module.exports = function (editorType, targetPath) {
9
+ const targetDir = targetPath ? path.resolve(targetPath) : process.cwd();
10
+ const isMac = process.platform === 'darwin';
11
+ const isWindows = process.platform === 'win32';
12
+ const isLinux = process.platform === 'linux';
13
+
14
+ // 构建命令列表(按优先级排序)
15
+ let commands = [];
16
+ let editorName = '编辑器';
17
+
18
+ // 根据编辑器类型和平台构建命令列表
19
+ if (editorType === 'cursor') {
20
+ editorName = 'Cursor';
21
+ if (isMac) {
22
+ commands = [`cursor "${targetDir}"`, `open -a "Cursor" "${targetDir}"`];
23
+ } else if (isWindows || isLinux) {
24
+ commands = [`cursor "${targetDir}"`];
25
+ }
26
+ } else if (editorType === 'vscode' || editorType === 'code') {
27
+ editorName = 'Visual Studio Code';
28
+ if (isMac) {
29
+ commands = [`code "${targetDir}"`, `open -a "Visual Studio Code" "${targetDir}"`];
30
+ } else if (isWindows || isLinux) {
31
+ commands = [`code "${targetDir}"`];
32
+ }
33
+ } else {
34
+ // 自动检测:优先尝试 Cursor,如果失败则尝试 VSCode
35
+ if (isMac) {
36
+ commands = [
37
+ `cursor "${targetDir}"`,
38
+ `code "${targetDir}"`,
39
+ `open -a "Cursor" "${targetDir}"`,
40
+ `open -a "Visual Studio Code" "${targetDir}"`
41
+ ];
42
+ } else if (isWindows || isLinux) {
43
+ commands = [`cursor "${targetDir}"`, `code "${targetDir}"`];
44
+ }
45
+ }
46
+
47
+ if (commands.length === 0) {
48
+ console.error(`${consoleTag}不支持的操作系统: ${process.platform}`);
49
+ process.exit(1);
50
+ }
51
+
52
+ const spinner = ora(`${consoleTag}正在尝试使用 ${editorName} 打开项目: ${targetDir}`).start();
53
+
54
+ // 尝试执行命令列表中的第一个可用命令
55
+ const tryCommand = (index) => {
56
+ if (index >= commands.length) {
57
+ spinner.fail(
58
+ `${consoleTag}无法打开编辑器,已尝试所有可用方式。\n 请确保已安装 Cursor 或 Visual Studio Code 并将其添加到系统 PATH 中。`
59
+ );
60
+ if (isMac) {
61
+ console.error(
62
+ `${consoleTag}macOS 提示: 如果已安装编辑器,请确保在编辑器中通过 Command+Shift+P 执行 "Shell Command: Install 'code' command in PATH" 或类似命令。`
63
+ );
64
+ }
65
+ process.exit(1);
66
+ }
67
+
68
+ const command = commands[index];
69
+ // 使用 exec 配合 shell 执行命令,这样可以更好地处理路径中的空格和特殊字符
70
+ exec(command, { shell: true }, (error, stdout, stderr) => {
71
+ if (error) {
72
+ // 如果当前命令失败,尝试下一个
73
+ tryCommand(index + 1);
74
+ } else {
75
+ // 命令执行成功,不需要继续尝试
76
+ // 编辑器命令通常会立即返回,即使编辑器窗口已经打开
77
+ }
78
+ });
79
+ };
80
+
81
+ tryCommand(0);
82
+ spinner.stop();
83
+ };
package/test/demo3.js ADDED
@@ -0,0 +1,3 @@
1
+ const openEditor = require('../src/module/main').openEditor;
2
+
3
+ openEditor('cursor', '/Users/liudan/neo-custom-widget/neo-cmp-cli/src/template/antd-custom-cmp-template');