react-native-bspatch 1.0.1 → 1.0.3

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": "1.0.1",
3
+ "version": "1.0.3",
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",
@@ -44,11 +44,13 @@ FLAVOR="dev"
44
44
  ACTION="ota"
45
45
  PLATFORM_ARG=""
46
46
 
47
+ PUSH_REMOTE=false
47
48
  # Arg parsing
48
49
  while [[ "$#" -gt 0 ]]; do
49
50
  case $1 in
50
51
  --platform) PLATFORM_ARG="$2"; shift ;;
51
52
  --flavor) FLAVOR="$2"; shift ;;
53
+ --push) PUSH_REMOTE=true ;;
52
54
  reset|base) ACTION="$1" ;;
53
55
  setup) ACTION="$1" ;;
54
56
  apk|ipa|seed) ACTION="base" ;; # Compatibility for legacy/seed actions
@@ -81,86 +83,79 @@ REGISTRY_SHARED="$ROOT/ota_registry.json"
81
83
  # ── Registry Sync ──────────────────────────────────────────────────────────
82
84
  # Skip registry sync for reset/clean actions
83
85
  if [ "$ACTION" != "reset" ]; then
84
- # Download the latest ota_registry.json from server so we don't clobber other platforms
85
86
  echo "Syncing remote registry..."
86
87
 
