feat(delight): add Yoink Receipt, weekly digest, and site change monitor #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Yoinkit | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-macos: | |
| runs-on: macos-latest | |
| strategy: | |
| matrix: | |
| target: [aarch64-apple-darwin, x86_64-apple-darwin] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| - name: Install frontend dependencies | |
| run: pnpm install | |
| - name: Build wget from source (static) | |
| run: | | |
| # Install build dependencies | |
| brew install autoconf automake pkg-config | |
| # Download wget source | |
| curl -L -o wget-1.24.5.tar.gz https://ftp.gnu.org/gnu/wget/wget-1.24.5.tar.gz | |
| tar xzf wget-1.24.5.tar.gz | |
| cd wget-1.24.5 | |
| # Build with static linking - bundle OpenSSL and other deps | |
| # Use Apple's SecureTransport instead of OpenSSL to avoid dynamic deps | |
| ./configure \ | |
| --with-ssl=openssl \ | |
| --with-included-regex \ | |
| --without-libidn \ | |
| --without-libuuid \ | |
| --disable-nls \ | |
| --disable-pcre \ | |
| --disable-pcre2 \ | |
| OPENSSL_CFLAGS="$(pkg-config --cflags openssl 2>/dev/null || echo '-I/opt/homebrew/opt/openssl@3/include')" \ | |
| OPENSSL_LIBS="$(pkg-config --libs --static openssl 2>/dev/null || echo '-L/opt/homebrew/opt/openssl@3/lib -lssl -lcrypto')" \ | |
| LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" \ | |
| CFLAGS="-I/opt/homebrew/opt/openssl@3/include" | |
| make -j$(sysctl -n hw.ncpu) | |
| # Copy the built binary | |
| mkdir -p ../apps/desktop/src-tauri/bin | |
| cp src/wget ../apps/desktop/src-tauri/bin/wget | |
| chmod +x ../apps/desktop/src-tauri/bin/wget | |
| cd .. | |
| # Recursively bundle ALL dylib dependencies (including transitive) | |
| WGET_BIN=apps/desktop/src-tauri/bin/wget | |
| LIBS_DIR=apps/desktop/src-tauri/bin/libs | |
| mkdir -p "$LIBS_DIR" | |
| # Function to get non-system dylib deps of a binary | |
| get_deps() { | |
| otool -L "$1" | awk '{print $1}' | tail -n +2 | grep -v '/usr/lib\|/System\|@executable_path\|@rpath\|@loader_path' | |
| } | |
| # Recursively collect all dylibs needed | |
| QUEUE=$(get_deps "$WGET_BIN") | |
| PROCESSED="" | |
| while [ -n "$QUEUE" ]; do | |
| NEXT_QUEUE="" | |
| for lib in $QUEUE; do | |
| LIBNAME=$(basename "$lib") | |
| # Skip if already processed | |
| echo "$PROCESSED" | grep -q "$LIBNAME" && continue | |
| PROCESSED="$PROCESSED $LIBNAME" | |
| if [ -f "$lib" ]; then | |
| echo "Bundling: $lib -> $LIBS_DIR/$LIBNAME" | |
| cp "$lib" "$LIBS_DIR/$LIBNAME" | |
| chmod 644 "$LIBS_DIR/$LIBNAME" | |
| # Get this lib's own deps and add to queue | |
| SUB_DEPS=$(get_deps "$lib") | |
| for sub in $SUB_DEPS; do | |
| SUB_NAME=$(basename "$sub") | |
| echo "$PROCESSED" | grep -q "$SUB_NAME" || NEXT_QUEUE="$NEXT_QUEUE $sub" | |
| done | |
| else | |
| echo "WARNING: dependency not found: $lib" | |
| fi | |
| done | |
| QUEUE="$NEXT_QUEUE" | |
| done | |
| # Rewrite all paths in the wget binary | |
| for lib in $(get_deps "$WGET_BIN"); do | |
| LIBNAME=$(basename "$lib") | |
| install_name_tool -change "$lib" "@executable_path/libs/$LIBNAME" "$WGET_BIN" | |
| done | |
| # Rewrite all paths in every bundled dylib | |
| for bundled_lib in "$LIBS_DIR"/*.dylib; do | |
| # Set its own id | |
| install_name_tool -id "@executable_path/libs/$(basename "$bundled_lib")" "$bundled_lib" | |
| # Rewrite references to other bundled libs | |
| for dep in $(otool -L "$bundled_lib" | awk '{print $1}' | tail -n +2 | grep -v '/usr/lib\|/System\|@executable_path\|@rpath\|@loader_path'); do | |
| DEP_NAME=$(basename "$dep") | |
| if [ -f "$LIBS_DIR/$DEP_NAME" ]; then | |
| install_name_tool -change "$dep" "@executable_path/libs/$DEP_NAME" "$bundled_lib" | |
| fi | |
| done | |
| done | |
| # Ad-hoc codesign everything | |
| for f in "$LIBS_DIR"/*.dylib; do | |
| codesign --force --sign - "$f" | |
| done | |
| codesign --force --sign - "$WGET_BIN" | |
| # Strip quarantine | |
| xattr -cr apps/desktop/src-tauri/bin/ | |
| # Verify — no non-system, non-@executable_path deps should remain | |
| echo "=== Verifying wget dependencies ===" | |
| otool -L "$WGET_BIN" | |
| echo "=== Verifying bundled libs ===" | |
| for f in "$LIBS_DIR"/*.dylib; do | |
| echo "--- $(basename "$f") ---" | |
| otool -L "$f" | |
| done | |
| echo "=== Checking for any remaining Homebrew paths ===" | |
| REMAINING=$(otool -L "$WGET_BIN" "$LIBS_DIR"/*.dylib 2>/dev/null | grep '/opt/homebrew' || true) | |
| if [ -n "$REMAINING" ]; then | |
| echo "ERROR: Homebrew paths still found!" | |
| echo "$REMAINING" | |
| exit 1 | |
| else | |
| echo "All clear — no Homebrew paths remain." | |
| fi | |
| - name: Bundle yt-dlp and ffmpeg | |
| run: | | |
| BIN_DIR=apps/desktop/src-tauri/bin | |
| # Download yt-dlp (standalone binary) | |
| echo "=== Downloading yt-dlp ===" | |
| if [[ "${{ matrix.target }}" == "aarch64-apple-darwin" ]]; then | |
| curl -L -o "$BIN_DIR/yt-dlp" "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos" | |
| else | |
| curl -L -o "$BIN_DIR/yt-dlp" "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos" | |
| fi | |
| chmod +x "$BIN_DIR/yt-dlp" | |
| # Download ffmpeg (static build) | |
| echo "=== Downloading ffmpeg ===" | |
| if [[ "${{ matrix.target }}" == "aarch64-apple-darwin" ]]; then | |
| curl -L -o ffmpeg.zip "https://evermeet.cx/ffmpeg/getrelease/zip" | |
| else | |
| curl -L -o ffmpeg.zip "https://evermeet.cx/ffmpeg/getrelease/zip" | |
| fi | |
| unzip -o ffmpeg.zip -d "$BIN_DIR/" | |
| chmod +x "$BIN_DIR/ffmpeg" | |
| rm ffmpeg.zip | |
| # Codesign both binaries | |
| codesign --force --sign - "$BIN_DIR/yt-dlp" | |
| codesign --force --sign - "$BIN_DIR/ffmpeg" | |
| # Strip quarantine attributes | |
| xattr -cr "$BIN_DIR/yt-dlp" | |
| xattr -cr "$BIN_DIR/ffmpeg" | |
| echo "=== Verifying binaries ===" | |
| file "$BIN_DIR/yt-dlp" | |
| file "$BIN_DIR/ffmpeg" | |
| ls -la "$BIN_DIR/" | |
| - name: Build Tauri app | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: "" | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| cd apps/desktop | |
| pnpm exec tauri build --target "$TARGET" | |
| - name: Fix code signing on bundled binaries | |
| run: | | |
| # Find the built .app bundle | |
| APP_PATH=$(find apps/desktop/src-tauri/target -name "Yoinkit.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Found app at: $APP_PATH" | |
| # Strip quarantine from everything | |
| xattr -cr "$APP_PATH" | |
| # Re-sign bundled libs first, then wget, then the whole app | |
| LIBS_IN_APP="$APP_PATH/Contents/Resources/bin/libs" | |
| if [ -d "$LIBS_IN_APP" ]; then | |
| find "$LIBS_IN_APP" -name "*.dylib" -exec codesign --force --sign - {} \; | |
| fi | |
| WGET_IN_APP="$APP_PATH/Contents/Resources/bin/wget" | |
| if [ -f "$WGET_IN_APP" ]; then | |
| codesign --force --sign - "$WGET_IN_APP" | |
| fi | |
| YTDLP_IN_APP="$APP_PATH/Contents/Resources/bin/yt-dlp" | |
| if [ -f "$YTDLP_IN_APP" ]; then | |
| codesign --force --sign - "$YTDLP_IN_APP" | |
| fi | |
| FFMPEG_IN_APP="$APP_PATH/Contents/Resources/bin/ffmpeg" | |
| if [ -f "$FFMPEG_IN_APP" ]; then | |
| codesign --force --sign - "$FFMPEG_IN_APP" | |
| fi | |
| # Re-sign the entire app bundle | |
| codesign --force --deep --sign - "$APP_PATH" | |
| fi | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: yoinkit-${{ matrix.target }} | |
| path: | | |
| apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg | |
| apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.app | |
| create-release: | |
| needs: build-macos | |
| runs-on: macos-latest | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download arm64 artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: yoinkit-aarch64-apple-darwin | |
| path: artifacts/arm64 | |
| - name: Download x86_64 artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: yoinkit-x86_64-apple-darwin | |
| path: artifacts/x86_64 | |
| - name: List artifacts | |
| run: find artifacts -type f | head -20 | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| artifacts/arm64/**/*.dmg | |
| artifacts/x86_64/**/*.dmg | |
| generate_release_notes: true | |
| draft: false |