Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
59b1ff6
refactor: replace PipeMessage with InboundEvent for improved event ha…
rgdevment Apr 21, 2026
ad65856
feat: implement macOS support with cursor and tray functionality
rgdevment Apr 21, 2026
24ad3c4
feat: implement macOS support and refactor platform bindings for impr…
rgdevment Apr 21, 2026
4074314
refactor: improve code formatting for better readability in bootstrap…
rgdevment Apr 21, 2026
d8d98f7
feat(macos): scaffold runner + bundle config (M1)
rgdevment Apr 21, 2026
baf6ac9
feat(macos): implement inbound events channel and update AppDelegate …
rgdevment Apr 21, 2026
b44492c
feat(macos): add native channels for browser detection, icon extracti…
rgdevment Apr 21, 2026
a28685a
feat(macos): add macOS tray controller implementation using tray_mana…
rgdevment Apr 21, 2026
d162273
feat(macos): add macOS tray icons and bindings for improved tray func…
rgdevment Apr 21, 2026
ffc1279
fix(macos): correct argument order for launching browsers in MacLaunc…
rgdevment Apr 21, 2026
87e3153
feat(macos): update icon path separators and improve macOS layout han…
rgdevment Apr 21, 2026
07cc076
feat(macos): enhance local file handling and validation for macOS and…
rgdevment Apr 21, 2026
c46e3a2
refactor(macos): streamline local file handling and logging in URL pr…
rgdevment Apr 21, 2026
6885719
style(macos): format code for improved readability in various files
rgdevment Apr 21, 2026
aa14e16
feat(macos): enhance macOS window management and update localization …
rgdevment Apr 21, 2026
2224fd2
fix(macos): adjust window management for macOS and update startup tex…
rgdevment Apr 21, 2026
6553449
feat(tests): add comprehensive test suite for macOS platform features…
rgdevment Apr 21, 2026
cd51c95
refactor(macos): improve code formatting and readability in various f…
rgdevment Apr 21, 2026
c4621b2
fix(tests): update localization tests to ensure proper widget tree co…
rgdevment Apr 21, 2026
c8f39f3
fix(macos): improve background handling in bootstrap and enhance exit…
rgdevment Apr 21, 2026
ce89073
refactor(picker): remove logging and clean up build method
rgdevment Apr 21, 2026
d0f57f0
feat(readme): add macOS support details and update platform information
rgdevment Apr 21, 2026
12fc7ea
fix(app): enhance window blur handling and improve exit behavior in s…
rgdevment Apr 21, 2026
488d8a4
fix(macos): change StreamController to broadcast for inbound events a…
rgdevment Apr 21, 2026
22d58ec
fix(logging): manage log subscription lifecycle and handle potential …
rgdevment Apr 21, 2026
c2bc209
fix(tests): format test case for bootstrap suite skipping
rgdevment Apr 21, 2026
88b119f
feat(dev-cleanup): add macOS cleanup script to remove user-scoped traces
rgdevment Apr 21, 2026
7c3d0c6
fix(macos): defer 'ready' signal until a listener subscribes to preve…
rgdevment Apr 21, 2026
1ef287b
fix(tests): skip bootstrap test for integration-level validation on m…
rgdevment Apr 21, 2026
2f3433b
feat(macos): add macOS support and update documentation for platform …
rgdevment Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,29 @@ on:
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
markdown:
name: Markdown Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Lint Markdown files
uses: DavidAnson/markdownlint-cli2-action@v19
with:
globs: |
*.md
.github/**/*.md
config: ".markdownlint.yaml"
continue-on-error: true

quality:
name: Quality
runs-on: ubuntu-latest
Expand All @@ -17,6 +39,14 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-pub-

- name: Install melos
run: dart pub global activate melos
Expand All @@ -30,6 +60,12 @@ jobs:
- name: Analyze
run: melos analyze

- name: Check auto-fixable issues
run: |
OUTPUT=$(dart fix --dry-run . 2>&1)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "Nothing to fix!" || { echo "::error::Auto-fixable issues found. Run 'dart fix --apply' and commit."; exit 1; }

