ota-manager 1.0.14 → 1.0.16
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/lib/ota-build.cjs +73 -11
- package/lib/ota-security.js +2 -1
- package/lib/verify-dist.cjs +21 -21
- package/package.json +1 -1
package/lib/ota-build.cjs
CHANGED
|
@@ -49,6 +49,44 @@ if (fs.existsSync(envPath)) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function isBase64(str) {
|
|
53
|
+
if (!str) return false;
|
|
54
|
+
const clean = str.replace(/\s+/g, '');
|
|
55
|
+
if (clean.length === 0) return false;
|
|
56
|
+
const regex = /^[A-Za-z0-9+/=]+$/;
|
|
57
|
+
return regex.test(clean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getBase64OrFileContent(val) {
|
|
61
|
+
if (!val) return '';
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(val)) {
|
|
64
|
+
const fileContent = fs.readFileSync(val);
|
|
65
|
+
const textContent = fileContent.toString('utf8').trim();
|
|
66
|
+
|
|
67
|
+
if (textContent.includes('=')) {
|
|
68
|
+
const match = textContent.match(/^\s*[\w.-]+\s*=\s*(.*)?\s*$/);
|
|
69
|
+
if (match && match[1]) {
|
|
70
|
+
const cleanVal = match[1].trim();
|
|
71
|
+
if (isBase64(cleanVal)) {
|
|
72
|
+
return cleanVal;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isBase64(textContent)) {
|
|
78
|
+
return textContent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return fileContent.toString('base64');
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn(`⚠️ Warning: Error reading file at "${val}":`, e.message);
|
|
85
|
+
}
|
|
86
|
+
return val;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
52
90
|
const rawArgs = process.argv.slice(2).map(a => a.toLowerCase());
|
|
53
91
|
const hasNv = rawArgs.includes('-nv') || rawArgs.includes('--nv') || rawArgs.includes('--no-version') || rawArgs.includes('nover') || rawArgs.includes('no-ver') || rawArgs.includes('-no') || rawArgs.includes('--no') || rawArgs.includes('no') || rawArgs.includes('-norev') || rawArgs.includes('--norev') || rawArgs.includes('norev');
|
|
54
92
|
|
|
@@ -244,15 +282,28 @@ async function buildIos() {
|
|
|
244
282
|
try {
|
|
245
283
|
execSync('git add .', { stdio: 'ignore' });
|
|
246
284
|
const status = execSync('git status --porcelain').toString();
|
|
285
|
+
|
|
286
|
+
let remoteName = 'origin';
|
|
287
|
+
try {
|
|
288
|
+
const remotes = execSync('git remote').toString().trim().split(/\s+/);
|
|
289
|
+
if (!remotes.includes('origin')) {
|
|
290
|
+
if (remotes.includes('github')) {
|
|
291
|
+
remoteName = 'github';
|
|
292
|
+
} else if (remotes.length > 0 && remotes[0]) {
|
|
293
|
+
remoteName = remotes[0];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {}
|
|
297
|
+
|
|
247
298
|
if (status) {
|
|
248
|
-
console.log(
|
|
299
|
+
console.log(`📤 Pushing changes to ${remoteName}...`);
|
|
249
300
|
execSync('git commit -m "chore: automated build sync"', { stdio: 'ignore' });
|
|
250
|
-
execSync(`git push
|
|
301
|
+
execSync(`git push ${remoteName} ${branch}`, { stdio: 'ignore' });
|
|
251
302
|
console.log('✅ Changes pushed!');
|
|
252
303
|
await sleep(2000);
|
|
253
304
|
} else {
|
|
254
|
-
console.log(
|
|
255
|
-
execSync(`git push
|
|
305
|
+
console.log(`✅ No local file changes. Ensuring active branch ref is pushed to remote on ${remoteName}...`);
|
|
306
|
+
execSync(`git push ${remoteName} ${branch}`, { stdio: 'ignore' });
|
|
256
307
|
}
|
|
257
308
|
} catch (e) {
|
|
258
309
|
console.log('⚠️ Git push skipped or branch already up to date on remote.');
|
|
@@ -261,13 +312,24 @@ async function buildIos() {
|
|
|
261
312
|
console.log('🚀 Triggering GitHub Action...');
|
|
262
313
|
try {
|
|
263
314
|
const payload = { ref: branch };
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
315
|
+
payload.inputs = {
|
|
316
|
+
channel: process.env.PUBLIC_APP_CHANNEL || 'training',
|
|
317
|
+
api_url: process.env.PUBLIC_API_URL || '',
|
|
318
|
+
github_ota_pat: process.env.PUBLIC_GITHUB_OTA_PAT || '',
|
|
319
|
+
ota_update_url: process.env.PUBLIC_OTA_UPDATE_URL || ''
|
|
320
|
+
};
|
|
321
|
+
if (activeWorkflowId === 'ios-release.yml') {
|
|
322
|
+
const certVal = process.env.APPLE_BUILD_CERTIFICATE_BASE64 || '';
|
|
323
|
+
const provVal = process.env.APPLE_BUILD_PROVISION_PROFILE_BASE64 || '';
|
|
324
|
+
const apiKeyVal = process.env.APP_STORE_CONNECT_API_KEY_BASE64 || '';
|
|
325
|
+
|
|
326
|
+
payload.inputs.apple_build_certificate_base64 = getBase64OrFileContent(certVal);
|
|
327
|
+
payload.inputs.apple_build_provision_profile_base64 = getBase64OrFileContent(provVal);
|
|
328
|
+
payload.inputs.app_store_connect_api_key_base64 = getBase64OrFileContent(apiKeyVal);
|
|
329
|
+
payload.inputs.app_store_connect_api_key_id = process.env.APP_STORE_CONNECT_API_KEY_ID || '';
|
|
330
|
+
payload.inputs.app_store_connect_api_issuer = process.env.APP_STORE_CONNECT_API_ISSUER || '';
|
|
331
|
+
payload.inputs.apple_p12_password = process.env.APPLE_P12_PASSWORD || '';
|
|
332
|
+
payload.inputs.apple_keychain_password = process.env.APPLE_KEYCHAIN_PASSWORD || '';
|
|
271
333
|
}
|
|
272
334
|
await githubApi(`/repos/${REPO}/actions/workflows/${activeWorkflowId}/dispatches`, 'POST', payload);
|
|
273
335
|
console.log('✅ Trigger Success! Waiting for run to start...');
|
package/lib/ota-security.js
CHANGED
|
@@ -114,7 +114,8 @@ async function runSecurityAudit() {
|
|
|
114
114
|
|
|
115
115
|
if (content.includes('"version"')) {
|
|
116
116
|
const data = JSON.parse(content);
|
|
117
|
-
|
|
117
|
+
const remoteVer = data.version || data[channel]?.version || (data.live?.version || data.training?.version);
|
|
118
|
+
console.log(`✅ Read Successful! Current Remote Version: v${remoteVer}`);
|
|
118
119
|
} else {
|
|
119
120
|
console.log(`❌ Raw Response: ${content.substring(0, 100)}...`);
|
|
120
121
|
throw new Error("Invalid content");
|
package/lib/verify-dist.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const distDir = path.join(process.cwd(), 'dist');
|
|
5
5
|
const filesToVerify = ['splash.html', 'index.html', 'home.html'];
|
|
6
6
|
|
|
7
|
-
console.log('🔍
|
|
7
|
+
console.log('🔍 STARTING OTA ZIP VERIFICATION (Pre-Flight Check)...\n');
|
|
8
8
|
|
|
9
9
|
if (!fs.existsSync(distDir)) {
|
|
10
10
|
console.log(`❌ Error: dist/ directory not found in ${process.cwd()}`);
|
|
@@ -16,70 +16,70 @@ let totalErrors = 0;
|
|
|
16
16
|
filesToVerify.forEach(filename => {
|
|
17
17
|
const filePath = path.join(distDir, filename);
|
|
18
18
|
if (!fs.existsSync(filePath)) {
|
|
19
|
-
console.log(`⚠️ WARNING:
|
|
19
|
+
console.log(`⚠️ WARNING: Main file ${filename} not found in dist/`);
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
console.log(`📄
|
|
23
|
+
console.log(`📄 Verifying: ${filename}`);
|
|
24
24
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
25
25
|
|
|
26
|
-
// Regex
|
|
26
|
+
// Regex to search for src="..." or href="..."
|
|
27
27
|
const regex = /(?:src|href)=["']([^"']+)["']/g;
|
|
28
28
|
let match;
|
|
29
29
|
|
|
30
30
|
while ((match = regex.exec(content)) !== null) {
|
|
31
31
|
const assetPath = match[1];
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// Ignore external links or base64
|
|
34
34
|
if (assetPath.startsWith('http') || assetPath.startsWith('data:')) continue;
|
|
35
35
|
|
|
36
36
|
let hasError = false;
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// CHECK 1: Illegal slashes specifically for assets folder
|
|
39
39
|
if (assetPath.startsWith('/assets') || assetPath.startsWith('.//')) {
|
|
40
|
-
console.log(` ❌ ERROR
|
|
40
|
+
console.log(` ❌ PATH ERROR: Illegal path found -> "${assetPath}"`);
|
|
41
41
|
hasError = true;
|
|
42
42
|
totalErrors++;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// Ignore physical file validation for HTML navigation or SVG anchors
|
|
46
46
|
if (assetPath.endsWith('.html') || assetPath.startsWith('#')) continue;
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
48
|
+
// CHECK 2: Does the file physically exist?
|
|
49
|
+
// Since our paths must be relative (e.g., assets/file.css),
|
|
50
|
+
// we join them with the root dist/ folder
|
|
51
51
|
const physicalPath = path.join(distDir, assetPath);
|
|
52
52
|
if (!fs.existsSync(physicalPath)) {
|
|
53
|
-
console.log(` ❌
|
|
53
|
+
console.log(` ❌ MISSING FILE ERROR: File not found at -> "${assetPath}"`);
|
|
54
54
|
hasError = true;
|
|
55
55
|
totalErrors++;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
console.log(` ✅ ${filename}
|
|
58
|
+
console.log(` ✅ ${filename} verification complete.`);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// Verify contents of dist/assets/ folder
|
|
62
62
|
const assetsDir = path.join(distDir, 'assets');
|
|
63
63
|
if (fs.existsSync(assetsDir)) {
|
|
64
|
-
console.log('\n📁
|
|
64
|
+
console.log('\n📁 Verifying JS files in dist/assets/...');
|
|
65
65
|
const jsFiles = fs.readdirSync(assetsDir).filter(f => f.endsWith('.js'));
|
|
66
66
|
|
|
67
67
|
jsFiles.forEach(f => {
|
|
68
68
|
const content = fs.readFileSync(path.join(assetsDir, f), 'utf8');
|
|
69
69
|
if (content.includes('return"/"+e') || content.includes('return "/" + e')) {
|
|
70
|
-
console.log(` ❌
|
|
70
|
+
console.log(` ❌ VITE PRELOAD ERROR: Absolute dynamic path found in JS file -> "${f}"`);
|
|
71
71
|
totalErrors++;
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
console.log(' ✅
|
|
74
|
+
console.log(' ✅ JS file verification in dist/assets/ complete.');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
console.log('\n------------------------------------');
|
|
78
78
|
if (totalErrors === 0) {
|
|
79
|
-
console.log('🎉
|
|
80
|
-
console.log('
|
|
79
|
+
console.log('🎉 VERIFICATION SUCCESS! All paths are purely relative and physical files detected.');
|
|
80
|
+
console.log('This OTA bundle is 100% SAFE to deploy.\n');
|
|
81
81
|
} else {
|
|
82
|
-
console.log(`💥
|
|
83
|
-
console.log('💡
|
|
82
|
+
console.log(`💥 VERIFICATION FAILED! Found ${totalErrors} error(s) in path structure or files.`);
|
|
83
|
+
console.log('💡 MUST FIX before deploying OTA to avoid blank screens on users\' devices!\n');
|
|
84
84
|
process.exit(1);
|
|
85
85
|
}
|