diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e4a2bc5ad..d22ac0320 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,15 +4,15 @@ ## Project Context -MrRSS is a modern, privacy-focused, cross-platform desktop RSS reader built with Wails (Go + Vue.js). +MrRSS is a privacy-focused RSS reader. On this branch the interface is a native macOS client in SwiftUI, talking to a Go backend over its HTTP API. **Core Principles**: Privacy-first, cross-platform, modern UI, high performance, accessible ## Tech Stack -- **Backend**: Go 1.27+ with Wails v3 (beta) framework, SQLite with `modernc.org/sqlite` -- **Frontend**: Vue 3.5+ Composition API, Pinia, Tailwind CSS 3.3+, Vite 5+ -- **Tools**: Wails CLI v3, npm, Go modules +- **Backend**: Go 1.27+, SQLite with `modernc.org/sqlite`, serving only `/api` +- **Client**: SwiftUI for macOS 14+, a SwiftPM package with no third-party dependencies +- **Tools**: Swift toolchain from Xcode, Go modules - **Icons**: Phosphor Icons | **I18n**: vue-i18n (English/Chinese) ## Quick Patterns Reference @@ -29,282 +29,51 @@ MrRSS is a modern, privacy-focused, cross-platform desktop RSS reader built with 📚 **Full Patterns**: See [CODE_PATTERNS.md](../docs/CODE_PATTERNS.md#backend-patterns-go) -### Frontend (Vue 3) +### Client (SwiftUI) -When writing Vue components, follow this pattern: +When writing a view, follow this shape: -```vue - - - -``` - -📚 **Full Patterns**: See [CODE_PATTERNS.md](../docs/CODE_PATTERNS.md#frontend-patterns-vue) - -## Internationalization - -Always use i18n for user-facing strings: - -```vue - -

{{ t('welcome') }}

- - - -window.showToast(t('successMessage'), 'success'); -``` - -## UI Components - -### Common Patterns - -**Card Container**: - -```html -
-

{{ t('title') }}

-

{{ t('description') }}