87
- # Load credentials from environment variables (required for security)
88
- # Upload credentials are used by the build script
89
- BASE_URL="${OTA_BASE_URL:-https://sync-server.osp.com.vn}"
90
- UPCODE_USER="${OTA_UPLOAD_USER:-}"
91
- UPCODE_PASS="${OTA_UPLOAD_PASSWORD:-}"
92
- BASE_FOLDER="${OTA_FOLDER:-congplqg}"
93
-
94
- # Debug: show credentials (without exposing password)
95
- echo " Using URL: $BASE_URL"
96
- echo " Using User: $UPCODE_USER"
97
- echo " Using Folder: $BASE_FOLDER"
98
-
99
- # Validate required credentials
100
- if [[ -z "$UPCODE_USER" ]] || [[ -z "$UPCODE_PASS" ]]; then
101
- echo "ERROR: OTA upload credentials not set. Please set OTA_UPLOAD_USER and OTA_UPLOAD_PASSWORD in .env file."
102
- echo " OTA_UPLOAD_USER=\"your-username\""
103
- echo " OTA_UPLOAD_PASSWORD=\"your-password\""
104
- echo " Optional: OTA_BASE_URL OTA_FOLDER"
105
- exit 1
106
- fi
107
-
108
- # Debug: show curl command for manual testing
109
- echo " Login URL: ${BASE_URL}/public/authen/login"
110
-
111
- LOGIN_RES=$(curl -s -X "POST" "$BASE_URL/public/authen/login" \
112
- -H 'Content-Type: application/json; charset=utf-8' \
113
- -w "\n---HTTP_STATUS:%{http_code}" \
114
- -d "{\"username\": \"$UPCODE_USER\", \"password\": \"$UPCODE_PASS\"}")
115
-
116
- # Extract HTTP status and response body
117
- HTTP_STATUS=$(echo "$LOGIN_RES" | grep -o 'HTTP_STATUS:[0-9]*' | cut -d: -f2)
118
- LOGIN_BODY=$(echo "$LOGIN_RES" | sed 's/---HTTP_STATUS:.*//')
119
-
120
- # Debug: show login response on error or empty
121
- if [ -z "$LOGIN_BODY" ] || [ "$HTTP_STATUS" != "200" ]; then
122
- echo " ⚠ Login failed - HTTP $HTTP_STATUS"
123
- echo " ⚠ Response: $LOGIN_BODY"
124
- fi
125
-
126
- TOKEN=$(echo "$LOGIN_BODY" | node -e "
127
- try {
128
- const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
129
- console.log(r.data?.accessTokenInfo?.accessToken || '');
130
- } catch(e) { console.log(''); }
131
- ")
132
-
133
- if [[ -n "$TOKEN" ]]; then
134
- REGISTRY_REMOTE_PATH="$BASE_FOLDER/ota_registry.json"
135
- # Encode slash as %2F for MinIO API
136
- ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REGISTRY_REMOTE_PATH', safe=''))")
137
- GEN_LINK_URL="$BASE_URL/api/minio/gen-download-link?pathFile=$ENCODED_PATH"
138
-
139
- LINK_RES=$(curl -s -X "GET" "$GEN_LINK_URL" -H "Authorization: Bearer $TOKEN")
140
- REGISTRY_URL=$(echo "$LINK_RES" | node -e "
141
- try {
142
- const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
143
- console.log(r.data || '');
144
- } catch(e) { console.log(''); }
145
- ")
146
-
147
- if [[ -n "$REGISTRY_URL" ]]; then
148
- echo " Downloading remote registry..."
149
- curl -s -o "$REGISTRY_SHARED" "$REGISTRY_URL"
150
- echo " ✓ Registry synced"
88
+ BASE_URL="${OTA_BASE_URL:-https://sync-server.osp.com.vn}"
89
+ UPCODE_USER="${OTA_UPLOAD_USER:-}"
90
+ UPCODE_PASS="${OTA_UPLOAD_PASSWORD:-}"
91
+ BASE_FOLDER="${OTA_FOLDER:-ota}"
92
+
93
+ echo " Using URL: $BASE_URL"
94
+ echo " Using Folder: $BASE_FOLDER"
95
+
96
+ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
97
+ echo " Downloading remote registry from GitHub..."
98
+ HTTP_CODE=$(curl -s -w "%{http_code}" -o "$REGISTRY_SHARED.tmp" "$BASE_URL/$BASE_FOLDER/ota_registry.json" 2>/dev/null || true)
99
+ if [ "$HTTP_CODE" != "200" ]; then
100
+ HTTP_CODE=$(curl -s -w "%{http_code}" -o "$REGISTRY_SHARED.tmp" "$BASE_URL/ota_registry.json" 2>/dev/null || true)
101
+ fi
102
+ if [ "$HTTP_CODE" = "200" ] && [ -s "$REGISTRY_SHARED.tmp" ]; then
103
+ mv "$REGISTRY_SHARED.tmp" "$REGISTRY_SHARED"
104
+ echo " ✓ Remote registry synced"
105
+ else
106
+ rm -f "$REGISTRY_SHARED.tmp"
107
+ echo " Notice: Remote registry not found on GitHub (starting fresh with local state)."
108
+ fi
151
109
  else
152
- echo " ⚠ Remote registry not found (starting fresh)."
153
- # Do not delete existing local registry if sync fails, just proceed
110
+ if [[ -n "$UPCODE_USER" ]] && [[ -n "$UPCODE_PASS" ]]; then
111
+ LOGIN_RES=$(curl -s -X "POST" "$BASE_URL/public/authen/login" \
112
+ -H 'Content-Type: application/json; charset=utf-8' \
113
+ -w "\n---HTTP_STATUS:%{http_code}" \
114
+ -d "{\"username\": \"$UPCODE_USER\", \"password\": \"$UPCODE_PASS\"}")
115
+ HTTP_STATUS=$(echo "$LOGIN_RES" | grep -o 'HTTP_STATUS:[0-9]*' | cut -d: -f2)
116
+ LOGIN_BODY=$(echo "$LOGIN_RES" | sed 's/---HTTP_STATUS:.*//')
117
+
118
+ TOKEN=$(echo "$LOGIN_BODY" | node -e "
119
+ try {
120
+ const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
121
+ console.log(r.data?.accessTokenInfo?.accessToken || '');
122
+ } catch(e) { console.log(''); }
123
+ ")
124
+
125
+ if [[ -n "$TOKEN" ]]; then
126
+ REGISTRY_REMOTE_PATH="$BASE_FOLDER/ota_registry.json"
127
+ ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REGISTRY_REMOTE_PATH', safe=''))")
128
+ GEN_LINK_URL="$BASE_URL/api/minio/gen-download-link?pathFile=$ENCODED_PATH"
129
+ LINK_RES=$(curl -s -X "GET" "$GEN_LINK_URL" -H "Authorization: Bearer $TOKEN")
130
+ REGISTRY_URL=$(echo "$LINK_RES" | node -e "
131
+ try {
132
+ const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
133
+ console.log(r.data || '');
134
+ } catch(e) { console.log(''); }
135
+ ")
136
+ if [[ -n "$REGISTRY_URL" ]]; then
137
+ curl -s -o "$REGISTRY_SHARED" "$REGISTRY_URL"
138
+ echo " ✓ Registry synced"
139
+ fi
140
+ else
141
+ echo " ⚠ MinIO login skipped. Proceeding with local state."
142
+ fi
143
+ else
144
+ echo " ⚠ No MinIO credentials. Proceeding with local state."
145
+ fi
154
146
  fi
155
- else
156
- echo " ⚠ Login failed during registry sync. Proceeding with local state."
157
- fi
158
147
  fi
159
148
 
160
149
  # ── Helper: Download from Server ──────────────────────────────────────────────
161
150
  download_from_minio() {
162
151
  local REMOTE_PATH="$1"
163
152
  local DEST="$2"
153
+
154
+ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
155
+ curl -s -f -o "$DEST" "$BASE_URL/$REMOTE_PATH" 2>/dev/null || curl -s -f -o "$DEST" "$BASE_URL/$(basename "$REMOTE_PATH")" 2>/dev/null
156
+ return $?
157
+ fi
158
+
164
159
  if [[ -z "$TOKEN" ]]; then return 1; fi
165
160
 
166
161
  local ENCODED_PATH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REMOTE_PATH', safe=''))")
@@ -203,7 +198,7 @@ fi
203
198
  # ── Reset ─────────────────────────────────────────────────────────────────────
204
199
  if [ "$ACTION" = "reset" ]; then
205
200
  echo "Resetting OTA registry and clearing output..."
206
- rm -f "$REGISTRY_SHARED"
201
+ echo '{"apps":{}}' > "$REGISTRY_SHARED"
207
202
  # Preserve base-assets manifests so the build after reset can still
208
203
  # correctly identify which assets are already bundled in the base app.
209
204
  # Only delete bundles, patches, manifests and workspace — not base-assets.
@@ -216,6 +211,11 @@ import type { OtaPatch } from 'react-native-bspatch';
216
211
  export const OTA_PATCHES: OtaPatch[] = [];
217
212
  TSEEOF
218
213
  echo " ✓ Registry and output files cleared (base-assets preserved)"
214
+
215
+ if [ "$PUSH_REMOTE" = true ]; then
216
+ echo " Syncing reset state to remote storage..."
217
+ bash "$SCRIPT_DIR/upload_ota_custom.sh" "$APP_VERSION" "$FLAVOR_LOWER" "0" "" "ios" "--clean-remote"
218
+ fi
219
219
  exit 0
220
220
  fi
221
221
 
@@ -91,44 +91,24 @@ function patchPbxproj(content) {
91
91
  // Already patched?
92
92
  if (content.includes(XCODE_BUNDLE_SYNC_MARKER)) return content;
93
93
 
94
- // Find the Bundle React Native code and images shellScript value
95
- // Pattern: name = "Bundle React Native code and images"; ... shellScript = "...";
96
- // The shellScript ends with \n\";\n — we inject before the last closing \n\";
97
- const shellScriptRegex = /(00DD1BFF1BD5951E006B06BC[^;]*?shellScript = ")([\s\S]*?)(\s*";)/m;
98
- const match = content.match(shellScriptRegex);
99
- if (!match) {
100
- // Fallback: find by name
101
- console.log(' ⚠ Could not find "Bundle React Native code and images" build phase by UUID, trying by name...');
102
- // Simpler approach: find shellScript belonging to the bundle phase by name proximity
103
- const nameIdx = content.indexOf('name = "Bundle React Native code and images"');
104
- if (nameIdx === -1) {
105
- console.log(' ⚠ Could not find build phase. Please add the bundle sync snippet manually.');
106
- return content;
94
+ // Search by phase name
95
+ const nameIdx = content.indexOf('name = "Bundle React Native code and images";');
96
+ if (nameIdx !== -1) {
97
+ const shellScriptIdx = content.indexOf('shellScript = "', nameIdx);
98
+ if (shellScriptIdx !== -1) {
99
+ const endIdx = content.indexOf('";', shellScriptIdx + 15);
100
+ if (endIdx !== -1) {
101
+ return (
102
+ content.slice(0, endIdx) +
103
+ XCODE_BUNDLE_SYNC_SNIPPET +
104
+ content.slice(endIdx)
105
+ );
106
+ }
107
107
  }
108
- // Find the shellScript = "..." after this name line
109
- const afterName = content.indexOf('shellScript = "', nameIdx - 500);
110
- if (afterName === -1) return content;
111
-
112
- // Find the end of this shellScript: look for \n\"; after it
113
- const scriptEnd = content.indexOf('\\n";', afterName);
114
- if (scriptEnd === -1) return content;
115
-
116
- return (
117
- content.slice(0, scriptEnd) +
118
- XCODE_BUNDLE_SYNC_SNIPPET +
119
- content.slice(scriptEnd)
120
- );
121
108
  }
122
109
 
123
- // Insert before the closing \n\";
124
- const scriptBody = match[2];
125
- // Remove trailing \n if present before closing
126
- const insertPoint = match.index + match[1].length + scriptBody.length;
127
- return (
128
- content.slice(0, insertPoint) +
129
- XCODE_BUNDLE_SYNC_SNIPPET +
130
- content.slice(insertPoint)
131
- );
110
+ console.log(' ⚠ Could not find "Bundle React Native code and images" build phase in project.pbxproj.');
111
+ return content;
132
112
  }
133
113
 
134
114
  // ── iOS: AppDelegate.swift ────────────────────────────────────────────────────
@@ -229,8 +209,8 @@ function main() {
229
209
  // ── iOS ──
230
210
  console.log('\n[iOS]');
231
211
 
232
- // project.pbxproj
233
- const pbxprojFiles = findFile(IOS_DIR, (name) => name === 'project.pbxproj');
212
+ // project.pbxproj (ignore CocoaPods project file)
213
+ const pbxprojFiles = findFile(IOS_DIR, (name, fullPath) => name === 'project.pbxproj' && !fullPath.includes('Pods'));
234
214
  if (pbxprojFiles.length === 0) {
235
215
  console.log(' ⚠ No project.pbxproj found. Is this an iOS project?');
236
216
  } else {
@@ -42,6 +42,14 @@ if [[ ! -f "$REGISTRY_FILE" ]]; then
42
42
  exit 1
43
43
  fi
44
44
 
45
+ # Check for clean remote flag
46
+ CLEAN_REMOTE=false
47
+ for ARG in "$@"; do
48
+ if [ "$ARG" == "--clean-remote" ]; then
49
+ CLEAN_REMOTE=true
50
+ fi
51
+ done
52
+
45
53
  # ── GitHub Auto-Push Integration ──────────────────────────────────────────────
46
54
  if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
47
55
  # Parse OWNER, REPO, BRANCH from URL
@@ -84,29 +92,37 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
84
92
  git clone --branch "$BRANCH" --depth 1 "$GIT_REMOTE" "$GH_REPO_DIR" 2>/dev/null || git clone "$GIT_REMOTE" "$GH_REPO_DIR"
85
93
  fi
86
94
 
87
- # 2. Copy generated files into storage repo
88
- TARGET_RELEASE_DIR="$GH_REPO_DIR/$BASE_FOLDER/$APP_VERSION/$CHANNEL"
89
- mkdir -p "$TARGET_RELEASE_DIR"
90
-
91
- echo " Copying release files to storage repo..."
92
- for FILE in "$OUTPUT_DIR"/*.{patch,hbc,json}; do
93
- [[ -f "$FILE" ]] || continue
94
- FNAME=$(basename "$FILE")
95
- cp "$FILE" "$TARGET_RELEASE_DIR/$FNAME"
96
- done
95
+ # 2. Copy generated files into storage repo (or reset remote)
96
+ if [ "$CLEAN_REMOTE" = true ]; then
97
+ echo " Resetting remote storage state..."
98
+ rm -rf "$GH_REPO_DIR/$BASE_FOLDER/$APP_VERSION"
99
+ mkdir -p "$GH_REPO_DIR/$BASE_FOLDER"
100
+ cp "$REGISTRY_FILE" "$GH_REPO_DIR/$BASE_FOLDER/ota_registry.json"
101
+ cp "$REGISTRY_FILE" "$GH_REPO_DIR/ota_registry.json" 2>/dev/null || true
102
+ else
103
+ TARGET_RELEASE_DIR="$GH_REPO_DIR/$BASE_FOLDER/$APP_VERSION/$CHANNEL"
104
+ mkdir -p "$TARGET_RELEASE_DIR"
105
+
106
+ echo " Copying release files to storage repo..."
107
+ for FILE in "$OUTPUT_DIR"/*.{patch,hbc,json}; do
108
+ [[ -f "$FILE" ]] || continue
109
+ FNAME=$(basename "$FILE")
110
+ cp "$FILE" "$TARGET_RELEASE_DIR/$FNAME"
111
+ done
112
+
113
+ # Copy asset store if present
114
+ if [[ -d "$OUTPUT_DIR/asset-store" ]]; then
115
+ TARGET_ASSET_DIR="$GH_REPO_DIR/$BASE_FOLDER/assets"
116
+ mkdir -p "$TARGET_ASSET_DIR"
117
+ cp -R "$OUTPUT_DIR/asset-store/"* "$TARGET_ASSET_DIR/" 2>/dev/null || true
118
+ fi
97
119
 
98
- # Copy asset store if present
99
- if [[ -d "$OUTPUT_DIR/asset-store" ]]; then
100
- TARGET_ASSET_DIR="$GH_REPO_DIR/$BASE_FOLDER/assets"
101
- mkdir -p "$TARGET_ASSET_DIR"
102
- cp -R "$OUTPUT_DIR/asset-store/"* "$TARGET_ASSET_DIR/" 2>/dev/null || true
120
+ # Copy ota_registry.json
121
+ mkdir -p "$GH_REPO_DIR/$BASE_FOLDER"
122
+ cp "$REGISTRY_FILE" "$GH_REPO_DIR/$BASE_FOLDER/ota_registry.json"
123
+ cp "$REGISTRY_FILE" "$GH_REPO_DIR/ota_registry.json" 2>/dev/null || true
103
124
  fi
104
125
 
105
- # Copy ota_registry.json
106
- mkdir -p "$GH_REPO_DIR/$BASE_FOLDER"
107
- cp "$REGISTRY_FILE" "$GH_REPO_DIR/$BASE_FOLDER/ota_registry.json"
108
- cp "$REGISTRY_FILE" "$GH_REPO_DIR/ota_registry.json" 2>/dev/null || true
109
-
110
126
  # 3. Commit and Push to GitHub
111
127
  cd "$GH_REPO_DIR"
112
128
  git config user.name "hungbv-taureau" 2>/dev/null || true
@@ -115,8 +131,12 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
115
131
  if git diff --staged --quiet; then
116
132
  echo " ✓ Storage repository is up to date (no changes)."
117
133
  else
118
- echo " Committing OTA release (App: $APP_VERSION v$OTA_VERSION)..."
119
- git commit -m "auto(ota): publish $PLATFORM $APP_VERSION v$OTA_VERSION ($CHANNEL)"
134
+ COMMIT_MSG="auto(ota): publish $PLATFORM $APP_VERSION v$OTA_VERSION ($CHANNEL)"
135
+ if [ "$CLEAN_REMOTE" = true ]; then
136
+ COMMIT_MSG="auto(ota): reset remote OTA registry and releases"
137
+ fi
138
+ echo " Committing OTA changes ($COMMIT_MSG)..."
139
+ git commit -m "$COMMIT_MSG"
120
140
  echo " Pushing to GitHub (${BRANCH})..."
121
141
 
122
142
  if git push origin "$BRANCH"; then