react-native-bspatch 3.1.0 → 3.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-bspatch",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "Premium OTA Update Solution for React Native using TurboModules and bsdiff",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -65,6 +65,23 @@ FLAVOR_LOWER=$(echo "$FLAVOR" | tr '[:upper:]' '[:lower:]')
65
65
  if [ "$FLAVOR_LOWER" = "dev" ]; then FLAVOR_LOWER="development"; fi
66
66
  if [ "$FLAVOR_LOWER" = "prod" ]; then FLAVOR_LOWER="production"; fi
67
67
 
68
+ # ── Output (exported: upload_ota.sh prints in the same layout) ────────────────
69
+ if [ -t 1 ]; then
70
+ BOLD=$'\033[1m'; DIM=$'\033[2m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RESET=$'\033[0m'
71
+ else
72
+ BOLD=""; DIM=""; GREEN=""; YELLOW=""; RESET=""
73
+ fi
74
+ # Pad to N characters, not bytes ("→" is 3 bytes, printf %-Ns counts bytes)
75
+ padr() { local LC_ALL=en_US.UTF-8; printf "%s%*s" "$1" $(( $2 > ${#1} ? $2 - ${#1} : 0 )) ""; }
76
+ info() { printf " ${DIM}%s${RESET} %s\n" "$(padr "$1" 9)" "$2"; } # " storage R2 bucket"
77
+ sub() { printf " %s %s\n" "$(padr "$1" 18)" "$2"; } # detail under a step
78
+ export BOLD DIM GREEN YELLOW RESET
79
+ export -f padr info sub
80
+ if [ "$ACTION" != "setup" ]; then
81
+ if [ "$ACTION" = "ota" ]; then TITLE="patch ${PLATFORM_ARG:-android + ios}"; else TITLE="$ACTION"; fi
82
+ printf "\n${BOLD}react-native-bspatch${RESET} ${DIM}%s · %s${RESET}\n" "$TITLE" "$FLAVOR_LOWER"
83
+ fi
84
+
68
85
  # ── Environment ──────────────────────────────────────────────────────────────
69
86
  # .env.<flavor>: OTA_BASE_URL / OTA_FOLDER, the same values react-native-config bakes into the app.
70
87
  # .env.ota: upload secrets (OTA_R2_*). Never put them in .env.<flavor>: react-native-config
@@ -72,7 +89,7 @@ if [ "$FLAVOR_LOWER" = "prod" ]; then FLAVOR_LOWER="production"; fi
72
89
  # Parsed like react-native-config (KEY=VALUE, optional quotes), never executed:
73
90
  # `APP_NAME=Baseform Dev` is valid dotenv but breaks `source`.
74
91
  load_env() {
75
- echo "Loading $(basename "$1")..."
92
+ ENV_LOADED="${ENV_LOADED:+$ENV_LOADED + }$(basename "$1")"
76
93
  local line k v
77
94
  while IFS= read -r line || [ -n "$line" ]; do
78
95
  [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=(.*)$ ]] || continue
@@ -93,6 +110,7 @@ if [ "$ACTION" != "setup" ]; then
93
110
  fi
94
111
  load_env "$ROOT/.env.$FLAVOR_LOWER"
95
112
  if [ -f "$ROOT/.env.ota" ]; then load_env "$ROOT/.env.ota"; fi
113
+ info env "$ENV_LOADED"
96
114
  fi
97
115
 
98
116
  # ── Setup (project wiring only, no build env needed) ─────────────────────────
@@ -104,7 +122,6 @@ fi
104
122
 
105
123
  # ── Build environment ──────────────────────────────────────────────────────
106
124
  if [ "$ACTION" = "ota" ]; then
107
- echo "Preparing build environment..."
108
125
  # Kill any existing Metro processes to prevent file locks
109
126
  pkill -f "react-native/local-cli/cli.js" || true
110
127
  pkill -f "metro-config/src/index.js" || true
@@ -112,8 +129,10 @@ if [ "$ACTION" = "ota" ]; then
112
129
  # Manually clear metro cache if it exists (robust vs ENOTEMPTY error)
113
130
  METRO_CACHE_DIR=$(node -e "console.log(require('os').tmpdir())")/metro-cache
114
131
  if [ -d "$METRO_CACHE_DIR" ]; then
115
- echo " Cleaning Metro cache..."
116
132
  rm -rf "$METRO_CACHE_DIR"
133
+ info metro "stopped · cache cleared"
134
+ else
135
+ info metro "stopped"
117
136
  fi
118
137
  fi
119
138
 
@@ -125,12 +144,10 @@ REGISTRY_SHARED="$ROOT/ota_registry.json"
125
144
  # ── Registry Sync ──────────────────────────────────────────────────────────
126
145
  # Reset syncs too: it edits the latest remote registry instead of overwriting it with a stale local copy
127
146
  if [ "$ACTION" != "setup" ]; then
128
- echo "Syncing remote registry..."
129
-
130
147
  BASE_URL="${OTA_BASE_URL:-}"
131
148
  BASE_FOLDER="${OTA_FOLDER:-ota}"
132
149
 
133
- echo " Storage: ${OTA_R2_BUCKET:+R2 $OTA_R2_BUCKET → }$BASE_URL/$BASE_FOLDER"
150
+ info storage "${OTA_R2_BUCKET:+R2 $OTA_R2_BUCKET → }$BASE_URL/$BASE_FOLDER"
134
151
 
135
152
  if [[ -n "${OTA_R2_BUCKET:-}" ]]; then
136
153
  R2_URL="https://${OTA_R2_ACCOUNT_ID:?set OTA_R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${OTA_R2_BUCKET}"
@@ -162,13 +179,13 @@ if [ "$ACTION" != "setup" ]; then
162
179
 
163
180
  if [[ "$API_HTTP" == "200" ]] && node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$REGISTRY_SHARED.tmp" 2>/dev/null; then
164
181
  mv "$REGISTRY_SHARED.tmp" "$REGISTRY_SHARED"
165
- echo " ✓ Remote registry synced"
182
+ info registry "synced"
166
183
  elif [[ "$API_HTTP" == "404" ]]; then
167
184
  rm -f "$REGISTRY_SHARED.tmp"
168
185
  # Remote is the source of truth: a local registry from another storage would publish
169
186
  # entries for files that don't exist here
170
187
  echo '{"apps":{}}' > "$REGISTRY_SHARED"
171
- echo " Notice: No remote registry yet (HTTP 404) — starting a new one."
188
+ info registry "new (nothing on storage yet)"
172
189
  else
173
190
  # Continuing would later push a stale local registry over the remote one
174
191
  rm -f "$REGISTRY_SHARED.tmp"
@@ -209,8 +226,19 @@ human_size() {
209
226
  file_size() { wc -c < "$1" | tr -d ' '; }
210
227
  STEP=0
211
228
  STEP_TOTAL=5
212
- step() { STEP=$((STEP + 1)); STEP_START=$SECONDS; echo ""; echo " [$STEP/$STEP_TOTAL] $*"; }
213
- step_done() { echo " ✓ $* ($((SECONDS - STEP_START))s)"; }
229
+ # One line per step: " [3/5] Assets 59 files · 1s". In a terminal the label shows
230
+ # while the step runs and step_progress updates it in place.
231
+ step_line() { printf " [%d/%d] %-16s %s" "$STEP" "$STEP_TOTAL" "$STEP_LABEL" "$1"; }
232
+ step() {
233
+ STEP=$((STEP + 1)); STEP_START=$SECONDS; STEP_LABEL="$1"
234
+ if [ -t 1 ]; then step_line "${DIM}…${RESET}"; fi
235
+ }
236
+ step_progress() { if [ -t 1 ]; then printf "\r\033[K"; step_line "${DIM}$1${RESET}"; fi; }
237
+ step_done() {
238
+ if [ -t 1 ]; then printf "\r\033[K"; fi
239
+ step_line "$1 ${DIM}· $((SECONDS - STEP_START))s${RESET}"; printf "\n"
240
+ }
241
+ step_fail() { if [ -t 1 ]; then printf "\n"; fi; } # leave the step line before error output
214
242
  export -f human_size file_size
215
243
 
216
244
  # ── Release version (native app version the OTA targets) ─────────────────────
@@ -239,10 +267,9 @@ fi
239
267
 
240
268
  # Confirm in a terminal (Enter keeps the detected version); CI / -y / --release-version skip it
241
269
  if [ -t 0 ] && [ "$CONFIRM_VERSION" = true ] && [ -z "$APP_VERSION_ARG" ]; then
242
- echo ""
243
270
  if [ "$ACTION" = "reset" ]; then PROMPT_WHAT="Release version to RESET ($FLAVOR_LOWER, android + ios)"
244
271
  else PROMPT_WHAT="Release version for ${PLATFORM_ARG:-android + ios} ($FLAVOR_LOWER)"; fi
245
- read -r -p " $PROMPT_WHAT [${APP_VERSION:-none detected}]: " INPUT_VERSION
272
+ read -r -p " ${BOLD}?${RESET} $PROMPT_WHAT [${APP_VERSION:-none detected}]: " INPUT_VERSION
246
273
  INPUT_VERSION="${INPUT_VERSION// /}"
247
274
  APP_VERSION="${INPUT_VERSION:-$APP_VERSION}"
248
275
  fi
@@ -251,15 +278,14 @@ if [ -z "$APP_VERSION" ]; then
251
278
  echo "❌ Could not read the native app version. Pass --release-version <x.y.z>."
252
279
  exit 1
253
280
  fi
254
- echo " ✓ Release version: $APP_VERSION"
281
+ info release "$APP_VERSION"
255
282
 
256
283
  # ── Reset ─────────────────────────────────────────────────────────────────────
257
284
  if [ "$ACTION" = "reset" ]; then
258
- echo "Resetting OTA registry and output for flavor '$FLAVOR_LOWER'..."
259
-
260
285
  # Reset only the specific flavor in registry JSON if registry exists
286
+ LATEST=1
261
287
  if [ -f "$REGISTRY_SHARED" ]; then
262
- node -e "
288
+ LATEST=$(node -e "
263
289
  try {
264
290
  const fs = require('fs');
265
291
  const reg = JSON.parse(fs.readFileSync('$REGISTRY_SHARED', 'utf8'));
@@ -270,8 +296,9 @@ if [ "$ACTION" = "reset" ]; then
270
296
  flavors['$FLAVOR_LOWER'] = { latestOtaVersion: flavors['$FLAVOR_LOWER'].latestOtaVersion || 1, patches: [] };
271
297
  }
272
298
  fs.writeFileSync('$REGISTRY_SHARED', JSON.stringify(reg, null, 4));
273
- } catch(e) {}
274
- "
299
+ console.log(flavors?.['$FLAVOR_LOWER']?.latestOtaVersion || 1);
300
+ } catch(e) { console.log(1); }
301
+ ")
275
302
  else
276
303
  echo '{"apps":{}}' > "$REGISTRY_SHARED"
277
304
  fi
@@ -282,20 +309,20 @@ if [ "$ACTION" = "reset" ]; then
282
309
  find "$D" -maxdepth 1 -type f -name "asset-manifest-v*_${FLAVOR_LOWER}.json" -delete 2>/dev/null || true # legacy names
283
310
  done
284
311
 
285
- echo " ✓ Registry and output cleared for $APP_VERSION/$FLAVOR_LOWER "
312
+ info local "cleared $APP_VERSION/$FLAVOR_LOWER · next OTA is v$((LATEST + 1))"
286
313
 
287
314
  if [ "$PUSH_REMOTE" = true ]; then
288
- echo " Syncing reset state for flavor '$FLAVOR_LOWER' to remote storage..."
289
315
  bash "$SCRIPT_DIR/upload_ota.sh" "$APP_VERSION" "$FLAVOR_LOWER" "0" "" "ios" "--clean-remote"
316
+ else
317
+ info remote "unchanged ${DIM}(add --push to delete on storage)${RESET}"
290
318
  fi
291
319
  exit 0
292
320
  fi
293
321
 
294
322
  # ── Ensure bsdiff compiled ───────────────────────────────────────────────────
295
323
  if [ ! -f "$BSDIFF" ]; then
296
- echo "Compiling bsdiff..."
297
324
  clang -O2 -D BSDIFF_EXECUTABLE -o "$BSDIFF" "$SCRIPT_DIR/bsdiff.c" -lbz2
298
- echo " ✓ bsdiff compiled"
325
+ info bsdiff "compiled"
299
326
  fi
300
327
 
301
328
  find_hermesc() {
@@ -328,8 +355,7 @@ build_platform() {
328
355
  if [ "$PLATFORM" == "ios" ]; then OUTPUT="$ROOT/ios/output"; fi
329
356
  local REGISTRY="$REGISTRY_SHARED"
330
357
 
331
- echo ""
332
- echo ">>> Building $PLATFORM | Version: $APP_VERSION | OTA: v$CURRENT → v$NEXT"
358
+ printf "\n${BOLD}%s${RESET} · %s · ${BOLD}v%s${RESET} ${DIM}(previous v%s)${RESET}\n" "$PLATFORM" "$APP_VERSION" "$NEXT" "$CURRENT"
333
359
  STEP=0
334
360
  local PLATFORM_START=$SECONDS
335
361
 
@@ -341,17 +367,17 @@ build_platform() {
341
367
  # Determine source bundle
342
368
  local BUILD_LOG="$WORKSPACE/bundle.log"
343
369
  if [ "$PLATFORM" == "android" ]; then
344
- step "Bundling JS (Gradle createBundle${FLAVOR_CAP}ReleaseJsAndAssets)..."
370
+ step "Bundle JS"
345
371
  # Gradle's config noise goes to a log; shown only on failure
346
372
  if ! (cd "$ROOT/android" && ./gradlew ":app:createBundle${FLAVOR_CAP}ReleaseJsAndAssets" -Pkotlin.incremental=false --quiet) > "$BUILD_LOG" 2>&1; then
347
- tail -n 40 "$BUILD_LOG"; echo " ❌ Gradle bundle failed (full log: $BUILD_LOG)"; exit 1
373
+ step_fail; tail -n 40 "$BUILD_LOG"; echo " ❌ Gradle bundle failed (full log: $BUILD_LOG)"; exit 1
348
374
  fi
349
375
  BUNDLE_SOURCE="$ROOT/android/app/build/generated/assets/react/${FLAVOR_LOWER}Release/index.android.bundle"
350
376
  else
351
- step "Bundling JS (react-native bundle)..."
377
+ step "Bundle JS"
352
378
  if ! npx react-native bundle --platform ios --dev false --entry-file index.js \
353
379
  --bundle-output "$WORKSPACE/source.bundle" --assets-dest "$OUTPUT/assets" > "$BUILD_LOG" 2>&1; then
354
- tail -n 40 "$BUILD_LOG"; echo " ❌ Bundle failed (full log: $BUILD_LOG)"; exit 1
380
+ step_fail; tail -n 40 "$BUILD_LOG"; echo " ❌ Bundle failed (full log: $BUILD_LOG)"; exit 1
355
381
  fi
356
382
  BUNDLE_SOURCE="$WORKSPACE/source.bundle"
357
383
  fi
@@ -366,12 +392,12 @@ build_platform() {
366
392
  if [ "$FIRST_BYTE" == "c6" ]; then IS_HBC=true; fi
367
393
  fi
368
394
 
369
- step "Hermes bytecode..."
370
- if [ "$IS_HBC" = true ]; then
371
- echo " already Hermes bytecode"
372
- else
395
+ step "Hermes"
396
+ local HERMES_NOTE="already bytecode"
397
+ if [ "$IS_HBC" != true ]; then
398
+ HERMES_NOTE="compiled"
373
399
  if [ -z "$HERMESC_PATH" ] || [ ! -f "$HERMESC_PATH" ]; then
374
- echo " ❌ ERROR: hermesc not found"; exit 1
400
+ step_fail; echo " ❌ ERROR: hermesc not found"; exit 1
375
401
  fi
376
402
  # -O matches what Xcode/Gradle use for release
377
403
  "$HERMESC_PATH" -emit-binary -O -out "$WORKSPACE/source.hbc" "$BUNDLE_SOURCE"
@@ -382,7 +408,7 @@ build_platform() {
382
408
  local NEW_BUNDLE_PATH="$OUTPUT/$BUNDLE_NAME"
383
409
 
384
410
  cp "$BUNDLE_SOURCE" "$NEW_BUNDLE_PATH"
385
- step_done "$BUNDLE_NAME, $(human_size "$(file_size "$NEW_BUNDLE_PATH")")"
411
+ step_done "$HERMES_NOTE"
386
412
 
387
413
  # ── Assets ──
388
414
  local ASSET_MANIFEST_FILE="$OUTPUT/asset-manifest_${APP_VERSION}_v${NEXT}_${PLATFORM}_${FLAVOR_LOWER}.json"
@@ -394,7 +420,7 @@ build_platform() {
394
420
  local ASSET_JSON_LIST="["
395
421
  local FIRST=true
396
422
  local ASSET_COUNT=0
397
- step "Hashing assets..."
423
+ step "Assets"
398
424
  if [ -d "$ASSETS_SRC_DIR" ]; then
399
425
  while IFS= read -r -d '' ASSET_FILE; do
400
426
  local HASH=$(sha256 "$ASSET_FILE")
@@ -409,12 +435,11 @@ build_platform() {
409
435
  if [ "$FIRST" = true ]; then FIRST=false; else ASSET_JSON_LIST+=", "; fi
410
436
  ASSET_JSON_LIST+="{\"hash\": \"$HASH\", \"ext\": \"$EX\", \"originalPath\": \"$REL_PATH\"}"
411
437
  ASSET_COUNT=$((ASSET_COUNT + 1))
412
- if [ -t 1 ]; then printf "\r %d assets" "$ASSET_COUNT"; fi
438
+ step_progress "$ASSET_COUNT files"
413
439
  done < <(find "$ASSETS_SRC_DIR" -type f -print0 2>/dev/null)
414
- if [ -t 1 ]; then printf "\r\033[K"; fi
415
440
  fi
416
441
  ASSET_JSON_LIST+="]"
417
- step_done "$ASSET_COUNT assets"
442
+ step_done "$ASSET_COUNT files"
418
443
 
419
444
  cat > "$ASSET_MANIFEST_FILE" << EOF
420
445
  { "version": $NEXT, "appVersion": "$APP_VERSION", "platform": "$PLATFORM", "flavor": "$FLAVOR_LOWER", "assets": $ASSET_JSON_LIST }
@@ -426,48 +451,49 @@ EOF
426
451
  local NEW_HASH=$(sha256 "$NEW_BUNDLE_PATH")
427
452
  local PATCHES_JSON="["
428
453
  local FIRST_FROM=$((NEXT - 5)); if [ $FIRST_FROM -lt 2 ]; then FIRST_FROM=2; fi
429
- local PATCH_TOTAL=$((NEXT - FIRST_FROM)) PATCH_N=0 PATCH_MADE=0
430
- if [ $PATCH_TOTAL -gt 0 ]; then
431
- step "Creating $PATCH_TOTAL patch(es) from v$FIRST_FROM..v$((NEXT - 1))..."
432
- else
433
- step "Patches: none (first OTA of this release — clients take the full bundle)"
434
- fi
454
+ local PATCH_TOTAL=$((NEXT - FIRST_FROM)) PATCH_MADE=0 PATCH_LINES=() PATCH_MISSING=""
455
+ step "Patches"
435
456
  for (( i=FIRST_FROM; i<NEXT; i++ )); do
436
- PATCH_N=$((PATCH_N + 1))
437
457
  local OLD_B="bundle_${APP_VERSION}_v${i}_${PLATFORM}_${FLAVOR_LOWER}.hbc"
438
458
  local OLD_P="$OUTPUT/$OLD_B"
439
459
  if [ ! -f "$OLD_P" ]; then
440
- echo " ($PATCH_N/$PATCH_TOTAL) v$i: downloading base bundle..."
460
+ step_progress "v$i: fetching bundle…"
441
461
  local REMOTE_BUNDLE_PATH="$BASE_FOLDER/$APP_VERSION/$FLAVOR_LOWER/$OLD_B"
442
462
  download_remote "$REMOTE_BUNDLE_PATH" "$OLD_P" || true
443
463
  fi
444
464
  if [ ! -f "$OLD_P" ]; then
445
- echo " ($PATCH_N/$PATCH_TOTAL) v$i: ⚠ bundle not found, skipped (v$i clients take the full bundle)"
465
+ PATCH_MISSING="${PATCH_MISSING:+$PATCH_MISSING, }v$i"
446
466
  continue
447
467
  fi
448
468
  local PNAME="patch_${APP_VERSION}_v${i}_to_v${NEXT}_${FLAVOR_LOWER}_${PLATFORM}.patch"
449
469
  local PATCH_START=$SECONDS
450
- if [ -t 1 ]; then printf " (%d/%d) v%d → v%d ..." "$PATCH_N" "$PATCH_TOTAL" "$i" "$NEXT"; fi
470
+ step_progress "v$i → v$NEXT…"
451
471
  "$BSDIFF" "$OLD_P" "$NEW_BUNDLE_PATH" "$OUTPUT/$PNAME"
452
- if [ -t 1 ]; then printf "\r\033[K"; fi
453
- echo " ($PATCH_N/$PATCH_TOTAL) v$i → v$NEXT: $(human_size "$(file_size "$OUTPUT/$PNAME")") ($((SECONDS - PATCH_START))s)"
472
+ PATCH_LINES+=("v$i → v$NEXT|$(human_size "$(file_size "$OUTPUT/$PNAME")") ${DIM}· $((SECONDS - PATCH_START))s${RESET}")
454
473
  PATCH_MADE=$((PATCH_MADE + 1))
455
474
  if [ "$PATCHES_JSON" != "[" ]; then PATCHES_JSON+=", "; fi
456
475
  PATCHES_JSON+="{\"from\": $i, \"to\": $NEXT, \"file\": \"$PNAME\", \"baseHash\": \"$(sha256 "$OLD_P")\", \"bundleHash\": \"$NEW_HASH\"}"
457
476
  done
458
477
  PATCHES_JSON+="]"
459
- if [ $PATCH_TOTAL -gt 0 ]; then step_done "$PATCH_MADE patch(es)"; fi
478
+ if [ $PATCH_TOTAL -eq 0 ]; then
479
+ step_done "none ${DIM}(first OTA of this release: full bundle only)${RESET}"
480
+ else
481
+ step_done "$PATCH_MADE created"
482
+ fi
483
+ local L
484
+ for L in "${PATCH_LINES[@]}"; do sub "${L%%|*}" "${L#*|}"; done
485
+ if [ -n "$PATCH_MISSING" ]; then sub "$PATCH_MISSING" "${DIM}no bundle on storage → full download${RESET}"; fi
460
486
 
461
487
  # ── Finalize ──
462
- step "Registry + upload..."
488
+ step "Publish"
489
+ if [ -t 1 ]; then printf "\r\033[K"; fi
490
+ step_line ""; printf "\n"
463
491
  node "$SCRIPT_DIR/update_registry.js" "$REGISTRY_SHARED" "$APP_VERSION" "$NEXT" "$PATCHES_JSON" "$PLATFORM" "$ASSET_MANIFEST_FILE" "$FLAVOR_LOWER"
464
492
  bash "$SCRIPT_DIR/upload_ota.sh" "$APP_VERSION" "$FLAVOR_LOWER" "$NEXT" "$NEW_BUNDLE_PATH" "$PLATFORM"
465
- echo ""
466
- echo " ✓ $PLATFORM v$NEXT published in $((SECONDS - PLATFORM_START))s"
493
+ printf "${GREEN}✓${RESET} ${BOLD}%s v%s${RESET} published ${DIM}in %ss${RESET}\n" "$PLATFORM" "$NEXT" "$((SECONDS - PLATFORM_START))"
467
494
  }
468
495
 
469
496
  # ── Main ───────────────────────────────────────────────────────────────────
470
- echo "=== OTA Build Pipeline | App: $APP_VERSION | Flavor: $FLAVOR ==="
471
497
 
472
498
  FLAVOR_LOWER=$(echo "$FLAVOR" | tr '[:upper:]' '[:lower:]')
473
499
  if [ "$FLAVOR_LOWER" = "dev" ]; then FLAVOR_LOWER="development"; fi
@@ -496,8 +522,7 @@ else
496
522
  build_platform "ios" "$FLAVOR" "$CURRENT_TOTAL" "$TARGET_OTA"
497
523
  fi
498
524
 
499
- echo ""
500
- echo "=== All Builds Complete ==="
525
+
501
526
 
502
527
 
503
528
 
@@ -6,7 +6,7 @@
6
6
  // - package.json ("version")
7
7
  // - android/app/build.gradle (versionName & increments versionCode)
8
8
  // - ios/*.xcodeproj/project.pbxproj (MARKETING_VERSION & increments CURRENT_PROJECT_VERSION)
9
- // - src/ota/otaConfig.ts (APP_VERSION, if it exists)
9
+ // JS reads the native version at runtime (getAppVersion); never hardcode it in source.
10
10
  //
11
11
  // Usage:
12
12
  // node bump_version.js <new_version> [--root <path>]
@@ -118,16 +118,4 @@ if (fs.existsSync(iosDir)) {
118
118
  }
119
119
  }
120
120
 
121
- // 4. src/ota/otaConfig.ts (if exists)
122
- const otaConfigPath = path.join(rootDir, 'src/ota/otaConfig.ts');
123
- if (fs.existsSync(otaConfigPath)) {
124
- try {
125
- let content = fs.readFileSync(otaConfigPath, 'utf8');
126
- content = content.replace(/export const APP_VERSION = ['"][^'"]+['"];/, `export const APP_VERSION = '${newVersion}';`);
127
- fs.writeFileSync(otaConfigPath, content, 'utf8');
128
- console.log(` ✓ src/ota/otaConfig.ts: APP_VERSION → '${newVersion}'`);
129
- changedCount++;
130
- } catch (e) {}
131
- }
132
-
133
121
  console.log(`\n✓ Done! Updated ${changedCount} file(s) to version ${newVersion}.\n`);
@@ -95,7 +95,6 @@ if (!signingKey) {
95
95
  }
96
96
  const signer = crypto.createSign('SHA256');
97
97
  signer.update(fs.readFileSync(filePath));
98
- console.log(` ✓ Signed ${file}`);
99
98
  return signer.sign(signingKey, 'base64');
100
99
  };
101
100
 
@@ -138,6 +137,9 @@ if (!signingKey) {
138
137
  // ── Write updated registry ───────────────────────────────────────────────────
139
138
  fs.mkdirSync(path.dirname(registryPath), { recursive: true });
140
139
  fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n');
140
+ // Same layout as build_ota.sh step details
141
+ const patchCount = newPatches.length;
141
142
  console.log(
142
- ` ✓ Registry updated | App: ${appVersion} | Flavor: ${flavor} | latestOta: ${toOtaVersion}`,
143
+ ` ${'entries'.padEnd(18)} full bundle${patchCount ? ` + ${patchCount} patch${patchCount > 1 ? 'es' : ''}` : ''}` +
144
+ (signingKey ? ' · signed' : ' · unsigned (no ota_private_key.pem)'),
143
145
  );
@@ -53,39 +53,49 @@ if [[ -n "${OTA_R2_BUCKET:-}" ]]; then
53
53
  : "${OTA_R2_ACCOUNT_ID:?set OTA_R2_ACCOUNT_ID}" "${OTA_R2_ACCESS_KEY_ID:?set OTA_R2_ACCESS_KEY_ID}" "${OTA_R2_SECRET_ACCESS_KEY:?set OTA_R2_SECRET_ACCESS_KEY}"
54
54
  R2_URL="https://${OTA_R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${OTA_R2_BUCKET}"
55
55
  r2() { curl -sS --aws-sigv4 "aws:amz:auto:s3" --user "$OTA_R2_ACCESS_KEY_ID:$OTA_R2_SECRET_ACCESS_KEY" "$@"; }
56
- r2_put() { # <file> <key> <cache-control> [label]
57
- local SIZE PROGRESS=(-s)
56
+ # "patch_1.0.4_v4_to_v7_…" → "patch v4 → v7", "bundle_…_v7_…" → "bundle v7"
57
+ pretty() {
58
+ case "$1" in
59
+ patch_*) echo "$1" | sed -E 's/.*_v([0-9]+)_to_v([0-9]+)_.*/patch v\1 → v\2/' ;;
60
+ bundle_*) echo "$1" | sed -E 's/.*_v([0-9]+)_.*/bundle v\1/' ;;
61
+ asset-manifest_*) echo "asset manifest" ;;
62
+ *) echo "$1" ;;
63
+ esac
64
+ }
65
+ r2_put() { # <file> <key> <cache-control> <label>
66
+ local SIZE PROGRESS=(-s) BAR=false
58
67
  SIZE=$(wc -c < "$1" | tr -d ' ')
59
- # Progress bar for big files (bundles) in a terminal
60
- if [ -t 2 ] && [ "$SIZE" -gt 1048576 ]; then echo " ↑ ${4:-$2}"; PROGRESS=(-#); fi
61
- curl -S "${PROGRESS[@]}" -f --aws-sigv4 "aws:amz:auto:s3" --user "$OTA_R2_ACCESS_KEY_ID:$OTA_R2_SECRET_ACCESS_KEY" \
68
+ # Progress bar for big files (bundles) in a terminal; erased once done
69
+ if [ -t 1 ] && [ "$SIZE" -gt 1048576 ]; then printf " ↑ %s\n" "$4"; PROGRESS=(-#); BAR=true; fi
70
+ COLUMNS=60 curl -S "${PROGRESS[@]}" -f --aws-sigv4 "aws:amz:auto:s3" --user "$OTA_R2_ACCESS_KEY_ID:$OTA_R2_SECRET_ACCESS_KEY" \
62
71
  -X PUT -T "$1" -H "Cache-Control: $3" "$R2_URL/$2" -o /dev/null || { echo " ❌ Upload failed: $2"; exit 1; }
63
- echo " ✓ ${4:-$2} ($(human_size "$SIZE"))"
72
+ if [ "$BAR" = true ]; then printf "\033[2A\033[K\n\033[K\033[1A"; fi
73
+ printf " ${GREEN}✓${RESET} %s %s\n" "$(padr "$4" 16)" "$(human_size "$SIZE")"
64
74
  }
65
75
  RELEASE_PREFIX="$BASE_FOLDER/$APP_VERSION/$CHANNEL"
66
76
  IMMUTABLE="public, max-age=31536000, immutable"
67
77
 
68
- echo " → Cloudflare R2: $OTA_R2_BUCKET/$RELEASE_PREFIX"
69
78
  if [ "$CLEAN_REMOTE" = true ]; then
70
- echo " Deleting $RELEASE_PREFIX/ ..."
71
79
  # ponytail: one ListObjectsV2 page (1000 keys); a release+channel holds far fewer
72
80
  LISTING=$(r2 -f "$R2_URL?list-type=2&prefix=$RELEASE_PREFIX/") || { echo " ❌ Listing $RELEASE_PREFIX/ failed"; exit 1; }
73
- DELETED=0
81
+ DELETED=0; BUNDLES=0; PATCHES=0; MANIFESTS=0
74
82
  for KEY in $(echo "$LISTING" | grep -o '<Key>[^<]*</Key>' | sed 's/<[^>]*>//g'); do
75
83
  r2 -f -X DELETE "$R2_URL/$KEY" -o /dev/null || { echo " ❌ Delete failed: $KEY"; exit 1; }
76
- echo " ✗ $KEY"; DELETED=$((DELETED + 1))
84
+ DELETED=$((DELETED + 1))
85
+ case "$KEY" in *.hbc) BUNDLES=$((BUNDLES + 1)) ;; *.patch) PATCHES=$((PATCHES + 1)) ;; *.json) MANIFESTS=$((MANIFESTS + 1)) ;; esac
86
+ if [ -t 1 ]; then printf "\r\033[K ${DIM}%-9s${RESET} deleting… %d" "remote" "$DELETED"; fi
77
87
  done
78
- echo " ✓ $DELETED object(s) deleted"
88
+ if [ -t 1 ]; then printf "\r\033[K"; fi
89
+ info remote "$DELETED object(s) deleted ${DIM}($BUNDLES bundles · $PATCHES patches · $MANIFESTS manifests)${RESET}"
79
90
  else
80
91
  # Only this version's files: earlier bundles/patches are already uploaded and never change
81
92
  RELEASE_FILES=()
82
93
  for FILE in "$OUTPUT_DIR"/*_v"${OTA_VERSION}"_*.{patch,hbc,json}; do
83
94
  [[ -f "$FILE" ]] && is_release_file "$(basename "$FILE")" && RELEASE_FILES+=("$FILE")
84
95
  done
85
- N=0
86
96
  for FILE in "${RELEASE_FILES[@]}"; do
87
- N=$((N + 1)); FNAME=$(basename "$FILE")
88
- r2_put "$FILE" "$RELEASE_PREFIX/$FNAME" "$IMMUTABLE" "($N/${#RELEASE_FILES[@]}) $FNAME"
97
+ FNAME=$(basename "$FILE")
98
+ r2_put "$FILE" "$RELEASE_PREFIX/$FNAME" "$IMMUTABLE" "$(pretty "$FNAME")"
89
99
  done
90
100
  # Content-addressed assets: upload only hashes the bucket doesn't have yet
91
101
  ASSETS=("$OUTPUT_DIR"/asset-store/*)
@@ -98,13 +108,18 @@ if [[ -n "${OTA_R2_BUCKET:-}" ]]; then
98
108
  r2 -f -X PUT -T "$FILE" -H "Cache-Control: $IMMUTABLE" "$R2_URL/$KEY" -o /dev/null || { echo " ❌ Upload failed: $KEY"; exit 1; }
99
109
  NEW=$((NEW + 1)); NEW_BYTES=$((NEW_BYTES + $(wc -c < "$FILE")))
100
110
  fi
101
- if [ -t 1 ]; then printf "\r assets %d/%d checked, %d uploaded" "$N" "${#ASSETS[@]}" "$NEW"; fi
111
+ if [ -t 1 ]; then printf "\r\033[K ${DIM}assets %d/%d checked${RESET}" "$N" "${#ASSETS[@]}"; fi
102
112
  done
103
113
  if [ -t 1 ] && [ ${#ASSETS[@]} -gt 0 ]; then printf "\r\033[K"; fi
104
- echo " ✓ assets: ${#ASSETS[@]} total, $NEW uploaded ($(human_size "$NEW_BYTES")), $((${#ASSETS[@]} - NEW)) already on R2"
114
+ printf " ${GREEN}✓${RESET} %s %s\n" "$(padr assets 16)" "${#ASSETS[@]} · $NEW new ($(human_size "$NEW_BYTES"))"
105
115
  fi
106
116
  # Registry last: clients never see a version whose files aren't uploaded yet
107
- r2_put "$REGISTRY_FILE" "$BASE_FOLDER/ota_registry.json" "no-cache" "ota_registry.json"
117
+ if [ "$CLEAN_REMOTE" = true ]; then
118
+ r2 -f -X PUT -T "$REGISTRY_FILE" -H "Cache-Control: no-cache" "$R2_URL/$BASE_FOLDER/ota_registry.json" -o /dev/null || { echo " ❌ Upload failed: ota_registry.json"; exit 1; }
119
+ info registry "uploaded"
120
+ else
121
+ r2_put "$REGISTRY_FILE" "$BASE_FOLDER/ota_registry.json" "no-cache" "registry"
122
+ fi
108
123
  exit 0
109
124
  fi
110
125