-
-``` - -**Modal/Dialog**: - -```html -
-
- -
-
-``` - -### Toast Notifications - -```javascript -// Success message -window.showToast(message, 'success'); - -// Error message -window.showToast(t('operationFailed'), 'error'); - -// Info message with custom duration -window.showToast(t('updateAvailable'), 'info', 5000); -``` - -### Confirm Dialogs - -```javascript -const confirmed = await window.showConfirm( - t('confirmDelete'), - t('deleteWarning'), - true // isDanger - shows red confirmation button -); - -if (confirmed) { - // Proceed with dangerous operation -} -``` - -### Context Menu Pattern - -```vue - - - -``` - -## Settings Management (OPTIMIZED) - -✅ **The settings system has been optimized with schema-driven code generation!** - -### Quick Method (3 Steps) - -**Step 1**: Edit `internal/config/settings_schema.json` - -```json -"new_setting_key": { - "type": "bool", - "default": false, - "category": "general", - "encrypted": false, - "frontend_key": "new_setting_key" -} -``` - -**Step 2**: Generate all code - -```bash -go run tools/settings-generator/main.go -``` - -**Step 3**: Add UI (optional) - -```vue - - - -``` - -### What Gets Generated Automatically - -- ✅ Backend types and handlers -- ✅ Frontend types and composables -- ✅ Database initialization keys -- ✅ Default values - -### Old Method (Deprecated) - -The manual 8-file checklist is **no longer needed**. All new settings should use the schema-driven approach. - -📚 **Complete Guide**: See [docs/SETTINGS.md](../docs/SETTINGS.md) - -## Security Best Practices - -### Input Validation - -Always validate user inputs, especially URLs and file paths: - -```go -// Validate URL format and scheme -func validateFeedURL(urlStr string) error { - u, err := url.Parse(urlStr) - if err != nil { - return fmt.Errorf("invalid URL: %w", err) - } - - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("URL must use HTTP or HTTPS") + var body: some View { + HStack(alignment: .top, spacing: 10) { + unreadDot + VStack(alignment: .leading, spacing: 4) { + // Every interface string comes from the catalogue. + Text(article.displayTitle(preferTranslation: showsTranslation)) + .fontWeight(article.isRead ? .regular : .semibold) + metadata + } + } + .contextMenu { contextMenu } } - return nil -} + private var unreadDot: some View { /* ... */ } + private var metadata: some View { /* ... */ } -// Validate file path to prevent directory traversal -func validateFilePath(baseDir, filePath string) error { - cleanPath := filepath.Clean(filePath) - if !strings.HasPrefix(cleanPath, filepath.Clean(baseDir)) { - return errors.New("invalid file path: path traversal detected") + @ViewBuilder + private var contextMenu: some View { + Button { + viewModel.setArticleRead(article, read: !article.isRead) + } label: { + Label(t("article.action.markAsRead"), systemImage: "checkmark.circle") + } } - return nil } ``` -### Safe Command Execution +Behaviour belongs on `AppViewModel` or one of its extensions, not in the view: -**NEVER** use shell command concatenation: - -```go -// ❌ BAD: Command injection vulnerability -cmd := exec.Command("sh", "-c", "rm " + filePath) - -// ✅ GOOD: Use Go standard library -if err := os.Remove(filePath); err != nil { - return fmt.Errorf("remove file: %w", err) -} - -// ✅ GOOD: If external command is necessary, use separate args -cmd := exec.Command("installer.exe", "/S") // No concatenation -``` - -### File Operations - -Always clean up temporary files and use proper error handling: - -```go -// Schedule cleanup with timeout -scheduleCleanup := func(filePath string, delay time.Duration) { - go func() { - time.Sleep(delay) - if err := os.Remove(filePath); err != nil { - log.Printf("Failed to cleanup %s: %v", filePath, err) - } else { - log.Printf("Cleaned up temporary file: %s", filePath) +```swift +extension AppViewModel { + func toggleReadLater(_ article: Article) { + mutateArticle(article.id, apply: { $0.isReadLater.toggle() }) { [weak self] in + try await self?.api.toggleReadLater(id: article.id) } - }() + } } ``` @@ -312,19 +81,18 @@ scheduleCleanup := func(filePath string, delay time.Duration) { ❌ **Don't**: -- Use `var` declarations in Vue (use `ref` or `reactive`) -- Hardcode user-facing strings (always use i18n `t()`) -- Use inline styles (use Tailwind classes or scoped styles) +- Hardcode user-facing strings (always use `t("some.key")`) +- Write long SwiftUI bodies (type checking stalls; split into computed properties) +- Force unwrap outside tests - Forget error handling in async operations -- Use `any` type without strong justification - Commit API keys, secrets, or sensitive data -- Use `v-html` for user content (XSS risk) -- Make breaking changes without migration path +- Render untrusted markup without sanitising it (see `HTMLDocument.sanitize`) +- Make breaking changes without a migration path - Use shell command concatenation (security risk) -- Create multiple deep watchers when one suffices -- Forget to clean up timers/intervals on component unmount +- Let a superseded request overwrite newer state (check the request identifier) +- Forget to remove time observers and event monitors when a view disappears - Delete favorited articles during cleanup operations -- Use synchronous operations in UI thread for long tasks +- Block the main actor with long-running work ## Do's @@ -354,21 +122,22 @@ scheduleCleanup := func(filePath string, delay time.Duration) { **Build Commands**: -- Development: `wails3 dev` -- Production Build: `wails3 build` -- Important: MrRSS uses HTTP API, not Wails bindings +- Development: `./frontend/run.sh` +- Client only: `swift build --package-path frontend` +- Release bundle: `make build-app VERSION=1.3.28` -**Store Access**: +**State Access**: -- `const store = useAppStore()` -- `const { t } = useI18n()` -- Theme: `store.theme` returns `'light'` or `'dark'` -- Language: `store.i18n.locale.value` returns `'en'` or `'zh'` +- `@ObservedObject var viewModel: AppViewModel` +- Strings: `t("some.key")`, `t("some.key", ["count": n])` +- Settings: `viewModel.setting("key")`, `viewModel.boolSetting("key")` +- Theme follows the `theme` setting through `viewModel.preferredColorScheme` **UI Helpers**: -- Toast: `window.showToast(message, type)` -- Confirm: `await window.showConfirm(title, message, isDanger)` +- Confirmation: `viewModel.statusMessage = t("...")` shows a short message +- Errors: `viewModel.errorMessage = ...` raises an alert +- Confirmations use `.confirmationDialog` **API Endpoints**: diff --git a/.github/workflows/pre-release-check.yml b/.github/workflows/pre-release-check.yml index a8a7a7567..57b99bbd2 100644 --- a/.github/workflows/pre-release-check.yml +++ b/.github/workflows/pre-release-check.yml @@ -5,40 +5,10 @@ on: jobs: build-check: - name: Build Check ${{ matrix.platform }}-${{ matrix.arch }} - runs-on: ${{ matrix.os }} + name: Build macOS Client Bundle + runs-on: macos-latest permissions: contents: read - strategy: - fail-fast: false - matrix: - include: - # Linux AMD64 build - - os: ubuntu-24.04 - platform: linux - arch: amd64 - ext: '' - # Linux ARM64 build - using native ARM64 runner - - os: ubuntu-24.04-arm - platform: linux - arch: arm64 - ext: '' - # Windows AMD64 build - - os: windows-latest - platform: windows - arch: amd64 - ext: '.exe' - # Windows ARM64 build - using native ARM64 runner - - os: windows-11-arm - platform: windows - arch: arm64 - ext: '.exe' - # macOS Universal build (includes both Intel and Apple Silicon) - - os: macos-latest - platform: darwin - arch: universal - ext: '.app' - steps: - uses: actions/checkout@v7 @@ -47,222 +17,22 @@ jobs: with: go-version: '1.27' - - name: Set up Node.js - uses: actions/setup-node@v7 - with: - node-version: '24' - - - name: Install Task (task runner) - uses: arduino/setup-task@v3 - with: - version: 3.x - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Linux dependencies (AMD64) - if: matrix.platform == 'linux' && matrix.arch == 'amd64' - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config \ - wget \ - file - - # Verify GTK4/WebKitGTK 6.0 are available for current Wails v3 - pkg-config --modversion gtk4 || exit 1 - pkg-config --modversion webkitgtk-6.0 || exit 1 - echo "GTK4 and WebKitGTK 6.0 installed successfully for Wails v3" - - - name: Install Linux dependencies (ARM64) - if: matrix.platform == 'linux' && matrix.arch == 'arm64' - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config \ - wget \ - file - - # Verify GTK4/WebKitGTK 6.0 are available for current Wails v3 - pkg-config --modversion gtk4 || exit 1 - pkg-config --modversion webkitgtk-6.0 || exit 1 - echo "GTK4 and WebKitGTK 6.0 installed successfully for Wails v3 ARM64" - - - name: Install Windows dependencies (AMD64) - if: matrix.platform == 'windows' && matrix.arch == 'amd64' - run: | - choco install mingw nsis -y - shell: pwsh - - - name: Install Windows dependencies (ARM64) - if: matrix.platform == 'windows' && matrix.arch == 'arm64' - run: | - choco install mingw nsis -y - # MinGW is required for CGO - shell: pwsh - - - name: Install Wails CLI - shell: bash - run: | - WAILS_VERSION=$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v3) - echo "Installing Wails CLI ${WAILS_VERSION}" - go install github.com/wailsapp/wails/v3/cmd/wails3@${WAILS_VERSION} - - - name: Install frontend dependencies - working-directory: ./frontend - run: npm ci - - - name: Build application (Linux) - if: matrix.platform == 'linux' + - name: Run backend tests env: CGO_ENABLED: 1 - run: | - # wails3 build automatically handles frontend build via Taskfile - wails3 build - - # Verify the build - if [ ! -f "build/bin/MrRSS" ]; then - echo "Error: Binary not found" - exit 1 - fi + run: go test -timeout=5m ./internal/... - # Check architecture - file build/bin/MrRSS - echo "Binary built successfully for ${{ matrix.arch }}" - - - name: Build application (Windows) - if: matrix.platform == 'windows' - shell: pwsh - env: - CGO_ENABLED: 1 - run: | - # wails3 build automatically handles frontend build via Taskfile - wails3 build - - # Verify the build - if (-not (Test-Path "build/bin/MrRSS.exe")) { - Write-Error "Binary not created" - exit 1 - } - - Write-Host "Binary built successfully for ${{ matrix.arch }}" - - - - - name: Build application (macOS) - if: matrix.platform == 'darwin' - run: | - # macOS runners can build universal binaries natively - # wails3 build automatically handles frontend build via Taskfile - wails3 build - - - name: Create installer (Windows) - if: matrix.platform == 'windows' - run: | - # Find NSIS installation - $nsisPath = Get-Command makensis -ErrorAction SilentlyContinue - if (-not $nsisPath) { - # Try common installation paths - $possiblePaths = @( - "C:\Program Files (x86)\NSIS\makensis.exe", - "C:\Program Files\NSIS\makensis.exe" - ) - foreach ($path in $possiblePaths) { - if (Test-Path $path) { - $nsisPath = $path - break - } - } - } else { - $nsisPath = $nsisPath.Source - } - - if (-not $nsisPath) { - Write-Error "NSIS not found" - exit 1 - } - - Write-Host "Using NSIS at: $nsisPath" - - # Get version from package.json - $packageJson = Get-Content "frontend/package.json" -Raw | ConvertFrom-Json - $version = $packageJson.version - $arch = "${{ matrix.arch }}" - $installerPath = "build/windows/installer.nsi" - - if (Test-Path $installerPath) { - $content = Get-Content $installerPath -Raw - $content = $content -replace '!define APP_VERSION ".*"', "!define APP_VERSION `"$version`"" - $content = $content -replace 'OutFile ".*"', "OutFile `"..\bin\MrRSS-$version-windows-$arch-installer.exe`"" - Set-Content $installerPath $content - - # Build NSIS installer - & $nsisPath $installerPath - - if ($LASTEXITCODE -ne 0) { - Write-Error "NSIS build failed with exit code $LASTEXITCODE" - exit 1 - } - } else { - Write-Warning "Installer script not found at $installerPath" - exit 1 - } - shell: pwsh - continue-on-error: true - - - name: Create installer (Linux) - if: matrix.platform == 'linux' - run: | - # Clean up any cached appimagetool to ensure correct architecture is downloaded - rm -f build/appimagetool-*.AppImage - - # Debug: Show system architecture info - echo "System architecture: $(uname -m)" - echo "Target architecture: ${{ matrix.arch }}" - - # Debug: Verify binary architecture - echo "Binary architecture check:" - file build/bin/MrRSS || true - - chmod +x build/linux/create-appimage.sh - ARCH=${{ matrix.arch }} ./build/linux/create-appimage.sh || echo "AppImage creation failed, will use tar.gz" - continue-on-error: true - - - name: Create installer (macOS) - if: matrix.platform == 'darwin' - run: | - codesign --deep --force --verify --verbose \ - --sign - \ - --entitlements build/darwin/entitlements.plist \ - build/bin/MrRSS.app - codesign --verify --deep --strict --verbose=2 build/bin/MrRSS.app - chmod +x build/darwin/create-dmg.sh - ./build/darwin/create-dmg.sh + - name: Run macOS client tests + working-directory: ./frontend + run: swift test - - name: Package application (Linux - tar.gz fallback) - if: matrix.platform == 'linux' + - name: Build the application bundle run: | - cd build/bin - if [ -f "MrRSS" ]; then - # Get version from package.json - VERSION=$(jq -r '.version' ../../frontend/package.json) - tar -czf MrRSS-${VERSION}-linux-${{ matrix.arch }}.tar.gz MrRSS - echo "Created tar.gz package" - else - echo "Error: MrRSS binary not found" - exit 1 - fi + chmod +x frontend/build-app.sh + frontend/build-app.sh "pre-release-check" - - name: Verify build artifacts - shell: bash + - name: Verify bundle contents run: | - echo "Build verification for ${{ matrix.platform }}-${{ matrix.arch }}" - echo "Checking build/bin directory..." - ls -lh build/bin/ || echo "No artifacts found" - echo "Build check completed successfully" + test -x frontend/dist/MrRSS.app/Contents/MacOS/MrRSS + test -x frontend/dist/MrRSS.app/Contents/Resources/mrrss-server + test -f frontend/dist/MrRSS.app/Contents/Info.plist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe544163d..41868bc81 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,503 +146,30 @@ jobs: files: build/skills/MrRSS-${{ needs.create-release.outputs.version }}-skills.zip token: ${{ secrets.GITHUB_TOKEN }} - build-release: - name: Build ${{ matrix.platform }}-${{ matrix.arch }} + build-macos-client: + name: Build macOS Universal Client needs: create-release - runs-on: ${{ matrix.os }} + runs-on: macos-latest permissions: contents: write - strategy: - fail-fast: false - matrix: - include: - # Desktop platforms - always build - # Linux AMD64 build - - os: ubuntu-24.04 - platform: linux - arch: amd64 - ext: '' - build_condition: 'true' - # Linux ARM64 build - using native ARM64 runner - - os: ubuntu-24.04-arm - platform: linux - arch: arm64 - ext: '' - build_condition: 'true' - # Windows AMD64 build - - os: windows-latest - platform: windows - arch: amd64 - ext: '.exe' - build_condition: 'true' - # Windows ARM64 build - using native ARM64 runner - - os: windows-11-arm - platform: windows - arch: arm64 - ext: '.exe' - build_condition: 'true' - # macOS Universal build (includes both Intel and Apple Silicon) - - os: macos-latest - platform: darwin - arch: universal - ext: '.app' - build_condition: 'true' - - # Mobile platforms - only for Wails v3 (commented out by default) - # Uncomment these when you want to build mobile versions - # - os: macos-latest - # platform: ios - # arch: arm64 - # ext: '.ipa' - # build_condition: 'wails_v3_only' - # - os: ubuntu-24.04 - # platform: android - # arch: arm64 - # ext: '.apk' - # build_condition: 'wails_v3_only' - steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ needs.create-release.outputs.tag }} - - name: Check if artifacts already exist - id: check_artifacts - shell: bash - run: | - # Get release info and check for existing assets - TAG="${{ needs.create-release.outputs.tag }}" - VERSION="${{ needs.create-release.outputs.version }}" - - # Determine expected artifact names for this platform/arch - if [ "${{ matrix.platform }}" = "windows" ]; then - INSTALLER="MrRSS-${VERSION}-windows-${{ matrix.arch }}-installer.exe" - PACKAGE="" # No package for Windows - elif [ "${{ matrix.platform }}" = "linux" ]; then - INSTALLER="MrRSS-${VERSION}-linux-${{ matrix.arch }}.AppImage" - PACKAGE="MrRSS-${VERSION}-linux-${{ matrix.arch }}.tar.gz" - elif [ "${{ matrix.platform }}" = "darwin" ]; then - INSTALLER="MrRSS-${VERSION}-darwin-${{ matrix.arch }}.dmg" - PACKAGE="" # No package for macOS - elif [ "${{ matrix.platform }}" = "ios" ]; then - INSTALLER="MrRSS-${VERSION}-ios-${{ matrix.arch }}.ipa" - PACKAGE="" # No package for iOS - elif [ "${{ matrix.platform }}" = "android" ]; then - INSTALLER="MrRSS-${VERSION}-android-${{ matrix.arch }}.apk" - PACKAGE="" # No package for Android - fi - - # Check if release has these assets - RELEASE_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/releases/tags/${TAG}") - - ASSETS=$(echo "$RELEASE_JSON" | jq -r '.assets[].name') - - echo "Checking for existing assets..." - echo "Expected installer: $INSTALLER" - if [ -n "$PACKAGE" ]; then - echo "Expected package: $PACKAGE" - fi - echo "Found assets:" - echo "$ASSETS" - - # Check if any of our artifacts exist - SKIP_BUILD="false" - if echo "$ASSETS" | grep -q "$INSTALLER"; then - echo "Installer for ${{ matrix.platform }}-${{ matrix.arch }} already exists in release" - SKIP_BUILD="true" - elif [ -n "$PACKAGE" ] && echo "$ASSETS" | grep -q "$PACKAGE"; then - echo "Package for ${{ matrix.platform }}-${{ matrix.arch }} already exists in release" - SKIP_BUILD="true" - fi - - echo "skip_build=$SKIP_BUILD" >> $GITHUB_OUTPUT - - if [ "$SKIP_BUILD" = "true" ]; then - echo "⏭ïļ Skipping build for ${{ matrix.platform }}-${{ matrix.arch }} (already exists)" - else - echo "✅ Proceeding with build for ${{ matrix.platform }}-${{ matrix.arch }}" - fi - - name: Set up Go - if: steps.check_artifacts.outputs.skip_build == 'false' - uses: actions/setup-go@v7 + uses: actions/setup-go@v5 with: go-version: '1.27' - - name: Set up Node.js - if: steps.check_artifacts.outputs.skip_build == 'false' - uses: actions/setup-node@v7 - with: - node-version: '24' - - - name: Install Task (task runner) - if: steps.check_artifacts.outputs.skip_build == 'false' - uses: arduino/setup-task@v3 - with: - version: 3.x - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Linux dependencies (AMD64) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' && matrix.arch == 'amd64' - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config \ - wget \ - file - - # Verify GTK4/WebKitGTK 6.0 are available for current Wails v3 - pkg-config --modversion gtk4 || exit 1 - pkg-config --modversion webkitgtk-6.0 || exit 1 - echo "GTK4 and WebKitGTK 6.0 installed successfully for Wails v3" - - - name: Install Linux dependencies (ARM64) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' && matrix.arch == 'arm64' + - name: Build SwiftUI application bundle run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config \ - wget \ - file - - # Verify GTK4/WebKitGTK 6.0 are available for current Wails v3 - pkg-config --modversion gtk4 || exit 1 - pkg-config --modversion webkitgtk-6.0 || exit 1 - echo "GTK4 and WebKitGTK 6.0 installed successfully for Wails v3 ARM64" - - - name: Install Windows dependencies (AMD64) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'windows' && matrix.arch == 'amd64' - run: | - choco install mingw nsis -y - shell: pwsh - - - name: Install Windows dependencies (ARM64) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'windows' && matrix.arch == 'arm64' - run: | - choco install mingw nsis -y - # MinGW is required for CGO - shell: pwsh - - - name: Install Wails CLI - if: steps.check_artifacts.outputs.skip_build == 'false' - shell: bash - run: | - WAILS_VERSION=$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v3) - echo "Installing Wails CLI ${WAILS_VERSION}" - go install github.com/wailsapp/wails/v3/cmd/wails3@${WAILS_VERSION} - - - name: Install frontend dependencies - if: steps.check_artifacts.outputs.skip_build == 'false' - working-directory: ./frontend - run: npm ci - - - name: Build application (Linux) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' - env: - CGO_ENABLED: 1 - ARCH: ${{ matrix.arch }} - run: | - # wails3 build via Taskfile automatically handles frontend build - task linux:build - - # Verify the build - if [ ! -f "build/bin/MrRSS" ]; then - echo "Error: Binary not found" - exit 1 - fi - - # Check architecture - file build/bin/MrRSS - echo "Binary built successfully for ${{ matrix.arch }}" - - - name: Build application (Windows) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'windows' - shell: pwsh - env: - CGO_ENABLED: 1 - ARCH: ${{ matrix.arch }} - run: | - # wails3 build via Taskfile automatically handles frontend build - task windows:build - - # Verify the build - if (-not (Test-Path "build/bin/MrRSS.exe")) { - Write-Error "Binary not created" - exit 1 - } - - Write-Host "Binary built successfully for ${{ matrix.arch }}" - - - name: Build application (macOS) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'darwin' - env: - CGO_ENABLED: 1 - ARCH: ${{ matrix.arch }} - run: | - # macOS runners can build universal binaries natively - # wails3 build via Taskfile automatically handles frontend build - task darwin:build - - - name: Create installer (Windows) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'windows' - run: | - # Find NSIS installation - $nsisPath = Get-Command makensis -ErrorAction SilentlyContinue - if (-not $nsisPath) { - # Try common installation paths - $possiblePaths = @( - "C:\Program Files (x86)\NSIS\makensis.exe", - "C:\Program Files\NSIS\makensis.exe" - ) - foreach ($path in $possiblePaths) { - if (Test-Path $path) { - $nsisPath = $path - break - } - } - } else { - $nsisPath = $nsisPath.Source - } - - if (-not $nsisPath) { - Write-Error "NSIS not found" - exit 1 - } - - Write-Host "Using NSIS at: $nsisPath" - - # Update version and architecture in installer script - $version = "${{ needs.create-release.outputs.version }}" - $arch = "${{ matrix.arch }}" - $installerPath = "build/windows/installer.nsi" - - if (Test-Path $installerPath) { - $content = Get-Content $installerPath -Raw - $content = $content -replace '!define APP_VERSION ".*"', "!define APP_VERSION `"$version`"" - $content = $content -replace 'OutFile ".*"', "OutFile `"..\bin\MrRSS-$version-windows-$arch-installer.exe`"" - Set-Content $installerPath $content - - # Build NSIS installer - & $nsisPath $installerPath - - if ($LASTEXITCODE -ne 0) { - Write-Error "NSIS build failed with exit code $LASTEXITCODE" - exit 1 - } - } else { - Write-Warning "Installer script not found at $installerPath" - exit 1 - } - shell: pwsh - continue-on-error: true + chmod +x frontend/build-app.sh + frontend/build-app.sh "${{ needs.create-release.outputs.version }}" - - name: Create portable package (Windows) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'windows' - run: | - $version = "${{ needs.create-release.outputs.version }}" - $arch = "${{ matrix.arch }}" - $portableDir = "build/portable-temp" - - # Create portable directory structure - New-Item -ItemType Directory -Force -Path $portableDir | Out-Null - - # Copy executable - Copy-Item "build/bin/MrRSS.exe" -Destination $portableDir - - # Create portable.txt marker - New-Item -ItemType File -Path "$portableDir/portable.txt" | Out-Null - - # Create README for portable version - @" - MrRSS Portable Edition - - This is the portable version of MrRSS. All data will be stored in the 'data' folder - next to this executable. - - To run: Double-click MrRSS.exe - - For more information, visit: https://github.com/DevXDojo/MrRSS - "@ | Out-File -FilePath "$portableDir/README.txt" -Encoding UTF8 - - # Create zip file - $zipName = "MrRSS-$version-windows-$arch-portable.zip" - Compress-Archive -Path "$portableDir/*" -DestinationPath "build/bin/$zipName" -Force - - Write-Host "Created portable package: $zipName" - shell: pwsh - - - name: Create installer (Linux) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' - run: | - # Clean up any cached appimagetool to ensure correct architecture is downloaded - rm -f build/appimagetool-*.AppImage - - # Debug: Show system architecture info - echo "System architecture: $(uname -m)" - echo "Target architecture: ${{ matrix.arch }}" - - # Debug: Verify binary architecture - echo "Binary architecture check:" - file build/bin/MrRSS || true - - chmod +x build/linux/create-appimage.sh - ARCH=${{ matrix.arch }} ./build/linux/create-appimage.sh || echo "AppImage creation failed, will use tar.gz" - continue-on-error: true - - - name: Create installer (macOS) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'darwin' - run: | - codesign --deep --force --verify --verbose \ - --sign - \ - --entitlements build/darwin/entitlements.plist \ - build/bin/MrRSS.app - codesign --verify --deep --strict --verbose=2 build/bin/MrRSS.app - chmod +x build/darwin/create-dmg.sh - ./build/darwin/create-dmg.sh - - - name: Create portable package (macOS) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'darwin' - run: | - VERSION="${{ needs.create-release.outputs.version }}" - ARCH="${{ matrix.arch }}" - PORTABLE_DIR="build/portable-temp" - - # Create portable directory structure - mkdir -p "$PORTABLE_DIR" - - # Copy .app bundle - cp -R build/bin/MrRSS.app "$PORTABLE_DIR/" - - # Create portable.txt marker - touch "$PORTABLE_DIR/portable.txt" - - # Create README for portable version - cat > "$PORTABLE_DIR/README.txt" << 'EOF' - MrRSS Portable Edition - - This is the portable version of MrRSS. All data will be stored in the 'data' folder - next to the MrRSS.app. - - To run: Double-click MrRSS.app - - For more information, visit: https://github.com/DevXDojo/MrRSS - EOF - - # Create zip file - cd "$PORTABLE_DIR" - zip -r "../bin/MrRSS-${VERSION}-darwin-${ARCH}-portable.zip" * - cd ../.. - - echo "Created portable package: MrRSS-${VERSION}-darwin-${ARCH}-portable.zip" - - - name: Package application (Linux - tar.gz fallback) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' - run: | - cd build/bin - if [ -f "MrRSS" ]; then - tar -czf MrRSS-${{ needs.create-release.outputs.version }}-linux-${{ matrix.arch }}.tar.gz MrRSS - else - echo "Error: MrRSS binary not found" - exit 1 - fi - - - name: Create portable package (Linux) - if: steps.check_artifacts.outputs.skip_build == 'false' && matrix.platform == 'linux' - run: | - VERSION="${{ needs.create-release.outputs.version }}" - ARCH="${{ matrix.arch }}" - PORTABLE_DIR="build/portable-temp" - - # Create portable directory structure - mkdir -p "$PORTABLE_DIR" - - # Copy executable - cp build/bin/MrRSS "$PORTABLE_DIR/" - chmod +x "$PORTABLE_DIR/MrRSS" - - # Create portable.txt marker - touch "$PORTABLE_DIR/portable.txt" - - # Create README for portable version - cat > "$PORTABLE_DIR/README.txt" << 'EOF' - MrRSS Portable Edition - - This is the portable version of MrRSS. All data will be stored in the 'data' folder - next to this executable. - - To run: ./MrRSS - - For more information, visit: https://github.com/DevXDojo/MrRSS - EOF - - # Create tar.gz file - cd "$PORTABLE_DIR" - tar -czf "../bin/MrRSS-${VERSION}-linux-${ARCH}-portable.tar.gz" * - cd ../.. - - echo "Created portable package: MrRSS-${VERSION}-linux-${ARCH}-portable.tar.gz" - - - name: Collect artifacts - if: steps.check_artifacts.outputs.skip_build == 'false' - id: collect_artifacts - shell: bash - run: | - cd build/bin - artifacts="" - - # Check for installer and portable package - if [ "${{ matrix.platform }}" = "windows" ]; then - installer="MrRSS-${{ needs.create-release.outputs.version }}-windows-${{ matrix.arch }}-installer.exe" - portable="MrRSS-${{ needs.create-release.outputs.version }}-windows-${{ matrix.arch }}-portable.zip" - if [ -f "$installer" ]; then - artifacts="$artifacts$installer," - fi - if [ -f "$portable" ]; then - artifacts="$artifacts$portable," - fi - elif [ "${{ matrix.platform }}" = "linux" ]; then - installer="MrRSS-${{ needs.create-release.outputs.version }}-linux-${{ matrix.arch }}.AppImage" - portable="MrRSS-${{ needs.create-release.outputs.version }}-linux-${{ matrix.arch }}-portable.tar.gz" - if [ -f "$installer" ]; then - artifacts="$artifacts$installer," - fi - # Check for tar.gz fallback - package="MrRSS-${{ needs.create-release.outputs.version }}-linux-${{ matrix.arch }}.tar.gz" - if [ -f "$package" ]; then - artifacts="$artifacts$package," - fi - if [ -f "$portable" ]; then - artifacts="$artifacts$portable," - fi - elif [ "${{ matrix.platform }}" = "darwin" ]; then - installer="MrRSS-${{ needs.create-release.outputs.version }}-darwin-${{ matrix.arch }}.dmg" - portable="MrRSS-${{ needs.create-release.outputs.version }}-darwin-${{ matrix.arch }}-portable.zip" - if [ -f "$installer" ]; then - artifacts="$artifacts$installer," - fi - if [ -f "$portable" ]; then - artifacts="$artifacts$portable," - fi - fi - - echo "artifacts=$artifacts" >> $GITHUB_OUTPUT - echo "Found artifacts: $artifacts" - - - name: Upload Release Assets - if: steps.check_artifacts.outputs.skip_build == 'false' - uses: softprops/action-gh-release@v3 + - name: Upload SwiftUI macOS release asset + uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.create-release.outputs.tag }} - files: | - build/bin/MrRSS-${{ needs.create-release.outputs.version }}-* + files: frontend/dist/MrRSS-${{ needs.create-release.outputs.version }}-macos.dmg token: ${{ secrets.GITHUB_TOKEN }} - fail_on_unmatched_files: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e369f98f..48b02a079 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Test on: push: - branches: [ main ] + branches: [ main, feature/macos-client ] pull_request: - branches: [ main, develop ] + branches: [ main, develop, feature/macos-client ] workflow_dispatch: jobs: @@ -16,34 +16,11 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Install Linux dependencies (Wails v3) - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config - - name: Set up Go uses: actions/setup-go@v7 with: go-version: '1.27' - - name: Set up Node.js - uses: actions/setup-node@v7 - with: - node-version: '24' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Build frontend (required for embed) - working-directory: ./frontend - run: | - npm ci - npm run build - - name: Download dependencies run: go mod download @@ -62,37 +39,6 @@ jobs: files: ./coverage.out flags: backend - test-frontend: - name: Test Frontend - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v7 - - - name: Set up Node.js - uses: actions/setup-node@v7 - with: - node-version: '24' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install dependencies - working-directory: ./frontend - run: npm ci - - - name: Run linter - working-directory: ./frontend - run: npm run lint - - - name: Run unit tests - working-directory: ./frontend - run: npm test - - - name: Build frontend - working-directory: ./frontend - run: npm run build - validate-skills: name: Validate Skills runs-on: ubuntu-latest @@ -114,6 +60,22 @@ jobs: python skills/mrrss-assistant/scripts/generate_api_reference.py docs/SERVER_MODE/swagger.json skills/mrrss-assistant/references/api.md git diff --exit-code skills/mrrss-assistant/references/api.md + test-macos-client: + name: Test macOS Client + runs-on: macos-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - name: Run Swift tests + working-directory: ./frontend + run: swift test + + - name: Build release executable + working-directory: ./frontend + run: swift build -c release + build-check: name: Build Check runs-on: ubuntu-latest @@ -122,36 +84,11 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Install Linux dependencies (Wails v3) - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config - - name: Set up Go uses: actions/setup-go@v7 with: go-version: '1.27' - - name: Set up Node.js - uses: actions/setup-node@v7 - with: - node-version: '24' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install frontend dependencies - working-directory: ./frontend - run: npm ci - - - name: Build frontend (required for embed) - working-directory: ./frontend - run: npm run build - - name: Verify Go code compiles env: CGO_ENABLED: 1 diff --git a/.gitignore b/.gitignore index 20c911009..bcbf81f86 100644 --- a/.gitignore +++ b/.gitignore @@ -44,15 +44,10 @@ go.work.sum .idea/ .vscode/ -# Wails -build/bin/ -build/dmg/ -build/appimage/ +# Build output +bin/ build/skills/ -build/appimagetool-x86_64.AppImage dist/ -wailsjs/wailsjs/runtime/ -frontend/bindings/ # database files *.db @@ -60,18 +55,12 @@ frontend/bindings/ *.db-wal data/ -# vue & vite +# node (website only) node_modules/ -dist/ package.json.md5 *.syso unused-files.json -# Cypress -frontend/cypress/videos/ -frontend/cypress/screenshots/ -frontend/cypress/downloads/ - # log *.log @@ -88,3 +77,14 @@ tmpclaude-*-cwd i18n_usage_report.md __pycache__/ .VSCodeCounter + +# Swift frontend +.swiftpm +.build +DerivedData/ +Checkouts/ +*.xcodeproj +*.xcworkspace + +# macOS +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7089386f..ea83ebbca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,9 +28,9 @@ repos: language: system files: go\.mod$ pass_filenames: false - - id: eslint - name: eslint - entry: powershell.exe -Command "cd frontend; npm run lint" + - id: swift-build + name: swift-build + entry: swift build --package-path frontend language: system - files: \.(js|ts|vue)$ + files: ^frontend/.*\.swift$ pass_filenames: false diff --git a/AGENTS.md b/AGENTS.md index 385e64cb0..8aa71bc6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,16 +10,20 @@ - **Privacy-First**: No external analytics, all data stored locally - **Cross-Platform**: Native desktop experience on Windows, macOS, and Linux -- **Modern Tech Stack**: Go 1.27+ + Wails v3 + Vue 3.5+ with TypeScript +- **Modern Tech Stack**: Go 1.27+ backend with a native SwiftUI client for macOS - **AI-Enhanced**: Local algorithms (TF-IDF + TextRank) and cloud AI integration - **Performance-Optimized**: Concurrent processing, intelligent caching, WAL mode SQLite ### Tech Stack -- **Backend**: Go 1.27+ with Wails v3 (beta) framework, SQLite with `modernc.org/sqlite` -- **Frontend**: Vue 3.5+ Composition API, Pinia, Tailwind CSS 3.3+, Vite 5+, TypeScript -- **Communication**: HTTP REST API (not Wails bindings) for data operations -- **Icons**: Phosphor Icons | **I18n**: vue-i18n (English/Chinese) +- **Backend**: Go 1.27+, SQLite with `modernc.org/sqlite`, serving only `/api` +- **Client**: SwiftUI for macOS 14+, a SwiftPM package with no third-party dependencies +- **Communication**: the HTTP REST API alone; there are no native bindings +- **Translations**: a catalogue ported from the previous frontend (English/Chinese) + +> **This branch builds the macOS client.** The Vue frontend and the Wails shell +> were removed, so the Go binary has no embedded web assets. + ### Key Features @@ -59,8 +63,7 @@ ```plaintext MrRSS/ -├── main.go # Desktop application entry point -├── main-core.go # Headless server entry point +├── main.go # API server entry point ├── internal/ # Backend Go code │ ├── ai/ # AI configuration and utilities │ ├── aiusage/ # AI usage tracking and limits @@ -83,21 +86,24 @@ MrRSS/ │ ├── summary/ # TF-IDF + TextRank + AI summarization │ ├── translation/ # Multi-service translation │ ├── utils/ # Platform utilities -│ ├── version/ # Version constant -│ └── webview/ # Webview utilities -├── frontend/src/ -│ ├── components/ # Vue components (article/, sidebar/, modals/, common/) -│ ├── composables/ # Reusable logic (article/, feed/, discovery/, rules/, ui/) -│ ├── stores/ # Pinia state management -│ ├── types/ # TypeScript definitions -│ ├── i18n/ # Translations (en, zh) -│ └── utils/ # Frontend utilities -├── docs/ # Comprehensive documentation -├── build/ # Platform-specific build configurations -├── tools/ # Development tools (settings generator) +│ └── version/ # Version constant +├── frontend/ +│ ├── Sources/ +│ │ ├── Models/ # Codable mirrors of the API payloads +│ │ ├── Services/API/ # Transport plus one extension per domain +│ │ ├── Localization/ # Translation catalogue and client-only wording +│ │ ├── ViewModels/ # AppViewModel and its feature extensions +│ │ └── Views/ # SwiftUI views, including the sidebar outline +│ ├── Tests/ # XCTest suite +│ ├── build-app.sh # Bundle and DMG packaging +│ └── run.sh # Backend plus client launcher +├── docs/ # Documentation +├── build/ # Icon assets used when packaging +├── tools/ # Settings generators (Go and Swift) └── scripts/ # Automation scripts (check, pre-release) ``` + 📚 **Detailed Structure**: See [ARCHITECTURE.md](docs/ARCHITECTURE.md) ## Key Technologies & Patterns @@ -105,8 +111,7 @@ MrRSS/ ### Backend Architecture (Go 1.27+) #### Framework & Communication -- **Wails v3**: Desktop application framework with HTTP API -- **HTTP REST API**: Primary communication (not Wails bindings) +- **HTTP REST API**: the only channel between the client and the backend - **SQLite**: Pure Go implementation (`modernc.org/sqlite`) with WAL mode #### Core Packages @@ -172,80 +177,46 @@ MrRSS/ - **Safe File Operations**: No shell command concatenation - **Script Sandboxing**: Restricted execution context -### Frontend Architecture (Vue 3.5+) - -#### Core Technologies -- **Vue 3.5+**: Composition API with ` +```swift +struct FeedRow: View { + let feed: Feed + @ObservedObject var viewModel: AppViewModel + + var body: some View { + HStack { + Text(feed.title) + Spacer() + Text("\(viewModel.badgeCount(for: feed.id, activity: .unread))") + .foregroundStyle(.secondary) + } + } +} ``` ### File Organization - Backend: `internal/` for internal packages -- Frontend: `frontend/src/components/` for Vue components -- Tests: Co-locate tests with the code they test -- Assets: `frontend/assets/` for images, icons, etc. +- Client: `frontend/Sources/` — models, services, view models, views +- Backend tests: alongside the code they test +- Client tests: `frontend/Tests/` +- Assets: `imgs/` for artwork, `build/` for the packaging icons ### Commit Messages diff --git a/Dockerfile.server b/Dockerfile.server index 66fb2b0a9..a87004535 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -4,8 +4,8 @@ # Build stage FROM golang:1.27-alpine AS builder -# Install git, Node.js, and build tools (needed for frontend build and CGO) -RUN apk add --no-cache git nodejs npm gcc musl-dev pkgconfig +# Install git and build tools (needed for CGO) +RUN apk add --no-cache git gcc musl-dev pkgconfig # Set working directory WORKDIR /app @@ -19,11 +19,8 @@ RUN go mod download # Copy source code COPY . . -# Build frontend -RUN cd frontend && npm install && npm run build - # Build the server version -RUN CGO_ENABLED=1 GOOS=linux go build -tags server -trimpath -buildvcs=false -ldflags="-w -s" -o mrrss-server ./main-core.go +RUN CGO_ENABLED=1 GOOS=linux go build -trimpath -buildvcs=false -ldflags="-w -s" -o mrrss-server . # Runtime stage - minimal alpine image FROM alpine:latest diff --git a/Makefile b/Makefile index 828a1c8cd..2f540309b 100644 --- a/Makefile +++ b/Makefile @@ -1,193 +1,108 @@ -# Makefile for MrRSS (Wails v3 + Task) -.PHONY: help dev build package run test test-frontend test-backend lint lint-frontend format format-backend install-deps update-deps check setup clean love swagger swagger-validate swagger-serve static-check - -# Detect OS -ifeq ($(OS),Windows_NT) - DETECTED_OS := Windows - SHELL := pwsh.exe - .SHELLFLAGS := -Command - TASK := task.exe -else - DETECTED_OS := $(shell uname -s) - SHELL := /bin/bash - TASK := task -endif - -# Default target +# Makefile for MrRSS (Go backend + native macOS SwiftUI client) +.PHONY: help dev build build-app run test test-client test-backend test-coverage \ + lint lint-client lint-backend format format-backend install-deps update-deps \ + check setup clean swagger swagger-validate static-check pre-commit release-check love + +SWIFT_PACKAGE := frontend + help: ## Show this help message - @echo "MrRSS Development Makefile ($(DETECTED_OS))" + @echo "MrRSS Development Makefile (macOS client)" @echo "" - @echo "Wails v3 Build System - Using Task Runner" - @echo "" - @echo "Available targets:" @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' - @echo "" - @echo "ðŸ’Ą Tip: Use 'task --list' to see all available tasks" -# Development (Wails v3 + Task) -dev: ## Start development server with hot reload - $(TASK) dev +# Development +dev: ## Start the backend and the macOS client together + ./$(SWIFT_PACKAGE)/run.sh -# Building (Wails v3 + Task) -build: ## Build application for current platform - $(TASK) build +serve: ## Start only the backend on 127.0.0.1:1234 + MRRSS_DEBUG=1 go run . -host 127.0.0.1 -port 1234 -package: ## Package application with installer - $(TASK) package +# Building +build: build-backend build-client ## Build the backend and the macOS client -run: ## Run the built application - $(TASK) run +build-backend: ## Build the backend binary + go build -v -o bin/mrrss-server . -build-frontend: ## Build frontend only - $(TASK) common:build:frontend +build-client: ## Build the macOS client executable + swift build --package-path $(SWIFT_PACKAGE) -build-backend: ## Build backend only - go build -v -o build/bin/ ./... +build-app: ## Build the signed .app bundle and DMG (VERSION=x.y.z) + ./$(SWIFT_PACKAGE)/build-app.sh $(or $(VERSION),dev) -# Testing -test: test-frontend test-backend ## Run all tests - -test-frontend: ## Run frontend tests - cd frontend && npm test +run: build-backend ## Run the backend binary + ./bin/mrrss-server -host 127.0.0.1 -port 1234 -test-frontend-e2e: ## Run frontend E2E tests with Cypress - cd frontend && npm run test:e2e +# Testing +test: test-backend test-client ## Run all tests test-backend: ## Run backend tests go test -v -timeout=5m -cover ./internal/... +test-client: ## Run macOS client tests + swift test --package-path $(SWIFT_PACKAGE) + test-coverage: ## Run backend tests with coverage go test -v -timeout=5m -coverprofile=coverage.out -covermode=atomic ./internal/... go tool cover -html=coverage.out -o coverage.html @echo "Coverage report generated: coverage.html" -test-all: test-frontend test-frontend-e2e test-backend ## Run all tests including E2E - -# Code Quality -lint: lint-frontend lint-backend ## Run all linters - -lint-frontend: ## Run frontend linter - cd frontend && npm run lint +# Code quality +lint: lint-backend lint-client ## Run all linters lint-backend: ## Run backend linter go vet ./... -ifeq ($(DETECTED_OS),Windows) - powershell -Command '$$files = Get-ChildItem -Recurse -Include *.go | Where-Object { $$_ -notmatch "docs[\\/]SERVER_MODE" }; $$result = gofmt -d $$files ; if ($$result) { Write-Host $$result -ForegroundColor Red; exit 1 }' - powershell -Command '$$files = Get-ChildItem -Recurse -Include *.go | Where-Object { $$_ -notmatch "docs[\\/]SERVER_MODE" }; $$importsResult = goimports -d $$files ; if ($$importsResult) { Write-Host $$importsResult -ForegroundColor Red; exit 1 }' -else find . -name '*.go' -not -path './docs/SERVER_MODE/*' -exec gofmt -d {} + | tee /dev/stderr | test -z "$$(cat)" find . -name '*.go' -not -path './docs/SERVER_MODE/*' -exec goimports -d {} + | tee /dev/stderr | test -z "$$(cat)" -endif -format: format-frontend format-backend ## Format all code +lint-client: ## Check macOS client formatting + @if command -v swift-format >/dev/null 2>&1; then \ + swift-format lint --recursive $(SWIFT_PACKAGE)/Sources $(SWIFT_PACKAGE)/Tests; \ + else \ + echo "swift-format not installed, skipping"; \ + fi -format-frontend: ## Format frontend code - cd frontend && npm run format +format: format-backend ## Format all code format-backend: ## Format backend code -ifeq ($(DETECTED_OS),Windows) - powershell -Command '$$files = Get-ChildItem -Recurse -Include *.go | Where-Object { $$_ -notmatch "docs[\\/]SERVER_MODE" }; gofmt -w $$files; goimports -w $$files' -else find . -name '*.go' -not -path './docs/SERVER_MODE/*' -exec gofmt -w {} + find . -name '*.go' -not -path './docs/SERVER_MODE/*' -exec goimports -w {} + -endif - -# Dependencies -install-deps: install-frontend-deps install-backend-deps ## Install all dependencies -install-frontend-deps: ## Install frontend dependencies - cd frontend && npm install +static-check: ## Run staticcheck for Go code analysis + staticcheck ./... -install-backend-deps: ## Install backend dependencies +# Dependencies +install-deps: ## Install backend dependencies go mod download -update-deps: update-frontend-deps update-backend-deps ## Update all dependencies - -update-frontend-deps: ## Update frontend dependencies - cd frontend && npm update - -update-backend-deps: ## Update backend dependencies +update-deps: ## Update backend dependencies go get -u ./... go mod tidy -# Setup setup: install-deps ## Initial project setup pre-commit install -# Task runner commands -task-list: ## List all available tasks - $(TASK) --list - -task-summary: ## Show task summary - $(TASK) --summary build dev package - -icons: ## Generate platform icons - $(TASK) common:generate:icons - -bindings: ## Generate TypeScript bindings - $(TASK) common:generate:bindings - -setup-docker: ## Setup Docker for cross-compilation - $(TASK) common:setup:docker - # Clean clean: ## Clean build artifacts -ifeq ($(DETECTED_OS),Windows) - -Remove-Item -Recurse -Force build/bin,frontend/dist,coverage.out,coverage.html,*.syso 2>$$null -else - rm -rf build/bin frontend/dist coverage.out coverage.html *.syso -endif - @echo "✅ Cleaned build artifacts" - -# Development helpers + rm -rf bin coverage.out coverage.html $(SWIFT_PACKAGE)/.build $(SWIFT_PACKAGE)/dist + @echo "Cleaned build artifacts" + check: lint test build ## Run full check (lint, test, build) -ifeq ($(DETECTED_OS),Windows) - powershell -File scripts/check.ps1 -else ./scripts/check.sh -endif pre-commit: ## Run pre-commit hooks on all files pre-commit run --all-files release-check: check ## Run all checks before release -ifeq ($(DETECTED_OS),Windows) - powershell -File scripts/pre-release.ps1 -else ./scripts/pre-release.sh -endif - -love: ## Show some love - @echo "âĪïļ MrRSS loves you too! âĪïļ" - -# Platform-specific builds -build-windows: ## Build for Windows - $(TASK) windows:build - -build-linux: ## Build for Linux - $(TASK) linux:build - -build-darwin: ## Build for macOS - $(TASK) darwin:build -# API Documentation +# API documentation swagger: ## Generate Swagger API documentation (JSON only) - swag init -g main-core.go --parseDependency --parseInternal -o docs/SERVER_MODE -ifeq ($(DETECTED_OS),Windows) - powershell -Command "Remove-Item -ErrorAction SilentlyContinue docs/SERVER_MODE/docs.go, docs/SERVER_MODE/swagger.yaml" -else + swag init -g main.go --parseDependency --parseInternal -o docs/SERVER_MODE $(RM) docs/SERVER_MODE/docs.go docs/SERVER_MODE/swagger.yaml -endif - @echo "✅ Swagger JSON documentation generated: docs/SERVER_MODE/swagger.json" + @echo "Swagger JSON documentation generated: docs/SERVER_MODE/swagger.json" swagger-validate: ## Validate Swagger annotations - @echo "🔍 Validating Swagger annotations..." - swag init -g main-core.go --parseDependency --parseInternal --parseInternal -o docs/SERVER_MODE + swag init -g main.go --parseDependency --parseInternal -o docs/SERVER_MODE -swagger-serve: ## Generate docs and serve Swagger UI - swag init -g main-core.go --parseDependency --parseInternal -o docs/SERVER_MODE - @echo "🌐 Starting development server..." - $(TASK) dev - -static-check: ## Run staticcheck for Go code analysis - staticcheck ./... +love: ## Show some love + @echo "MrRSS loves you too!" diff --git a/README.md b/README.md index 794dc1c35..65f442235 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,16 @@ English | įŪ€ä―“äļ­æ–‡

