jjb-cmd 2.5.3 → 2.5.4
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/SECURITY.md +71 -0
- package/bin/command.js +6 -0
- package/build.js +20 -0
- package/obf.config.json +3 -0
- package/package.json +4 -2
- package/src/ai-pull.js +675 -0
- package/src/auth.js +71 -1
- package/src/code-optimization.js +46 -1
- package/src/config.js +122 -1
- package/src/crypto-utils.js +183 -1
- package/src/password-input.js +79 -1
- package/src/publish.js +307 -1
- package/src/push.js +417 -1
- package/src/rm-rf.js +49 -1
- package/src/utils.js +228 -1
package/src/publish.js
CHANGED
|
@@ -1 +1,307 @@
|
|
|
1
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const { getApiHost } = require('./config');
|
|
6
|
+
const {
|
|
7
|
+
CONSTANTS,
|
|
8
|
+
logInfo,
|
|
9
|
+
logSuccess,
|
|
10
|
+
logWarning,
|
|
11
|
+
logError,
|
|
12
|
+
executeCommand,
|
|
13
|
+
fileExists,
|
|
14
|
+
readFile,
|
|
15
|
+
writeFile
|
|
16
|
+
} = require('./utils');
|
|
17
|
+
const { loadAuth } = require('./crypto-utils');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 解析组件配置信息
|
|
21
|
+
* @param {string} distContent - 构建后的文件内容
|
|
22
|
+
* @returns {Object} 解析后的配置信息
|
|
23
|
+
*/
|
|
24
|
+
function parseComponentConfig(distContent) {
|
|
25
|
+
const matches = distContent.match(/(window\[).+?(\.componentKey|.+]={)/img);
|
|
26
|
+
if (!matches || !matches.length) {
|
|
27
|
+
throw new Error('请在组件入口文件中添加"window[props.componentKey]"以暴露组件的配置信息!');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const componentConfig = {};
|
|
31
|
+
|
|
32
|
+
// 解析 info 配置
|
|
33
|
+
const infoMatches = distContent.match(/componentKey.+([{,]info:\s{0,}{)/img);
|
|
34
|
+
if (!infoMatches || !infoMatches.length) {
|
|
35
|
+
throw new Error('暴露的配置信息中缺少"info"!');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let infoStr = '{';
|
|
39
|
+
const infoStr1 = distContent.split(infoMatches[0])[1];
|
|
40
|
+
for (let i = 0; i < infoStr1.length; i++) {
|
|
41
|
+
infoStr += infoStr1[i];
|
|
42
|
+
if (infoStr1[i] === '}') {
|
|
43
|
+
try {
|
|
44
|
+
const currJson = new Function(`return ${infoStr}`)();
|
|
45
|
+
Object.assign(componentConfig, currJson);
|
|
46
|
+
componentConfig.defaultSetting = {};
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// 忽略解析错误
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 解析 settings 配置
|
|
55
|
+
const settingsMatches = distContent.match(/componentKey.+([{,]settings:\s{0,}{)/img);
|
|
56
|
+
if (!settingsMatches || !settingsMatches.length) {
|
|
57
|
+
throw new Error('暴露的配置信息中缺少"settings"!');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let settingsStr = '{';
|
|
61
|
+
const settingsStr1 = distContent.split(settingsMatches[0])[1];
|
|
62
|
+
for (let i = 0; i < settingsStr1.length; i++) {
|
|
63
|
+
settingsStr += settingsStr1[i];
|
|
64
|
+
if (settingsStr1[i] === '}') {
|
|
65
|
+
try {
|
|
66
|
+
componentConfig.defaultSetting = new Function(`return ${settingsStr}`)();
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// 忽略解析错误
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return JSON.stringify(componentConfig);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 执行构建命令
|
|
79
|
+
* @param {string} rootPath - 项目根路径
|
|
80
|
+
*/
|
|
81
|
+
function executeBuild(rootPath) {
|
|
82
|
+
const yarnLockPath = path.join(rootPath, 'yarn.lock');
|
|
83
|
+
if (fileExists(yarnLockPath)) {
|
|
84
|
+
executeCommand('yarn build:production', rootPath);
|
|
85
|
+
} else {
|
|
86
|
+
executeCommand('npm run build:production', rootPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 验证必要的文件
|
|
92
|
+
* @param {string} rootPath - 项目根路径
|
|
93
|
+
* @returns {Object} 验证结果和文件路径
|
|
94
|
+
*/
|
|
95
|
+
function validateRequiredFiles(rootPath) {
|
|
96
|
+
const readmePath = path.join(rootPath, 'README.md');
|
|
97
|
+
const distPath = path.join(rootPath, 'dist', 'index.js');
|
|
98
|
+
const packageJsonPath = path.join(rootPath, 'package.json');
|
|
99
|
+
const thumbnailPath = path.join(rootPath, 'thumbnail.png');
|
|
100
|
+
|
|
101
|
+
if (!fileExists(packageJsonPath)) {
|
|
102
|
+
throw new Error('组件发布失败!根目录缺少\'package.json\'文件!');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!fileExists(distPath)) {
|
|
106
|
+
throw new Error('组件发布失败!根目录缺少\'dist/index.js\'文件!');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!fileExists(readmePath)) {
|
|
110
|
+
throw new Error('请在组件根目录添加一个README.md说明文件!');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!fileExists(thumbnailPath)) {
|
|
114
|
+
throw new Error('请在组件根目录为组件添加一个缩略图"thumbnail.png"文件!');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
readmePath,
|
|
119
|
+
distPath,
|
|
120
|
+
packageJsonPath,
|
|
121
|
+
thumbnailPath
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 提交发布数据
|
|
127
|
+
* @param {string} message - 发布消息
|
|
128
|
+
* @param {Object} pushData - 发布数据
|
|
129
|
+
*/
|
|
130
|
+
const submitFun = function (message, pushData) {
|
|
131
|
+
pushData.message = message || 'no message';
|
|
132
|
+
axios.post(`${getApiHost()}/api/file/publish`, pushData).then(res => {
|
|
133
|
+
logSuccess(res.data.message || '组件发布成功');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}).catch(e => {
|
|
136
|
+
if (e && e.response) {
|
|
137
|
+
const status = e.response.status;
|
|
138
|
+
const serverMsg = (e.response.data && (e.response.data.message || e.response.data.msg)) || '';
|
|
139
|
+
logError(`发布失败 (${status}): ${serverMsg || e.message}`);
|
|
140
|
+
} else if (e && e.request) {
|
|
141
|
+
logError(`网络连接失败: 无法连接到服务器`);
|
|
142
|
+
} else {
|
|
143
|
+
logError(`发布失败: ${e.message || '未知错误'}`);
|
|
144
|
+
}
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
module.exports = version => {
|
|
150
|
+
const authPath = path.join(__dirname, '../.auth');
|
|
151
|
+
|
|
152
|
+
if (!fileExists(authPath)) {
|
|
153
|
+
logError('未检测到认证信息,请先执行登录操作');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 安全读取认证信息
|
|
158
|
+
let username, password;
|
|
159
|
+
try {
|
|
160
|
+
logInfo('正在加载认证信息...');
|
|
161
|
+
const authData = loadAuth(authPath);
|
|
162
|
+
username = authData.username;
|
|
163
|
+
password = authData.password;
|
|
164
|
+
logSuccess('认证信息加载成功');
|
|
165
|
+
} catch (error) {
|
|
166
|
+
logError(`读取认证信息失败: ${error.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
logInfo('正在请求服务器授权...');
|
|
171
|
+
|
|
172
|
+
axios.post(`${getApiHost()}/api/auth`, {
|
|
173
|
+
username,
|
|
174
|
+
password
|
|
175
|
+
}).then(async res => {
|
|
176
|
+
if (res.data.status) {
|
|
177
|
+
logSuccess('服务器授权成功');
|
|
178
|
+
|
|
179
|
+
const rootPath = path.resolve('./');
|
|
180
|
+
|
|
181
|
+
// 执行构建
|
|
182
|
+
logInfo('正在执行项目构建...');
|
|
183
|
+
try {
|
|
184
|
+
executeBuild(rootPath);
|
|
185
|
+
logSuccess('项目构建完成');
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logError(`项目构建失败: ${error.message}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 验证必要文件
|
|
192
|
+
logInfo('正在验证发布所需文件...');
|
|
193
|
+
let filePaths;
|
|
194
|
+
try {
|
|
195
|
+
filePaths = validateRequiredFiles(rootPath);
|
|
196
|
+
logSuccess('文件验证通过');
|
|
197
|
+
} catch (error) {
|
|
198
|
+
logError(error.message);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 读取包信息
|
|
203
|
+
logInfo('正在读取包信息...');
|
|
204
|
+
const packageJsonFile = JSON.parse(readFile(path.join(__dirname, '../package.json')));
|
|
205
|
+
const { version: cmdVersion } = packageJsonFile;
|
|
206
|
+
|
|
207
|
+
// 准备发布数据
|
|
208
|
+
logInfo('正在准备发布数据...');
|
|
209
|
+
const pushData = {
|
|
210
|
+
username,
|
|
211
|
+
cmd_version: cmdVersion,
|
|
212
|
+
hostname: os.hostname(),
|
|
213
|
+
platform: os.platform(),
|
|
214
|
+
package_version: version || 0,
|
|
215
|
+
package_content: readFile(filePaths.distPath),
|
|
216
|
+
readme_content: readFile(filePaths.readmePath)
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// 解析组件配置
|
|
220
|
+
try {
|
|
221
|
+
logInfo('正在解析组件配置信息...');
|
|
222
|
+
const distContent = readFile(filePaths.distPath);
|
|
223
|
+
pushData.component_config_content = parseComponentConfig(distContent);
|
|
224
|
+
logSuccess('组件配置解析完成');
|
|
225
|
+
} catch (error) {
|
|
226
|
+
logError(error.message);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 处理缩略图
|
|
231
|
+
logInfo('正在处理缩略图...');
|
|
232
|
+
const thumbnailData = readFile(filePaths.thumbnailPath, 'binary');
|
|
233
|
+
pushData.thumbnail_base64 = Buffer.from(thumbnailData, 'binary').toString('base64');
|
|
234
|
+
logSuccess('缩略图处理完成');
|
|
235
|
+
|
|
236
|
+
// 危险操作警告确认
|
|
237
|
+
logWarning('警告:您即将进行组件发布操作,此操作不可逆,请谨慎操作');
|
|
238
|
+
logWarning('发布错误可能导致线上应用产生严重生产故障,请务必确保:');
|
|
239
|
+
logWarning(' 1. 已充分测试组件各项功能');
|
|
240
|
+
logWarning(' 2. 已检查构建产物完整性');
|
|
241
|
+
logWarning(' 3. 已确认版本号和发布信息正确');
|
|
242
|
+
logWarning(' 4. 已了解发布后的影响范围');
|
|
243
|
+
logWarning(' 5. 发布成功后请立即通知相关人员进行测试和验证');
|
|
244
|
+
|
|
245
|
+
await new Promise((resolve) => {
|
|
246
|
+
let countdown = 10;
|
|
247
|
+
const interval = setInterval(() => {
|
|
248
|
+
process.stdout.write(`\r倒计时 ${countdown} 秒... `);
|
|
249
|
+
countdown--;
|
|
250
|
+
if (countdown < 0) {
|
|
251
|
+
clearInterval(interval);
|
|
252
|
+
process.stdout.write('\r\n');
|
|
253
|
+
resolve();
|
|
254
|
+
}
|
|
255
|
+
}, 1000);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const { confirmed } = await inquirer.prompt([
|
|
259
|
+
{
|
|
260
|
+
type: 'confirm',
|
|
261
|
+
name: 'confirmed',
|
|
262
|
+
message: '我已充分了解以上可能存在的严重风险,确认继续发布操作?',
|
|
263
|
+
default: false
|
|
264
|
+
}
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
if (!confirmed) {
|
|
268
|
+
logInfo('发布操作已取消');
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 提交发布
|
|
273
|
+
const { message } = await inquirer.prompt([
|
|
274
|
+
{
|
|
275
|
+
type: 'input',
|
|
276
|
+
name: 'message',
|
|
277
|
+
message: '请输入此次组件发布的信息:',
|
|
278
|
+
default: 'no message'
|
|
279
|
+
}
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
logInfo('正在提交发布请求...');
|
|
283
|
+
submitFun(message || 'no message', pushData);
|
|
284
|
+
} else {
|
|
285
|
+
logError(`授权失败: ${res.data.message || '未知错误'}`);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}).catch(e => {
|
|
289
|
+
// 更准确的错误分类,避免误导为授权问题
|
|
290
|
+
if (e && e.response) {
|
|
291
|
+
const status = e.response.status;
|
|
292
|
+
if (status === 401 || status === 403) {
|
|
293
|
+
logError(`授权失败 (${status}): 认证信息无效或无权限`);
|
|
294
|
+
} else {
|
|
295
|
+
const serverMsg = (e.response.data && (e.response.data.message || e.response.data.msg)) || '';
|
|
296
|
+
logError(`请求失败 (${status}): ${serverMsg || e.message}`);
|
|
297
|
+
}
|
|
298
|
+
} else if (e && e.request) {
|
|
299
|
+
// 请求已发出但未收到响应,多为网络问题
|
|
300
|
+
logError(`网络连接失败: 无法连接到服务器或服务器无响应`);
|
|
301
|
+
} else {
|
|
302
|
+
// 触发请求前发生的错误
|
|
303
|
+
logError(`操作失败: ${e ? e.message : '未知错误'}`);
|
|
304
|
+
}
|
|
305
|
+
process.exit(1);
|
|
306
|
+
});
|
|
307
|
+
};
|