@shijiu/jsview 2.2.201 → 2.2.373
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/dom/bin/jsview-dom-browser.min.js +1 -1
- package/dom/bin/jsview-dom-native.min.js +1 -1
- package/dom/bin/jsview-engine-js-browser.min.js +3 -3
- package/dom/bin/jsview-engine-js-native.min.js +2 -0
- package/dom/bin/jsview-forge-define.min.js +1 -1
- package/dom/index.mjs +40 -14
- package/dom/target_core_revision.mjs +7 -5
- package/loader/jsview-config.js +19 -0
- package/loader/jsview-loader.js +17 -17
- package/loader/jsview-main.mjs +7 -13
- package/loader/jsview.config.default.js +2 -2
- package/package.json +1 -1
- package/patches/node_modules/vite/dist/node/jsview-vite-extension.js +1 -1
- package/tools/jsview-common.mjs +1 -0
- package/tools/jsview-post-build.mjs +152 -73
- package/tools/jsview-vue-devtools.mjs +2 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
parseArguments,
|
|
13
13
|
Logger,
|
|
14
14
|
} from './jsview-common.mjs';
|
|
15
|
+
import { assert } from 'node:console';
|
|
15
16
|
|
|
16
17
|
// npm run build 时检查jsview-dom是否处于Debug模式。
|
|
17
18
|
async function checkDomDebugDisabled(options) {
|
|
@@ -24,15 +25,52 @@ async function checkDomDebugDisabled(options) {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
function getEntryFilePath(options)
|
|
29
|
+
{
|
|
30
|
+
let jsEntryFilePath;
|
|
31
|
+
const jsFileNames = fs.readdirSync(options.distJsDir);
|
|
32
|
+
for(const fileName of jsFileNames) {
|
|
33
|
+
if (fileName.startsWith('main.jsv') && fileName.endsWith('.js')) {
|
|
34
|
+
jsEntryFilePath = path.resolve(options.distJsDir, fileName);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return jsEntryFilePath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getEncryptBase64(appPrivKey, appPubKey, plainText)
|
|
43
|
+
{
|
|
44
|
+
// 编码md5值
|
|
45
|
+
const encryptBase64 = crypto.privateEncrypt({
|
|
46
|
+
key: appPrivKey,
|
|
47
|
+
padding: crypto.constants.RSA_PKCS1_PADDING
|
|
48
|
+
},
|
|
49
|
+
Buffer.from(plainText)
|
|
50
|
+
).toString('base64');
|
|
51
|
+
|
|
52
|
+
// 校验publicKey是否是配对的
|
|
53
|
+
const decryptText = crypto.publicDecrypt({
|
|
54
|
+
key: appPubKey,
|
|
55
|
+
padding: crypto.constants.RSA_PKCS1_PADDING
|
|
56
|
+
},
|
|
57
|
+
Buffer.from(encryptBase64, 'base64')
|
|
58
|
+
).toString();
|
|
59
|
+
if (decryptText !== plainText) {
|
|
60
|
+
Logger.ErrorAndExit('public key dismath to private key!');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return encryptBase64;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function updateOptions(options)
|
|
29
67
|
{
|
|
30
68
|
// 加载私钥文件
|
|
31
69
|
if (!fs.existsSync(options.appPrivKeyFile)) {
|
|
32
70
|
Logger.ErrorAndExit('Failed to open private key file from ' + options.appPrivKeyFile);
|
|
33
71
|
}
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
72
|
+
const privKey = fs.readFileSync(options.appPrivKeyFile);
|
|
73
|
+
if (privKey.indexOf('-----BEGIN PRIVATE KEY-----') < 0) {
|
|
36
74
|
Logger.ErrorAndExit('Get private from src/appConfig/app_sign_private_key.crt failed.');
|
|
37
75
|
}
|
|
38
76
|
|
|
@@ -40,40 +78,62 @@ async function prepareMainAppData(options, fileMd5)
|
|
|
40
78
|
if (!fs.existsSync(options.appPubKeyFile)) {
|
|
41
79
|
Logger.ErrorAndExit('Failed to open public key file from ' + options.appPubKeyFile);
|
|
42
80
|
}
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
81
|
+
const pubKey = fs.readFileSync(options.appPubKeyFile);
|
|
82
|
+
if (pubKey.indexOf('-----BEGIN PUBLIC KEY-----') < 0) {
|
|
45
83
|
Logger.ErrorAndExit('Get private from src/appConfig/app_sign_public_key.pem failed.');
|
|
46
84
|
}
|
|
47
|
-
// 公钥格式转化pem -> der,因为java中的解码处理只识别der格式
|
|
48
|
-
let publicKeyDerText = publicKeyText.toString();
|
|
49
|
-
if (publicKeyDerText.indexOf('\r') >= 0) {
|
|
50
|
-
publicKeyDerText = publicKeyDerText.replace(/\r/g, '');
|
|
51
|
-
}
|
|
52
|
-
if (publicKeyDerText.indexOf('\n') >= 0) {
|
|
53
|
-
publicKeyDerText = publicKeyDerText.replace(/\n/g, '');
|
|
54
|
-
}
|
|
55
|
-
publicKeyDerText = publicKeyDerText.replace('-----BEGIN PUBLIC KEY-----', '');
|
|
56
|
-
publicKeyDerText = publicKeyDerText.replace('-----END PUBLIC KEY-----', '');
|
|
57
|
-
|
|
58
|
-
// 编码md5值
|
|
59
|
-
const encryptCodeBase64 = crypto.privateEncrypt({
|
|
60
|
-
key: privateKeyText,
|
|
61
|
-
padding: crypto.constants.RSA_PKCS1_PADDING
|
|
62
|
-
},
|
|
63
|
-
Buffer.from(fileMd5)
|
|
64
|
-
).toString('base64');
|
|
65
85
|
|
|
66
86
|
// 校验publicKey是否是配对的
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
).
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
const encryptBase64 = getEncryptBase64(privKey, pubKey, 'crypto-test');
|
|
88
|
+
assert(encryptBase64)
|
|
89
|
+
|
|
90
|
+
const appEntryFilePath = getEntryFilePath(options);
|
|
91
|
+
const appEntryContent = fs.readFileSync(appEntryFilePath, 'utf8');
|
|
92
|
+
const appEntryMd5 = crypto.createHash('md5').update(appEntryContent).digest('hex');
|
|
93
|
+
|
|
94
|
+
options.appPrivKey = privKey;
|
|
95
|
+
options.appPubKey = pubKey;
|
|
96
|
+
options.appEntryMd5 = appEntryMd5;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getOtherSignVerifyKeys(options)
|
|
100
|
+
{
|
|
101
|
+
const signVerifyKeys = {};
|
|
102
|
+
const signVerifyKeyFiles = fs.readdirSync(options.appConfigSVKeysDir);
|
|
103
|
+
for (const keyName of signVerifyKeyFiles) {
|
|
104
|
+
const filePath = path.resolve(options.appConfigSVKeysDir, keyName);
|
|
105
|
+
if (keyName == '.gitkeep') {
|
|
106
|
+
continue;
|
|
107
|
+
} else if (!keyName.endsWith('.pem')) {
|
|
108
|
+
Logger.Warn(`Ignore to append invalid signature verification key: ${filePath}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const keyValue = fs.readFileSync(filePath, 'utf8');
|
|
113
|
+
signVerifyKeys[keyName] = keyValue;
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
return signVerifyKeys;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function publicPemToDer(pubKeyPem)
|
|
120
|
+
{
|
|
121
|
+
let pubKeyDer = pubKeyPem.toString();
|
|
122
|
+
if (pubKeyDer.indexOf('\r') >= 0) {
|
|
123
|
+
pubKeyDer = pubKeyDer.replace(/\r/g, '');
|
|
124
|
+
}
|
|
125
|
+
if (pubKeyDer.indexOf('\n') >= 0) {
|
|
126
|
+
pubKeyDer = pubKeyDer.replace(/\n/g, '');
|
|
127
|
+
}
|
|
128
|
+
pubKeyDer = pubKeyDer.replace('-----BEGIN PUBLIC KEY-----', '');
|
|
129
|
+
pubKeyDer = pubKeyDer.replace('-----END PUBLIC KEY-----', '');
|
|
130
|
+
|
|
131
|
+
return pubKeyDer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// main.js处理AppData信息
|
|
135
|
+
async function prepareMainAppData(options)
|
|
136
|
+
{
|
|
77
137
|
// 获取AppData信息
|
|
78
138
|
const appConfigFilePath = path.resolve(options.appConfigDir, 'app.config.mjs');
|
|
79
139
|
if (!fs.existsSync(appConfigFilePath)) {
|
|
@@ -82,29 +142,47 @@ async function prepareMainAppData(options, fileMd5)
|
|
|
82
142
|
const appConfigFileUrl = url.pathToFileURL(appConfigFilePath);
|
|
83
143
|
const { default: appConfig } = await import(appConfigFileUrl);
|
|
84
144
|
|
|
85
|
-
// 获取
|
|
86
|
-
const
|
|
145
|
+
// 获取PublickKey数组
|
|
146
|
+
const publicKeys = [publicPemToDer(options.appPubKey)];
|
|
147
|
+
// TODO: 验签PubKey的安全性尚需考虑
|
|
148
|
+
// const signVerifyKeys = getOtherSignVerifyKeys(options);
|
|
149
|
+
// for(const [keyName, keyValue] of Object.entries(signVerifyKeys)) {
|
|
150
|
+
// Logger.Info(` -> Append ${keyName} to jsvapp.`)
|
|
151
|
+
// publicKeys.push(publicPemToDer(keyValue));
|
|
152
|
+
// }
|
|
153
|
+
|
|
154
|
+
// 获取密文
|
|
155
|
+
const encryptBase64 = getEncryptBase64(options.appPrivKey, options.appPubKey, options.appEntryMd5);
|
|
156
|
+
|
|
157
|
+
// 获取 预加载的文件名
|
|
158
|
+
const filterKeys = [
|
|
159
|
+
'domNativePath',
|
|
160
|
+
'NativePlatformDomBridge',
|
|
161
|
+
'ForgeExtension',
|
|
162
|
+
'CodeRevision',
|
|
163
|
+
'__vue_app__',
|
|
164
|
+
'mount("#app")',
|
|
165
|
+
];
|
|
166
|
+
const preloadChunks = [];
|
|
87
167
|
const jsFileNames = fs.readdirSync(options.distJsDir);
|
|
88
168
|
for(const fileName of jsFileNames) {
|
|
89
169
|
if (!fileName.startsWith('chunk.jsv') || !fileName.endsWith('.js')) {
|
|
90
170
|
continue;
|
|
91
171
|
}
|
|
92
172
|
|
|
93
|
-
const hash = fileName.replace(/.*chunk\.jsv.([a-fA-F0-9].*).js/, '$1');
|
|
94
|
-
|
|
95
173
|
const filePath = path.resolve(options.distJsDir, fileName);
|
|
96
174
|
const sourceContent = fs.readFileSync(filePath, 'utf8');
|
|
97
|
-
|
|
175
|
+
if(filterKeys.some(it => sourceContent.includes(it)) == false) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
98
178
|
|
|
99
|
-
|
|
179
|
+
preloadChunks.push(fileName);
|
|
100
180
|
}
|
|
101
181
|
|
|
102
182
|
// 组装AppData
|
|
103
|
-
appConfig.PublicKeys =
|
|
104
|
-
appConfig.EncryptCodes = [
|
|
105
|
-
appConfig.
|
|
106
|
-
|
|
107
|
-
|
|
183
|
+
appConfig.PublicKeys = publicKeys; // 使用签名数组,支持后续追加签名
|
|
184
|
+
appConfig.EncryptCodes = [encryptBase64]; // 同样使用数组,支持后续追加
|
|
185
|
+
appConfig.PreloadChunks = preloadChunks;
|
|
108
186
|
|
|
109
187
|
return JSON.stringify(appConfig);
|
|
110
188
|
}
|
|
@@ -146,20 +224,31 @@ async function signApp(options)
|
|
|
146
224
|
|
|
147
225
|
var sourceContent = fs.readFileSync(filePath, 'utf8');
|
|
148
226
|
|
|
149
|
-
|
|
227
|
+
const jsvMd5 = crypto.createHash('md5').update(sourceContent).digest('hex');
|
|
228
|
+
|
|
229
|
+
// 获取签名并验签
|
|
230
|
+
const cryptoSigner = crypto.createSign('sha256').update(jsvMd5).end();
|
|
231
|
+
const cryptoVerifier = crypto.createVerify('sha256').update(jsvMd5).end();
|
|
232
|
+
const jsvSign = cryptoSigner.sign(options.appPrivKey, 'hex');
|
|
233
|
+
const verified = cryptoVerifier.verify(options.appPubKey, jsvSign, 'hex');
|
|
234
|
+
if(!verified) {
|
|
235
|
+
Logger.ErrorAndExit('Failed to create signature verification.');
|
|
236
|
+
}
|
|
150
237
|
|
|
151
|
-
const jsvAppMd5 = crypto.createHash('md5').update(sourceContent).digest('hex');
|
|
152
238
|
let appDataInfo = '';
|
|
153
239
|
if (fileName.indexOf('main.jsv.') >= 0) {
|
|
154
240
|
// 对main文件加入应用头信息
|
|
155
|
-
appDataInfo = await prepareMainAppData(options,
|
|
241
|
+
appDataInfo = await prepareMainAppData(options, jsvMd5);
|
|
156
242
|
|
|
157
243
|
// 格式化jsvapp信息 /*jsvapp:内容长度:{内容}*/
|
|
158
244
|
// 使用TextEncoder解决中文长度问题
|
|
159
245
|
const infoLen = new TextEncoder().encode(appDataInfo).length;
|
|
160
246
|
appDataInfo = '/*jsvapp:' + infoLen + ':' + appDataInfo + ':' + infoLen + ':jsvapp*/';
|
|
161
247
|
}
|
|
162
|
-
sourceContent = sourceContent + appDataInfo
|
|
248
|
+
sourceContent = sourceContent + appDataInfo;
|
|
249
|
+
|
|
250
|
+
sourceContent = sourceContent + '/*jsvsign:' + jsvSign + '*/';
|
|
251
|
+
|
|
163
252
|
fs.writeFileSync(filePath, sourceContent, 'utf8');
|
|
164
253
|
}
|
|
165
254
|
}
|
|
@@ -182,6 +271,9 @@ function redirectSourceMappingURL(options)
|
|
|
182
271
|
'# sourceMappingURL=', '# sourceMappingURL=http://localhost:57245/map/');
|
|
183
272
|
sourceContent = sourceContent.substring(0, mapUrlOffset) + mapUrlStr;
|
|
184
273
|
|
|
274
|
+
//添加一个回车,后续把\n归入算进md5计算中
|
|
275
|
+
sourceContent += '\n';
|
|
276
|
+
|
|
185
277
|
fs.writeFileSync(filePath, sourceContent, 'utf8');
|
|
186
278
|
};
|
|
187
279
|
}
|
|
@@ -192,41 +284,35 @@ function makeMainJsvMjs(options, framework)
|
|
|
192
284
|
return;
|
|
193
285
|
}
|
|
194
286
|
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
if (fileName.startsWith('main.jsv') && fileName.endsWith('.js')) {
|
|
199
|
-
jsEntryFilePath = path.resolve(options.distJsDir, fileName);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const sourceContent = fs.readFileSync(jsEntryFilePath, 'utf8');
|
|
205
|
-
const md5 = sourceContent.replace(/\n/g, '').replace(/.*\/\*jsvmd5:([a-fA-F0-9]{8})[a-fA-F0-9]*\*\/.*/, '$1');
|
|
206
|
-
const newEntryFileName = `main.jsv.${md5}.js`;
|
|
287
|
+
// 更新entry hash
|
|
288
|
+
const jsEntryFilePath = getEntryFilePath(options);
|
|
289
|
+
const newEntryFileName = `main.jsv.${options.appEntryMd5.slice(0, 8)}.js`;
|
|
207
290
|
const newEntryFilePath = path.resolve(path.dirname(jsEntryFilePath), newEntryFileName);
|
|
208
291
|
Logger.Info(' -> ' + path.relative(options.projectDir, newEntryFilePath));
|
|
209
292
|
fs.renameSync(jsEntryFilePath, newEntryFilePath);
|
|
210
293
|
|
|
294
|
+
// 更新index.html hash
|
|
211
295
|
var indexContent = fs.readFileSync(options.distJsIndexFile, 'utf8');
|
|
212
296
|
indexContent = indexContent.replace(path.basename(jsEntryFilePath), newEntryFileName);
|
|
213
297
|
Logger.Info(' -> ' + path.relative(options.projectDir, options.distJsIndexFile));
|
|
214
298
|
fs.writeFileSync(options.distJsIndexFile, indexContent, 'utf8');
|
|
215
299
|
|
|
300
|
+
// 创建JsView 加载用的 mjs entry
|
|
216
301
|
const moduleEntryFilePath = newEntryFilePath.replace(/\.js$/, '.mjs');
|
|
217
302
|
fs.copyFileSync(newEntryFilePath, moduleEntryFilePath);
|
|
303
|
+
|
|
304
|
+
// 创建JsView Debug用的 mjs entry
|
|
305
|
+
const debugEntryFilePath = path.resolve(options.distJsDir, 'main.jsv.mjs');
|
|
306
|
+
Logger.Info(' -> ' + path.relative(options.projectDir, debugEntryFilePath));
|
|
307
|
+
fs.copyFileSync(newEntryFilePath, debugEntryFilePath);
|
|
218
308
|
}
|
|
219
309
|
|
|
220
310
|
function makeDebugMap(options, framework)
|
|
221
311
|
{
|
|
222
312
|
fs.mkdirSync(options.distDebugMapDir, { recursive: true });
|
|
223
313
|
|
|
224
|
-
let jsEntryFilePath;
|
|
225
314
|
const jsFileNames = fs.readdirSync(options.distJsDir);
|
|
226
315
|
for(const fileName of jsFileNames) {
|
|
227
|
-
if (fileName.startsWith('main.jsv') && fileName.endsWith('.mjs')) {
|
|
228
|
-
jsEntryFilePath = path.resolve(options.distJsDir, fileName);
|
|
229
|
-
}
|
|
230
316
|
if (!fileName.endsWith('.map')) {
|
|
231
317
|
continue;
|
|
232
318
|
}
|
|
@@ -237,16 +323,6 @@ function makeDebugMap(options, framework)
|
|
|
237
323
|
fs.renameSync(from, to);
|
|
238
324
|
};
|
|
239
325
|
|
|
240
|
-
if (jsEntryFilePath) {
|
|
241
|
-
let extName = path.extname(jsEntryFilePath);
|
|
242
|
-
if (framework == 'vue') {
|
|
243
|
-
extName = '.mjs'
|
|
244
|
-
}
|
|
245
|
-
const to = path.resolve(options.distJsDir, 'main.jsv' + extName);
|
|
246
|
-
Logger.Info(' -> ' + path.relative(options.projectDir, to));
|
|
247
|
-
fs.copyFileSync(jsEntryFilePath, to);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
326
|
const jsmapServeName = 'jsview-jsmap-serve.mjs';
|
|
251
327
|
const jsmapServePath = path.resolve(options.jsviewToolsDir, jsmapServeName);
|
|
252
328
|
if (!fs.existsSync(jsmapServePath)) {
|
|
@@ -335,6 +411,9 @@ async function main(argv)
|
|
|
335
411
|
redirectSourceMappingURL(options)
|
|
336
412
|
Logger.Info('Redirected JsView source map...');
|
|
337
413
|
|
|
414
|
+
// EntryMd5 要放在sourcemap更新之后
|
|
415
|
+
updateOptions(options);
|
|
416
|
+
|
|
338
417
|
Logger.Info('Checking JsView app config...');
|
|
339
418
|
await checkAppConfig(options);
|
|
340
419
|
Logger.Info('Checked JsView app config...');
|
|
@@ -55,8 +55,10 @@ async function runVueDevtoolsStandalone(options)
|
|
|
55
55
|
|
|
56
56
|
const exportElectronMirror = exportPrefix + ' ELECTRON_MIRROR=http://cdn.release.qcast.cn/RN_devtools/electron-env/'
|
|
57
57
|
let cmdline = exportElectronMirror + ' && npm install --package-lock-only --save-exact --save-dev electron@21.4.4 @vue/devtools@6.5.1'
|
|
58
|
+
cmdline = cmdline.replace(' &&', '&&'); // 解决window系统设置后多了一个空格的问题
|
|
58
59
|
execCommand(cmdline);
|
|
59
60
|
cmdline = exportElectronMirror + ' && npm ci'
|
|
61
|
+
cmdline = cmdline.replace(' &&', '&&'); // 解决window系统设置后多了一个空格的问题
|
|
60
62
|
execCommand(cmdline);
|
|
61
63
|
|
|
62
64
|
console.log()
|