-[![Version](https://img.shields.io/badge/version-1.3.27-blue.svg)](https://github.com/DevXDojo/MrRSS/releases) +> **This branch builds the macOS client.** The interface is a native SwiftUI +> application in `frontend`, and the Go backend runs as a plain HTTP API +> server behind it. The Vue frontend and the Wails shell are not part of this +> branch. + +[![Version](https://img.shields.io/badge/version-1.3.28-blue.svg)](https://github.com/DevXDojo/MrRSS/releases) [![License](https://img.shields.io/badge/license-GPLv3-green.svg)](LICENSE) [![Go](https://img.shields.io/badge/Go-1.27+-00ADD8?logo=go)](https://go.dev/) -[![Wails](https://img.shields.io/badge/Wails-v3%20alpha-red)](https://wails.io/) -[![Vue.js](https://img.shields.io/badge/Vue.js-3.5+-4FC08D?logo=vue.js)](https://vuejs.org/) +[![Swift](https://img.shields.io/badge/Swift-5.9+-F05138?logo=swift)](https://swift.org/) +[![macOS](https://img.shields.io/badge/macOS-14+-000000?logo=apple)](https://www.apple.com/macos/) ## âœĻ Features @@ -36,22 +41,18 @@ Download the latest installer for your platform from the [Releases](https://gith
-**Standard Installation:** - -- **Windows:** `MrRSS-{version}-windows-amd64-installer.exe` / `MrRSS-{version}-windows-arm64-installer.exe` -- **macOS:** `MrRSS-{version}-darwin-universal.dmg` -- **Linux:** `MrRSS-{version}-linux-amd64.AppImage` / `MrRSS-{version}-linux-arm64.AppImage` - -**Portable Version** (no installation required, all data in one folder): +**macOS client:** -- **Windows:** `MrRSS-{version}-windows-{arch}-portable.zip` -- **Linux:** `MrRSS-{version}-linux-{arch}-portable.tar.gz` -- **macOS:** `MrRSS-{version}-darwin-{arch}-portable.zip` +- `MrRSS-{version}-macos.dmg` — universal, and carries the backend, so nothing + else needs installing **AI Agent Skills:** - **Codex:** `MrRSS-{version}-skills.zip` ([usage guide](docs/SKILLS.md)) +Releases of this branch carry the macOS client alone. Builds for Windows and +Linux come from the project's main branch. +
@@ -66,24 +67,11 @@ Download the latest installer for your platform from the [Releases](https://gith ### Prerequisites -Before you begin, ensure you have the following installed: - -- [Go](https://go.dev/) (1.27 or higher) -- [Node.js](https://nodejs.org/) (20 LTS or higher with npm) -- [Wails v3](https://v3alpha.wails.io/getting-started/installation/) CLI +- [Go](https://go.dev/) 1.27 or higher +- macOS 14 or later +- Xcode 15 or later (for the Swift toolchain) -**Platform-specific requirements:** - -- **Linux**: GTK4, WebKitGTK 6.0, libsoup 3.0, GCC, pkg-config -- **Windows**: MinGW-w64 (for CGO support), NSIS (for installers) -- **macOS**: Xcode Command Line Tools - -For detailed installation instructions, see [Build Requirements](docs/BUILD_REQUIREMENTS.md) - -```bash -# Quick setup for Linux (Ubuntu 24.04+): -sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-config -``` +See [Build Requirements](docs/BUILD_REQUIREMENTS.md) for details. ### Installation @@ -94,40 +82,67 @@ sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-c cd MrRSS ``` -2. **Install frontend dependencies** +2. **Run the client** ```bash - cd frontend - npm install - cd .. + ./frontend/run.sh ``` -3. **Install Wails v3 CLI** + The launcher builds the Go backend, starts it on `http://127.0.0.1:1234`, + waits for the API to answer, and then starts the client. An existing server + on that address is reused. + +3. **Or run the two halves separately** ```bash - go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha2.117 + go run . -host 127.0.0.1 -port 1234 + swift run --package-path frontend MrRSS ``` -4. **Build the application** + The backend address can be changed in Settings, or before launch: ```bash - # Using Task (recommended) - task build + MRRSS_API_BASE_URL=http://127.0.0.1:8080/api swift run --package-path frontend MrRSS + ``` - # Or using Makefile - make build +4. **Build the application bundle** - # Or directly with wails3 - wails3 build + ```bash + make build-app VERSION=1.3.28 ``` - The executable will be created in the `build/bin` directory. + This produces `frontend/dist/MrRSS.app` and + `frontend/dist/MrRSS--macos.dmg` beside it. The bundle carries the backend, so it launches without a + separately installed server. + + + + + +### Server Mode + +
-5. **Run the application** +Click to expand the server guide + +
+ +The Go binary is an HTTP API server. Run it on its own to share one library +between machines, and point the client at it in Settings: + +```bash +go build -o mrrss-server . +./mrrss-server -host 0.0.0.0 -port 1234 +``` - - Windows: `build/bin/MrRSS.exe` - - macOS: `build/bin/MrRSS.app` - - Linux: `build/bin/MrRSS` +A Docker image is available too: + +```bash +docker build -f Dockerfile.server -t mrrss-server:latest . +docker run -p 1234:1234 -v $PWD/data:/app/data mrrss-server:latest +``` + +The API is documented in [docs/SERVER_MODE/swagger.json](docs/SERVER_MODE/swagger.json).
@@ -141,17 +156,24 @@ sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-c
-**Normal Mode** (default): +The backend keeps its database, logs and scripts in a `data` directory. Which +one it uses depends on how it was started, and **the copies are independent of +each other**: -- **Windows:** `%APPDATA%\MrRSS\` (e.g., `C:\Users\YourName\AppData\Roaming\MrRSS\`) -- **macOS:** `~/Library/Application Support/MrRSS/` -- **Linux:** `~/.local/share/MrRSS/` +| How it was started | Data directory | +| --- | --- | +| `./frontend/run.sh` | `data/` in the repository | +| The packaged `.app` | `~/Library/Application Support/MrRSS/data/` | +| `go run .` or the built binary | `data/` beside the directory it was started from | +| The Docker image | `/app/data` in the container | -**Portable Mode** (when `portable.txt` exists): +So the library you build up while running from source is not the one the +installed application reads. To carry one across, quit both and copy +`data/rss.db` (along with `rss.db-shm` and `rss.db-wal` if they are present). -- All data stored in `data/` folder - -This ensures your data persists across application updates and reinstalls. +Because the application's data lives outside the bundle, removing the +application leaves the library in place; delete the directory above to remove it +as well.
@@ -167,21 +189,22 @@ This ensures your data persists across application updates and reinstalls. ### Running in Development Mode -Start the application with hot reloading: - ```bash -# Using Wails v3 -wails3 dev +# Backend and client together +make dev -# Or using Task -task dev +# Backend only, with debug logging +make serve + +# Client only, against a running backend +swift run --package-path frontend MrRSS ``` ### Code Quality Tools #### Using Make -We provide a `Makefile` for handling common development tasks (available on Linux/macOS/Windows): +A `Makefile` covers the common development tasks: ```bash # Show all available commands @@ -215,19 +238,18 @@ pre-commit run --all-files make test ``` -### Local API and Server Mode - -The desktop app exposes its REST API at `http://localhost:1234/api` while it is -running. The listener is restricted to the local computer. +### Local API -For server deployments and API integration, use the headless server version: +The bundled backend serves its REST API at `http://localhost:1234/api`, and the +listener is restricted to the local computer. Point the client at another +address in Settings to read a shared library. ```bash -# Using Docker (recommended) +# Using Docker docker run -p 1234:1234 mrrss-server:latest # Or build from source -go build -tags server -o mrrss-server . +go build -o mrrss-server . ./mrrss-server ``` diff --git a/README_zh.md b/README_zh.md index 60fb90468..0a3bfe291 100644 --- a/README_zh.md +++ b/README_zh.md @@ -8,11 +8,14 @@ English | įŪ€ä―“äļ­æ–‡

-[![Version](https://img.shields.io/badge/version-1.3.27-blue.svg)](https://github.com/DevXDojo/MrRSS/releases) +> **朎分æ”Ŋ构åŧš macOS åŪĒæˆ·įŦŊ。** į•ŒéĒäļš `frontend` äļ­įš„åŽŸį”Ÿ SwiftUI åš”į”Ļ +> Go 后įŦŊä―œäļšįšŊ HTTP API æœåŠĄčŋčĄŒã€‚朎分æ”ŊäļåŒ…åŦ Vue 前įŦŊäļŽ Wails åĪ–åĢģ。 + +[![Version](https://img.shields.io/badge/version-1.3.28-blue.svg)](https://github.com/DevXDojo/MrRSS/releases) [![License](https://img.shields.io/badge/license-GPLv3-green.svg)](LICENSE) [![Go](https://img.shields.io/badge/Go-1.27+-00ADD8?logo=go)](https://go.dev/) -[![Wails](https://img.shields.io/badge/Wails-v3%20alpha-red)](https://wails.io/) -[![Vue.js](https://img.shields.io/badge/Vue.js-3.5+-4FC08D?logo=vue.js)](https://vuejs.org/) +[![Swift](https://img.shields.io/badge/Swift-5.9+-F05138?logo=swift)](https://swift.org/) +[![macOS](https://img.shields.io/badge/macOS-14+-000000?logo=apple)](https://www.apple.com/macos/) ## âœĻ åŠŸčƒ―į‰đ性 @@ -36,22 +39,16 @@
-**标准åŪ‰čĢ…į‰ˆïžš** - -- **Windows:** `MrRSS-{version}-windows-amd64-installer.exe` / `MrRSS-{version}-windows-arm64-installer.exe` -- **macOS:** `MrRSS-{version}-darwin-universal.dmg` -- **Linux:** `MrRSS-{version}-linux-amd64.AppImage` / `MrRSS-{version}-linux-arm64.AppImage` - -**äūŋæšį‰ˆ**无需åŪ‰čĢ…ïžŒæ‰€æœ‰æ•°æŪåœĻäļ€äļŠæ–‡äŧķåĪđ内 +**macOS åŪĒæˆ·įŦŊïžš** -- **Windows:** `MrRSS-{version}-windows-{arch}-portable.zip` -- **Linux:** `MrRSS-{version}-linux-{arch}-portable.tar.gz` -- **macOS:** `MrRSS-{version}-darwin-{arch}-portable.zip` +- `MrRSS-{version}-macos.dmg` — 通į”Ļæžķ构内åŦ后įŦŊįĻ‹åšïžŒæ— éœ€åĶ行åŪ‰čĢ…æœåŠĄįŦŊ **AI Agent Skillsïžš** - **Codex:** `MrRSS-{version}-skills.zip`[ä―ŋį”ĻčŊŽ](docs/SKILLS.zh.md) +朎分æ”Ŋįš„å‘åļƒäŧ…包åŦ macOS åŪĒæˆ·įŦŊ。Windows äļŽ Linux į‰ˆæœŽį”ąéĄđį›Ūäļŧåđē分æ”Ŋ构åŧšã€‚ +
@@ -66,24 +63,11 @@ ##### 前į―ŪčĶæą‚ -åœĻ垀始äđ‹å‰ïžŒčŊ·įĄŪäŋå·ēåŪ‰čĢ…äŧĨäļ‹įŽŊåĒƒïžš - -- [Go](https://go.dev/) (1.27 或æ›īéŦ˜į‰ˆæœŽ) -- [Node.js](https://nodejs.org/) (20 LTS 或æ›īéŦ˜į‰ˆæœŽïžŒåļĶ npm) -- [Wails v3](https://v3alpha.wails.io/getting-started/installation/) CLI +- [Go](https://go.dev/) 1.27 或æ›īéŦ˜į‰ˆæœŽ +- macOS 14 或æ›īéŦ˜į‰ˆæœŽ +- Xcode 15 或æ›īéŦ˜į‰ˆæœŽïžˆæäū› Swift å·Ĩ具é“ū -**åđģ台į‰đåۚčĶæą‚ïžš** - -- **Linux**: GTK4、WebKitGTK 6.0、libsoup 3.0、GCC、pkg-config -- **Windows**: MinGW-w64į”Ļ䚎 CGO æ”Ŋ持、NSISį”Ļ䚎åŪ‰čĢ…åŒ…ïž‰ -- **macOS**: Xcode å‘―äŧĪ行å·Ĩ具 - -čŊĶįŧ†åŪ‰čĢ…čŊŽčŊ·å‚见[构åŧščĶæą‚](docs/BUILD_REQUIREMENTS.md) - -```bash -# Linux åŋŦ速čŪūį―ŪUbuntu 24.04+ -sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-config -``` +čŊĶįŧ†čŊŽčŊ·å‚见[构åŧščĶæą‚](docs/BUILD_REQUIREMENTS.md)。 ##### åŪ‰čĢ…æ­ĨéŠĪ @@ -94,45 +78,72 @@ sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-c cd MrRSS ``` -2. **åŪ‰čĢ…å‰įŦŊäūčĩ–** +2. **čŋčĄŒåŪĒæˆ·įŦŊ** ```bash - cd frontend - npm install - cd .. + ./frontend/run.sh ``` -3. **åŪ‰čĢ… Wails v3 CLI** + čŊĨč„šæœŽäžšæž„åŧš Go 后įŦŊåœĻ `http://127.0.0.1:1234` åŊåŠĻåđķį­‰åū… API å°ąįŧŠïžŒ + 随后åŊåŠĻåŪĒæˆ·įŦŊ。č‹ĨčŊĨ地址å·ēæœ‰æœåŠĄåœĻčŋčĄŒïžŒåˆ™į›īæŽĨåĪį”Ļ。 + +3. **äđŸåŊäŧĨ分åˆŦčŋčĄŒäļĪéƒĻ分** ```bash - go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha2.117 + go run . -host 127.0.0.1 -port 1234 + swift run --package-path frontend MrRSS ``` -4. **构åŧšåš”į”Ļ** + 后įŦŊ地址åŊåœĻčŪūį―Ūäļ­äŋŪæ”đäđŸåŊåœĻåŊåŠĻ前指åŪšïžš ```bash - # ä―ŋį”Ļ TaskæŽĻčïž‰ - task build + MRRSS_API_BASE_URL=http://127.0.0.1:8080/api swift run --package-path frontend MrRSS + ``` - # 或ä―ŋį”Ļ Makefile - make build +4. **构åŧšåš”į”ĻįĻ‹åšåŒ…** - # 或į›īæŽĨä―ŋį”Ļ wails3 - wails3 build + ```bash + make build-app VERSION=1.3.28 ``` - åŊæ‰§čĄŒæ–‡äŧķ将åœĻ `build/bin` į›Ūå―•äļ‹į”Ÿæˆã€‚ + 构åŧšįŧ“æžœäļš `frontend/dist/MrRSS.app` äļŽåŒį›Ūå―•äļ‹įš„ + `frontend/dist/MrRSS-<į‰ˆæœŽ>-macos.dmg`。 + čŊĨåš”į”Ļ包内åŦ后įŦŊįĻ‹åšïžŒæ— éœ€åĶ行åŪ‰čĢ…æœåŠĄįŦŊåģåŊåŊåŠĻ。 -5. **čŋčĄŒåš”į”Ļ** + + + - - Windows: `build/bin/MrRSS.exe` - - macOS: `build/bin/MrRSS.app` - - Linux: `build/bin/MrRSS` +### æœåŠĄå™ĻæĻĄåž + +
+ +į‚đå‡ŧåą•åž€æœåŠĄå™ĻæĻĄåžčŊŽ + +
+ +Go įĻ‹åšæœŽčšŦåģäļš HTTP API æœåŠĄã€‚åŊå•į‹ŽéƒĻį―ēäŧĨäūŋåœĻåĪšå°čŪūå·é—īå…ąäšŦ同äļ€äŧ―čŪĒ阅数æŪ +åđķåœĻåŪĒæˆ·įŦŊčŪūį―Ūäļ­åĄŦ写å…ķ地址 + +```bash +go build -o mrrss-server . +./mrrss-server -host 0.0.0.0 -port 1234 +``` + +äđŸåŊä―ŋį”Ļ Docker 镜像 + +```bash +docker build -f Dockerfile.server -t mrrss-server:latest . +docker run -p 1234:1234 -v $PWD/data:/app/data mrrss-server:latest +``` + +API 文æĄĢ见 [docs/SERVER_MODE/swagger.json](docs/SERVER_MODE/swagger.json)。
+ ### 数æŪ存å‚Ļ
@@ -141,17 +152,21 @@ sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-c
-**æ­ĢåļļæĻĄåž**éŧ˜čŪĪ +后įŦŊ将数æŪ嚓、æ—Ĩåŋ—äļŽč„šæœŽå­˜æ”ūåœĻ名äļš `data` įš„į›Ūå―•äļ­ã€‚å…·ä―“ä―ŋį”Ļ哊äļ€äļŠå–å†ģ䚎åŊåŠĻæ–đ垏 +**各æ–đ垏äđ‹é—īįš„æ•°æŪį›ļäš’į‹ŽįŦ‹**ïžš -- **Windows:** `%APPDATA%\MrRSS\` (äū‹åĶ‚ `C:\Users\YourName\AppData\Roaming\MrRSS\`) -- **macOS:** `~/Library/Application Support/MrRSS/` -- **Linux:** `~/.local/share/MrRSS/` +| åŊåŠĻæ–đ垏 | 数æŪį›Ūå―• | +| --- | --- | +| `./frontend/run.sh` | äŧ“å𓿠đį›Ūå―•äļ‹įš„ `data/` | +| æ‰“åŒ…åŽįš„ `.app` | `~/Library/Application Support/MrRSS/data/` | +| `go run .` 或å·ēįž–čŊ‘įš„äšŒčŋ›åˆķ | åŊåŠĻæ—ķ所åœĻį›Ūå―•äļ‹įš„ `data/` | +| Docker 镜像 | åŪđå™Ļå†…įš„ `/app/data` | -**äūŋ搚æĻĄåž**ïžˆå―“ `portable.txt` 文äŧķ存åœĻæ—ķ +因æ­ĪäŧŽæšį čŋčĄŒæ—ķį§ŊįīŊįš„čŪĒ阅数æŪåđķäļæ˜Ŋå·ēåŪ‰čĢ…åš”į”Ļ所čŊŧå–įš„é‚Ģäļ€äŧ―。åĶ‚éœ€čŋį§ŧ +čŊ·å…ˆé€€å‡šåŒæ–đ再åĪåˆķ `data/rss.db`č‹Ĩ存åœĻ `rss.db-shm` äļŽ `rss.db-wal` äđŸäļ€åđķåĪåˆķ。 -- 所有数æŪ存å‚ĻåœĻ `data/` 文äŧķåĪđäļ­ - -čŋ™įĄŪäŋäš†æ‚Ļįš„æ•°æŪåœĻåš”į”Ļæ›ī新和重新åŪ‰čĢ…æ—ķåū—äŧĨäŋį•™ã€‚ +į”ąäšŽåš”į”Ļ数æŪ存æ”ūåœĻįĻ‹åšåŒ…äđ‹åĪ–ïžŒåˆ é™Īåš”į”Ļäļäžšåˆ é™Ī数æŪïž›åĶ‚éœ€äļ€åđķæļ…é™ĪčŊ·æ‰‹åŠĻ删é™ĪäļŠčĄĻäļ­ +åŊđåš”įš„į›Ūå―•ã€‚
@@ -170,11 +185,14 @@ sudo apt-get install libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-c åŊåŠĻåļĶæœ‰įƒ­é‡č――įš„åš”į”Ļïžš ```bash -# ä―ŋį”Ļ Wails v3 -wails3 dev +# 同æ—ķåŊåŠĻ后įŦŊäļŽåŪĒæˆ·įŦŊ +make dev + +# äŧ…åŊåŠĻ后įŦŊ垀åŊ调čŊ•æ—Ĩåŋ— +make serve -# 或ä―ŋį”Ļ Task -task dev +# äŧ…åŊåŠĻåŪĒæˆ·įŦŊčŋžæŽĨå·ēčŋčĄŒįš„后įŦŊ +swift run --package-path frontend MrRSS ``` ### äŧĢ᠁čīĻ量å·Ĩ具 @@ -227,7 +245,7 @@ make test docker run -p 1234:1234 mrrss-server:latest # 或äŧŽæšį æž„åŧš -go build -tags server -o mrrss-server . +go build -o mrrss-server . ./mrrss-server ``` diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index a8557b0a4..000000000 --- a/Taskfile.yml +++ /dev/null @@ -1,58 +0,0 @@ -version: '3' - -includes: - common: ./build/Taskfile.yml - windows: ./build/windows/Taskfile.yml - darwin: ./build/darwin/Taskfile.yml - linux: ./build/linux/Taskfile.yml - # iOS and Android are optional - only include if needed - # ios: ./build/ios/Taskfile.yml - # android: ./build/android/Taskfile.yml - -vars: - APP_NAME: "MrRSS" - BIN_DIR: "build/bin" - VITE_PORT: '{{.WAILS_VITE_PORT | default 5173}}' - -tasks: - build: - summary: Builds the application - cmds: - - task: "{{OS}}:build" - - package: - summary: Packages a production build of the application - cmds: - - task: "{{OS}}:package" - - run: - summary: Runs the application - cmds: - - task: "{{OS}}:run" - - dev: - summary: Runs the application in development mode (monitoring disabled) - cmds: - - wails3 dev -config ./build/config.yml - env: - MRRSS_DEBUG: "1" - - setup:docker: - summary: Builds Docker image for cross-compilation (~800MB download) - cmds: - - task: common:setup:docker - - build:server: - summary: Builds the server version using Docker - cmds: - - task: "{{OS}}:build:server" - - docker:build:server: - summary: Builds the server version Docker image - cmds: - - docker build -f Dockerfile.server -t mrrss-server:latest . - - docker:run:server: - summary: Runs the server version in Docker - cmds: - - docker run -p 1234:1234 -v {{.PWD}}/data:/app/data mrrss-server:latest diff --git a/build/README.md b/build/README.md index c91a15b01..5939ff797 100644 --- a/build/README.md +++ b/build/README.md @@ -1,319 +1,36 @@ -# Wails v3 Build System Guide +# Build Assets -This document describes how to build and package MrRSS using the Wails v3 build system with Task runner. +This directory holds the icon assets used when packaging the macOS client. -## Prerequisites +| File | Purpose | +| --- | --- | +| `appicon.png` | Source icon artwork | +| `darwin/icons.icns` | Icon bundled into `MrRSS.app` | -### Required Tools +## Building the application -- **Go 1.27+**: [https://golang.org/dl/](https://golang.org/dl/) -- **Node.js 18+**: [https://nodejs.org/](https://nodejs.org/) -- **Wails CLI v3**: `go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha2.117` -- **Task**: [https://taskfile.dev/installation/](https://taskfile.dev/installation/) - -### Platform-Specific Dependencies - -#### Windows - -```powershell -choco install mingw nsis -y -``` - -#### Linux (Ubuntu/Debian) - -```bash -sudo apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config -``` - -#### macOS - -```bash -xcode-select --install -``` - -## Quick Start - -### Development Mode - -Run the application in development mode with hot reload: - -```bash -# Using wails3 dev command -wails3 dev - -# Or using task -task dev -``` - -The dev server will start on port 5173 (configurable via WAILS_VITE_PORT). - -### Building for Your Platform - -Build for your current platform: - -```bash -# Using task (recommended) -task build - -# Or directly via platform-specific task -task windows:build # on Windows -task linux:build # on Linux -task darwin:build # on macOS -``` - -Build output: `build/bin/` - -### Packaging - -Create installers and packages: +The macOS client is a Swift package in `frontend`, and the Go backend is +built as a plain binary that the bundle launches on demand. ```bash -# Using task -task package - -# Platform-specific -task windows:package # Creates NSIS installer -task linux:package # Creates AppImage + tar.gz -task darwin:package # Creates DMG -``` - -## Advanced Usage - -### Cross-Platform Builds - -#### Using Docker (Linux/Windows only) - -First, build the Docker image (one-time setup): - -```bash -task setup:docker -``` - -Then build for other platforms: - -```bash -# From any OS, build for Windows -task windows:build CGO_ENABLED=1 - -# From any OS, build for Linux -task linux:build CGO_ENABLED=1 -``` - -**Note**: macOS builds should be done on native macOS runners due to signing requirements. - -### Architecture-Specific Builds - -```bash -# Build for specific architecture -task windows:build ARCH=amd64 -task windows:build ARCH=arm64 - -task linux:build ARCH=amd64 -task linux:build ARCH=arm64 - -task darwin:build ARCH=universal # Intel + Apple Silicon -``` - -### Mobile Platforms (Wails v3 only) - -**Note**: iOS and Android support is experimental in Wails v3 alpha. - -```bash -# iOS (requires macOS + Xcode) -task ios:build - -# Android (requires Android SDK) -task android:build -``` - -## Task Commands Reference - -### Common Tasks - -- `task build` - Build application for current platform -- `task package` - Package application with installer -- `task run` - Run the built application -- `task dev` - Run in development mode -- `task setup:docker` - Build Docker image for cross-compilation - -### Platform-Specific Tasks - -#### Windows - -- `task windows:build` - Build Windows executable -- `task windows:package` - Create NSIS installer -- `task windows:sign` - Sign executable (requires certificate) - -#### Linux +# Build the .app bundle and the DMG +make build-app VERSION=1.3.28 -- `task linux:build` - Build Linux binary -- `task linux:package` - Create AppImage + packages -- `task linux:create:appimage` - Create AppImage only -- `task linux:create:tarball` - Create tar.gz only - -#### macOS - -- `task darwin:build` - Build macOS app bundle -- `task darwin:package` - Create DMG installer -- `task darwin:sign` - Sign app bundle (requires certificate) -- `task darwin:notarize` - Notarize with Apple (requires profile) - -### Frontend Tasks - -- `task common:install:frontend:deps` - Install frontend dependencies -- `task common:build:frontend` - Build frontend for production -- `task common:dev:frontend` - Run frontend dev server - -### Utility Tasks - -- `task common:generate:icons` - Generate platform icons from appicon.png -- `task common:generate:bindings` - Generate TypeScript bindings -- `task common:update:build-assets` - Update build assets from config - -## Configuration - -### Main Configuration - -Edit `build/config.yml` to change: - -- Application name and version -- Company information -- Bundle identifiers -- Dev mode settings - -### Platform-Specific Configuration - -Each platform has its own Taskfile in `build//Taskfile.yml`: - -- `build/windows/Taskfile.yml` - Windows build configuration -- `build/linux/Taskfile.yml` - Linux build configuration -- `build/darwin/Taskfile.yml` - macOS build configuration - -### Signing Configuration - -#### Windows - -Edit `build/windows/Taskfile.yml`: - -```yaml -vars: - SIGN_CERTIFICATE: "path/to/certificate.pfx" - TIMESTAMP_SERVER: "http://timestamp.digicert.com" -``` - -Then setup password: - -```bash -wails3 setup signing -``` - -#### macOS - -Edit `build/darwin/Taskfile.yml`: - -```yaml -vars: - SIGN_IDENTITY: "Developer ID Application: Your Name (TEAM_ID)" - NOTARIZATION_PROFILE: "notarization-profile-name" -``` - -#### Linux - -Edit `build/linux/Taskfile.yml`: - -```yaml -vars: - PGP_KEY: "path/to/signing-key.asc" -``` - -## GitHub Actions - -The project includes automated workflows: - -### Release Workflow - -Triggered manually via GitHub Actions UI: - -1. Go to Actions → Release -2. Click "Run workflow" -3. Enter version (e.g., `v1.2.21`) -4. Click "Run workflow" - -Builds for all platforms: - -- Windows (AMD64, ARM64) -- Linux (AMD64, ARM64) -- macOS (Universal) - -### Test Build Workflow - -Triggered on push/PR to main: - -- Tests build on all platforms -- Validates configuration - -## Troubleshooting - -### CGO is disabled - -**Solution**: CGO must be enabled for Wails v3: - -```bash -export CGO_ENABLED=1 -task build -``` - -### Missing dependencies (Linux) - -**Solution**: Install all required libraries: - -```bash -sudo apt-get update -sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev libsoup-3.0-dev gcc pkg-config -``` - -### Task not found - -**Solution**: Install Task runner: - -```bash -# macOS -brew install go-task - -# Linux -sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin - -# Windows -choco install go-task -``` - -### Frontend build fails - -**Solution**: Clean and reinstall dependencies: - -```bash -cd frontend -rm -rf node_modules package-lock.json -npm install -npm run build +# Or call the script directly +./frontend/build-app.sh 1.3.28 ``` -## Resources +The script builds a universal SwiftUI executable, builds the Go backend for +`arm64` and `x86_64`, merges them with `lipo`, copies the icon and `Info.plist`, +signs the bundle, and produces `frontend/dist/MrRSS--macos.dmg`. -- [Wails v3 Documentation](https://v3.wails.io/) -- [Task Documentation](https://taskfile.dev/) -- [Build Configuration Reference](./config.yml) -- [GitHub Actions Workflows](../.github/workflows/) +Set `MRRSS_BUILD_ARCHS` to limit the architectures, for example +`MRRSS_BUILD_ARCHS=arm64 ./frontend/build-app.sh dev` for a faster local build. -## Support +## Requirements -For issues: +- macOS 14 or later +- Xcode 15 or later +- Go 1.27 or later -1. Check [GitHub Issues](https://github.com/DevXDojo/MrRSS/issues) -2. Review [Build Requirements](../docs/BUILD_REQUIREMENTS.md) -3. Check [Wails Discord](https://discord.gg/wails) +See [docs/BUILD_REQUIREMENTS.md](../docs/BUILD_REQUIREMENTS.md) for details. diff --git a/build/Taskfile.yml b/build/Taskfile.yml deleted file mode 100644 index 32a8d94c1..000000000 --- a/build/Taskfile.yml +++ /dev/null @@ -1,104 +0,0 @@ -version: '3' - -tasks: - go:mod:tidy: - summary: Runs `go mod tidy` - internal: true - sources: - - go.mod - - go.sum - cmds: - - go mod tidy - - install:frontend:deps: - summary: Install frontend dependencies - dir: frontend - sources: - - package.json - - package-lock.json - generates: - - node_modules - preconditions: - - sh: npm version - msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" - cmds: - - npm install - - build:frontend: - label: build:frontend (DEV={{.DEV}}) - summary: Build the frontend project - dir: frontend - sources: - - "**/*" - generates: - - dist/**/* - deps: - - task: install:frontend:deps - - task: generate:bindings - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - cmds: - - npm run {{.BUILD_COMMAND}} -q - env: - PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}' - vars: - BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}' - - generate:bindings: - label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) - summary: Generates bindings for the frontend - deps: - - task: go:mod:tidy - sources: - - "**/*.[jt]s" - - exclude: frontend/**/* - - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output - - "**/*.go" - - go.mod - - go.sum - generates: - - frontend/bindings/**/* - cmds: - - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true - - generate:icons: - summary: Generates Windows `.ico` and Mac `.icns` files from an image - dir: build - sources: - - "appicon.png" - - "windows/icon.png" - generates: - - "darwin/icons.icns" - - "windows/icon.ico" - cmds: - # Wails supplies build/windows and build/darwin defaults for omitted output - # flags. This task already runs from build/, so explicitly disable the - # unrelated output while preserving the platform-specific source images. - - wails3 generate icons -input appicon.png -windowsfilename= -macfilename darwin/icons.icns - - wails3 generate icons -input windows/icon.png -windowsfilename windows/icon.ico -macfilename= - - dev:frontend: - summary: Runs the frontend in development mode - dir: frontend - deps: - - task: install:frontend:deps - cmds: - - npm run dev -- --port {{.VITE_PORT}} --strictPort - - update:build-assets: - summary: Updates the build assets - dir: build - cmds: - - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . - - setup:docker: - summary: Builds Docker image for cross-compilation (~800MB download) - desc: | - Builds the Docker image needed for cross-compiling to any platform. - Run this once to enable cross-platform builds from any OS. - cmds: - - docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/ - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required. Please install Docker first." diff --git a/build/config.yml b/build/config.yml deleted file mode 100644 index 2b4ef5184..000000000 --- a/build/config.yml +++ /dev/null @@ -1,71 +0,0 @@ -# This file contains the configuration for this project. -# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets. -# Note that this will overwrite any changes you have made to the assets. -version: '3' - -# This information is used to generate the build assets. -info: - companyName: "Ch3nyang" # The name of the company - productName: "MrRSS" # The name of the application - productIdentifier: "com.mrrss.app" # The unique product identifier - description: "A modern, standalone RSS reader" # The application description - copyright: "Copyright ÂĐ 2026" # Copyright text - comments: "Built with Wails" # Comments - version: "1.3.27" # The application version - -# iOS build configuration (uncomment to customise iOS project generation) -# Note: Keys under `ios` OVERRIDE values under `info` when set. -# ios: -# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier) -# bundleID: "com.mrrss.app" -# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName) -# displayName: "MrRSS" -# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion) -# version: "1.3.27" -# # The company/organisation name for templates and project settings -# company: "Ch3nyang" -# # Additional comments to embed in Info.plist metadata -# comments: "Built with Wails" - -# Dev mode configuration -dev_mode: - root_path: . - log_level: warn - debounce: 1000 - ignore: - dir: - - .git - - node_modules - - frontend - - bin - - build - file: - - .DS_Store - - .gitignore - - .gitkeep - watched_extension: - - "*.go" - - "*.js" # Watch for changes to JS/TS files included using the //wails:include directive. - - "*.ts" # The frontend directory will be excluded entirely by the setting above. - git_ignore: true - executes: - - cmd: wails3 build DEV=true - type: blocking - - cmd: wails3 task common:dev:frontend - type: background - - cmd: wails3 task run - type: primary - -# File Associations -# More information at: https://v3.wails.io/noit/done/yet -fileAssociations: -# - ext: opml -# name: OPML -# description: OPML File -# iconName: opmlFileIcon -# role: Editor -# mimeType: text/x-opml - -# Other data -other: - - name: MrRSS Project Data diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist deleted file mode 100644 index 9f22e4ba7..000000000 --- a/build/darwin/Info.dev.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - MrRSS - CFBundleExecutable - MrRSS - CFBundleIdentifier - com.mrrss.app - CFBundleVersion - 1.3.24 - CFBundleGetInfoString - Built with Wails - CFBundleShortVersionString - 1.3.24 - CFBundleIconFile - icons - LSMinimumSystemVersion - 10.15.0 - NSHighResolutionCapable - true - NSHumanReadableCopyright - Copyright ÂĐ 2026 - NSAppTransportSecurity - - NSAllowsLocalNetworking - - - - diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist deleted file mode 100644 index 4ba250ef3..000000000 --- a/build/darwin/Info.plist +++ /dev/null @@ -1,65 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - MrRSS - CFBundleExecutable - MrRSS - CFBundleIdentifier - com.mrrss.app - CFBundleVersion - 1.3.24 - CFBundleGetInfoString - Built with Wails - CFBundleShortVersionString - 1.3.24 - CFBundleIconFile - icons - LSMinimumSystemVersion - 10.13 - NSHighResolutionCapable - true - NSSupportsAutomaticGraphicsSwitching - true - NSHumanReadableCopyright - Copyright ÂĐ 2026 - - - - - - - NSAppleEventsUsageDescription - MrRSS needs permission to open articles in your default web browser and execute custom feed scripts. - - - NSNetworkUsageDescription - MrRSS requires network access to fetch RSS feeds and update content. - - - NSDocumentsFolderUsageDescription - MrRSS needs access to Documents folder to import and export OPML feed lists. - - NSDownloadsFolderUsageDescription - MrRSS needs access to Downloads folder to import feed lists and save exported content. - - - - - - - - - - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains - - - - diff --git a/build/darwin/Taskfile.yml b/build/darwin/Taskfile.yml deleted file mode 100644 index 272ecf113..000000000 --- a/build/darwin/Taskfile.yml +++ /dev/null @@ -1,215 +0,0 @@ -version: '3' - -includes: - common: ../Taskfile.yml - -vars: - # Signing configuration - edit these values for your project - # SIGN_IDENTITY: "Developer ID Application: Your Name (TEAM_ID)" - # NOTARIZATION_PROFILE: "notarization-profile-name" - # - # Password/API Key is stored securely in system keychain. Run: wails3 setup signing - -tasks: - build: - summary: Builds the application for macOS - deps: - - task: common:go:mod:tidy - - task: common:build:frontend - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - DEV: - ref: .DEV - - task: common:generate:icons - cmds: - # Check if building universal binary - - | - if [ "{{.ARCH}}" = "universal" ]; then - echo "Building universal binary..." - # Build for amd64 - GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-amd64 - # Build for arm64 - GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-arm64 - # Merge with lipo - lipo -create -output {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}-amd64 {{.BIN_DIR}}/{{.APP_NAME}}-arm64 - # Clean up intermediate files - rm -f {{.BIN_DIR}}/{{.APP_NAME}}-amd64 {{.BIN_DIR}}/{{.APP_NAME}}-arm64 - else - # Build for specific architecture - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} - fi - - task: create:app:bundle - vars: - BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' - env: - GOOS: darwin - CGO_ENABLED: 1 - GOARCH: '{{if ne .ARCH "universal"}}{{.ARCH | default ARCH}}{{else}}{{end}}' - - create:app:bundle: - summary: Creates a macOS .app bundle - internal: true - cmds: - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS - - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources - - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS/ - - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources/ - - | - cat > {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Info.plist << EOF - - - - - CFBundleExecutable - {{.APP_NAME}} - CFBundleIconFile - icons.icns - CFBundleIdentifier - com.mrrss.app - CFBundleName - {{.APP_NAME}} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.3.24 - CFBundleVersion - 1.3.24 - LSMinimumSystemVersion - 10.13 - NSHighResolutionCapable - - NSSupportsAutomaticGraphicsSwitching - - NSHumanReadableCopyright - Copyright ÂĐ 2026 - NSAppleEventsUsageDescription - MrRSS needs permission to open articles in your default web browser and execute custom feed scripts. - NSNetworkUsageDescription - MrRSS requires network access to fetch RSS feeds and update content. - NSDocumentsFolderUsageDescription - MrRSS needs access to Documents folder to import and export OPML feed lists. - NSDownloadsFolderUsageDescription - MrRSS needs access to Downloads folder to import feed lists and save exported content. - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains - - - - - EOF - - package: - summary: Signs and packages the application into a DMG - deps: - - task: sign:dev - cmds: - - task: create:dmg - - create:dmg: - summary: Creates a DMG installer - cmds: - - | - if [ -f "build/darwin/create-dmg.sh" ]; then - chmod +x build/darwin/create-dmg.sh - ./build/darwin/create-dmg.sh - else - echo "Warning: create-dmg.sh not found, skipping DMG creation" - fi - - run: - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS/{{.APP_NAME}}' - - sign: - summary: Signs the macOS application - desc: | - Signs the .app bundle with an Apple Developer certificate. - Configure SIGN_IDENTITY in the vars section at the top of this file. - deps: - - task: build - cmds: - - | - # Check if entitlements file exists - if [ -f "build/darwin/entitlements.plist" ]; then - echo "Signing with entitlements..." - codesign --deep --force --verify --verbose \ - --sign "{{.SIGN_IDENTITY}}" \ - --entitlements build/darwin/entitlements.plist \ - --options runtime \ - {{.BIN_DIR}}/{{.APP_NAME}}.app - else - echo "Signing without entitlements (entitlements.plist not found)..." - codesign --deep --force --verify --verbose \ - --sign "{{.SIGN_IDENTITY}}" \ - --options runtime \ - {{.BIN_DIR}}/{{.APP_NAME}}.app - fi - preconditions: - - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' - msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" - - notarize: - summary: Notarizes the macOS application - desc: | - Notarizes the .app bundle or DMG with Apple's notarization service. - Configure NOTARIZATION_PROFILE in the vars section at the top of this file. - deps: - - task: sign - cmds: - - xcrun notarytool submit {{.BIN_DIR}}/{{.APP_NAME}}.app --keychain-profile "{{.NOTARIZATION_PROFILE}}" --wait - - xcrun stapler staple {{.BIN_DIR}}/{{.APP_NAME}}.app - preconditions: - - sh: '[ -n "{{.NOTARIZATION_PROFILE}}" ]' - msg: "NOTARIZATION_PROFILE is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" - - sign:dev: - summary: Signs the macOS application for development (ad-hoc signature) - desc: | - Signs the .app bundle with ad-hoc signature for local development testing. - This does not require an Apple Developer certificate. - deps: - - task: build - cmds: - - | - # Remove any existing signature - codesign --remove-signature {{.BIN_DIR}}/{{.APP_NAME}}.app 2>/dev/null || true - - # Apply ad-hoc signature with entitlements - if [ -f "build/darwin/entitlements.plist" ]; then - echo "Applying ad-hoc signature with entitlements..." - codesign --deep --force --verify --verbose \ - --sign - \ - --entitlements build/darwin/entitlements.plist \ - {{.BIN_DIR}}/{{.APP_NAME}}.app - else - echo "Applying ad-hoc signature without entitlements..." - codesign --deep --force --verify --verbose \ - --sign - \ - {{.BIN_DIR}}/{{.APP_NAME}}.app - fi - - # Verify signature - codesign --verify --deep --strict --verbose=2 {{.BIN_DIR}}/{{.APP_NAME}}.app - build:server: - summary: Builds the server version using Docker - deps: - - task: common:build:frontend - cmds: - - docker build -f Dockerfile.server -t mrrss-server:latest . - - docker create --name mrrss-server-temp mrrss-server:latest - - docker cp mrrss-server-temp:/app/mrrss-server {{.BIN_DIR}}/{{.APP_NAME}}-server - - docker rm mrrss-server-temp - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required for building the server version" - - run:server: - summary: Runs the server version - deps: - - task: build:server - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}-server' diff --git a/build/darwin/create-dmg.sh b/build/darwin/create-dmg.sh deleted file mode 100644 index 7a7dc573f..000000000 --- a/build/darwin/create-dmg.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# Script to create a macOS DMG installer for MrRSS -# -# Application Information: -# Name: MrRSS -# Description: A Modern, Cross-Platform Desktop RSS Reader -# Publisher: Ch3nyang -# URL: https://github.com/DevXDojo/MrRSS -# Copyright: Copyright ÂĐ Ch3nyang - -set -e - -APP_NAME="MrRSS" -# Get version from frontend/package.json if available, otherwise use default -VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' frontend/package.json 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "1.3.24") -APP_PUBLISHER="Ch3nyang" -APP_URL="https://github.com/DevXDojo/MrRSS" -APP_DESCRIPTION="A Modern, Cross-Platform Desktop RSS Reader" -BUILD_DIR="build/bin" -DMG_DIR="build/dmg" -APP_PATH="${BUILD_DIR}/${APP_NAME}.app" -DMG_NAME="${APP_NAME}-${VERSION}-darwin-universal.dmg" - -echo "Creating DMG for ${APP_NAME} ${VERSION}..." -echo "Publisher: ${APP_PUBLISHER}" -echo "Description: ${APP_DESCRIPTION}" -echo "" - -# Check if app exists -if [ ! -d "${APP_PATH}" ]; then - echo "Error: Application not found at ${APP_PATH}" - echo "Please build the application first with: wails3 build -platform darwin/universal" - exit 1 -fi - -# Create DMG directory -rm -rf "${DMG_DIR}" -mkdir -p "${DMG_DIR}" - -# Copy app to DMG directory -echo "Copying application..." -cp -R "${APP_PATH}" "${DMG_DIR}/" - -# Create Applications symlink -echo "Creating Applications symlink..." -ln -s /Applications "${DMG_DIR}/Applications" - -# Create DMG -echo "Creating DMG image..." -rm -f "${BUILD_DIR}/${DMG_NAME}" - -# Use hdiutil to create the DMG -if ! hdiutil create -volname "${APP_NAME}" \ - -srcfolder "${DMG_DIR}" \ - -ov -format UDZO \ - "${BUILD_DIR}/${DMG_NAME}"; then - echo "Error: Failed to create DMG with hdiutil" - echo "This might be due to permissions or disk space issues" - exit 1 -fi - -# Verify the DMG was created -if [ ! -f "${BUILD_DIR}/${DMG_NAME}" ]; then - echo "Error: DMG file was not created at ${BUILD_DIR}/${DMG_NAME}" - exit 1 -fi - -# Clean up -rm -rf "${DMG_DIR}" - -echo "DMG created successfully: ${BUILD_DIR}/${DMG_NAME}" -echo "" -echo "Installation instructions:" -echo "1. Open the DMG file" -echo "2. Drag ${APP_NAME}.app to the Applications folder" -echo "3. Launch ${APP_NAME} from Applications" -echo "" -echo "User data will be stored in: ~/Library/Application Support/MrRSS/" diff --git a/build/darwin/entitlements.plist b/build/darwin/entitlements.plist deleted file mode 100644 index 74ad9698f..000000000 --- a/build/darwin/entitlements.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - com.apple.security.automation.apple-events - - com.apple.security.files.downloads.read-write - - com.apple.security.files.user-selected.read-only - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - - diff --git a/build/docker/Dockerfile.cross b/build/docker/Dockerfile.cross deleted file mode 100644 index e1ad1a216..000000000 --- a/build/docker/Dockerfile.cross +++ /dev/null @@ -1,42 +0,0 @@ -# Multi-platform cross-compilation Docker image for Wails v3 -# Supports building for Windows, Linux, macOS with CGO enabled using Zig as CC - -FROM golang:1.27-bookworm - -# Install basic tools -RUN apt-get update && apt-get install -y \ - wget \ - git \ - xz-utils \ - zip \ - unzip \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Install Zig (for cross-compilation with CGO) -ARG ZIG_VERSION=0.13.0 -RUN wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ - tar -xf zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ - mv zig-linux-x86_64-${ZIG_VERSION} /usr/local/zig && \ - ln -s /usr/local/zig/zig /usr/local/bin/zig && \ - rm zig-linux-x86_64-${ZIG_VERSION}.tar.xz - -# Install Linux development dependencies -RUN apt-get update && apt-get install -y \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev \ - gcc \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -# Install NSIS for Windows installer creation -RUN apt-get update && apt-get install -y nsis && rm -rf /var/lib/apt/lists/* - -# Create build script -COPY build-script.sh /usr/local/bin/build-cross -RUN chmod +x /usr/local/bin/build-cross - -WORKDIR /app - -ENTRYPOINT ["/usr/local/bin/build-cross"] diff --git a/build/docker/build-script.sh b/build/docker/build-script.sh deleted file mode 100644 index da7777c73..000000000 --- a/build/docker/build-script.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -set -e - -PLATFORM=$1 -ARCH=${2:-amd64} -APP_NAME=${APP_NAME:-MrRSS} - -echo "Building for $PLATFORM/$ARCH..." - -# Setup Zig as CC for cross-compilation -export CC="zig cc -target" -export CXX="zig c++ -target" - -case "$PLATFORM" in - windows) - if [ "$ARCH" = "amd64" ]; then - export GOOS=windows - export GOARCH=amd64 - export CC="$CC x86_64-windows-gnu" - export CXX="$CXX x86_64-windows-gnu" - elif [ "$ARCH" = "arm64" ]; then - export GOOS=windows - export GOARCH=arm64 - export CC="$CC aarch64-windows-gnu" - export CXX="$CXX aarch64-windows-gnu" - fi - export CGO_ENABLED=1 - go build -tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui" -o bin/${APP_NAME}.exe . - ;; - - linux) - if [ "$ARCH" = "amd64" ]; then - export GOOS=linux - export GOARCH=amd64 - export CC="$CC x86_64-linux-gnu" - export CXX="$CXX x86_64-linux-gnu" - elif [ "$ARCH" = "arm64" ]; then - export GOOS=linux - export GOARCH=arm64 - export CC="$CC aarch64-linux-gnu" - export CXX="$CXX aarch64-linux-gnu" - fi - export CGO_ENABLED=1 - export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig - go build -tags production -trimpath -buildvcs=false -ldflags="-w -s" -o bin/${APP_NAME}-${PLATFORM}-${ARCH} . - ;; - - darwin) - # Note: macOS cross-compilation is complex, better to build natively on macOS - echo "macOS builds should be done on native macOS runners" - exit 1 - ;; - - *) - echo "Unknown platform: $PLATFORM" - exit 1 - ;; -esac - -echo "Build complete!" diff --git a/build/ios/Assets.xcassets b/build/ios/Assets.xcassets deleted file mode 100644 index 11a1df693..000000000 --- a/build/ios/Assets.xcassets +++ /dev/null @@ -1,116 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "images" : [ - { - "filename" : "icon-20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon-20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "icon-29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon-29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "icon-40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon-40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "icon-60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "icon-60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "icon-20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "icon-20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon-29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "icon-29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon-40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "icon-40@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon-76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "icon-76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "icon-83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "icon-1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ] -} diff --git a/build/ios/Info.dev.plist b/build/ios/Info.dev.plist deleted file mode 100644 index 4c7e630df..000000000 --- a/build/ios/Info.dev.plist +++ /dev/null @@ -1,62 +0,0 @@ - - - - - CFBundleExecutable - MrRSS - CFBundleIdentifier - com.mrrss.app.dev - CFBundleName - MrRSS (Dev) - CFBundleDisplayName - MrRSS (Dev) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.3.24-dev - CFBundleVersion - 1.3.24 - LSRequiresIPhoneOS - - MinimumOSVersion - 15.0 - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - - WailsDevelopmentMode - - - NSHumanReadableCopyright - Copyright ÂĐ 2026 - - - CFBundleGetInfoString - Built with Wails - - - diff --git a/build/ios/Info.plist b/build/ios/Info.plist deleted file mode 100644 index 9822a655e..000000000 --- a/build/ios/Info.plist +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CFBundleExecutable - MrRSS - CFBundleIdentifier - com.mrrss.app - CFBundleName - MrRSS - CFBundleDisplayName - MrRSS - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.3.24 - CFBundleVersion - 1.3.24 - LSRequiresIPhoneOS - - MinimumOSVersion - 13.0 - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - - NSHumanReadableCopyright - Copyright ÂĐ 2026 - - - CFBundleGetInfoString - Built with Wails - - - diff --git a/build/ios/LaunchScreen.storyboard b/build/ios/LaunchScreen.storyboard deleted file mode 100644 index bbc331be9..000000000 --- a/build/ios/LaunchScreen.storyboard +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/ios/build.sh b/build/ios/build.sh deleted file mode 100644 index 9e77df629..000000000 --- a/build/ios/build.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -set -e - -# Build configuration -APP_NAME="MrRSS" -BUNDLE_ID="com.mrrss.app" -VERSION="1.3.24" -BUILD_NUMBER="1.3.24" -BUILD_DIR="build/ios" -TARGET="simulator" - -echo "Building iOS app: $APP_NAME" -echo "Bundle ID: $BUNDLE_ID" -echo "Version: $VERSION ($BUILD_NUMBER)" -echo "Target: $TARGET" - -# Ensure build directory exists -mkdir -p "$BUILD_DIR" - -# Determine SDK and target architecture -if [ "$TARGET" = "simulator" ]; then - SDK="iphonesimulator" - ARCH="arm64-apple-ios15.0-simulator" -elif [ "$TARGET" = "device" ]; then - SDK="iphoneos" - ARCH="arm64-apple-ios15.0" -else - echo "Unknown target: $TARGET" - exit 1 -fi - -# Get SDK path -SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path) - -# Compile the application -echo "Compiling with SDK: $SDK" -xcrun -sdk $SDK clang \ - -target $ARCH \ - -isysroot "$SDK_PATH" \ - -framework Foundation \ - -framework UIKit \ - -framework WebKit \ - -framework CoreGraphics \ - -o "$BUILD_DIR/$APP_NAME" \ - "$BUILD_DIR/main.m" - -# Create app bundle -echo "Creating app bundle..." -APP_BUNDLE="$BUILD_DIR/$APP_NAME.app" -rm -rf "$APP_BUNDLE" -mkdir -p "$APP_BUNDLE" - -# Move executable -mv "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/" - -# Copy Info.plist -cp "$BUILD_DIR/Info.plist" "$APP_BUNDLE/" - -# Sign the app -echo "Signing app..." -codesign --force --sign - "$APP_BUNDLE" - -echo "Build complete: $APP_BUNDLE" - -# Deploy to simulator if requested -if [ "$TARGET" = "simulator" ]; then - echo "Deploying to simulator..." - xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true - xcrun simctl install booted "$APP_BUNDLE" - xcrun simctl launch booted "$BUNDLE_ID" - echo "App launched on simulator" -fi diff --git a/build/ios/entitlements.plist b/build/ios/entitlements.plist deleted file mode 100644 index a2c7225da..000000000 --- a/build/ios/entitlements.plist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - get-task-allow - - - - com.apple.security.app-sandbox - - - - com.apple.security.network.client - - - - com.apple.security.files.user-selected.read-only - - - diff --git a/build/ios/project.pbxproj b/build/ios/project.pbxproj deleted file mode 100644 index bbf0ba9e6..000000000 --- a/build/ios/project.pbxproj +++ /dev/null @@ -1,222 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = {}; - objectVersion = 56; - objects = { - -/* Begin PBXBuildFile section */ - C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; }; - C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; }; - C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; }; - C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; }; - C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; }; - C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; }; - C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; }; - C0DEBEEF00000000000000F7 /* MrRSS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* MrRSS.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C0DEBEEF0000000000000004 /* MrRSS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MrRSS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; - C0DEBEEF0000000000000107 /* MrRSS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "MrRSS.a"; path = ../../../bin/MrRSS.a; sourceTree = SOURCE_ROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXGroup section */ - C0DEBEEF0000000000000010 = { - isa = PBXGroup; - children = ( - C0DEBEEF0000000000000020 /* Products */, - C0DEBEEF0000000000000045 /* Frameworks */, - C0DEBEEF0000000000000030 /* main */, - ); - sourceTree = ""; - }; - C0DEBEEF0000000000000020 /* Products */ = { - isa = PBXGroup; - children = ( - C0DEBEEF0000000000000004 /* MrRSS.app */, - ); - name = Products; - sourceTree = ""; - }; - C0DEBEEF0000000000000030 /* main */ = { - isa = PBXGroup; - children = ( - C0DEBEEF0000000000000002 /* main.m */, - C0DEBEEF0000000000000003 /* Info.plist */, - ); - path = main; - sourceTree = SOURCE_ROOT; - }; - C0DEBEEF0000000000000045 /* Frameworks */ = { - isa = PBXGroup; - children = ( - C0DEBEEF0000000000000101 /* UIKit.framework */, - C0DEBEEF0000000000000102 /* Foundation.framework */, - C0DEBEEF0000000000000103 /* WebKit.framework */, - C0DEBEEF0000000000000104 /* Security.framework */, - C0DEBEEF0000000000000105 /* CoreFoundation.framework */, - C0DEBEEF0000000000000106 /* libresolv.tbd */, - C0DEBEEF0000000000000107 /* MrRSS.a */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - C0DEBEEF0000000000000040 /* MrRSS */ = { - isa = PBXNativeTarget; - buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "MrRSS" */; - buildPhases = ( - C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */, - C0DEBEEF0000000000000050 /* Sources */, - C0DEBEEF0000000000000056 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "MrRSS"; - productName = "MrRSS"; - productReference = C0DEBEEF0000000000000004 /* MrRSS.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - C0DEBEEF0000000000000060 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1500; - ORGANIZATIONNAME = "Ch3nyang"; - TargetAttributes = { - C0DEBEEF0000000000000040 = { - CreatedOnToolsVersion = 15.0; - }; - }; - }; - buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */; - compatibilityVersion = "Xcode 15.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = C0DEBEEF0000000000000010; - productRefGroup = C0DEBEEF0000000000000020 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - C0DEBEEF0000000000000040 /* MrRSS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXFrameworksBuildPhase section */ - C0DEBEEF0000000000000056 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C0DEBEEF00000000000000F7 /* MrRSS.a in Frameworks */, - C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */, - C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */, - C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */, - C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */, - C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */, - C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Prebuild: Wails Go Archive"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/MrRSS.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/MrRSS.a\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - C0DEBEEF0000000000000050 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C0DEBEEF0000000000000001 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - C0DEBEEF0000000000000090 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = main/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.mrrss.app"; - PRODUCT_NAME = "MrRSS"; - CODE_SIGNING_ALLOWED = NO; - SDKROOT = iphonesimulator; - }; - name = Debug; - }; - C0DEBEEF00000000000000A0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = main/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.mrrss.app"; - PRODUCT_NAME = "MrRSS"; - CODE_SIGNING_ALLOWED = NO; - SDKROOT = iphonesimulator; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "MrRSS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0DEBEEF0000000000000090 /* Debug */, - C0DEBEEF00000000000000A0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; - C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C0DEBEEF0000000000000090 /* Debug */, - C0DEBEEF00000000000000A0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; -/* End XCConfigurationList section */ - }; - rootObject = C0DEBEEF0000000000000060 /* Project object */; -} diff --git a/build/linux/Taskfile.yml b/build/linux/Taskfile.yml deleted file mode 100644 index ce2a09501..000000000 --- a/build/linux/Taskfile.yml +++ /dev/null @@ -1,169 +0,0 @@ -version: '3' - -includes: - common: ../Taskfile.yml - -vars: - # Signing configuration - edit these values for your project - # PGP_KEY: "path/to/signing-key.asc" - # SIGN_ROLE: "builder" # Options: origin, maint, archive, builder - # - # Password is stored securely in system keychain. Run: wails3 setup signing - - # Docker image for cross-compilation (used when building on non-Linux or no CC available) - CROSS_IMAGE: wails-cross - -tasks: - build: - summary: Builds the application for Linux - cmds: - # Linux requires CGO - use Docker when cross-compiling from non-Linux OR when no C compiler is available - - task: '{{if and (eq OS "linux") (eq .HAS_CC "true")}}build:native{{else}}build:docker{{end}}' - vars: - ARCH: '{{.ARCH}}' - DEV: '{{.DEV}}' - OUTPUT: '{{.OUTPUT}}' - vars: - DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' - OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' - # Check if a C compiler is available (gcc or clang) - HAS_CC: - sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"' - - build:native: - summary: Builds the application natively on Linux - internal: true - deps: - - task: common:go:mod:tidy - - task: common:build:frontend - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - DEV: - ref: .DEV - - task: common:generate:icons - - task: generate:dotdesktop - cmds: - - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} - vars: - BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' - DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' - OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' - env: - GOOS: linux - CGO_ENABLED: 1 - GOARCH: '{{.ARCH | default ARCH}}' - - build:docker: - summary: Cross-compiles for Linux using Docker with Zig (for macOS/Windows hosts) - internal: true - deps: - - task: common:build:frontend - - task: common:generate:icons - - task: generate:dotdesktop - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required for cross-compilation to Linux. Please install Docker." - - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 - msg: | - Docker image '{{.CROSS_IMAGE}}' not found. - Build it first: wails3 task setup:docker - cmds: - - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME={{.APP_NAME}} {{.CROSS_IMAGE}} linux {{.DOCKER_ARCH}} - - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin - - mkdir -p {{.BIN_DIR}} - - mv bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}} {{.OUTPUT}} - vars: - DOCKER_ARCH: '{{.ARCH | default "amd64"}}' - DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' - OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' - # Mount Go module cache for faster builds - GO_CACHE_MOUNT: - sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' - # Extract replace directives from go.mod and create -v mounts for each - REPLACE_MOUNTS: - sh: | - grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do - path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') - # Convert relative paths to absolute - if [ "${path#/}" = "$path" ]; then - path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" - fi - # Only mount if directory exists - if [ -d "$path" ]; then - echo "-v $path:$path:ro" - fi - done | tr '\n' ' ' - - package: - summary: Packages the application for Linux - deps: - - task: build - cmds: - - task: create:appimage - - task: create:tarball - - create:appimage: - summary: Creates an AppImage - deps: - - task: build - - task: generate:dotdesktop - cmds: - - | - if [ -f "build/linux/create-appimage.sh" ]; then - chmod +x build/linux/create-appimage.sh - ARCH={{.ARCH | default "amd64"}} ./build/linux/create-appimage.sh - else - echo "Warning: create-appimage.sh not found, skipping AppImage creation" - fi - - create:tarball: - summary: Creates a tarball package - deps: - - task: build - cmds: - - cd {{.BIN_DIR}} && tar -czf {{.APP_NAME}}-{{.VERSION}}-linux-{{.ARCH}}.tar.gz {{.APP_NAME}} - vars: - VERSION: '1.3.27' - ARCH: '{{.ARCH | default "amd64"}}' - - generate:dotdesktop: - summary: Generates a `.desktop` file - dir: build - cmds: - - mkdir -p {{.ROOT_DIR}}/build/linux - - | - cat > {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop << EOF - [Desktop Entry] - Name={{.APP_NAME}} - Comment=A modern, standalone RSS reader - Exec={{.APP_NAME}} - Icon={{.APP_NAME}} - Terminal=false - Type=Application - Categories=Network;News; - EOF - - run: - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}' - - build:server: - summary: Builds the server version using Docker - deps: - - task: common:build:frontend - cmds: - - docker build -f Dockerfile.server -t mrrss-server:latest . - - docker create --name mrrss-server-temp mrrss-server:latest - - docker cp mrrss-server-temp:/app/mrrss-server {{.BIN_DIR}}/{{.APP_NAME}}-server - - docker rm mrrss-server-temp - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required for building the server version" - - run:server: - summary: Runs the server version - deps: - - task: build:server - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}-server' diff --git a/build/linux/create-appimage.sh b/build/linux/create-appimage.sh deleted file mode 100755 index 2ff5b784d..000000000 --- a/build/linux/create-appimage.sh +++ /dev/null @@ -1,225 +0,0 @@ -#!/bin/bash -# Script to create a Linux AppImage for MrRSS -# -# Application Information: -# Name: MrRSS -# Description: A Modern, Cross-Platform Desktop RSS Reader -# Publisher: Ch3nyang -# URL: https://github.com/DevXDojo/MrRSS -# Copyright: Copyright ÂĐ Ch3nyang - -# Exit on error, but allow some commands to fail gracefully -set -e - -APP_NAME="MrRSS" -# Get version from frontend/package.json if available, otherwise use default -VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' frontend/package.json 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "1.3.24") -# Get architecture from environment variable or default to amd64 -ARCH=${ARCH:-amd64} -echo "Target architecture: ${ARCH}" -echo "System architecture: $(uname -m)" -APP_PUBLISHER="Ch3nyang" -APP_URL="https://github.com/DevXDojo/MrRSS" -APP_DESCRIPTION="A Modern, Cross-Platform Desktop RSS Reader" -BUILD_DIR="build/bin" -APPDIR="build/appimage/${APP_NAME}.AppDir" -APPIMAGE_NAME="${APP_NAME}-${VERSION}-linux-${ARCH}.AppImage" - -echo "Creating AppImage for ${APP_NAME} ${VERSION}..." -echo "Publisher: ${APP_PUBLISHER}" -echo "Description: ${APP_DESCRIPTION}" -echo "" - -# Check if binary exists -if [ ! -f "${BUILD_DIR}/${APP_NAME}" ]; then - echo "Error: Binary not found at ${BUILD_DIR}/${APP_NAME}" - echo "Please build the application first with: wails3 build -platform linux/amd64" - exit 1 -fi - -# Create AppDir structure -echo "Creating AppDir structure..." -rm -rf "build/appimage" -mkdir -p "${APPDIR}/usr/bin" -mkdir -p "${APPDIR}/usr/share/applications" -mkdir -p "${APPDIR}/usr/share/icons/hicolor/256x256/apps" - -# Copy binary -echo "Copying binary..." -cp "${BUILD_DIR}/${APP_NAME}" "${APPDIR}/usr/bin/" -chmod +x "${APPDIR}/usr/bin/${APP_NAME}" - -# Create desktop file -echo "Creating desktop file..." -cat > "${APPDIR}/usr/share/applications/${APP_NAME}.desktop" << EOF -[Desktop Entry] -Type=Application -Name=${APP_NAME} -GenericName=RSS Reader -Comment=${APP_DESCRIPTION} -Exec=${APP_NAME} -Icon=${APP_NAME} -Categories=Network;News;Feed; -Terminal=false -StartupWMClass=${APP_NAME} -Keywords=RSS;Atom;Feed;News;Reader; -X-GNOME-UsesNotifications=true -EOF - -# Create AppRun script -echo "Creating AppRun script..." -cat > "${APPDIR}/AppRun" << 'EOF' -#!/bin/bash -SELF=$(readlink -f "$0") -HERE=${SELF%/*} -export PATH="${HERE}/usr/bin:${PATH}" -export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" -exec "${HERE}/usr/bin/MrRSS" "$@" -EOF -chmod +x "${APPDIR}/AppRun" - -# Copy icon (if exists, otherwise create placeholder) -# Icon handling is non-critical - continue even if it fails -set +e -if [ -f "imgs/logo.svg" ] && (command -v inkscape &> /dev/null || command -v convert &> /dev/null); then - echo "Converting icon..." - # If inkscape is available, convert SVG to PNG - if command -v inkscape &> /dev/null; then - inkscape "imgs/logo.svg" -o "${APPDIR}/usr/share/icons/hicolor/256x256/apps/${APP_NAME}.png" -w 256 -h 256 2>/dev/null || echo "Warning: inkscape icon conversion failed" - cp "${APPDIR}/usr/share/icons/hicolor/256x256/apps/${APP_NAME}.png" "${APPDIR}/${APP_NAME}.png" 2>/dev/null || true - elif command -v convert &> /dev/null; then - convert -background none -size 256x256 "imgs/logo.svg" "${APPDIR}/usr/share/icons/hicolor/256x256/apps/${APP_NAME}.png" 2>/dev/null || echo "Warning: ImageMagick icon conversion failed" - cp "${APPDIR}/usr/share/icons/hicolor/256x256/apps/${APP_NAME}.png" "${APPDIR}/${APP_NAME}.png" 2>/dev/null || true - fi -elif [ -f "build/appicon.png" ]; then - # Fallback to pre-built PNG icon from Wails build process - echo "Using existing PNG icon..." - cp "build/appicon.png" "${APPDIR}/usr/share/icons/hicolor/256x256/apps/${APP_NAME}.png" 2>/dev/null || echo "Warning: Failed to copy icon" - cp "build/appicon.png" "${APPDIR}/${APP_NAME}.png" 2>/dev/null || true -else - echo "Warning: No icon available - AppImage will be created without an icon" -fi -set -e - -# Copy desktop file to root -cp "${APPDIR}/usr/share/applications/${APP_NAME}.desktop" "${APPDIR}/" - -# Set APPIMAGE_ARCH early for checks -APPIMAGE_ARCH="${ARCH}" -if [ "${ARCH}" = "amd64" ]; then - APPIMAGE_ARCH="x86_64" -elif [ "${ARCH}" = "arm64" ]; then - APPIMAGE_ARCH="aarch64" -fi - -# Download appimagetool if not present -APPIMAGETOOL_ARCH="x86_64" -if [ "${ARCH}" = "arm64" ]; then - APPIMAGETOOL_ARCH="aarch64" -fi -echo "Determining appimagetool architecture..." -echo "ARCH variable: ${ARCH}" -echo "APPIMAGETOOL_ARCH will be: ${APPIMAGETOOL_ARCH}" -APPIMAGETOOL="build/appimagetool-${APPIMAGETOOL_ARCH}.AppImage" - -# Clean up wrong architecture appimagetool if exists -if [ "${APPIMAGETOOL_ARCH}" = "aarch64" ] && [ -f "build/appimagetool-x86_64.AppImage" ]; then - echo "Removing x86_64 appimagetool (need aarch64)..." - rm -f build/appimagetool-x86_64.AppImage -elif [ "${APPIMAGETOOL_ARCH}" = "x86_64" ] && [ -f "build/appimagetool-aarch64.AppImage" ]; then - echo "Removing aarch64 appimagetool (need x86_64)..." - rm -f build/appimagetool-aarch64.AppImage -fi - -if [ ! -f "${APPIMAGETOOL}" ]; then - echo "Downloading appimagetool for ${APPIMAGETOOL_ARCH}..." - APPIMAGETOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${APPIMAGETOOL_ARCH}.AppImage" - echo "URL: ${APPIMAGETOOL_URL}" - if ! wget -q "${APPIMAGETOOL_URL}" -O "${APPIMAGETOOL}"; then - echo "Error: Failed to download appimagetool for ${APPIMAGETOOL_ARCH}" - echo "Please download it manually from: https://github.com/AppImage/AppImageKit/releases" - exit 1 - fi - chmod +x "${APPIMAGETOOL}" - echo "Downloaded and made executable: ${APPIMAGETOOL}" - # Verify the architecture of downloaded file - file "${APPIMAGETOOL}" || true -fi - -# Verify appimagetool is executable -if [ ! -x "${APPIMAGETOOL}" ]; then - echo "Warning: appimagetool is not executable, attempting to fix permissions..." - if ! chmod +x "${APPIMAGETOOL}"; then - echo "Error: Failed to make appimagetool executable" - echo "Please check file permissions on: ${APPIMAGETOOL}" - exit 1 - fi -fi - -# Create AppImage -echo "Creating AppImage..." -echo "Current architecture: ${ARCH}" -echo "AppImage tool architecture: ${APPIMAGETOOL_ARCH}" -echo "System architecture: $(uname -m)" -rm -f "${BUILD_DIR}/${APPIMAGE_NAME}" - -# Verify binary architecture before creating AppImage -echo "Verifying binary architecture..." -BINARY_ARCH=$(file "${APPDIR}/usr/bin/${APP_NAME}" | grep -o 'aarch64\|x86-64\|ARM aarch64' || true) -echo "Binary architecture: ${BINARY_ARCH}" -echo "Expected AppImage architecture: ${APPIMAGE_ARCH}" - -# Debug: List ALL files in AppDir with their architecture -echo "Listing all ELF files in AppDir:" -find "${APPDIR}" -type f -exec file {} \; 2>/dev/null | grep -E 'ELF|executable|shared' || echo "No ELF files found (besides binary)" - -if [ -n "${CI}" ] || ! [ -e /dev/fuse ]; then - echo "FUSE not available, using --appimage-extract-and-run mode" - echo "Running: ARCH=${APPIMAGE_ARCH} ${APPIMAGETOOL} --appimage-extract-and-run --verbose ${APPDIR} ${BUILD_DIR}/${APPIMAGE_NAME}" - if ! ARCH="${APPIMAGE_ARCH}" "${APPIMAGETOOL}" --appimage-extract-and-run --no-appstream --verbose "${APPDIR}" "${BUILD_DIR}/${APPIMAGE_NAME}" 2>&1; then - echo "Error: AppImage creation failed" - echo "This might be due to architecture mismatch or missing dependencies" - echo "Checking AppDir contents..." - find "${APPDIR}" -type f -exec file {} \; | grep -E 'ELF|shared object' || true - - # Fallback: create tar.gz instead - echo "Falling back to tar.gz archive..." - if [ -f "${APPDIR}/usr/bin/${APP_NAME}" ]; then - tar czf "${BUILD_DIR}/${APP_NAME}-${VERSION}-linux-${ARCH}.tar.gz" -C "${APPDIR}/usr/bin" "${APP_NAME}" - echo "Created fallback archive: ${BUILD_DIR}/${APP_NAME}-${VERSION}-linux-${ARCH}.tar.gz" - else - echo "Error: Binary not found at ${APPDIR}/usr/bin/${APP_NAME}" - exit 1 - fi - exit 0 - fi -else - echo "Running: ARCH=${APPIMAGE_ARCH} ${APPIMAGETOOL} --verbose ${APPDIR} ${BUILD_DIR}/${APPIMAGE_NAME}" - if ! ARCH="${APPIMAGE_ARCH}" "${APPIMAGETOOL}" --no-appstream --verbose "${APPDIR}" "${BUILD_DIR}/${APPIMAGE_NAME}" 2>&1; then - echo "Error: AppImage creation failed" - echo "Checking AppDir contents..." - find "${APPDIR}" -type f -exec file {} \; | grep -E 'ELF|shared object' || true - - # Fallback: create tar.gz instead - echo "Falling back to tar.gz archive..." - if [ -f "${APPDIR}/usr/bin/${APP_NAME}" ]; then - tar czf "${BUILD_DIR}/${APP_NAME}-${VERSION}-linux-${ARCH}.tar.gz" -C "${APPDIR}/usr/bin" "${APP_NAME}" - echo "Created fallback archive: ${BUILD_DIR}/${APP_NAME}-${VERSION}-linux-${ARCH}.tar.gz" - else - echo "Error: Binary not found at ${APPDIR}/usr/bin/${APP_NAME}" - exit 1 - fi - exit 0 - fi -fi - -# Clean up -rm -rf "build/appimage" - -echo "AppImage created successfully: ${BUILD_DIR}/${APPIMAGE_NAME}" -echo "" -echo "Installation instructions:" -echo "1. Make the AppImage executable: chmod +x ${APPIMAGE_NAME}" -echo "2. Run the AppImage: ./${APPIMAGE_NAME}" -echo "" -echo "User data will be stored in: ~/.local/share/MrRSS/" diff --git a/build/linux/desktop b/build/linux/desktop deleted file mode 100644 index 0e33659a6..000000000 --- a/build/linux/desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=MrRSS -Comment=A modern, standalone RSS reader -# The Exec line includes %u to pass the URL to the application -Exec=/usr/local/bin/MrRSS %u -Terminal=false -Type=Application -Icon=MrRSS -Categories=Utility; -StartupWMClass=MrRSS diff --git a/build/linux/nfpm/nfpm.yaml b/build/linux/nfpm/nfpm.yaml deleted file mode 100644 index 0a39500b3..000000000 --- a/build/linux/nfpm/nfpm.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# Feel free to remove those if you don't want/need to use them. -# Make sure to check the documentation at https://nfpm.goreleaser.com -# -# The lines below are called `modelines`. See `:help modeline` - -name: "MrRSS" -arch: ${GOARCH} -platform: "linux" -version: "1.3.27" -section: "default" -priority: "extra" -maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> -description: "A modern, standalone RSS reader" -vendor: "Ch3nyang" -homepage: "https://mrrss.ch3nyang.top" -license: "MIT" -release: "1" - -contents: - - src: "./bin/MrRSS" - dst: "/usr/local/bin/MrRSS" - - src: "./build/appicon.png" - dst: "/usr/share/icons/hicolor/128x128/apps/MrRSS.png" - - src: "./build/linux/MrRSS.desktop" - dst: "/usr/share/applications/MrRSS.desktop" - -# Default dependencies for Ubuntu 24.04+ with GTK4/WebKitGTK 6.0 -depends: - - libgtk-4-1 - - libwebkitgtk-6.0-4 - -# Distribution-specific overrides for different package formats and WebKit versions -overrides: - # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux - rpm: - depends: - - gtk4 - - webkitgtk6.0 - - # Arch Linux packages - archlinux: - depends: - - gtk4 - - webkitgtk-6.0 - -# scripts section to ensure desktop database is updated after install -scripts: - postinstall: "./build/linux/nfpm/scripts/postinstall.sh" - # You can also add preremove, postremove if needed - # preremove: "./build/linux/nfpm/scripts/preremove.sh" - # postremove: "./build/linux/nfpm/scripts/postremove.sh" - -# replaces: -# - foobar -# provides: -# - bar -# depends: -# - gtk4 -# - libwebkitgtk -# recommends: -# - whatever -# suggests: -# - something-else -# conflicts: -# - not-foo -# - not-bar -# changelog: "changelog.yaml" diff --git a/build/windows/Taskfile.yml b/build/windows/Taskfile.yml deleted file mode 100644 index f2f086e5a..000000000 --- a/build/windows/Taskfile.yml +++ /dev/null @@ -1,176 +0,0 @@ -version: '3' - -includes: - common: ../Taskfile.yml - -vars: - # Signing configuration - edit these values for your project - # SIGN_CERTIFICATE: "path/to/certificate.pfx" - # SIGN_THUMBPRINT: "certificate-thumbprint" # Alternative to SIGN_CERTIFICATE - # TIMESTAMP_SERVER: "http://timestamp.digicert.com" - # - # Password is stored securely in system keychain. Run: wails3 setup signing - - # Docker image for cross-compilation with CGO (used when CGO_ENABLED=1 on non-Windows) - CROSS_IMAGE: wails-cross - -tasks: - build: - summary: Builds the application for Windows - cmds: - # Auto-detect CGO: if CGO_ENABLED=1, use Docker; otherwise use native Go cross-compile - - task: '{{if and (ne OS "windows") (eq .CGO_ENABLED "1")}}build:docker{{else}}build:native{{end}}' - vars: - ARCH: '{{.ARCH}}' - DEV: '{{.DEV}}' - vars: - # Default to CGO_ENABLED=1 for Wails v3 - CGO_ENABLED: '{{.CGO_ENABLED | default "1"}}' - - build:native: - summary: Builds the application using native Go cross-compilation - internal: true - deps: - - task: common:go:mod:tidy - - task: common:build:frontend - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - DEV: - ref: .DEV - - task: common:generate:icons - cmds: - - cmd: powershell Remove-item *.syso -ErrorAction SilentlyContinue - platforms: [windows] - - cmd: rm -f *.syso - platforms: [linux, darwin] - - task: generate:syso - - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe - - cmd: powershell Remove-item *.syso - platforms: [windows] - - cmd: rm -f *.syso - platforms: [linux, darwin] - vars: - BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}' - env: - GOOS: windows - CGO_ENABLED: '{{.CGO_ENABLED | default "1"}}' - GOARCH: '{{.ARCH | default ARCH}}' - - build:docker: - summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows) - internal: true - deps: - - task: common:build:frontend - - task: common:generate:icons - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required for CGO cross-compilation. Please install Docker." - - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 - msg: | - Docker image '{{.CROSS_IMAGE}}' not found. - Build it first: wails3 task setup:docker - cmds: - - task: generate:syso - - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME={{.APP_NAME}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}} - - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin - - rm -f *.syso - vars: - DOCKER_ARCH: '{{.ARCH | default "amd64"}}' - # Mount Go module cache for faster builds - GO_CACHE_MOUNT: - sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' - # Extract replace directives from go.mod and create -v mounts for each - REPLACE_MOUNTS: - sh: | - grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do - path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') - # Convert relative paths to absolute - if [ "${path#/}" = "$path" ]; then - path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" - fi - # Only mount if directory exists - if [ -d "$path" ]; then - echo "-v $path:$path:ro" - fi - done | tr '\n' ' ' - - package: - summary: Packages the application - cmds: - - task: create:nsis:installer - - generate:syso: - summary: Generates Windows `.syso` file - dir: build - deps: - - task: generate:icon:ico - cmds: - - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso - vars: - ARCH: '{{.ARCH | default ARCH}}' - - generate:icon:ico: - summary: Generates Windows `.ico` file from `.png` - dir: build - sources: - - "windows/icon.png" - generates: - - "windows/icon.ico" - cmds: - - wails3 generate icons -input windows/icon.png -windowsfilename windows/icon.ico -macfilename= - - create:nsis:installer: - summary: Creates an NSIS installer - dir: build/windows - deps: - - task: build - cmds: - # Create the Microsoft WebView2 bootstrapper if it doesn't exist - - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows" - - | - {{if eq OS "windows"}} - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\build\bin\{{.APP_NAME}}.exe" installer.nsi - {{else}} - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/build/bin/{{.APP_NAME}}.exe" installer.nsi - {{end}} - vars: - ARCH: '{{.ARCH | default ARCH}}' - ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' - - run: - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' - - sign: - summary: Signs the Windows executable - desc: | - Signs the .exe with an Authenticode certificate. - Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file. - Password is retrieved from system keychain (run: wails3 setup signing) - deps: - - task: build - cmds: - - wails3 tool sign --input {{.BIN_DIR}}/{{.APP_NAME}}.exe {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} - preconditions: - - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' - msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" - build:server: - summary: Builds the server version using Docker - deps: - - task: common:build:frontend - cmds: - - docker build -f Dockerfile.server -t mrrss-server:latest . - - docker create --name mrrss-server-temp mrrss-server:latest - - docker cp mrrss-server-temp:/app/mrrss-server {{.BIN_DIR}}/{{.APP_NAME}}-server.exe - - docker rm mrrss-server-temp - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required for building the server version" - - run:server: - summary: Runs the server version - deps: - - task: build:server - cmds: - - '{{.BIN_DIR}}/{{.APP_NAME}}-server.exe' diff --git a/build/windows/icon.ico b/build/windows/icon.ico deleted file mode 100644 index 8668dcd66..000000000 Binary files a/build/windows/icon.ico and /dev/null differ diff --git a/build/windows/icon.png b/build/windows/icon.png deleted file mode 100644 index 3b680452a..000000000 Binary files a/build/windows/icon.png and /dev/null differ diff --git a/build/windows/info.json b/build/windows/info.json deleted file mode 100644 index 65db3c570..000000000 --- a/build/windows/info.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "fixed": { - "file_version": "1.3.24" - }, - "info": { - "0000": { - "ProductVersion": "1.3.24", - "CompanyName": "Ch3nyang", - "FileDescription": "MrRSS", - "LegalCopyright": "Copyright ÂĐ 2026", - "ProductName": "MrRSS", - "Comments": "Built with Wails" - } - } -} diff --git a/build/windows/installer.nsi b/build/windows/installer.nsi deleted file mode 100644 index 91ca4e9cf..000000000 --- a/build/windows/installer.nsi +++ /dev/null @@ -1,118 +0,0 @@ -; MrRSS NSIS Installer Script -; This script creates a Windows installer for MrRSS -; -; IMPORTANT: This script creates a Windows installer for MrRSS -; -; To build: -; makensis build/windows/installer.nsi -; -; All paths in this script are relative to the script directory. - -!define APP_NAME "MrRSS" -!define APP_VERSION "1.3.24" -!define APP_VERSION_NUMERIC "1.3.24.0" ; NSIS requires X.X.X.X format -!define APP_PUBLISHER "Ch3nyang" -!define APP_URL "https://github.com/DevXDojo/MrRSS" -!define APP_DESCRIPTION "A Modern, Cross-Platform Desktop RSS Reader" -!define APP_EXE "MrRSS.exe" - -; Include Modern UI -!include "MUI2.nsh" - -; General Settings -Name "${APP_NAME} ${APP_VERSION}" -; Output path relative to script directory -OutFile "..\bin\MrRSS-${APP_VERSION}-windows-amd64-installer.exe" -InstallDir "$PROGRAMFILES64\${APP_NAME}" -InstallDirRegKey HKLM "Software\${APP_NAME}" "Install_Dir" -RequestExecutionLevel admin - -; MUI Settings -!define MUI_ABORTWARNING -; Use custom icons from build/windows/ -!define MUI_ICON "icon.ico" -!define MUI_UNICON "icon.ico" - -; Welcome page -!insertmacro MUI_PAGE_WELCOME - -; License page -!insertmacro MUI_PAGE_LICENSE "..\..\LICENSE" - -; Directory page -!insertmacro MUI_PAGE_DIRECTORY - -; Instfiles page -!insertmacro MUI_PAGE_INSTFILES - -; Finish page -!define MUI_FINISHPAGE_RUN "$INSTDIR\${APP_EXE}" -!define MUI_FINISHPAGE_RUN_TEXT "Launch ${APP_NAME}" -!insertmacro MUI_PAGE_FINISH - -; Uninstaller pages -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES - -; Language -!insertmacro MUI_LANGUAGE "English" - -; Version Information -VIProductVersion "${APP_VERSION_NUMERIC}" -VIAddVersionKey "ProductName" "${APP_NAME}" -VIAddVersionKey "FileDescription" "${APP_DESCRIPTION}" -VIAddVersionKey "FileVersion" "${APP_VERSION}" -VIAddVersionKey "ProductVersion" "${APP_VERSION}" -VIAddVersionKey "CompanyName" "${APP_PUBLISHER}" -VIAddVersionKey "LegalCopyright" "Copyright (C) ${APP_PUBLISHER}" - -; Installer Sections -Section "MainSection" SEC01 - SetOutPath "$INSTDIR" - - ; Copy the executable - File "..\bin\${APP_EXE}" - - ; Create shortcuts - CreateDirectory "$SMPROGRAMS\${APP_NAME}" - CreateShortcut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" - CreateShortcut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" - CreateShortcut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" - - ; Write registry keys - WriteRegStr HKLM "Software\${APP_NAME}" "Install_Dir" "$INSTDIR" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayName" "${APP_NAME}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayVersion" "${APP_VERSION}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "Publisher" "${APP_PUBLISHER}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "URLInfoAbout" "${APP_URL}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayIcon" "$INSTDIR\${APP_EXE}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe" - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "NoModify" 1 - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "NoRepair" 1 - - ; Create uninstaller - WriteUninstaller "$INSTDIR\Uninstall.exe" -SectionEnd - -; Uninstaller Section -Section "Uninstall" - ; Remove files - Delete "$INSTDIR\${APP_EXE}" - Delete "$INSTDIR\Uninstall.exe" - - ; Remove shortcuts - Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" - Delete "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" - Delete "$DESKTOP\${APP_NAME}.lnk" - RMDir "$SMPROGRAMS\${APP_NAME}" - - ; Remove installation directory - RMDir "$INSTDIR" - - ; Remove registry keys - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" - DeleteRegKey HKLM "Software\${APP_NAME}" - - ; Note: User data directory is NOT removed to preserve user data - ; Data location: %APPDATA%\MrRSS -SectionEnd diff --git a/build/windows/nsis/wails_tools.nsh b/build/windows/nsis/wails_tools.nsh deleted file mode 100644 index 55998fe87..000000000 --- a/build/windows/nsis/wails_tools.nsh +++ /dev/null @@ -1,240 +0,0 @@ -# DO NOT EDIT - Generated automatically by `wails build` - -!include "x64.nsh" -!include "WinVer.nsh" -!include "FileFunc.nsh" - -!ifndef INFO_PROJECTNAME - !define INFO_PROJECTNAME "MrRSS" -!endif -!ifndef INFO_COMPANYNAME - !define INFO_COMPANYNAME "Ch3nyang" -!endif -!ifndef INFO_PRODUCTNAME - !define INFO_PRODUCTNAME "MrRSS" -!endif -!ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "1.3.24" -!endif -!ifndef INFO_COPYRIGHT - !define INFO_COPYRIGHT "Copyright ÂĐ 2026" -!endif -!ifndef PRODUCT_EXECUTABLE - !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" -!endif -!ifndef UNINST_KEY_NAME - !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" -!endif -!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" - -!ifndef REQUEST_EXECUTION_LEVEL - !define REQUEST_EXECUTION_LEVEL "admin" -!endif - -RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" - -!ifndef ARG_WAILS_AMD64_BINARY - !define ARG_WAILS_AMD64_BINARY "MrRSS.exe" -!endif - -!ifdef ARG_WAILS_AMD64_BINARY - !define SUPPORTS_AMD64 -!endif - -!ifdef ARG_WAILS_ARM64_BINARY - !define SUPPORTS_ARM64 -!endif - -!ifdef SUPPORTS_AMD64 - !ifdef SUPPORTS_ARM64 - !define ARCH "amd64_arm64" - !else - !define ARCH "amd64" - !endif -!else - !ifdef SUPPORTS_ARM64 - !define ARCH "arm64" - !else - !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" - !endif -!endif - -!macro wails.checkArchitecture - !ifndef WAILS_WIN10_REQUIRED - !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." - !endif - - !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED - !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" - !endif - - ${If} ${AtLeastWin10} - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - Goto ok - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - Goto ok - ${EndIf} - !endif - - IfSilent silentArch notSilentArch - silentArch: - SetErrorLevel 65 - Abort - notSilentArch: - MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" - Quit - ${else} - IfSilent silentWin notSilentWin - silentWin: - SetErrorLevel 64 - Abort - notSilentWin: - MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" - Quit - ${EndIf} - - ok: -!macroend - -!macro wails.files - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" - ${EndIf} - !endif -!macroend - -!macro wails.writeUninstaller - WriteUninstaller "$INSTDIR\uninstall.exe" - - SetRegView 64 - WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" - WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" - - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 - WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" -!macroend - -!macro wails.deleteUninstaller - Delete "$INSTDIR\uninstall.exe" - - SetRegView 64 - DeleteRegKey HKLM "${UNINST_KEY}" -!macroend - -!macro wails.setShellContext - ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" - SetShellVarContext all - ${else} - SetShellVarContext current - ${EndIf} -!macroend - -# Install webview2 by launching the bootstrapper -# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment -!macro wails.webview2runtime - !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT - !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" - !endif - - SetRegView 64 - # If the admin key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - - ${If} ${REQUEST_EXECUTION_LEVEL} == "user" - # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - ${EndIf} - - SetDetailsPrint both - DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" - SetDetailsPrint listonly - - InitPluginsDir - CreateDirectory "$pluginsdir\webview2bootstrapper" - SetOutPath "$pluginsdir\webview2bootstrapper" - File "MicrosoftEdgeWebview2Setup.exe" - ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - - SetDetailsPrint both - ok: -!macroend - -# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b -!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" - - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" - - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` -!macroend - -!macro APP_UNASSOCIATE EXT FILECLASS - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" - - DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` -!macroend - -!macro wails.associateFiles - ; Create file associations - -!macroend - -!macro wails.unassociateFiles - ; Delete app associations - -!macroend - -!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" -!macroend - -!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" -!macroend - -!macro wails.associateCustomProtocols - ; Create custom protocols associations - -!macroend - -!macro wails.unassociateCustomProtocols - ; Delete app custom protocol associations - -!macroend diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest deleted file mode 100644 index a58ecba46..000000000 --- a/build/windows/wails.exe.manifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - true/pm - permonitorv2,permonitor - - - - - - - - - - diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2fef9d979..23759d0d7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -15,10 +15,10 @@ MrRSS is built with a modern, modular architecture using: -- **Backend**: Go 1.27+ with Wails v3 (beta) framework -- **Frontend**: Vue 3.5+ Composition API with TypeScript +- **Backend**: Go 1.27+ serving an HTTP API +- **Client**: SwiftUI for macOS 14+, a SwiftPM package - **Database**: SQLite with pure Go implementation (`modernc.org/sqlite`) -- **Communication**: HTTP REST API (not Wails bindings) +- **Communication**: the HTTP REST API alone ### Key Design Principles @@ -26,7 +26,7 @@ MrRSS is built with a modern, modular architecture using: 2. **Performance-Optimized**: Concurrent processing, intelligent caching, WAL mode SQLite 3. **Modular Architecture**: Feature-based organization, clear separation of concerns 4. **Schema-Driven Configuration**: JSON schema-driven settings system with code generation -5. **Hybrid Communication**: HTTP API for data, Wails bindings for system integration +5. **One Channel**: everything goes over the HTTP API; system integration is native ## Backend Architecture @@ -142,122 +142,84 @@ handlers/ - `ai.go` - AI-based translation integration - `dynamic.go` - Dynamic translation service selection -## Frontend Architecture +## Client Architecture -### Component Organization +The macOS client lives in `frontend` and is a SwiftPM package with no +third-party dependencies. -Components are organized by feature in `frontend/src/components/`: +### Organisation ```plaintext -components/ -├── article/ # Article display and rendering -│ ├── ArticleList.vue -│ ├── ArticleItem.vue -│ ├── ArticleDetail.vue -│ ├── ArticleContent.vue -│ ├── ArticleDetailToolbar.vue -│ ├── ArticleToolbar.vue -│ └── parts/ # Content rendering parts -│ ├── ArticleTitle.vue -│ ├── ArticleSummary.vue -│ ├── ArticleBody.vue -│ ├── ArticleLoading.vue -│ ├── AudioPlayer.vue -│ └── VideoPlayer.vue -├── sidebar/ # Feed list sidebar -│ ├── Sidebar.vue -│ ├── SidebarFeed.vue -│ ├── SidebarCategory.vue -│ └── SidebarNavItem.vue -├── common/ # Reusable components -│ ├── Toast.vue -│ ├── ContextMenu.vue -│ └── ImageViewer.vue -└── modals/ # Modal dialogs - ├── SettingsModal.vue - ├── settings/ # Settings tabs - ├── feed/ # Feed modals - ├── filter/ # Filter modals - ├── rules/ # Rules editor - ├── discovery/ # Discovery modal - └── common/ # Common modals +frontend/Sources/ +├── MrRSSApp.swift # Scene, menu commands, settings window +├── Localization/ +│ ├── LocalizationTables.swift # The catalogue ported from the previous frontend +│ ├── ClientStrings.swift # Wording only this client needs +│ └── Localization.swift # Lookup, fallback, placeholder substitution +├── Models/ # Codable mirrors of the API payloads +│ ├── Feed.swift, Article.swift, Organization.swift, AI.swift, System.swift +│ ├── FilterFields.swift # The fields and operators saved filters can use +│ └── SettingsCatalog*.swift # Generated from the backend schema +├── Services/ +│ ├── API/ # Transport plus one extension per domain +│ ├── KeyboardShortcuts.swift # Bindings, read from the stored settings +│ └── AppDelegate.swift # Starts the bundled backend when one is packaged +├── ViewModels/ +│ ├── AppViewModel.swift # Connection, feeds, folders, selection, settings +│ ├── AppViewModel+Articles.swift # List state, article actions, AI search +│ ├── AppViewModel+Feeds.swift # Feed, tag, saved-filter and OPML actions +│ └── AppViewModel+Shortcuts.swift # What each key press does +└── Views/ + ├── Sidebar/ # An NSOutlineView, so dragging behaves natively + ├── ArticleListView.swift, ArticleRowView.swift + ├── ArticleDetailView.swift, WebView.swift, ImageGalleryView.swift + ├── FeedEditorView.swift, SavedFilterEditorView.swift, DiscoveryView.swift + ├── ArticleChatView.swift + └── Settings/ # The panes that need more than a generated list ``` -### Composables Organization +### The sidebar is an outline view -Composables provide reusable logic in `frontend/src/composables/`: +`List` cannot reproduce what source lists do while something is dragged over +them: the gap that opens between rows, the way a long list follows the pointer +past its edges, and the folder that lights up underneath. `SidebarOutline` +wraps `NSOutlineView` so all of that comes for free. -```plaintext -composables/ -├── article/ # Article-related logic -│ ├── useArticleDetail.ts -│ ├── useArticleList.ts -│ ├── useArticleContent.ts -│ └── useArticleSummary.ts -├── feed/ # Feed management -│ ├── useFeedManagement.ts -│ └── useFeedRefresh.ts -├── discovery/ # Feed discovery -│ └── useFeedDiscovery.ts -├── filter/ # Article filtering -│ └── useArticleFilter.ts -├── rules/ # Filtering rules -│ └── useRules.ts -├── ui/ # UI utilities -│ ├── useContextMenu.ts -│ ├── useKeyboardShortcuts.ts -│ └── useToast.ts -└── core/ # Core utilities - └── useSettings.ts -``` - -### State Management - -Pinia store (`frontend/src/stores/app.ts`) manages global state: - -- Articles list and selection -- Feeds and categories -- Filter states -- Theme and locale -- Refresh progress -- Unread counts - -### Multimedia Support +### Reading -Enhanced content rendering (`ArticleContent.vue` + `ArticleContent.css`): +`WebView` renders either the article text the backend supplied or the original +page, loaded live. Rendered text is stripped of scripts, frames and inline +handlers, and is typeset from the reading settings; a live page keeps its own +scripts because it needs them to display. -- **Images**: Clickable for viewer, right-click context menu, download support -- **Audio**: Full-width player with podcast container styling (`AudioPlayer.vue`) -- **Video**: Responsive player with proper aspect ratio (`VideoPlayer.vue`) -- **Iframes**: 16:9 aspect ratio for YouTube/Vimeo embeds -- **Rich Text**: Tables, blockquotes, code blocks, definition lists +### Settings -### Translation Integration +`SettingsCatalog.generated.swift` is produced from +`internal/config/settings_schema.json` by `tools/settings-swift/generate.py`, +and paired with the wording the previous frontend used. Panes that need more +than a list of controls add their own section. -Auto-translation features: +### Translations -- Title translation (on-demand) -- Content paragraph translation (inline display) -- Summary translation -- Supports Google Translate, DeepL, Baidu Translation, and AI-based translation +Every interface string is looked up by key through `t(_:)`. The language +follows the `language` setting the backend stores, so a reader who switches it +on one machine sees the same language on another. Dates are formatted with the +matching locale. ## Communication Flow ### HTTP API Pattern -Frontend uses direct HTTP fetch (not Wails bindings): +The client talks to the backend through `APIService`: -```javascript +```swift // GET request -const response = await fetch('/api/articles'); -const articles = await response.json(); +let articles: [Article] = try await get("articles", queryItems: [ + URLQueryItem(name: "filter", value: "unread") +]) // POST request -await fetch('/api/settings', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) -}); +try await sendJSONReturningData("settings", body: settings) ``` ### Backend Handler Pattern @@ -350,12 +312,12 @@ func (h *Handler) GetArticles(w http.ResponseWriter, r *http.Request) { - Prepared statement caching - Periodic VACUUM for space reclamation -### Frontend +### Client -- Virtual scrolling for large article lists -- Debounced operations (search, auto-save) -- Lazy loading of article content -- Efficient state updates with Pinia +- `LazyVStack` and list reuse for long article lists +- Paged loading, with the next page requested as the last row appears +- Thumbnails decoded at the size they are drawn and cached in memory +- Ordering applied on this Mac, so switching it does not refetch ### Concurrency @@ -544,59 +506,30 @@ CREATE INDEX idx_articles_hidden ON articles(is_hidden); - **Age-Based Cleanup**: Remove articles older than X days - **Automatic VACUUM**: Reclaim disk space -## Frontend Architecture Details +## Client Details -### Component Communication Patterns +### How state reaches the views -#### Props vs Events +`AppViewModel` is the single observable object. Views bind to it directly, and +the feature extensions beside it hold the behaviour, so no view owns state that +another view needs. -**Best Practice**: Use props for data down, events for actions up - -```vue - - - - - -``` - -#### Store Communication - -**For Cross-Component State**: Use Pinia store - -```typescript -// Access store -const store = useAppStore() - -// Read state -const articles = computed(() => store.articles) - -// Update state -store.loadArticles() -``` +For state that belongs to one screen — a sheet's draft, a search field, an +expanded disclosure — use `@State` in that view. Anything the rest of the +interface reacts to belongs on the view model. ### Multimedia Support #### Image Handling -- **Lazy Loading**: Images load as needed -- **Click to View**: Opens in image viewer -- **Context Menu**: Right-click for options -- **Download Support**: Save images locally -- **Proxy Caching**: Cached media proxy +Images in rendered articles are drawn by the web view, which keeps the original +layout. Every image the backend can extract is also available in a gallery +sheet, with a larger preview and links to open or copy the original. -#### Audio/Video Support +#### Audio and Video -- **HTML5 Players**: Native browser support -- **Responsive Sizing**: Adapts to content -- **Custom Styling**: Branded player appearance -- **Keyboard Controls**: Space to play/pause +An article carrying audio or video shows a row above the body offering it in the +system player, so playback uses the machine's own controls and output device. #### Math Rendering diff --git a/docs/BUILD_REQUIREMENTS.md b/docs/BUILD_REQUIREMENTS.md index 958a56679..e1a729481 100644 --- a/docs/BUILD_REQUIREMENTS.md +++ b/docs/BUILD_REQUIREMENTS.md @@ -1,289 +1,118 @@ # Build Requirements -This document describes the system-level dependencies required for building MrRSS on different platforms. +This branch builds the macOS client and the Go backend behind it. There is no +web frontend and no desktop shell to compile. -## Overview +## Requirements -MrRSS uses Wails v3 (alpha) framework which requires CGO (C bindings for Go): +- **macOS 14** or later +- **Xcode 15** or later, for the Swift toolchain +- **Go 1.27** or later -- **Wails v3**: For the desktop application framework with built-in system tray -- **SQLite**: Pure Go implementation (`modernc.org/sqlite`), no C dependencies - -## Important: CGO Requirement - -⚠ïļ **CRITICAL**: Wails v3 requires CGO to be enabled. You must set: - -```bash -export CGO_ENABLED=1 -``` - -Or when building: +Check what you have: ```bash -CGO_ENABLED=1 wails3 build +sw_vers -productVersion +swift --version +go version ``` -## Platform-Specific Requirements - -### Linux - -#### Development Dependencies +If `swift` is missing, install the command line tools: ```bash -sudo apt-get update -sudo apt-get install -y \ - gcc \ - pkg-config \ - libgtk-4-dev \ - libwebkitgtk-6.0-dev \ - libsoup-3.0-dev -``` - -**Dependency Breakdown**: - -- `gcc`: C compiler (required for CGO) -- `pkg-config`: Build tool for finding libraries -- `libgtk-4-dev`: GTK4 development headers (for Wails UI) -- `libwebkitgtk-6.0-dev`: WebKitGTK 6.0 development headers (for Wails webview, **required for current Wails v3**) -- `libsoup-3.0-dev`: HTTP library 3.0 (required for Wails v3) - -**Important**: Current Wails v3 requires GTK4, WebKitGTK 6.0, and libsoup 3.0. Older WebKitGTK 4.x and GTK3 packages are not sufficient. - -**Note for Linux Mint**: Also install `libxapp-dev` - -#### Runtime Dependencies - -End users running the compiled binary will need: - -- `libgtk-4-1` -- `libwebkitgtk-6.0-4` -- `libsoup-3.0-0` - -### Windows - -#### Development Dependencies - -Install via Chocolatey: - -```powershell -choco install mingw nsis -y +xcode-select --install ``` -**Dependency Breakdown**: - -- `mingw`: MinGW-w64 GCC compiler (required for CGO) -- `nsis`: Nullsoft Scriptable Install System (for creating installers) +## CGO -#### Alternative: Manual Installation +The backend uses `modernc.org/sqlite`, a pure Go implementation, so CGO is not +required. The packaging script builds the backend with `CGO_ENABLED=0` for both +architectures and merges them with `lipo`. -If not using Chocolatey: +Running the backend tests does use CGO in continuous integration, which is why +the workflow sets `CGO_ENABLED=1` there. -1. Install [MinGW-w64](https://www.mingw-w64.org/) -2. Install [NSIS](https://nsis.sourceforge.io/) (optional, for installers) -3. Add MinGW `bin` directory to PATH +## Building -#### Build Flags - -To avoid opening a console at application startup: +### The client alone ```bash -go build -ldflags "-H=windowsgui" +swift build --package-path frontend +swift test --package-path frontend ``` -Or with Wails: +### The backend alone ```bash -wails3 build -ldflags "-H=windowsgui" +go build -o bin/mrrss-server . +go test ./internal/... ``` -#### Runtime Dependencies - -Windows binaries are statically linked and don't require additional runtime dependencies. - -### macOS - -#### Development Dependencies - -Install Xcode Command Line Tools (if not already installed): +### Both, and the application bundle ```bash -xcode-select --install +make build # backend binary and client executable +make build-app VERSION=1.3.28 # signed .app bundle and universal DMG ``` -**Note**: macOS has native support for systray through AppKit, so no additional libraries are needed. - -#### Application Bundle - -macOS requires an application bundle structure: - -```plaintext -MrRSS.app/ - Contents/ - Info.plist - MacOS/ - MrRSS - Resources/ - MrRSS.icns -``` - -Wails automatically creates this structure during build. - -#### Info.plist Settings - -Add these keys for better macOS integration: - -```xml - -NSHighResolutionCapable -True - - -LSUIElement -1 -``` +The packaging script builds a universal SwiftUI executable, builds the backend +for `arm64` and `x86_64`, merges them, copies the icon and `Info.plist`, signs +the bundle and produces +`frontend/dist/MrRSS--macos.dmg`. -#### Runtime Dependencies - -macOS binaries are self-contained and don't require additional runtime dependencies. - -## Building with Wails - -### Standard Build +To build for one architecture while developing: ```bash -# Development build with hot reload -wails3 dev - -# Production build (recommended: use Task) -task build - -# Or directly with wails3 -wails3 build - -# Platform-specific build with Task -task linux:build -task windows:build -task darwin:build +MRRSS_BUILD_ARCHS=arm64 ./frontend/build-app.sh dev ``` -### Build Configuration - -Wails v3 uses `build/config.yml` for build configuration and Taskfile for platform-specific builds: - -- **Frontend**: Automatically built via `frontend/package.json` scripts -- **Backend**: CGO-enabled Go build with platform-specific flags -- **Installers**: Created via platform-specific scripts (NSIS, create-dmg.sh, create-appimage.sh) - -### Cross-Compilation - -**Note**: Cross-compilation with CGO is complex. For best results: - -- Build Linux binaries on Linux -- Build Windows binaries on Windows -- Build macOS binaries on macOS - -GitHub Actions handles this automatically using platform-specific runners. - -## GitHub Actions - -Our CI/CD pipeline automatically installs all required dependencies: - -### Test Workflow - -- Installs Linux dependencies for backend tests -- Sets `CGO_ENABLED=1` - -### Release Workflow - -- Platform-specific dependency installation -- Cross-platform builds using native runners -- Artifact creation (installers, AppImages, DMGs) - -## Troubleshooting - -### "CGO is disabled" Error - -**Solution**: Enable CGO before building: +## Running during development ```bash -export CGO_ENABLED=1 -wails3 build +./frontend/run.sh ``` -### Linux: "Package webkitgtk-6.0 was not found" +The launcher builds the backend, starts it on `http://127.0.0.1:1234`, waits for +the API to answer and then starts the client. An existing server on that address +is reused. -**Solution**: Install WebKitGTK 6.0 development headers: +To run the halves separately: ```bash -sudo apt-get install libwebkitgtk-6.0-dev +go run . -host 127.0.0.1 -port 1234 +MRRSS_API_BASE_URL=http://127.0.0.1:1234/api swift run --package-path frontend MrRSS ``` -### Linux: "Package ayatana-appindicator3-0.1 was not found" - -This error is from older versions. Wails v3 uses its own system tray implementation. - -### Linux: "Package libsoup-3.0 was not found" +## The server on other platforms -**Solution**: Install libsoup3 development headers: +The backend itself is portable. Building it for Linux needs nothing beyond Go: ```bash -sudo apt-get install libsoup-3.0-dev -``` - -### Windows: "gcc: command not found" - -**Solution**: Install MinGW: - -```powershell -choco install mingw -y +GOOS=linux GOARCH=amd64 go build -o mrrss-server . ``` -Or download from [mingw-w64.org](https://www.mingw-w64.org/) and add to PATH. - -### macOS: Missing Xcode Command Line Tools - -**Solution**: Install Xcode Command Line Tools: +Or use the Docker image: ```bash -xcode-select --install +docker build -f Dockerfile.server -t mrrss-server:latest . ``` -## Development Environment Setup +## Troubleshooting -### Quick Setup Scripts +**`swift build` cannot find the toolchain** -**Linux/macOS**: +Point `xcode-select` at a full Xcode installation: ```bash -# Install Go dependencies -go mod download - -# Install frontend dependencies -cd frontend -npm install -cd .. - -# Run development server -wails3 dev +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer ``` -**Windows (PowerShell)**: - -```powershell -# Install Go dependencies -go mod download +**The build stalls while compiling a view** -# Install frontend dependencies -cd frontend -npm install -cd .. - -# Run development server -wails3 dev -``` +SwiftUI bodies are type-checked as a whole, and a long expression can take a very +long time. Split the body into smaller computed properties. -## Related Documentation +**`codesign` fails during packaging** -- [Architecture Overview](ARCHITECTURE.md) -- [Code Patterns](CODE_PATTERNS.md) -- [Testing Guide](TESTING.md) +The script signs ad hoc, which needs no certificate. If signing still fails, +check that the bundle is not on a volume that strips extended attributes. diff --git a/docs/CODE_PATTERNS.md b/docs/CODE_PATTERNS.md index 3468f0e1a..eaf896590 100644 --- a/docs/CODE_PATTERNS.md +++ b/docs/CODE_PATTERNS.md @@ -7,7 +7,7 @@ This document provides common coding patterns and best practices for the MrRSS p - [Code Organization Guidelines](#code-organization-guidelines) - [Settings Management](#settings-management) - [Backend Patterns (Go)](#backend-patterns-go) -- [Frontend Patterns (Vue)](#frontend-patterns-vue) +- [Client Patterns (SwiftUI)](#client-patterns-swiftui) - [Styling Patterns](#styling-patterns) - [API Communication](#api-communication) @@ -18,7 +18,7 @@ This document provides common coding patterns and best practices for the MrRSS p When a file becomes too long (typically over 300-400 lines), consider refactoring: - **Go**: Extract related functions into separate files within the same package -- **Vue**: Split into smaller components or extract logic into composables +- **SwiftUI**: Split the body into computed properties, or move behaviour onto the view model - **TypeScript**: Extract utilities into separate modules ### Folder Organization @@ -31,14 +31,14 @@ When a folder contains too many files (typically over 10-15 files), create subfo ### Build Verification -Before completing any significant change, verify the build: +Before completing any significant change, verify both halves build and their +tests pass: ```bash -wails3 build +make build +make test ``` -This ensures the application can be properly packaged and distributed. - ## Settings Management **✅ UPDATED**: The settings system has been optimized with schema-driven code generation! @@ -101,14 +101,13 @@ See **[docs/SETTINGS.md](SETTINGS.md)** for: After running the generator, verify: ```bash -# Build backend -go build - -# Build frontend -cd frontend && npm run build - -# Run tests +# Backend +go build ./... go test ./internal/config + +# Client, after running python3 tools/settings-swift/generate.py +swift build --package-path frontend +swift test --package-path frontend --filter SettingsCatalogTests ``` ### Legacy Method (Deprecated) @@ -488,593 +487,150 @@ func (h *Handler) HandleGetArticles(w http.ResponseWriter, r *http.Request) { - Log errors (don't expose to client) - Use `http.Error` for error responses -## Frontend Patterns (Vue) - -### Vue Component Structure - -#### Basic Component Pattern - -```vue - - - - - -``` - -**Key Points**: - -- Use ` -``` - -**Key Points**: - -- 500ms debounce delay -- Deep watch for nested objects -- Clear timeout on unmount to prevent memory leaks -- Apply settings immediately for better UX - -### Settings Component Pattern - -**⚠ïļ CRITICAL PATTERN**: When creating settings components that receive props and emit updates, follow this pattern to avoid reactivity issues. - -#### ❌ **WRONG Pattern** (DO NOT USE) - -```vue - - - -``` - -**Problems with this approach**: - -1. `localSettings` is a shallow copy that doesn't sync when `props.settings` changes -2. User modifies localSettings → emits to parent → parent updates → **but localSettings doesn't update** -3. v-if conditions checking different data source than v-model causes UI inconsistencies -4. Closing and reopening settings shows stale values - -#### ✅ **CORRECT Pattern** (USE THIS) - -```vue - - -