diff --git a/.github/workflows/bundle-desktop-macos.yml b/.github/workflows/bundle-desktop-macos.yml deleted file mode 100644 index 8bfc5cbdc..000000000 --- a/.github/workflows/bundle-desktop-macos.yml +++ /dev/null @@ -1,79 +0,0 @@ -# Reusable workflow to bundle the Sprout desktop app for macOS (arm64). -# Called from release.yml — not triggered directly. -on: - workflow_call: - inputs: - signing: - description: 'Whether to perform signing and notarization' - required: false - default: false - type: boolean - ref: - description: 'Git ref to checkout' - required: false - type: string - default: '' - secrets: - OSX_CODESIGN_ROLE: - required: false - CODESIGN_S3_BUCKET: - required: false - -name: Reusable workflow to bundle desktop app for macOS - -jobs: - bundle-desktop-macos: - runs-on: macos-latest - name: Bundle Desktop App (macOS arm64) - timeout-minutes: 45 - permissions: - id-token: write - contents: read - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ inputs.ref != '' && inputs.ref || '' }} - - - name: Activate Hermit - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - - - name: Cache Rust artifacts - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: desktop/src-tauri - - - name: Install desktop dependencies - run: just desktop-install-ci - - - name: Build Tauri app - run: cd desktop && pnpm tauri build - env: - CMAKE_POLICY_VERSION_MINIMUM: "3.5" - - - name: Codesign and Notarize - if: ${{ inputs.signing }} - id: codesign - uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0 - with: - osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }} - codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }} - unsigned-artifact-path: desktop/src-tauri/target/release/bundle/macos/Sprout.app - artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-arm64 - - - name: Package signed app - if: ${{ inputs.signing }} - env: - SIGNED_PATH: ${{ steps.codesign.outputs.signed-artifact-path }} - run: cp "$SIGNED_PATH" Sprout-darwin-arm64.zip - - - name: Package unsigned app - if: ${{ !inputs.signing }} - run: ditto -c -k --keepParent desktop/src-tauri/target/release/bundle/macos/Sprout.app Sprout-darwin-arm64.zip - - - name: Upload artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: Sprout-darwin-arm64 - path: Sprout-darwin-arm64.zip - retention-days: 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index a4efdb844..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Canary Release - -on: - workflow_run: - workflows: ["CI"] - branches: [main] - types: [completed] - workflow_dispatch: - -concurrency: - group: release - cancel-in-progress: true - -permissions: {} - -jobs: - resolve: - if: | - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || - (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - sha: ${{ steps.resolve.outputs.sha }} - steps: - - name: Resolve commit SHA - id: resolve - env: - WF_SHA: ${{ github.event.workflow_run.head_sha }} - FALLBACK_SHA: ${{ github.sha }} - run: echo "sha=${WF_SHA:-$FALLBACK_SHA}" >> "$GITHUB_OUTPUT" - - build: - needs: resolve - permissions: - contents: read - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - target: - - x86_64-unknown-linux-musl - - aarch64-unknown-linux-musl - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ needs.resolve.outputs.sha }} - - - name: Activate Hermit - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - - - name: Cache Rust artifacts - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - key: ${{ matrix.target }} - - - name: Install cross - uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2 - with: - tool: cross@0.2.5 - - - name: Build binaries - env: - TARGET: ${{ matrix.target }} - run: | - cross build --release --target "$TARGET" \ - -p sprout-relay \ - -p sprout-acp \ - -p sprout-mcp - - - name: Package tarballs - env: - TARGET: ${{ matrix.target }} - run: | - BIN="target/${TARGET}/release" - - mkdir sprout-relay && cp "${BIN}/sprout-relay" sprout-relay/ - tar czf "sprout-relay-canary-${TARGET}.tar.gz" sprout-relay - - mkdir sprout-agent && cp "${BIN}/sprout-acp" "${BIN}/sprout-mcp-server" sprout-agent/ - tar czf "sprout-agent-canary-${TARGET}.tar.gz" sprout-agent - - - name: Upload artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: sprout-${{ matrix.target }} - path: sprout-*-canary-${{ matrix.target }}.tar.gz - retention-days: 1 - - bundle-desktop-macos: - needs: resolve - uses: ./.github/workflows/bundle-desktop-macos.yml - permissions: - id-token: write - contents: read - with: - ref: ${{ needs.resolve.outputs.sha }} - signing: true - secrets: - OSX_CODESIGN_ROLE: ${{ secrets.OSX_CODESIGN_ROLE }} - CODESIGN_S3_BUCKET: ${{ secrets.CODESIGN_S3_BUCKET }} - - release: - needs: [resolve, build, bundle-desktop-macos] - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ needs.resolve.outputs.sha }} - - - name: Download all artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - path: dist/ - merge-multiple: true - - - name: Get build timestamp - id: timestamp - run: echo "ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" - - - name: Force-move canary tag - env: - COMMIT_SHA: ${{ needs.resolve.outputs.sha }} - run: | - git tag -f canary "$COMMIT_SHA" - git push origin canary --force - - - name: Create or update canary release - uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1 - with: - tag: canary - name: "Canary (latest main)" - body: | - Pre-alpha canary build. Not for production. - - Built from commit `${{ needs.resolve.outputs.sha }}` at ${{ steps.timestamp.outputs.ts }}. - - **Relay**: `sprout-relay-canary-.tar.gz` - **Agent tooling** (ACP harness + MCP server): `sprout-agent-canary-.tar.gz` - **Desktop** (macOS arm64): `Sprout-darwin-arm64.zip` - prerelease: true - allowUpdates: true - removeArtifacts: true - artifacts: "dist/*.tar.gz,dist/*.zip" - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sprout-desktop-release.yml b/.github/workflows/sprout-desktop-release.yml deleted file mode 100644 index 3e8c94235..000000000 --- a/.github/workflows/sprout-desktop-release.yml +++ /dev/null @@ -1,164 +0,0 @@ -name: Desktop Release - -on: - push: - tags: - - 'desktop/v*' - -env: - CARGO_TERM_COLOR: always - -jobs: - release: - name: Desktop Release - runs-on: macos-latest - permissions: - id-token: write - contents: write - env: - RELEASE_VERSION: '' # set in the "Extract version from tag" step - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - - - run: corepack enable pnpm - - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: desktop/src-tauri - key: desktop-release-aarch64-apple-darwin - - - name: Extract version from tag - run: | - RELEASE_VERSION="${GITHUB_REF#refs/tags/desktop/v}" - echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_ENV" - echo "Release version: ${RELEASE_VERSION}" - - - name: Set version from tag - working-directory: desktop - run: node scripts/set-version-from-tag.mjs "$RELEASE_VERSION" - - - name: Regenerate Cargo lockfile - working-directory: desktop/src-tauri - run: cargo generate-lockfile - - - name: Validate release secrets - env: - SPROUT_RELAY_URL: ${{ secrets.SPROUT_RELAY_URL }} - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - OSX_CODESIGN_ROLE: ${{ secrets.OSX_CODESIGN_ROLE }} - CODESIGN_S3_BUCKET: ${{ secrets.CODESIGN_S3_BUCKET }} - run: | - missing=() - for name in \ - SPROUT_RELAY_URL \ - SPROUT_UPDATER_PUBLIC_KEY \ - TAURI_SIGNING_PRIVATE_KEY \ - TAURI_SIGNING_PRIVATE_KEY_PASSWORD \ - OSX_CODESIGN_ROLE \ - CODESIGN_S3_BUCKET; do - if [ -z "${!name}" ]; then - missing+=("$name") - fi - done - if [ "${#missing[@]}" -gt 0 ]; then - echo "::error::Missing required desktop release secrets: ${missing[*]}" - exit 1 - fi - - - name: Install dependencies - run: | - cd desktop && pnpm install --frozen-lockfile - cd src-tauri && cargo fetch - - - name: Build release config - working-directory: desktop - env: - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - SPROUT_UPDATER_ENDPOINT: https://github.com/${{ github.repository }}/releases/download/sprout-desktop-latest/latest.json - run: pnpm run tauri:release:config - - - name: Build Tauri app - working-directory: desktop - env: - SPROUT_RELAY_URL: ${{ secrets.SPROUT_RELAY_URL }} - CMAKE_POLICY_VERSION_MINIMUM: "3.5" - run: pnpm tauri build --config src-tauri/tauri.release.conf.json - - - name: Codesign and Notarize - id: codesign - uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0 - with: - osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }} - codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }} - unsigned-artifact-path: desktop/src-tauri/target/release/bundle/macos/Sprout.app - artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-arm64 - - - name: Replace with signed app - env: - SIGNED_PATH: ${{ steps.codesign.outputs.signed-artifact-path }} - run: | - BUNDLE_DIR="desktop/src-tauri/target/release/bundle/macos" - rm -rf "${BUNDLE_DIR}/Sprout.app" - unzip -o "$SIGNED_PATH" -d "${BUNDLE_DIR}" - - - name: Create DMG from signed app - run: | - brew install create-dmg - DMG_DIR="desktop/src-tauri/target/release/bundle/dmg" - mkdir -p "${DMG_DIR}" - rm -f "${DMG_DIR}"/*.dmg - set +e - create-dmg \ - --volname "Sprout" \ - --window-pos 200 120 \ - --window-size 600 400 \ - --icon-size 128 \ - --icon "Sprout.app" 150 200 \ - --app-drop-link 450 200 \ - --hide-extension "Sprout.app" \ - --no-internet-enable \ - "${DMG_DIR}/Sprout_${RELEASE_VERSION}_aarch64.dmg" \ - "desktop/src-tauri/target/release/bundle/macos/Sprout.app" - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 2 ]; then - exit $EXIT_CODE - fi - - - name: Create updater archive from signed app - env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - run: | - BUNDLE_DIR="desktop/src-tauri/target/release/bundle/macos" - rm -f "${BUNDLE_DIR}/Sprout.app.tar.gz" "${BUNDLE_DIR}/Sprout.app.tar.gz.sig" - tar czf "${BUNDLE_DIR}/Sprout.app.tar.gz" -C "${BUNDLE_DIR}" Sprout.app - cd desktop && pnpm tauri signer sign -k "$TAURI_SIGNING_PRIVATE_KEY" -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" "src-tauri/target/release/bundle/macos/Sprout.app.tar.gz" - - - name: Create versioned release - env: - GH_TOKEN: ${{ github.token }} - run: | - gh release create "$GITHUB_REF_NAME" \ - --repo "$GITHUB_REPOSITORY" \ - --title "Sprout Desktop v${RELEASE_VERSION}" \ - --notes "See the assets to download and install this version." - - - name: Publish updater alias - working-directory: desktop - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - VERSION: ${{ env.RELEASE_VERSION }} - run: pnpm run release:updater:publish - - - name: Publish DMG alias - working-directory: desktop - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - VERSION: ${{ env.RELEASE_VERSION }} - run: pnpm run release:dmg:publish diff --git a/desktop/RELEASING.md b/desktop/RELEASING.md deleted file mode 100644 index 25f388d0d..000000000 --- a/desktop/RELEASING.md +++ /dev/null @@ -1,175 +0,0 @@ -# Releasing the Sprout Desktop App - -This guide covers the end-to-end process for releasing the Sprout desktop app, -including secrets setup, cutting releases, and troubleshooting. - ---- - -## Prerequisites / Secrets Setup - -The following GitHub repository secrets must be configured before the first -release: - -| Secret | Description | -| ---------------------------------- | ------------------------------------------------------------------ | -| `SPROUT_RELAY_URL` | WebSocket relay URL baked into the release binary (for example `wss://...`) | -| `SPROUT_UPDATER_PUBLIC_KEY` | Tauri updater public key (generate with `pnpm tauri signer generate`) | -| `TAURI_SIGNING_PRIVATE_KEY` | Tauri updater private key | -| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for the private key | -| `OSX_CODESIGN_ROLE` | IAM role ARN for Block's Apple codesigning service | -| `CODESIGN_S3_BUCKET` | S3 bucket for codesigning artifacts | - ---- - -## Generating Tauri Updater Keys - -```bash -cd desktop -pnpm tauri signer generate -w ~/.tauri/sprout.key -``` - -This generates a keypair: - -- The **public key** goes in the `SPROUT_UPDATER_PUBLIC_KEY` secret. -- The **private key** goes in the `TAURI_SIGNING_PRIVATE_KEY` secret. - -Store the password you chose in `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. - ---- - -## Cutting a Release - -From any branch (typically `main`): - -```bash -just desktop-release 0.3.0 -``` - -Or equivalently: - -```bash -git tag desktop/v0.3.0 -git push origin desktop/v0.3.0 -``` - -That's it. CI extracts the version from the tag and writes it into -`package.json`, `tauri.conf.json`, and `Cargo.toml` at build time. The -versions checked into the repo are not used for releases — the tag is the -source of truth. - -### Verify - -Check GitHub Releases for: - -- The **versioned release** (e.g. `desktop/v0.3.0`) -- The **`sprout-desktop-latest` rolling release** (updated with every release) - ---- - -## What CI Does - -The `sprout-desktop-release.yml` workflow: - -1. **Extracts the version** from the git tag once into `RELEASE_VERSION`. -2. **Sets the version** into `package.json`, `tauri.conf.json`, and - `Cargo.toml` using `set-version-from-tag.mjs`. -3. **Regenerates `Cargo.lock`** to match the patched `Cargo.toml`. -4. **Validates** all required secrets are present. -5. **Builds** the release config with the updater public key and endpoint. -6. **Builds** the Tauri app (unsigned). -7. **Signs and notarizes** the macOS bundle via `block/apple-codesign-action`. -8. **Re-packages** the signed app into a DMG and updater archive. -9. **Signs** the updater archive with the Tauri updater key. -10. **Publishes** the updater manifest (`latest.json`) to the rolling - `sprout-desktop-latest` release. -11. **Publishes** the DMG to both the versioned and rolling releases. - ---- - -## Local Release Build (Testing) - -Local builds will not be codesigned or notarized — that only happens in CI -via `block/apple-codesign-action`. Local builds are useful for testing the -updater runtime config and DMG packaging. - -```bash -# Set updater env vars -export SPROUT_UPDATER_PUBLIC_KEY="your-public-key" -export SPROUT_UPDATER_ENDPOINT="https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json" - -# Generate release config -cd desktop -pnpm run tauri:release:config - -# Build (unsigned) — pass a version to set it before building -just desktop-release-build version=0.3.0 -``` - -You can also set the version separately without building: - -```bash -just desktop-set-version 0.3.0 -``` - ---- - -## Auto-Updates - -The app uses `tauri-plugin-updater` to check for updates. The updater endpoint -is: - -``` -https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json -``` - -This `latest.json` is updated on every release and contains the download URL -and signature for the latest version. - ---- - -## Relay URL Configuration - -The app connects to the relay via the `SPROUT_RELAY_URL` environment variable. - -- **Production releases**: The GitHub release workflow builds the app with - `SPROUT_RELAY_URL` sourced from the `SPROUT_RELAY_URL` repository secret, - which is baked into the release binary as its default relay URL. -- **Local release builds**: Export `SPROUT_RELAY_URL` before running - `just desktop-release-build` if you want a non-localhost relay URL compiled - into the app. -- **Development**: If not set, it defaults to `ws://localhost:3000`. - ---- - -## How Versioning Works - -The git tag is the single source of truth for the release version. The version -fields in `package.json`, `tauri.conf.json`, and `Cargo.toml` on `main` are -**not** used for releases — CI overwrites them at build time from the tag. - -This means the tagged commit will show a different version in its source files -than what the release actually contains. This is an accepted tradeoff — the tag -is the canonical version, the commit is just the code state at release time. -This is standard practice in ecosystems like Docker, Go, and Rust where the -tag drives the version. - ---- - -## Troubleshooting - -- **"Missing required desktop release secrets"**: Ensure all secrets listed in - [Prerequisites](#prerequisites--secrets-setup) are configured in GitHub repo - settings. - -- **Codesigning failures**: Verify `OSX_CODESIGN_ROLE` and - `CODESIGN_S3_BUCKET` are configured correctly. Check the - `block/apple-codesign-action` step logs for details. - -- **Build failures**: If versions are wrong, check that the tag follows the - format `desktop/v` (e.g. `desktop/v0.3.0`). CI extracts the version - from the tag automatically. - -- **"A public key has been found, but no private key"**: The Tauri build should - not require `TAURI_SIGNING_PRIVATE_KEY`. If you see this, the build is trying - to generate updater artifacts before the signed app bundle exists. The updater - archive is supposed to be created and signed later from the notarized app. diff --git a/desktop/package.json b/desktop/package.json index 502a8acee..dc8afc460 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -18,10 +18,7 @@ "test:e2e:smoke": "pnpm build && playwright test --project=smoke", "test:e2e:integration": "pnpm build && playwright test --project=integration", "test:e2e:report": "playwright show-report", - "tauri:build": "tauri build", - "tauri:release:config": "node scripts/build-release-config.mjs", - "release:dmg:publish": "node scripts/publish-dmg-to-github-release.mjs", - "release:updater:publish": "node scripts/publish-updater-to-github-release.mjs" + "tauri:build": "tauri build" }, "dependencies": { "@emoji-mart/data": "^1.2.1", @@ -42,7 +39,6 @@ "@tauri-apps/plugin-notification": "^2.3.3", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-process": "^2.3.1", - "@tauri-apps/plugin-updater": "^2.10.0", "@tiptap/core": "^3.22.3", "@tiptap/extension-link": "^3.22.3", "@tiptap/extension-placeholder": "^3.22.3", diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 9bedda6dd..79739d736 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -62,9 +62,6 @@ importers: '@tauri-apps/plugin-process': specifier: ^2.3.1 version: 2.3.1 - '@tauri-apps/plugin-updater': - specifier: ^2.10.0 - version: 2.10.1 '@tiptap/core': specifier: ^3.22.3 version: 3.22.3(@tiptap/pm@3.22.3) @@ -79,7 +76,7 @@ importers: version: 3.22.3 '@tiptap/react': specifier: ^3.22.3 - version: 3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.22.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tiptap/starter-kit': specifier: ^3.22.3 version: 3.22.3 @@ -1325,9 +1322,6 @@ packages: '@tauri-apps/plugin-process@2.3.1': resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==} - '@tauri-apps/plugin-updater@2.10.1': - resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==} - '@tiptap/core@3.22.3': resolution: {integrity: sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==} peerDependencies: @@ -1343,6 +1337,12 @@ packages: peerDependencies: '@tiptap/core': ^3.22.3 + '@tiptap/extension-bubble-menu@3.22.3': + resolution: {integrity: sha512-Y6zQjh0ypDg32HWgICEvmPSKjGLr39k3aDxxt/H0uQEZSfw4smT0hxUyyyjVjx68C6t6MTnwdfz0hPI5lL68vQ==} + peerDependencies: + '@tiptap/core': ^3.22.3 + '@tiptap/pm': ^3.22.3 + '@tiptap/extension-bullet-list@3.22.3': resolution: {integrity: sha512-xOmW/b1hgECIE6r3IeZvKn4VVlG3+dfTjCWE6lnnyLaqdNkNhKS1CwUmDZdYNLUS2ryIUtgz5ID1W/8A3PhbiA==} peerDependencies: @@ -1369,6 +1369,13 @@ packages: peerDependencies: '@tiptap/extensions': ^3.22.3 + '@tiptap/extension-floating-menu@3.22.3': + resolution: {integrity: sha512-0f8b4KZ3XKai8GXWseIYJGdOfQr3evtFbBo3U08zy2aYzMMXWG0zEF7qe5/oiYp2aZ95edjjITnEceviTsZkIg==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.22.3 + '@tiptap/pm': ^3.22.3 + '@tiptap/extension-gapcursor@3.22.3': resolution: {integrity: sha512-L/Px4UeQEVG/D9WIlcAOIej+4wyIBCMUSYicSR+hW68UsObe4rxVbUas1QgidQKm6DOhoT7U7D4KQHA/Gdg/7A==} peerDependencies: @@ -3639,10 +3646,6 @@ snapshots: dependencies: '@tauri-apps/api': 2.10.1 - '@tauri-apps/plugin-updater@2.10.1': - dependencies: - '@tauri-apps/api': 2.10.1 - '@tiptap/core@3.22.3(@tiptap/pm@3.22.3)': dependencies: '@tiptap/pm': 3.22.3 @@ -3655,6 +3658,13 @@ snapshots: dependencies: '@tiptap/core': 3.22.3(@tiptap/pm@3.22.3) + '@tiptap/extension-bubble-menu@3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.22.3(@tiptap/pm@3.22.3) + '@tiptap/pm': 3.22.3 + optional: true + '@tiptap/extension-bullet-list@3.22.3(@tiptap/extension-list@3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3))': dependencies: '@tiptap/extension-list': 3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3) @@ -3676,6 +3686,13 @@ snapshots: dependencies: '@tiptap/extensions': 3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3) + '@tiptap/extension-floating-menu@3.22.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.22.3(@tiptap/pm@3.22.3) + '@tiptap/pm': 3.22.3 + optional: true + '@tiptap/extension-gapcursor@3.22.3(@tiptap/extensions@3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3))': dependencies: '@tiptap/extensions': 3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3) @@ -3766,7 +3783,7 @@ snapshots: prosemirror-transform: 1.12.0 prosemirror-view: 1.41.8 - '@tiptap/react@3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@tiptap/react@3.22.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@tiptap/core': 3.22.3(@tiptap/pm@3.22.3) '@tiptap/pm': 3.22.3 @@ -3777,6 +3794,11 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) use-sync-external-store: 1.6.0(react@19.2.5) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.22.3(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3) + '@tiptap/extension-floating-menu': 3.22.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.3(@tiptap/pm@3.22.3))(@tiptap/pm@3.22.3) + transitivePeerDependencies: + - '@floating-ui/dom' '@tiptap/starter-kit@3.22.3': dependencies: diff --git a/desktop/scripts/build-release-config.mjs b/desktop/scripts/build-release-config.mjs deleted file mode 100644 index 3de486517..000000000 --- a/desktop/scripts/build-release-config.mjs +++ /dev/null @@ -1,45 +0,0 @@ -import { readFileSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const publicKey = process.env.SPROUT_UPDATER_PUBLIC_KEY; -const endpoint = process.env.SPROUT_UPDATER_ENDPOINT; - -const baseConfigPath = resolve(process.cwd(), "src-tauri/tauri.conf.json"); -const outputConfigPath = resolve( - process.cwd(), - "src-tauri/tauri.release.conf.json", -); -const baseConfig = JSON.parse(readFileSync(baseConfigPath, "utf-8")); - -const releaseConfig = { ...baseConfig }; - -releaseConfig.bundle.macOS = { - ...(releaseConfig.bundle?.macOS ?? baseConfig.bundle?.macOS ?? {}), - minimumSystemVersion: "10.15", -}; - -if (publicKey && endpoint) { - // Build-time updater artifacts are created later from the signed app bundle. - releaseConfig.plugins = { - ...(baseConfig.plugins ?? {}), - updater: { - pubkey: publicKey, - endpoints: [endpoint], - }, - }; - console.log(`Updater config enabled (${endpoint})`); -} else { - const missing = []; - if (!publicKey) missing.push("SPROUT_UPDATER_PUBLIC_KEY"); - if (!endpoint) missing.push("SPROUT_UPDATER_ENDPOINT"); - if (releaseConfig.plugins) { - delete releaseConfig.plugins.updater; - if (Object.keys(releaseConfig.plugins).length === 0) { - delete releaseConfig.plugins; - } - } - console.log(`Updater config skipped (missing: ${missing.join(", ")})`); -} - -writeFileSync(outputConfigPath, `${JSON.stringify(releaseConfig, null, 2)}\n`); -console.log(`Wrote ${outputConfigPath}`); diff --git a/desktop/scripts/publish-dmg-to-github-release.mjs b/desktop/scripts/publish-dmg-to-github-release.mjs deleted file mode 100644 index ecf2bcc11..000000000 --- a/desktop/scripts/publish-dmg-to-github-release.mjs +++ /dev/null @@ -1,187 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { join, resolve } from "node:path"; -import { cpSync, existsSync, mkdtempSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; - -const repo = process.env.GITHUB_REPOSITORY ?? "block/sprout"; -const version = requireVersionEnv(); -const versionTag = `desktop/v${version}`; -const latestTag = "sprout-desktop-latest"; -const dryRun = process.env.DRY_RUN === "true" || process.env.DRY_RUN === "1"; - -const dmgName = `Sprout_${version}_aarch64.dmg`; -const latestAssetName = "Sprout-latest-aarch64.dmg"; - -const defaultDmgDirs = [ - "src-tauri/target/aarch64-apple-darwin/release/bundle/dmg", - "src-tauri/target/release/bundle/dmg", -]; -const dmgDir = resolve( - process.cwd(), - process.env.DMG_BUNDLE_DIR ?? - defaultDmgDirs.find((dir) => - existsSync(resolve(process.cwd(), dir, dmgName)), - ) ?? - defaultDmgDirs[0], -); -const dmgPath = join(dmgDir, dmgName); - -function requireVersionEnv() { - const v = process.env.VERSION; - if (!v || !v.trim()) { - throw new Error( - "VERSION env var is required. CI sets this from the git tag; for local use, run: VERSION=x.y.z pnpm run release:dmg:publish", - ); - } - return v.trim(); -} - -function quote(arg) { - if (/^[a-zA-Z0-9._:/=-]+$/.test(arg)) return arg; - return `'${arg.replace(/'/g, `'\\''`)}'`; -} - -function runGh(args, options = {}) { - return execFileSync("gh", args, options); -} - -function releaseExists(tag) { - try { - runGh(["release", "view", tag, "--repo", repo], { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function ensureRelease(tag, { title, prerelease }) { - if (releaseExists(tag)) { - return; - } - - const args = [ - "release", - "create", - tag, - "--repo", - repo, - "--title", - title, - "--notes", - "Automated release placeholder.", - ]; - if (prerelease) { - args.push("--prerelease"); - } - - runGh(args, { stdio: "inherit" }); -} - -function readReleaseAssets(tag) { - const assetsRaw = runGh( - [ - "release", - "view", - tag, - "--repo", - repo, - "--json", - "assets", - "--jq", - ".assets[].name", - ], - { encoding: "utf-8" }, - ); - return assetsRaw - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0); -} - -function assetUrl(tag, name) { - return `https://github.com/${repo}/releases/download/${tag}/${encodeURIComponent(name)}`; -} - -function main() { - if (!existsSync(dmgPath)) { - throw new Error(`Missing DMG artifact: ${dmgPath}`); - } - - const stageDir = mkdtempSync(join(tmpdir(), "sprout-release-dmg-")); - const latestAliasPath = join(stageDir, latestAssetName); - - try { - cpSync(dmgPath, latestAliasPath); - - const uploadVersionedArgs = [ - "release", - "upload", - versionTag, - dmgPath, - "--repo", - repo, - "--clobber", - ]; - const uploadLatestArgs = [ - "release", - "upload", - latestTag, - latestAliasPath, - "--repo", - repo, - "--clobber", - ]; - - console.log(`Preparing DMG upload for ${repo}`); - console.log(`- version tag: ${versionTag}`); - console.log(`- latest tag: ${latestTag}`); - console.log(`- versioned asset: ${dmgName}`); - console.log(`- latest alias: ${latestAssetName}`); - - if (dryRun) { - console.log("DRY_RUN enabled. Skipping upload."); - console.log(`gh release view ${quote(versionTag)} --repo ${quote(repo)}`); - console.log( - `gh release view ${quote(latestTag)} --repo ${quote(repo)} || gh release create ${quote(latestTag)} --repo ${quote(repo)} --title ${quote("Sprout Desktop Latest")} --notes ${quote("Automated release placeholder.")}`, - ); - console.log(`gh ${uploadVersionedArgs.map(quote).join(" ")}`); - console.log(`gh ${uploadLatestArgs.map(quote).join(" ")}`); - console.log(`Versioned URL: ${assetUrl(versionTag, dmgName)}`); - console.log(`Channel URL: ${assetUrl(latestTag, latestAssetName)}`); - return; - } - - ensureRelease(versionTag, { - title: `Sprout v${version}`, - prerelease: false, - }); - ensureRelease(latestTag, { - title: "Sprout Desktop Latest", - prerelease: false, - }); - - runGh(uploadVersionedArgs, { stdio: "inherit" }); - runGh(uploadLatestArgs, { stdio: "inherit" }); - - const versionAssets = readReleaseAssets(versionTag); - const latestAssets = readReleaseAssets(latestTag); - if (!versionAssets.includes(dmgName)) { - throw new Error( - `Release ${versionTag} is missing versioned asset ${dmgName} after upload`, - ); - } - if (!latestAssets.includes(latestAssetName)) { - throw new Error( - `Release ${latestTag} is missing latest alias asset ${latestAssetName} after upload`, - ); - } - - console.log("GitHub DMG assets verified."); - console.log(`Versioned URL: ${assetUrl(versionTag, dmgName)}`); - console.log(`Channel URL: ${assetUrl(latestTag, latestAssetName)}`); - } finally { - rmSync(stageDir, { recursive: true, force: true }); - } -} - -main(); diff --git a/desktop/scripts/publish-updater-to-github-release.mjs b/desktop/scripts/publish-updater-to-github-release.mjs deleted file mode 100644 index ff6c7baa7..000000000 --- a/desktop/scripts/publish-updater-to-github-release.mjs +++ /dev/null @@ -1,223 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { - cpSync, - existsSync, - mkdtempSync, - readFileSync, - readdirSync, - rmSync, - writeFileSync, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { basename, join, resolve } from "node:path"; - -const repo = process.env.GITHUB_REPOSITORY ?? "block/sprout"; -const version = requireVersionEnv(); -const latestTag = "sprout-desktop-latest"; -const tauriTarget = process.env.TAURI_TARGET ?? "aarch64-apple-darwin"; -const updaterPlatform = process.env.UPDATER_PLATFORM ?? "darwin-aarch64"; -const dryRun = process.env.DRY_RUN === "true" || process.env.DRY_RUN === "1"; - -const defaultBundleDirs = [ - `src-tauri/target/${tauriTarget}/release/bundle/macos`, - "src-tauri/target/release/bundle/macos", -]; -const bundleDir = resolve( - process.cwd(), - process.env.UPDATER_BUNDLE_DIR ?? - defaultBundleDirs.find((dir) => existsSync(resolve(process.cwd(), dir))) ?? - defaultBundleDirs[0], -); -const latestPath = join(bundleDir, "latest.json"); - -function requireVersionEnv() { - const v = process.env.VERSION; - if (!v || !v.trim()) { - throw new Error( - "VERSION env var is required. CI sets this from the git tag; for local use, run: VERSION=x.y.z pnpm run release:updater:publish", - ); - } - return v.trim(); -} - -function requirePath(path) { - if (!existsSync(path)) { - throw new Error(`Missing required file: ${path}`); - } -} - -function runGh(args, options = {}) { - return execFileSync("gh", args, options); -} - -function releaseExists(tag) { - try { - runGh(["release", "view", tag, "--repo", repo], { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function ensureRelease(tag, title) { - if (releaseExists(tag)) { - return; - } - - const args = [ - "release", - "create", - tag, - "--repo", - repo, - "--title", - title, - "--notes", - "Automated release placeholder.", - ]; - - runGh(args, { stdio: "inherit" }); -} - -function readReleaseAssets(tag) { - const assetsRaw = runGh( - [ - "release", - "view", - tag, - "--repo", - repo, - "--json", - "assets", - "--jq", - ".assets[].name", - ], - { encoding: "utf-8" }, - ); - return assetsRaw - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0); -} - -function downloadUrl(name) { - return `https://github.com/${repo}/releases/download/${latestTag}/${encodeURIComponent(name)}`; -} - -function resolveArchivePath() { - const canonicalArchiveName = "Sprout.app.tar.gz"; - const canonicalPath = join(bundleDir, canonicalArchiveName); - if (existsSync(canonicalPath)) { - return canonicalPath; - } - - const candidates = readdirSync(bundleDir).filter((entry) => - entry.endsWith(".app.tar.gz"), - ); - if (candidates.length === 1) { - return join(bundleDir, candidates[0]); - } - if (candidates.length === 0) { - throw new Error( - `Could not find updater archive in ${bundleDir}. Expected ${canonicalArchiveName}.`, - ); - } - throw new Error( - `Found multiple updater archives in ${bundleDir}: ${candidates.join(", ")}. Cannot determine which to use.`, - ); -} - -function buildLatestJson(signaturePath) { - const signature = readFileSync(signaturePath, "utf-8").trim(); - return { - version, - notes: `Release v${version}.`, - pub_date: new Date().toISOString(), - platforms: { - [updaterPlatform]: { - signature, - url: "", - }, - }, - }; -} - -function main() { - const archivePath = resolveArchivePath(); - const signaturePath = `${archivePath}.sig`; - requirePath(archivePath); - requirePath(signaturePath); - - const latest = existsSync(latestPath) - ? JSON.parse(readFileSync(latestPath, "utf-8")) - : buildLatestJson(signaturePath); - latest.version = version; - latest.pub_date = new Date().toISOString(); - - const platformRecord = latest?.platforms?.[updaterPlatform]; - if (!platformRecord) { - const available = Object.keys(latest?.platforms ?? {}); - throw new Error( - `Platform "${updaterPlatform}" missing in latest.json. Available: ${available.join(", ") || "(none)"}`, - ); - } - - const archiveName = basename(archivePath); - const signatureName = basename(signaturePath); - platformRecord.signature = readFileSync(signaturePath, "utf-8").trim(); - platformRecord.url = downloadUrl(archiveName); - - const stageDir = mkdtempSync(join(tmpdir(), "sprout-github-updater-")); - const stagedLatestPath = join(stageDir, "latest.json"); - - try { - cpSync(archivePath, join(stageDir, archiveName)); - cpSync(signaturePath, join(stageDir, signatureName)); - writeFileSync(stagedLatestPath, `${JSON.stringify(latest, null, 2)}\n`); - - const uploadArgs = [ - "release", - "upload", - latestTag, - stagedLatestPath, - join(stageDir, archiveName), - join(stageDir, signatureName), - "--repo", - repo, - "--clobber", - ]; - - console.log(`Preparing updater upload for ${repo}`); - console.log(`- latest tag: ${latestTag}`); - console.log(`- updater archive: ${archiveName}`); - console.log(`- updater endpoint: ${downloadUrl("latest.json")}`); - - if (dryRun) { - console.log("DRY_RUN enabled. Skipping upload."); - console.log( - `gh release view ${latestTag} --repo ${repo} || gh release create ${latestTag} --repo ${repo} --title "Sprout Desktop Latest" --notes "Automated release placeholder."`, - ); - console.log(`gh ${uploadArgs.join(" ")}`); - return; - } - - ensureRelease(latestTag, "Sprout Desktop Latest"); - runGh(uploadArgs, { stdio: "inherit" }); - - const latestAssets = readReleaseAssets(latestTag); - for (const expected of ["latest.json", archiveName, signatureName]) { - if (!latestAssets.includes(expected)) { - throw new Error( - `Release ${latestTag} is missing ${expected} after upload`, - ); - } - } - - console.log("GitHub updater assets verified."); - console.log(`Updater endpoint: ${downloadUrl("latest.json")}`); - } finally { - rmSync(stageDir, { recursive: true, force: true }); - } -} - -main(); diff --git a/desktop/scripts/set-version-from-tag.mjs b/desktop/scripts/set-version-from-tag.mjs deleted file mode 100644 index df2c49b07..000000000 --- a/desktop/scripts/set-version-from-tag.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import { readFileSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const version = process.argv[2]; - -if (!version) { - console.error("Usage: node scripts/set-version-from-tag.mjs "); - process.exit(1); -} - -if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) { - console.error( - `Invalid version "${version}". Expected semver format (e.g. 1.2.3 or 1.2.3-beta.1)`, - ); - process.exit(1); -} - -const packageJsonPath = resolve(process.cwd(), "package.json"); -const tauriConfigPath = resolve(process.cwd(), "src-tauri/tauri.conf.json"); -const cargoTomlPath = resolve(process.cwd(), "src-tauri/Cargo.toml"); - -const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")); -packageJson.version = version; -writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`); -console.log(`Set package.json to ${version}`); - -const tauriConfig = JSON.parse(readFileSync(tauriConfigPath, "utf8")); -tauriConfig.version = version; -writeFileSync(tauriConfigPath, `${JSON.stringify(tauriConfig, null, 2)}\n`); -console.log(`Set tauri.conf.json to ${version}`); - -const cargoToml = readFileSync(cargoTomlPath, "utf8"); -const updatedCargoToml = cargoToml.replace( - /^version = ".*"$/m, - `version = "${version}"`, -); -writeFileSync(cargoTomlPath, updatedCargoToml); -console.log(`Set Cargo.toml to ${version}`); diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index f5478c3d2..6c0c3cf1e 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -2872,12 +2872,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minisign-verify" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3315,18 +3309,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-osa-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -3482,20 +3464,6 @@ dependencies = [ "ureq 3.3.0", ] -[[package]] -name = "osakit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" -dependencies = [ - "objc2", - "objc2-foundation", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.18", -] - [[package]] name = "pango" version = "0.18.3" @@ -5233,7 +5201,6 @@ dependencies = [ "tauri-plugin-notification", "tauri-plugin-opener", "tauri-plugin-process", - "tauri-plugin-updater", "tauri-plugin-websocket", "tauri-plugin-window-state", "tempfile", @@ -5244,7 +5211,7 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "zeroize", - "zip 2.4.2", + "zip", ] [[package]] @@ -5887,39 +5854,6 @@ dependencies = [ "tauri-plugin", ] -[[package]] -name = "tauri-plugin-updater" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" -dependencies = [ - "base64 0.22.1", - "dirs", - "flate2", - "futures-util", - "http", - "infer", - "log", - "minisign-verify", - "osakit", - "percent-encoding", - "reqwest 0.13.2", - "rustls", - "semver", - "serde", - "serde_json", - "tar", - "tauri", - "tauri-plugin", - "tempfile", - "thiserror 2.0.18", - "time", - "tokio", - "url", - "windows-sys 0.60.2", - "zip 4.6.1", -] - [[package]] name = "tauri-plugin-websocket" version = "2.4.2" @@ -8039,18 +7973,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "zip" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" -dependencies = [ - "arbitrary", - "crc32fast", - "indexmap 2.14.0", - "memchr", -] - [[package]] name = "zmij" version = "1.0.21" diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 8f19a87b0..929d95578 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -31,7 +31,6 @@ tauri-plugin-opener = "2" tauri-plugin-window-state = "2" tauri-plugin-websocket = "2" tauri-plugin-dialog = "2" -tauri-plugin-updater = "2" tauri-plugin-process = "2" infer = "0.19" hex = "0.4" diff --git a/desktop/src-tauri/build.rs b/desktop/src-tauri/build.rs index e148451a3..cb6618e1c 100644 --- a/desktop/src-tauri/build.rs +++ b/desktop/src-tauri/build.rs @@ -1,9 +1,6 @@ fn main() { println!("cargo:rerun-if-env-changed=SPROUT_RELAY_URL"); println!("cargo:rerun-if-env-changed=SPROUT_RELAY_HTTP"); - println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_PUBLIC_KEY"); - println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_ENDPOINT"); - println!("cargo:rustc-check-cfg=cfg(sprout_updater_enabled)"); if let Ok(relay_url) = std::env::var("SPROUT_RELAY_URL") { println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_URL={relay_url}"); @@ -13,18 +10,5 @@ fn main() { println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_HTTP={relay_http}"); } - let updater_public_key = std::env::var("SPROUT_UPDATER_PUBLIC_KEY") - .ok() - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()); - let updater_endpoint = std::env::var("SPROUT_UPDATER_ENDPOINT") - .ok() - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()); - - if updater_public_key.is_some() && updater_endpoint.is_some() { - println!("cargo:rustc-cfg=sprout_updater_enabled"); - } - tauri_build::build() } diff --git a/desktop/src-tauri/capabilities/default.json b/desktop/src-tauri/capabilities/default.json index fc4eae76c..bd0565d11 100644 --- a/desktop/src-tauri/capabilities/default.json +++ b/desktop/src-tauri/capabilities/default.json @@ -18,9 +18,6 @@ "websocket:default", "window-state:default", "dialog:default", - "updater:default", - "updater:allow-check", - "updater:allow-download-and-install", "process:allow-restart", "global-shortcut:allow-register", "global-shortcut:allow-unregister", diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 34c2c3119..61596ad2a 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -444,19 +444,6 @@ pub fn run() { .build() }); - // Only register the updater in release builds that were compiled with a - // real updater configuration. Local unsigned builds omit that config and - // should still launch for debugging. - #[cfg(sprout_updater_enabled)] - let builder = if cfg!(debug_assertions) { - builder - } else { - builder.plugin(tauri_plugin_updater::Builder::new().build()) - }; - - #[cfg(not(sprout_updater_enabled))] - let builder = builder; - let shutdown_started = Arc::new(AtomicBool::new(false)); let restore_shutdown_started = Arc::clone(&shutdown_started); let app = builder diff --git a/desktop/src/features/settings/UpdateChecker.tsx b/desktop/src/features/settings/UpdateChecker.tsx deleted file mode 100644 index b1e17d55d..000000000 --- a/desktop/src/features/settings/UpdateChecker.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { useState } from "react"; -import { check } from "@tauri-apps/plugin-updater"; -import { relaunch } from "@tauri-apps/plugin-process"; - -type UpdateStatus = - | { state: "idle" } - | { state: "checking" } - | { state: "up-to-date" } - | { state: "available"; version: string } - | { state: "downloading"; progress?: number } - | { state: "installing" } - | { state: "ready" } - | { state: "error"; message: string }; - -export function UpdateChecker() { - const [status, setStatus] = useState({ state: "idle" }); - - async function checkForUpdate() { - try { - setStatus({ state: "checking" }); - const update = await check(); - - if (update) { - setStatus({ state: "available", version: update.version }); - } else { - setStatus({ state: "up-to-date" }); - } - } catch (err) { - setStatus({ - state: "error", - message: err instanceof Error ? err.message : String(err), - }); - } - } - - async function downloadAndInstall() { - try { - setStatus({ state: "downloading" }); - const update = await check(); - if (!update) { - setStatus({ state: "up-to-date" }); - return; - } - - await update.downloadAndInstall((event) => { - if (event.event === "Started" && event.data.contentLength) { - setStatus({ state: "downloading", progress: 0 }); - } else if (event.event === "Progress") { - // Could track progress here - } else if (event.event === "Finished") { - setStatus({ state: "installing" }); - } - }); - - setStatus({ state: "ready" }); - } catch (err) { - setStatus({ - state: "error", - message: err instanceof Error ? err.message : String(err), - }); - } - } - - async function handleRelaunch() { - await relaunch(); - } - - return ( -
-

