From d07681e54a4202e2dbc0517b3eb1df9ee1fdce08 Mon Sep 17 00:00:00 2001 From: Mitriyweb <4174322+Mitriyweb@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:32:21 +0000 Subject: [PATCH] fix: robustify installation script and release process - Improved `scripts/install.sh` with better error handling, 404 detection, and file verification. - Updated `scripts/macos/create-dmg.sh` to properly create DMG files using `hdiutil`. - Standardized `scripts/macos/create-archive.sh` for tarball creation. - Modified `scripts/publish-release.sh` to build and upload both DMG and tarball assets with unified checksums. - Updated `package.json` scripts to reflect the new installer build process. - Ensured cross-platform compatibility and added post-installation attribute cleanup on macOS. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- package.json | 6 ++- scripts/install.sh | 86 +++++++++++++++++++++++---------- scripts/macos/create-archive.sh | 26 +++++----- scripts/macos/create-dmg.sh | 66 ++++++++++++------------- scripts/publish-release.sh | 56 +++++++++++++-------- 5 files changed, 144 insertions(+), 96 deletions(-) diff --git a/package.json b/package.json index a1f7b61..3ff1a55 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,11 @@ "build:rust:beta": "cargo build --release --features beta", "build:app": "./scripts/macos/build-macos-app.sh prod", "build:app:beta": "./scripts/macos/build-macos-app.sh beta", + "build:archive": "./scripts/macos/create-archive.sh $(cat .version)", + "build:dmg": "./scripts/macos/create-dmg.sh $(cat .version)", "build:universal": "./scripts/macos/build-universal.sh", - "build:installer": "bun run build:app && ./scripts/macos/create-dmg.sh $(cat .version)", - "build:installer:beta": "bun run build:app:beta && ./scripts/macos/create-dmg.sh $(cat .version)", + "build:installer": "bun run build:app && bun run build:archive && bun run build:dmg", + "build:installer:beta": "bun run build:app:beta && bun run build:archive && bun run build:dmg", "audit": "bun audit", "prepare": "git config --unset-all --local core.hooksPath || true && prek install || true", "bump": "bash scripts/bump-version.sh" diff --git a/scripts/install.sh b/scripts/install.sh index 9b4cac7..ac4a03d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -26,11 +26,12 @@ if [[ "$OSTYPE" != "darwin"* ]]; then fi # Get the latest release version -echo "📥 Fetching latest release..." -LATEST_RELEASE=$(curl -s https://api.github.com/repos/$REPO/releases/latest | grep '"tag_name"' | sed -E 's/.*"v([^"]+)".*/\1/') +echo "📥 Fetching latest release information..." +# Use a more robust way to get the latest tag +LATEST_RELEASE=$(curl -sL https://api.github.com/repos/$REPO/releases/latest | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/') if [ -z "$LATEST_RELEASE" ]; then - echo -e "${RED}❌ Error: Could not fetch latest release${NC}" + echo -e "${RED}❌ Error: Could not fetch latest release version from GitHub API${NC}" exit 1 fi @@ -38,26 +39,43 @@ echo " Latest version: $LATEST_RELEASE" # Try to download tar.gz archive (preferred for script-based install) ARCHIVE_URL="https://github.com/$REPO/releases/download/v$LATEST_RELEASE/chase-ai-$LATEST_RELEASE-macos.tar.gz" -ARCHIVE_FILE="/tmp/chase-ai-$LATEST_RELEASE.tar.gz" +ARCHIVE_FILE="/tmp/chase-ai-$LATEST_RELEASE-macos.tar.gz" echo "📦 Downloading ChaseAI $LATEST_RELEASE..." -if ! curl -sL -f -o "$ARCHIVE_FILE" "$ARCHIVE_URL"; then +DOWNLOAD_SUCCESS=false + +# Use -f to fail on HTTP errors +if curl -sL -f -o "$ARCHIVE_FILE" "$ARCHIVE_URL"; then + DOWNLOAD_SUCCESS=true + IS_DMG=false +else echo -e "${YELLOW}⚠ Warning: Failed to download tar.gz, trying DMG fallback...${NC}" DMG_URL="https://github.com/$REPO/releases/download/v$LATEST_RELEASE/chase-ai-$LATEST_RELEASE-macos.dmg" - ARCHIVE_FILE="/tmp/chase-ai-$LATEST_RELEASE.dmg" - if ! curl -sL -f -o "$ARCHIVE_FILE" "$DMG_URL"; then - echo -e "${RED}❌ Error: Failed to download both tar.gz and DMG${NC}" - exit 1 + ARCHIVE_FILE="/tmp/chase-ai-$LATEST_RELEASE-macos.dmg" + if curl -sL -f -o "$ARCHIVE_FILE" "$DMG_URL"; then + DOWNLOAD_SUCCESS=true + IS_DMG=true + else + # Try without 'v' prefix in download URL if it failed + ARCHIVE_URL_NO_V="https://github.com/$REPO/releases/download/$LATEST_RELEASE/chase-ai-$LATEST_RELEASE-macos.tar.gz" + ARCHIVE_FILE="/tmp/chase-ai-$LATEST_RELEASE-macos.tar.gz" + if curl -sL -f -o "$ARCHIVE_FILE" "$ARCHIVE_URL_NO_V"; then + DOWNLOAD_SUCCESS=true + IS_DMG=false + fi fi - IS_DMG=true -else - IS_DMG=false +fi + +if [ "$DOWNLOAD_SUCCESS" = false ]; then + echo -e "${RED}❌ Error: Failed to download both tar.gz and DMG for version $LATEST_RELEASE${NC}" + echo " Check if the assets exist at: https://github.com/$REPO/releases/latest" + exit 1 fi # Verify file is not empty and not a 404 page FILE_SIZE=$(stat -f%z "$ARCHIVE_FILE" 2>/dev/null || stat -c%s "$ARCHIVE_FILE" 2>/dev/null || echo "0") -if [ "$FILE_SIZE" -lt 1000 ]; then - echo -e "${RED}❌ Error: Downloaded file is too small ($FILE_SIZE bytes). It might be a 404 page.${NC}" +if [ "$FILE_SIZE" -lt 10000 ]; then + echo -e "${RED}❌ Error: Downloaded file is too small ($FILE_SIZE bytes). Download might be corrupted or asset is missing.${NC}" rm -f "$ARCHIVE_FILE" exit 1 fi @@ -65,18 +83,15 @@ fi # Verify checksum if available CHECKSUMS_URL="https://github.com/$REPO/releases/download/v$LATEST_RELEASE/checksums.sha256" CHECKSUMS_FILE="/tmp/checksums.sha256" -if curl -s -f "$CHECKSUMS_URL" > "$CHECKSUMS_FILE" 2>/dev/null && [ -s "$CHECKSUMS_FILE" ]; then +if curl -sL -f -o "$CHECKSUMS_FILE" "$CHECKSUMS_URL" 2>/dev/null || curl -sL -f -o "$CHECKSUMS_FILE" "https://github.com/$REPO/releases/download/$LATEST_RELEASE/checksums.sha256" 2>/dev/null; then echo "🔐 Verifying checksum..." - FILENAME=$(basename "$ARCHIVE_FILE") EXPECTED_CHECKSUM=$(grep "$FILENAME" "$CHECKSUMS_FILE" | awk '{print $1}') if [ -z "$EXPECTED_CHECKSUM" ]; then echo -e "${YELLOW}⚠ Warning: Could not find checksum for $FILENAME in checksums.sha256${NC}" else - # Calculate actual checksum ACTUAL_CHECKSUM=$(shasum -a 256 "$ARCHIVE_FILE" | awk '{print $1}') - if [ "$EXPECTED_CHECKSUM" = "$ACTUAL_CHECKSUM" ]; then echo -e "${GREEN}✓ Checksum verified${NC}" else @@ -88,7 +103,7 @@ if curl -s -f "$CHECKSUMS_URL" > "$CHECKSUMS_FILE" 2>/dev/null && [ -s "$CHECKSU fi fi else - echo -e "${YELLOW}⚠ Warning: Could not download checksums.sha256 file, skipping verification${NC}" + echo -e "${YELLOW}⚠ Warning: Could not download checksums.sha256, skipping verification${NC}" fi # Extract and install @@ -97,27 +112,45 @@ MOUNT_POINT=$(mktemp -d) if [ "$IS_DMG" = true ]; then echo " Mounting DMG..." - hdiutil attach "$ARCHIVE_FILE" -mountpoint "$MOUNT_POINT" -nobrowse + if ! hdiutil attach "$ARCHIVE_FILE" -mountpoint "$MOUNT_POINT" -nobrowse -quiet; then + echo -e "${RED}❌ Error: Failed to mount DMG. The file might be corrupted.${NC}" + rm -f "$ARCHIVE_FILE" + exit 1 + fi SOURCE_PATH="$MOUNT_POINT/$APP_NAME" else echo " Extracting archive..." - tar -xzf "$ARCHIVE_FILE" -C "$MOUNT_POINT" - SOURCE_PATH="$MOUNT_POINT/$APP_NAME" + if ! tar -xzf "$ARCHIVE_FILE" -C "$MOUNT_POINT"; then + echo -e "${RED}❌ Error: Failed to extract tar.gz archive. The file might be corrupted.${NC}" + rm -f "$ARCHIVE_FILE" + exit 1 + fi + # Sometimes the app is inside a subfolder in the archive + SOURCE_PATH=$(find "$MOUNT_POINT" -name "$APP_NAME" -maxdepth 2 | head -n 1) fi # Copy app to Applications +if [ -z "$SOURCE_PATH" ] || [ ! -d "$SOURCE_PATH" ]; then + echo -e "${RED}❌ Error: Could not find $APP_NAME in the downloaded package${NC}" + # Cleanup + if [ "$IS_DMG" = true ]; then hdiutil detach "$MOUNT_POINT" -quiet || true; fi + rm -f "$ARCHIVE_FILE" + rm -rf "$MOUNT_POINT" + exit 1 +fi + echo "📋 Copying to $INSTALL_DIR..." if [ -d "$INSTALL_DIR/$APP_NAME" ]; then echo " Removing existing installation..." rm -rf "$INSTALL_DIR/$APP_NAME" fi -cp -r "$SOURCE_PATH" "$INSTALL_DIR/" +cp -R "$SOURCE_PATH" "$INSTALL_DIR/" # Cleanup if [ "$IS_DMG" = true ]; then echo " Unmounting DMG..." - hdiutil detach "$MOUNT_POINT" + hdiutil detach "$MOUNT_POINT" -quiet fi rm -f "$ARCHIVE_FILE" @@ -125,6 +158,9 @@ rm -rf "$MOUNT_POINT" # Verify installation if [ -d "$INSTALL_DIR/$APP_NAME" ]; then + # Remove quarantine attributes to allow the app to run + xattr -dr com.apple.quarantine "$INSTALL_DIR/$APP_NAME" 2>/dev/null || true + echo -e "${GREEN}✅ Installation successful!${NC}" echo "" echo "📍 ChaseAI installed to: $INSTALL_DIR/$APP_NAME" @@ -134,6 +170,6 @@ if [ -d "$INSTALL_DIR/$APP_NAME" ]; then echo "" echo "💡 Or use Spotlight search (Cmd+Space) and type 'ChaseAI'" else - echo -e "${RED}❌ Error: Installation failed${NC}" + echo -e "${RED}❌ Error: Installation failed - $APP_NAME not found in $INSTALL_DIR${NC}" exit 1 fi diff --git a/scripts/macos/create-archive.sh b/scripts/macos/create-archive.sh index 8a8c239..3069400 100644 --- a/scripts/macos/create-archive.sh +++ b/scripts/macos/create-archive.sh @@ -2,7 +2,6 @@ # Create tar.gz archive for macOS # This script packages the ChaseAI application into a tar.gz archive -# as a fallback to DMG creation set -e @@ -10,12 +9,12 @@ set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$SCRIPT_DIR/../.." -echo "📦 Creating tar.gz archive..." +echo "📦 Creating macOS tar.gz archive..." # Configuration APP_NAME="ChaseAI" BINARY_NAME="chase-ai" -VERSION="${1:-0.1.0}" +VERSION="${1:-$(grep '^version =' Cargo.toml | head -1 | cut -d '"' -f2)}" RELEASE_DIR="target/release" APP_BUNDLE="${RELEASE_DIR}/${APP_NAME}.app" ARCHIVE_NAME="${BINARY_NAME}-${VERSION}-macos.tar.gz" @@ -28,6 +27,7 @@ echo " Archive Path: ${ARCHIVE_PATH}" # Verify app bundle exists if [ ! -d "${APP_BUNDLE}" ]; then echo "❌ Error: App bundle not found at ${APP_BUNDLE}" + echo " Please run scripts/macos/build-macos-app.sh first" exit 1 fi @@ -35,6 +35,7 @@ echo "✓ App bundle found" # Create tar.gz archive echo "🎨 Creating tar.gz archive..." +rm -f "${ARCHIVE_PATH}" cd "${RELEASE_DIR}" tar -czf "${ARCHIVE_NAME}" "${APP_NAME}.app" cd - > /dev/null @@ -45,11 +46,7 @@ if [ ! -f "${ARCHIVE_PATH}" ]; then exit 1 fi -echo "✓ Archive file created" - -# Check archive file size -ARCHIVE_SIZE=$(stat -f%z "${ARCHIVE_PATH}" 2>/dev/null || stat -c%s "${ARCHIVE_PATH}" 2>/dev/null || echo "unknown") -echo " Size: ${ARCHIVE_SIZE} bytes" +echo "✓ Archive created successfully" # Generate checksums echo "🔐 Generating checksums..." @@ -57,10 +54,15 @@ SHA256_FILE="${ARCHIVE_PATH}.sha256" shasum -a 256 "${ARCHIVE_PATH}" > "${SHA256_FILE}" # Display checksum -echo " SHA256: $(cat ${SHA256_FILE})" +echo " SHA256: $(cat ${SHA256_FILE} | awk '{print $1}')" # Display file info -ARCHIVE_SIZE=$(du -h "${ARCHIVE_PATH}" | cut -f1) +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHIVE_SIZE=$(du -h "${ARCHIVE_PATH}" | cut -f1) +else + ARCHIVE_SIZE=$(ls -lh "${ARCHIVE_PATH}" | awk '{print $5}') +fi + echo "" echo "✅ Archive created successfully!" echo " Name: ${ARCHIVE_NAME}" @@ -68,7 +70,3 @@ echo " Size: ${ARCHIVE_SIZE}" echo " Path: ${ARCHIVE_PATH}" echo " Checksum: ${SHA256_FILE}" echo "" -echo "To extract the archive:" -echo " tar -xzf ${ARCHIVE_NAME}" -echo " cp -r ${APP_NAME}.app /Applications/" -echo "" diff --git a/scripts/macos/create-dmg.sh b/scripts/macos/create-dmg.sh index 5a39cab..5fd0be0 100755 --- a/scripts/macos/create-dmg.sh +++ b/scripts/macos/create-dmg.sh @@ -1,7 +1,7 @@ #!/bin/bash -# Create installer for macOS -# Creates a tar.gz archive of the ChaseAI application +# Create DMG installer for macOS +# This script packages the ChaseAI application into a DMG file set -e @@ -9,71 +9,65 @@ set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$SCRIPT_DIR/../.." -echo "📦 Creating macOS installer..." +echo "📦 Creating macOS DMG installer..." # Configuration APP_NAME="ChaseAI" BINARY_NAME="chase-ai" -VERSION="${1:-0.1.0}" +VERSION="${1:-$(grep '^version =' Cargo.toml | head -1 | cut -d '"' -f2)}" RELEASE_DIR="target/release" APP_BUNDLE="${RELEASE_DIR}/${APP_NAME}.app" -ARCHIVE_NAME="${BINARY_NAME}-${VERSION}-macos.tar.gz" -ARCHIVE_PATH="${RELEASE_DIR}/${ARCHIVE_NAME}" +DMG_NAME="${BINARY_NAME}-${VERSION}-macos.dmg" +DMG_PATH="${RELEASE_DIR}/${DMG_NAME}" echo " Version: ${VERSION}" echo " App Bundle: ${APP_BUNDLE}" +echo " DMG Path: ${DMG_PATH}" # Verify app bundle exists if [ ! -d "${APP_BUNDLE}" ]; then echo "❌ Error: App bundle not found at ${APP_BUNDLE}" echo " Please run scripts/macos/build-macos-app.sh first" - ls -la "${RELEASE_DIR}/" || echo "Release directory doesn't exist" exit 1 fi echo "✓ App bundle found" -# Create tar.gz archive -echo "🎨 Creating tar.gz archive..." -cd "${RELEASE_DIR}" -tar -czf "${ARCHIVE_NAME}" "${APP_NAME}.app" -cd - > /dev/null - -# Verify archive was created -if [ ! -f "${ARCHIVE_PATH}" ]; then - echo "❌ Error: Archive creation failed" - exit 1 +# Check if running on macOS +if [[ "$OSTYPE" != "darwin"* ]]; then + echo "⚠️ Warning: hdiutil is only available on macOS. Skipping DMG creation on this platform." + # On non-macOS, we don't want to fail the build if it's just a check, + # but for a real release it must be run on macOS. + exit 0 fi -echo "✓ Archive created successfully" - -# Check archive file size -ARCHIVE_SIZE=$(stat -f%z "${ARCHIVE_PATH}" 2>/dev/null || stat -c%s "${ARCHIVE_PATH}" 2>/dev/null || echo "0") -echo " Size: ${ARCHIVE_SIZE} bytes" +# Create DMG +echo "🎨 Creating DMG..." +rm -f "${DMG_PATH}" +hdiutil create -volname "${APP_NAME}" -srcfolder "${APP_BUNDLE}" -ov -format UDZO "${DMG_PATH}" -if [ "${ARCHIVE_SIZE}" -lt 1000000 ]; then - echo "⚠️ Warning: Archive file is very small (${ARCHIVE_SIZE} bytes)" +# Verify DMG was created +if [ ! -f "${DMG_PATH}" ]; then + echo "❌ Error: DMG creation failed" exit 1 fi +echo "✓ DMG created successfully" + # Generate checksums echo "🔐 Generating checksums..." -SHA256_FILE="${ARCHIVE_PATH}.sha256" -shasum -a 256 "${ARCHIVE_PATH}" > "${SHA256_FILE}" +SHA256_FILE="${DMG_PATH}.sha256" +shasum -a 256 "${DMG_PATH}" > "${SHA256_FILE}" # Display checksum -echo " SHA256: $(cat ${SHA256_FILE})" +echo " SHA256: $(cat ${SHA256_FILE} | awk '{print $1}')" # Display file info -ARCHIVE_SIZE=$(du -h "${ARCHIVE_PATH}" | cut -f1) +DMG_SIZE=$(du -h "${DMG_PATH}" | cut -f1) echo "" -echo "✅ Installer created successfully!" -echo " Name: ${ARCHIVE_NAME}" -echo " Size: ${ARCHIVE_SIZE}" -echo " Path: ${ARCHIVE_PATH}" +echo "✅ DMG created successfully!" +echo " Name: ${DMG_NAME}" +echo " Size: ${DMG_SIZE}" +echo " Path: ${DMG_PATH}" echo " Checksum: ${SHA256_FILE}" echo "" -echo "To extract and install:" -echo " tar -xzf ${ARCHIVE_NAME}" -echo " cp -r ${APP_NAME}.app /Applications/" -echo "" diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index 1312b14..a9cb9f6 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -1,7 +1,7 @@ #!/bin/bash # Publish Release Script -# Builds the app, creates a DMG, and uploads it to GitHub Releases +# Builds the app, creates DMG and tar.gz, and uploads them to GitHub Releases set -e @@ -23,8 +23,9 @@ if ! gh auth status &> /dev/null; then exit 1 fi -# 1. Bump version (optional but recommended) -echo "Current version in Cargo.toml: $(grep '^version =' Cargo.toml | head -1 | cut -d '"' -f2)" +# 1. Get version +VERSION=$(grep '^version =' Cargo.toml | head -1 | cut -d '"' -f2) +echo "Current version in Cargo.toml: $VERSION" read -p "Enter new version (or press Enter to keep current): " NEW_VERSION if [ ! -z "$NEW_VERSION" ]; then @@ -46,30 +47,42 @@ if [ ! -z "$NEW_VERSION" ]; then git commit -m "chore: bump version to $NEW_VERSION" git push VERSION=$NEW_VERSION -else - VERSION=$(grep '^version =' Cargo.toml | head -1 | cut -d '"' -f2) fi TAG="v$VERSION" -echo "🚀 preparing release $TAG..." +echo "🚀 Preparing release $TAG..." # 2. Build App (Prod) echo "🔨 Building Production App..." bun run build:app -# 3. Create DMG -echo "📦 Creating DMG..." -./scripts/macos/create-dmg.sh "$VERSION" +# 3. Create Installers +echo "📦 Creating Installers..." +# These scripts handle their own platform checks +./scripts/macos/create-dmg.sh "$VERSION" || true +./scripts/macos/create-archive.sh "$VERSION" DMG_FILE="target/release/chase-ai-$VERSION-macos.dmg" +ARCHIVE_FILE="target/release/chase-ai-$VERSION-macos.tar.gz" + +# 4. Generate Unified Checksums +echo "🔐 Generating unified checksums..." +CHECKSUMS_FILE="target/release/checksums.sha256" +rm -f "$CHECKSUMS_FILE" + +# Use a subshell to avoid changing directory in the main script +( + cd target/release + if [ -f "chase-ai-$VERSION-macos.dmg" ]; then + shasum -a 256 "chase-ai-$VERSION-macos.dmg" >> "../../$CHECKSUMS_FILE" + fi + if [ -f "chase-ai-$VERSION-macos.tar.gz" ]; then + shasum -a 256 "chase-ai-$VERSION-macos.tar.gz" >> "../../$CHECKSUMS_FILE" + fi +) -if [ ! -f "$DMG_FILE" ]; then - echo "❌ Error: DMG file not found at $DMG_FILE" - exit 1 -fi - -# 4. Create GitHub Release +# 5. Create GitHub Release echo "⬆️ Creating GitHub Release..." # Check if tag exists locally @@ -81,17 +94,22 @@ else git push origin "$TAG" fi +# Prepare list of files to upload +UPLOAD_FILES=("$CHECKSUMS_FILE" "scripts/install.sh") +if [ -f "$DMG_FILE" ]; then UPLOAD_FILES+=("$DMG_FILE"); fi +if [ -f "$ARCHIVE_FILE" ]; then UPLOAD_FILES+=("$ARCHIVE_FILE"); fi + +echo " Uploading assets: ${UPLOAD_FILES[*]}" + # Upload assets -# Only if release doesn't exist, create it. If it does, edit it (or just upload). if gh release view "$TAG" &> /dev/null; then echo " Release $TAG exists. Uploading assets..." - gh release upload "$TAG" "$DMG_FILE" "scripts/install.sh" --clobber + gh release upload "$TAG" "${UPLOAD_FILES[@]}" --clobber else echo " Creating new release $TAG..." - gh release create "$TAG" "$DMG_FILE" "scripts/install.sh" --title "ChaseAI $VERSION" --notes "Release $VERSION" + gh release create "$TAG" "${UPLOAD_FILES[@]}" --title "ChaseAI $VERSION" --notes "Release $VERSION" fi echo "" echo "✅ Release published successfully!" -echo " Download URL: https://github.com/Mitriyweb/ChaseAI/releases/download/$TAG/$(basename $DMG_FILE)" echo " Install Command: curl -sL https://github.com/Mitriyweb/ChaseAI/releases/latest/download/install.sh | bash"