This commit is contained in:
tapframe 2025-09-06 23:42:27 +05:30
parent 019f99c956
commit b1494e8a1d
7 changed files with 153 additions and 95 deletions

View file

@ -1,8 +1,9 @@
#!/bin/bash
# Check if the correct number of arguments are provided
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <runtimeVersion> <xavia-ota-url>"
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <xavia-ota-url>"
echo "Example: $0 https://grim-reyna-tapframe-69970143.koyeb.app"
exit 1
fi
@ -10,52 +11,150 @@ fi
commitHash=$(git rev-parse HEAD)
commitMessage=$(git log -1 --pretty=%B)
# Check if app.json exists
if [ ! -f "app.json" ]; then
echo "Error: app.json not found in current directory"
exit 1
fi
# Auto-detect runtime version from app.json
runtimeVersion=$(jq -r '.expo.runtimeVersion' app.json)
if [ "$runtimeVersion" = "null" ] || [ -z "$runtimeVersion" ]; then
echo "Error: Could not find runtimeVersion in app.json"
echo "Please ensure app.json contains: \"runtimeVersion\": \"your-version\""
exit 1
fi
# Assign arguments to variables
runtimeVersion=$1
serverHost=$2
serverHost=$1
# Validate server URL format
if [[ ! "$serverHost" =~ ^https?:// ]]; then
echo "Error: Server URL must start with http:// or https://"
echo "Example: https://grim-reyna-tapframe-69970143.koyeb.app"
exit 1
fi
# Generate a timestamp for the output folder
timestamp=$(date -u +%Y%m%d%H%M%S)
outputFolder="ota-builds/$timestamp"
# Ask the user to confirm the hash, commit message, runtime version, and output folder
echo "Output Folder: $outputFolder"
echo "Runtime Version: $runtimeVersion"
echo "Commit Hash: $commitHash"
echo "Commit Message: $commitMessage"
# Display build information
echo "🚀 Nuvio OTA Build & Deploy Script"
echo "=================================="
echo "📁 Output Folder: $outputFolder"
echo "📱 Runtime Version: $runtimeVersion"
echo "🔗 Commit Hash: $commitHash"
echo "📝 Commit Message: $commitMessage"
echo "🌐 Server URL: $serverHost"
echo ""
read -p "Do you want to proceed with these values? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "Operation cancelled by the user."
echo "Operation cancelled by the user."
exit 1
fi
echo "🔨 Starting build process..."
# Clean up any existing output folder
rm -rf $outputFolder
mkdir -p $outputFolder
# Run expo export with the specified output folder
npx expo export --output-dir $outputFolder
echo "📦 Exporting Expo bundle..."
if ! npx expo export --output-dir $outputFolder; then
echo "❌ Error: Expo export failed"
exit 1
fi
# Extract expo config property from app.json and save to expoconfig.json
echo "⚙️ Extracting Expo configuration..."
jq '.expo' app.json > $outputFolder/expoconfig.json
# Zip the output folder
echo "📦 Creating deployment package..."
cd $outputFolder
zip -q -r ${timestamp}.zip .
if ! zip -q -r ${timestamp}.zip .; then
echo "❌ Error: Failed to create zip file"
exit 1
fi
# Upload the zip file to the server
curl -X POST $serverHost/api/upload -F "file=@${timestamp}.zip" -F "runtimeVersion=$runtimeVersion" -F "commitHash=$commitHash" -F "commitMessage=$commitMessage"
echo "🚀 Uploading to server..."
echo "📊 File size: $(du -h ${timestamp}.zip | cut -f1)"
# Check server health before upload
echo "🔍 Checking server status..."
if ! curl --max-time 10 --connect-timeout 5 -s -o /dev/null "$serverHost/api/manifest"; then
echo "⚠️ Warning: Server may be slow or unresponsive"
echo "💡 Proceeding with upload anyway..."
else
echo "✅ Server is responding"
fi
echo ""
echo "Uploaded to $serverHost/api/upload"
# Try upload with extended timeout and retry logic
max_retries=3
retry_count=0
while [ $retry_count -lt $max_retries ]; do
echo "🔄 Upload attempt $((retry_count + 1))/$max_retries..."
response=$(curl --max-time 300 --connect-timeout 30 -X POST $serverHost/api/upload \
-F "file=@${timestamp}.zip" \
-F "runtimeVersion=$runtimeVersion" \
-F "commitHash=$commitHash" \
-F "commitMessage=$commitMessage" \
--write-out "HTTP_CODE:%{http_code}" \
--silent \
--show-error)
# Extract HTTP code from response
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
# Check if we got a valid HTTP code
if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then
echo "❌ Failed to extract HTTP status code from response"
echo "Response: $response"
http_code="000"
fi
echo "HTTP Status: $http_code"
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo ""
echo "✅ Successfully uploaded to $serverHost/api/upload"
break
else
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "⚠️ Upload attempt $retry_count failed, retrying in 5 seconds..."
sleep 5
else
echo "❌ Error: Upload failed after $max_retries attempts"
echo "📊 Final HTTP Status: $http_code"
if [ "$http_code" = "524" ]; then
echo "💡 Error 524: Server timeout - try again later or check server capacity"
elif [ "$http_code" = "413" ]; then
echo "💡 Error 413: File too large - consider reducing bundle size"
elif [ "$http_code" = "500" ]; then
echo "💡 Error 500: Server error - check server logs"
else
echo "💡 Check server status and try again"
fi
exit 1
fi
fi
done
cd ..
# Remove the output folder and zip file
echo "🧹 Cleaning up temporary files..."
rm -rf $outputFolder
echo "Removed $outputFolder"
echo "Done"
echo "🎉 Build and deployment completed successfully!"
echo "📱 Runtime Version: $runtimeVersion"
echo "🔗 Commit: $commitHash"

7
manifest_response.json Normal file
View file

@ -0,0 +1,7 @@
----------------------------568300859475270590089014
Content-Disposition: form-data; name="directive"
Content-Type: application/json
content-type: application/json; charset=utf-8
{"type":"noUpdateAvailable"}
----------------------------568300859475270590089014--

View file

@ -0,0 +1,7 @@
----------------------------694338510290346396309710
Content-Disposition: form-data; name="directive"
Content-Type: application/json
content-type: application/json; charset=utf-8
{"type":"noUpdateAvailable"}
----------------------------694338510290346396309710--

View file

@ -72,7 +72,7 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
<View style={[
styles.popup,
{
backgroundColor: currentTheme.colors.elevation1 || '#1a1a1a',
backgroundColor: currentTheme.colors.darkBackground || '#1a1a1a',
borderColor: currentTheme.colors.elevation2 || '#333333',
marginTop: insets.top + 20,
marginBottom: insets.bottom + 20,
@ -173,8 +173,8 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
styles.button,
styles.secondaryButton,
{
backgroundColor: currentTheme.colors.elevation2,
borderColor: currentTheme.colors.elevation3,
backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3 || '#444444',
}
]}
onPress={handleUpdateLater}
@ -194,8 +194,8 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
styles.button,
styles.secondaryButton,
{
backgroundColor: currentTheme.colors.elevation2,
borderColor: currentTheme.colors.elevation3,
backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3 || '#444444',
}
]}
onPress={handleDismiss}
@ -239,7 +239,7 @@ const styles = StyleSheet.create({
width: Math.min(width - 40, 400),
borderRadius: 20,
borderWidth: 1,
backgroundColor: '#1a1a1a', // Fallback solid background
backgroundColor: '#1a1a1a', // Solid background - not transparent
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.5,
@ -297,7 +297,7 @@ const styles = StyleSheet.create({
marginTop: 8,
padding: 12,
borderRadius: 8,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
backgroundColor: 'rgba(255, 255, 255, 0.15)',
},
description: {
fontSize: 14,

View file

@ -62,7 +62,7 @@ const createStyles = (colors: any) => StyleSheet.create({
flex: 1,
},
section: {
backgroundColor: colors.elevation1,
backgroundColor: colors.darkBackground,
marginBottom: 16,
borderRadius: 12,
padding: 16,
@ -181,7 +181,7 @@ const createStyles = (colors: any) => StyleSheet.create({
lineHeight: 20,
},
textInput: {
backgroundColor: colors.elevation1,
backgroundColor: colors.darkBackground,
borderRadius: 8,
padding: 12,
color: colors.white,
@ -369,7 +369,7 @@ const createStyles = (colors: any) => StyleSheet.create({
},
// New styles for improved UX
collapsibleSection: {
backgroundColor: colors.elevation1,
backgroundColor: colors.darkBackground,
marginBottom: 16,
borderRadius: 12,
overflow: 'hidden',
@ -392,7 +392,7 @@ const createStyles = (colors: any) => StyleSheet.create({
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.elevation1,
backgroundColor: colors.darkBackground,
borderRadius: 12,
marginBottom: 16,
paddingHorizontal: 12,
@ -489,7 +489,7 @@ const createStyles = (colors: any) => StyleSheet.create({
alignItems: 'center',
},
modalContent: {
backgroundColor: colors.elevation1,
backgroundColor: colors.darkBackground,
borderRadius: 16,
padding: 24,
margin: 20,
@ -750,10 +750,7 @@ const PluginsScreen: React.FC = () => {
const [currentRepositoryId, setCurrentRepositoryId] = useState<string>('');
const [showAddRepositoryModal, setShowAddRepositoryModal] = useState(false);
const [newRepositoryUrl, setNewRepositoryUrl] = useState('');
const [newRepositoryName, setNewRepositoryName] = useState('');
const [newRepositoryDescription, setNewRepositoryDescription] = useState('');
const [switchingRepository, setSwitchingRepository] = useState<string | null>(null);
const [fetchingRepoName, setFetchingRepoName] = useState(false);
// New UX state
const [searchQuery, setSearchQuery] = useState('');
@ -837,29 +834,8 @@ const PluginsScreen: React.FC = () => {
}
};
const handleUrlChange = async (url: string) => {
const handleUrlChange = (url: string) => {
setNewRepositoryUrl(url);
// Auto-populate repository name if it's empty and URL is valid
if (!newRepositoryName.trim() && url.trim()) {
setFetchingRepoName(true);
try {
// Try to fetch name from manifest first
const manifestName = await localScraperService.fetchRepositoryNameFromManifest(url.trim());
setNewRepositoryName(manifestName);
} catch (error) {
// Fallback to URL extraction if manifest fetch fails
try {
const extractedName = localScraperService.extractRepositoryName(url.trim());
if (extractedName !== 'Unknown Repository') {
setNewRepositoryName(extractedName);
}
} catch (extractError) {
// Ignore errors, just don't auto-populate
}
} finally {
setFetchingRepoName(false);
}
}
};
const handleAddRepository = async () => {
@ -873,7 +849,7 @@ const PluginsScreen: React.FC = () => {
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
Alert.alert(
'Invalid URL Format',
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/branch/\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/main/'
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
);
return;
}
@ -881,9 +857,9 @@ const PluginsScreen: React.FC = () => {
try {
setIsLoading(true);
const repoId = await localScraperService.addRepository({
name: newRepositoryName.trim(), // Let the service fetch from manifest if empty
name: '', // Let the service fetch from manifest
url,
description: newRepositoryDescription.trim(),
description: '',
enabled: true
});
@ -895,9 +871,6 @@ const PluginsScreen: React.FC = () => {
await loadScrapers();
setNewRepositoryUrl('');
setNewRepositoryName('');
setNewRepositoryDescription('');
setFetchingRepoName(false);
setShowAddRepositoryModal(false);
Alert.alert('Success', 'Repository added and refreshed successfully');
} catch (error) {
@ -1016,7 +989,7 @@ const PluginsScreen: React.FC = () => {
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
Alert.alert(
'Invalid URL Format',
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/branch/\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/main/'
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
);
return;
}
@ -1051,7 +1024,7 @@ const PluginsScreen: React.FC = () => {
const errorMessage = error instanceof Error ? error.message : String(error);
Alert.alert(
'Repository Error',
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/branch/`
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch`
);
} finally {
setIsRefreshing(false);
@ -1135,7 +1108,7 @@ const PluginsScreen: React.FC = () => {
};
const handleUseDefaultRepo = () => {
const defaultUrl = 'https://raw.githubusercontent.com/tapframe/nuvio-providers/main';
const defaultUrl = 'https://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master';
setRepositoryUrl(defaultUrl);
};
@ -1731,53 +1704,24 @@ const PluginsScreen: React.FC = () => {
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Add New Repository</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
<Text style={styles.settingTitle}>Repository Name</Text>
{fetchingRepoName && (
<ActivityIndicator size="small" color={colors.primary} style={{ marginLeft: 8 }} />
)}
</View>
<TextInput
style={styles.textInput}
value={newRepositoryName}
onChangeText={setNewRepositoryName}
placeholder="Enter repository name"
placeholderTextColor={colors.mediumGray}
autoCapitalize="words"
/>
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Repository URL</Text>
<TextInput
style={styles.textInput}
value={newRepositoryUrl}
onChangeText={handleUrlChange}
placeholder="https://raw.githubusercontent.com/username/repo/branch/"
placeholder="https://raw.githubusercontent.com/username/repo/refs/heads/branch"
placeholderTextColor={colors.mediumGray}
autoCapitalize="none"
autoCorrect={false}
keyboardType="url"
/>
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Description (Optional)</Text>
<TextInput
style={[styles.textInput, { height: 80 }]}
value={newRepositoryDescription}
onChangeText={setNewRepositoryDescription}
placeholder="Enter repository description"
placeholderTextColor={colors.mediumGray}
multiline={true}
numberOfLines={3}
/>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.secondaryButton, { flex: 1 }]}
onPress={() => {
setShowAddRepositoryModal(false);
setNewRepositoryUrl('');
setNewRepositoryName('');
setNewRepositoryDescription('');
setFetchingRepoName(false);
}}
>
<Text style={styles.secondaryButtonText}>Cancel</Text>

View file

@ -410,6 +410,7 @@ const UpdateScreen: React.FC = () => {
</Text>
</TouchableOpacity>
)}
</View>
</View>

@ -1 +1 @@
Subproject commit ba2fe779d75a8ed285c5d37f05532c3ff4acce22
Subproject commit ef5455acaa04404d816a6bffc25a259e28189918