- name: Run core tests with coverage
working-directory: packages/core
run: |
Expand Down Expand Up @@ -90,6 +126,14 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-pub-

- name: Resolve dependencies
run: |
Expand Down Expand Up @@ -117,17 +161,41 @@ jobs:
-Dsonar.coverage.exclusions=**/main.dart,**/app.dart,**/platform/**,**/update_service.dart,**/picker_window.dart,**/settings_window.dart,**/settings_view.dart,**/title_bar.dart

build:
name: Build (Windows)
name: Build (${{ matrix.name }})
needs: quality
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
name: Windows
build_cmd: flutter build windows --release
- os: macos-15
name: macOS
build_cmd: flutter build macos --release
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-pub-

- name: Install melos
run: dart pub global activate melos

- name: Bootstrap workspace
run: melos bootstrap

- name: Build Windows release
- name: Build ${{ matrix.name }} release
working-directory: apps/linkunbound
run: flutter build windows --release
run: ${{ matrix.build_cmd }}
233 changes: 233 additions & 0 deletions .github/workflows/release-mac.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
name: Release (macOS)

on:
workflow_call:
inputs:
version:
required: true
type: string
workflow_dispatch:
inputs:
version:
description: "Version to use (e.g. 2.0.0). Defaults to 2.0.0-dev"
required: false
default: "2.0.0-dev"

permissions:
contents: read

jobs:
build-macos:
runs-on: macos-15
timeout-minutes: 45
name: Build macOS (Universal)

env:
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.ref_name }}

- name: Resolve version
id: get_version
run: |
VERSION="${{ inputs.version }}"

IS_PRERELEASE="false"
if [[ "$VERSION" == *-* ]]; then
IS_PRERELEASE="true"
fi

BUILD_NAME="${VERSION%-*}"
BUILD_NUMBER=$(echo "$BUILD_NAME" | awk -F. '{printf "%d%03d%03d", $1, $2, $3}')

echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
echo "BUILD_NAME=$BUILD_NAME" >> "$GITHUB_OUTPUT"
echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
echo "IS_PRERELEASE=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION Build: $BUILD_NAME ($BUILD_NUMBER) PreRelease: $IS_PRERELEASE"

- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Cache pub dependencies
uses: actions/cache@v5
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-pub-

- name: Install Melos
run: dart pub global activate melos

- name: Get dependencies
run: melos bootstrap

- name: Update pubspec version
run: |
VERSION="${{ steps.get_version.outputs.VERSION }}"
sed -i '' "s/^version:.*/version: $VERSION/" apps/linkunbound/pubspec.yaml
echo "Updated pubspec.yaml version to: $VERSION"

# ── Code Signing Setup ──
- name: Import signing certificate
if: env.MACOS_CERTIFICATE_P12 != ''
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

CERT_PATH="$RUNNER_TEMP/certificate.p12"
echo "$MACOS_CERTIFICATE_P12" | base64 --decode > "$CERT_PATH"

security import "$CERT_PATH" \
-k "$KEYCHAIN_PATH" \
-P "$MACOS_CERTIFICATE_PASSWORD" \
-T /usr/bin/codesign \
-T /usr/bin/security

security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

rm -f "$CERT_PATH"
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
echo "Certificate imported successfully"

# ── Build (universal: arm64 + x86_64) ──
- name: Refresh CocoaPods lock
run: |
rm -f apps/linkunbound/macos/Podfile.lock
cd apps/linkunbound/macos && pod install --repo-update

- name: Build macOS release (universal)
run: |
cd apps/linkunbound
flutter build macos --release \
--build-name="${{ steps.get_version.outputs.BUILD_NAME }}" \
--build-number="${{ steps.get_version.outputs.BUILD_NUMBER }}" \
--dart-define="APP_VERSION=${{ steps.get_version.outputs.VERSION }}"

