jjb-cmd 2.5.5 → 2.5.6-beta.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.
@@ -0,0 +1,203 @@
1
+ # 云组件开发指南
2
+
3
+ ## 🛠 技术栈
4
+
5
+ | 类别 | 技术选型 |
6
+ |------|----------|
7
+ | **语言** | JavaScript (ES6+) |
8
+ | **框架** | React 18 |
9
+ | **样式** | Less |
10
+ | **UI组件库** | Ant Design |
11
+ | **编译工具** | Babel 7 |
12
+ | **构建工具** | Webpack 5 |
13
+
14
+ ## 📁 目录结构
15
+
16
+ ```
17
+ .
18
+ ├── jjb.script/ # Webpack开发与构建配置(不建议修改)
19
+ ├── public/ # 本地开发调试HTML
20
+ ├── src/ # 源码目录
21
+ ├── jjb.config.json # 项目基础配置
22
+ ├── package.json # 项目依赖配置
23
+ ├── thumbnail.png # 组件缩略图
24
+ └── webstorm.config.js # WebStorm路径别名配置(其他编辑器可忽略)
25
+ ```
26
+
27
+ ## 🚀 开发说明
28
+
29
+ ### 基本信息配置
30
+ - **组件名称**:修改 `package.json` 中的 `name` 属性(必须唯一)
31
+ - **检索标题**:修改 `package.json` 中的 `description` 属性
32
+ - **描述说明**:修改 `package.json` 中的 `remark` 属性
33
+ - **版本号**:修改 `package.json` 中的 `version` 属性
34
+ - **缩略图**:在根目录添加 `thumbnail.png` 文件
35
+
36
+ ### 入口文件
37
+ - `__dev__.js` - 开发环境入口
38
+ - `__prod__.js` - 生产环境构建入口
39
+
40
+ ## ⚙️ jjb.config.json 配置详解
41
+
42
+ ### 1. environment
43
+ 配置云组件的接口服务环境,与 `package.json` 中 scripts 的 `NODE_ENV` 匹配。云组件运行时,接口服务域名会与应用保持一致。
44
+
45
+ ### 2. dependencies
46
+ 配置云组件的依赖清单。如果应用中没有提供匹配的依赖项,运行时将报错。
47
+
48
+ 云组件默认从 `window.__coreLib` 获取依赖项,如果应用使用其他变量,需要显式指定:
49
+
50
+ ```javascript
51
+ // 应用入口文件中声明依赖
52
+ // 默认依赖变量
53
+ window.__coreLib = {
54
+ 'react': require('react'),
55
+ 'react-dom': require('react-dom')
56
+ };
57
+
58
+ // 其他依赖变量
59
+ window.__coreTestLib = {
60
+ 'react': require('react'),
61
+ 'react-dom': require('react-dom')
62
+ };
63
+ ```
64
+
65
+ ```jsx
66
+ // 使用组件时指定依赖变量
67
+ function App() {
68
+ return (
69
+ <>
70
+ {/* 使用默认依赖变量 */}
71
+ <CloudComponent />
72
+
73
+ {/* 使用其他依赖变量 */}
74
+ <CloudComponent lib="__coreTestLib" />
75
+ </>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### 3. server
81
+
82
+ 配置云组件本地开发服务。
83
+
84
+ ### 4. framework
85
+
86
+ 配置UI库主题,可根据业务需求定制颜色和其他样式。
87
+
88
+ ## 💻 本地开发
89
+
90
+ ### 安装依赖
91
+
92
+ ```bash
93
+ npm install
94
+ ```
95
+
96
+ ### 启动开发服务
97
+
98
+ ```bash
99
+ npm run serve:<env>
100
+ ```
101
+
102
+ ## 本地联调
103
+
104
+ ### 1. 构建云组件
105
+ ```bash
106
+ npm run build:<env>
107
+ ```
108
+ ### 2. 启动代理服务
109
+ ```bash
110
+ node jjb.script/proxy.js
111
+ ```
112
+ ### 3. 在应用中使用
113
+ 访问 http://127.0.0.1:8080/index.js 可查看编译后的代码,将此链接放入应用的云组件中:
114
+ ```jsx
115
+ function App() {
116
+ return (
117
+ <CloudComponent from="http://127.0.0.1:8080/index.js" />
118
+ );
119
+ }
120
+ ```
121
+ 注意:云组件更新后需要重新执行第一步编译。
122
+
123
+ ## 📤 发布云组件
124
+
125
+ ### 自动发布(推荐)
126
+ 1. 安装命令行工具
127
+ ```bash
128
+ npm install jjb-cmd -g
129
+ ```
130
+ 2. 账号认证
131
+ ```bash
132
+ jjb-cmd auth <用户名> <密码>
133
+ ```
134
+ 账号密码请联系管理员获取
135
+ 3. 发布组件
136
+ ```bash
137
+ jjb-cmd publish <版本号 | latest>
138
+ ```
139
+ 注意:版本号不要加 v 前缀!
140
+
141
+ ### 👀 查看已发布组件
142
+ * 云组件平台:https://jcloud.cqjjb.cn/container/home?pageNum=1&pageSize=10
143
+ * 云组件检索表:https://cdn.cqjjb.cn
144
+
145
+
146
+ ### 手动发布
147
+
148
+ 1. 构建组件
149
+ ```bash
150
+ npm run build:<env>
151
+ ```
152
+ 2. 部署文件 将 dist/index.js 上传到文件服务器,并确保允许跨域访问。
153
+
154
+ ## 📦 在应用中使用
155
+
156
+ ### 安装依赖
157
+
158
+ ```bash
159
+ # 使用 npm
160
+ npm install @cqsjjb/jjb-cloud-component
161
+
162
+ # 使用 yarn
163
+ yarn add @cqsjjb/jjb-cloud-component
164
+ ```
165
+
166
+ ### 基本用法
167
+
168
+ ```jsx
169
+ import React from 'react';
170
+ import { CloudComponent } from '@cqsjjb/jjb-cloud-component';
171
+
172
+ function App() {
173
+ return (
174
+ <CloudComponent
175
+ // 依赖变量(默认使用 window.__coreLib)
176
+ lib="__coreLib"
177
+
178
+ // 云组件地址(从云组件平台获取)
179
+ from="https://your-component-url/index.js"
180
+
181
+ // 缓存策略
182
+ cache={true}
183
+
184
+ // 组件唯一标识
185
+ componentKey="your-component-unique-key"
186
+
187
+ // 组件属性
188
+ componentProps={{}}
189
+
190
+ // 事件回调
191
+ onLoadStart={() => console.log('开始加载')}
192
+ onLoadEnd={() => console.log('加载完成')}
193
+ onMounted={() => console.log('挂载完成')}
194
+ onUpdated={() => console.log('数据更新')}
195
+ onDestroy={() => console.log('组件销毁')}
196
+ />
197
+ );
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ 💡 提示:更多详细配置请参考组件类型定义文件(d.ts)
package/bin/command.js CHANGED
@@ -3,16 +3,18 @@
3
3
  const commander = require('commander');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
+ const { checkCmdVersion } = require('../src/utils');
6
7
 
7
- commander.command('v').description('-- 查看版本').action(() => {
8
+ commander.command('v').description('-- 查看版本').action(async () => {
8
9
  const package_json_file = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')).toString());
9
10
  const {
10
11
  version
11
12
  } = package_json_file;
12
13
  console.log(`当前版本: v${version}`);
14
+ // 版本查看命令不需要检查版本
13
15
  });
14
16
 
15
- commander.command('help').description('-- 帮助').action(() => {
17
+ commander.command('help').description('-- 帮助').action(async () => {
16
18
  console.log('jjb-cmd <command>');
17
19
  console.log('');
18
20
  console.log('使用:');
@@ -23,26 +25,43 @@ commander.command('help').description('-- 帮助').action(() => {
23
25
  console.log('jjb-cmd publish <version> 发布云组件\n\targ1 <version> 发布版本,可设置为latest');
24
26
  console.log('jjb-cmd auth 登录授权(交互式输入用户名和密码)');
25
27
  console.log('jjb-cmd push java 推送微应用到服务器');
26
- console.log('jjb-cmd ai-pull 拉取 AI 配置文件和规则');
28
+ console.log('jjb-cmd ai-pull [branch] 拉取 AI 配置文件和规则\n\targ1 <branch> 指定分支/标签,默认为 v_1.0.0');
29
+ // 帮助命令不需要检查版本
27
30
  });
28
31
 
29
32
  // 命令
30
33
  commander.command('auth').description('-- 授权').action(async () => {
34
+ const isLatest = await checkCmdVersion();
35
+ if (!isLatest) {
36
+ process.exit(1);
37
+ }
31
38
  await require('../src/auth.js')([]);
32
39
  });
33
40
 
34
41
  // 优化
35
42
  commander.command('opti').description('-- 代码优化').action(async args => {
43
+ const isLatest = await checkCmdVersion();
44
+ if (!isLatest) {
45
+ process.exit(1);
46
+ }
36
47
  await require('../src/code-optimization')();
37
48
  });
38
49
 
39
50
  // 发包
40
- commander.command('publish [args]').description('-- 发布包').action(args => {
51
+ commander.command('publish [args]').description('-- 发布包').action(async args => {
52
+ const isLatest = await checkCmdVersion();
53
+ if (!isLatest) {
54
+ process.exit(1);
55
+ }
41
56
  require('../src/publish.js')(args);
42
57
  });
43
58
 
44
59
  // publish 命令
45
- commander.command('push [args]').description('-- 发布包').action(args => {
60
+ commander.command('push [args]').description('-- 发布包').action(async args => {
61
+ const isLatest = await checkCmdVersion();
62
+ if (!isLatest) {
63
+ process.exit(1);
64
+ }
46
65
  if (args) {
47
66
  if ([ 'java' ].includes(args)) {
48
67
  require('../src/push.js')([]);
@@ -56,14 +75,41 @@ commander.command('push [args]').description('-- 发布包').action(args => {
56
75
  }
57
76
  });
58
77
 
78
+ // npm-publish 命令 - 发布npm包
79
+ commander.command('npm-publish [version]')
80
+ .description('-- 发布npm包(内部命令)')
81
+ .option('--tag <tag>', 'npm发布标签(如:beta、alpha等,默认为latest)')
82
+ .action(async (version, options) => {
83
+ const isLatest = await checkCmdVersion();
84
+ if (!isLatest) {
85
+ process.exit(1);
86
+ }
87
+ if (!version) {
88
+ console.log(`缺少必要参数,请使用:`);
89
+ console.log(` yarn npm-publish <version> 发布npm包(latest标签)`);
90
+ console.log(` yarn npm-publish <version> --tag <tag> 发布npm包(指定标签)`);
91
+ process.exit(0);
92
+ }
93
+ const tag = options.tag || null;
94
+ await require('../src/push-npm.js')(version, tag);
95
+ });
96
+
59
97
  // rm-rf 命令
60
98
  commander.command('rm-rf').description('-- 删除全部').action(async () => {
99
+ const isLatest = await checkCmdVersion();
100
+ if (!isLatest) {
101
+ process.exit(1);
102
+ }
61
103
  await require('../src/rm-rf.js')();
62
104
  });
63
105
 
64
106
  // ai-pull 命令
65
- commander.command('ai-pull').description('-- 拉取 AI 配置文件和规则').action(() => {
66
- require('../src/ai-pull.js')();
107
+ commander.command('ai-pull [branch]').description('-- 拉取 AI 配置文件和规则').action(async (branch) => {
108
+ const isLatest = await checkCmdVersion();
109
+ if (!isLatest) {
110
+ process.exit(1);
111
+ }
112
+ require('../src/ai-pull.js')(branch);
67
113
  });
68
114
 
69
115
  commander.parse(process.argv);
package/package.json CHANGED
@@ -1,40 +1,41 @@
1
- {
2
- "name": "jjb-cmd",
3
- "use": "no",
4
- "env": "prod",
5
- "httpMethod": "http",
6
- "pushMessage": "yes",
7
- "version": "2.5.5",
8
- "description": "jjb-cmd命令行工具",
9
- "main": "index.js",
10
- "scripts": {
11
- "test": "node bin/command.js help",
12
- "install:package": "npm i",
13
- "test:ai-pull": "node bin/command.js ai-pull"
14
- },
15
- "bin": {
16
- "jjb-cmd": "bin/command.js"
17
- },
18
- "author": "jiaoxiwei",
19
- "license": "MIT",
20
- "dependencies": {
21
- "@babel/core": "^7.19.3",
22
- "@babel/generator": "^7.26.0",
23
- "@babel/parser": "^7.26.1",
24
- "@babel/preset-env": "^7.23.2",
25
- "@babel/preset-react": "^7.18.6",
26
- "@babel/preset-typescript": "^7.23.2",
27
- "@babel/traverse": "^7.25.9",
28
- "@cqsjjb/react-code-optimization": "latest",
29
- "axios": "^1.1.3",
30
- "better-sqlite3": "^12.6.2",
31
- "chalk": "2.4.0",
32
- "commander": "^1.3.2",
33
- "compressing": "^1.5.1",
34
- "inquirer": "^8.2.7",
35
- "jenkins": "^1.0.2",
36
- "prettier": "^3.3.3",
37
- "request": "2.88.2",
38
- "single-line-log": "1.1.2"
39
- }
40
- }
1
+ {
2
+ "name": "jjb-cmd",
3
+ "use": "no",
4
+ "env": "prod",
5
+ "httpMethod": "http",
6
+ "pushMessage": "yes",
7
+ "version": "2.5.6-beta.2",
8
+ "description": "jjb-cmd命令行工具",
9
+ "main": "index.js",
10
+ "scripts": {
11
+ "test": "node bin/command.js help",
12
+ "install:package": "npm i",
13
+ "test:ai-pull": "node bin/command.js ai-pull",
14
+ "npm-publish": "node bin/command.js npm-publish"
15
+ },
16
+ "bin": {
17
+ "jjb-cmd": "bin/command.js"
18
+ },
19
+ "author": "jiaoxiwei",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@babel/core": "^7.19.3",
23
+ "@babel/generator": "^7.26.0",
24
+ "@babel/parser": "^7.26.1",
25
+ "@babel/preset-env": "^7.23.2",
26
+ "@babel/preset-react": "^7.18.6",
27
+ "@babel/preset-typescript": "^7.23.2",
28
+ "@babel/traverse": "^7.25.9",
29
+ "@cqsjjb/react-code-optimization": "latest",
30
+ "axios": "^1.1.3",
31
+ "better-sqlite3": "^12.6.2",
32
+ "chalk": "2.4.0",
33
+ "commander": "^1.3.2",
34
+ "compressing": "^1.5.1",
35
+ "inquirer": "^8.2.7",
36
+ "jenkins": "^1.0.2",
37
+ "prettier": "^3.3.3",
38
+ "request": "2.88.2",
39
+ "single-line-log": "1.1.2"
40
+ }
41
+ }
package/src/ai-pull.js CHANGED
@@ -17,8 +17,8 @@ const CURSOR_DB_PATH = path.join(
17
17
 
18
18
  // Git 仓库地址
19
19
  const AI_REPO_URL = 'http://192.168.1.242:10985/root/jjb-ai.git';
20
- // Git 分支/标签
21
- const AI_REPO_BRANCH = 'v_1.0.0';
20
+ // Git 分支/标签(默认值)
21
+ const DEFAULT_BRANCH = 'v_1.0.0';
22
22
 
23
23
  /**
24
24
  * 删除目录(回调方式,使用递归删除)
@@ -217,25 +217,29 @@ function copyFileWithRetry(srcPath, tarPath, retries, callback) {
217
217
  rs.pipe(ws);
218
218
  }
219
219
 
220
- module.exports = () => {
220
+ module.exports = (branch) => {
221
221
  const root_path = path.resolve('./');
222
222
  const tmpDir = os.tmpdir();
223
223
  const cloneDir = path.join(tmpDir, 'jjb-ai-temp');
224
+
225
+ // 使用传入的分支参数,如果没有则使用默认值
226
+ const targetBranch = branch || DEFAULT_BRANCH;
224
227
 
225
228
  console.log('【jjb-cmd ai-pull】:开始执行...');
229
+ console.log(`【分支】:${targetBranch}`);
226
230
 
227
231
  // 步骤1: 拉取或更新仓库代码
228
- console.log('步骤1: 正在拉取 jjb-ai 仓库代码...');
232
+ console.log(`步骤1: 正在拉取 jjb-ai 仓库代码(分支: ${targetBranch})...`);
229
233
 
230
234
  // 如果临时目录已存在,先删除
231
235
  deleteDir(cloneDir, () => {
232
236
  try {
233
237
  // 克隆仓库指定分支/标签
234
- child_process.execSync(`git clone -b ${AI_REPO_BRANCH} ${AI_REPO_URL} "${cloneDir}"`, {
238
+ child_process.execSync(`git clone -b ${targetBranch} ${AI_REPO_URL} "${cloneDir}"`, {
235
239
  stdio: 'inherit',
236
240
  cwd: tmpDir
237
241
  });
238
- console.log('✓ 仓库代码拉取成功');
242
+ console.log(`✓ 仓库代码拉取成功(分支: ${targetBranch})`);
239
243
 
240
244
  // 步骤2: 复制 .ai 文件夹到当前项目
241
245
  console.log('步骤2: 正在复制 .ai 文件夹...');
package/src/publish.js CHANGED
@@ -12,10 +12,41 @@ const {
12
12
  executeCommand,
13
13
  fileExists,
14
14
  readFile,
15
- writeFile
15
+ writeFile,
16
+ getGitBranch,
17
+ getDeviceInfo,
18
+ calculateSimilarity
16
19
  } = require('./utils');
17
20
  const { loadAuth } = require('./crypto-utils');
18
21
 
22
+ /**
23
+ * 格式化版本号
24
+ * 如果输入的版本号以v开头(包括v_),则去掉v前缀(服务器端会统一添加v前缀)
25
+ * 如果输入的版本号不以v开头,则保持原样(服务器端会添加v前缀)
26
+ * 这样可以避免服务器端重复添加前缀导致vv_1.0.0的问题
27
+ * @param {string} version - 输入的版本号
28
+ * @returns {string} 格式化后的版本号(去掉v前缀的纯版本号)
29
+ */
30
+ function formatVersion(version) {
31
+ if (!version) {
32
+ return '0';
33
+ }
34
+
35
+ let formattedVersion = version.trim();
36
+
37
+ // 如果版本号以v_开头,去掉v_前缀(服务器端会添加v前缀)
38
+ if (formattedVersion.toLowerCase().startsWith('v_')) {
39
+ formattedVersion = formattedVersion.substring(2);
40
+ }
41
+ // 如果版本号以v开头(但不是v_),去掉v前缀(服务器端会添加v前缀)
42
+ else if (formattedVersion.toLowerCase().startsWith('v')) {
43
+ formattedVersion = formattedVersion.substring(1);
44
+ }
45
+ // 如果版本号不以v开头,保持原样(服务器端会添加v前缀)
46
+
47
+ return formattedVersion;
48
+ }
49
+
19
50
  /**
20
51
  * 解析组件配置信息
21
52
  * @param {string} distContent - 构建后的文件内容
@@ -198,12 +229,67 @@ module.exports = version => {
198
229
  logError(error.message);
199
230
  process.exit(1);
200
231
  }
232
+
233
+ // 检查README相似度
234
+ logInfo('正在检查README文档...');
235
+ try {
236
+ const defaultReadmePath = path.join(__dirname, '../DEFAULT_README.md');
237
+ if (fileExists(defaultReadmePath)) {
238
+ const defaultReadme = readFile(defaultReadmePath);
239
+ const projectReadme = readFile(filePaths.readmePath);
240
+ const similarity = calculateSimilarity(defaultReadme, projectReadme);
241
+
242
+ if (similarity >= 90) {
243
+ logError(`README文档与默认文档相似度过高(${similarity.toFixed(2)}%),请修改README文档后再发布`);
244
+ logWarning('提示:README文档应该包含组件的具体说明和使用方法,不能使用默认模板');
245
+ process.exit(1);
246
+ } else {
247
+ logSuccess(`README文档检查通过(相似度:${similarity.toFixed(2)}%)`);
248
+ }
249
+ } else {
250
+ logWarning('未找到DEFAULT_README.md文件,跳过README相似度检查');
251
+ }
252
+ } catch (error) {
253
+ logWarning(`README相似度检查失败: ${error.message},将继续发布流程`);
254
+ }
201
255
 
202
256
  // 读取包信息
203
257
  logInfo('正在读取包信息...');
204
258
  const packageJsonFile = JSON.parse(readFile(path.join(__dirname, '../package.json')));
205
259
  const { version: cmdVersion } = packageJsonFile;
206
260
 
261
+ // 格式化版本号
262
+ const formattedVersion = formatVersion(version);
263
+
264
+ // 读取dist/index.js内容
265
+ let distContent = readFile(filePaths.distPath);
266
+
267
+ // 获取构建信息
268
+ const gitBranch = getGitBranch(rootPath);
269
+ const deviceInfo = getDeviceInfo();
270
+ const buildTime = new Date().toLocaleString('zh-CN', {
271
+ year: 'numeric',
272
+ month: '2-digit',
273
+ day: '2-digit',
274
+ hour: '2-digit',
275
+ minute: '2-digit',
276
+ second: '2-digit',
277
+ hour12: false
278
+ });
279
+
280
+ // 在文件最上方添加构建信息注释
281
+ const buildInfoComment = `/*
282
+ * Build Info:
283
+ * Branch: ${gitBranch}
284
+ * Device: ${deviceInfo.hostname} (MAC: ${deviceInfo.macAddress}, IP: ${deviceInfo.localIP})
285
+ * Build Time: ${buildTime}
286
+ */
287
+ `;
288
+
289
+ // 如果文件开头已经有注释,需要判断是否已有构建信息注释
290
+ // 简单处理:直接在最前面添加
291
+ distContent = buildInfoComment + distContent;
292
+
207
293
  // 准备发布数据
208
294
  logInfo('正在准备发布数据...');
209
295
  const pushData = {
@@ -211,8 +297,8 @@ module.exports = version => {
211
297
  cmd_version: cmdVersion,
212
298
  hostname: os.hostname(),
213
299
  platform: os.platform(),
214
- package_version: version || 0,
215
- package_content: readFile(filePaths.distPath),
300
+ package_version: formattedVersion || 0,
301
+ package_content: distContent,
216
302
  readme_content: readFile(filePaths.readmePath)
217
303
  };
218
304
 
@@ -0,0 +1,199 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const child_process = require('child_process');
4
+ const axios = require('axios');
5
+ const {
6
+ logInfo,
7
+ logSuccess,
8
+ logError,
9
+ logWarning,
10
+ readFile,
11
+ writeFile,
12
+ executeCommand,
13
+ fileExists
14
+ } = require('./utils');
15
+ const { getApiHost } = require('./config');
16
+ const { loadAuth } = require('./crypto-utils');
17
+
18
+ /**
19
+ * 执行npm发布流程
20
+ * @param {string} version - 版本号
21
+ * @param {string} tag - npm tag(可选,默认为latest)
22
+ */
23
+ module.exports = async (version, tag) => {
24
+ if (!version) {
25
+ logError('请提供版本号,例如: jjb-cmd push 2.5.7 或 jjb-cmd push 2.5.7 --tag beta');
26
+ process.exit(1);
27
+ }
28
+
29
+ // 如果没有指定tag,默认为latest
30
+ const npmTag = tag || 'latest';
31
+
32
+ const rootPath = path.resolve(__dirname, '..');
33
+ const packageJsonPath = path.join(rootPath, 'package.json');
34
+
35
+ // 读取当前package.json
36
+ let packageJson;
37
+ try {
38
+ const packageJsonContent = readFile(packageJsonPath);
39
+ packageJson = JSON.parse(packageJsonContent);
40
+ } catch (error) {
41
+ logError(`读取package.json失败: ${error.message}`);
42
+ process.exit(1);
43
+ }
44
+
45
+ const currentVersion = packageJson.version;
46
+ logInfo(`当前版本: ${currentVersion}`);
47
+ logInfo(`目标版本: ${version}`);
48
+
49
+ // 更新package.json中的版本号
50
+ try {
51
+ packageJson.version = version;
52
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
53
+ logSuccess(`已更新package.json版本号为: ${version}`);
54
+ } catch (error) {
55
+ logError(`更新package.json失败: ${error.message}`);
56
+ process.exit(1);
57
+ }
58
+
59
+ // 保存原始npm registry配置
60
+ let originalRegistry = 'https://registry.npmmirror.com/';
61
+ try {
62
+ const registryResult = child_process.execSync('npm config get registry', { encoding: 'utf8', cwd: rootPath });
63
+ originalRegistry = registryResult.trim() || 'https://registry.npmmirror.com/';
64
+ } catch (error) {
65
+ logWarning('无法获取当前npm registry配置,将使用默认值');
66
+ }
67
+
68
+ try {
69
+ // 1. 切换npm源到官方源
70
+ logInfo('正在切换npm源到官方源...');
71
+ executeCommand('npm config set registry https://registry.npmjs.org/', rootPath);
72
+ logSuccess('npm源已切换到官方源');
73
+
74
+ // 检查是否已登录npm(针对官方源)
75
+ logInfo('正在检查npm登录状态...');
76
+ try {
77
+ const whoamiResult = child_process.execSync('npm whoami --registry=https://registry.npmjs.org/', { encoding: 'utf8', cwd: rootPath, stdio: 'pipe' });
78
+ const npmUser = whoamiResult.trim();
79
+ if (npmUser) {
80
+ logSuccess(`已登录npm账号: ${npmUser}`);
81
+ } else {
82
+ logError('未登录npm官方源,请先执行: npm login --registry=https://registry.npmjs.org/');
83
+ throw new Error('需要先登录npm官方源才能发布');
84
+ }
85
+ } catch (error) {
86
+ if (error.message.includes('需要先登录')) {
87
+ throw error;
88
+ }
89
+ logError('未登录npm官方源,请先执行: npm login --registry=https://registry.npmjs.org/');
90
+ logError('或者执行: npm adduser --registry=https://registry.npmjs.org/');
91
+ throw new Error('需要先登录npm官方源才能发布');
92
+ }
93
+
94
+ // 2. 发布到npm
95
+ // 注意:npm publish 不需要在命令中指定版本号,版本号已经从package.json中读取
96
+ // 但需要明确指定registry,确保使用官方源
97
+ logInfo(`正在发布版本 ${version} 到npm(tag: ${npmTag})...`);
98
+ logInfo(`执行命令: npm publish --tag ${npmTag} --registry=https://registry.npmjs.org/(版本号 ${version} 已从package.json读取)`);
99
+ try {
100
+ const publishCommand = npmTag === 'latest'
101
+ ? 'npm publish --registry=https://registry.npmjs.org/'
102
+ : `npm publish --tag ${npmTag} --registry=https://registry.npmjs.org/`;
103
+ executeCommand(publishCommand, rootPath);
104
+ logSuccess(`版本 ${version} 发布成功(tag: ${npmTag})!`);
105
+ } catch (error) {
106
+ // 如果发布失败,恢复package.json版本号
107
+ packageJson.version = currentVersion;
108
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
109
+ logError(`npm发布失败: ${error.message}`);
110
+ throw error;
111
+ }
112
+
113
+ // 3. 切回镜像源
114
+ logInfo('正在切回npm镜像源...');
115
+ executeCommand('npm config set registry https://registry.npmmirror.com/', rootPath);
116
+ logSuccess('npm源已切回镜像源');
117
+
118
+ // 4. 更新数据库中的版本号
119
+ logInfo('正在更新服务器端版本号...');
120
+ try {
121
+ const authPath = path.join(rootPath, '.auth');
122
+ let username, password;
123
+
124
+ // 尝试从认证文件读取
125
+ if (fileExists(authPath)) {
126
+ try {
127
+ const authData = loadAuth(authPath);
128
+ username = authData.username;
129
+ password = authData.password;
130
+ } catch (error) {
131
+ logWarning(`读取认证信息失败: ${error.message}`);
132
+ logWarning('跳过服务器端版本号更新,请手动执行 jjb-cmd update-server-version');
133
+ logSuccess('npm发布完成!');
134
+ process.exit(0);
135
+ }
136
+ } else {
137
+ logWarning('未找到认证信息,跳过服务器端版本号更新');
138
+ logWarning('请手动执行 jjb-cmd update-server-version 来更新服务器端版本号');
139
+ logSuccess('npm发布完成!');
140
+ process.exit(0);
141
+ }
142
+
143
+ // 调用服务器接口更新版本号
144
+ const apiHost = getApiHost();
145
+ const response = await axios.post(`${apiHost}/api/cmd/update-version`, {
146
+ username,
147
+ password
148
+ }, {
149
+ timeout: 15000
150
+ });
151
+
152
+ if (response.data && response.data.status) {
153
+ const { version: updatedVersion, previousVersion } = response.data.data;
154
+ logSuccess('服务器端版本号更新成功!');
155
+ if (previousVersion) {
156
+ logInfo(` 旧版本: ${previousVersion}`);
157
+ }
158
+ logInfo(` 新版本: ${updatedVersion}`);
159
+ } else {
160
+ logWarning(`服务器端版本号更新失败: ${response.data.message || '未知错误'}`);
161
+ logWarning('请手动执行 jjb-cmd update-server-version 来更新服务器端版本号');
162
+ }
163
+ } catch (error) {
164
+ if (error && error.response) {
165
+ const status = error.response.status;
166
+ const serverMsg = (error.response.data && (error.response.data.message || error.response.data.msg)) || '';
167
+ logWarning(`服务器端版本号更新失败 (${status}): ${serverMsg || error.message}`);
168
+ } else if (error && error.request) {
169
+ logWarning('网络连接失败: 无法连接到服务器');
170
+ } else {
171
+ logWarning(`服务器端版本号更新失败: ${error.message || '未知错误'}`);
172
+ }
173
+ logWarning('请手动执行 jjb-cmd update-server-version 来更新服务器端版本号');
174
+ }
175
+
176
+ logSuccess('所有操作完成!');
177
+ process.exit(0);
178
+ } catch (error) {
179
+ // 发生错误时,尝试恢复npm源
180
+ try {
181
+ logInfo('正在恢复npm源...');
182
+ executeCommand(`npm config set registry ${originalRegistry}`, rootPath);
183
+ logInfo('npm源已恢复');
184
+ } catch (restoreError) {
185
+ logError(`恢复npm源失败: ${restoreError.message}`);
186
+ }
187
+
188
+ // 恢复package.json版本号
189
+ try {
190
+ packageJson.version = currentVersion;
191
+ writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
192
+ logInfo('已恢复package.json版本号');
193
+ } catch (restoreError) {
194
+ logError(`恢复package.json失败: ${restoreError.message}`);
195
+ }
196
+
197
+ process.exit(1);
198
+ }
199
+ };
package/src/utils.js CHANGED
@@ -2,6 +2,8 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const child_process = require('child_process');
4
4
  const chalk = require('chalk');
5
+ const axios = require('axios');
6
+ const { getApiHost } = require('./config');
5
7
 
6
8
  // 常量定义
7
9
  const CONSTANTS = {
@@ -207,6 +209,230 @@ function CopyFolder(srcDir, tarDir, cb) {
207
209
  }
208
210
 
209
211
 
212
+ /**
213
+ * 获取Git当前分支名
214
+ * @param {string} rootPath - 项目根路径
215
+ * @returns {string} 分支名,如果获取失败返回'unknown'
216
+ */
217
+ function getGitBranch(rootPath) {
218
+ try {
219
+ const result = executeCommand('git rev-parse --abbrev-ref HEAD', rootPath, { encoding: 'utf8' });
220
+ return result.toString().trim() || 'unknown';
221
+ } catch (error) {
222
+ return 'unknown';
223
+ }
224
+ }
225
+
226
+ /**
227
+ * 获取MAC地址
228
+ * @returns {string} MAC地址
229
+ */
230
+ function getMacAddress() {
231
+ const os = require('os');
232
+ const networkInterfaces = os.networkInterfaces();
233
+
234
+ for (const interfaceName in networkInterfaces) {
235
+ const interfaces = networkInterfaces[interfaceName];
236
+ for (const iface of interfaces) {
237
+ // 跳过内部(即127.0.0.1)和非IPv4地址
238
+ if (!iface.internal && iface.family === 'IPv4') {
239
+ return iface.mac || 'unknown';
240
+ }
241
+ }
242
+ }
243
+ return 'unknown';
244
+ }
245
+
246
+ /**
247
+ * 获取内网IP地址
248
+ * @returns {string} 内网IP地址
249
+ */
250
+ function getLocalIP() {
251
+ const os = require('os');
252
+ const networkInterfaces = os.networkInterfaces();
253
+
254
+ for (const interfaceName in networkInterfaces) {
255
+ const interfaces = networkInterfaces[interfaceName];
256
+ for (const iface of interfaces) {
257
+ // 跳过内部(即127.0.0.1)和非IPv4地址
258
+ if (!iface.internal && iface.family === 'IPv4') {
259
+ return iface.address || 'unknown';
260
+ }
261
+ }
262
+ }
263
+ return 'unknown';
264
+ }
265
+
266
+ /**
267
+ * 获取设备信息
268
+ * @returns {Object} 设备信息对象
269
+ */
270
+ function getDeviceInfo() {
271
+ const os = require('os');
272
+ return {
273
+ hostname: os.hostname(),
274
+ macAddress: getMacAddress(),
275
+ localIP: getLocalIP()
276
+ };
277
+ }
278
+
279
+ /**
280
+ * 标准化文本(去除空白字符、换行符等)
281
+ * @param {string} text - 原始文本
282
+ * @returns {string} 标准化后的文本
283
+ */
284
+ function normalizeText(text) {
285
+ if (!text) return '';
286
+ return text
287
+ .replace(/\s+/g, ' ') // 将所有空白字符(包括换行、制表符等)替换为单个空格
288
+ .replace(/\n/g, ' ') // 替换换行符
289
+ .replace(/\r/g, '') // 替换回车符
290
+ .replace(/\t/g, ' ') // 替换制表符
291
+ .trim(); // 去除首尾空格
292
+ }
293
+
294
+ /**
295
+ * 计算两个文本的相似度(基于字符匹配)
296
+ * @param {string} text1 - 文本1
297
+ * @param {string} text2 - 文本2
298
+ * @returns {number} 相似度百分比(0-100)
299
+ */
300
+ function calculateSimilarity(text1, text2) {
301
+ if (!text1 || !text2) {
302
+ return 0;
303
+ }
304
+
305
+ // 标准化文本
306
+ const normalized1 = normalizeText(text1);
307
+ const normalized2 = normalizeText(text2);
308
+
309
+ if (normalized1 === normalized2) {
310
+ return 100;
311
+ }
312
+
313
+ // 使用简单的字符匹配算法
314
+ // 计算相同字符的数量
315
+ const len1 = normalized1.length;
316
+ const len2 = normalized2.length;
317
+
318
+ if (len1 === 0 && len2 === 0) {
319
+ return 100;
320
+ }
321
+
322
+ if (len1 === 0 || len2 === 0) {
323
+ return 0;
324
+ }
325
+
326
+ // 使用最长公共子序列(LCS)的简化版本
327
+ // 这里使用更简单的字符匹配算法
328
+ let matches = 0;
329
+ const minLen = Math.min(len1, len2);
330
+ const maxLen = Math.max(len1, len2);
331
+
332
+ // 计算相同位置的相同字符数
333
+ for (let i = 0; i < minLen; i++) {
334
+ if (normalized1[i] === normalized2[i]) {
335
+ matches++;
336
+ }
337
+ }
338
+
339
+ // 计算相似度百分比
340
+ const similarity = (matches / maxLen) * 100;
341
+
342
+ // 如果相似度很高,使用更精确的算法
343
+ if (similarity > 80) {
344
+ // 使用编辑距离算法(Levenshtein距离)的简化版本
345
+ return calculateLevenshteinSimilarity(normalized1, normalized2);
346
+ }
347
+
348
+ return similarity;
349
+ }
350
+
351
+ /**
352
+ * 使用Levenshtein距离计算相似度
353
+ * @param {string} str1 - 字符串1
354
+ * @param {string} str2 - 字符串2
355
+ * @returns {number} 相似度百分比(0-100)
356
+ */
357
+ function calculateLevenshteinSimilarity(str1, str2) {
358
+ const len1 = str1.length;
359
+ const len2 = str2.length;
360
+
361
+ if (len1 === 0) return len2 === 0 ? 100 : 0;
362
+ if (len2 === 0) return 0;
363
+
364
+ // 创建矩阵
365
+ const matrix = [];
366
+ for (let i = 0; i <= len1; i++) {
367
+ matrix[i] = [i];
368
+ }
369
+ for (let j = 0; j <= len2; j++) {
370
+ matrix[0][j] = j;
371
+ }
372
+
373
+ // 填充矩阵
374
+ for (let i = 1; i <= len1; i++) {
375
+ for (let j = 1; j <= len2; j++) {
376
+ if (str1[i - 1] === str2[j - 1]) {
377
+ matrix[i][j] = matrix[i - 1][j - 1];
378
+ } else {
379
+ matrix[i][j] = Math.min(
380
+ matrix[i - 1][j - 1] + 1, // 替换
381
+ matrix[i][j - 1] + 1, // 插入
382
+ matrix[i - 1][j] + 1 // 删除
383
+ );
384
+ }
385
+ }
386
+ }
387
+
388
+ // 计算相似度
389
+ const distance = matrix[len1][len2];
390
+ const maxLen = Math.max(len1, len2);
391
+ const similarity = ((maxLen - distance) / maxLen) * 100;
392
+
393
+ return Math.max(0, Math.min(100, similarity));
394
+ }
395
+
396
+ /**
397
+ * 检查jjb-cmd版本是否为最新版本
398
+ * @returns {Promise<boolean>} 是否为最新版本
399
+ */
400
+ async function checkCmdVersion() {
401
+ try {
402
+ const packageJsonPath = path.join(__dirname, '../package.json');
403
+ const packageJson = JSON.parse(readFile(packageJsonPath));
404
+ const currentVersion = packageJson.version;
405
+
406
+ const apiHost = getApiHost();
407
+ const response = await axios.get(`${apiHost}/api/cmd/version`, {
408
+ timeout: 5000
409
+ });
410
+
411
+ if (response.data && response.data.status && response.data.data) {
412
+ const latestVersion = response.data.data.version;
413
+
414
+ // 版本号比较:将版本号转换为数字进行比较
415
+ const currentVersionNum = parseInt(currentVersion.replace(/[.]/g, ''));
416
+ const latestVersionNum = parseInt(latestVersion.replace(/[.]/g, ''));
417
+
418
+ if (currentVersionNum < latestVersionNum) {
419
+ logError(`当前版本 v${currentVersion} 不是最新版本,最新版本为 v${latestVersion}`);
420
+ logInfo('请执行以下命令更新到最新版本:');
421
+ logInfo('npm install jjb-cmd -g');
422
+ return false;
423
+ }
424
+ return true;
425
+ }
426
+ // 如果API返回异常,允许继续执行(避免因网络问题阻塞)
427
+ logWarning('无法获取服务器最新版本信息,将继续执行命令');
428
+ return true;
429
+ } catch (error) {
430
+ // 网络错误或其他错误时,允许继续执行
431
+ logWarning('版本检查失败,将继续执行命令');
432
+ return true;
433
+ }
434
+ }
435
+
210
436
  module.exports = {
211
437
  CONSTANTS,
212
438
  logInfo,
@@ -224,5 +450,9 @@ module.exports = {
224
450
  validateEnvironment,
225
451
  DeleteDirAllFile,
226
452
  CopyFile,
227
- CopyFolder
453
+ CopyFolder,
454
+ checkCmdVersion,
455
+ getGitBranch,
456
+ getDeviceInfo,
457
+ calculateSimilarity
228
458
  };