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 +1 -1
- package/tools/build_ota.sh +71 -71
- package/tools/setup_project.js +17 -37
- package/tools/upload_ota_custom.sh +42 -22
package/package.json
CHANGED
package/tools/build_ota.sh
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
echo "
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
|
package/tools/setup_project.js
CHANGED
|
@@ -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
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|