feat: Implement default welcome page for missing web application
- Added a professional HTML welcome page displayed when no web application is configured. - Enhanced `_load_webapp()` method to support improved path resolution for both development and bundled modes. - Updated error handling to show the welcome page instead of a bare error message when the webapp file is not found. - Modified unit tests to verify the welcome page is displayed in error scenarios. build: Complete Windows and macOS build scripts - Created `build_windows.py` for building Windows executable and optional MSI installer using PyInstaller. - Developed `build_macos.sh` for creating macOS application bundle and DMG image. - Added logging and error handling to build scripts for better user feedback. docs: Add build and icon requirements documentation - Created `PHASE_3_BUILD_SUMMARY.md` detailing the build process, results, and next steps. - Added `resources/icons/README.md` outlining icon requirements and creation guidelines. chore: Sync remotes script for repository maintenance - Introduced `sync_remotes.ps1` PowerShell script to fetch updates from origin and upstream remotes.
This commit is contained in:
parent
90dc09eb4d
commit
f0c96f15b8
10 changed files with 1415 additions and 39 deletions
295
build/scripts/build_macos.sh
Normal file
295
build/scripts/build_macos.sh
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
#!/bin/bash
|
||||
# Build macOS DMG package using PyInstaller
|
||||
#
|
||||
# This script builds the WebDrop Bridge application for macOS and creates
|
||||
# a distributable DMG image.
|
||||
#
|
||||
# Requirements:
|
||||
# - PyInstaller 6.0+
|
||||
# - Python 3.10+
|
||||
# - Xcode Command Line Tools (for code signing)
|
||||
# - create-dmg (optional, for custom DMG: brew install create-dmg)
|
||||
#
|
||||
# Usage:
|
||||
# bash build_macos.sh [--sign] [--notarize]
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
DIST_DIR="$BUILD_DIR/dist/macos"
|
||||
TEMP_BUILD="$BUILD_DIR/temp/macos"
|
||||
SPECS_DIR="$BUILD_DIR/specs"
|
||||
SPEC_FILE="$BUILD_DIR/webdrop_bridge.spec"
|
||||
|
||||
APP_NAME="WebDropBridge"
|
||||
DMG_VOLUME_NAME="WebDrop Bridge"
|
||||
VERSION="1.0.0"
|
||||
|
||||
# Parse arguments
|
||||
SIGN_APP=0
|
||||
NOTARIZE_APP=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--sign)
|
||||
SIGN_APP=1
|
||||
shift
|
||||
;;
|
||||
--notarize)
|
||||
NOTARIZE_APP=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌${NC} $1"
|
||||
}
|
||||
|
||||
# Main build function
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo "🚀 WebDrop Bridge macOS Build"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites
|
||||
|
||||
# Clean previous builds
|
||||
clean_builds
|
||||
|
||||
# Build with PyInstaller
|
||||
build_executable
|
||||
|
||||
# Create DMG
|
||||
create_dmg
|
||||
|
||||
# Optional: Sign and notarize
|
||||
if [ $SIGN_APP -eq 1 ]; then
|
||||
sign_app
|
||||
fi
|
||||
|
||||
if [ $NOTARIZE_APP -eq 1 ]; then
|
||||
notarize_app
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
log_success "Build completed successfully"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_info "Output: $DIST_DIR/"
|
||||
ls -lh "$DIST_DIR"
|
||||
}
|
||||
|
||||
check_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
# Check Python
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
log_error "Python 3 not found"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Python 3 found: $(python3 --version)"
|
||||
|
||||
# Check PyInstaller
|
||||
if ! python3 -m pip show pyinstaller &> /dev/null; then
|
||||
log_error "PyInstaller not installed. Run: pip install pyinstaller"
|
||||
exit 1
|
||||
fi
|
||||
log_success "PyInstaller installed"
|
||||
|
||||
# Check spec file
|
||||
if [ ! -f "$SPEC_FILE" ]; then
|
||||
log_error "Spec file not found: $SPEC_FILE"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Spec file found"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
clean_builds() {
|
||||
log_info "Cleaning previous builds..."
|
||||
|
||||
for dir in "$DIST_DIR" "$TEMP_BUILD" "$SPECS_DIR"; do
|
||||
if [ -d "$dir" ]; then
|
||||
rm -rf "$dir"
|
||||
log_success "Removed $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$DIST_DIR" "$TEMP_BUILD" "$SPECS_DIR"
|
||||
echo ""
|
||||
}
|
||||
|
||||
build_executable() {
|
||||
log_info "Building macOS executable with PyInstaller..."
|
||||
echo ""
|
||||
|
||||
python3 -m PyInstaller \
|
||||
--distpath="$DIST_DIR" \
|
||||
--buildpath="$TEMP_BUILD" \
|
||||
--specpath="$SPECS_DIR" \
|
||||
"$SPEC_FILE"
|
||||
|
||||
if [ ! -d "$DIST_DIR/$APP_NAME.app" ]; then
|
||||
log_error "Application bundle not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Application bundle built successfully"
|
||||
log_info "Output: $DIST_DIR/$APP_NAME.app"
|
||||
echo ""
|
||||
}
|
||||
|
||||
create_dmg() {
|
||||
log_info "Creating DMG package..."
|
||||
echo ""
|
||||
|
||||
DMG_FILE="$DIST_DIR/${APP_NAME}-${VERSION}.dmg"
|
||||
|
||||
# Remove existing DMG
|
||||
if [ -f "$DMG_FILE" ]; then
|
||||
rm -f "$DMG_FILE"
|
||||
fi
|
||||
|
||||
# Check if create-dmg is available
|
||||
if command -v create-dmg &> /dev/null; then
|
||||
log_info "Using create-dmg for professional DMG..."
|
||||
|
||||
create-dmg \
|
||||
--volname "$DMG_VOLUME_NAME" \
|
||||
--icon-size 128 \
|
||||
--window-size 512 400 \
|
||||
--app-drop-link 380 200 \
|
||||
"$DMG_FILE" \
|
||||
"$DIST_DIR/$APP_NAME.app"
|
||||
else
|
||||
log_warning "create-dmg not found, using hdiutil (less stylish)"
|
||||
log_info "For professional DMG: brew install create-dmg"
|
||||
|
||||
# Create temporary DMG directory structure
|
||||
DMG_TEMP="$TEMP_BUILD/dmg_contents"
|
||||
mkdir -p "$DMG_TEMP"
|
||||
|
||||
# Copy app bundle
|
||||
cp -r "$DIST_DIR/$APP_NAME.app" "$DMG_TEMP/"
|
||||
|
||||
# Create symlink to Applications folder
|
||||
ln -s /Applications "$DMG_TEMP/Applications"
|
||||
|
||||
# Create DMG
|
||||
hdiutil create \
|
||||
-volname "$DMG_VOLUME_NAME" \
|
||||
-srcfolder "$DMG_TEMP" \
|
||||
-ov \
|
||||
-format UDZO \
|
||||
"$DMG_FILE"
|
||||
|
||||
# Clean up
|
||||
rm -rf "$DMG_TEMP"
|
||||
fi
|
||||
|
||||
if [ ! -f "$DMG_FILE" ]; then
|
||||
log_error "DMG file not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get file size
|
||||
SIZE=$(du -h "$DMG_FILE" | cut -f1)
|
||||
log_success "DMG created successfully"
|
||||
log_info "Output: $DMG_FILE (Size: $SIZE)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
sign_app() {
|
||||
log_info "Signing application..."
|
||||
echo ""
|
||||
|
||||
# Get signing identity from environment or use ad-hoc
|
||||
SIGNING_ID="${APPLE_SIGNING_ID:--}"
|
||||
|
||||
codesign \
|
||||
--deep \
|
||||
--force \
|
||||
--verify \
|
||||
--verbose \
|
||||
--options=runtime \
|
||||
--sign "$SIGNING_ID" \
|
||||
"$DIST_DIR/$APP_NAME.app"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Code signing failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Application signed successfully"
|
||||
echo ""
|
||||
}
|
||||
|
||||
notarize_app() {
|
||||
log_info "Notarizing application..."
|
||||
echo ""
|
||||
|
||||
# Requires:
|
||||
# - APPLE_ID environment variable
|
||||
# - APPLE_PASSWORD environment variable (app-specific password)
|
||||
# - APPLE_TEAM_ID environment variable
|
||||
|
||||
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_PASSWORD" ]; then
|
||||
log_error "APPLE_ID and APPLE_PASSWORD environment variables required for notarization"
|
||||
return 1
|
||||
fi
|
||||
|
||||
DMG_FILE="$DIST_DIR/${APP_NAME}-${VERSION}.dmg"
|
||||
|
||||
# Upload for notarization
|
||||
log_info "Uploading to Apple Notarization Service..."
|
||||
xcrun notarytool submit "$DMG_FILE" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_PASSWORD" \
|
||||
--team-id "${APPLE_TEAM_ID}" \
|
||||
--wait
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Notarization failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Staple the notarization
|
||||
xcrun stapler staple "$DMG_FILE"
|
||||
|
||||
log_success "Application notarized successfully"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
321
build/scripts/build_windows.py
Normal file
321
build/scripts/build_windows.py
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
"""Build Windows installer (MSI) using PyInstaller.
|
||||
|
||||
This script builds the WebDrop Bridge application for Windows using PyInstaller.
|
||||
It creates both a standalone executable and optionally an MSI installer.
|
||||
|
||||
Requirements:
|
||||
- PyInstaller 6.0+
|
||||
- Python 3.10+
|
||||
- For MSI: WiX Toolset (optional, requires separate installation)
|
||||
|
||||
Usage:
|
||||
python build_windows.py [--msi] [--code-sign]
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Fix Unicode output on Windows
|
||||
if sys.platform == "win32":
|
||||
import io
|
||||
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||
|
||||
|
||||
class WindowsBuilder:
|
||||
"""Build Windows installer using PyInstaller."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize builder paths."""
|
||||
self.project_root = Path(__file__).parent.parent.parent
|
||||
self.build_dir = self.project_root / "build"
|
||||
self.dist_dir = self.build_dir / "dist" / "windows"
|
||||
self.temp_dir = self.build_dir / "temp" / "windows"
|
||||
self.spec_file = self.build_dir / "webdrop_bridge.spec"
|
||||
self.version = self._get_version()
|
||||
|
||||
def _get_version(self) -> str:
|
||||
"""Get version from config.py."""
|
||||
config_file = self.project_root / "src" / "webdrop_bridge" / "config.py"
|
||||
for line in config_file.read_text().split("\n"):
|
||||
if "app_version" in line and "1.0.0" in line:
|
||||
# Extract default version from config
|
||||
return "1.0.0"
|
||||
return "1.0.0"
|
||||
|
||||
def clean(self):
|
||||
"""Clean previous builds."""
|
||||
print("🧹 Cleaning previous builds...")
|
||||
for path in [self.dist_dir, self.temp_dir]:
|
||||
if path.exists():
|
||||
shutil.rmtree(path)
|
||||
print(f" Removed {path}")
|
||||
|
||||
def build_executable(self) -> bool:
|
||||
"""Build executable using PyInstaller."""
|
||||
print("\n🔨 Building Windows executable with PyInstaller...")
|
||||
|
||||
self.dist_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# PyInstaller command using spec file
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"PyInstaller",
|
||||
"--distpath",
|
||||
str(self.dist_dir),
|
||||
"--workpath",
|
||||
str(self.temp_dir),
|
||||
str(self.spec_file),
|
||||
]
|
||||
|
||||
print(f" Command: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, cwd=str(self.project_root))
|
||||
|
||||
if result.returncode != 0:
|
||||
print("❌ PyInstaller build failed")
|
||||
return False
|
||||
|
||||
exe_path = self.dist_dir / "WebDropBridge.exe"
|
||||
if not exe_path.exists():
|
||||
print(f"❌ Executable not found at {exe_path}")
|
||||
return False
|
||||
|
||||
print("✅ Executable built successfully")
|
||||
print(f"📦 Output: {exe_path}")
|
||||
print(f" Size: {exe_path.stat().st_size / 1024 / 1024:.1f} MB")
|
||||
|
||||
return True
|
||||
|
||||
def create_msi(self) -> bool:
|
||||
"""Create MSI installer using WiX Toolset.
|
||||
|
||||
This requires WiX Toolset to be installed:
|
||||
https://wixtoolset.org/releases/
|
||||
"""
|
||||
print("\n📦 Creating MSI installer with WiX...")
|
||||
|
||||
# Check if WiX is installed
|
||||
heat_exe = shutil.which("heat.exe")
|
||||
candle_exe = shutil.which("candle.exe")
|
||||
light_exe = shutil.which("light.exe")
|
||||
|
||||
if not all([heat_exe, candle_exe, light_exe]):
|
||||
print("⚠️ WiX Toolset not found in PATH")
|
||||
print(" Install from: https://wixtoolset.org/releases/")
|
||||
print(" Or use: choco install wixtoolset")
|
||||
return False
|
||||
|
||||
# Create WiX source file
|
||||
if not self._create_wix_source():
|
||||
return False
|
||||
|
||||
# Compile and link
|
||||
wix_obj = self.build_dir / "WebDropBridge.wixobj"
|
||||
msi_output = self.dist_dir / f"WebDropBridge-{self.version}-Setup.msi"
|
||||
|
||||
# Run candle (compiler)
|
||||
candle_cmd = [
|
||||
str(candle_exe),
|
||||
"-o",
|
||||
str(wix_obj),
|
||||
str(self.build_dir / "WebDropBridge.wxs"),
|
||||
]
|
||||
|
||||
print(f" Compiling WiX source...")
|
||||
result = subprocess.run(candle_cmd)
|
||||
if result.returncode != 0:
|
||||
print("❌ WiX compilation failed")
|
||||
return False
|
||||
|
||||
# Run light (linker)
|
||||
light_cmd = [
|
||||
str(light_exe),
|
||||
"-o",
|
||||
str(msi_output),
|
||||
str(wix_obj),
|
||||
]
|
||||
|
||||
print(f" Linking MSI installer...")
|
||||
result = subprocess.run(light_cmd)
|
||||
if result.returncode != 0:
|
||||
print("❌ MSI linking failed")
|
||||
return False
|
||||
|
||||
if not msi_output.exists():
|
||||
print(f"❌ MSI not found at {msi_output}")
|
||||
return False
|
||||
|
||||
print("✅ MSI installer created successfully")
|
||||
print(f"📦 Output: {msi_output}")
|
||||
print(f" Size: {msi_output.stat().st_size / 1024 / 1024:.1f} MB")
|
||||
|
||||
return True
|
||||
|
||||
def _create_wix_source(self) -> bool:
|
||||
"""Create WiX source file for MSI generation."""
|
||||
wix_content = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*" Name="WebDrop Bridge" Language="1033" Version="{self.version}"
|
||||
Manufacturer="HIM-Tools"
|
||||
UpgradeCode="12345678-1234-1234-1234-123456789012">
|
||||
|
||||
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
|
||||
<Media Id="1" Cabinet="WebDropBridge.cab" EmbedCab="yes" />
|
||||
|
||||
<Feature Id="ProductFeature" Title="WebDrop Bridge" Level="1">
|
||||
<ComponentRef Id="MainExecutable" />
|
||||
<ComponentRef Id="ProgramMenuShortcut" />
|
||||
</Feature>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFilesFolder">
|
||||
<Directory Id="INSTALLFOLDER" Name="WebDrop Bridge" />
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="WebDrop Bridge"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="MainExecutable" Guid="*">
|
||||
<File Id="WebDropBridgeExe" Source="$(var.DistDir)\\WebDropBridge.exe" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ProgramMenuShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="WebDrop Bridge"
|
||||
Description="Web Drag-and-Drop Bridge"
|
||||
Target="[INSTALLFOLDER]WebDropBridge.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="ApplicationProgramsFolderRemove"
|
||||
On="uninstall" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\WebDropBridge"
|
||||
Name="installed"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</Product>
|
||||
</Wix>
|
||||
'''
|
||||
|
||||
wix_file = self.build_dir / "WebDropBridge.wxs"
|
||||
wix_file.write_text(wix_content)
|
||||
print(f" Created WiX source: {wix_file}")
|
||||
return True
|
||||
|
||||
def sign_executable(self, cert_path: str, password: str) -> bool:
|
||||
"""Sign executable with certificate (optional).
|
||||
|
||||
Args:
|
||||
cert_path: Path to code signing certificate
|
||||
password: Certificate password
|
||||
|
||||
Returns:
|
||||
True if signing successful
|
||||
"""
|
||||
print("\n🔐 Signing executable...")
|
||||
|
||||
signtool = shutil.which("signtool.exe")
|
||||
if not signtool:
|
||||
print("⚠️ signtool.exe not found (part of Windows SDK)")
|
||||
print(" Skipping code signing")
|
||||
return True
|
||||
|
||||
exe_path = self.dist_dir / "WebDropBridge.exe"
|
||||
cmd = [
|
||||
signtool,
|
||||
"sign",
|
||||
"/f",
|
||||
cert_path,
|
||||
"/p",
|
||||
password,
|
||||
"/t",
|
||||
"http://timestamp.comodoca.com/authenticode",
|
||||
str(exe_path),
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode != 0:
|
||||
print("❌ Code signing failed")
|
||||
return False
|
||||
|
||||
print("✅ Executable signed successfully")
|
||||
return True
|
||||
|
||||
def build(self, create_msi: bool = False, sign: bool = False) -> bool:
|
||||
"""Run complete build process.
|
||||
|
||||
Args:
|
||||
create_msi: Whether to create MSI installer
|
||||
sign: Whether to sign executable (requires certificate)
|
||||
|
||||
Returns:
|
||||
True if build successful
|
||||
"""
|
||||
start_time = datetime.now()
|
||||
print("=" * 60)
|
||||
print("🚀 WebDrop Bridge Windows Build")
|
||||
print("=" * 60)
|
||||
|
||||
self.clean()
|
||||
|
||||
if not self.build_executable():
|
||||
return False
|
||||
|
||||
if create_msi:
|
||||
if not self.create_msi():
|
||||
print("⚠️ MSI creation failed, but executable is available")
|
||||
|
||||
if sign:
|
||||
# Would need certificate path from environment
|
||||
cert_path = os.getenv("CODE_SIGN_CERT")
|
||||
if cert_path:
|
||||
self.sign_executable(cert_path, os.getenv("CODE_SIGN_PASSWORD", ""))
|
||||
|
||||
elapsed = (datetime.now() - start_time).total_seconds()
|
||||
print("\n" + "=" * 60)
|
||||
print(f"✅ Build completed in {elapsed:.1f}s")
|
||||
print("=" * 60)
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build WebDrop Bridge for Windows"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--msi",
|
||||
action="store_true",
|
||||
help="Create MSI installer (requires WiX Toolset)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sign",
|
||||
action="store_true",
|
||||
help="Sign executable (requires CODE_SIGN_CERT environment variable)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
builder = WindowsBuilder()
|
||||
success = builder.build(create_msi=args.msi, sign=args.sign)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
55
build/scripts/sync_remotes.ps1
Normal file
55
build/scripts/sync_remotes.ps1
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Sync script to keep origin and upstream remotes in sync
|
||||
# Usage: .\sync_remotes.ps1 [--push-to-origin]
|
||||
|
||||
param(
|
||||
[switch]$PushToOrigin
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Split-Path -Parent (Split-Path -Parent $scriptPath)
|
||||
|
||||
Write-Host "🔄 WebDrop Bridge - Remote Sync Script" -ForegroundColor Cyan
|
||||
Write-Host "Repository: $repoRoot`n" -ForegroundColor Gray
|
||||
|
||||
# Change to repo directory
|
||||
Push-Location $repoRoot
|
||||
|
||||
try {
|
||||
# Fetch from both remotes
|
||||
Write-Host "📥 Fetching from origin..." -ForegroundColor Yellow
|
||||
git fetch origin
|
||||
|
||||
Write-Host "📥 Fetching from upstream..." -ForegroundColor Yellow
|
||||
git fetch upstream
|
||||
|
||||
# Show status
|
||||
Write-Host "`n📊 Remote Status:" -ForegroundColor Cyan
|
||||
git remote -v
|
||||
|
||||
# Show branch comparison
|
||||
Write-Host "`n📋 Branch Comparison:" -ForegroundColor Cyan
|
||||
Write-Host "Local branches vs origin:" -ForegroundColor Gray
|
||||
git log --oneline origin/main -5 | ForEach-Object { Write-Host " origin: $_" }
|
||||
Write-Host ""
|
||||
git log --oneline upstream/main -5 | ForEach-Object { Write-Host " upstream: $_" }
|
||||
|
||||
# Optionally push to origin
|
||||
if ($PushToOrigin) {
|
||||
Write-Host "`n📤 Pushing current branch to origin..." -ForegroundColor Yellow
|
||||
$currentBranch = git rev-parse --abbrev-ref HEAD
|
||||
git push origin $currentBranch
|
||||
Write-Host "✅ Pushed $currentBranch to origin" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n💡 Tip: Use --push-to-origin flag to push current branch to origin" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host "`n✅ Sync complete!" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "`n❌ Error: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue