react-native-bspatch 1.1.3 → 2.0.0
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/README.md +47 -52
- package/android/src/main/java/vn/reactnativebspatch/BsPatchModule.kt +15 -102
- package/android/src/main/java/vn/reactnativebspatch/OtaBundleResolver.kt +21 -1
- package/cpp/bsdiff/bspatch.c +1 -0
- package/ios/OtaBundleResolver.swift +16 -1
- package/ios/RNBsPatch.mm +61 -154
- package/lib/commonjs/NativeBsPatch.js.map +1 -1
- package/lib/commonjs/index.js +4 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ota/OtaProvider.js +26 -22
- package/lib/commonjs/ota/OtaProvider.js.map +1 -1
- package/lib/commonjs/ota/otaAssetResolver.js +8 -2
- package/lib/commonjs/ota/otaAssetResolver.js.map +1 -1
- package/lib/commonjs/ota/otaCore.js +89 -141
- package/lib/commonjs/ota/otaCore.js.map +1 -1
- package/lib/commonjs/ota/otaLogger.js +0 -2
- package/lib/commonjs/ota/otaLogger.js.map +1 -1
- package/lib/commonjs/ota/otaMetadata.js +18 -7
- package/lib/commonjs/ota/otaMetadata.js.map +1 -1
- package/lib/commonjs/ota/storage/GitHubRawStorage.js +3 -1
- package/lib/commonjs/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/commonjs/ota/types.js +1 -10
- package/lib/commonjs/ota/types.js.map +1 -1
- package/lib/module/NativeBsPatch.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ota/OtaProvider.js +26 -22
- package/lib/module/ota/OtaProvider.js.map +1 -1
- package/lib/module/ota/otaAssetResolver.js +8 -2
- package/lib/module/ota/otaAssetResolver.js.map +1 -1
- package/lib/module/ota/otaCore.js +88 -139
- package/lib/module/ota/otaCore.js.map +1 -1
- package/lib/module/ota/otaLogger.js +0 -2
- package/lib/module/ota/otaLogger.js.map +1 -1
- package/lib/module/ota/otaMetadata.js +18 -7
- package/lib/module/ota/otaMetadata.js.map +1 -1
- package/lib/module/ota/storage/GitHubRawStorage.js +3 -1
- package/lib/module/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/module/ota/types.js +0 -9
- package/lib/module/ota/types.js.map +1 -1
- package/lib/typescript/NativeBsPatch.d.ts +3 -15
- package/lib/typescript/NativeBsPatch.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ota/OtaProvider.d.ts.map +1 -1
- package/lib/typescript/ota/otaAssetResolver.d.ts.map +1 -1
- package/lib/typescript/ota/otaCore.d.ts +5 -42
- package/lib/typescript/ota/otaCore.d.ts.map +1 -1
- package/lib/typescript/ota/otaLogger.d.ts +1 -3
- package/lib/typescript/ota/otaLogger.d.ts.map +1 -1
- package/lib/typescript/ota/otaMetadata.d.ts +2 -2
- package/lib/typescript/ota/otaMetadata.d.ts.map +1 -1
- package/lib/typescript/ota/types.d.ts +18 -54
- package/lib/typescript/ota/types.d.ts.map +1 -1
- package/package.json +3 -6
- package/react-native-bspatch.podspec +0 -1
- package/src/NativeBsPatch.ts +3 -16
- package/src/index.ts +1 -1
- package/src/ota/OtaProvider.tsx +24 -23
- package/src/ota/otaAssetResolver.ts +5 -2
- package/src/ota/otaCore.ts +118 -176
- package/src/ota/otaLogger.ts +0 -2
- package/src/ota/otaMetadata.ts +18 -8
- package/src/ota/storage/GitHubRawStorage.ts +1 -1
- package/src/ota/types.ts +18 -57
- package/tools/build_ota.sh +356 -177
- package/tools/setup_project.js +95 -11
- package/tools/update_registry.js +24 -28
- package/tools/{upload_ota_custom.sh → upload_ota.sh} +33 -114
- package/lib/commonjs/crypto-js.d.js +0 -2
- package/lib/commonjs/crypto-js.d.js.map +0 -1
- package/lib/commonjs/ota/storage/CustomStorage.js +0 -160
- package/lib/commonjs/ota/storage/CustomStorage.js.map +0 -1
- package/lib/commonjs/ota/storage/GitHubReleaseStorage.js +0 -99
- package/lib/commonjs/ota/storage/GitHubReleaseStorage.js.map +0 -1
- package/lib/module/crypto-js.d.js +0 -2
- package/lib/module/crypto-js.d.js.map +0 -1
- package/lib/module/ota/storage/CustomStorage.js +0 -154
- package/lib/module/ota/storage/CustomStorage.js.map +0 -1
- package/lib/module/ota/storage/GitHubReleaseStorage.js +0 -93
- package/lib/module/ota/storage/GitHubReleaseStorage.js.map +0 -1
- package/lib/typescript/ota/storage/CustomStorage.d.ts +0 -12
- package/lib/typescript/ota/storage/CustomStorage.d.ts.map +0 -1
- package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts +0 -20
- package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts.map +0 -1
- package/src/crypto-js.d.ts +0 -1
- package/src/ota/storage/CustomStorage.ts +0 -166
- package/src/ota/storage/GitHubReleaseStorage.ts +0 -103
- package/tools/bsdiff +0 -0
package/tools/build_ota.sh
CHANGED
|
@@ -30,10 +30,7 @@ fi
|
|
|
30
30
|
|
|
31
31
|
if [ -n "$ENV_FILE" ]; then
|
|
32
32
|
echo "Loading environment variables from $(basename "$ENV_FILE")..."
|
|
33
|
-
|
|
34
|
-
# Debug: verify loaded variables
|
|
35
|
-
echo " Loaded OTA_UPLOAD_USER=${OTA_UPLOAD_USER:+[SET]}"
|
|
36
|
-
echo " Loaded OTA_UPLOAD_PASSWORD=${OTA_UPLOAD_PASSWORD:+[SET]}"
|
|
33
|
+
set -a; . "$ENV_FILE"; set +a
|
|
37
34
|
else
|
|
38
35
|
echo " No .env file found in $ROOT or $MODULE_DIR"
|
|
39
36
|
fi
|
|
@@ -45,20 +42,41 @@ PLATFORM_ARG=""
|
|
|
45
42
|
APP_VERSION_ARG=""
|
|
46
43
|
NON_INTERACTIVE=false
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
SEED_APK=""
|
|
46
|
+
SEED_IPA=""
|
|
47
|
+
SEED_BUNDLE=""
|
|
48
|
+
FORCE_BASE=false
|
|
49
|
+
|
|
49
50
|
# Arg parsing
|
|
50
51
|
while [[ "$#" -gt 0 ]]; do
|
|
51
52
|
case $1 in
|
|
52
53
|
--platform) PLATFORM_ARG="$2"; shift ;;
|
|
53
54
|
--flavor) FLAVOR="$2"; shift ;;
|
|
54
55
|
--app-version) APP_VERSION_ARG="$2"; shift ;;
|
|
56
|
+
--apk) SEED_APK="$2"; ACTION="seed"; shift ;;
|
|
57
|
+
--ipa) SEED_IPA="$2"; ACTION="seed"; shift ;;
|
|
58
|
+
--bundle) SEED_BUNDLE="$2"; ACTION="seed"; shift ;;
|
|
59
|
+
--force) FORCE_BASE=true ;;
|
|
55
60
|
-y|--yes|--non-interactive) NON_INTERACTIVE=true ;;
|
|
56
61
|
--push) PUSH_REMOTE=true ;;
|
|
57
62
|
reset|base) ACTION="$1" ;;
|
|
58
63
|
setup) ACTION="$1" ;;
|
|
59
|
-
|
|
64
|
+
seed) ACTION="seed" ;;
|
|
65
|
+
apk|ipa) ACTION="base" ;; # Compatibility for legacy actions
|
|
60
66
|
dev|staging|production) FLAVOR="$1" ;; # Support positional flavor
|
|
61
67
|
[0-9]*) ;; # Ignore version if passed as $1 (pos 1 usually)
|
|
68
|
+
*)
|
|
69
|
+
if [[ "$1" == *.apk ]] || [[ "$1" == *.aab ]]; then
|
|
70
|
+
SEED_APK="$1"
|
|
71
|
+
ACTION="seed"
|
|
72
|
+
elif [[ "$1" == *.ipa ]]; then
|
|
73
|
+
SEED_IPA="$1"
|
|
74
|
+
ACTION="seed"
|
|
75
|
+
elif [[ "$1" == *.hbc ]] || [[ "$1" == *.bundle ]] || [[ "$1" == *.jsbundle ]]; then
|
|
76
|
+
SEED_BUNDLE="$1"
|
|
77
|
+
ACTION="seed"
|
|
78
|
+
fi
|
|
79
|
+
;;
|
|
62
80
|
esac
|
|
63
81
|
shift
|
|
64
82
|
done
|
|
@@ -86,156 +104,65 @@ mkdir -p "$ROOT/ios/output"
|
|
|
86
104
|
REGISTRY_SHARED="$ROOT/ota_registry.json"
|
|
87
105
|
|
|
88
106
|
# ── Registry Sync ──────────────────────────────────────────────────────────
|
|
89
|
-
#
|
|
90
|
-
if [ "$ACTION" != "
|
|
107
|
+
# Reset syncs too: it edits the latest remote registry instead of overwriting it with a stale local copy
|
|
108
|
+
if [ "$ACTION" != "setup" ]; then
|
|
91
109
|
echo "Syncing remote registry..."
|
|
92
110
|
|
|
93
|
-
BASE_URL="${OTA_BASE_URL:-
|
|
94
|
-
UPCODE_USER="${OTA_UPLOAD_USER:-}"
|
|
95
|
-
UPCODE_PASS="${OTA_UPLOAD_PASSWORD:-}"
|
|
111
|
+
BASE_URL="${OTA_BASE_URL:-}"
|
|
96
112
|
BASE_FOLDER="${OTA_FOLDER:-ota}"
|
|
97
113
|
|
|
98
114
|
echo " Using URL: $BASE_URL"
|
|
99
115
|
echo " Using Folder: $BASE_FOLDER"
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-H "Accept: application/vnd.github.v3+json" \
|
|
132
|
-
"$REGISTRY_API_URL" > "$API_HTTP_FILE" 2>/dev/null || true
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
API_HTTP=$(cat "$API_HTTP_FILE" 2>/dev/null || echo "0")
|
|
136
|
-
rm -f "$API_HTTP_FILE"
|
|
137
|
-
|
|
138
|
-
if [[ "$API_HTTP" = "200" ]]; then
|
|
139
|
-
# Decode base64 content returned by the GitHub Contents API in one python3 pass
|
|
140
|
-
python3 - "$API_BODY_FILE" "$REGISTRY_SHARED.tmp" << 'PYEOF'
|
|
141
|
-
import sys, json, base64
|
|
142
|
-
body_file, out_file = sys.argv[1], sys.argv[2]
|
|
143
|
-
try:
|
|
144
|
-
with open(body_file, 'r') as f:
|
|
145
|
-
d = json.load(f)
|
|
146
|
-
content = d.get('content', '')
|
|
147
|
-
decoded = base64.b64decode(content.replace('\n', ''))
|
|
148
|
-
with open(out_file, 'wb') as f:
|
|
149
|
-
f.write(decoded)
|
|
150
|
-
except Exception as e:
|
|
151
|
-
sys.exit(1)
|
|
152
|
-
PYEOF
|
|
153
|
-
rm -f "$API_BODY_FILE"
|
|
154
|
-
|
|
155
|
-
if [ -s "$REGISTRY_SHARED.tmp" ]; then
|
|
156
|
-
mv "$REGISTRY_SHARED.tmp" "$REGISTRY_SHARED"
|
|
157
|
-
echo " ✓ Remote registry synced (via GitHub API)"
|
|
158
|
-
else
|
|
159
|
-
rm -f "$REGISTRY_SHARED.tmp"
|
|
160
|
-
echo " Notice: Remote registry empty or decode failed (starting fresh with local state)."
|
|
161
|
-
fi
|
|
162
|
-
else
|
|
163
|
-
rm -f "$API_BODY_FILE" "$REGISTRY_SHARED.tmp"
|
|
164
|
-
echo " Notice: Remote registry not found on GitHub API (HTTP $API_HTTP — starting fresh with local state)."
|
|
165
|
-
fi
|
|
166
|
-
|
|
117
|
+
# GitHub is the only storage backend for now
|
|
118
|
+
if [[ "$BASE_URL" != "https://raw.githubusercontent.com/"* ]]; then
|
|
119
|
+
echo " ❌ OTA_BASE_URL must be https://raw.githubusercontent.com/<owner>/<repo>/<branch>"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
echo " Downloading remote registry from GitHub..."
|
|
123
|
+
|
|
124
|
+
CLEAN_URL="${BASE_URL#https://raw.githubusercontent.com/}"
|
|
125
|
+
GH_OWNER=$(echo "$CLEAN_URL" | cut -d'/' -f1)
|
|
126
|
+
GH_REPO=$(echo "$CLEAN_URL" | cut -d'/' -f2)
|
|
127
|
+
GH_BRANCH=$(echo "$CLEAN_URL" | cut -d'/' -f3)
|
|
128
|
+
if [[ -z "$GH_BRANCH" ]]; then GH_BRANCH="main"; fi
|
|
129
|
+
|
|
130
|
+
# Contents API (not the raw CDN) always returns the latest commit; the raw
|
|
131
|
+
# media type has no 1 MB limit and needs no base64 decoding.
|
|
132
|
+
GH_API_TOKEN="$(gh auth token 2>/dev/null || true)"
|
|
133
|
+
GH_API_TOKEN="${GH_API_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
|
|
134
|
+
GH_API_AUTH=()
|
|
135
|
+
if [[ -n "$GH_API_TOKEN" ]]; then GH_API_AUTH=(-H "Authorization: Bearer $GH_API_TOKEN"); fi
|
|
136
|
+
|
|
137
|
+
API_HTTP=$(curl -s -o "$REGISTRY_SHARED.tmp" -w "%{http_code}" "${GH_API_AUTH[@]}" \
|
|
138
|
+
-H "Accept: application/vnd.github.raw" \
|
|
139
|
+
"https://api.github.com/repos/${GH_OWNER}/${GH_REPO}/contents/${BASE_FOLDER}/ota_registry.json?ref=${GH_BRANCH}" || echo 0)
|
|
140
|
+
|
|
141
|
+
if [[ "$API_HTTP" == "200" ]] && node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$REGISTRY_SHARED.tmp" 2>/dev/null; then
|
|
142
|
+
mv "$REGISTRY_SHARED.tmp" "$REGISTRY_SHARED"
|
|
143
|
+
echo " ✓ Remote registry synced (via GitHub API)"
|
|
144
|
+
elif [[ "$API_HTTP" == "404" ]]; then
|
|
145
|
+
rm -f "$REGISTRY_SHARED.tmp"
|
|
146
|
+
echo " Notice: No remote registry yet (HTTP 404) — starting fresh with local state."
|
|
167
147
|
else
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-d "{\"username\": \"$UPCODE_USER\", \"password\": \"$UPCODE_PASS\"}")
|
|
173
|
-
HTTP_STATUS=$(echo "$LOGIN_RES" | grep -o 'HTTP_STATUS:[0-9]*' | cut -d: -f2)
|
|
174
|
-
LOGIN_BODY=$(echo "$LOGIN_RES" | sed 's/---HTTP_STATUS:.*//')
|
|
175
|
-
|
|
176
|
-
TOKEN=$(echo "$LOGIN_BODY" | node -e "
|
|
177
|
-
try {
|
|
178
|
-
const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
179
|
-
console.log(r.data?.accessTokenInfo?.accessToken || '');
|
|
180
|
-
} catch(e) { console.log(''); }
|
|
181
|
-
")
|
|
182
|
-
|
|
183
|
-
if [[ -n "$TOKEN" ]]; then
|
|
184
|
-
REGISTRY_REMOTE_PATH="$BASE_FOLDER/ota_registry.json"
|
|
185
|
-
ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REGISTRY_REMOTE_PATH', safe=''))")
|
|
186
|
-
GEN_LINK_URL="$BASE_URL/api/minio/gen-download-link?pathFile=$ENCODED_PATH"
|
|
187
|
-
LINK_RES=$(curl -s -X "GET" "$GEN_LINK_URL" -H "Authorization: Bearer $TOKEN")
|
|
188
|
-
REGISTRY_URL=$(echo "$LINK_RES" | node -e "
|
|
189
|
-
try {
|
|
190
|
-
const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
191
|
-
console.log(r.data || '');
|
|
192
|
-
} catch(e) { console.log(''); }
|
|
193
|
-
")
|
|
194
|
-
if [[ -n "$REGISTRY_URL" ]]; then
|
|
195
|
-
curl -s -o "$REGISTRY_SHARED" "$REGISTRY_URL"
|
|
196
|
-
echo " ✓ Registry synced"
|
|
197
|
-
fi
|
|
198
|
-
else
|
|
199
|
-
echo " ⚠ MinIO login skipped. Proceeding with local state."
|
|
200
|
-
fi
|
|
201
|
-
else
|
|
202
|
-
echo " ⚠ No MinIO credentials. Proceeding with local state."
|
|
203
|
-
fi
|
|
148
|
+
# Continuing would later push a stale local registry over the remote one
|
|
149
|
+
rm -f "$REGISTRY_SHARED.tmp"
|
|
150
|
+
echo " ❌ Registry sync failed (HTTP $API_HTTP). Check network / gh auth and retry."
|
|
151
|
+
exit 1
|
|
204
152
|
fi
|
|
205
153
|
fi
|
|
206
154
|
|
|
207
|
-
# ── Helper: Download from
|
|
208
|
-
|
|
155
|
+
# ── Helper: Download from remote storage ───────────────────────────────────────
|
|
156
|
+
download_remote() {
|
|
209
157
|
local REMOTE_PATH="$1"
|
|
210
158
|
local DEST="$2"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return $?
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
if [[ -z "$TOKEN" ]]; then return 1; fi
|
|
221
|
-
|
|
222
|
-
local ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REMOTE_PATH', safe=''))")
|
|
223
|
-
local GEN_LINK_URL="$BASE_URL/api/minio/gen-download-link?pathFile=$ENCODED_PATH"
|
|
224
|
-
local LINK_RES=$(curl -s -X "GET" "$GEN_LINK_URL" -H "Authorization: Bearer $TOKEN")
|
|
225
|
-
local FILE_URL=$(echo "$LINK_RES" | node -e "
|
|
226
|
-
try {
|
|
227
|
-
const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
228
|
-
console.log(r.data || '');
|
|
229
|
-
} catch(e) { console.log(''); }
|
|
230
|
-
")
|
|
231
|
-
if [[ -n "$FILE_URL" ]]; then
|
|
232
|
-
# Use -f to return non-zero exit code on HTTP errors (like 404 Not Found)
|
|
233
|
-
curl -s -f -o "$DEST" "$FILE_URL"
|
|
234
|
-
if [ $? -eq 0 ]; then
|
|
235
|
-
return 0
|
|
236
|
-
fi
|
|
159
|
+
# no-cache + timestamp query bypasses the GitHub CDN 5-min cache
|
|
160
|
+
if curl -s -f -H "Cache-Control: no-cache" -o "$DEST.tmp" "$BASE_URL/$REMOTE_PATH?cb=$(date +%s)"; then
|
|
161
|
+
mv "$DEST.tmp" "$DEST"
|
|
162
|
+
else
|
|
163
|
+
rm -f "$DEST.tmp"
|
|
164
|
+
return 1
|
|
237
165
|
fi
|
|
238
|
-
return 1
|
|
239
166
|
}
|
|
240
167
|
|
|
241
168
|
# ── Helper: compute SHA-256 hash ──────────────────────────────────────────────
|
|
@@ -368,17 +295,19 @@ if [ "$ACTION" = "reset" ]; then
|
|
|
368
295
|
echo '{"apps":{}}' > "$REGISTRY_SHARED"
|
|
369
296
|
fi
|
|
370
297
|
|
|
371
|
-
# Delete only
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
298
|
+
# Delete only this app version + flavor. Base bundles (v1 = store binary) are kept:
|
|
299
|
+
# the next build re-registers them automatically. Delete them by hand to re-seed.
|
|
300
|
+
for D in "$ROOT/android/output" "$ROOT/ios/output"; do
|
|
301
|
+
find "$D" -maxdepth 1 -type f -name "*_${APP_VERSION}_*" -name "*_${FLAVOR_LOWER}[._]*" \
|
|
302
|
+
-not -name "bundle_${APP_VERSION}_v1_*" -delete 2>/dev/null || true
|
|
303
|
+
find "$D" -maxdepth 1 -type f -name "asset-manifest-v*_${FLAVOR_LOWER}.json" -delete 2>/dev/null || true # legacy names
|
|
304
|
+
done
|
|
376
305
|
|
|
377
|
-
echo " ✓ Registry and output
|
|
306
|
+
echo " ✓ Registry and output cleared for $APP_VERSION/$FLAVOR_LOWER (base bundle v1 + base-assets kept)"
|
|
378
307
|
|
|
379
308
|
if [ "$PUSH_REMOTE" = true ]; then
|
|
380
309
|
echo " Syncing reset state for flavor '$FLAVOR_LOWER' to remote storage..."
|
|
381
|
-
bash "$SCRIPT_DIR/
|
|
310
|
+
bash "$SCRIPT_DIR/upload_ota.sh" "$APP_VERSION" "$FLAVOR_LOWER" "0" "" "ios" "--clean-remote"
|
|
382
311
|
fi
|
|
383
312
|
exit 0
|
|
384
313
|
fi
|
|
@@ -397,23 +326,44 @@ if [ ! -f "$BSDIFF" ]; then
|
|
|
397
326
|
echo " ✓ bsdiff compiled"
|
|
398
327
|
fi
|
|
399
328
|
|
|
329
|
+
find_hermesc() {
|
|
330
|
+
HERMESC_PATH="$ROOT/node_modules/hermes-compiler/hermesc/osx-bin/hermesc"
|
|
331
|
+
if [ ! -f "$HERMESC_PATH" ]; then
|
|
332
|
+
HERMESC_PATH="$ROOT/node_modules/react-native/sdks/hermesc/osx-bin/hermesc"
|
|
333
|
+
fi
|
|
334
|
+
if [ ! -f "$HERMESC_PATH" ]; then
|
|
335
|
+
HERMESC_PATH="$ROOT/node_modules/hermes-compiler/hermesc/linux64-bin/hermesc"
|
|
336
|
+
fi
|
|
337
|
+
if [ ! -f "$HERMESC_PATH" ]; then
|
|
338
|
+
HERMESC_PATH="$ROOT/node_modules/react-native/sdks/hermesc/linux64-bin/hermesc"
|
|
339
|
+
fi
|
|
340
|
+
if [ ! -f "$HERMESC_PATH" ]; then
|
|
341
|
+
HERMESC_PATH="$(which hermesc 2>/dev/null || echo "")"
|
|
342
|
+
fi
|
|
343
|
+
}
|
|
344
|
+
|
|
400
345
|
build_platform() {
|
|
401
346
|
local PLATFORM="$1"
|
|
402
347
|
local FLAVOR="$2"
|
|
403
348
|
local CURRENT="$3"
|
|
404
349
|
local NEXT="$4"
|
|
405
|
-
local FLAVOR_CAP=$(echo "$FLAVOR" | perl -ne 'print ucfirst')
|
|
406
350
|
local FLAVOR_LOWER=$(echo "$FLAVOR" | tr '[:upper:]' '[:lower:]')
|
|
407
|
-
|
|
351
|
+
if [ "$FLAVOR_LOWER" = "dev" ]; then FLAVOR_LOWER="development"; fi
|
|
352
|
+
if [ "$FLAVOR_LOWER" = "prod" ]; then FLAVOR_LOWER="production"; fi
|
|
353
|
+
local FLAVOR_CAP=$(echo "$FLAVOR_LOWER" | perl -ne 'print ucfirst')
|
|
354
|
+
|
|
408
355
|
local OUTPUT="$ROOT/android/output"
|
|
409
356
|
if [ "$PLATFORM" == "ios" ]; then OUTPUT="$ROOT/ios/output"; fi
|
|
410
357
|
local REGISTRY="$REGISTRY_SHARED"
|
|
411
358
|
|
|
412
359
|
echo ""
|
|
413
360
|
echo ">>> Building $PLATFORM | Version: $APP_VERSION | OTA: v$CURRENT → v$NEXT"
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
361
|
+
if [ "$PLATFORM" == "ios" ]; then
|
|
362
|
+
local IOS_VER=$(grep -ho 'MARKETING_VERSION = [^;]*' "$ROOT"/ios/*.xcodeproj/project.pbxproj 2>/dev/null | head -n 1 | sed 's/.*= //')
|
|
363
|
+
if [ -n "$IOS_VER" ] && [ "$IOS_VER" != "$APP_VERSION" ]; then
|
|
364
|
+
echo " ⚠ iOS MARKETING_VERSION is $IOS_VER but building for $APP_VERSION — iOS clients on $IOS_VER won't see this OTA"
|
|
365
|
+
fi
|
|
366
|
+
fi
|
|
417
367
|
|
|
418
368
|
# ── Bundle ──
|
|
419
369
|
# Workspace for building
|
|
@@ -427,7 +377,15 @@ build_platform() {
|
|
|
427
377
|
cd "$ROOT"
|
|
428
378
|
BUNDLE_SOURCE="$ROOT/android/app/build/generated/assets/react/${FLAVOR_LOWER}Release/index.android.bundle"
|
|
429
379
|
else
|
|
430
|
-
|
|
380
|
+
local XCODE_STALE=""
|
|
381
|
+
if [ -f "$BUNDLE_XCODE_IOS" ]; then
|
|
382
|
+
# ponytail: mtime check, any source edit/checkout after the last Xcode build marks it stale
|
|
383
|
+
XCODE_STALE=$(find "$ROOT/src" "$ROOT/App.tsx" "$ROOT/index.js" "$ROOT/package.json" -type f -newer "$BUNDLE_XCODE_IOS" -print -quit 2>/dev/null)
|
|
384
|
+
if [ -n "$XCODE_STALE" ] && [ "$ACTION" = "base" ]; then
|
|
385
|
+
echo " ⚠ Xcode bundle ($(stat -f '%Sm' "$BUNDLE_XCODE_IOS")) is older than $XCODE_STALE — ignoring it"
|
|
386
|
+
fi
|
|
387
|
+
fi
|
|
388
|
+
if [ -f "$BUNDLE_XCODE_IOS" ] && [ -z "$XCODE_STALE" ] && [ "$ACTION" = "base" ] && [ $CURRENT -eq 0 ]; then
|
|
431
389
|
# Base action: use Xcode-compiled bundle (already Hermes-compiled, matches IPA exactly)
|
|
432
390
|
echo " Using Xcode bundle as base source..."
|
|
433
391
|
cp "$BUNDLE_XCODE_IOS" "$WORKSPACE/source.bundle"
|
|
@@ -446,10 +404,7 @@ build_platform() {
|
|
|
446
404
|
fi
|
|
447
405
|
|
|
448
406
|
# Ensure Hermes bytecode format (for both iOS and Android OTA bundles)
|
|
449
|
-
|
|
450
|
-
if [ ! -f "$HERMESC_PATH" ]; then
|
|
451
|
-
HERMESC_PATH="$ROOT/node_modules/react-native/sdks/hermesc/osx-bin/hermesc"
|
|
452
|
-
fi
|
|
407
|
+
find_hermesc
|
|
453
408
|
|
|
454
409
|
IS_HBC=false
|
|
455
410
|
if [ -f "$BUNDLE_SOURCE" ]; then
|
|
@@ -461,7 +416,11 @@ build_platform() {
|
|
|
461
416
|
echo " Bundle is already in Hermes bytecode format."
|
|
462
417
|
else
|
|
463
418
|
echo " Compiling to Hermes bytecode..."
|
|
464
|
-
"$HERMESC_PATH"
|
|
419
|
+
if [ -z "$HERMESC_PATH" ] || [ ! -f "$HERMESC_PATH" ]; then
|
|
420
|
+
echo " ❌ ERROR: hermesc not found"; exit 1
|
|
421
|
+
fi
|
|
422
|
+
# -O matches what Xcode/Gradle use for release, keeping patches against the store bundle small
|
|
423
|
+
"$HERMESC_PATH" -emit-binary -O -out "$WORKSPACE/source.hbc" "$BUNDLE_SOURCE"
|
|
465
424
|
mv "$WORKSPACE/source.hbc" "$BUNDLE_SOURCE"
|
|
466
425
|
fi
|
|
467
426
|
|
|
@@ -486,7 +445,7 @@ build_platform() {
|
|
|
486
445
|
fi
|
|
487
446
|
|
|
488
447
|
# ── Assets ──
|
|
489
|
-
local ASSET_MANIFEST_FILE="$OUTPUT/asset-
|
|
448
|
+
local ASSET_MANIFEST_FILE="$OUTPUT/asset-manifest_${APP_VERSION}_v${NEXT}_${PLATFORM}_${FLAVOR_LOWER}.json"
|
|
490
449
|
local BASE_ASSET_MANIFEST="$OUTPUT/base-assets_${PLATFORM}_${FLAVOR_LOWER}.json"
|
|
491
450
|
local ASSET_STORE_DIR="$OUTPUT/asset-store"
|
|
492
451
|
mkdir -p "$ASSET_STORE_DIR"
|
|
@@ -494,20 +453,16 @@ build_platform() {
|
|
|
494
453
|
if [ "$PLATFORM" == "ios" ]; then ASSETS_SRC_DIR="$OUTPUT/assets"; fi
|
|
495
454
|
|
|
496
455
|
# Load base assets — try server if missing locally (e.g. after ota:clean)
|
|
497
|
-
if [ ! -f "$BASE_ASSET_MANIFEST" ]
|
|
456
|
+
if [ ! -f "$BASE_ASSET_MANIFEST" ]; then
|
|
498
457
|
local REMOTE_BASE_ASSET_PATH="$BASE_FOLDER/$APP_VERSION/$FLAVOR_LOWER/$(basename "$BASE_ASSET_MANIFEST")"
|
|
499
458
|
echo " Base asset manifest missing locally, downloading from server..."
|
|
500
|
-
|
|
459
|
+
download_remote "$REMOTE_BASE_ASSET_PATH" "$BASE_ASSET_MANIFEST" || true
|
|
501
460
|
if [ -f "$BASE_ASSET_MANIFEST" ]; then
|
|
502
461
|
echo " ✓ base-assets manifest restored from server"
|
|
503
462
|
else
|
|
504
463
|
echo " ⚠ base-assets manifest not found on server — all assets will be treated as new"
|
|
505
464
|
fi
|
|
506
465
|
fi
|
|
507
|
-
local BASE_ASSETS_JSON="{}"
|
|
508
|
-
if [ -f "$BASE_ASSET_MANIFEST" ]; then
|
|
509
|
-
BASE_ASSETS_JSON=$(cat "$BASE_ASSET_MANIFEST")
|
|
510
|
-
fi
|
|
511
466
|
|
|
512
467
|
local ASSET_JSON_LIST="["
|
|
513
468
|
local FIRST=true
|
|
@@ -526,12 +481,10 @@ build_platform() {
|
|
|
526
481
|
NEW_BASE_ASSETS+="\"$REL_PATH\": \"$HASH\""
|
|
527
482
|
|
|
528
483
|
# Optimization: Skip if hash matches the base APK version (client already has it)
|
|
529
|
-
local IS_IN_BASE
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
} catch(e) { console.log('false'); }
|
|
534
|
-
")
|
|
484
|
+
local IS_IN_BASE=false
|
|
485
|
+
if [ -f "$BASE_ASSET_MANIFEST" ] && grep -qF "\"$REL_PATH\": \"$HASH\"" "$BASE_ASSET_MANIFEST"; then
|
|
486
|
+
IS_IN_BASE=true
|
|
487
|
+
fi
|
|
535
488
|
# Always add ALL assets to the manifest so the resolver can find them at runtime.
|
|
536
489
|
# Only skip the file copy if the asset is unchanged from base (avoids redundant uploads).
|
|
537
490
|
local ASSET_IS_BASE="false"
|
|
@@ -563,13 +516,16 @@ EOF
|
|
|
563
516
|
local NEW_HASH=$(sha256 "$NEW_BUNDLE_PATH")
|
|
564
517
|
if [ $CURRENT -gt 0 ]; then
|
|
565
518
|
PATCHES_JSON="["
|
|
519
|
+
# Patches from v1 (fresh store installs) + the last 5 versions. Older clients
|
|
520
|
+
# fall back to the full bundle instead of bsdiff-ing every version each build.
|
|
566
521
|
for (( i=1; i<$NEXT; i++ )); do
|
|
522
|
+
if [ $i -gt 1 ] && [ $i -lt $((NEXT - 5)) ]; then continue; fi
|
|
567
523
|
local OLD_B="bundle_${APP_VERSION}_v${i}_${PLATFORM}_${FLAVOR_LOWER}.hbc"
|
|
568
524
|
local OLD_P="$OUTPUT/$OLD_B"
|
|
569
525
|
if [ ! -f "$OLD_P" ]; then
|
|
570
526
|
echo " Missing local bundle for v$i. Attempting to download from server..."
|
|
571
527
|
local REMOTE_BUNDLE_PATH="$BASE_FOLDER/$APP_VERSION/$FLAVOR_LOWER/$OLD_B"
|
|
572
|
-
|
|
528
|
+
download_remote "$REMOTE_BUNDLE_PATH" "$OLD_P" || true
|
|
573
529
|
fi
|
|
574
530
|
if [ ! -f "$OLD_P" ]; then
|
|
575
531
|
echo " ⚠ Warning: Could not find or download bundle v$i. Skipping patch generation from v$i."
|
|
@@ -585,14 +541,164 @@ EOF
|
|
|
585
541
|
|
|
586
542
|
# ── Finalize ──
|
|
587
543
|
node "$SCRIPT_DIR/update_registry.js" "$REGISTRY_SHARED" "$APP_VERSION" "$NEXT" "$PATCHES_JSON" "$PLATFORM" "$ASSET_MANIFEST_FILE" "$FLAVOR_LOWER"
|
|
588
|
-
bash "$SCRIPT_DIR/
|
|
544
|
+
bash "$SCRIPT_DIR/upload_ota.sh" "$APP_VERSION" "$FLAVOR_LOWER" "$NEXT" "$NEW_BUNDLE_PATH" "$PLATFORM"
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
seed_platform() {
|
|
548
|
+
local PLATFORM="$1"
|
|
549
|
+
local FLAVOR="$2"
|
|
550
|
+
local BINARY_PATH="$3"
|
|
551
|
+
local FLAVOR_LOWER=$(echo "$FLAVOR" | tr '[:upper:]' '[:lower:]')
|
|
552
|
+
if [ "$FLAVOR_LOWER" = "dev" ]; then FLAVOR_LOWER="development"; fi
|
|
553
|
+
if [ "$FLAVOR_LOWER" = "prod" ]; then FLAVOR_LOWER="production"; fi
|
|
554
|
+
|
|
555
|
+
local OUTPUT="$ROOT/android/output"
|
|
556
|
+
if [ "$PLATFORM" == "ios" ]; then OUTPUT="$ROOT/ios/output"; fi
|
|
557
|
+
mkdir -p "$OUTPUT"
|
|
558
|
+
|
|
559
|
+
echo ""
|
|
560
|
+
echo ">>> Seeding Base Bundle (v1) | Platform: $PLATFORM | App: $APP_VERSION | Flavor: $FLAVOR_LOWER"
|
|
561
|
+
echo " Source binary: $BINARY_PATH"
|
|
562
|
+
|
|
563
|
+
if [ ! -f "$BINARY_PATH" ]; then
|
|
564
|
+
echo " ❌ ERROR: Source binary file not found: $BINARY_PATH"
|
|
565
|
+
exit 1
|
|
566
|
+
fi
|
|
567
|
+
|
|
568
|
+
local TARGET_BUNDLE="$OUTPUT/bundle_${APP_VERSION}_v1_${PLATFORM}_${FLAVOR_LOWER}.hbc"
|
|
569
|
+
local BASE_ASSET_MANIFEST="$OUTPUT/base-assets_${PLATFORM}_${FLAVOR_LOWER}.json"
|
|
570
|
+
local ASSET_MANIFEST_FILE="$OUTPUT/asset-manifest_${APP_VERSION}_v1_${PLATFORM}_${FLAVOR_LOWER}.json"
|
|
571
|
+
local TMP_DIR=$(mktemp -d)
|
|
572
|
+
|
|
573
|
+
if [ "$PLATFORM" == "android" ]; then
|
|
574
|
+
echo " Extracting index.android.bundle from Android binary..."
|
|
575
|
+
if unzip -l "$BINARY_PATH" 2>/dev/null | grep -q "assets/index.android.bundle"; then
|
|
576
|
+
unzip -p "$BINARY_PATH" assets/index.android.bundle > "$TMP_DIR/extracted.bundle"
|
|
577
|
+
elif unzip -l "$BINARY_PATH" 2>/dev/null | grep -q "base/assets/index.android.bundle"; then
|
|
578
|
+
unzip -p "$BINARY_PATH" base/assets/index.android.bundle > "$TMP_DIR/extracted.bundle"
|
|
579
|
+
elif [[ "$BINARY_PATH" == *.bundle ]] || [[ "$BINARY_PATH" == *.hbc ]]; then
|
|
580
|
+
cp "$BINARY_PATH" "$TMP_DIR/extracted.bundle"
|
|
581
|
+
else
|
|
582
|
+
echo " ❌ ERROR: Could not find 'assets/index.android.bundle' inside $BINARY_PATH"
|
|
583
|
+
rm -rf "$TMP_DIR"
|
|
584
|
+
exit 1
|
|
585
|
+
fi
|
|
586
|
+
else
|
|
587
|
+
echo " Extracting main.jsbundle from iOS binary..."
|
|
588
|
+
local IPA_ENTRY=$(unzip -l "$BINARY_PATH" 2>/dev/null | grep -o 'Payload/[^/]*\.app/main\.jsbundle' | head -n 1)
|
|
589
|
+
if [ -n "$IPA_ENTRY" ]; then
|
|
590
|
+
unzip -p "$BINARY_PATH" "$IPA_ENTRY" > "$TMP_DIR/extracted.bundle"
|
|
591
|
+
elif unzip -l "$BINARY_PATH" 2>/dev/null | grep -q "main.jsbundle"; then
|
|
592
|
+
local ANY_BUNDLE=$(unzip -l "$BINARY_PATH" 2>/dev/null | grep -o '[^ ]*main\.jsbundle' | head -n 1)
|
|
593
|
+
unzip -p "$BINARY_PATH" "$ANY_BUNDLE" > "$TMP_DIR/extracted.bundle"
|
|
594
|
+
elif [[ "$BINARY_PATH" == *.bundle ]] || [[ "$BINARY_PATH" == *.hbc ]] || [[ "$BINARY_PATH" == *.jsbundle ]]; then
|
|
595
|
+
cp "$BINARY_PATH" "$TMP_DIR/extracted.bundle"
|
|
596
|
+
else
|
|
597
|
+
echo " ❌ ERROR: Could not find 'main.jsbundle' inside $BINARY_PATH"
|
|
598
|
+
rm -rf "$TMP_DIR"
|
|
599
|
+
exit 1
|
|
600
|
+
fi
|
|
601
|
+
fi
|
|
602
|
+
|
|
603
|
+
# Check Hermes bytecode format (0xc6 magic number)
|
|
604
|
+
FIRST_BYTE=$(od -An -N1 -t x1 "$TMP_DIR/extracted.bundle" | tr -d ' ' || echo "")
|
|
605
|
+
if [ "$FIRST_BYTE" == "c6" ]; then
|
|
606
|
+
echo " ✓ Bundle is verified Hermes bytecode format."
|
|
607
|
+
cp "$TMP_DIR/extracted.bundle" "$TARGET_BUNDLE"
|
|
608
|
+
else
|
|
609
|
+
echo " Bundle is JavaScript text. Compiling to Hermes bytecode..."
|
|
610
|
+
find_hermesc
|
|
611
|
+
if [ -z "$HERMESC_PATH" ] || [ ! -f "$HERMESC_PATH" ]; then
|
|
612
|
+
echo " ❌ ERROR: hermesc compiler not found. Cannot compile bundle to bytecode."
|
|
613
|
+
rm -rf "$TMP_DIR"
|
|
614
|
+
exit 1
|
|
615
|
+
fi
|
|
616
|
+
"$HERMESC_PATH" -emit-binary -O -out "$TARGET_BUNDLE" "$TMP_DIR/extracted.bundle"
|
|
617
|
+
fi
|
|
618
|
+
|
|
619
|
+
# Extract base assets manifest
|
|
620
|
+
echo " Scanning assets from binary for base manifest..."
|
|
621
|
+
local NEW_BASE_ASSETS="{"
|
|
622
|
+
local ASSET_JSON_LIST="["
|
|
623
|
+
local FIRST=true
|
|
624
|
+
|
|
625
|
+
if [ "$PLATFORM" == "android" ]; then
|
|
626
|
+
unzip -q -o "$BINARY_PATH" "res/drawable*/*" "res/raw/*" "base/res/drawable*/*" "base/res/raw/*" -d "$TMP_DIR/assets_unzipped" 2>/dev/null || true
|
|
627
|
+
else
|
|
628
|
+
unzip -q -o "$BINARY_PATH" "Payload/*.app/assets/*" -d "$TMP_DIR/assets_unzipped" 2>/dev/null || true
|
|
629
|
+
fi
|
|
630
|
+
|
|
631
|
+
if [ -d "$TMP_DIR/assets_unzipped" ]; then
|
|
632
|
+
while IFS= read -r -d '' ASSET_FILE; do
|
|
633
|
+
local HASH=$(sha256 "$ASSET_FILE")
|
|
634
|
+
local FNAME=$(basename "$ASSET_FILE")
|
|
635
|
+
local EX="${FNAME##*.}"
|
|
636
|
+
local REL_PATH="${ASSET_FILE#$TMP_DIR/assets_unzipped/}"
|
|
637
|
+
# Same keys as build_platform: iOS "assets/...", Android "drawable-*/..." / "raw/..."
|
|
638
|
+
REL_PATH=$(echo "$REL_PATH" | sed -E 's#^Payload/[^/]+\.app/##; s#^(base/)?res/##')
|
|
639
|
+
|
|
640
|
+
if [ "$NEW_BASE_ASSETS" != "{" ]; then NEW_BASE_ASSETS+=", "; fi
|
|
641
|
+
NEW_BASE_ASSETS+="\"$REL_PATH\": \"$HASH\""
|
|
642
|
+
|
|
643
|
+
if [ "$FIRST" = true ]; then FIRST=false; else ASSET_JSON_LIST+=", "; fi
|
|
644
|
+
ASSET_JSON_LIST+="{\"hash\": \"$HASH\", \"ext\": \"$EX\", \"originalPath\": \"$REL_PATH\", \"isBase\": true}"
|
|
645
|
+
done < <(find "$TMP_DIR/assets_unzipped" -type f -print0 2>/dev/null)
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
NEW_BASE_ASSETS+="}"
|
|
649
|
+
ASSET_JSON_LIST+="]"
|
|
650
|
+
|
|
651
|
+
echo "$NEW_BASE_ASSETS" > "$BASE_ASSET_MANIFEST"
|
|
652
|
+
cat > "$ASSET_MANIFEST_FILE" << EOF
|
|
653
|
+
{ "version": 1, "appVersion": "$APP_VERSION", "platform": "$PLATFORM", "flavor": "$FLAVOR_LOWER", "assets": $ASSET_JSON_LIST }
|
|
654
|
+
EOF
|
|
655
|
+
|
|
656
|
+
rm -rf "$TMP_DIR"
|
|
657
|
+
|
|
658
|
+
local BUNDLE_HASH=$(sha256 "$TARGET_BUNDLE")
|
|
659
|
+
echo " ✓ Base bundle v1 successfully created: $(basename "$TARGET_BUNDLE")"
|
|
660
|
+
echo " SHA-256: $BUNDLE_HASH"
|
|
661
|
+
|
|
662
|
+
# Update registry for v1
|
|
663
|
+
node "$SCRIPT_DIR/update_registry.js" "$REGISTRY_SHARED" "$APP_VERSION" "1" "[]" "$PLATFORM" "$ASSET_MANIFEST_FILE" "$FLAVOR_LOWER"
|
|
664
|
+
|
|
665
|
+
# Push if requested
|
|
666
|
+
if [ "$PUSH_REMOTE" = true ]; then
|
|
667
|
+
echo " Pushing seeded base bundle to remote..."
|
|
668
|
+
bash "$SCRIPT_DIR/upload_ota.sh" "$APP_VERSION" "$FLAVOR_LOWER" "1" "$TARGET_BUNDLE" "$PLATFORM"
|
|
669
|
+
fi
|
|
670
|
+
|
|
671
|
+
echo " ✓ Seeding complete for $PLATFORM!"
|
|
589
672
|
}
|
|
590
673
|
|
|
591
674
|
# ── Main ───────────────────────────────────────────────────────────────────
|
|
592
|
-
echo "=== OTA
|
|
675
|
+
echo "=== OTA Build Pipeline | App: $APP_VERSION | Flavor: $FLAVOR ==="
|
|
593
676
|
|
|
594
|
-
# Read current version once to coordinate across platforms
|
|
595
677
|
FLAVOR_LOWER=$(echo "$FLAVOR" | tr '[:upper:]' '[:lower:]')
|
|
678
|
+
if [ "$FLAVOR_LOWER" = "dev" ]; then FLAVOR_LOWER="development"; fi
|
|
679
|
+
if [ "$FLAVOR_LOWER" = "prod" ]; then FLAVOR_LOWER="production"; fi
|
|
680
|
+
|
|
681
|
+
# ── Seed Action ────────────────────────────────────────────────────────────
|
|
682
|
+
if [ "$ACTION" = "seed" ]; then
|
|
683
|
+
if [ -n "$SEED_APK" ]; then
|
|
684
|
+
seed_platform "android" "$FLAVOR_LOWER" "$SEED_APK"
|
|
685
|
+
elif [ -n "$SEED_IPA" ]; then
|
|
686
|
+
seed_platform "ios" "$FLAVOR_LOWER" "$SEED_IPA"
|
|
687
|
+
elif [ -n "$SEED_BUNDLE" ]; then
|
|
688
|
+
PLATFORM="${PLATFORM_ARG:-android}"
|
|
689
|
+
seed_platform "$PLATFORM" "$FLAVOR_LOWER" "$SEED_BUNDLE"
|
|
690
|
+
else
|
|
691
|
+
echo "❌ Please provide a binary file to seed:"
|
|
692
|
+
echo " ./build_ota.sh seed --apk <path-to-apk>"
|
|
693
|
+
echo " ./build_ota.sh seed --ipa <path-to-ipa>"
|
|
694
|
+
exit 1
|
|
695
|
+
fi
|
|
696
|
+
echo ""
|
|
697
|
+
echo "=== Base Seeding Complete ==="
|
|
698
|
+
exit 0
|
|
699
|
+
fi
|
|
700
|
+
|
|
701
|
+
# Read current version once to coordinate across platforms
|
|
596
702
|
CURRENT_TOTAL=0
|
|
597
703
|
if [ -f "$REGISTRY_SHARED" ]; then
|
|
598
704
|
CURRENT_TOTAL=$(node -e "
|
|
@@ -602,6 +708,79 @@ if [ -f "$REGISTRY_SHARED" ]; then
|
|
|
602
708
|
} catch(e) { console.log(0); }
|
|
603
709
|
")
|
|
604
710
|
fi
|
|
711
|
+
|
|
712
|
+
# ── Fail-Safe Guard for OTA Updates ────────────────────────────────────────
|
|
713
|
+
# If building an OTA patch but no base version (v1) exists in the registry:
|
|
714
|
+
if [ "$ACTION" = "ota" ] && [ $CURRENT_TOTAL -eq 0 ]; then
|
|
715
|
+
echo "Checking for pre-existing Base Bundle v1..."
|
|
716
|
+
|
|
717
|
+
CHECK_PLATFORMS=()
|
|
718
|
+
if [ -n "$PLATFORM_ARG" ]; then
|
|
719
|
+
CHECK_PLATFORMS=("$PLATFORM_ARG")
|
|
720
|
+
else
|
|
721
|
+
CHECK_PLATFORMS=("android" "ios")
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
ALL_FOUND=true
|
|
725
|
+
MISSING_PLATFORM=""
|
|
726
|
+
for P in "${CHECK_PLATFORMS[@]}"; do
|
|
727
|
+
P_OUTPUT="$ROOT/$P/output"
|
|
728
|
+
P_BASE_BUNDLE="$P_OUTPUT/bundle_${APP_VERSION}_v1_${P}_${FLAVOR_LOWER}.hbc"
|
|
729
|
+
|
|
730
|
+
# Check if local exists
|
|
731
|
+
if [ ! -f "$P_BASE_BUNDLE" ]; then
|
|
732
|
+
# Try downloading from remote if available
|
|
733
|
+
REMOTE_BASE_PATH="$BASE_FOLDER/$APP_VERSION/$FLAVOR_LOWER/$(basename "$P_BASE_BUNDLE")"
|
|
734
|
+
download_remote "$REMOTE_BASE_PATH" "$P_BASE_BUNDLE" || true
|
|
735
|
+
fi
|
|
736
|
+
|
|
737
|
+
if [ -f "$P_BASE_BUNDLE" ]; then
|
|
738
|
+
echo " ✓ Found Base Bundle v1 for $P: $(basename "$P_BASE_BUNDLE")"
|
|
739
|
+
# Auto-adopt base bundle in registry
|
|
740
|
+
node "$SCRIPT_DIR/update_registry.js" "$REGISTRY_SHARED" "$APP_VERSION" "1" "[]" "$P" "" "$FLAVOR_LOWER"
|
|
741
|
+
else
|
|
742
|
+
ALL_FOUND=false
|
|
743
|
+
MISSING_PLATFORM="$P"
|
|
744
|
+
fi
|
|
745
|
+
done
|
|
746
|
+
|
|
747
|
+
if [ "$ALL_FOUND" = true ]; then
|
|
748
|
+
echo " ✓ Base bundle v1 verified and registered. Proceeding with incremental OTA update..."
|
|
749
|
+
CURRENT_TOTAL=1
|
|
750
|
+
else
|
|
751
|
+
if [ "$FORCE_BASE" = true ]; then
|
|
752
|
+
echo " ⚠ Warning: --force flag detected. Creating base bundle v1 from current working tree..."
|
|
753
|
+
ACTION="base"
|
|
754
|
+
else
|
|
755
|
+
echo ""
|
|
756
|
+
echo "╔══════════════════════════════════════════════════════════════════════════════════════════╗"
|
|
757
|
+
echo "║ ❌ CANNOT BUILD OTA: MISSING BASE BUNDLE (v1) ║"
|
|
758
|
+
echo "╠══════════════════════════════════════════════════════════════════════════════════════════╣"
|
|
759
|
+
echo "║ App Version: $APP_VERSION | Flavor: $FLAVOR_LOWER | Missing: $MISSING_PLATFORM"
|
|
760
|
+
echo "║"
|
|
761
|
+
echo "║ No base bundle (v1) was found for this app version (neither locally nor on remote). ║"
|
|
762
|
+
echo "║ If an OTA update is built now, it would generate a base bundle from your current ║"
|
|
763
|
+
echo "║ working tree, which will MISMATCH the version installed on user devices and force a ║"
|
|
764
|
+
echo "║ 100% full-bundle download (wasting bandwidth and bypassing patch benefits). ║"
|
|
765
|
+
echo "║"
|
|
766
|
+
echo "║ How to resolve: ║"
|
|
767
|
+
echo "║ 1. If you already have the release APK/IPA submitted to Google Play / App Store: ║"
|
|
768
|
+
echo "║ Seed the exact base bundle from your release binary: ║"
|
|
769
|
+
echo "║ ./build_ota.sh seed --apk <path-to-apk> ║"
|
|
770
|
+
echo "║ ./build_ota.sh seed --ipa <path-to-ipa> ║"
|
|
771
|
+
echo "║"
|
|
772
|
+
echo "║ 2. If you are preparing a brand new Store release right now from this commit: ║"
|
|
773
|
+
echo "║ ./build_ota.sh base ║"
|
|
774
|
+
echo "║"
|
|
775
|
+
echo "║ 3. If you really want to force create base v1 from current code: ║"
|
|
776
|
+
echo "║ ./build_ota.sh --force ║"
|
|
777
|
+
echo "╚══════════════════════════════════════════════════════════════════════════════════════════╝"
|
|
778
|
+
echo ""
|
|
779
|
+
exit 1
|
|
780
|
+
fi
|
|
781
|
+
fi
|
|
782
|
+
fi
|
|
783
|
+
|
|
605
784
|
TARGET_OTA=$((CURRENT_TOTAL + 1))
|
|
606
785
|
|
|
607
786
|
if [ -n "$PLATFORM_ARG" ]; then
|