- Software Updates -

- - {status.state === "idle" && ( -
-

- Check if a new version is available. -

- -
- )} - - {status.state === "checking" && ( -

Checking for updates…

- )} - - {status.state === "up-to-date" && ( -
-

- ✓ You're on the latest version. -

- -
- )} - - {status.state === "available" && ( -
-

- Version {status.version} is - available. -

- -
- )} - - {status.state === "downloading" && ( -

Downloading update…

- )} - - {status.state === "installing" && ( -

Installing update…

- )} - - {status.state === "ready" && ( -
-

- Update installed. Restart to apply. -

- -
- )} - - {status.state === "error" && ( -
-

- Update failed: {status.message} -

- -
- )} -
- ); -} diff --git a/justfile b/justfile index 75aaef1ec..a76b59da6 100644 --- a/justfile +++ b/justfile @@ -177,32 +177,6 @@ desktop-dev: desktop-app *ARGS: just dev {{ARGS}} -# ─── Desktop Release ────────────────────────────────────────────────────────── - -# Tag and push to trigger the desktop release workflow (CI handles version bumping) -desktop-release version: - #!/usr/bin/env bash - set -euo pipefail - - git tag "desktop/v{{version}}" - git push origin "desktop/v{{version}}" - - echo "Pushed tag desktop/v{{version}} — CI will set the version, build, and publish the release." - -# Set the desktop app version (patches package.json, tauri.conf.json, Cargo.toml) -desktop-set-version version: - cd {{desktop_dir}} && node scripts/set-version-from-tag.mjs "{{version}}" - cd {{desktop_dir}}/src-tauri && cargo generate-lockfile - -# Build a local desktop release (for testing). Optionally set a version first. -desktop-release-build version="" target="aarch64-apple-darwin" *args: - #!/usr/bin/env bash - set -euo pipefail - if [ -n "{{version}}" ]; then - just desktop-set-version "{{version}}" - fi - cd {{desktop_dir}} && pnpm exec tauri build --target {{target}} --config src-tauri/tauri.release.conf.json {{args}} - # ─── Mobile ────────────────────────────────────────────────────────────────── mobile_dir := "mobile"