- name: Verify universal binary
run: |
APP="apps/linkunbound/build/macos/Build/Products/Release/LinkUnbound.app"
ARCHS=$(lipo -archs "$APP/Contents/MacOS/LinkUnbound")
echo "Architectures: $ARCHS"
if [[ "$ARCHS" != *"x86_64"* ]] || [[ "$ARCHS" != *"arm64"* ]]; then
echo "::error::Expected universal binary (x86_64 + arm64), got: $ARCHS"
exit 1
fi
echo "Universal binary verified (x86_64 + arm64)"

# ── Code Sign the .app ──
- name: Sign application
if: env.MACOS_CERTIFICATE_P12 != ''
run: |
APP_PATH="apps/linkunbound/build/macos/Build/Products/Release/LinkUnbound.app"

SIGN_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
echo "Signing with identity: $SIGN_IDENTITY"

codesign --deep --force --options runtime \
--entitlements "apps/linkunbound/macos/Runner/Release.entitlements" \
--sign "$SIGN_IDENTITY" \
"$APP_PATH"

echo "Verifying signature..."
codesign --verify --deep --strict "$APP_PATH"
echo "Signature verified"

- name: Ad-hoc sign (unsigned build)
if: env.MACOS_CERTIFICATE_P12 == ''
run: |
APP_PATH="apps/linkunbound/build/macos/Build/Products/Release/LinkUnbound.app"
codesign --deep --force --sign - "$APP_PATH"
echo "Ad-hoc signed (unsigned build)"

# ── Create DMG ──
- name: Install create-dmg
run: brew install create-dmg

- name: Create DMG
run: |
VERSION="${{ steps.get_version.outputs.VERSION }}"
APP_PATH="apps/linkunbound/build/macos/Build/Products/Release/LinkUnbound.app"
DMG_NAME="LinkUnbound_${VERSION}_universal.dmg"

mkdir -p apps/linkunbound/dist

create-dmg \
--volname "LinkUnbound" \
--window-pos 200 120 \
--window-size 660 400 \
--icon-size 80 \
--icon "LinkUnbound.app" 180 190 \
--app-drop-link 480 190 \
--hide-extension "LinkUnbound.app" \
--no-internet-enable \
"apps/linkunbound/dist/$DMG_NAME" \
"$APP_PATH" \
|| true

if [[ ! -f "apps/linkunbound/dist/$DMG_NAME" ]]; then
echo "::error::DMG was not created"
exit 1
fi

echo "DMG created: $DMG_NAME ($(du -h "apps/linkunbound/dist/$DMG_NAME" | cut -f1))"

# ── Sign & Notarize DMG ──
- name: Sign DMG
if: env.MACOS_CERTIFICATE_P12 != ''
run: |
DMG_PATH="apps/linkunbound/dist/LinkUnbound_${{ steps.get_version.outputs.VERSION }}_universal.dmg"

SIGN_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
echo "Signing DMG with identity: $SIGN_IDENTITY"

codesign --force --sign "$SIGN_IDENTITY" "$DMG_PATH"
codesign --verify "$DMG_PATH"
echo "DMG signed"

- name: Notarize DMG
if: env.APPLE_ID != '' && env.MACOS_CERTIFICATE_P12 != ''
run: |
DMG_PATH="apps/linkunbound/dist/LinkUnbound_${{ steps.get_version.outputs.VERSION }}_universal.dmg"

echo "Submitting for notarization..."
xcrun notarytool submit "$DMG_PATH" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait \
--timeout 600

echo "Stapling notarization ticket..."
xcrun stapler staple "$DMG_PATH"

echo "Verifying notarization..."
spctl --assess --type open --context context:primary-signature "$DMG_PATH"
echo "Notarization complete"

# ── Cleanup ──
- name: Cleanup keychain
if: always() && env.KEYCHAIN_PATH != ''
run: security delete-keychain "$KEYCHAIN_PATH" || true

# ── Upload ──
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: release-macos
path: apps/linkunbound/dist/*.dmg
retention-days: 5
Loading
Loading